From 0df1901fc0376ff751a8c0db7d28251f8935ccf6 Mon Sep 17 00:00:00 2001 From: geekalaa Date: Mon, 4 Aug 2025 15:46:03 +0100 Subject: [PATCH 01/30] fix: add missing L. prefix for un_clip localization string Fixes ReferenceError when copying links from recently uploaded files. The un_clip localization string was missing the L. prefix in up2k.js. Fixes #467 --- copyparty/web/up2k.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copyparty/web/up2k.js b/copyparty/web/up2k.js index 2f588446..31ed1d6a 100644 --- a/copyparty/web/up2k.js +++ b/copyparty/web/up2k.js @@ -1595,7 +1595,7 @@ function up2k_init(subtle) { ev(e); var txt = linklist(); cliptxt(txt + '\n', function () { - toast.inf(5, un_clip.format(txt.split('\n').length)); + toast.inf(5, L.un_clip.format(txt.split('\n').length)); }); }; From c2ac57a2a8a48afcd916384102a2875f15c8b6c7 Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 4 Aug 2025 22:56:30 +0000 Subject: [PATCH 02/30] improve helptext for multi-value options (#475) --- copyparty/__main__.py | 122 +++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index d92089d0..3bb8c69e 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -993,18 +993,18 @@ def build_flags_desc(): def add_general(ap, nc, srvname): - ap2 = ap.add_argument_group('general options') - ap2.add_argument("-c", metavar="PATH", type=u, default=CFG_DEF, action="append", help="add config file") + ap2 = ap.add_argument_group("general options") + ap2.add_argument("-c", metavar="PATH", type=u, default=CFG_DEF, action="append", help="\033[34mREPEATABLE:\033[0m add config file") ap2.add_argument("-nc", metavar="NUM", type=int, default=nc, help="max num clients") ap2.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores, 0=all") - ap2.add_argument("-a", metavar="ACCT", type=u, action="append", help="add account, \033[33mUSER\033[0m:\033[33mPASS\033[0m; example [\033[32med:wark\033[0m]") - ap2.add_argument("-v", metavar="VOL", type=u, action="append", help="add volume, \033[33mSRC\033[0m:\033[33mDST\033[0m:\033[33mFLAG\033[0m; examples [\033[32m.::r\033[0m], [\033[32m/mnt/nas/music:/music:r:aed\033[0m], see --help-accounts") - ap2.add_argument("--grp", metavar="G:N,N", type=u, action="append", help="add group, \033[33mNAME\033[0m:\033[33mUSER1\033[0m,\033[33mUSER2\033[0m,\033[33m...\033[0m; example [\033[32madmins:ed,foo,bar\033[0m]") + ap2.add_argument("-a", metavar="ACCT", type=u, action="append", help="\033[34mREPEATABLE:\033[0m add account, \033[33mUSER\033[0m:\033[33mPASS\033[0m; example [\033[32med:wark\033[0m]") + ap2.add_argument("-v", metavar="VOL", type=u, action="append", help="\033[34mREPEATABLE:\033[0m add volume, \033[33mSRC\033[0m:\033[33mDST\033[0m:\033[33mFLAG\033[0m; examples [\033[32m.::r\033[0m], [\033[32m/mnt/nas/music:/music:r:aed\033[0m], see --help-accounts") + ap2.add_argument("--grp", metavar="G:N,N", type=u, action="append", help="\033[34mREPEATABLE:\033[0m add group, \033[33mNAME\033[0m:\033[33mUSER1\033[0m,\033[33mUSER2\033[0m,\033[33m...\033[0m; example [\033[32madmins:ed,foo,bar\033[0m]") ap2.add_argument("-ed", action="store_true", help="enable the ?dots url parameter / client option which allows clients to see dotfiles / hidden files (volflag=dots)") ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,xm", help="how to handle url-form POSTs; see \033[33m--help-urlform\033[0m") ap2.add_argument("--wintitle", metavar="TXT", type=u, default="cpp @ $pub", help="server terminal title, for example [\033[32m$ip-10.1.2.\033[0m] or [\033[32m$ip-]") ap2.add_argument("--name", metavar="TXT", type=u, default=srvname, help="server name (displayed topleft in browser and in mDNS)") - ap2.add_argument("--mime", metavar="EXT=MIME", type=u, action="append", help="map file \033[33mEXT\033[0mension to \033[33mMIME\033[0mtype, for example [\033[32mjpg=image/jpeg\033[0m]") + ap2.add_argument("--mime", metavar="EXT=MIME", type=u, action="append", help="\033[34mREPEATABLE:\033[0m map file \033[33mEXT\033[0mension to \033[33mMIME\033[0mtype, for example [\033[32mjpg=image/jpeg\033[0m]") ap2.add_argument("--mimes", action="store_true", help="list default mimetype mapping and exit") ap2.add_argument("--rmagic", action="store_true", help="do expensive analysis to improve accuracy of returned mimetypes; will make file-downloads, rss, and webdav slower (volflag=rmagic)") ap2.add_argument("--license", action="store_true", help="show licenses and exit") @@ -1012,7 +1012,7 @@ def add_general(ap, nc, srvname): def add_qr(ap, tty): - ap2 = ap.add_argument_group('qr options') + ap2 = ap.add_argument_group("qr options") ap2.add_argument("--qr", action="store_true", help="show http:// QR-code on startup") ap2.add_argument("--qrs", action="store_true", help="show https:// QR-code on startup") ap2.add_argument("--qrl", metavar="PATH", type=u, default="", help="location to include in the url, for example [\033[32mpriv/?pw=hunter2\033[0m]") @@ -1034,7 +1034,7 @@ def add_fs(ap): def add_share(ap): db_path = os.path.join(E.cfg, "shares.db") - ap2 = ap.add_argument_group('share-url options') + ap2 = ap.add_argument_group("share-url options") ap2.add_argument("--shr", metavar="DIR", type=u, default="", help="toplevel virtual folder for shared files/folders, for example [\033[32m/share\033[0m]") ap2.add_argument("--shr-db", metavar="FILE", type=u, default=db_path, help="database to store shares in") ap2.add_argument("--shr-adm", metavar="U,U", type=u, default="", help="comma-separated list of users allowed to view/delete any share") @@ -1043,7 +1043,7 @@ def add_share(ap): def add_upload(ap): - ap2 = ap.add_argument_group('upload options') + ap2 = ap.add_argument_group("upload options") ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads, hiding them from clients unless \033[33m-ed\033[0m") ap2.add_argument("--plain-ip", action="store_true", help="when avoiding filename collisions by appending the uploader's ip to the filename: append the plaintext ip instead of salting and hashing the ip") ap2.add_argument("--put-name", metavar="TXT", type=u, default="put-{now.6f}-{cip}.bin", help="filename for nameless uploads (when uploader doesn't provide a name); default is [\033[32mput-UNIXTIME-IP.bin\033[0m] (the \033[32m.6f\033[0m means six decimal places) (volflag=put_name)") @@ -1085,14 +1085,14 @@ def add_upload(ap): def add_network(ap): - ap2 = ap.add_argument_group('network options') - ap2.add_argument("-i", metavar="IP", type=u, default="::", help="IPs and/or unix-sockets to listen on (see \033[33m--help-bind\033[0m). Default: all IPv4 and IPv6") + ap2 = ap.add_argument_group("network options") + ap2.add_argument("-i", metavar="IP", type=u, default="::", help="IPs and/or unix-sockets to listen on (comma-separated list; see \033[33m--help-bind\033[0m). Default: all IPv4 and IPv6") ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to listen on (comma/range); ignored for unix-sockets") ap2.add_argument("--ll", action="store_true", help="include link-local IPv4/IPv6 in mDNS replies, even if the NIC has routable IPs (breaks some mDNS clients)") ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to associate clients with; [\033[32m0\033[0m]=tcp, [\033[32m1\033[0m]=origin (first x-fwd, unsafe), [\033[32m2\033[0m]=outermost-proxy, [\033[32m3\033[0m]=second-proxy, [\033[32m-1\033[0m]=closest-proxy") ap2.add_argument("--xff-hdr", metavar="NAME", type=u, default="x-forwarded-for", help="if reverse-proxied, which http header to read the client's real ip from") - ap2.add_argument("--xff-src", metavar="CIDR", type=u, default="127.0.0.0/8, ::1/128", help="comma-separated list of trusted reverse-proxy CIDRs; only accept the real-ip header (\033[33m--xff-hdr\033[0m) and IdP headers if the incoming connection is from an IP within either of these subnets. Specify [\033[32mlan\033[0m] to allow all LAN / private / non-internet IPs. Can be disabled with [\033[32many\033[0m] if you are behind cloudflare (or similar) and are using \033[32m--xff-hdr=cf-connecting-ip\033[0m (or similar)") - ap2.add_argument("--ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m; examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") + ap2.add_argument("--xff-src", metavar="CIDR", type=u, default="127.0.0.0/8, ::1/128", help="list of trusted reverse-proxy CIDRs (comma-separated); only accept the real-ip header (\033[33m--xff-hdr\033[0m) and IdP headers if the incoming connection is from an IP within either of these subnets. Specify [\033[32mlan\033[0m] to allow all LAN / private / non-internet IPs. Can be disabled with [\033[32many\033[0m] if you are behind cloudflare (or similar) and are using \033[32m--xff-hdr=cf-connecting-ip\033[0m (or similar)") + ap2.add_argument("--ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m (comma-separated); examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") ap2.add_argument("--rp-loc", metavar="PATH", type=u, default="", help="if reverse-proxying on a location instead of a dedicated domain/subdomain, provide the base location here; example: [\033[32m/foo/bar\033[0m]") if ANYWIN: ap2.add_argument("--reuseaddr", action="store_true", help="set reuseaddr on listening sockets on windows; allows rapid restart of copyparty at the expense of being able to accidentally start multiple instances") @@ -1110,7 +1110,7 @@ def add_network(ap): def add_tls(ap, cert_path): - ap2 = ap.add_argument_group('SSL/TLS options') + ap2 = ap.add_argument_group("SSL/TLS options") ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls -- force plaintext") ap2.add_argument("--https-only", action="store_true", help="disable plaintext -- force tls") ap2.add_argument("--cert", metavar="PATH", type=u, default=cert_path, help="path to file containing a concatenation of TLS key and certificate chain") @@ -1122,7 +1122,7 @@ def add_tls(ap, cert_path): def add_cert(ap, cert_path): cert_dir = os.path.dirname(cert_path) - ap2 = ap.add_argument_group('TLS certificate generator options') + ap2 = ap.add_argument_group("TLS certificate generator options") ap2.add_argument("--no-crt", action="store_true", help="disable automatic certificate creation") ap2.add_argument("--crt-ns", metavar="N,N", type=u, default="", help="comma-separated list of FQDNs (domains) to add into the certificate") ap2.add_argument("--crt-exact", action="store_true", help="do not add wildcard entries for each \033[33m--crt-ns\033[0m") @@ -1142,7 +1142,7 @@ def add_cert(ap, cert_path): def add_auth(ap): idp_db = os.path.join(E.cfg, "idp.db") ses_db = os.path.join(E.cfg, "sessions.db") - ap2 = ap.add_argument_group('IdP / identity provider / user authentication options') + ap2 = ap.add_argument_group("IdP / identity provider / user authentication options") ap2.add_argument("--idp-h-usr", metavar="HN", type=u, default="", help="bypass the copyparty authentication checks if the request-header \033[33mHN\033[0m contains a username to associate the request with (for use with authentik/oauth/...)\n\033[1;31mWARNING:\033[0m if you enable this, make sure clients are unable to specify this header themselves; must be washed away and replaced by a reverse-proxy") ap2.add_argument("--idp-h-grp", metavar="HN", type=u, default="", help="assume the request-header \033[33mHN\033[0m contains the groupname of the requesting user; can be referenced in config files for group-based access control") ap2.add_argument("--idp-h-key", metavar="HN", type=u, default="", help="optional but recommended safeguard; your reverse-proxy will insert a secret header named \033[33mHN\033[0m into all requests, and the other IdP headers will be ignored if this header is not present") @@ -1156,14 +1156,14 @@ def add_auth(ap): ap2.add_argument("--ses-db", metavar="PATH", type=u, default=ses_db, help="where to store the sessions database (if you run multiple copyparty instances, make sure they use different DBs)") ap2.add_argument("--ses-len", metavar="CHARS", type=int, default=20, help="session key length; default is 120 bits ((20//4)*4*6)") ap2.add_argument("--no-ses", action="store_true", help="disable sessions; use plaintext passwords in cookies") - ap2.add_argument("--ipu", metavar="CIDR=USR", type=u, action="append", help="users with IP matching \033[33mCIDR\033[0m are auto-authenticated as username \033[33mUSR\033[0m; example: [\033[32m172.16.24.0/24=dave]") + ap2.add_argument("--ipu", metavar="CIDR=USR", type=u, action="append", help="\033[34mREPEATABLE:\033[0m users with IP matching \033[33mCIDR\033[0m are auto-authenticated as username \033[33mUSR\033[0m; example: [\033[32m172.16.24.0/24=dave]") def add_chpw(ap): db_path = os.path.join(E.cfg, "chpw.json") - ap2 = ap.add_argument_group('user-changeable passwords options') + ap2 = ap.add_argument_group("user-changeable passwords options") ap2.add_argument("--chpw", action="store_true", help="allow users to change their own passwords") - ap2.add_argument("--chpw-no", metavar="U,U,U", type=u, action="append", help="do not allow password-changes for this comma-separated list of usernames") + ap2.add_argument("--chpw-no", metavar="U,U,U", type=u, action="append", help="\033[34mREPEATABLE:\033[0m do not allow password-changes for this comma-separated list of usernames") ap2.add_argument("--chpw-db", metavar="PATH", type=u, default=db_path, help="where to store the passwords database (if you run multiple copyparty instances, make sure they use different DBs)") ap2.add_argument("--chpw-len", metavar="N", type=int, default=8, help="minimum password length") ap2.add_argument("--chpw-v", metavar="LVL", type=int, default=2, help="verbosity of summary on config load [\033[32m0\033[0m] = nothing at all, [\033[32m1\033[0m] = number of users, [\033[32m2\033[0m] = list users with default-pw, [\033[32m3\033[0m] = list all users") @@ -1212,12 +1212,12 @@ def add_zc_ssdp(ap): def add_ftp(ap): - ap2 = ap.add_argument_group('FTP options (TCP only)') + ap2 = ap.add_argument_group("FTP options (TCP only)") ap2.add_argument("--ftp", metavar="PORT", type=int, default=0, help="enable FTP server on \033[33mPORT\033[0m, for example \033[32m3921") ap2.add_argument("--ftps", metavar="PORT", type=int, default=0, help="enable FTPS server on \033[33mPORT\033[0m, for example \033[32m3990") ap2.add_argument("--ftpv", action="store_true", help="verbose") ap2.add_argument("--ftp4", action="store_true", help="only listen on IPv4") - ap2.add_argument("--ftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m; specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") + ap2.add_argument("--ftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m (comma-separated); specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") ap2.add_argument("--ftp-no-ow", action="store_true", help="if target file exists, reject upload instead of overwrite") ap2.add_argument("--ftp-wt", metavar="SEC", type=int, default=7, help="grace period for resuming interrupted uploads (any client can write to any file last-modified more recently than \033[33mSEC\033[0m seconds ago)") ap2.add_argument("--ftp-nat", metavar="ADDR", type=u, default="", help="the NAT address to use for passive connections") @@ -1225,7 +1225,7 @@ def add_ftp(ap): def add_webdav(ap): - ap2 = ap.add_argument_group('WebDAV options') + ap2 = ap.add_argument_group("WebDAV options") ap2.add_argument("--daw", action="store_true", help="enable full write support, even if client may not be webdav. \033[1;31mWARNING:\033[0m This has side-effects -- PUT-operations will now \033[1;31mOVERWRITE\033[0m existing files, rather than inventing new filenames to avoid loss of data. You might want to instead set this as a volflag where needed. By not setting this flag, uploaded files can get written to a filename which the client does not expect (which might be okay, depending on client)") ap2.add_argument("--dav-inf", action="store_true", help="allow depth:infinite requests (recursive file listing); extremely server-heavy but required for spec compliance -- luckily few clients rely on this") ap2.add_argument("--dav-mac", action="store_true", help="disable apple-garbage filter -- allow macos to create junk files (._* and .DS_Store, .Spotlight-*, .fseventsd, .Trashes, .AppleDouble, __MACOS)") @@ -1235,7 +1235,7 @@ def add_webdav(ap): def add_tftp(ap): - ap2 = ap.add_argument_group('TFTP options (UDP only)') + ap2 = ap.add_argument_group("TFTP options (UDP only)") ap2.add_argument("--tftp", metavar="PORT", type=int, default=0, help="enable TFTP server on \033[33mPORT\033[0m, for example \033[32m69 \033[0mor \033[32m3969") ap2.add_argument("--tftp4", action="store_true", help="only listen on IPv4") ap2.add_argument("--tftpv", action="store_true", help="verbose") @@ -1243,12 +1243,12 @@ def add_tftp(ap): ap2.add_argument("--tftp-no-fast", action="store_true", help="debug: disable optimizations") ap2.add_argument("--tftp-lsf", metavar="PTN", type=u, default="\\.?(dir|ls)(\\.txt)?", help="return a directory listing if a file with this name is requested and it does not exist; defaults matches .ls, dir, .dir.txt, ls.txt, ...") ap2.add_argument("--tftp-nols", action="store_true", help="if someone tries to download a directory, return an error instead of showing its directory listing") - ap2.add_argument("--tftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m; specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") + ap2.add_argument("--tftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m (comma-separated); specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") ap2.add_argument("--tftp-pr", metavar="P-P", type=u, default="", help="the range of UDP ports to use for data transfer, for example \033[32m12000-13000") def add_smb(ap): - ap2 = ap.add_argument_group('SMB/CIFS options') + ap2 = ap.add_argument_group("SMB/CIFS options") ap2.add_argument("--smb", action="store_true", help="enable smb (read-only) -- this requires running copyparty as root on linux and macos unless \033[33m--smb-port\033[0m is set above 1024 and your OS does port-forwarding from 445 to that.\n\033[1;31mWARNING:\033[0m this protocol is DANGEROUS and buggy! Never expose to the internet!") ap2.add_argument("--smbw", action="store_true", help="enable write support (please dont)") ap2.add_argument("--smb1", action="store_true", help="disable SMBv2, only enable SMBv1 (CIFS)") @@ -1262,30 +1262,30 @@ def add_smb(ap): def add_handlers(ap): - ap2 = ap.add_argument_group('handlers (see --help-handlers)') - ap2.add_argument("--on404", metavar="PY", type=u, action="append", help="handle 404s by executing \033[33mPY\033[0m file") - ap2.add_argument("--on403", metavar="PY", type=u, action="append", help="handle 403s by executing \033[33mPY\033[0m file") + ap2 = ap.add_argument_group("handlers (see --help-handlers)") + ap2.add_argument("--on404", metavar="PY", type=u, action="append", help="\033[34mREPEATABLE:\033[0m handle 404s by executing \033[33mPY\033[0m file") + ap2.add_argument("--on403", metavar="PY", type=u, action="append", help="\033[34mREPEATABLE:\033[0m handle 403s by executing \033[33mPY\033[0m file") ap2.add_argument("--hot-handlers", action="store_true", help="recompile handlers on each request -- expensive but convenient when hacking on stuff") def add_hooks(ap): - ap2 = ap.add_argument_group('event hooks (see --help-hooks)') - ap2.add_argument("--xbu", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file upload starts") - ap2.add_argument("--xau", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file upload finishes") - ap2.add_argument("--xiu", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after all uploads finish and volume is idle") - ap2.add_argument("--xbc", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file copy") - ap2.add_argument("--xac", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file copy") - ap2.add_argument("--xbr", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file move/rename") - ap2.add_argument("--xar", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file move/rename") - ap2.add_argument("--xbd", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file delete") - ap2.add_argument("--xad", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file delete") - ap2.add_argument("--xm", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m on message") - ap2.add_argument("--xban", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m if someone gets banned (pw/404/403/url)") + ap2 = ap.add_argument_group("event hooks (see --help-hooks)") + ap2.add_argument("--xbu", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m before a file upload starts") + ap2.add_argument("--xau", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m after a file upload finishes") + ap2.add_argument("--xiu", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m after all uploads finish and volume is idle") + ap2.add_argument("--xbc", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m before a file copy") + ap2.add_argument("--xac", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m after a file copy") + ap2.add_argument("--xbr", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m before a file move/rename") + ap2.add_argument("--xar", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m after a file move/rename") + ap2.add_argument("--xbd", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m before a file delete") + ap2.add_argument("--xad", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m after a file delete") + ap2.add_argument("--xm", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m on message") + ap2.add_argument("--xban", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m if someone gets banned (pw/404/403/url)") ap2.add_argument("--hook-v", action="store_true", help="verbose hooks") def add_stats(ap): - ap2 = ap.add_argument_group('grafana/prometheus metrics endpoint') + ap2 = ap.add_argument_group("grafana/prometheus metrics endpoint") ap2.add_argument("--stats", action="store_true", help="enable openmetrics at /.cpr/metrics for admin accounts") ap2.add_argument("--nos-hdd", action="store_true", help="disable disk-space metrics (used/free space)") ap2.add_argument("--nos-vol", action="store_true", help="disable volume size metrics (num files, total bytes, vmaxb/vmaxn)") @@ -1295,7 +1295,7 @@ def add_stats(ap): def add_yolo(ap): - ap2 = ap.add_argument_group('yolo options') + ap2 = ap.add_argument_group("yolo options") ap2.add_argument("--allow-csrf", action="store_true", help="disable csrf protections; let other domains/sites impersonate you through cross-site requests") ap2.add_argument("--cookie-lax", action="store_true", help="allow cookies from other domains (if you follow a link from another website into your server, you will arrive logged-in); this reduces protection against CSRF") ap2.add_argument("--getmod", action="store_true", help="permit ?move=[...] and ?delete as GET") @@ -1303,7 +1303,7 @@ def add_yolo(ap): def add_optouts(ap): - ap2 = ap.add_argument_group('opt-outs') + ap2 = ap.add_argument_group("opt-outs") ap2.add_argument("-nw", action="store_true", help="never write anything to disk (debug/benchmark)") ap2.add_argument("--keep-qem", action="store_true", help="do not disable quick-edit-mode on windows (it is disabled to avoid accidental text selection in the terminal window, as this would pause execution)") ap2.add_argument("--no-dav", action="store_true", help="disable webdav support") @@ -1329,7 +1329,7 @@ def add_optouts(ap): def add_safety(ap): - ap2 = ap.add_argument_group('safety options') + ap2 = ap.add_argument_group("safety options") ap2.add_argument("-s", action="count", default=0, help="increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers.\n └─Alias of\033[32m --dotpart --no-thumb --no-mtag-ff --no-robots --force-js") ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --hardlink --vague-403 -nih") ap2.add_argument("-sss", action="store_true", help="further increase safety: Enable logging to disk, scan for dangerous symlinks.\n └─Alias of\033[32m -ss --no-dav --no-logues --no-readme -lo=cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz --ls=**,*,ln,p,r") @@ -1360,7 +1360,7 @@ def add_safety(ap): def add_salt(ap, fk_salt, dk_salt, ah_salt): - ap2 = ap.add_argument_group('salting options') + ap2 = ap.add_argument_group("salting options") ap2.add_argument("--ah-alg", metavar="ALG", type=u, default="none", help="account-pw hashing algorithm; one of these, best to worst: \033[32margon2 scrypt sha2 none\033[0m (each optionally followed by alg-specific comma-sep. config)") ap2.add_argument("--ah-salt", metavar="SALT", type=u, default=ah_salt, help="account-pw salt; ignored if \033[33m--ah-alg\033[0m is none (default)") ap2.add_argument("--ah-gen", metavar="PW", type=u, default="", help="generate hashed password for \033[33mPW\033[0m, or read passwords from STDIN if \033[33mPW\033[0m is [\033[32m-\033[0m]") @@ -1374,14 +1374,14 @@ def add_salt(ap, fk_salt, dk_salt, ah_salt): def add_shutdown(ap): - ap2 = ap.add_argument_group('shutdown options') + ap2 = ap.add_argument_group("shutdown options") ap2.add_argument("--ign-ebind", action="store_true", help="continue running even if it's impossible to listen on some of the requested endpoints") ap2.add_argument("--ign-ebind-all", action="store_true", help="continue running even if it's impossible to receive connections at all") ap2.add_argument("--exit", metavar="WHEN", type=u, default="", help="shutdown after \033[33mWHEN\033[0m has finished; [\033[32mcfg\033[0m] config parsing, [\033[32midx\033[0m] volscan + multimedia indexing") def add_logging(ap): - ap2 = ap.add_argument_group('logging options') + ap2 = ap.add_argument_group("logging options") ap2.add_argument("-q", action="store_true", help="quiet; disable most STDOUT messages") ap2.add_argument("-lo", metavar="PATH", type=u, default="", help="logfile, example: \033[32mcpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz\033[0m (NB: some errors may appear on STDOUT only)") ap2.add_argument("--no-ansi", action="store_true", default=not VT100, help="disable colors; same as environment-variable NO_COLOR") @@ -1399,7 +1399,7 @@ def add_logging(ap): def add_admin(ap): - ap2 = ap.add_argument_group('admin panel options') + ap2 = ap.add_argument_group("admin panel options") ap2.add_argument("--no-reload", action="store_true", help="disable ?reload=cfg (reload users/volumes/volflags from config file)") ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)") ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks)") @@ -1413,7 +1413,7 @@ def add_admin(ap): def add_thumbnail(ap): th_ram = (RAM_AVAIL or RAM_TOTAL or 9) * 0.6 th_ram = int(max(min(th_ram, 6), 0.3) * 10) / 10 - ap2 = ap.add_argument_group('thumbnail options') + ap2 = ap.add_argument_group("thumbnail options") ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails (volflag=dthumb)") ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails (volflag=dvthumb)") ap2.add_argument("--no-athumb", action="store_true", help="disable audio thumbnails (spectrograms) (volflag=dathumb)") @@ -1445,7 +1445,7 @@ def add_thumbnail(ap): def add_transcoding(ap): - ap2 = ap.add_argument_group('transcoding options') + ap2 = ap.add_argument_group("transcoding options") ap2.add_argument("--q-opus", metavar="KBPS", type=int, default=128, help="target bitrate for transcoding to opus; set 0 to disable") ap2.add_argument("--q-mp3", metavar="QUALITY", type=u, default="q2", help="target quality for transcoding to mp3, for example [\033[32m192k\033[0m] (CBR) or [\033[32mq0\033[0m] (CQ/CRF, q0=maxquality, q9=smallest); set 0 to disable") ap2.add_argument("--allow-wav", action="store_true", help="allow transcoding to wav (lossless, uncompressed)") @@ -1458,7 +1458,7 @@ def add_transcoding(ap): def add_tail(ap): - ap2 = ap.add_argument_group('tailing options (realtime streaming of a growing file)') + ap2 = ap.add_argument_group("tailing options (realtime streaming of a growing file)") ap2.add_argument("--tail-who", metavar="LVL", type=int, default=2, help="who can tail? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=authenticated-with-read-access, [\033[32m3\033[0m]=everyone-with-read-access (volflag=tail_who)") ap2.add_argument("--tail-cmax", metavar="N", type=int, default=64, help="do not allow starting a new tail if more than \033[33mN\033[0m active downloads") ap2.add_argument("--tail-tmax", metavar="SEC", type=float, default=0, help="terminate connection after \033[33mSEC\033[0m seconds; [\033[32m0\033[0m]=never (volflag=tail_tmax)") @@ -1468,7 +1468,7 @@ def add_tail(ap): def add_rss(ap): - ap2 = ap.add_argument_group('RSS options') + ap2 = ap.add_argument_group("RSS options") ap2.add_argument("--rss", action="store_true", help="enable RSS output (experimental) (volflag=rss)") ap2.add_argument("--rss-nf", metavar="HITS", type=int, default=250, help="default number of files to return (url-param 'nf')") ap2.add_argument("--rss-fext", metavar="E,E", type=u, default="", help="default list of file extensions to include (url-param 'fext'); blank=all") @@ -1477,7 +1477,7 @@ def add_rss(ap): def add_db_general(ap, hcores): noidx = APPLESAN_TXT if MACOS else "" - ap2 = ap.add_argument_group('general db options') + ap2 = ap.add_argument_group("general db options") ap2.add_argument("-e2d", action="store_true", help="enable up2k database; this enables file search, upload-undo, improves deduplication") ap2.add_argument("-e2ds", action="store_true", help="scan writable folders for new files on startup; sets \033[33m-e2d\033[0m") ap2.add_argument("-e2dsa", action="store_true", help="scans all folders on startup; sets \033[33m-e2ds\033[0m") @@ -1506,7 +1506,7 @@ def add_db_general(ap, hcores): def add_db_metadata(ap): - ap2 = ap.add_argument_group('metadata db options') + ap2 = ap.add_argument_group("metadata db options") ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing; makes it possible to search for artist/title/codec/resolution/...") ap2.add_argument("-e2ts", action="store_true", help="scan newly discovered files for metadata on startup; sets \033[33m-e2t\033[0m") ap2.add_argument("-e2tsr", action="store_true", help="delete all metadata from DB and do a full rescan; sets \033[33m-e2ts\033[0m") @@ -1516,14 +1516,14 @@ def add_db_metadata(ap): ap2.add_argument("--mtag-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for tag scanning") ap2.add_argument("--mtag-v", action="store_true", help="verbose tag scanning; print errors from mtp subprocesses and such") ap2.add_argument("--mtag-vv", action="store_true", help="debug mtp settings and mutagen/FFprobe parsers") - ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping") + ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="\033[34mREPEATABLE:\033[0m add/replace metadata mapping") ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.); either an entire replacement list, or add/remove stuff on the default-list with +foo or /bar", default=DEF_MTE) ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.); assign/add/remove same as \033[33m-mte\033[0m", default=DEF_MTH) - ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="read tag \033[33mM\033[0m using program \033[33mBIN\033[0m to parse the file") + ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="\033[34mREPEATABLE:\033[0m read tag \033[33mM\033[0m using program \033[33mBIN\033[0m to parse the file") def add_txt(ap): - ap2 = ap.add_argument_group('textfile options') + ap2 = ap.add_argument_group("textfile options") ap2.add_argument("--md-hist", metavar="TXT", type=u, default="s", help="where to store old version of markdown files; [\033[32ms\033[0m]=subfolder, [\033[32mv\033[0m]=volume-histpath, [\033[32mn\033[0m]=nope/disabled (volflag=md_hist)") ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="the textfile editor will check for serverside changes every \033[33mSEC\033[0m seconds") ap2.add_argument("-emp", action="store_true", help="enable markdown plugins -- neat but dangerous, big XSS risk") @@ -1534,7 +1534,7 @@ def add_txt(ap): def add_og(ap): - ap2 = ap.add_argument_group('og / open graph / discord-embed options') + ap2 = ap.add_argument_group("og / open graph / discord-embed options") ap2.add_argument("--og", action="store_true", help="disable hotlinking and return an html document instead; this is required by open-graph, but can also be useful on its own (volflag=og)") ap2.add_argument("--og-ua", metavar="RE", type=u, default="", help="only disable hotlinking / engage OG behavior if the useragent matches regex \033[33mRE\033[0m (volflag=og_ua)") ap2.add_argument("--og-tpl", metavar="PATH", type=u, default="", help="do not return the regular copyparty html, but instead load the jinja2 template at \033[33mPATH\033[0m (if path contains 'EXT' then EXT will be replaced with the requested file's extension) (volflag=og_tpl)") @@ -1552,7 +1552,7 @@ def add_og(ap): def add_ui(ap, retry): - ap2 = ap.add_argument_group('ui options') + ap2 = ap.add_argument_group("ui options") ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)") ap2.add_argument("--gsel", action="store_true", help="select files in grid by ctrl-click (volflag=gsel)") ap2.add_argument("--localtime", action="store_true", help="default to local timezone instead of UTC") @@ -1567,7 +1567,7 @@ def add_ui(ap, retry): ap2.add_argument("--qdel", metavar="LVL", type=int, default=2, help="number of confirmations to show when deleting files (2/1/0)") ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files/folders matching \033[33mREGEX\033[0m in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)") ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable") - ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="use thumbnail-image \033[33mVP\033[0m for file-extension \033[33mE\033[0m, example: [\033[32mexe=/.res/exe.png\033[0m] (volflag=ext_th)") + ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="\033[34mREPEATABLE:\033[0m use thumbnail-image \033[33mVP\033[0m for file-extension \033[33mE\033[0m, example: [\033[32mexe=/.res/exe.png\033[0m] (volflag=ext_th)") ap2.add_argument("--mpmc", type=u, default="", help=argparse.SUPPRESS) ap2.add_argument("--spinner", metavar="TXT", type=u, default="🌲", help="\033[33memoji\033[0m or \033[33memoji,css\033[0m Example: [\033[32m🥖,padding:0\033[0m]") ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include in the filebrowser html") @@ -1593,7 +1593,7 @@ def add_ui(ap, retry): def add_debug(ap): - ap2 = ap.add_argument_group('debug options') + ap2 = ap.add_argument_group("debug options") ap2.add_argument("--vc", action="store_true", help="verbose config file parser (explain config)") ap2.add_argument("--cgen", action="store_true", help="generate config file from current config (best-effort; probably buggy)") ap2.add_argument("--deps", action="store_true", help="list information about detected optional dependencies") From b664ebb01f28094749e233ac70795ccd11fed253 Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 4 Aug 2025 22:56:37 +0000 Subject: [PATCH 03/30] add chungus.conf (#475) --- docs/chungus.conf | 2138 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2138 insertions(+) create mode 100644 docs/chungus.conf diff --git a/docs/chungus.conf b/docs/chungus.conf new file mode 100644 index 00000000..7ac7ba9a --- /dev/null +++ b/docs/chungus.conf @@ -0,0 +1,2138 @@ +# not actually YAML but lets pretend: +# -*- mode: yaml -*- +# vim: ft=yaml: + + +## this config-file does not make any sense at all, and will not work anywhere +## +## it is a nearly-complete example of every config option and how each can be used, +## but will NOT be maintained, it's just to get a feel for how the config works +## +## this file was initially generated by and based on: +## cat copyparty/__main__.py | awk -F\" -vp1=$(printf %090d 0) '/arse.SUPPRE/{next}/add_argument_group/{printf"\n ###%s\\\n ###// %s \\\\%s\n",p1,$2,p1};{m=""}/metavar=/{m=": "$4}/add_argument\(/{h=$0;sub(/.*, help="/,"",h);sub(/"\)$/,"",h);k=$2;sub(/^-+/,"",k);printf"\n # %s\n %s%s\n",h,k,m}' | sed -r 's/^( ###\/\/ .{88})0+/\1\\/;s/([^#]{40}) +/\1/;s/\\033\[[^m]*[0-9]m//g;s/ \(volflag=([^)]+)\)$/\n # 📂 also available as volflag "\1"/' | xsel -ib +## grep -A9001 ^flagcats copyparty/cfg.py | grep -B9001 ^flagdescs | awk -F\" -vp1=$(printf %090d 0) '/^ "/{printf"\n ###%s\\\n ###// %s \\\\%s\n",p1,$2,p1};/^ [^ ]/{k=$0;h=$0;sub(/^ +./,"",k);sub(/.: .*/,"",k);sub(/[^:]+: ./,"",h);sub(/.,$/,"",h);sub(/=/,": ",k);printf"\n # %s\n %s\n",h,k}' | sed -r 's/^( ###\/\/ .{88})0+/\1\\/;s/([^#]{40}) +/\1/' | xsel -ib + + +[global] + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// general options \\0000000000000000000000000000000000000000000000000000000000000000000000\ + + # REPEATABLE: add a config file; add multiple by repeating the option + c: /etc/some.conf + c: /etc/another.conf + + # max num clients; will not accept more http/https connections at this point + nc: 1024 + + # max num cpu cores, 0=all, 1=default=recommended + j: 4 + + # enable the ?dots url parameter / client option which allows clients to see dotfiles / hidden files + # 📂 also available as volflag "dots" + ed + + # how to handle url-form POSTs; see --help-urlform + urlform: save,get + + # server terminal title, for example [$ip-10.1.2.] or [$ip-] + wintitle: $ip-10.1.2. + + # server name (displayed topleft in browser and in mDNS) + name: mogra + + # REPEATABLE: map file EXTension to MIMEtype, for example [jpg=image/jpeg] + mime: qoi=image/x-qoi + mime: adf=application/x-amiga-disk-format + + # list default mimetype mapping and exit + mimes + + # do expensive analysis to improve accuracy of returned mimetypes; will make file-downloads, rss, and webdav slower + # 📂 also available as volflag "rmagic" + rmagic + + # show licenses and exit + license + + # show versions and exit + version + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// qr options \\000000000000000000000000000000000000000000000000000000000000000000000000000\ + + # show http:// QR-code on startup + qr + + # show https:// QR-code on startup + qrs + + # location to include in the url, for example [priv/?pw=hunter2] + qrl: uploads/?pw=okletsgo # hint + + # select IP which starts with PREFIX; [.] to force default IP when mDNS URL would have been used instead + qri: 192.168. # hint + + # foreground; try [0] if the qr-code is unreadable + qr-fg: 46 # hint; default=0=black + + # background (white=255) + qr-bg: 92 # hint; default=229=parchment + + # padding (spec says 4 or more, but 1 is usually fine) + qrp: 2 # hint + + # [1]=1x, [2]=2x, [0]=auto (try [2] on broken fonts) + qrz: 2 # hint + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// filesystem options \\0000000000000000000000000000000000000000000000000000000000000000000\ + + # if a file cannot be deleted because it is busy, continue trying for T seconds, retry every R seconds; disable with 0/0 + # 📂 also available as volflag "rm_retry" + rm-retry: 15/0.1 # default on windows; 0 on unix + + # if a file cannot be renamed because it is busy, continue trying for T seconds, retry every R seconds; disable with 0/0 + # 📂 also available as volflag "mv_retry" + mv-retry: 15/0.1 # default on windows; 0 on unix + + # file I/O buffer-size; if your volumes are on a network drive, try increasing to 524288 or even 4194304 (and let me know if that improves your performance) + iobuf: 262144 # default + + # rebuild mountpoint cache every SEC to keep track of sparse-files support; keep low on servers with removable media + mtab-age: 60 # default + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// share-url options \\00000000000000000000000000000000000000000000000000000000000000000000\ + + # toplevel virtual folder for shared files/folders, for example [/share] + shr: /shares # hint; default is unset + + # database to store shares in + shr-db: $XDG_CONFIG_PATH/.copyparty/shares.db # default (and yes, $ENV will expand in configs) + + # comma-separated list of users allowed to view/delete any share + shr-adm: ame,same # hint + + # shares can be revived by their owner if they expired less than MIN minutes ago; [60]=hour, [1440]=day, [10080]=week + shr-rt: 1440 # default + + # debug + shr-v + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// upload options \\00000000000000000000000000000000000000000000000000000000000000000000000\ + + # dotfile incomplete uploads, hiding them from clients unless -ed + dotpart + + # when avoiding filename collisions by appending the uploader's ip to the filename: append the plaintext ip instead of salting and hashing the ip + plain-ip + + # filename for nameless uploads (when uploader doesn't provide a name); default is [put-UNIXTIME-IP.bin] (the .6f means six decimal places) + # 📂 also available as volflag "put_name" + put-name: put-{now.6f}-{cip}.bin # default + + # default checksum-hasher for PUT/WebDAV uploads: no / md5 / sha1 / sha256 / sha512 / b2 / blake2 / b2s / blake2s + # 📂 also available as volflag "put_ck" + put-ck: sha512 # default + + # default checksum-hasher for bup/basic-uploader: no / md5 / sha1 / sha256 / sha512 / b2 / blake2 / b2s / blake2s + # 📂 also available as volflag "bup_ck" + bup-ck: sha512 # default + + # grace period where uploads can be deleted by the uploader, even without delete permissions; 0=disabled, default=12h + unpost: 43200 # default (12h) + + # clients can abort incomplete uploads by using the unpost tab (requires -e2d). [0] = never allowed (disable feature), [1] = allow if client has the same IP as the upload AND is using the same account, [2] = just check the IP, [3] = just check account-name + # 📂 also available as volflag "u2abort" + u2abort: 1 # default + + # file write grace period (any client can write to a blank file last-modified more recently than SEC seconds ago) + blank-wt: 300 # default + + # max number of uploads to keep in memory when running without -e2d; roughly 1 MiB RAM per 600 + reg-cap: 38400 # default + + # disable file-handle pooling -- instead, repeatedly close and reopen files during upload (bad idea to enable this on windows and/or cow filesystems) + no-fpool + + # force file-handle pooling, even when it might be dangerous (multiprocessing, filesystems lacking sparse-files support, ...) + use-fpool + + # unix file permissions to use when creating files; default is probably 644 (OS-decided), see --help-chmod. Examples: [644] = owner-RW + all-R, [755] = owner-RWX + all-RX, [777] = full-yolo + # 📂 also available as volflag "chmod_f" + chmod-f: 644 # hint; default is unset + + # unix file permissions to use when creating directories; see --help-chmod. Examples: [755] = owner-RW + all-R, [777] = full-yolo + # 📂 also available as volflag "chmod_d" + chmod-d: 755 # default + + # unix user-id to chown new files/folders to; default = -1 = do-not-change + # 📂 also available as volflag "uid" + uid: 1000 # hint + + # unix group-id to chown new files/folders to; default = -1 = do-not-change + # 📂 also available as volflag "gid" + gid: 573 # hint + + # enable symlink-based upload deduplication + # 📂 also available as volflag "dedup" + dedup + + # how careful to be when deduplicating files; [1] = just verify the filesize, [50] = verify file contents have not been altered + # 📂 also available as volflag "safededup" + safe-dedup: 50 # default + + # enable hardlink-based dedup; will fallback on symlinks when that is impossible (across filesystems) + # 📂 also available as volflag "hardlink" + hardlink + + # do not fallback to symlinks when a hardlink cannot be made + # 📂 also available as volflag "hardlinkonly" + hardlink-only + + # enable reflink-based dedup; will fallback on full copies when that is impossible (non-CoW filesystem) + # 📂 also available as volflag "reflink" + reflink + + # reject duplicate files during upload; only matches within the same volume + # 📂 also available as volflag "nodupe" + no-dupe + + # do not use existing data on disk to satisfy dupe uploads; reduces server HDD reads in exchange for much more network load + # 📂 also available as volflag "noclone" + no-clone + + # disable snapshots -- forget unfinished uploads on shutdown; don't create .hist/up2k.snap files -- abandoned/interrupted uploads must be cleaned up manually + no-snap + + # write upload state to ./hist/up2k.snap every SEC seconds; allows resuming incomplete uploads after a server crash + snap-wri: 300 # default + + # forget unfinished uploads after MIN minutes; impossible to resume them after that (360=6h, 1440=24h) + snap-drop: 1440 # default + + # how to timestamp uploaded files; [c]=client-last-modified, [u]=upload-time, [fc]=force-c, [fu]=force-u + # 📂 also available as volflag "u2ts" + u2ts: c # default + + # force randomized filenames, --nrand chars long + # 📂 also available as volflag "rand" + rand + + # randomized filenames length + # 📂 also available as volflag "nrand" + nrand: 9 # default + + # enable filetype detection on nameless uploads + # 📂 also available as volflag "magic" + magic + + # ensure GiB free disk space by rejecting upload requests; assumes gigabytes unless a unit suffix is given: [256m], [4], [2T] + # 📂 also available as volflag "df" + df: 4 # hint; default=0 + + # windows-only: minimum size of incoming uploads through up2k before they are made into sparse files + sparse: 4 # default + + # configure turbo-mode in up2k client; [-1] = forbidden/always-off, [0] = default-off and warn if enabled, [1] = default-off, [2] = on, [3] = on and disable datecheck + turbo: 0 # default + + # when to use a wasm-hasher instead of the browser's builtin; faster on chrome, but buggy in older chrome versions. [0] = only when necessary (non-https), [1] = always (all browsers), [2] = always on chrome/firefox, [3] = always on chrome, [N] = chrome-version N and newer (recommendation: 137) + nosubtle: 0 # default + + # web-client: number of file chunks to upload in parallel; 1 or 2 is good when latency is low (same-country), 2~4 for android-clients, 2~6 for cross-atlantic. Max is 6 in most browsers. Big values increase network-speed but may reduce HDD-speed + u2j: 2 # default + + # web-client: default upload chunksize (MiB); sets min,default,max in the settings gui. Each HTTP POST will aim for default, and never exceed max. Cloudflare max is 96. Big values are good for cross-atlantic but may increase HDD fragmentation on some FS. Disable this optimization with [1,1,1] + u2sz: 1,64,96 # default + + # web-client: default setting for when to replace/overwrite existing files; [0]=never, [1]=if-client-newer, [2]=always + # 📂 also available as volflag "u2ow" + u2ow: 0 # default + + # upload order; [s]=smallest-first, [n]=alphabetical, [fs]=force-s, [fn]=force-n -- alphabetical is a bit slower on fiber/LAN but makes it easier to eyeball if everything went fine + u2sort: s # default + + # write POST reports to textfiles in working-directory + write-uplog + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// network options \\0000000000000000000000000000000000000000000000000000000000000000000000\ + + # IPs and/or unix-sockets to listen on, COMMA-SEPARATED LIST (see --help-bind). Default: all IPv4 and IPv6 + i: 192.168.0.1,::1,unix:770:www:/dev/shm/party.sock # hint; default=:: + + # ports to listen on (comma/range); ignored for unix-sockets + p: 3923,4001-4005 # hint; default=3923 + + # include link-local IPv4/IPv6 in mDNS replies, even if the NIC has routable IPs (breaks some mDNS clients) + ll + + # which ip to associate clients with; [0]=tcp, [1]=origin (first x-fwd, unsafe), [2]=outermost-proxy, [3]=second-proxy, [-1]=closest-proxy + rproxy: 1 # default + + # if reverse-proxied, which http header to read the client's real ip from + xff-hdr: x-forwarded-for # default + + # list of trusted reverse-proxy CIDRs (comma-separated); only accept the real-ip header (--xff-hdr) and IdP headers if the incoming connection is from an IP within either of these subnets. Specify [lan] to allow all LAN / private / non-internet IPs. Can be disabled with [any] if you are behind cloudflare (or similar) and are using --xff-hdr=cf-connecting-ip (or similar) + xff-src: 127.0.0.0/8, ::1/128 # default + + # only accept connections from IP-addresses inside CIDR (comma-separated); examples: [lan] or [10.89.0.0/16, 192.168.33.0/24] + ipa: 10.89.0.0/16, 192.168.33.0/24 # hint; default is unset + + # if reverse-proxying on a location instead of a dedicated domain/subdomain, provide the base location here; example: [/foo/bar] + rp-loc: /files # hint; default is unset + + # set reuseaddr on listening sockets on windows; allows rapid restart of copyparty at the expense of being able to accidentally start multiple instances + reuseaddr + + # allow listening on IPs which do not yet exist, for example if the network interfaces haven't finished going up. Only makes sense for IPs other than '0.0.0.0', '127.0.0.1', '::', and '::1'. May require running as root (unless net.ipv6.ip_nonlocal_bind) + freebind + + # write list of listening-on ip:port to textfile at PATH when http-servers have started + wr-h-eps: /dev/shm/listening-on.txt # hint; default is unset + + # write list of accessible-on ip:port to textfile at PATH when http-servers have started + wr-h-aon: /dev/shm/accessible-on.txt # hint; default is unset + + # socket timeout (read request header) + s-thead: 120 # default + + # socket timeout (read/write request/response bodies). Use 60 on fast servers (default is extremely safe). Disable with 0 if reverse-proxied for a 2% speed boost + s-tbody: 128 # default + + # socket read size in bytes (indirectly affects filesystem writes; recommendation: keep equal-to or lower-than --iobuf) + s-rd-sz: 262144 # default + + # socket write size in bytes + s-wr-sz: 262144 # default + + # debug: socket write delay in seconds + s-wr-slp: 0 # default + + # debug: response delay in seconds + rsp-slp: 0 # default + + # debug: response delay, random duration 0..SEC + rsp-jtr: 0 # default + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// SSL/TLS options \\0000000000000000000000000000000000000000000000000000000000000000000000\ + + # disable ssl/tls -- force plaintext + http-only + + # disable plaintext -- force tls + https-only + + # path to file containing a concatenation of TLS key and certificate chain + cert: $XDG_CONFIG_PATH/.copyparty/cert.pem # default + + # set allowed ssl/tls versions; [help] shows available versions; default is what your python version considers safe + ssl-ver: ssl3,tls10 # hint; default is unset + + # set allowed ssl/tls ciphers; [help] shows available ciphers + ciphers: xtea,rot13 # hint; default is unset + + # dump some tls info + ssl-dbg + + # log master secrets for later decryption in wireshark + ssl-log: /mnt/stash/gotem.log # hint; default is unset + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// TLS certificate generator options \\0000000000000000000000000000000000000000000000000000\ + + # disable automatic certificate creation + no-crt + + # comma-separated list of FQDNs (domains) to add into the certificate + crt-ns: example.com,fileshare.nasa.gov + + # do not add wildcard entries for each --crt-ns + crt-exact + + # do not add autodetected IP addresses into cert + crt-noip + + # do not add 127.0.0.1 / localhost into cert + crt-nolo + + # do not add mDNS names / hostname into cert + crt-nohn + + # where to save the CA cert + crt-dir: $XDG_CONFIG_PATH/.copyparty/ # default + + # ca-certificate expiration time in days + crt-cdays: 3650 # default + + # server-cert expiration time in days + crt-sdays: 365 # default + + # CA/server-cert common-name + crt-cn: partyco # default + + # override CA name + crt-cnc: --crt-cn # default = copy the above + + # override server-cert name + crt-cns: --crt-cnc # default = copy the above + + # backdate in hours + crt-back: 72 # default + + # algorithm and keysize; one of these: ecdsa-256 rsa-4096 rsa-2048 + crt-alg: ecdsa-256 # default + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// IdP / identity provider / user authentication options \\00000000000000000000000000000000\ + + # bypass the copyparty authentication checks if the request-header HN contains a username to associate the request with (for use with authentik/oauth/...) + # └─WARNING: if you enable this, make sure clients are unable to specify this header themselves; must be washed away and replaced by a reverse-proxy + idp-h-usr: idp-username # hint; default is unset + + # assume the request-header HN contains the groupname of the requesting user; can be referenced in config files for group-based access control + idp-h-grp: idp-groups # hint; default is unset + + # optional but recommended safeguard; your reverse-proxy will insert a secret header named HN into all requests, and the other IdP headers will be ignored if this header is not present + idp-h-key: supersecretmagicword # hint; default is unset + + # if there are multiple groups in --idp-h-grp, they are separated by one of the characters in RE + idp-gsep: |:;+, # default + + # where to store the known IdP users/groups (if you run multiple copyparty instances, make sure they use different DBs) + idp-db: $XDG_CONFIG_PATH/.copyparty/idp.db # default + + # how to use --idp-db; [0] = entirely disable, [1] = write-only (effectively disabled), [2] = remember users, [3] = remember users and groups. + # └─NOTE: Will remember and restore the IdP-volumes of all users for all eternity if set to 2 or 3, even when user is deleted from your IdP + idp-store: 1 # default + + # comma-separated list of users allowed to use /?idp (the cache management UI) + idp-adm: ben,jerry # hint; default is unset + + # generate a session-token for IdP users which is written to cookie cppws (or cppwd if plaintext), to reduce the load on the IdP server, lifetime S seconds. + # └─note: The expiration time is a client hint only; the actual lifetime of the session-token is infinite (until next restart with --ses-db wiped) + idp-cookie: 600 # hint; default=0=disabled + + # disable basic-authentication support; do not accept passwords from the 'Authenticate' header at all. NOTE: This breaks support for the android app + no-bauth + + # keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins + bauth-last + + # where to store the sessions database (if you run multiple copyparty instances, make sure they use different DBs) + ses-db: $XDG_CONFIG_PATH/.copyparty/sessions.db # default + + # session key length; default is 120 bits ((20//4)*4*6) + ses-len: 20 + + # disable sessions; use plaintext passwords in cookies + no-ses + + # REPEATABLE: users with IP matching CIDR are auto-authenticated as username USR; example: [172.16.24.0/24=dave] + ipu: CIDR=USR # placeholder + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// user-changeable passwords options \\0000000000000000000000000000000000000000000000000000\ + + # allow users to change their own passwords + chpw + + # REPEATABLE: do not allow password-changes for this comma-separated list of usernames + chpw-no: ole,dole,doffen # hint; default is unset + + # where to store the passwords database (if you run multiple copyparty instances, make sure they use different DBs) + chpw-db: $XDG_CONFIG_PATH/.copyparty/chpw.json + + # minimum password length + chpw-len: 8 # default + + # verbosity of summary on config load [0] = nothing at all, [1] = number of users, [2] = list users with default-pw, [3] = list all users + chpw-v: 2 # default + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// Zeroconf options \\000000000000000000000000000000000000000000000000000000000000000000000\ + + # enable all zeroconf backends (mdns, ssdp) + z + + # enable zeroconf ONLY on the comma-separated list of subnets and/or interface names/indexes + # └─example: eth0, wlo1, virhost0, 192.168.123.0/24, fd00:fda::/96 + z-on: NETS # placeholder; default is unset + + # disable zeroconf on the comma-separated list of subnets and/or interface names/indexes + z-off: NETS # placeholder; default is unset + + # check for network changes every SEC seconds (0=disable) + z-chk: 10 # default + + # verbose all zeroconf backends + zv + + # rejoin multicast groups every SEC seconds (workaround for some switches/routers which cause mDNS to suddenly stop working after some time); try [300] or [180] + # └─note: can be due to firewalls; make sure UDP port 5353 is open in both directions (on clients too) + mc-hop: 0 # default + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// Zeroconf-mDNS options; also see --help-zm \\00000000000000000000000000000000000000000000\ + + # announce the enabled protocols over mDNS (multicast DNS-SD) -- compatible with KDE, gnome, macOS, ... + zm + + # enable mDNS ONLY on the comma-separated list of subnets and/or interface names/indexes + zm-on: NETS # placeholder; default is unset + + # disable mDNS on the comma-separated list of subnets and/or interface names/indexes + zm-off: NETS # placeholder; default is unset + + # IPv4 only -- try this if some clients can't connect + zm4 + + # IPv6 only + zm6 + + # verbose mdns + zmv + + # verboser mdns + zmvv + + # mute parser errors (invalid incoming MDNS packets) + zm-no-pe + + # disable workaround for avahi-bug #379 (corruption in Avahi's mDNS reflection feature) + zm-nwa-1 + + # list of services to announce -- d=webdav h=http f=ftp s=smb -- lowercase=plaintext uppercase=TLS -- default: all enabled services except http/https (Ddfs if --ftp and --smb is set, Dd otherwise) + zms: dhf # probably default + + # link a specific folder for webdav shares + zm-ld: /public/stuff # hint; default is unset (webroot) + + # link a specific folder for http shares + zm-lh: /public/stuff # hint; default is unset (webroot) + + # link a specific folder for ftp shares + zm-lf: /public/stuff # hint; default is unset (webroot) + + # link a specific folder for smb shares + zm-ls: /public/stuff # hint; default is unset (webroot) + + # merge NICs which share subnets; assume that same subnet means same network + zm-mnic + + # merge subnets on each NIC -- always enabled for ipv6 -- reduces network load, but gnome-gvfs clients may stop working, and clients cannot be in subnets that the server is not + zm-msub + + # disable NSEC replies -- try this if some clients don't see copyparty + zm-noneg + + # send unsolicited announce every SEC; useful if clients have IPs in a subnet which doesn't overlap with the server, or to avoid some firewall issues + zm-spam: 0 # default + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// Zeroconf-SSDP options \\0000000000000000000000000000000000000000000000000000000000000000\ + + # announce the enabled protocols over SSDP -- compatible with Windows + zs + + # enable SSDP ONLY on the comma-separated list of subnets and/or interface names/indexes + zs-on: NETS # placeholder; default is unset + + # disable SSDP on the comma-separated list of subnets and/or interface names/indexes + zs-off: NETS # placeholder; default is unset + + # verbose SSDP + zsv + + # location to include in the url (or a complete external URL), for example [priv/?pw=hunter2] (goes directly to /priv/ with password hunter2) or [?hc=priv&pw=hunter2] (shows mounting options for /priv/ with password) + zsl: /?hc # default + + # USN (device identifier) to announce + zsid: UUID # placeholder; default is an autogenerated UUID + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// FTP options (TCP only) \\000000000000000000000000000000000000000000000000000000000000000\ + + # enable FTP server on PORT, for example 3921 + ftp: 3921 # hint; default is unset + + # enable FTPS server on PORT, for example 3990 + ftps: 3990 # hint; default is unset + + # verbose + ftpv + + # only listen on IPv4 + ftp4 + + # only accept connections from IP-addresses inside CIDR (comma-separated); specify [any] to disable inheriting --ipa. Examples: [lan] or [10.89.0.0/16, 192.168.33.0/24] + ftp-ipa: CIDR # placeholder + + # if target file exists, reject upload instead of overwrite + ftp-no-ow + + # grace period for resuming interrupted uploads (any client can write to any file last-modified more recently than SEC seconds ago) + ftp-wt: 7 # default + + # the NAT address to use for passive connections + ftp-nat: 192.168.1.13 # hint; default is unset + + # the range of TCP ports to use for passive connections, for example 12000-13000 + ftp-pr: 12000-12099 # hint; default is unset + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// WebDAV options \\00000000000000000000000000000000000000000000000000000000000000000000000\ + + # enable full write support, even if client may not be webdav. WARNING: This has side-effects -- PUT-operations will now OVERWRITE existing files, rather than inventing new filenames to avoid loss of data. You might want to instead set this as a volflag where needed. By not setting this flag, uploaded files can get written to a filename which the client does not expect (which might be okay, depending on client) + daw + + # allow depth:infinite requests (recursive file listing); extremely server-heavy but required for spec compliance -- luckily few clients rely on this + dav-inf + + # disable apple-garbage filter -- allow macos to create junk files (._* and .DS_Store, .Spotlight-*, .fseventsd, .Trashes, .AppleDouble, __MACOS) + dav-mac + + # show symlink-destination's lastmodified instead of the link itself; always enabled for recursive listings + # 📂 also available as volflag "davrt" + dav-rt + + # force auth for all folders (required by davfs2 when only some folders are world-readable) + # 📂 also available as volflag "davauth" + dav-auth + + # regex of tricky user-agents which expect 401 from GET requests; disable with [no] or blank + dav-ua1: kioworker/ # default (KDE) + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// TFTP options (UDP only) \\00000000000000000000000000000000000000000000000000000000000000\ + + # enable TFTP server on PORT, for example 69 or 3969 + tftp: 3969 # hint; default is unset + + # only listen on IPv4 + tftp4 + + # verbose + tftpv + + # verboser + tftpvv + + # debug: disable optimizations + tftp-no-fast + + # return a directory listing if a file with this name is requested and it does not exist; defaults matches .ls, dir, .dir.txt, ls.txt, ... + tftp-lsf: \.?(dir|ls)(\.txt)? # default + + # if someone tries to download a directory, return an error instead of showing its directory listing + tftp-nols + + # only accept connections from IP-addresses inside CIDR (comma-separated); specify [any] to disable inheriting --ipa. Examples: [lan] or [10.89.0.0/16, 192.168.33.0/24] + tftp-ipa: CIDR # placeholder + + # the range of UDP ports to use for data transfer, for example 12000-13000 + tftp-pr: 12100-12199 # hint; default is unset + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// SMB/CIFS options \\000000000000000000000000000000000000000000000000000000000000000000000\ + + # enable smb (read-only) -- this requires running copyparty as root on linux and macos unless --smb-port is set above 1024 and your OS does port-forwarding from 445 to that. + # └─WARNING: this protocol is DANGEROUS and buggy! Never expose to the internet! + smb + + # enable write support (please dont) + smbw + + # disable SMBv2, only enable SMBv1 (CIFS) + smb1 + + # port to listen on -- if you change this value, you must NAT from TCP:445 to this port using iptables or similar + smb-port: 445 # default + + # truncate directory listings to 64kB (~400 files); avoids impacket-0.11 bug, fixes impacket-0.12 performance + smb-nwa-1 + + # disable impacket workaround for filecopy globs + smb-nwa-2 + + # small performance boost: disable per-account permissions, enables account coalescing instead (if one user has write/delete-access, then everyone does) + smba + + # verbose + smbv + + # verboser + smbvv + + # verbosest + smbvvv + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// handlers (see --help-handlers) \\0000000000000000000000000000000000000000000000000000000\ + + # REPEATABLE: handle 404s by executing PY file + on404: ~/bin/on404.py # hint; default is unset + on404: ~/bin/on404season2.py + + # REPEATABLE: handle 403s by executing PY file + on403: ~/bin/on403.py # hint; default is unset + + # recompile handlers on each request -- expensive but convenient when hacking on stuff + hot-handlers + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// event hooks (see --help-hooks) \\0000000000000000000000000000000000000000000000000000000\ + + # REPEATABLE: execute CMD before a file upload starts + xbu: ~/bin/execute-before-upload.py # hint; default is unset + + # REPEATABLE: execute CMD after a file upload finishes + xau: ~/bin/execute-after-upload.py # hint; default is unset + + # REPEATABLE: execute CMD after all uploads finish and volume is idle + xiu: ~/bin/execute-idle-upload.py # hint; default is unset + + # REPEATABLE: execute CMD before a file copy + xbc: ~/bin/execute-before-copy.py # hint; default is unset + + # REPEATABLE: execute CMD after a file copy + xac: ~/bin/execute-after-copy.py # hint; default is unset + + # REPEATABLE: execute CMD before a file move/rename + xbr: ~/bin/execute-before-rename.py # hint; default is unset + + # REPEATABLE: execute CMD after a file move/rename + xar: ~/bin/execute-after-rename.py # hint; default is unset + + # REPEATABLE: execute CMD before a file delete + xbd: ~/bin/execute-before-delete.py # hint; default is unset + + # REPEATABLE: execute CMD after a file delete + xad: ~/bin/execute-after-delete.py # hint; default is unset + + # REPEATABLE: execute CMD on message + xm: ~/bin/execute-on-message.py # hint; default is unset + + # REPEATABLE: execute CMD if someone gets banned (pw/404/403/url) + xban: ~/bin/execute-on-ban.py # hint; default is unset + + # verbose hooks + hook-v + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// grafana/prometheus metrics endpoint \\00000000000000000000000000000000000000000000000000\ + + # enable openmetrics at /.cpr/metrics for admin accounts + stats + + # disable disk-space metrics (used/free space) + nos-hdd + + # disable volume size metrics (num files, total bytes, vmaxb/vmaxn) + nos-vol + + # disable volume state metrics (indexing, analyzing, activity) + nos-vst + + # disable dupe-files metrics (good idea; very slow) + nos-dup + + # disable unfinished-uploads metrics + nos-unf + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// yolo options \\0000000000000000000000000000000000000000000000000000000000000000000000000\ + + # disable csrf protections; let other domains/sites impersonate you through cross-site requests + allow-csrf + + # allow cookies from other domains (if you follow a link from another website into your server, you will arrive logged-in); this reduces protection against CSRF + cookie-lax + + # permit ?move=[...] and ?delete as GET + getmod + + # allow users with write-only access to upload logues and readmes without adding the _wo_ filename prefix + # 📂 also available as volflag "wo_up_readme" + wo-up-readme + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// opt-outs \\00000000000000000000000000000000000000000000000000000000000000000000000000000\ + + # never write anything to disk (debug/benchmark) + nw + + # do not disable quick-edit-mode on windows (it is disabled to avoid accidental text selection in the terminal window, as this would pause execution) + keep-qem + + # disable webdav support + no-dav + + # disable delete operations + no-del + + # disable move/rename operations + no-mv + + # disable copy operations + no-cp + + # no title hostname; don't show --name in + nth + + # no info hostname -- don't show in UI + nih + + # no info disk-usage -- don't show in UI + nid + + # no powered-by-copyparty branding in UI + nb + + # reject download-as-zip if more than N files in total; optionally takes a unit suffix: [256], [9K], [4G] + # 📂 also available as volflag "zipmaxn" + zipmaxn: N # placeholder; default is unset + + # reject download-as-zip if total download size exceeds SZ bytes; optionally takes a unit suffix: [256M], [4G], [2T] + # 📂 also available as volflag "zipmaxs" + zipmaxs: SZ # placeholder; default is unset + + # custom errormessage when download size exceeds max + # 📂 also available as volflag "zipmaxt" + zipmaxt: TXT # placeholder; default is unset + + # authenticated users bypass the zip size limit + # 📂 also available as volflag "zipmaxu" + zipmaxu + + # who can download as zip/tar? [0]=nobody, [1]=admins, [2]=authenticated-with-read-access, [3]=everyone-with-read-access (volflag=zip_who) + # └─WARNING: if a nested volume has a more restrictive value than a parent volume, then this will be ignored if the download is initiated from the parent, more lenient volume + zip-who: 3 # default + + # regex of user-agents to reject from download-as-zip/tar; disable with [no] or blank + ua-nozip: Barkrowler|bingbot|BLEXBot|Googlebot|GoogleOther|GPTBot|PetalBot|SeekportBot|SemrushBot|YandexBot # default + + # disable download as zip/tar; same as --zip-who=0 + no-zip + + # disable download as compressed tar (?tar=gz, ?tar=bz2, ?tar=xz, ?tar=gz:9, ...) + no-tarcmp + + # do not allow clients (or server config) to schedule an upload to be deleted after a given time + no-lifetime + + # disable race-the-beam (lockstep download of files which are currently being uploaded) + # 📂 also available as volflag "nopipe" + no-pipe + + # disable streaming a growing files with ?tail + # 📂 also available as volflag "notail" + no-tail + + # do not write uploader-IP into the database; will also disable unpost, you may want --forget-ip instead + # 📂 also available as volflag "no_db_ip" + no-db-ip + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// safety options \\00000000000000000000000000000000000000000000000000000000000000000000000\ + + # increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers. + # └─Alias of --dotpart --no-thumb --no-mtag-ff --no-robots --force-js + s + + # further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav, 404 on 403, ban on excessive 404s. + # └─Alias of -s --unpost=0 --no-del --no-mv --hardlink --vague-403 -nih + ss + + # further increase safety: Enable logging to disk, scan for dangerous symlinks. + # └─Alias of -ss --no-dav --no-logues --no-readme -lo=cpp-%Y-%m%d-%H%M%S.txt.xz --ls=**,*,ln,p,r + sss + + # do a sanity/safety check of all volumes on startup; arguments USER,VOL,FLAGS (see --help-ls); example [**,*,ln,p,r] + ls: **,*,ln,p,r # hint; default is unset + + # never follow symlinks leaving the volume root, unless the link is into another volume where the user has similar access + # 📂 also available as volflag "xvol" + xvol + + # stay within the filesystem of the volume root; do not descend into other devices (symlink or bind-mount to another HDD, ...) + # 📂 also available as volflag "xdev" + xdev + + # disallow moving dotfiles; makes it impossible to move folders containing dotfiles + no-dot-mv + + # disallow renaming dotfiles; makes it impossible to turn something into a dotfile + no-dot-ren + + # disable rendering .prologue/.epilogue.html into directory listings + no-logues + + # disable rendering readme/preadme.md into directory listings + no-readme + + # send 404 instead of 403 (security through ambiguity, very enterprise) + vague-403 + + # don't send folder listings as HTML, force clients to use the embedded json instead -- slight protection against misbehaving search engines which ignore --no-robots + force-js + + # adds http and html headers asking search engines to not index anything + # 📂 also available as volflag "norobots" + no-robots + + # logout clients after H hours of inactivity; [0.0028]=10sec, [0.1]=6min, [24]=day, [168]=week, [720]=month, [8760]=year) + logout: 8086 # default + + # more than N wrong passwords in W minutes = ban for B minutes; disable with [no] + ban-pw: 9,60,1440 # default + + # more than N password-changes in W minutes = ban for B minutes; disable with [no] + ban-pwc: 5,60,1440 # default + + # hitting more than N 404's in W minutes = ban for B minutes; only affects users who cannot see directory listings because their access is either g/G/h + ban-404: 50,60,1440 # default + + # hitting more than N 403's in W minutes = ban for B minutes; [1440]=day, [10080]=week, [43200]=month + ban-403: 9,2,1440 # default + + # hitting more than N 422's in W minutes = ban for B minutes (invalid requests, attempted exploits ++) + ban-422: 9,2,1440 # default + + # hitting more than N sus URL's in W minutes = ban for B minutes; applies only to permissions g/G/h (decent replacement for --ban-404 if that can't be used) + ban-url: 9,2,1440 # default + + # URLs which are considered sus / eligible for banning; disable with blank or [no] + sus-urls: \.php$|(^|/)wp-(admin|content|includes)/ # default + + # harmless URLs ignored from 404-bans; disable with blank or [no] + nonsus-urls: ^(favicon\.ico|robots\.txt)$|^apple-touch-icon|^\.well-known # default + + # if a client is banned, reject its connection as soon as possible; not a good idea to enable when proxied behind cloudflare since it could ban your reverse-proxy + early-ban + + # if a client maxes out the server connection limit, downgrade it from connection:keep-alive to connection:close for MIN minutes (and also kill its active connections) -- disable with 0 + aclose: 10 # default + + # if a client maxes out the server connection limit without sending headers, ban it for B minutes; disable with [0] + loris: 60 # default + + # Access-Control-Allow-Origin; list of origins (domains/IPs without port) to accept requests from; [https://1.2.3.4]. Default [*] allows requests from all sites but removes cookies and http-auth; only ?pw=hunter2 survives + acao: * # default + + # Access-Control-Allow-Methods; list of methods to accept from offsite ('*' behaves like --acao's description) + acam: GET,HEAD # default + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// salting options \\0000000000000000000000000000000000000000000000000000000000000000000000\ + + # account-pw hashing algorithm; one of these, best to worst: argon2 scrypt sha2 none (each optionally followed by alg-specific comma-sep. config) + ah-alg: argon2 # hint; default is unset + + # account-pw salt; ignored if --ah-alg is none (default) + ah-salt: shangalabangala # hint; default is autogenerated and stored in $XDG_CONFIG_PATH/.copyparty/ + + # generate hashed password for PW, or read passwords from STDIN if PW is [-] + ah-gen: PW # placeholder + + # launch an interactive shell which hashes passwords without ever storing or displaying the original passwords + ah-cli + + # per-file accesskey salt; used to generate unpredictable URLs for hidden files + fk-salt: shangalabangala # hint; default is autogenerated and stored in $XDG_CONFIG_PATH/.copyparty/ + + # per-directory accesskey salt; used to generate unpredictable URLs to share folders with users who only have the 'get' permission + dk-salt: shangalabangala # hint; default is autogenerated and stored in $XDG_CONFIG_PATH/.copyparty/ + + # up2k file-hash salt; serves no purpose, no reason to change this (but delete all databases if you do) + warksalt: hunter2 # default + + # on startup, print the effective value of --ah-salt (the autogenerated value in $XDG_CONFIG_HOME unless otherwise specified) + show-ah-salt + + # on startup, print the effective value of --fk-salt (the autogenerated value in $XDG_CONFIG_HOME unless otherwise specified) + show-fk-salt + + # on startup, print the effective value of --dk-salt (the autogenerated value in $XDG_CONFIG_HOME unless otherwise specified) + show-dk-salt + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// shutdown options \\000000000000000000000000000000000000000000000000000000000000000000000\ + + # continue running even if it's impossible to listen on some of the requested endpoints + ign-ebind + + # continue running even if it's impossible to receive connections at all + ign-ebind-all + + # shutdown after WHEN has finished; [cfg] config parsing, [idx] volscan + multimedia indexing + exit: WHEN # placeholder; default is unset + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// logging options \\0000000000000000000000000000000000000000000000000000000000000000000000\ + + # quiet; disable most STDOUT messages + q + + # logfile, example: cpp-%Y-%m%d-%H%M%S.txt.xz (NB: some errors may appear on STDOUT only) + lo: PATH # placeholder + + # disable colors; same as environment-variable NO_COLOR + no-ansi + + # force colors; overrides environment-variable NO_COLOR + ansi + + # don't flush the logfile after each write; tiny bit faster + no-logflush + + # do not list volumes and permissions on startup + no-voldump + + # do not use local timezone; assume the TZ env-var is UTC (tiny bit faster) + log-utc + + # timestamp resolution / number of timestamp decimals + log-tdec: 3 # default + + # log failed login attempt passwords: 0=terse, 1=plaintext, 2=hashed + log-badpwd: 1 # default + + # debug: print tcp-server msgs + log-conn + + # debug: print http-server threadpool scaling + log-htp + + # print request HEADER; [*]=all + ihead: user-agent # hint; default is unset + + # print response HEADER; [*]=all + ohead: set-cooke # hint; default is unset + + # dont log URLs matching regex RE + lf-url: ^/\.cpr/|[?&]th=[wjp]|/\.(_|ql_|DS_Store$|localized$) # default + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// admin panel options \\000000000000000000000000000000000000000000000000000000000000000000\ + + # disable ?reload=cfg (reload users/volumes/volflags from config file) + no-reload + + # disable ?scan (volume reindexing) + no-rescan + + # disable ?stack (list all stacks) + no-stack + + # disable ?ru (list of recent uploads) + no-ups-page + + # don't show list of incoming files in controlpanel + no-up-list + + # who can see active downloads in the controlpanel? [0]=nobody, [1]=admins, [2]=everyone + dl-list: 2 # default + + # who can see recent uploads on the ?ru page? [0]=nobody, [1]=admins, [2]=everyone + # 📂 also available as volflag "ups_who" + ups-who: 2 # default + + # let everyone see upload timestamps on the ?ru page, not just admins + ups-when + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// thumbnail options \\00000000000000000000000000000000000000000000000000000000000000000000\ + + # disable all thumbnails + # 📂 also available as volflag "dthumb" + no-thumb + + # disable video thumbnails + # 📂 also available as volflag "dvthumb" + no-vthumb + + # disable audio thumbnails (spectrograms) + # 📂 also available as volflag "dathumb" + no-athumb + + # thumbnail res + # 📂 also available as volflag "thsize" + th-size: 320x256 # default + + # num cpu cores to use for generating thumbnails + th-mt: 4 # hint; default is autodetect + + # conversion timeout in seconds + # 📂 also available as volflag "convt" + th-convt: 60 # default + + # max memory usage (GiB) permitted by thumbnailer; not very accurate + th-ram-max: 3 # hint; default is 60% of free ram at startup (some conditions apply) + + # crop thumbnails to 4:3 or keep dynamic height; client can override in UI unless force. [y]=crop, [n]=nocrop, [fy]=force-y, [fn]=force-n + # 📂 also available as volflag "crop" + th-crop: y # default + + # show thumbs at 3x resolution; client can override in UI unless force. [y]=yes, [n]=no, [fy]=force-yes, [fn]=force-no + # 📂 also available as volflag "th3x" + th-x3: n # default + + # image decoders, in order of preference + th-dec: vips,pil,ff # default + + # disable jpg output + th-no-jpg + + # disable webp output + th-no-webp + + # force jpg output for video thumbs (avoids issues on some FFmpeg builds) + th-ff-jpg + + # use swresample instead of soxr for audio thumbs (faster, lower accuracy, avoids issues on some FFmpeg builds) + th-ff-swr + + # activity labeling cooldown -- avoids doing keepalive pokes (updating the mtime) on thumbnail folders more often than SEC seconds + th-poke: 300 # default + + # cleanup interval; 0=disabled + th-clean: 43200 # default + + # max folder age -- folders which haven't been poked for longer than --th-poke seconds will get deleted every --th-clean seconds + th-maxage: 604800 # default + + # folder thumbnails to stat/look for; enabling -e2d will make these case-insensitive, and try them as dotfiles (.folder.jpg), and also automatically select thumbnails for all folders that contain pics, even if none match this pattern + th-covers: folder.png,folder.jpg,cover.png,cover.jpg # default + + # image formats to decode using pillow + th-r-pil: a,very,long,list,of,file,extensions # hint + + # image formats to decode using pyvips + th-r-vips: a,very,long,list,of,file,extensions # hint + + # image formats to decode using ffmpeg + th-r-ffi: a,very,long,list,of,file,extensions # hint + + # video formats to decode using ffmpeg + th-r-ffv: a,very,long,list,of,file,extensions # hint + + # audio formats to decode using ffmpeg + th-r-ffa: a,very,long,list,of,file,extensions # hint + + # audio formats which provoke https://trac.ffmpeg.org/ticket/10797 (huge ram usage for s3xmodit spectrograms) + th-spec-cnv: a,very,long,list,of,file,extensions # hint + + # audio/image formats to decompress before passing to ffmpeg + au-unpk: mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, and so on + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// transcoding options \\000000000000000000000000000000000000000000000000000000000000000000\ + + # target bitrate for transcoding to opus; set 0 to disable + q-opus: 128 # default + + # target quality for transcoding to mp3, for example [192k] (CBR) or [q0] (CQ/CRF, q0=maxquality, q9=smallest); set 0 to disable + q-mp3: q2 # default + + # allow transcoding to wav (lossless, uncompressed) + allow-wav + + # allow transcoding to flac (lossless, compressed) + allow-flac + + # disable transcoding to caf-opus (affects iOS v12~v17), will use mp3 instead + no-caf + + # disable transcoding to webm-opus (iOS v18 and later), will use mp3 instead + no-owa + + # disable audio transcoding + no-acode + + # disable batch audio transcoding by folder download (zip/tar) + no-bacode + + # delete cached transcode output after SEC seconds + ac-maxage: 86400 # default + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// tailing options (realtime streaming of a growing file) \\0000000000000000000000000000000\ + + # who can tail? [0]=nobody, [1]=admins, [2]=authenticated-with-read-access, [3]=everyone-with-read-access + # 📂 also available as volflag "tail_who" + tail-who: 2 # default + + # do not allow starting a new tail if more than N active downloads + tail-cmax: 64 # default + + # terminate connection after SEC seconds; [0]=never + # 📂 also available as volflag "tail_tmax" + tail-tmax: 0 # default + + # check for new data every SEC seconds + # 📂 also available as volflag "tail_rate" + tail-rate: 0.2 # default + + # send a zerobyte if connection is idle for SEC seconds to prevent disconnect + tail-ka: 3 # default + + # check if file was replaced (new fd) if idle for SEC seconds + # 📂 also available as volflag "tail_fd" + tail-fd: 1 # default + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// RSS options \\00000000000000000000000000000000000000000000000000000000000000000000000000\ + + # enable RSS output (experimental) + # 📂 also available as volflag "rss" + rss + + # default number of files to return (url-param 'nf') + rss-nf: 250 # default + + # default list of file extensions to include (url-param 'fext'); blank=all + rss-fext: mp3,opus # hint; default is unset + + # default sort order (url-param 'sort'); [m]=last-modified [u]=upload-time [n]=filename [s]=filesize; Uppercase=oldest-first. Note that upload-time is 0 for non-uploaded files + rss-sort: m # default + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// general db options \\0000000000000000000000000000000000000000000000000000000000000000000\ + + # enable up2k database; this enables file search, upload-undo, improves deduplication + e2d + + # scan writable folders for new files on startup; sets -e2d + e2ds + + # scans all folders on startup; sets -e2ds + e2dsa + + # verify file integrity; rehash all files and compare with db + e2v + + # on hash mismatch: update the database with the new hash + e2vu + + # on hash mismatch: panic and quit copyparty + e2vp + + # where to store volume data (db, thumbs); default is a folder named ".hist" inside each volume + # 📂 also available as volflag "hist" + hist: PATH # placeholder; default is unset + + # override where the volume databases are to be placed; default is the same as --hist + # 📂 also available as volflag "dbpath" + dbpath: PATH # placeholder; default is unset + + # regex: disable hashing of matching absolute-filesystem-paths during e2ds folder scans + # 📂 also available as volflag "nohash" + no-hash: ^/mnt/nas/linux-isos/knoppix/ + + # regex: disable indexing of matching absolute-filesystem-paths during e2ds folder scans + # 📂 also available as volflag "noidx" + no-idx: ^/mnt/nas/logs/ + + # do not show total recursive size of folders in listings, show inode size instead; slightly faster + # 📂 also available as volflag "nodirsz" + no-dirsz + + # if the directory-sizes in the UI are bonkers, use this along with -e2dsa to rebuild the index from scratch + re-dirsz + + # disable rescan acceleration; do full database integrity check -- makes the db ~5% smaller and bootup/rescans 3~10x slower + no-dhash + + # force a cache rebuild on startup; enable this once if it gets out of sync (should never be necessary) + re-dhash + + # never forget indexed files, even when deleted from disk -- makes it impossible to ever upload the same file twice -- only useful for offloading uploads to a cloud service or something + # 📂 also available as volflag "noforget" + no-forget + + # remove uploader-IP from database (and make unpost impossible) MIN minutes after upload, for GDPR reasons. Default [0] is never-forget. [1440]=day, [10080]=week, [43200]=month. + # 📂 also available as volflag "forget_ip" + forget-ip: 0 # default (disabled) + + # database durability profile; sets the tradeoff between robustness and speed, see --help-dbd + # 📂 also available as volflag "dbd" + dbd: wal # default + + # on upload: check all volumes for dupes, not just the target volume (probably buggy, not recommended) + # 📂 also available as volflag "xlink" + xlink + + # num cpu cores to use for file hashing; set 0 or 1 for single-core hashing + hash-mt: 4 # default is autodetect but max 5 + + # rescan filesystem for changes every SEC seconds; 0=off + # 📂 also available as volflag "scan" + re-maxage: 0 # default + + # defer any scheduled volume reindexing until SEC seconds after last db write (uploads, renames, ...) + db-act: 10 # default + + # search deadline -- terminate searches running for more than SEC seconds + srch-time: 45 # default + + # max search results to allow clients to fetch; 125 results will be shown initially + srch-hits: 7999 # default + + # regex: exclude files from search results if the file-URL matches PTN (case-sensitive) + # 📂 also available as volflag "srch_excl" + srch-excl: password|logs/[0-9] # hint (any URL containing 'password' or 'logs/DIGIT'), default is unset + + # show dotfiles in search results (volflags: dotsrch | nodotsrch) + dotsrch + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// metadata db options \\000000000000000000000000000000000000000000000000000000000000000000\ + + # enable metadata indexing; makes it possible to search for artist/title/codec/resolution/... + e2t + + # scan newly discovered files for metadata on startup; sets -e2t + e2ts + + # delete all metadata from DB and do a full rescan; sets -e2ts + e2tsr + + # use FFprobe for tags instead; will detect more tags + no-mutagen + + # never use FFprobe as tag reader; is probably safer + no-mtag-ff + + # timeout for FFprobe tag-scan + mtag-to: 60 # default + + # num cpu cores to use for tag scanning + mtag-mt: 4 # hint; default is autodetect + + # verbose tag scanning; print errors from mtp subprocesses and such + mtag-v + + # debug mtp settings and mutagen/FFprobe parsers + mtag-vv + + # REPEATABLE: add/replace metadata mapping + mtm: qux=foo,bar # hint (clone metadata-key foo to qux with bar as fallback); default is unset + + # tags to index/display (comma-sep.); either an entire replacement list, or add/remove stuff on the default-list with +foo or /bar" + mte: .files,circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash,vhash # default + + # tags to hide by default (comma-sep.); assign/add/remove same as -mte" + mth: .vq,.aq,vc,ac,fmt,res,.fps # default + + # REPEATABLE: read tag M using program BIN to parse the file + mtp: .bpm=~/bin/audio-bpm.py # hint; default is unset + mtp: key=f,t5,~/bin/audio-key.py + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// textfile options \\000000000000000000000000000000000000000000000000000000000000000000000\ + + # where to store old version of markdown files; [s]=subfolder, [v]=volume-histpath, [n]=nope/disabled + # 📂 also available as volflag "md_hist" + md-hist: s # default + + # the textfile editor will check for serverside changes every SEC seconds + mcr: 60 # default + + # enable markdown plugins -- neat but dangerous, big XSS risk + emp + + # enable textfile expansion -- replace {{self.ip}} and such; see --help-exp + # 📂 also available as volflag "exp" + exp + + # comma/space-separated list of placeholders to expand in markdown files; add/remove stuff on the default list with +hdr_foo or /vf.scan + # 📂 also available as volflag "exp_md" + exp-md: self.ip self.ua self.uname self.host cfg.name cfg.logout vf.scan vf.thsize hdr.cf_ipcountry srv.itime srv.htime # default + + # comma/space-separated list of placeholders to expand in prologue/epilogue files + # 📂 also available as volflag "exp_lg" + exp-lg: self.ip self.ua self.uname self.host cfg.name cfg.logout vf.scan vf.thsize hdr.cf_ipcountry srv.itime srv.htime # default + + # regex of user-agents to reject from viewing documents through ?doc=[...]; disable with [no] or blank + ua-nodoc: Barkrowler|bingbot|BLEXBot|Googlebot|GoogleOther|GPTBot|PetalBot|SeekportBot|SemrushBot|YandexBot # default + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// og / open graph / discord-embed options \\0000000000000000000000000000000000000000000000\ + + # disable hotlinking and return an html document instead; this is required by open-graph, but can also be useful on its own + # 📂 also available as volflag "og" + og + + # only disable hotlinking / engage OG behavior if the useragent matches regex RE + # 📂 also available as volflag "og_ua" + og-ua: (Discord|Twitter|Slack)bot # hint; default is unset + + # do not return the regular copyparty html, but instead load the jinja2 template at PATH (if path contains 'EXT' then EXT will be replaced with the requested file's extension) + # 📂 also available as volflag "og_tpl" + og-tpl: /var/copyparty/opengraph-EXT.j2 # hint; default is unset + + # do not automatically add OG entries into <head> (useful if you're doing this yourself in a template or such) + # 📂 also available as volflag "og_no_head" + og-no-head + + # thumbnail format; j=jpeg, jf=jpeg-uncropped, jf3=jpeg-uncropped-large, w=webm, ... + # 📂 also available as volflag "og_th" + og-th: jf3 # default + + # fallback title if there is nothing in the -e2t database + # 📂 also available as volflag "og_title" + og-title: HEY CHECK THIS OUT # hint; default is blank + + # audio title format; takes any metadata key + # 📂 also available as volflag "og_title_a" + og-title-a: 🎵 {{ artist }} - {{ title }} # default + + # video title format; takes any metadata key + # 📂 also available as volflag "og_title_v" + og-title-v: {{ title }} # default + + # image title format; takes any metadata key + # 📂 also available as volflag "og_title_i" + og-title-i: {{ title }} # default + + # force default title; do not read from tags + # 📂 also available as volflag "og_s_title" + og-s-title + + # description text; same for all files, disable with [-] + # 📂 also available as volflag "og_desc" + og-desc: bottom text # hint; default is blank + + # sitename; defaults to --name, disable with [-] + # 📂 also available as volflag "og_site" + og-site: dank memes # hint; default is blank which means it'll copy the `name` global-option + + # accent color (3 or 6 hex digits); may also affect safari and/or android-chrome + # 📂 also available as volflag "tcolor" + tcolor: 333 # default (copyparty gray) + + # query-string parceling; translate a request for /foo/.uqe/BASE64 into /foo?TEXT, or /foo/?TEXT if the first character in TEXT is a slash. Automatically enabled for --og + uqe + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// ui options \\000000000000000000000000000000000000000000000000000000000000000000000000000\ + + # show grid/thumbnails by default + # 📂 also available as volflag "grid" + grid + + # select files in grid by ctrl-click + # 📂 also available as volflag "gsel" + gsel + + # default to local timezone instead of UTC + localtime + + # language; one of the following: eng nor chi + lang: eng # default + + # default theme to use (0..7) + theme: 0 # default + + # number of themes installed + themes: 8 # default + + # default audio/video volume percent + au-vol: 50 # default; anything 0-100 + + # default sort order, comma-separated column IDs (see header tooltips), prefix with '-' for descending. Examples: href -href ext sz ts tags/Album tags/.tn + # 📂 also available as volflag "sort" + sort: href # default + + # default-enable natural sort of filenames with leading numbers + # 📂 also available as volflag "nsort" + nsort + + # number of sorting rules to include in media URLs by default + # 📂 also available as volflag "hsortn" + hsortn: 2 # default + + # default-enable seeing dotfiles; only takes effect if user has the necessary permissions + see-dots + + # number of confirmations to show when deleting files (2/1/0) + qdel: 2 # default + + # don't show files/folders matching REGEX in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\.(js|css)$] + # 📂 also available as volflag "unlist" + unlist: REGEX # placeholder + + # favicon-text [ foreground [ background ] ], set blank to disable + favico: c 000 none # default + + # REPEATABLE: use thumbnail-image VP for file-extension E, example: [exe=/.res/exe.png] + # 📂 also available as volflag "ext_th" + ext-th: 7z=/.icons/archive.png # hint; default is unset + ext-th: exe=/.icons/glass-of-red-liquid.png + + # emoji or emoji,css Example: [🥖,padding:0] + spinner: 🌲 # default + + # URL to additional CSS to include in the filebrowser html + css-browser: /.res/rice.css # hint; default is unset + + # URL to additional JS to include in the filebrowser html + js-browser: /.res/mousetrail.js # hint; default is unset + + # URL to additional JS to include in all other pages + js-other: /.res/snowflakes.jpg # hint; default is unset + + # text to append to the <head> of all HTML pages (except for basic-browser); can be @PATH to send the contents of a file at PATH, and/or begin with % to render as jinja2 template + # 📂 also available as volflag "html_head" + html-head: <script>alert(1);</script> # hint; default is unset + + # if a folder contains index.html, show that instead of the directory listing by default (can be changed in the client settings UI, or add ?v to URL for override) + ih + + # file extensions to present as plaintext + textfiles: txt,nfo,diz,cue,readme # default + + # max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS) + txt-max: 64 # default + + # title / service-name to show in html documents + doctitle: copyparty @ --name # default (--name will copy from global-option `name`) + + # server name (displayed in filebrowser document title) + bname: --name # default (copy global-option `name`) + + # powered-by link; disable with -nb + pb-url: https://github.com/9001/copyparty # default + + # show version on the control panel (incompatible with -nb) + ver + + # configure the option to enable/disable k304 on the controlpanel (workaround for buggy reverse-proxies); [0] = hidden and default-off, [1] = visible and default-off, [2] = visible and default-on + k304: 0 # default + + # configure the option to enable/disable no304 on the controlpanel (workaround for buggy caching in browsers); [0] = hidden and default-off, [1] = visible and default-off, [2] = visible and default-on + no304: 0 # default + + # list of capabilities to allow in the iframe 'sandbox' attribute for README.md docs (volflag=md_sbf); see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox + md-sbf: downloads forms popups scripts top-navigation-by-user-activation # default + + # list of capabilities to allow in the iframe 'sandbox' attribute for prologue/epilogue docs + # 📂 also available as volflag "lg_sbf" + lg-sbf: downloads forms popups scripts top-navigation-by-user-activation # default + + # the value of the iframe 'allow' attribute for README.md docs, for example [fullscreen] + # 📂 also available as volflag "md_sba" + md-sba: fullscreen # hint; default is blank + + # the value of the iframe 'allow' attribute for prologue/epilogue docs (volflag=lg_sba); see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy#iframes + lg-sba: TXT # placeholder + + # don't sandbox README/PREADME.md documents (volflags: no_sb_md | sb_md) + no-sb-md + + # don't sandbox prologue/epilogue docs (volflags: no_sb_lg | sb_lg); enables non-js support + no-sb-lg + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// debug options \\000000000000000000000000000000000000000000000000000000000000000000000000\ + + # verbose config file parser (explain config) + vc + + # generate config file from current config (best-effort; probably buggy) + cgen + + # list information about detected optional dependencies + deps + + # kernel-bug workaround: disable poll; use select instead (limits max num clients to ~700) + no-poll + + # kernel-bug workaround: disable sendfile; do a safe and slow read-send-loop instead + no-sendfile + + # kernel-bug workaround: disable scandir; do a listdir + stat on each file instead + no-scandir + + # wait for initial filesystem indexing before accepting client requests + no-fastboot + + # disable httpserver threadpool, create threads as-needed instead + no-htp + + # when listening on unix-sockets, do a basic delete+bind instead of the default atomic bind + rm-sck + + # explain search processing, and do some extra expensive sanity checks + srch-dbg + + # use mdns-domain instead of server-ip on /?hc + rclone-mdns + + # write stacktrace to Path every S second, for example --stackmon=./st/%Y-%m/%d/%H%M.xz,60 + stackmon: P,S # placeholder + + # list active threads every SEC + log-thrs: 0 # default + + # log filekey params for files where path matches REGEX; [.] (a single dot) = all files + log-fk: /mnt/a-problematic-fuse/this-folder-breaks-filekeys/ # hint; default is unset + + # [up2k] if a client uploads a bitflipped/corrupted chunk, store a copy according to --bf-nc and --bf-dir + bak-flips + + # bak-flips: stop if there's more than NUM files at --kf-dir already; default: 6.3 GiB max (200*32M) + bf-nc: 200 # default + + # bak-flips: store corrupted chunks at PATH; default: folder named 'bf' wherever copyparty was started + bf-dir: /srv/bitflips/ # hint; default = bf + + # bak-flips: log corruption info to a textfile at PATH + bf-log: /srv/bitflips/the-history.txt # hint; default is unset + + ############################################################################################### + ############################################################################################### + ############################################################################################### + ############################################################################################### + ############################################################################################### + ############################################################################################### + #####/ + ##### This is the end of the [global] config section + ####/ + +[accounts] + foo: bar # username foo, password bar + +[groups] + g1: u1, u2, u3 # group "g1" with users u1, u2, and u3 + +[/the/url/to/share/this/volume/on/] + /the/actual/filesystem/path/ + accs: + r: username_who_gets_Read_access + w: username_who_gets_Write_access + m: username_who_gets_Move_access + d: username_who_gets_Delete_access + .: username_who_can_see_Dotfiles + g: username_who_gets_Get_access + G: username_who_gets_upGet_access + h: username_who_gets_html_access + a: username_who_gets_admin_access + A: username_who_gets_ReadWriteMoveDeleteDotfileAdmin_access + rwm: @g1 # the group "g1" gets Read+Write+Move + flags: + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// uploads, general \\000000000000000000000000000000000000000000000000000000000000000000000\ + + # enable symlink-based file deduplication + dedup + + # enable hardlink-based file deduplication, with fallback on symlinks when that is impossible + hardlink + + # dedup with hardlink only, never symlink; make a full copy if hardlink is impossible + hardlinkonly + + # enable reflink-based file deduplication, with fallback on full copy when that is impossible + reflink + + # verify on-disk data before using it for dedup + safededup + + # take dupe data from clients, even if available on HDD + noclone + + # rejects existing files (instead of linking/cloning them) + nodupe + + # unix-permission for new dirs/folders + chmod_d: 755 + + # unix-permission for new files + chmod_f: 644 + + # change owner of new files/folders to unix-user 573 + uid: 573 + + # change owner of new files/folders to unix-group 999 + gid: 999 + + # force use of sparse files, mainly for s3-backed storage + sparse + + # deny use of sparse files, mainly for slow storage + nosparse + + # enable full WebDAV write support (dangerous); PUT-operations will now OVERWRITE existing files + daw + + # forces all uploads into the top folder of the vfs + nosub + + # enables filetype detection for nameless uploads + magic + + # fallback filename for nameless uploads + put_name + + # default checksum-hasher for PUT/WebDAV uploads + put_ck + + # default checksum-hasher for bup/basic uploads + bup_ck + + # allows server-side gzip compression of uploads with ?gz + gz + + # allows server-side lzma compression of uploads with ?xz + xz + + # forces server-side compression, optional arg: xz,9 + pk + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// upload rules \\0000000000000000000000000000000000000000000000000000000000000000000000000\ + + # max 250 uploads over 15min + maxn: 250,600 + + # max 1 GiB over 5min (suffixes: b, k, m, g, t) + maxb: 1g,300 + + # total volume size max 1 GiB (suffixes: b, k, m, g, t) + vmaxb: 1g + + # max 4096 files in volume (suffixes: b, k, m, g, t) + vmaxn: 4k + + # return medialinks for non-up2k uploads (not hotlinks) + medialinks + + # write-only users can upload logues without getting renamed + wo_up_readme + + # force randomized filenames, 9 chars long by default + rand + + # randomized filenames are N chars long + nrand: N + + # overwrite existing files? 0=no 1=if-older 2=always + u2ow: N + + # [f]orce [c]lient-last-modified or [u]pload-time + u2ts: fc + + # allow aborting unfinished uploads? 0=no 1=strict 2=ip-chk 3=acct-chk + u2abort: 1 + + # allow filesizes between 1 KiB and 3MiB + sz: 1k-3m + + # ensure 1 GiB free disk space + df: 1g + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// upload rotation -- (moves all uploads into the specified folder structure) \\00000000000\ + + # 3 levels of subfolders with 100 entries in each + rotn: 100,3 + + # date-formatted organizing + rotf: %Y-%m/%d-%H + + # uploads are deleted after 1 hour + lifetime: 3600 + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// database, general \\00000000000000000000000000000000000000000000000000000000000000000000\ + + # enable database; makes files searchable + enables upload-undo + e2d + + # scan writable folders for new files on startup; also sets -e2d + e2ds + + # scans all folders for new files on startup; also sets -e2d + e2dsa + + # enable multimedia indexing; makes it possible to search for tags + e2t + + # scan existing files for tags on startup; also sets -e2t + e2ts + + # delete all metadata from DB (full rescan); also sets -e2ts + e2tsr + + # disables metadata collection for existing files + d2ts + + # verify integrity on startup by hashing files and comparing to db + e2v + + # when e2v fails, update the db (assume on-disk files are good) + e2vu + + # when e2v fails, panic and quit copyparty + e2vp + + # disables onboot indexing, overrides -e2ds* + d2ds + + # disables metadata collection, overrides -e2t* + d2t + + # disables file verification, overrides -e2v* + d2v + + # disables all database stuff, overrides -e2* + d2d + + # puts thumbnails and indexes at that location + hist: /tmp/cdb + + # puts indexes at that location + dbpath: /tmp/cdb + + # disable db if file foo doesn't exist + landmark: foo + + # scan for new files every 60sec, same as --re-maxage + scan: 60 + + # skips hashing file contents if path matches *.iso + nohash: \.iso$ + + # fully ignores the contents at paths matching *.iso + noidx: \.iso$ + + # don't forget files when deleted from disk + noforget + + # forget uploader-IP after 30 days (GDPR) + forget_ip: 43200 + + # never store uploader-IP in the db; disables unpost + no_db_ip + + # avoid excessive reindexing on android sdcardfs + fat32 + + # database speed-durability tradeoff + dbd: [acid|swal|wal|yolo] + + # cross-volume dupe detection / linking (dangerous) + xlink + + # do not descend into other filesystems + xdev + + # do not follow symlinks leaving the volume root + xvol + + # show dotfiles in search results + dotsrch + + # hide dotfiles in search results (default) + nodotsrch + + # exclude search results with URL matching this regex + srch_excl + + # media-tags to index/display + mte: artist,title + + # media-tags to hide by default + mth: fmt,res,ac + + # uses the "audio-bpm.py" program to generate ".bpm" tags from uploads (f = overwrite tags) + mtp: .bpm=f,audio-bpm.py + + # collects two tags at once + mtp: ahash,vhash=media-hash.py + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// thumbnails \\000000000000000000000000000000000000000000000000000000000000000000000000000\ + + # disables all thumbnails + dthumb + + # disables video thumbnails + dvthumb + + # disables audio thumbnails (spectrograms) + dathumb + + # disables image thumbnails + dithumb + + # compress audio waveforms 33% better + pngquant + + # thumbnail res; WxH + thsize + + # center-cropping (y/n/fy/fn) + crop + + # 3x resolution (y/n/fy/fn) + th3x + + # conversion timeout in seconds + convt + + # use /b.png as thumbnail for file-extension s + ext_th: s=/b.png + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// handlers -- (better explained in --help-handlers) \\000000000000000000000000000000000000\ + + # handle 404s by executing PY file + on404: ~/bin/hook.py + + # handle 403s by executing PY file + on403: ~/bin/hook.py + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// event hooks -- (better explained in --help-hooks) \\000000000000000000000000000000000000\ + + # execute CMD before a file upload starts + xbu: ~/bin/hook.py + + # execute CMD after a file upload finishes + xau: ~/bin/hook.py + + # execute CMD after all uploads finish and volume is idle + xiu: ~/bin/hook.py + + # execute CMD before a file copy + xbc: ~/bin/hook.py + + # execute CMD after a file copy + xac: ~/bin/hook.py + + # execute CMD before a file rename/move + xbr: ~/bin/hook.py + + # execute CMD after a file rename/move + xar: ~/bin/hook.py + + # execute CMD before a file delete + xbd: ~/bin/hook.py + + # execute CMD after a file delete + xad: ~/bin/hook.py + + # execute CMD on message + xm: ~/bin/hook.py + + # execute CMD if someone gets banned + xban: ~/bin/hook.py + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// client and ux \\000000000000000000000000000000000000000000000000000000000000000000000000\ + + # show grid/thumbnails by default + grid + + # select files in grid by ctrl-click + gsel + + # default sort order + sort + + # natural-sort of leading digits in filenames + nsort + + # number of sort-rules to add to media URLs + hsortn + + # dont list files matching REGEX + unlist + + # includes TXT in the <head>, or @PATH for file at PATH + html_head: <script>alert(1);</script> + + # theme color (a hint for webbrowsers, discord, etc.) + tcolor: #fc0 + + # don't show total folder size + nodirsz + + # allows indexing by search engines (default) + robots + + # kindly asks search engines to leave + norobots + + # don't list read-access in controlpanel + unlistcr + + # don't list write-access in controlpanel + unlistcw + + # disable js sandbox for markdown files + no_sb_md + + # disable js sandbox for prologue/epilogue + no_sb_lg + + # enable js sandbox for markdown files (default) + sb_md + + # enable js sandbox for prologue/epilogue (default) + sb_lg + + # list of markdown-sandbox safeguards to disable + md_sbf + + # list of *logue-sandbox safeguards to disable + lg_sbf + + # value of iframe allow-prop for markdown-sandbox + md_sba + + # value of iframe allow-prop for *logue-sandbox + lg_sba + + # return html and markdown as text/html + nohtml + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// opengraph (discord embeds) \\00000000000000000000000000000000000000000000000000000000000\ + + # enable OG (disables hotlinking) + og + + # sitename; defaults to --name, disable with '-' + og_site + + # description text for all files; disable with '-' + og_desc + + # thumbnail format; j / jf / jf3 / w / w3 / ... + og_th: jf + + # audio title format; default: {{ artist }} - {{ title }} + og_title_a + + # video title format; default: {{ title }} + og_title_v + + # image title format; default: {{ title }} + og_title_i + + # fallback title if there's nothing in the db + og_title: foo + + # force default title; do not read from tags + og_s_title + + # custom html; see --og-tpl in --help + og_tpl + + # you want to add tags manually with og_tpl + og_no_head + + # if defined: only send OG html if useragent matches this regex + og_ua + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// textfiles \\0000000000000000000000000000000000000000000000000000000000000000000000000000\ + + # where to put markdown backups; s=subfolder, v=volHist, n=nope + md_hist + + # enable textfile expansion; see --help-exp + exp + + # placeholders to expand in markdown files; see --help + exp_md + + # placeholders to expand in prologue/epilogue; see --help + exp_lg + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// tailing \\000000000000000000000000000000000000000000000000000000000000000000000000000000\ + + # disable ?tail (download a growing file continuously) + notail + + # check if file was replaced (new fd) every 1 sec + tail_fd: 1 + + # check for new data every 0.2 sec + tail_rate: 0.2 + + # kill connection after 30 sec + tail_tmax: 30 + + # restrict ?tail access (1=admins,2=authed,3=everyone) + tail_who: 2 + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// others \\0000000000000000000000000000000000000000000000000000000000000000000000000000000\ + + # allow all users with read-access to enable the option to show dotfiles in listings + dots + + # generates per-file accesskeys, which are then required at the "g" permission; keys are invalidated if filesize or inode changes + fk: 8 + + # generates slightly weaker per-file accesskeys, which are then required at the "g" permission; not affected by filesize or inode numbers + fka: 8 + + # generates per-directory accesskeys, which are then required at the "g" permission; keys are invalidated if filesize or inode changes + dk: 8 + + # per-directory accesskeys allow browsing into subdirs + dks + + # allow seeing files (not folders) inside a specific folder with "g" perm, and does not require a valid dirkey to do so + dky + + # allow '?rss' URL suffix (experimental) + rss + + # expensive analysis for mimetype accuracy + rmagic + + # restrict viewing the list of recent uploads + ups_who: 2 + + # restrict access to download-as-zip/tar + zip_who: 2 + + # reject download-as-zip if more than 9000 files + zipmaxn: 9k + + # reject download-as-zip if size over 2 GiB + zipmaxs: 2g + + # reply with 'no' if download-as-zip exceeds max + zipmaxt: no + + # zip-size-limit does not apply to authenticated users + zipmaxu + + # disable race-the-beam (download unfinished uploads) + nopipe + + # ms-windows: timeout for renaming busy files + mv_retry + + # ms-windows: timeout for deleting busy files + rm_retry + + # ask webdav clients to login for all folders + davauth + + # show lastmod time of symlink destination, not the link itself (note: this option is always enabled for recursive listings) + davrt From 00cb1f74e22dea5041c74b2389ac3087a4444041 Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Wed, 6 Aug 2025 20:23:39 +0000 Subject: [PATCH 04/30] golf --- copyparty/web/browser.js | 4 ++-- copyparty/web/md2.js | 6 +++--- copyparty/web/mde.js | 6 +++--- copyparty/web/shares.js | 8 +++----- copyparty/web/up2k.js | 2 +- copyparty/web/util.js | 10 +++++----- 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index d7982c78..df630251 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -11957,7 +11957,7 @@ function ev_load_m3u(e) { function () { load_m3u(url); }, function () { if (has(perms, 'write') && has(perms, 'delete')) - window.location = url + '?edit'; + location = url + '?edit'; else showfile.show(url); } @@ -12556,7 +12556,7 @@ var treectl = (function () { r.reqls = function (url, hpush, back, hydrate) { if (IE && !history.pushState) - return window.location = url; + return location = url; var xhr = new XHR(), m = /[?&](k=[^&#]+)/.exec(url), diff --git a/copyparty/web/md2.js b/copyparty/web/md2.js index f7d0b59c..26b1e14b 100644 --- a/copyparty/web/md2.js +++ b/copyparty/web/md2.js @@ -255,7 +255,7 @@ function Modpoll() { } console.log('modpoll...'); - var url = (document.location + '').split('?')[0] + '?_=' + Date.now(); + var url = (location + '').split('?')[0] + '?_=' + Date.now(); var xhr = new XHR(); xhr.open('GET', url, true); xhr.responseType = 'text'; @@ -346,7 +346,7 @@ function save(e) { fd.append("lastmod", (force ? -1 : last_modified)); fd.append("body", txt); - var url = (document.location + '').split('?')[0]; + var url = (location + '').split('?')[0]; var xhr = new XHR(); xhr.open('POST', url, true); xhr.responseType = 'text'; @@ -404,7 +404,7 @@ function save_cb() { function run_savechk(lastmod, txt, btn, ntry) { // download the saved doc from the server and compare - var url = (document.location + '').split('?')[0] + '?_=' + Date.now(); + var url = (location + '').split('?')[0] + '?_=' + Date.now(); var xhr = new XHR(); xhr.open('GET', url, true); xhr.responseType = 'text'; diff --git a/copyparty/web/mde.js b/copyparty/web/mde.js index 5c2872df..058825c9 100644 --- a/copyparty/web/mde.js +++ b/copyparty/web/mde.js @@ -6,7 +6,7 @@ var dom_doc = ebi('m'); var dom_md = ebi('mt'); (function () { - var n = document.location + ''; + var n = location + ''; n = (n.slice(n.indexOf('//') + 2).split('?')[0] + '?v').split('/'); n[0] = 'top'; var loc = []; @@ -113,7 +113,7 @@ function save(mde) { fd.append("lastmod", (force ? -1 : last_modified)); fd.append("body", txt); - var url = (document.location + '').split('?')[0]; + var url = (location + '').split('?')[0]; var xhr = new XHR(); xhr.open('POST', url, true); xhr.responseType = 'text'; @@ -166,7 +166,7 @@ function save_cb() { //alert('save OK -- wrote ' + r.size + ' bytes.\n\nsha512: ' + r.sha512); // download the saved doc from the server and compare - var url = (document.location + '').split('?')[0] + '?_=' + Date.now(); + var url = (location + '').split('?')[0] + '?_=' + Date.now(); var xhr = new XHR(); xhr.open('GET', url, true); xhr.responseType = 'text'; diff --git a/copyparty/web/shares.js b/copyparty/web/shares.js index d14dfeb7..281de673 100644 --- a/copyparty/web/shares.js +++ b/copyparty/web/shares.js @@ -1,11 +1,9 @@ -var SRS = SR.trimEnd('/') + '/'; - var t = QSA('a[k]'); for (var a = 0; a < t.length; a++) t[a].onclick = rm; function rm() { - var u = SRS + '?eshare=rm&skey=' + uricom_enc(this.getAttribute('k')), + var u = SR + '/?eshare=rm&skey=' + uricom_enc(this.getAttribute('k')), xhr = new XHR(); xhr.open('POST', u, true); @@ -15,7 +13,7 @@ function rm() { function bump() { var k = this.closest('tr').getElementsByTagName('a')[2].getAttribute('k'), - u = SRS + '?skey=' + uricom_enc(k) + '&eshare=' + this.value, + u = SR + '/?skey=' + uricom_enc(k) + '&eshare=' + this.value, xhr = new XHR(); xhr.open('POST', u, true); @@ -27,7 +25,7 @@ function cb() { if (this.status !== 200) return modal.alert('<h6>server error</h6>' + esc(unpre(this.responseText))); - document.location = '?shares'; + location = '?shares'; } function qr(e) { diff --git a/copyparty/web/up2k.js b/copyparty/web/up2k.js index 31ed1d6a..1e49fc4e 100644 --- a/copyparty/web/up2k.js +++ b/copyparty/web/up2k.js @@ -1572,7 +1572,7 @@ function up2k_init(subtle) { function linklist() { var ret = [], - base = document.location.origin.replace(/\/$/, ''); + base = location.origin.replace(/\/$/, ''); for (var a = 0; a < st.files.length; a++) { var t = st.files[a], diff --git a/copyparty/web/util.js b/copyparty/web/util.js index 929316e5..e58e7cca 100644 --- a/copyparty/web/util.js +++ b/copyparty/web/util.js @@ -120,7 +120,7 @@ function esc(txt) { function basenames(txt) { return (txt + '').replace(/https?:\/\/[^ \/]+\//g, '/').replace(/js\?_=[a-zA-Z]{4}/g, 'js'); } -if ((document.location + '').indexOf(',rej,') + 1) +if ((location + '').indexOf(',rej,') + 1) window.onunhandledrejection = function (e) { var err = e.reason; try { @@ -741,7 +741,7 @@ function assert_vp(path) { if (path.indexOf('//') + 1) throw 'nonlocal1: ' + path; - var o = window.location.origin; + var o = location.origin; if (have_URL && (new URL(path, o)).origin != o) throw 'nonlocal2: ' + path; } @@ -893,7 +893,7 @@ function uricom_adec(arr, li) { function get_evpath() { - var ret = document.location.pathname; + var ret = location.pathname; if (ret.indexOf('/') !== 0) ret = '/' + ret; @@ -1249,10 +1249,10 @@ function hist_replace(url) { function sethash(hv) { if (window.history && history.replaceState) { - hist_replace(document.location.pathname + document.location.search + '#' + hv); + hist_replace(location.pathname + location.search + '#' + hv); } else { - document.location.hash = hv; + location.hash = hv; } } From 0f55a1ae86675490fad2297770aa7c0f063bc031 Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Wed, 6 Aug 2025 20:24:31 +0000 Subject: [PATCH 05/30] fix js-crash for url `//`; closes #487 --- copyparty/web/browser.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index df630251..de66887e 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -6742,6 +6742,10 @@ var ACtx = !IPHONE && (window.AudioContext || window.webkitAudioContext), dk, mp; +if (location.pathname.indexOf('//') === 0) + hist_replace(location.pathname.replace(/^\/+/, '/')); + + if (window.og_fn) { hash0 = 1; hist_replace(vsplit(get_evpath())[0]); From a57f7cc2f859a4d1ed09f5ce928728f11ec3a1b8 Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Wed, 6 Aug 2025 20:25:29 +0000 Subject: [PATCH 06/30] ie9: recent-uploads --- copyparty/httpcli.py | 4 ++++ copyparty/web/rups.html | 9 +-------- copyparty/web/rups.js | 8 +++++--- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index d562485b..274316d5 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -5447,6 +5447,8 @@ class HttpCli(object): elif nfi == 3: if not vp.endswith(vfi): continue + else: + continue n -= 1 if not n: @@ -5571,6 +5573,8 @@ class HttpCli(object): elif nfi == 3: if not vp.endswith(vfi): continue + else: + continue if not dots and "/." in vp: continue diff --git a/copyparty/web/rups.html b/copyparty/web/rups.html index af90eefe..2cc0443a 100644 --- a/copyparty/web/rups.html +++ b/copyparty/web/rups.html @@ -19,14 +19,7 @@ <a href="{{ r }}/?h">control-panel</a>   Filter: <input type="text" id="filter" size="20" placeholder="documents/passwords" />   <span id="hits"></span> - <table id="tab"><thead><tr> - <th>size</th> - <th>who</th> - <th>when</th> - <th>age</th> - <th>dir</th> - <th>file</th> - </tr></thead><tbody id="tb"></tbody></table> + <div id="tw"></div> </div> <a href="#" id="repl">π</a> <script> diff --git a/copyparty/web/rups.js b/copyparty/web/rups.js index 9559be8c..17a44556 100644 --- a/copyparty/web/rups.js +++ b/copyparty/web/rups.js @@ -1,5 +1,6 @@ function render() { - var ups = V.ups, now = V.now, html = []; + var html = ['<table id="tab"><thead><tr><th>size</th><th>who</th><th>when</th><th>age</th><th>dir</th><th>file</th></tr></thead><tbody>']; + var ups = V.ups, now = V.now; ebi('filter').value = V.filter; ebi('hits').innerHTML = 'showing ' + ups.length + ' files'; @@ -26,7 +27,8 @@ function render() { var t = V.filter ? ' matching the filter' : ''; html = ['<tr><td colspan="6">there are no uploads' + t + '</td></tr>']; } - ebi('tb').innerHTML = html.join(''); + html.push('</tbody></table>'); + ebi('tw').innerHTML = html.join('\n'); } render(); @@ -46,7 +48,7 @@ function ask(e) { V = JSON.parse(this.responseText) } catch (ex) { - ebi('tb').innerHTML = '<tr><td colspan="6">failed to decode server response as json: <pre>' + esc(this.responseText) + '</pre></td></tr>'; + ebi('tw').innerHTML = 'failed to decode server response as json: <pre>' + esc(this.responseText) + '</pre>'; return; } render(); From 7ae84dea1a105483d5131b167ce7d5da981e16c3 Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Wed, 6 Aug 2025 20:30:01 +0000 Subject: [PATCH 07/30] autorefresh controlpanel --- copyparty/__main__.py | 1 + copyparty/httpcli.py | 8 ++++++++ copyparty/web/splash.html | 2 +- copyparty/web/splash.js | 3 +++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 3bb8c69e..3f07bfc1 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1583,6 +1583,7 @@ def add_ui(ap, retry): ap2.add_argument("--ver", action="store_true", help="show version on the control panel (incompatible with \033[33m-nb\033[0m)") ap2.add_argument("--k304", metavar="NUM", type=int, default=0, help="configure the option to enable/disable k304 on the controlpanel (workaround for buggy reverse-proxies); [\033[32m0\033[0m] = hidden and default-off, [\033[32m1\033[0m] = visible and default-off, [\033[32m2\033[0m] = visible and default-on") ap2.add_argument("--no304", metavar="NUM", type=int, default=0, help="configure the option to enable/disable no304 on the controlpanel (workaround for buggy caching in browsers); [\033[32m0\033[0m] = hidden and default-off, [\033[32m1\033[0m] = visible and default-off, [\033[32m2\033[0m] = visible and default-on") + ap2.add_argument("--ctl-re", metavar="SEC", type=int, default=1, help="the controlpanel Refresh-button will autorefresh every SEC; [\033[32m0\033[0m] = just once") ap2.add_argument("--md-sbf", metavar="FLAGS", type=u, default="downloads forms popups scripts top-navigation-by-user-activation", help="list of capabilities to allow in the iframe 'sandbox' attribute for README.md docs (volflag=md_sbf); see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox") ap2.add_argument("--lg-sbf", metavar="FLAGS", type=u, default="downloads forms popups scripts top-navigation-by-user-activation", help="list of capabilities to allow in the iframe 'sandbox' attribute for prologue/epilogue docs (volflag=lg_sbf)") ap2.add_argument("--md-sba", metavar="TXT", type=u, default="", help="the value of the iframe 'allow' attribute for README.md docs, for example [\033[32mfullscreen\033[0m] (volflag=md_sba)") diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 274316d5..b335fe4a 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -5071,6 +5071,13 @@ class HttpCli(object): self.reply(zb, mime="text/plain; charset=utf-8") return True + re_btn = "" + nre = self.args.ctl_re + if "re" in self.uparam: + self.out_headers["Refresh"] = str(nre) + elif nre: + re_btn = "&re=%s" % (nre,) + html = self.j2s( "splash", this=self, @@ -5088,6 +5095,7 @@ class HttpCli(object): mtpq=vs["mtpq"], dbwt=vs["dbwt"], url_suf=suf, + re=re_btn, k304=self.k304(), no304=self.no304(), k304vis=self.args.k304 > 0, diff --git a/copyparty/web/splash.html b/copyparty/web/splash.html index 2fdc264f..8dd56bab 100644 --- a/copyparty/web/splash.html +++ b/copyparty/web/splash.html @@ -15,7 +15,7 @@ <body> <div id="wrap"> {%- if not in_shr %} - <a id="a" href="{{ r }}/?h" class="af">refresh</a> + <a id="a" href="{{ r }}/?h{{ re }}" class="af">refresh</a> <a id="v" href="{{ r }}/?hc" class="af">connect</a> {%- if this.uname == '*' %} diff --git a/copyparty/web/splash.js b/copyparty/web/splash.js index bad3615f..d3d395af 100644 --- a/copyparty/web/splash.js +++ b/copyparty/web/splash.js @@ -385,6 +385,9 @@ if (o && /[0-9]+$/.exec(o.innerHTML)) ebi('uhash').value = '' + location.hash; +if (/\&re=/.test('' + location)) + ebi('a').className = 'af g'; + (function() { if (!ebi('x')) return; From 50f46187f112bb62b33af94539a1c3e61b98fe52 Mon Sep 17 00:00:00 2001 From: chamdim <94919340+chamdim@users.noreply.github.com> Date: Thu, 7 Aug 2025 00:00:42 +0300 Subject: [PATCH 08/30] Greek translation (#468) * Greek translation * Update browser.js * greek: fixes before merge --------- Signed-off-by: chamdim <94919340+chamdim@users.noreply.github.com> Signed-off-by: ed <s@ocv.me> Co-authored-by: ed <s@ocv.me> --- copyparty/web/browser.js | 631 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 630 insertions(+), 1 deletion(-) diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index de66887e..60b4a349 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -3152,6 +3152,635 @@ var Ls = { "lang_set": "ladataanko sivu uudestaan kielen vaihtamiseksi?", }, + "grc": { + "tt": "Ελληνικά", + + "cols": { + "c": "κουμπιά ενεργειών", + "dur": "διάρκεια", + "q": "ποιότητα / bitrate", + "Ac": "κωδικοποιητής ήχου", + "Vc": "κωδικοποιητής βίντεο", + "Fmt": "μορφή / container", + "Ahash": "checksum ήχου", + "Vhash": "checksum βίντεο", + "Res": "ανάλυση", + "T": "τύπος αρχείου", + "aq": "ποιότητα ήχου / bitrate", + "vq": "ποιότητα βίντεο / bitrate", + "pixfmt": "subsampling / δομή εικονοστοιχείων", + "resw": "οριζόντια ανάλυση", + "resh": "κάθετη ανάλυση", + "chs": "κανάλια ήχου", + "hz": "συχνότητα δειγματοληψίας" + }, + + "hks": [ + [ + "διάφορα", + ["ESC", "κλείσιμο διαφόρων λειτουργιών"], + + "διαχειριστής αρχείων", + ["G", "εναλλαγή λίστας / πλέγματος"], + ["T", "εναλλαγή μικρογραφιών / εικονιδίων"], + ["⇧ A/D", "μέγεθος μικρογραφιών"], + ["ctrl-K", "διαγραφή επιλεγμένων"], + ["ctrl-X", "αποκοπή επιλογής στο πρόχειρο"], + ["ctrl-C", "αντιγραφή επιλογής στο πρόχειρο"], + ["ctrl-V", "επικόλληση (μετακίνηση/αντιγραφή) εδώ"], + ["Y", "λήψη επιλεγμένων"], + ["F2", "μετονομασία επιλεγμένων"], + + "λίστα αρχείων", + ["space", "εναλλαγή επιλογής αρχείου"], + ["↑/↓", "μετακίνηση δείκτη επιλογής"], + ["ctrl ↑/↓", "μετακίνηση δείκτη και προβολής"], + ["⇧ ↑/↓", "επιλογή προηγούμενου/επόμενου αρχείου"], + ["ctrl-A", "επιλογή όλων των αρχείων / φακέλων"] + ], [ + "πλοήγηση", + ["B", "εναλλαγή σε καρτέλες διαδρομών / δέντρο διαδρομών"], + ["I/K", "προηγούμενος/επόμενος φάκελος"], + ["M", "γονικός φάκελος (ή σμίκρυνση τρέχοντος)"], + ["V", "εναλλαγή φακέλων / δέντρο αρχείων κειμένου"], + ["A/D", "μέγεθος πίνακα πλοήγησης"] + ], [ + "μουσική", + ["J/L", "προηγούμενο/επόμενο τραγούδι"], + ["U/O", "μετάβαση 10δευτ πίσω/μπροστά"], + ["0..9", "μετάβαση στο 0%..90%"], + ["P", "αναπαραγωγή/παύση (ξεκινάει κιόλας)"], + ["S", "επιλογή αναπαραγόμενου τραγουδιού"], + ["Y", "λήψη τραγουδιού"] + ], [ + "εικόνες", + ["J/L, ←/→", "προηγούμενη/επόμενη εικόνα"], + ["Home/End", "πρώτη/τελευταία εικόνα"], + ["F", "πλήρης οθόνη"], + ["R", "περιστροφή δεξιόστροφα"], + ["⇧ R", "περιστροφή αριστερόστροφα"], + ["S", "επιλογή εικόνας"], + ["Y", "λήψη εικόνας"] + ], [ + "βίντεο", + ["U/O", "μετάβαση 10δευτ πίσω/μπροστά"], + ["P/K/Space", "αναπαραγωγή/παύση"], + ["C", "συνέχεια στο επόμενο"], + ["V", "επανάληψη"], + ["M", "σίγαση"], + ["[ και ]", "ορισμός διαστήματος επανάληψης"] + ], [ + "αρχεία κειμένου", + ["I/K", "προηγούμενο/επόμενο αρχείο"], + ["M", "κλείσιμο αρχείου"], + ["E", "επεξεργασία αρχείου"], + ["S", "επιλογή αρχείου (για αποκοπή/αντιγραφή/μετονομασία)"] + ] + ], + + "m_ok": "Εντάξει", + "m_ng": "Άκυρο", + + "enable": "Ενεργοποίηση", + "danger": "ΚΙΝΔΥΝΟΣ", + "clipped": "αντιγράφηκε στο πρόχειρο", + + "ht_s1": "δευτερόλεπτο", + "ht_s2": "δευτερόλεπτα", + "ht_m1": "λεπτό", + "ht_m2": "λεπτά", + "ht_h1": "ώρα", + "ht_h2": "ώρες", + "ht_d1": "μέρα", + "ht_d2": "μέρες", + "ht_and": " και ", + + "goh": "πίνακας ελέγχου", + "gop": 'προηγούμενος φάκελος στο ίδιο επίπεδο">προηγούμενο', + "gou": 'γονικός φάκελος">πάνω', + "gon": 'επόμενος φάκελος">επόμενο', + "logout": "Αποσύνδεση ", + "access": " πρόσβαση", + "ot_close": "κλείσιμο υπομενού", + "ot_search": "αναζήτηση αρχείων με βάση χαρακτηριστικά, διαδρομή / όνομα, μουσικά tags ή οποιονδήποτε συνδυασμό$N$N<code>foo bar</code> = πρέπει να περιέχει και τα «foo» και «bar»,$N<code>foo -bar</code> = πρέπει να περιέχει το «foo» αλλά όχι το «bar»,$N<code>^yana .opus$</code> = να ξεκινά με «yana» και να είναι αρχείο «opus»$N<code>"try unite"</code> = να περιέχει ακριβώς «try unite»$N$Nη μορφή ημερομηνίας είναι iso-8601, όπως$N<code>2009-12-31</code> ή <code>2020-09-12 23:30:00</code>", + "ot_unpost": "unpost: διαγραφή πρόσφατων μεταφορτώσεων ή ακύρωση ανολοκλήρωτων", + "ot_bup": "bup: βασικός uploader, υποστηρίζει μέχρι και netscape 4.0", + "ot_mkdir": "mkdir: δημιουργία νέου φακέλου", + "ot_md": "new-md: δημιουργία νέου markdown εγγράφου", + "ot_msg": "msg: αποστολή μηνύματος στο server log", + "ot_mp": "επιλογές media player", + "ot_cfg": "επιλογές ρυθμίσεων", + "ot_u2i": 'up2k: ανέβασε αρχεία (αν έχεις δικαίωμα εγγραφής) ή ενεργοποίησε τη λειτουργία αναζήτησης για να δεις αν υπάρχουν ήδη στο server$N$Nοι μεταφορτώσεις συνεχίζονται αν διακοπούν, είναι πολυνηματικές και διατηρούν τις χρονοσφραγίδες, αλλά καταναλώνουν περισσότερο CPU από τον [🎈]  (βασικός uploader)<br /><br />κατά τη διάρκεια της μεταφόρτωσης, αυτό το εικονίδιο δείχνει την πρόοδό της!', + "ot_u2w": 'up2k: ανέβασε αρχεία με υποστήριξη συνέχισης (κλείσε τον browser και ρίξε τα ίδια αρχεία ξανά μετά)$N$Nπολυνηματικό, διατηρεί τις χρονοσφραγίδες, αλλά καταναλώνει περισσότερο CPU από τον [🎈]  (βασικός uploader)<br /><br />κατά τη διάρκεια της μεταφόρτωσης, αυτό το εικονίδιο δείχνει την πρόοδό της!', + "ot_noie": 'Χρησιμοποίησε Chrome / Firefox / Edge', + + "ab_mkdir": "δημιουργία φακέλου", + "ab_mkdoc": "νέο markdown έγγραφο", + "ab_msg": "στείλε μήνυμα στο server log", + + "ay_path": "πήγαινε σε φακέλους", + "ay_files": "πήγαινε σε αρχεία", + + "wt_ren": "μετονομασία επιλεγμένων$NΣυντόμευση: F2", + "wt_del": "διαγραφή επιλεγμένων$NΣυντόμευση: ctrl-K", + "wt_cut": "αποκοπή επιλεγμένων <small>(και επικόλληση αλλού)</small>$NΣυντόμευση: ctrl-X", + "wt_cpy": "αντιγραφή επιλεγμένων στο πρόχειρο$N(για επικόλληση αλλού)$NΣυντόμευση: ctrl-C", + "wt_pst": "επικόλληση αποκομμένων / αντεγραμμένων$NΣυντόμευση: ctrl-V", + "wt_selall": "επιλογή όλων$NΣυντόμευση: ctrl-A (με το αρχείο επιλεγμένο)", + "wt_selinv": "αντιστροφή επιλογής", + "wt_zip1": "κατέβασμα φακέλου ως συμπιεσμένο αρχείο", + "wt_selzip": "κατέβασμα επιλογής ως συμπιεσμένο αρχείο", + "wt_seldl": "κατέβασμα επιλογής ως μεμονωμένα αρχεία$NΣυντόμευση: Y", + "wt_npirc": "αντιγραφή πληροφοριών τραγουδιού σε μορφή irc", + "wt_nptxt": "αντιγραφή πληροφοριών τραγουδιού ως κείμενο", + "wt_m3ua": "προσθήκη σε m3u λίστα αναπαραγωγής (μετά πάτησε <code>📻αντιγραφή</code>)", + "wt_m3uc": "αντιγραφή m3u λίστας αναπαραγωγής στο πρόχειρο", + "wt_grid": "εναλλαγή πλέγματος / λίστας$NΣυντόμευση: G", + "wt_prev": "προηγούμενο κομμάτι$NΣυντόμευση: J", + "wt_play": "αναπαραγωγή / παύση$NΣυντόμευση: P", + "wt_next": "επόμενο κομμάτι$NΣυντόμευση: L", + + "ul_par": "παράλληλες μεταφορτώσεις:", + "ut_rand": "τυχαιοποίηση ονομάτων αρχείων", + "ut_u2ts": "αντιγραφή της τελευταίας τροποποιημένης χρονοσφραγίδας αλλαγής$Nαπό το σύστημά σου στον server\">📅", + "ut_ow": "αντικατάσταση σε ήδη υπάρχοντα αρχεία του server?$N🛡️: ποτέ (θα δημιουργηθεί νέο όνομα)$N🕒: αν το αρχείο του server είναι παλαιότερο$N♻️: πάντα να αντικαθίστανται αν διαφέρουν", + "ut_mt": "συνέχιση υπολογισμού hash για άλλα αρχεία κατά τη μεταφόρτωση$N$Nαπενεργοποίησέ το αν η CPU ή ο δίσκος σου ζορίζονται", + "ut_ask": 'επιβεβαίωση πριν ξεκινήσει η μεταφόρτωση">💭', + "ut_pot": "βελτίωση ταχύτητας μεταφόρτωσης σε αργές συσκευές$Nμε απλοποίηση του UI", + "ut_srch": "μην ανεβάζεις, έλεγξε αν τα αρχεία$Nυπάρχουν ήδη στον server (ψάχνει σε όλους τους φακέλους που έχεις πρόσβαση)", + "ut_par": "κάνε παύση στις μεταφορτώσεις βάζοντάς το 0$N$Nαύξησε το αν έχεις αργή/μεγάλη καθυστέρηση σύνδεσης$N$Nκράτα το 1 σε LAN ή αν ο server έχει αργό δίσκο", + "ul_btn": "ρίξε αρχεία / φακέλους<br>εδώ (ή κάνε κλικ σε μένα)", + "ul_btnu": "Μ Ε Τ Α Φ Ο Ρ Τ Ω Σ Η", + "ul_btns": "Α Ν Α Ζ Η Τ Η Σ Η", + + "ul_hash": "υπολογισμός hash", + "ul_send": "αποστολή", + "ul_done": "ολοκληρώθηκε", + "ul_idle1": "καμία μεταφόρτωση στην ουρά για την ώρα", + "ut_etah": "μέση ταχύτητα <em>υπολογισμού hash</em> και εκτίμηση χρόνου μέχρι την ολοκλήρωση", + "ut_etau": "μέση ταχύτητα <em>μεταφόρτωσης</em> και εκτίμηση χρόνου μέχρι την ολοκλήρωση", + "ut_etat": "μέση <em>συνολική</em> ταχύτητα και εκτίμηση χρόνου μέχρι την ολοκλήρωση", + + "uct_ok": "ολοκληρώθηκε επιτυχώς", + "uct_ng": "no-good: απέτυχε / απορρίφθηκε / δεν βρέθηκε", + "uct_done": "ολοκληρωμένα και αποτυχημένα", + "uct_bz": "κάνει hash ή μεταφορτώνει", + "uct_q": "σε αναμονή, εκκρεμεί", + + "utl_name": "όνομα αρχείου", + "utl_ulist": "λίστα", + "utl_ucopy": "αντιγραφή", + "utl_links": "σύνδεσμοι", + "utl_stat": "κατάσταση", + "utl_prog": "πρόοδος", + + // keep short: + "utl_404": "404", + "utl_err": "ΣΦΑΛΜΑ", + "utl_oserr": "ΣΦ-ΛΕ", + "utl_found": "βρέθηκε", + "utl_defer": "αναβολή", + "utl_yolo": "YOLO", + "utl_done": "έγινε", + + "ul_flagblk": "τα αρχεία προστέθηκαν στην ουρά</b><br>αλλά υπάρχει άλλη ενεργή μεταφόρτωση σε άλλη καρτέλα,<br>οπότε περίμενε να τελειώσει αυτό πρώτα", + "ul_btnlk": "ο διακομιστής έχει κλειδώσει αυτήν την επιλογή σε αυτήν την κατάσταση", + + "udt_up": "Μεταφόρτωση", + "udt_srch": "Αναζήτηση", + "udt_drop": "ρίξ' το εδώ", + + "u_nav_m": '<h6>οκ, τι έχουμε εδώ;</h6><code>Enter</code> = Αρχεία (ένα ή περισσότερα)\n<code>ESC</code> = Ένας φάκελος (μαζί με υποφακέλους)', + "u_nav_b": '<a href="#" id="modal-ok">Αρχεία</a><a href="#" id="modal-ng">Ένας φάκελος</a>', + + "cl_opts": "διακόπτες", + "cl_themes": "θέμα", + "cl_langs": "γλώσσα", + "cl_ziptype": "λήψη φακέλου", + "cl_uopts": "διακόπτες μεταφόρτωσης", + "cl_favico": "favicon", + "cl_bigdir": "μεγάλοι φάκελοι", + "cl_hsort": "#ταξινόμηση", + "cl_keytype": "σημείωση πλήκτρων", + "cl_hiddenc": "κρυφές στήλες", + "cl_hidec": "κρύψε", + "cl_reset": "επανεκκίνηση", + "cl_hpick": "πάτησε στις κεφαλίδες στηλών για να τις κρύψεις στον πίνακα παρακάτω", + "cl_hcancel": "η απόκρυψη στηλών ακυρώθηκε", + + "ct_grid": '田 το πλέγμα', + "ct_ttips": '◔ ◡ ◔">ℹ️ συμβουλές εργαλείων', + "ct_thumb": 'σε προβολή πλέγματος, εναλλαγή εικονιδίων ή μικρογραφιών$NΠλήκτρο συντόμευσης: T">🖼️ μικρογραφίες', + "ct_csel": 'χρησιμοποίησε CTRL και SHIFT για επιλογή αρχείων σε προβολή πλέγματος">επιλογή', + "ct_ihop": 'όταν η προβολή εικόνων κλείνει, κάνε scroll στο τελευταίο προβολόμενο αρχείο">g⮯', + "ct_dots": 'εμφάνιση κρυφών αρχείων (αν το επιτρέπει ο server)">dotfiles', + "ct_qdel": 'όταν διαγράφεις αρχεία, ζήτα επιβεβαίωση μόνο μία φορά">γρήγορη διαγραφή', + "ct_dir1st": 'ταξινόμηση φακέλων πριν από τα αρχεία">📁 πρώτα', + "ct_nsort": 'φυσική ταξινόμηση (για ονόματα αρχείων με αριθμούς στην αρχή)">φυσική ταξινόμηση', + "ct_utc": 'εμφάνιση όλων των ημερομηνιών σε UTC">UTC', + "ct_readme": 'εμφάνιση README.md στις λίστες φακέλων">📜 πληροφορίες', + "ct_idxh": 'εμφάνιση index.html αντί για λίστα φακέλων">html', + "ct_sbars": 'εμφάνιση μπαρών κύλισης">⟊', + + "cut_umod": "αν το αρχείο υπάρχει ήδη στον server, ενημέρωσέ το με την τελευταία χρονοσφραγίδα τροποποίησης για να ταιριάζει με το τοπικό αρχείο (απαιτεί δικαιώματα εγγραφής+διαγραφής)\">re📅", + + "cut_turbo": "το κουμπί yolo, πιθανόν να ΜΗΝ ΘΕΛΕΙΣ να το ενεργοποιήσεις:$N$Nχρησιμοποίησέ το αν μεταφορτώνεις πολλά αρχεία και χρειάστηκε να ξαναρχίσεις, και θες να συνεχίσεις τη μεταφότρωση όσο το δυνατόν πιο γρήγορα$N$Nαντικαθιστά τον έλεγχο hash με απλό <em>"έχει το ίδιο μέγεθος αρχείου στον server?"</em> οπότε αν το περιεχόμενο είναι διαφορετικό, ΔΕΝ θα ανέβει$N$Nπρέπει να το κλείσεις όταν τελειώσει η μεταφόρτωση και μετά να "μεταφορτώσεις" πάλι τα ίδια αρχεία για να τα επιβεβαιώσει το τοπικό σου πρόγραμμα\">turbo", + + "cut_datechk": "δεν επηρεάζει τίποτα εκτός αν το turbo είναι ενεργοποιημένο$N$Nμειώνει λίγο τον παράγοντα yolo; ελέγχει αν οι χρονοσφραγίδες στο διακομιστή ταιριάζουν με τα δικά σου$N$Nπιάνει <em>θεωρητικά</em> τις περισσότερες μισοτελειωμένες/κατεστραμμένες μεταφορτώσεις, αλλά δεν αντικαθιστά τον έλεγχο με το turbo απενεργοποιημένο μετέπειτα\">έλεγχος ημερομηνίας", + + "cut_u2sz": "μέγεθος (σε MiB) κάθε κομματιού μεταφόρτωσης; μεγάλες τιμές λειτουργούν καλύτερα σε μεγαλύτερες αποστάσεις διακομιστή-πελάτη. Δοκίμασε μικρές τιμές σε πολύ άστατες συνδέσεις", + + "cut_flag": "εξασφαλίζει ότι μόνο μία καρτέλα μεταφορτώνει κάθε φορά $N -- οι άλλες καρτέλες πρέπει να το έχουν κι αυτές ενεργό $N -- επηρεάζει μόνο τις καρτέλες που βρίσκονται στο ίδιο διεύθυνση", + + "cut_az": "μεταφόρτωσε τα αρχεία αλφαβητικά, αντί για το μικρότερο αρχείο, πρώτα$N$Nη αλφαβητική σειρά βοηθά να καταλάβεις αν κάτι χάλασε στο διακομιστή αλλά κάνει το ανέβασμα λίγο πιο αργό σε fiber / LAN", + + "cut_nag": "ειδοποίηση λειτουργικού συστήματος όταν τελειώσει η μεταφόρτωση$N(μόνο αν ο browser ή η καρτέλα δεν είναι ενεργά)", + "cut_sfx": "ηχητική ειδοποίηση όταν τελειώσει η μεταφόρτωση$N(μόνο αν ο browser ή η καρτέλα δεν είναι ενεργά)", + + "cut_mt": "χρησιμοποίησε multithreading για να επιταχύνεις το hashing των αρχείων$N$Nχρησιμοποιεί web-workers και χρειάζεται$Nπερισσότερη RAM (μέχρι 512 MiB επιπλέον)$N$Nκάνει το https 30% πιο γρήγορο, το http 4.5x πιο γρήγορο\">mt", + + "cut_wasm": "χρησιμοποίησε wasm αντί για τον ενσωματωμένο hasher του browser; βελτιώνει την ταχύτητα σε chrome-based browsers αλλά αυξάνει το φορτίο της CPU, και παλιές εκδόσεις chrome έχουν bugs που κάνουν το browser να τρώει όλη τη RAM και να κρασάρει αν ενεργοποιηθεί\">wasm", + + "cft_text": "κείμενο favicon (κενό και ανανέωση για απενεργοποίηση)", + "cft_fg": "χρώμα προσκηνίου", + "cft_bg": "χρώμα παρασκηνίου", + + "cdt_lim": "μέγιστος αριθμός αρχείων προς εμφάνιση σε ένα φάκελο", + "cdt_ask": "όταν φτάνεις στο τέλος,$Nαντί να φορτώσει περισσότερα αρχεία,$Nρώτα τι να κάνει", + "cdt_hsort": "πόσους κανόνες ταξινόμησης (<code>,sorthref</code>) να συμπεριλάβει σε URLs πολυμέσων. Αν το βάλεις 0 αγνοεί και κανόνες ταξινόμησης στους συνδέσμους πολυμέσων", + + "tt_entree": "εμφάνιση navpane (δέντρο διαδρομών)$NΠλήκτρο συντόμευσης: B", + "tt_detree": "εμφάνιση breadcrumbs (καρτέλες διαδρομών)$NΠλήκτρο συντόμευσης: B", + "tt_visdir": "κύλιση στον επιλεγμένο φάκελο", + "tt_ftree": "εναλλαγή δέντρου διαδρομών / αρχείων κειμένου$NΠλήκτρο συντόμευσης: V", + "tt_pdock": "εμφάνιση γονικών φακέλων σε σταθερή μπάρα επάνω", + "tt_dynt": "αυτόματη επέκταση καθώς επεκτείνεται το δέντρο διαδρομών", + "tt_wrap": "αναδίπλωση λέξεων", + "tt_hover": "αποκάλυψη των γραμμών που ξεπερνούν το πλάτος με το ποντίκι πάνω τους$N( σπάει το scroll εκτός αν το ποντίκι $N  είναι στην αριστερή στήλη )", + + "ml_pmode": "στο τέλος του φακέλου...", + "ml_btns": "εντολές", + "ml_tcode": "μετακωδικοποίηση", + "ml_tcode2": "μετακωδικοποίηση σε", + "ml_tint": "φίλτρο χρώματος", + "ml_eq": "ισοσταθμιστής ήχου", + "ml_drc": "συμπιεστής δυναμικής εμβέλειας", + + "mt_loop": "επανάληψη ενός τραγουδιού\">🔁", + "mt_one": "σταμάτα μετά από ένα τραγούδι\">1️⃣", + "mt_shuf": "τυχαία σειρά τραγουδιών σε κάθε φάκελο\">🔀", + "mt_aplay": "αυτόματη αναπαραγωγή αν υπάρχει song-ID στη διεύθυνση που μπήκες στο διακομιστή$N$Nη απενεργοποίηση αυτού, σταματά το URL από το να ενημερώνεται με τα song-ID ενώ παίζει η μουσική για να αποτραπεί η αυτόματη αναπαραγωγή αν χαθούν αυτές οι ρυθμίσεις αλλά το URL παραμείνει το ίδιο\">a▶", + "mt_preload": "ξεκίνα τη φόρτωση του επόμενου τραγουδιού κοντά στο τέλος για συνεχόμενη ακρόαση\">προφόρτωση", + "mt_prescan": "πήγαινε στον επόμενο φάκελο πριν τελειώσει το τελευταίο τραγούδι$Nγια να μη σταματήσει το πρόγραμμα περιήγησης να παίζει μουσική\">nav", + "mt_fullpre": "προσπάθησε να προφορτώσεις ολόκληρο το τραγούδι;$N✅ ενεργό σε <b>αναξιόπιστες</b> συνδέσεις,$N❌ πιθανότατα απενεργοποιημένο σε αργές συνδέσεις\">πλήρες", + "mt_fau": "σε κινητά, πρόλαβε να μην σταματήσει η μουσική αν το επόμενο τραγούδι δεν προφορτώθηκε γρήγορα (μπορεί να προκαλέσει πρόβλημα στην εμφάνιση των ετικετών)\">☕️", + "mt_waves": "γραμμή αναζήτησης κυματομορφής:$Nεμφάνιση έντασης ήχου στην μπάρα αναζήτησης\">~s", + "mt_npclip": "εμφάνισε κουμπιά για αντιγραφή του τρέχοντος τραγουδιού\">/np", + "mt_m3u_c": "εμφάνισε κουμπιά για αντιγραφή των$Nεπιλεγμένων τραγουδιών ως καταχωρήσεις λίστας m3u8\">📻", + "mt_octl": "ενσωμάτωση στο λειτουργικό σύστημα (σηντομεύσεις πλήκτρων πολυμέσων / osd)\">έλεγχος-OS", + "mt_oseek": "επιτρέπει την αναζήτηση μέσω ενσωμάτωσης του λειτουργικού συστήματος$N$Nσημείωση: σε μερικές συσκευές (iPhones),$Nαντικαθιστά το κουμπί επόμενου τραγουδιού\">αναζήτηση", + "mt_oscv": "εμφάνιση εξωφύλλου άλμπουμ σε osd\">εξώφυλλο", + "mt_follow": "κρατά το τρέχον κομμάτι ορατό κατά την κύλιση\">🎯", + "mt_compact": "συμπαγή κουμπιά ελέγχου\">⟎", + "mt_uncache": "καθάρισε την προσωρινή μνήμη  (δοκίμασε αυτό αν ο browser έχει αποθηκεύσει$Nχαλασμένο αντίγραφο τραγουδιού και αρνείται να παίξει)\">εκκαθάριση", + "mt_mloop": "τυχαία αναπαραγωγή στον ανοικτό φάκελο\">🔁 τυχαία αναπαραγωγή", + "mt_mnext": "φόρτωση επόμενου φακέλου και συνέχιση\">📂 επόμενο", + "mt_mstop": "σταμάτησε την αναπαραγωγή\">⏸ σταμάτημα", + "mt_cflac": "μετατροπή flac / wav σε opus\">flac", + "mt_caac": "μετατροπή aac / m4a σε opus\">aac", + "mt_coth": "μετατροπή όλων των άλλων (εκτός των mp3) σε opus\">άλλο", + "mt_c2opus": "καλύτερη επιλογή για desktop, laptop, android\">opus", + "mt_c2owa": "opus-weba, για iOS 17.5 και νεότερα\">owa", + "mt_c2caf": "opus-caf, για iOS 11 έως 17\">caf", + "mt_c2mp3": "χρησιμοποίησε αυτό σε πολύ παλιές συσκευές\">mp3", + "mt_c2flac": "βέλτιστη ποιότητα ήχου αλλά τεράστιο αρχείο για μεταφόρτωση\">flac", + "mt_c2wav": "ασυμπίεστη αναπαραγωγή (ακόμα μεγαλύτερο αρχείο)\">wav", + "mt_c2ok": "μια χαρά, σοφή επιλογή", + "mt_c2nd": "δεν είναι η προτεινόμενη μορφή εξόδου για τη συσκευή σου, αλλά αυτό είναι ok", + "mt_c2ng": "η συσκευή σου φαίνεται να μην υποστηρίζει αυτήν τη μορφή εξόδου, αλλά ας το δοκιμάσουμε ούτως ή άλλως", + "mt_xowa": "υπάρχουν bugs σε iOS που εμποδίζουν την αναπαραγωγή στο παρασκήνιο με αυτήν τη μορφή· χρησιμοποίησε caf ή mp3 αντ’ αυτού", + "mt_tint": "επίπεδο φόντου (0-100) στην μπάρα αναζήτησης$Nγια να κάνεις το buffering λιγότερο ενοχλητικό", + "mt_eq": "ενεργοποιεί τον ισοσταθμιστή και τον έλεγχο ενίσχυσης;$N$Nενίσχυση <code>0</code> = στάνταρ 100% ένταση (απαράλλαχτη)$N$Nεύρος <code>1  </code> = στάνταρ στερεοφωνικό (απαράλλαχτο)$Nεύρος <code>0.5</code> = 50% αριστερά-δεξιά μίξη ήχου$Nεύρος <code>0  </code> = μονοφωνικό$N$Nενίσχυση <code>-0.8</code> & εύρος <code>10</code> = αφαίρεση φωνής :^)$N$Nη ενεργοποίηση του ισοσταθμιστή κάνει τα άλμπουμ χωρίς κενά, να παίζουν χωρίς καθόλου κενά, οπότε άφησέ το ενεργό με όλες τις τιμές στο μηδέν (εκτός από εύρος = 1) αν σε νοιάζει", + "mt_drc": "ενεργοποιεί τον συμπιεστή δυναμικής εμβέλειας (εξομάλυνση έντασης / ακραία συμπίεση έντασης); θα ενεργοποιήσει και τον ισοσταθμιστή για να ισορροπήσει τον ήχο, οπότε βάλε όλα τα πεδία ισοσταθμιστή εκτός από το 'εύρος' στο 0 αν δεν το θες$N$Nχαμηλώνει την ένταση του ήχου πάνω από το όριο (THRESHOLD) dB; για κάθε RATIO dB πέρα από το όριο υπάρχει 1 dB εξόδου, οπότε οι προεπιλεγμένες τιμές όριο -24 και 'λόγος' 12 σημαίνουν ότι δεν θα ξεπεράσει ποτέ τα -22 dB και είναι ασφαλές να αυξήσεις την ενίσχυση ισοσταθμιστή σε 0.8, ή και 1.8 με ATK 0 και μεγάλο RLS όπως 90 (δουλεύει μόνο σε firefox· το RLS είναι max 1 σε άλλους browsers)$N$N(δες wikipedia, το εξηγούν καλύτερα)", + + "mb_play": "παίξε", + "mm_hashplay": "να παίξω αυτό το αρχείο ήχου;", + "mm_m3u": "πάτα <code>Enter/OK</code> για Αναπαραγωγή\nπάτα <code>ESC/Άκυρο</code> για Επεξεργασία", + "mp_breq": "χρειάζεται firefox 82+ ή chrome 73+ ή iOS 15+", + "mm_bload": "φορτώνει...", + "mm_bconv": "μετατροπή σε {0}, περίμενε...", + "mm_opusen": "ο browser σου δεν παίζει αρχεία aac / m4a;\nη μετατροπή σε opus είναι τώρα ενεργή", + "mm_playerr": "η αναπαραγωγή, απέτυχε: ", + "mm_eabrt": "Η προσπάθεια αναπαραγωγής ακυρώθηκε", + "mm_enet": "Η σύνδεση του ίντερνέτ σου είναι χάλια", + "mm_edec": "Το αρχείο αυτό είναι μάλλον κατεστραμμένο;;", + "mm_esupp": "Ο browser σου δεν καταλαβαίνει αυτή τη μορφή ήχου", + "mm_eunk": "Άγνωστο σφάλμα", + "mm_e404": "Αδύνατη η αναπαραγωγή ήχου; σφάλμα 404: Το αρχείο δεν βρέθηκε.", + "mm_e403": "Αδύνατη η αναπαραγωγή ήχου; σφάλμα 403: Άρνηση πρόσβασης.\n\nΔοκίμασε F5 για επαναφόρτωση, ίσως να έχεις αποσυνδεθεί", + "mm_e500": "Αδύνατη η αναπαραγωγή ήχου; σφάλμα 500: Έλεγξε τα logs του διακομιστή.", + "mm_e5xx": "Αδύνατη η αναπαραγωγή ήχου; σφάλμα διακομιστή", + "mm_nof": "δεν βρέθηκαν άλλα αρχεία ήχου τριγύρω", + "mm_prescan": "Αναζήτηση μουσικής για επόμενο τραγούδι...", + "mm_scank": "Βρέθηκε το επόμενο τραγούδι:", + "mm_uncache": "κρυφή μνήμη καθαρίστηκε· όλα τα τραγούδια θα ξανακατεβούν στην επόμενη αναπαραγωγή", + "mm_hnf": "το τραγούδι αυτό πλέον δεν υπάρχει", + + "im_hnf": "η εικόνα αυτή πλέον δεν υπάρχει", + + "f_empty": "αυτός ο φάκελος είναι άδειος", + "f_chide": "αυτό θα κρύψει τη στήλη «{0}»\n\nμπορείς να εμφανίσεις τις στήλες από τις ρυθμίσεις", + "f_bigtxt": "αυτό το αρχείο είναι {0} MiB σε μέγεθος — σίγουρα θέλεις να το δεις ως κείμενο;", + "f_bigtxt2": "να δεις μόνο το τέλος του αρχείου αντί για όλο; αυτό ενεργοποιεί και το following/tailing, που δείχνει νέες γραμμές που προστίθενται ζωντανά", + "fbd_more": '<div id="blazy">εμφανίζονται <code>{0}</code> από <code>{1}</code> αρχεία; <a href="#" id="bd_more">δείξε {2}</a> ή <a href="#" id="bd_all">δείξε τα όλα</a></div>', + "fbd_all": '<div id="blazy">εμφανίζονται <code>{0}</code> από <code>{1}</code> αρχεία; <a href="#" id="bd_all">δείξε όλα</a></div>', + "f_anota": "μόνο {0} από τα {1} αντικείμενα επιλέχθηκαν;\nγια να επιλέξεις ολόκληρο το φάκελο, κύλησε πρώτα μέχρι κάτω", + + "f_dls": 'οι σύνδεσμοι αρχείων στον τρέχοντα φάκελο έχουν\nμετατραπεί σε συνδέσμους λήψης', + + "f_partial": "Για να κατεβάσεις με ασφάλεια ένα αρχείο που ανεβαίνει, κλίκαρε το αρχείο με το ίδιο όνομα, αλλά χωρίς την κατάληξη <code>.PARTIAL</code>. Πάτα Άκυρο ή Escape για να σταματήσεις.\n\nΠάτα OK / Enter αν αγνοείς την προειδοποίηση και κατέβασε το <code>.PARTIAL</code> αρχείο, που σχεδόν σίγουρα θα είναι κατεστραμμένο.", + + "ft_paste": "επικόλλησε {0} αντικείμενα$NΠλήκτρο συντόμευσης: ctrl-V", + "fr_eperm": 'δεν μπορεί να μετονομαστεί:\nδεν έχεις δικαίωμα “μετακίνησης” σε αυτόν το φάκελο', + "fd_eperm": 'δεν μπορεί να διαγραφεί:\nδεν έχεις δικαίωμα “διαγραφής” σε αυτόν το φάκελο', + "fc_eperm": 'δεν μπορεί να κοπεί:\nδεν έχεις δικαίωμα “μετακίνησης” σε αυτόν το φάκελο', + "fp_eperm": 'δεν μπορεί να επικολληθεί:\nδεν έχεις δικαίωμα “εγγραφής” σε αυτόν το φάκελο', + "fr_emore": "επίλεξε τουλάχιστον ένα αντικείμενο για μετονομασία", + "fd_emore": "επίλεξε τουλάχιστον ένα αντικείμενο για διαγραφή", + "fc_emore": "επίλεξε τουλάχιστον ένα αντικείμενο για αποκοπή", + "fcp_emore": "επίλεξε τουλάχιστον ένα αντικείμενο για αντιγραφή στο πρόχειρο", + + "fs_sc": "μοιράσου το φάκελο που βρίσκεσαι", + "fs_ss": "μοιράσου τα επιλεγμένα αρχεία", + "fs_just1d": "δεν μπορείς να επιλέξεις περισσότερους από έναν φακέλους,\nή να αναμείξεις αρχεία και φακέλους στην ίδια επιλογή", + "fs_abrt": "❌ ακύρωση", + "fs_rand": "🎲 τυχαίο όνομα", + "fs_go": "✅ δημιούργησε κοινή χρήση", + "fs_name": "όνομα", + "fs_src": "πηγή", + "fs_pwd": "κωδικός", + "fs_exp": "λήξη", + "fs_tmin": "λεπτά", + "fs_thrs": "ώρες", + "fs_tdays": "ημέρες", + "fs_never": "αιώνιο", + "fs_pname": "προαιρετικό όνομα συνδέσμου; αν είναι κενό, θα είναι τυχαίο", + "fs_tsrc": "το αρχείο ή ο φάκελος προς κοινή χρήση", + "fs_ppwd": "προαιρετικός κωδικός", + "fs_w8": "δημιουργία κοινής χρήσης...", + "fs_ok": "πάτα <code>Enter/OK</code> για Πρόχειρο\nπάτα <code>ESC/Άκυρο</code> για Κλείσιμο", + + "frt_dec": "μπορεί να διορθώσει μερικές περιπτώσεις κατεστραμμένων ονομάτων αρχείων\">αποκωδικοποίηση url", + "frt_rst": "επανέφερε τα ονόματα αρχείων στα αρχικά τους\">↺ επαναφορά", + "frt_abrt": "ακύρωσε και κλείσε αυτό το παράθυρο\">❌ ακύρωση", + "frb_apply": "ΕΦΑΡΜΟΓΗ ΜΕΤΟΝΟΜΑΣΙΑΣ", + "fr_adv": "μαζική / μεταδεδομένα / μετονομασία με πρότυπα\">προχωρημένη", + "fr_case": "regex με διάκριση πεζών/κεφαλαίων\">case", + "fr_win": "ασφαλή ονόματα για windows; αντικαθιστά <code><>:"\\|?*</code> με ιαπωνικούς χαρακτήρες πλήρους πλάτους\">win", + "fr_slash": "αντικαθίσταται <code>/</code> με χαρακτήρα που δεν δημιουργεί νέους φακέλους\">όχι /", + "fr_re": "μοτίβα αναζήτησης (regex) για αναζήτηση στα αρχικά ονόματα; τα καταγραφόμενα groups μπορούν να χρησιμοποιηθούν στο πεδίο μορφοποίησης παρακάτω όπως <code>(1)</code> και <code>(2)</code> και ούτω καθεξής", + "fr_fmt": "εμπνευσμένο από foobar2000:$N<code>(title)</code> αντικαθίσταται από τίτλο τραγουδιού,$N<code>[(artist) - ](title)</code> παραλείπει το [this] αν το artist είναι κενό$N<code>$lpad((tn),2,0)</code> γεμίζει τον αριθμό κομματιού σε 2 ψηφία", + "fr_pdel": "διαγραφή", + "fr_pnew": "αποθήκευση ως", + "fr_pname": "δώσε όνομα για τη νέα προεπιλογή", + "fr_aborted": "ακυρώθηκε", + "fr_lold": "παλιό όνομα", + "fr_lnew": "νέο όνομα", + "fr_tags": "ετικέτες για τα επιλεγμένα αρχεία (μόνο για ανάγνωση):", + "fr_busy": "μετονομασία {0} αντικειμένων...\n\n{1}", + "fr_efail": "αποτυχία μετονομασίας:\n", + "fr_nchg": "{0} από τα νέα ονόματα άλλαξαν λόγω <code>win</code> και/ή <code>όχι /</code>\n\nΕίναι ΟΚ να συνεχίσουμε με αυτά τα ονόματα;", + + "fd_ok": "διαγραφή OK", + "fd_err": "αποτυχία διαγραφής:\n", + "fd_none": "δεν διαγράφηκε τίποτα; ίσως μπλοκαρισμένο από τις ρυθμίσεις του διακομιστή (xbd);", + "fd_busy": "διαγραφή {0} αντικειμένων...\n\n{1}", + "fd_warn1": "ΔΙΑΓΡΑΦΗ αυτών των {0} αντικειμένων;", + "fd_warn2": "<b>Τελευταία ευκαιρία!</b> Δεν υπάρχει αναίρεση. Διαγραφή;", + + "fc_ok": "αποκοπή {0} αντικειμένων", + "fc_warn": 'αποκοπή {0} αντικειμένων\n\nαλλά: μόνο <b>αυτή</b> η καρτέλα browser μπορεί να τα επικολλήσει\n(λόγω πολύ μεγάλης επιλογής)', + + "fcc_ok": "αντιγράφηκαν {0} αντικείμενα στο πρόχειρο", + "fcc_warn": "αντιγράφηκαν {0} αντικείμενα στο πρόχειρο\n\nαλλά: μόνο <b>αυτή</b> η καρτέλα browser μπορεί να τα επικολλήσει\n(λόγω πολύ μεγάλης επιλογής)", + + "fp_apply": "χρησιμοποίησε αυτά τα ονόματα", + "fp_ecut": "πρώτα κάνε αποκοπή ή αντιγραφή κάποιων αρχείων / φακέλων για επικόλληση / μετακίνηση\n\nσημείωση: μπορείς να αποκόπτεις / επικολλάς ανάμεσα σε διαφορετικές καρτέλες browser", + "fp_ename": "τα {0} αντικείμενα δεν μπορούν να μετακινηθούν εδώ γιατί τα ονόματα υπάρχουν ήδη. Δώσε νέα ονόματα παρακάτω για να συνεχίσεις, ή άφησε κενό για να τα αγνοήσεις:", + "fcp_ename": "τα {0} αντικείμενα δεν μπορούν να αντιγραφούν εδώ γιατί τα ονόματα υπάρχουν ήδη. Δώσε νέα ονόματα παρακάτω για να συνεχίσεις, ή άφησε κενό για να τα αγνοήσεις:", + "fp_emore": "υπάρχουν ακόμα συγκρούσεις ονομάτων που πρέπει να διορθωθούν", + "fp_ok": "μετακίνηση OK", + "fcp_ok": "αντιγραφή OK", + "fp_busy": "μετακίνηση {0} αντικειμένων...\n\n{1}", + "fcp_busy": "αντιγραφή {0} αντικειμένων...\n\n{1}", + "fp_err": "αποτυχία μετακίνησης:\n", + "fcp_err": "αποτυχία αντιγραφής:\n", + "fp_confirm": "να μετακινηθούν αυτά τα {0} αντικείμενα εδώ;", + "fcp_confirm": "να αντιγραφούν αυτά τα {0} αντικείμενα εδώ;", + "fp_etab": "αποτυχία ανάγνωσης πρόχειρου από άλλη καρτέλα browser", + "fp_name": "μεταφόρτωση αρχείου από τη συσκευή σου. Δώσε του όνομα:", + "fp_both_m": '<h6>διάλεξε τι θα επικολλήσεις</h6><code>Enter</code> = Μετακίνηση {0} αρχείων από «{1}»\n<code>ESC</code> = Μεταφόρτωση {2} αρχείων από τη συσκευή σου', + "fcp_both_m": '<h6>διάλεξε τι θα επικολλήσεις</h6><code>Enter</code> = Αντιγραφή {0} αρχείων από «{1}»\n<code>ESC</code> = Μεταφόρτωση {2} αρχείων από τη συσκευή σου', + "fp_both_b": '<a href="#" id="modal-ok">Μετακίνηση</a><a href="#" id="modal-ng">Μεταφόρτωση</a>', + "fcp_both_b": '<a href="#" id="modal-ok">Αντιγραφή</a><a href="#" id="modal-ng">Μεταφόρτωση</a>', + + "mk_noname": "γράψε ένα όνομα στο πεδίο κειμένου αριστερά πριν το κάνεις :p", + + "tv_load": "Φόρτωση αρχείου κειμένου:\n\n{0}\n\n{1}% ({2} από {3} MiB φορτωμένα)", + "tv_xe1": "αδυναμία φόρτωσης αρχείου κειμένου:\n\nσφάλμα ", + "tv_xe2": "404, αρχείο δεν βρέθηκε", + "tv_lst": "λίστα αρχείων κειμένου σε", + "tvt_close": "επιστροφή στην προβολή φακέλου$NΣυντόμευση: M (ή Esc)\">❌ κλείσιμο", + "tvt_dl": "κατέβασε αυτό το αρχείο$NΣυντόμευση: Y\">💾 λήψη", + "tvt_prev": "προβολή προηγούμενου εγγράφου$NΣυντόμευση: i\">⬆ προηγούμενο", + "tvt_next": "προβολή επόμενου εγγράφου$NΣυντόμευση: K\">⬇ επόμενο", + "tvt_sel": "επέλεξε αρχείο   (για αποκοπή / αντιγραφή / διαγραφή / ...)$NΣυντόμευση: S\">επιλογή", + "tvt_edit": "άνοιγμα αρχείου στον επεξεργαστή κειμένου$NΣυντόμευση: E\">✏️ επεξεργασία", + "tvt_tail": "παρακολούθηση αρχείου για αλλαγές; εμφάνιση νέων γραμμών σε πραγματικό χρόνο\">📡 παρακολούθηση", + "tvt_wrap": "αναδίπλωση λέξεων\">↵", + "tvt_atail": "κλείδωμα κύλισης στο κάτω μέρος\">⚓", + "tvt_ctail": "αποκωδικοποίηση χρωμάτων τερματικού (ansi escape codes)\">🌈", + "tvt_ntail": "όριο κύλισης (πόσα bytes κειμένου να κρατούνται φορτωμένα)", + + "m3u_add1": "το τραγούδι προστέθηκε στη λίστα m3u", + "m3u_addn": "προστέθηκαν {0} τραγούδια στη λίστα m3u", + "m3u_clip": "η λίστα m3u αντιγράφηκε στο πρόχειρο\n\nπρέπει να φτιάξεις ένα νέο αρχείο κειμένου με όνομα η_λίστα_μου.m3u και να επικολλήσεις τη λίστα μέσα· αυτό θα το καταστήσει αναπαράξιμο", + + "gt_vau": "μην δείχνεις το βίντεο, παίξε μόνο τον ήχο\">🎧", + "gt_msel": "ενεργοποίηση επιλογής αρχείων; ctrl-κλικ σε αρχείο για παράκαμψη$N$N<em>όταν είναι ενεργό: διπλό κλικ σε αρχείο / φάκελο το ανοίγει</em>$N$NΣυντόμευση: S\">πολλαπλή επιλογή", + "gt_crop": "κεντραρισμένη περικοπή μικρογραφιών\">περικοπή", + "gt_3x": "μικρογραφίες υψηλής ανάλυσης\">3x", + "gt_zoom": "ζουμ", + "gt_chop": "κόψε", + "gt_sort": "ταξινόμηση κατά", + "gt_name": "όνομα", + "gt_sz": "μέγεθος", + "gt_ts": "ημερομηνία", + "gt_ext": "τύπος", + "gt_c1": "μεγαλύτερη περικοπή ονομάτων αρχείων (δείξε λιγότερα)", + "gt_c2": "μικρότερη περικοπή ονομάτων αρχείων (δείξε περισσότερα)", + + "sm_w8": "αναζήτηση...", + "sm_prev": "τα παρακάτω αποτελέσματα αναζήτησης προέρχονται από προηγούμενη αναζήτηση:\n ", + "sl_close": "κλείσιμο αποτελεσμάτων αναζήτησης", + "sl_hits": "εμφανίζονται {0} αποτελέσματα", + "sl_moar": "φόρτωσε περισσότερα", + + "s_sz": "μέγεθος", + "s_dt": "ημερομηνία", + "s_rd": "μονοπάτι", + "s_fn": "όνομα", + "s_ta": "ετικέτες", + "s_ua": "ανέβηκε@", + "s_ad": "προχωρ.", + "s_s1": "ελάχιστο σε MiB", + "s_s2": "μέγιστο σε MiB", + "s_d1": "ελάχιστο iso8601", + "s_d2": "μέγιστο iso8601", + "s_u1": "μεταφορτώθηκε αργότερα", + "s_u2": "και/ή πριν", + "s_r1": "το μονοπάτι περιέχει   (χωρισμένα με κενό)", + "s_f1": "το όνομα περιέχει   (άρνηση με -nope)", + "s_t1": "οι ετικέτες περιέχουν   (^=αρχή, τέλος=$)", + "s_a1": "συγκεκριμένες ιδιότητες μεταδεδομένων", + + "md_eshow": "δεν μπορεί να εμφανιστεί ", + "md_off": "[📜<em>readme</em>] απενεργοποιημένο στο [⚙️] -- κρυμμένο έγγραφο", + + "badreply": "Αποτυχία ανάλυσης απάντησης από το διακομιστή", + + "xhr403": "403: Πρόσβαση αρνήθηκε\n\nδοκίμασε το F5, ίσως αποσυνδέθηκες", + "xhr0": "άγνωστο (πιθανόν αποσύνδεση από το διακομιστή ή ο διακομιστής είναι εκτός σύνδεσης)", + "cf_ok": "συγγνώμη γι' αυτό -- η προστασία DD" + wah + "oS ενεργοποιήθηκε\n\nοι διαδικασίες θα συνεχιστούν σε περίπου 30 δευτερόλεπτα\n\nαν δεν γίνει τίποτα, πάτα F5 για επαναφόρτωση", + "tl_xe1": "αδύνατη η λίστα υποφακέλων:\n\nσφάλμα ", + "tl_xe2": "404: Ο φάκελος δεν βρέθηκε", + "fl_xe1": "αδύνατη η λίστα αρχείων σε φάκελο:\n\nσφάλμα ", + "fl_xe2": "404: Ο φάκελος δεν βρέθηκε", + "fd_xe1": "αδύνατη η δημιουργία υποφακέλου:\n\nσφάλμα ", + "fd_xe2": "404: Ο γονικός φάκελος δεν βρέθηκε", + "fsm_xe1": "αδύνατη η αποστολή μηνύματος:\n\nσφάλμα ", + "fsm_xe2": "404: Ο γονικός φάκελος δεν βρέθηκε", + "fu_xe1": "αποτυχία φόρτωσης λίστας unpost από το διακομιστή:\n\nσφάλμα ", + "fu_xe2": "404: Το αρχείο δεν βρέθηκε??", + + "fz_tar": "μη συμπιεσμένο αρχείο gnu-tar (linux / mac)", + "fz_pax": "μη συμπιεσμένο pax-format tar (πιο αργό)", + "fz_targz": "gnu-tar με συμπίεση gzip επίπεδο 3$N$Nσυνήθως πολύ αργό, οπότε$Nχρησιμοποίησε καλύτερα μη συμπιεσμένο tar", + "fz_tarxz": "gnu-tar με συμπίεση xz επίπεδο 1$N$Nσυνήθως πολύ αργό, οπότε$Nχρησιμοποίησε καλύτερα μη συμπιεσμένο tar", + "fz_zip8": "zip με ονόματα αρχείων utf8 (ίσως να κολλάει σε windows 7 και παλιότερα)", + "fz_zipd": "zip με παραδοσιακά ονόματα cp437, για πολύ παλιό λογισμικό", + "fz_zipc": "cp437 με crc32 υπολογισμένο νωρίτερα,$Nγια MS-DOS PKZIP v2.04g (οκτώβριος 1993)$N(παίρνει παραπάνω χρόνο πριν ξεκινήσει η μεταφόρτωση)", + + "un_m1": "μπορείς να διαγράψεις τα πρόσφατα αρχεία που μεταφόρτωσες (ή να ακυρώσεις τα μισοτελειωμένα) παρακάτω", + "un_upd": "ανανέωση", + "un_m4": "ή μοιράσου τα αρχεία που βλέπεις παρακάτω:", + "un_ulist": "εμφάνιση", + "un_ucopy": "αντιγραφή", + "un_flt": "προαιρετικό φίλτρο:  η διεύθυνση πρέπει να περιέχει", + "un_fclr": "καθαρισμός φίλτρου", + "un_derr": "αποτυχία διαγραφής unpost:\n", + "un_f5": "κάτι χάλασε, δοκίμασε την ανανέωση ή πάτα F5", + "un_uf5": "συγγνώμη αλλά πρέπει να ανανεώσεις τη σελίδα (πχ με F5 ή CTRL-R) πριν ακυρώσεις αυτήν την αποστολή", + "un_nou": "<b>προσοχή:</b> ο διακομιστής είναι πολύ φορτωμένος για να δείξει μισοτελειωμένες αποστολές· πάτα την ανανέωση, σε λίγο", + "un_noc": "<b>προσοχή:</b> το unpost των ολοκληρωμένων αρχείων δεν επιτρέπεται από τη ρύθμιση του διακομιστή", + "un_max": "εμφανίζονται τα πρώτα 2000 αρχεία (χρησιμοποίησε φίλτρο)", + "un_avail": "μπορείς να διαγράψεις {0} πρόσφατα αρχεία<br />μπορείς να ακυρώσεις {1} μισοτελειωμένες αποστολές", + "un_m2": "ταξινομημένα κατά χρόνο μεταφόρτωσης; τα πιο πρόσφατα πρώτα:", + "un_no1": "άκυρο! καμία μεταφόρτωση δεν είναι αρκετά πρόσφατη", + "un_no2": "άκυρο! καμία μεταφόρτωση με αυτό το φίλτρο δεν είναι αρκετά πρόσφατη", + "un_next": "διάγραψε τα επόμενα {0} αρχεία παρακάτω", + "un_abrt": "άκυρο", + "un_del": "διαγραφή", + "un_m3": "φορτώνω τις πρόσφατες μεταφορτώσεις σου...", + "un_busy": "διαγράφω {0} αρχεία...", + "un_clip": "αντιγράφηκαν {0} σύνδεσμοι στο πρόχειρο", + + "u_https1": "πρέπει", + "u_https2": "μετάβαση σε https", + "u_https3": "για καλύτερη απόδοση", + "u_ancient": 'ο browser σου είναι εντυπωσιακά απαρχαιωμένος — ίσως να <a href="#" onclick="goto(\'bup\')">χρησιμοποιήσεις το bup αντί γι\' αυτό</a>', + "u_nowork": "χρειάζεται firefox 53+ ή chrome 57+ ή iOS 11+", + "tail_2old": "χρειάζεται firefox 105+ ή chrome 71+ ή iOS 14.5+", + "u_nodrop": "ο browser σου είναι πολύ παλιός για drag&drop μεταφορτώσεις", + "u_notdir": "αυτός δεν είναι φάκελος!\n\nο browser σου είναι πολύ παλιός,\nδοκίμασε drag&drop αντ' αυτού", + "u_uri": "για να κάνεις drag&drop εικόνων από άλλα παράθυρα browser,\nρίξ' τες πάνω στο μεγάλο κουμπί μεταφόρτωσης", + "u_enpot": 'άλλαξε στο <a href="#">potato UI</a> (ίσως ανεβάζει πιο γρήγορα)', + "u_depot": 'άλλαξε στο <a href="#">fancy UI</a> (ίσως ανεβάζει πιο αργά)', + "u_gotpot": "αλλάζω στο potato UI για πιο γρήγορη μεταφόρτωση,\n\nμπορείς να το αλλάξεις πάλι αν θες!", + "u_pott": "<p>αρχεία:   <b>{0}</b> ολοκληρωμένα,   <b>{1}</b> αποτυχημένα,   <b>{2}</b> σε εξέλιξη,   <b>{3}</b> σε ουρά</p>", + "u_ever": "αυτός είναι ο βασικός uploader; το up2k θέλει τουλάχιστον<br>chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1", + "u_su2k": 'αυτός είναι ο βασικός uploader; το <a href="#" id="u2yea">up2k</a> είναι καλύτερο', + "u_uput": "βελτιστοποίηση για ταχύτητα (παράλειψη ελέγχου ακεραιότητας)", + "u_ewrite": "δεν έχεις δικαίωμα εγγραφής σε αυτόν τον φάκελο", + "u_eread": "δεν έχεις δικαίωμα ανάγνωσης σε αυτόν τον φάκελο", + "u_enoi": "η αναζήτηση αρχείων δεν είναι ενεργοποιημένη στο αρχείο ρυθμίσεων του διακομιστή", + "u_enoow": "δεν μπορείς να κάνεις αντικατάσταση εδώ· χρειάζεται δικαίωμα Διαγραφής", + "u_badf": "Αυτά τα {0} αρχεία (από {1} συνολικά) παραλείφθηκαν, πιθανώς λόγω δικαιωμάτων συστήματος αρχείων:\n\n", + "u_blankf": "Αυτά τα {0} αρχεία (από {1} συνολικά) είναι άδεια / κενά· να τα μεταφορτώσω έτσι κι αλλιώς;\n\n", + "u_applef": "Αυτά τα {0} αρχεία (από {1} συνολικά) πιθανώς δεν είναι επιθυμητά;\nΠάτα <code>OK/Enter</code> για ΝΑ ΑΓΝΟΗΘΟΥΝ τα παρακάτω αρχεία,\nΠάτα <code>Cancel/ESC</code> για ΝΑ ΜΗΝ ΑΠΟΚΛΕΙΣΤΟΥΝ και να ΜΕΤΑΦΟΡΤΩΘΟΎΝ κι αυτά:\n\n", + "u_just1": "\nΊσως δουλέψει καλύτερα αν επιλέξεις μόνο ένα αρχείο", + "u_ff_many": "αν χρησιμοποιείς <b>Linux / MacOS / Android,</b> τότε τόσα αρχεία <a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1790500\" target=\"_blank\"><em>μπορεί</em> να κατάρρευση του Firefox!</a>\nαν γίνει αυτό, δοκίμασε ξανά (ή χρησιμοποίησε τον Chrome).", + "u_up_life": "Αυτή η μεταφόρτωση θα διαγραφεί από το διακομιστή\n{0} μετά την ολοκλήρωσή της", + "u_asku": "μεταφόρτωση αυτών των {0} αρχείων στο <code>{1}</code>", + "u_unpt": "μπορείς να αναιρέσεις / διαγράψεις αυτήν τη μεταφόρτωση χρησιμοποιώντας το 🧯, πάνω αριστερά", + "u_bigtab": "θα εμφανιστούν {0} αρχεία\n\nαυτό μπορεί να κάνει τον browser σου να κολλήσει, είσαι σίγουρος;", + "u_scan": "Σάρωση αρχείων...", + "u_dirstuck": "ο επεξεργαστής φακέλων κόλλησε προσπαθώντας να προσπελάσει τα εξής {0} αντικείμενα· θα τα παραλείψει:", + "u_etadone": "Ολοκληρώθηκε ({0}, {1} αρχεία)", + "u_etaprep": "(προετοιμασία για μεταφόρτωση)", + "u_hashdone": "το hashing ολοκληρώθηκε", + "u_hashing": "υπολογισμός hash", + "u_hs": "handshaking...", + "u_started": "τα αρχεία ανεβαίνουν τώρα· δες τα στο [🚀]", + "u_dupdefer": "διπλότυπο; θα επεξεργαστεί μετά από όλα τα άλλα αρχεία", + "u_actx": "πάτα αυτό το κείμενο για να μην χάσεις<br />απόδοση όταν αλλάζεις παράθυρα/καρτέλες", + "u_fixed": "ΟΚ!  Το διόρθωσα 👍", + "u_cuerr": "αποτυχία μεταφόρτωσης τμήματοςς {0} από {1};\nπιθανώς ακίνδυνο, συνεχίζω\n\nαρχείο: {2}", + "u_cuerr2": "ο διακομιστής απέρριψε τη μεταφόρτωση (τμήμα {0} από {1});\nθα ξαναδοκιμάσει αργότερα\n\nαρχείο: {2}\n\nσφάλμα ", + "u_ehstmp": "θα ξαναδοκιμάσει; δες κάτω δεξιά", + "u_ehsfin": "ο διακομιστής απέρριψε το αίτημα ολοκλήρωσης της μεταφόρτωσης; ξαναδοκιμάζει...", + "u_ehssrch": "ο διακομιστής απέρριψε το αίτημα αναζήτησης; ξαναδοκιμάζει...", + "u_ehsinit": "ο διακομιστής απέρριψε το αίτημα για εκκίνηση μεταφόρτωσης; ξαναδοκιμάζει...", + "u_eneths": "σφάλμα δικτύου κατά το handshake μεταφόρτωσης; ξαναδοκιμάζει...", + "u_enethd": "σφάλμα δικτύου κατά τον έλεγχο ύπαρξης στόχου; ξαναδοκιμάζει...", + "u_cbusy": "ο διακομιστής περιμένει να μας εμπιστευτεί ξανά μετά από πρόβλημα δικτύου...", + "u_ehsdf": "ο διακομιστής έμεινε από χώρο στο δίσκο!\n\nθα συνεχίσει να ξαναδοκιμάζει,\nσε περίπτωση που κάποιος\nελευθερώσει αρκετό χώρο για συνέχεια", + "u_emtleak1": "φαίνεται πως ο browser σου έχει διαρροή μνήμης;\nπαρακαλώ", + "u_emtleak2": ' <a href="{0}">αλλαγή σε https (συνιστάται)</a> ή ', + "u_emtleak3": ' ', + "u_emtleakc": 'δοκίμασε τα εξής:\n<ul><li>πάτα <code>F5</code> για ανανέωση σελίδας</li><li>μετά απενεργοποίησε το  <code>mt</code>  κουμπί στις  <code>⚙️ ρυθμίσεις</code></li><li>και δοκίμασε ξανά τη μεταφόρτωση</li></ul>Οι μεταφορτώσιες θα είναι λίγο πιο αργές, αλλά ok.\nΣυγγνώμη για την ταλαιπωρία!\n\nPS: το chrome v107 <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1354816" target="_blank">έχει διόρθωση γι\' αυτό</a>', + "u_emtleakf": 'δοκίμασε τα εξής:\n<ul><li>πάτα <code>F5</code> για ανανέωση σελίδας</li><li>μετά άνοιξε το <code>🥔</code> (potato) στο UI μεταφόρτωσης<li>και δοκίμασε ξανά τη μεταφόρτωση</li></ul>\nPS: ο firefox <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1790500" target="_blank">ελπίζει να φτιάξει αυτό το bug</a> κάποια στιγμή', + "u_s404": "δεν βρέθηκε στο διακομιστή", + "u_expl": "επεξήγηση", + "u_maxconn": "οι περισσότεροι browser το περιορίζουν στα 6, αλλά ο firefox σου επιτρέπει να το αυξήσεις με <code>connections-per-server</code> στο <code>about:config</code>", + "u_tu": '<p class="warn">ΠΡΟΕΙΔΟΠΟΙΗΣΗ: το turbo είναι ενεργοποιημένο, <span> το πρόγραμμα πελάτη ίσως να μην ανιχνεύσει και να μην ξαναεκκινήσει μισοτελειωμένες μεταφορτώσεις; δες τα tooltip του κουμπιού turbo</span></p>', + "u_ts": '<p class="warn">ΠΡΟΕΙΔΟΠΟΙΗΣΗ: το turbo είναι ενεργοποιημένο, <span> τα αποτελέσματα αναζήτησης μπορεί να είναι λάθος; δες τα tooltip του κουμπιού turbo</span></p>', + "u_turbo_c": "το turbo είναι απενεργοποιημένο στο αρχείο ρυθμίσεων του διακομιστή", + "u_turbo_g": "απενεργοποιώ το turbo επειδή δεν έχεις δικαίωμα\nγια τη λίστα φακέλων σε αυτόν τον τόμο", + "u_life_cfg": 'αυτόματη διαγραφή μετά από <input id="lifem" p="60" /> λεπτά (ή <input id="lifeh" p="3600" /> ώρες)', + "u_life_est": 'η μεταφόρτωση θα διαγραφεί <span id="lifew" tt="τοπική ώρα">---</span>', + "u_life_max": 'αυτός ο φάκελος επιβάλλει\nμέγιστη διάρκεια ζωής {0}', + "u_unp_ok": "επιτρέπεται το unpost για {0}", + "u_unp_ng": "δεν επιτρέπεται το unpost", + "ue_ro": "έχεις μόνο δικαίωμα ανάγνωσης σε αυτόν το φάκελο\n\n", + "ue_nl": "δεν είσαι συνδεδεμένος τώρα", + "ue_la": 'είσαι συνδεδεμένος ως "{0}"', + "ue_sr": "είσαι σε λειτουργία αναζήτησης αρχείων\n\nπήγαινε σε λειτουργία μεταφόρτωσης πατώντας το 🔎 (δίπλα στο μεγάλο κουμπί ΑΝΑΖΗΤΗΣΗΣ) και δοκίμασε πάλι\n\nσυγγνώμη", + "ue_ta": "δοκίμασε να μεταφορτώσεις εκ νέου, θα πρέπει να δουλέψει τώρα", + "ue_ab": "αυτό το αρχείο ανεβαίνει σε άλλο φάκελο και η μεταφόρτωση πρέπει να ολοκληρωθεί πριν ανέβει αλλού.\n\nΜπορείς να ακυρώσεις και να ξεχάσεις την αρχική μεταφόρτωση με το κουμπί 🧯 πάνω αριστερά", + "ur_1uo": "ΟΚ: Το αρχείο ανέβηκε επιτυχώς", + "ur_auo": "ΟΚ: Και τα {0} αρχεία ανέβηκαν επιτυχώς", + "ur_1so": "ΟΚ: Το αρχείο βρέθηκε στο διακομιστή", + "ur_aso": "ΟΚ: Και τα {0} αρχεία βρέθηκαν στο διακομιστή", + "ur_1un": "Η μεταφόρτωση απέτυχε, συγγνώμη", + "ur_aun": "Και οι {0} μεταφορτώσεις απέτυχαν, συγγνώμη", + "ur_1sn": "Το αρχείο ΔΕΝ βρέθηκε στο διακομιστή", + "ur_asn": "Τα {0} αρχεία ΔΕΝ βρέθηκαν στο διακομιστή", + "ur_um": "Ολοκληρώθηκε;\n{0} μεταφορτώσεις είναι OK,\n{1} μεταφορτώσεις απέτυχαν, συγγνώμη", + "ur_sm": "Ολοκληρώθηκε;\n{0} αρχεία βρέθηκαν στο διακομιστή,\n{1} αρχεία ΔΕΝ βρέθηκαν στο διακομιστή", + + "lang_set": "ανανέωση σελίδας για εφαρμογή της αλλαγής;" + }, "ita": { "tt": "Italiano", @@ -6298,7 +6927,7 @@ var Ls = { }, }; -var LANGS = ["eng", "nor", "chi", "deu", "fin", "ita", "nld", "rus", "spa", "ukr"]; +var LANGS = ["eng", "nor", "chi", "deu", "fin", "ita", "nld", "rus", "spa", "ukr", "grc"]; if (window.langmod) langmod(); From 392abd0675bfb13f6b74cb7918a39842bfe95475 Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Wed, 6 Aug 2025 21:02:25 +0000 Subject: [PATCH 09/30] add greek splash tl; closes #493 --- copyparty/web/splash.js | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/copyparty/web/splash.js b/copyparty/web/splash.js index d3d395af..14865781 100644 --- a/copyparty/web/splash.js +++ b/copyparty/web/splash.js @@ -177,6 +177,47 @@ var Ls = { "af1": "näytä viimeaikaiset lataukset", "ag1": "näytä tunnetut IdP-käyttäjät", }, + "grc": { + "a1": "ανανέωση", + "b1": "γεια σου ξένε!   <small>(δεν είσαι συνδεδεμένος)</small>", + "c1": "αποσύνδεση", + "d1": "σωρός απορριμμάτων", + "d2": "εμφανίζει την κατάσταση όλων των ενεργών διεργασιών", + "e1": "επαναφόρτωση του cfg", + "e2": "φορτώνει ξανά τα αρχεία ρυθμίσεων (λογαριασμοί/τόμοι/volflags),$Nκαι κάνει επανεξέταση όλων των τόμων e2ds$N$Nσημείωση: οποιαδήποτε αλλαγή στις καθολικές ρυθμίσεις$Nαπαιτεί πλήρη επανεκκίνηση για να εφαρμοστεί", + "f1": "μπορείς να περιηγηθείς:", + "g1": "μπορείς να εκτελέσεις μεταφόρτωση σε:", + "cc1": "άλλα πράγματα:", + "h1": "απενεργοποίση k304", + "i1": "ενεργοποίηση k304", + "j1": "η ενεργοποίηση του k304 θα αποσυνδέσει το πρόγραμμα πελάτη σου σε κάθε HTTP 304, κάτι που μπορεί να αποτρέψει κάποια προβληματικά proxies από το να κολλάνε (να μην φορτώνουν ξαφνικά σελίδες), <em>αλλά</em> θα κάνει τα πράγματα, γενικά πιο αργά", + "k1": "επαναφορά ρυθμίσεων στο πρόγραμμα πελάτη", + "l1": "συνδέσου για περισσότερα:", + "m1": "καλώς ήρθες,", + "n1": "404 δεν βρέθηκε  ┐( ´ -`)┌", + "o1": '´η μήπως δεν έχεις πρόσβαση -- δοκίμασε έναν κωδικό <a href="' + SR + '/?h">πήγαινε στην αρχική</a>', + "p1": "403 απαγορευμένο  ~┻━┻", + "q1": 'δοκίμασε έναν κωδικό <a href="' + SR + '/?h">πήγαινε στην αρχική</a>', + "r1": "πίσω στην αρχική", + ".s1": "επανάληψη σάρωσης", + "t1": "ενέργεια", + "u2": "χρόνος από την τελευταία εγγραφή του διακομιστή$N( μεταφόρτωση / μετονομασία / ... )$N$N17d = 17 days$N1ω23 = 1 ώρα 23 λεπτά$N4λ56 = 4 λεπτά 56 δευτερόλεπτα", + "v1": "σύνδεση", + "v2": "χρησιμοποίησε αυτόν το διακομιστή σαν τοπικό δίσκο", + "w1": "εναλλαγή σε https", + "x1": "αλλαγή κωδικού", + "y1": "επεξεργασία κοινόχρηστων φακέλων", + "z1": "ξεκλείδωμα αυτού του κοινόχρηστου φακέλου:", + "ta1": "συμπλήρωσε πρώτα το νέο σου κωδικό", + "ta2": "επανέλαβε για να επιβεβαιώσεις το νέο κωδικό:", + "ta3": "βρέθηκε τυπογραφικό λάθος· δοκίμασε ξανά", + "aa1": "εισερχόμενα αρχεία:", + "ab1": "απενεργοποίηση no304", + "ac1": "ενεργοποίηση no304", + "ad1": "η ενεργοποίηση του no304 θα απενεργοποιήσει όλη την προσωρινή αποθήκευση· δοκίμασέ το αν το k304 δεν ήταν αρκετό. Προσοχή, θα σπαταλήσει τεράστιο όγκο δικτυακής κίνησης!", + "ae1": "ενεργές μεταφορτώσεις:", + "af1": "προβολή πρόσφατων μεταφορτώσεων", + }, "ita": { "a1": "aggiorna", "b1": "ciao   <small>(non sei connesso)</small>", From 3b26884c691dc23f473e09107ad7f41a4a248fbe Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Wed, 6 Aug 2025 21:08:11 +0000 Subject: [PATCH 10/30] tl cleanup --- copyparty/web/browser.js | 2 +- copyparty/web/splash.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 60b4a349..9c20c5d8 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -6927,7 +6927,7 @@ var Ls = { }, }; -var LANGS = ["eng", "nor", "chi", "deu", "fin", "ita", "nld", "rus", "spa", "ukr", "grc"]; +var LANGS = ["eng", "nor", "chi", "deu", "fin", "grc", "ita", "nld", "rus", "spa", "ukr"]; if (window.langmod) langmod(); diff --git a/copyparty/web/splash.js b/copyparty/web/splash.js index 14865781..1a24beb2 100644 --- a/copyparty/web/splash.js +++ b/copyparty/web/splash.js @@ -174,7 +174,7 @@ var Ls = { "ac1": "ota no304 käyttöön", "ad1": "no304:n lopettaa välimuistin käytön kokonaan; kokeile tätä jos k304 ei riittänyt. Tuhlaa valtavan määrän verkkoliikennettä!", "ae1": "lähtevät:", - "af1": "näytä viimeaikaiset lataukset", + "af1": "näytä viimeaikaiset lataukset", "ag1": "näytä tunnetut IdP-käyttäjät", }, "grc": { @@ -259,7 +259,7 @@ var Ls = { "ae1": "in uscita:", "af1": "mostra i file caricati di recente", "ag1": "mostra utenti IdP conosciuti" -}, + }, "nld": { "a1": "Update", "b1": "Hallo, hoe gaat het met jou?   <small>(Je bent niet ingelogd)</small>", From 29a4e54799564b5124d1a84e62a11dc71739954f Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Wed, 6 Aug 2025 21:10:15 +0000 Subject: [PATCH 11/30] tl cleanup --- copyparty/web/browser.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 9c20c5d8..ea4c3f1f 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -2439,7 +2439,7 @@ var Ls = { "u_https1": "für bessere Performance solltest du", "u_https2": "auf HTTPS wechseln", - "u_https3": "", + "u_https3": " ", "u_ancient": 'Dein Browser ist verdammt antik -- vielleicht solltest du <a href="#" onclick="goto(\'bup\')">stattdessen bup benutzen</a>', "u_nowork": "Benötigt Firefox 53+ oder Chrome 57+ oder iOS 11+", "tail_2old": "Benötigt Firefox 105+ oder Chrome 71+ oder iOS 14.5+", @@ -2489,7 +2489,7 @@ var Ls = { "u_ehsdf": "Server hat kein Speicherplatz mehr!\n\nwerde es erneut versuchen, falls jemand\ngenug Platz schafft um fortzufahren", "u_emtleak1": "scheint, als ob dein Browser ein Memory Leak hätte;\nbitte", "u_emtleak2": ' <a href="{0}">wechsle auf HTTPS (empfohlen)</a> oder ', - "u_emtleak3": '', + "u_emtleak3": ' ', "u_emtleakc": 'versuche folgendes:\n<ul><li>drücke <code>F5</code> um die Seite neu zu laden</li><li>deaktivere dann den  <code>mt</code>  Button in den  <code>⚙️ Einstellungen</code></li><li>und versuche den Upload nochmal.</li></ul>Uploads werden etwas langsamer sein, aber man kann ja nicht alles haben.\nSorry für die Umstände !\n\nPS: Chrome v107 <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1354816" target="_blank">hat ein Bugfix</a> dafür', "u_emtleakf": 'versuche folgendes:\n<ul><li>drücke <code>F5</code> um die Seite neu zu laden</li><li>aktivere dann <code>🥔</code> (potato) im Upload UI<li>und versuche den Upload nochmal</li></ul>\nPS: Firefox <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1790500" target="_blank">hat hoffentlich irgendwann ein Bugfix</a>', "u_s404": "nicht auf dem Server gefunden", @@ -2857,7 +2857,7 @@ var Ls = { "mm_uncache": "välimuisti tyhjennetty; kaikki kappaleet ladataan uudelleen seuraavalla toistolla", "mm_hnf": "tuota kappaletta ei enää ole olemassa", -" im_hnf": "tuota kuvaa ei enää ole olemassa", + "im_hnf": "tuota kuvaa ei enää ole olemassa", "f_empty": 'tämä hakemisto on tyhjä', "f_chide": 'tämä piilottaa sarakkeen «{0}»\n\nvoit palauttaa sarakkeet asetuksista', From a9ee4f24d50286079788fc4f203b486ff5871f19 Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Wed, 6 Aug 2025 21:53:20 +0000 Subject: [PATCH 12/30] dropdowns for languages, themes, key-notation --- copyparty/web/browser.css | 5 +++ copyparty/web/browser.js | 73 +++++++++++++++++---------------------- 2 files changed, 37 insertions(+), 41 deletions(-) diff --git a/copyparty/web/browser.css b/copyparty/web/browser.css index 1f5a7da6..b5506f39 100644 --- a/copyparty/web/browser.css +++ b/copyparty/web/browser.css @@ -1374,6 +1374,7 @@ html.y #ops svg circle { #op_cfg input[type=text] { top: -.3em; } +.opview select, .opview input[type=text] { color: var(--fg); background: var(--txt-bg); @@ -1384,6 +1385,10 @@ html.y #ops svg circle { border-radius: .2em; padding: .2em .3em; } +.opview select { + padding: .3em; + margin: .2em .4em; +} .opview input.err { color: var(--err-fg); background: var(--err-bg); diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index ea4c3f1f..b84c39ee 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -7151,13 +7151,12 @@ ebi('op_cfg').innerHTML = ( '</div>\n' + '<div>\n' + ' <h3>' + L.cl_themes + '</h3>\n' + - ' <div id="themes">\n' + + ' <div><select id="themes"></select></div>\n' + ' </div>\n' + '</div>\n' + '<div>\n' + ' <h3>' + L.cl_langs + '</h3>\n' + - ' <div id="langs">\n' + - ' </div>\n' + + ' <div><select id="langs"></select></div>\n' + '</div>\n' + (have_zip ? ( '<div><h3>' + L.cl_ziptype + '</h3><div id="arc_fmt"></div></div>\n' @@ -7204,7 +7203,7 @@ ebi('op_cfg').innerHTML = ( ' </td>\n' + ' </div>\n' + '</div>\n' + - '<div><h3>' + L.cl_keytype + '</h3><div id="key_notation"></div></div>\n' + + '<div><h3>' + L.cl_keytype + '</h3><div><select id="key_notation"></select></div></div>\n' + '<div><h3>' + L.cl_hiddenc + '  ' + (MOBILE ? '<a href="#" id="hcolsh">' + L.cl_hidec + '</a> / ' : '') + '<a href="#" id="hcolsr">' + L.cl_reset + '</a></h3><div id="hcols"></div></div>' ); @@ -14109,25 +14108,21 @@ var mukey = (function () { defnot = 'rekobo_alnum'; var map = {}, - html = []; + html = [], + cb = ebi('key_notation'); for (var k in maps) { if (!maps.hasOwnProperty(k)) continue; - html.push( - '<span><input type="radio" name="keytype" value="' + k + '" id="key_' + k + '">' + - '<label for="key_' + k + '">' + k + '</label></span>'); - + html.push('<option value="{0}">{0}</option>'.format(k)); for (var a = 0; a < 24; a++) maps[k][a] = maps[k][a].trim(); } - ebi('key_notation').innerHTML = html.join('\n'); + cb.innerHTML = html.join(''); - function set_key_notation(e) { - ev(e); - var notation = this.getAttribute('value'); - load_notation(notation); + function set_key_notation() { + load_notation(cb.value); try_render(); } @@ -14184,14 +14179,10 @@ var mukey = (function () { if (!maps[notation]) notation = defnot; - ebi('key_' + notation).checked = true; + cb.value = notation; + cb.onchange = set_key_notation; load_notation(notation); - var o = QSA('#key_notation input'); - for (var a = 0; a < o.length; a++) { - o[a].onchange = set_key_notation; - } - return { "render": try_render }; @@ -14230,17 +14221,17 @@ var settheme = (function () { showfile.setstyle(); bchrome(); - var html = [], itheme = ax.indexOf(theme[0]) * 2 + (light ? 1 : 0), + var html = [], + cb = ebi('themes'), + itheme = ax.indexOf(theme[0]) * 2 + (light ? 1 : 0), names = ['classic dark', 'classic light', 'pm-monokai', 'flat light', 'vice', 'hotdog stand', 'hacker', 'hi-con']; for (var a = 0; a < themes; a++) - html.push('<a href="#" class="btn tgl' + (a == itheme ? ' on' : '') + - '" tt="' + (names[a] || 'custom') + '">' + a + '</a>'); + html.push('<option value="{0}">{0} ┃ {1}</option>'.format(a, names[a] || 'custom')); ebi('themes').innerHTML = html.join(''); - var btns = QSA('#themes a'); - for (var a = 0; a < themes; a++) - btns[a].onclick = r.go; + cb.value = itheme; + cb.onchange = r.onsel; if (chldr) { var x = r.ldr[itheme] || [tre]; @@ -14249,12 +14240,13 @@ var settheme = (function () { } bcfg_set('light', light); - tt.att(ebi('themes')); } - r.go = function (e) { - var i = e; - try { ev(e); i = e.target.textContent; } catch (ex) { } + r.onsel = function () { + r.go(parseInt(ebi('themes').value)); + }; + + r.go = function (i) { light = i % 2 == 1; var c = ax[Math.floor(i / 2)], l = light ? 'y' : 'z'; @@ -14262,7 +14254,7 @@ var settheme = (function () { themen = c + l; swrite('cpp_thm', theme); freshen(); - } + }; freshen(); return r; @@ -14272,26 +14264,25 @@ var settheme = (function () { (function () { function freshen() { lang = sread("cpp_lang", LANGS) || lang; - var k, html = []; + var k, cb = ebi('langs'), html = []; for (var a = 0; a < LANGS.length; a++) { k = LANGS[a]; - html.push('<a href="#" class="btn tgl' + (k == lang ? ' on' : '') + - '" tt="' + Ls[k].tt + '">' + k + '</a>'); + html.push('<option value="{0}">{0} ┃ {1}</option>'.format(k, Ls[k].tt)); } - ebi('langs').innerHTML = html.join(''); - var btns = QSA('#langs a'); - for (var a = 0, aa = btns.length; a < aa; a++) - btns[a].onclick = setlang; + cb.innerHTML = html.join(''); + cb.onchange = setlang; + cb.value = lang; } function setlang(e) { ev(e); var t = L.lang_set; - L = Ls[this.textContent]; - swrite("cpp_lang", this.textContent); + lang = ebi('langs').value; + L = Ls[lang]; + swrite("cpp_lang", lang); freshen(); modal.confirm(L.lang_set + "\n\n" + t, location.reload.bind(location), null); - }; + } freshen(); })(); From 9b9d2a92ca128289b9e6943ad24e3d6f5f3df5ce Mon Sep 17 00:00:00 2001 From: mati1210 <49544174+mati1210@users.noreply.github.com> Date: Thu, 7 Aug 2025 12:00:53 -0300 Subject: [PATCH 13/30] support systemd socket activation (fd passing) (#515) * add support for socket passing * slight tweaks before merge --------- Signed-off-by: mat <matheuz1210@gmail.com> Signed-off-by: ed <s@ocv.me> Co-authored-by: ed <s@ocv.me> --- copyparty/__main__.py | 6 ++++-- copyparty/ftpd.py | 2 +- copyparty/svchub.py | 4 ++-- copyparty/tcpsrv.py | 21 ++++++++++++++++----- copyparty/tftpd.py | 2 +- 5 files changed, 24 insertions(+), 11 deletions(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 3f07bfc1..86b6b031 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -536,7 +536,7 @@ def get_sects(): dedent( """ \033[33m-i\033[0m takes a comma-separated list of interfaces to listen on; - IP-addresses and/or unix-sockets (Unix Domain Sockets) + IP-addresses, unix-sockets and/or open file descriptors the default (\033[32m-i ::\033[0m) means all IPv4 and IPv6 addresses @@ -562,7 +562,9 @@ def get_sects(): \033[32m-i unix:\033[33m/dev/shm/party.sock\033[0m keeps umask-defined permission (usually \033[33m0600\033[0m) and the same user/group as copyparty - \033[33m-p\033[0m (tcp ports) is ignored for unix sockets + \033[32m-i fd:\033[33m3\033[0m uses the socket passed to copyparty on file descriptor 3 + + \033[33m-p\033[0m (tcp ports) is ignored for non ip-addresses """ ), ], diff --git a/copyparty/ftpd.py b/copyparty/ftpd.py index 2f45c3f4..bace7d28 100644 --- a/copyparty/ftpd.py +++ b/copyparty/ftpd.py @@ -607,7 +607,7 @@ class Ftpd(object): if "::" in ips: ips.append("0.0.0.0") - ips = [x for x in ips if "unix:" not in x] + ips = [x for x in ips if not x.startswith(("unix:", "fd:"))] if self.args.ftp4: ips = [x for x in ips if ":" not in x] diff --git a/copyparty/svchub.py b/copyparty/svchub.py index bd7562e4..9a00f890 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -868,7 +868,7 @@ class SvcHub(object): have_tcp = False for zs in al.i: - if not zs.startswith("unix:"): + if not zs.startswith(("unix:", "fd:")): have_tcp = True if not have_tcp: zb = False @@ -878,7 +878,7 @@ class SvcHub(object): setattr(al, zs, False) zb = True if zb: - t = "only listening on unix-sockets; cannot enable zeroconf/mdns/ssdp as requested" + t = "no ip addresses provided; cannot enable zeroconf/mdns/ssdp as requested" self.log("root", t, 3) if not self.args.no_dav: diff --git a/copyparty/tcpsrv.py b/copyparty/tcpsrv.py index 53388c5e..6c61fe61 100644 --- a/copyparty/tcpsrv.py +++ b/copyparty/tcpsrv.py @@ -25,8 +25,8 @@ from .util import ( termsize, ) -if True: - from typing import Generator, Union +if True: # pylint: disable=using-constant-test + from typing import Generator, Optional, Union if TYPE_CHECKING: from .svchub import SvcHub @@ -245,8 +245,10 @@ class TcpSrv(object): def _listen(self, ip: str, port: int) -> None: uds_perm = uds_gid = -1 + bound: Optional[socket.socket] = None + tcp = False + if "unix:" in ip: - tcp = False ipv = socket.AF_UNIX uds = ip.split(":") ip = uds[-1] @@ -259,7 +261,12 @@ class TcpSrv(object): import grp uds_gid = grp.getgrnam(uds[2]).gr_gid + elif "fd:" in ip: + fd = ip[3:] + bound = socket.socket(fileno=int(fd)) + tcp = bound.proto == socket.IPPROTO_TCP + ipv = bound.family elif ":" in ip: tcp = True ipv = socket.AF_INET6 @@ -267,7 +274,7 @@ class TcpSrv(object): tcp = True ipv = socket.AF_INET - srv = socket.socket(ipv, socket.SOCK_STREAM) + srv = bound or socket.socket(ipv, socket.SOCK_STREAM) if not ANYWIN or self.args.reuseaddr: srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) @@ -285,6 +292,10 @@ class TcpSrv(object): if getattr(self.args, "freebind", False): srv.setsockopt(socket.SOL_IP, socket.IP_FREEBIND, 1) + if bound: + self.srv.append(srv) + return + try: if tcp: srv.bind((ip, port)) @@ -437,7 +448,7 @@ class TcpSrv(object): def detect_interfaces(self, listen_ips: list[str]) -> dict[str, Netdev]: from .stolen.ifaddr import get_adapters - listen_ips = [x for x in listen_ips if "unix:" not in x] + listen_ips = [x for x in listen_ips if not x.startswith(("unix:", "fd:"))] nics = get_adapters(True) eps: dict[str, Netdev] = {} diff --git a/copyparty/tftpd.py b/copyparty/tftpd.py index 6f5726b3..4ef01103 100644 --- a/copyparty/tftpd.py +++ b/copyparty/tftpd.py @@ -179,7 +179,7 @@ class Tftpd(object): if "::" in ips: ips.append("0.0.0.0") - ips = [x for x in ips if "unix:" not in x] + ips = [x for x in ips if not x.startswith(("unix:", "fd:"))] if self.args.tftp4: ips = [x for x in ips if ":" not in x] From 54caf63f6aab9198d0e2038da38b2d7b0622bffc Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Thu, 7 Aug 2025 15:18:59 +0000 Subject: [PATCH 14/30] optimize --- copyparty/__main__.py | 4 ++-- copyparty/httpcli.py | 13 +++++-------- copyparty/multicast.py | 6 +----- copyparty/svchub.py | 2 +- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 86b6b031..eb44ef4f 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -536,7 +536,7 @@ def get_sects(): dedent( """ \033[33m-i\033[0m takes a comma-separated list of interfaces to listen on; - IP-addresses, unix-sockets and/or open file descriptors + IP-addresses, unix-sockets, and/or open file descriptors the default (\033[32m-i ::\033[0m) means all IPv4 and IPv6 addresses @@ -564,7 +564,7 @@ def get_sects(): \033[32m-i fd:\033[33m3\033[0m uses the socket passed to copyparty on file descriptor 3 - \033[33m-p\033[0m (tcp ports) is ignored for non ip-addresses + \033[33m-p\033[0m (tcp ports) is ignored for unix-sockets and FDs """ ), ], diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index b335fe4a..c982a812 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -262,7 +262,8 @@ class HttpCli(object): def _assert_safe_rem(self, rem: str) -> None: # sanity check to prevent any disasters - if rem.startswith("/") or rem.startswith("../") or "/../" in rem: + # (this function hopefully serves no purpose; validation has already happened at this point, this only exists as a last-ditch effort just in case) + if rem.startswith(("/", "../")) or "/../" in rem: raise Exception("that was close") def _gen_fk(self, alg: int, salt: str, fspath: str, fsize: int, inode: int) -> str: @@ -5031,7 +5032,7 @@ class HttpCli(object): wvol = [x for x in wvol if "unlistcw" not in allvols[x[1:-1]].flags] fmt = self.uparam.get("ls", "") - if not fmt and (self.ua.startswith("curl/") or self.ua.startswith("fetch")): + if not fmt and self.ua.startswith(("curl/", "fetch")): fmt = "v" if fmt in ["v", "t", "txt"]: @@ -5141,7 +5142,7 @@ class HttpCli(object): t = '<h1 id="n">404 not found  ┐( ´ -`)┌</h1><p><a id="r" href="{}/?h">go home</a></p>' pt = "404 not found ┐( ´ -`)┌" - if self.ua.startswith("curl/") or self.ua.startswith("fetch"): + if self.ua.startswith(("curl/", "fetch")): pt = "# acct: %s\n%s\n" % (self.uname, pt) self.reply(pt.encode("utf-8"), status=rc) return True @@ -6267,11 +6268,7 @@ class HttpCli(object): is_ls = "ls" in self.uparam is_js = self.args.force_js or self.cookies.get("js") == "y" - if ( - not is_ls - and not add_og - and (self.ua.startswith("curl/") or self.ua.startswith("fetch")) - ): + if not is_ls and not add_og and self.ua.startswith(("curl/", "fetch")): self.uparam["ls"] = "v" is_ls = True diff --git a/copyparty/multicast.py b/copyparty/multicast.py index 6d6507f8..766dc15d 100644 --- a/copyparty/multicast.py +++ b/copyparty/multicast.py @@ -183,11 +183,7 @@ class MCast(object): srv.ips[oth_ip.split("/")[0]] = ipaddress.ip_network(oth_ip, False) # gvfs breaks if a linklocal ip appears in a dns reply - ll = { - k: v - for k, v in srv.ips.items() - if k.startswith("169.254") or k.startswith("fe80") - } + ll = {k: v for k, v in srv.ips.items() if k.startswith(("169.254", "fe80"))} rt = {k: v for k, v in srv.ips.items() if k not in ll} if self.args.ll or not rt: diff --git a/copyparty/svchub.py b/copyparty/svchub.py index 9a00f890..07d85d83 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -878,7 +878,7 @@ class SvcHub(object): setattr(al, zs, False) zb = True if zb: - t = "no ip addresses provided; cannot enable zeroconf/mdns/ssdp as requested" + t = "not listening on any ip-addresses (only unix-sockets and/or FDs); cannot enable zeroconf/mdns/ssdp as requested" self.log("root", t, 3) if not self.args.no_dav: From 8a0746c6af6d72cac2c8f10dfa383c76b93562f8 Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Thu, 7 Aug 2025 15:24:15 +0000 Subject: [PATCH 15/30] fix viewing .MD files --- copyparty/httpcli.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index c982a812..79003bcc 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -3584,7 +3584,7 @@ class HttpCli(object): rem = "{}/{}".format(rp, fn).strip("/") dbv, vrem = vfs.get_dbv(rem) - if not rem.endswith(".md") and not self.can_delete: + if not rem.lower().endswith(".md") and not self.can_delete: raise Pebkac(400, "only markdown pls") if nullwrite: @@ -6196,7 +6196,8 @@ class HttpCli(object): if not use_filekey: return self.tx_404(True) - if add_og and not abspath.lower().endswith(".md"): + is_md = abspath.lower().endswith(".md") + if add_og and not is_md: if og_ua or self.host not in self.headers.get("referer", ""): self.vpath, og_fn = vsplit(self.vpath) vpath = self.vpath @@ -6208,10 +6209,10 @@ class HttpCli(object): vpnodes.pop() if ( - (abspath.endswith(".md") or self.can_delete) + (is_md or self.can_delete) and "nohtml" not in vn.flags and ( - ("v" in self.uparam and abspath.endswith(".md")) + (is_md and "v" in self.uparam) or "edit" in self.uparam or "edit2" in self.uparam ) From 4a0435681449ada06c87c9cb7cff37b92d4d3bb4 Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Thu, 7 Aug 2025 15:57:51 +0000 Subject: [PATCH 16/30] fix non-e2d dupe finalizing; when the up2k database is not enabled, only the 38400 most recent uploads are kept in memory serverside the webui did not anticipate this, expecting the server to finalize all dupes with just a single pass of brief handshakes fix this by doing as many passes as necessary, only stopping if a pass does not make any progress (filesystem-issues or some such) --- copyparty/web/up2k.js | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/copyparty/web/up2k.js b/copyparty/web/up2k.js index 1e49fc4e..2743ea36 100644 --- a/copyparty/web/up2k.js +++ b/copyparty/web/up2k.js @@ -964,6 +964,7 @@ function up2k_init(subtle) { "t": 0 }, "car": 0, + "nre": 0, "slow_io": null, "oserr": false, "modn": 0, @@ -1783,8 +1784,7 @@ function up2k_init(subtle) { } var tasker = (function () { - var running = false, - was_busy = false; + var running = false; var defer = function () { running = false; @@ -1801,7 +1801,17 @@ function up2k_init(subtle) { while (true) { var now = Date.now(), blocktime = now - r.tact, - is_busy = st.car < st.files.length; + was_busy = st.is_busy, + is_busy = !!( // gzip take the wheel + st.car < st.files.length || + st.busy.hash.length || + st.todo.hash.length || + st.busy.handshake.length || + st.todo.handshake.length || + st.busy.upload.length || + st.todo.upload.length || + st.busy.head.length || + st.todo.head.length); if (blocktime > 2500) console.log('main thread blocked for ' + blocktime); @@ -1809,7 +1819,16 @@ function up2k_init(subtle) { r.tact = now; if (was_busy && !is_busy) { - for (var a = 0; a < st.files.length; a++) { + var nre = 0, nf = 0; + for (var a = 0; a < st.files.length; a++) + if (st.files[a].want_recheck) + nre++; + console.log('nre', nre, 'st', st.nre); + if (st.nre != nre) { + st.nre = nre; + nf = st.files.length; + } + for (var a = 0; a < nf; a++) { var t = st.files[a]; if (t.want_recheck) { t.rechecks++; @@ -1817,7 +1836,7 @@ function up2k_init(subtle) { push_t(st.todo.handshake, t); } } - is_busy = st.todo.handshake.length; + is_busy = !!st.todo.handshake.length; try { if (!is_busy && !uc.fsearch && !msel.getsel().length && (!mp.au || mp.au.paused)) treectl.goto(); @@ -1826,7 +1845,7 @@ function up2k_init(subtle) { } if (was_busy != is_busy) { - st.is_busy = was_busy = is_busy; + st.is_busy = is_busy; window[(is_busy ? "add" : "remove") + "EventListener"]("beforeunload", warn_uploader_busy); @@ -1947,7 +1966,7 @@ function up2k_init(subtle) { for (var a = 0; a < st.files.length; a++) { var t = st.files[a]; - if (t.want_recheck && !t.rechecks) + if (t.want_recheck && t.rechecks < 999) return; } @@ -2693,8 +2712,9 @@ function up2k_init(subtle) { if (ofs !== -1) { err = err.slice(0, ofs + 1) + linksplit(err.slice(ofs + 2).trimEnd()).join(' / '); } - if (!t.rechecks && (err_pend || err_srcb)) { + if (!t.rechecks) t.rechecks = 0; + if (t.rechecks < 999 && (err_pend || err_srcb)) { t.want_recheck = true; if (st.busy.upload.length || st.busy.handshake.length || st.bytes.uploaded) { err = L.u_dupdefer; From 3f8cb7e877e9ef8dffd1cdf910c36ef1ac5aad34 Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Thu, 7 Aug 2025 17:00:42 +0000 Subject: [PATCH 17/30] xff: require explicit configuration of --rproxy; try to avoid dangerous misconfiguration of how to determine the client's IP by more aggressively asking for the correct config; if the --xff-hdr (default: x-forwarded-for) appears in a request then it will now be ignored unless --rproxy says which IP to use --- README.md | 8 ++++++-- copyparty/__main__.py | 2 +- copyparty/httpcli.py | 17 ++++++++++++++--- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9746581b..2afc4a81 100644 --- a/README.md +++ b/README.md @@ -2058,7 +2058,11 @@ you can either: * or do location-based proxying, using `--rp-loc=/stuff` to tell copyparty where it is mounted -- has a slight performance cost and higher chance of bugs * if copyparty says `incorrect --rp-loc or webserver config; expected vpath starting with [...]` it's likely because the webserver is stripping away the proxy location from the request URLs -- see the `ProxyPass` in the apache example below -when running behind a reverse-proxy (this includes services like cloudflare), it is important to configure real-ip correctly, as many features rely on knowing the client's IP. Look out for red and yellow log messages which explain how to do this. But basically, set `--xff-hdr` to the name of the http header to read the IP from (usually `x-forwarded-for`, but cloudflare uses `cf-connecting-ip`), and then `--xff-src` to the IP of the reverse-proxy so copyparty will trust the xff-hdr. Note that `--rp-loc` in particular will not work at all unless you do this +when running behind a reverse-proxy (this includes services like cloudflare), it is important to configure real-ip correctly, as many features rely on knowing the client's IP. The best/safest approach is to configure your reverse-proxy so it gives copyparty a header which only contains the client's true/real IP-address, and then setting `--xff-hdr theHeaderName --rproxy 1` but alternatively, if you want/need to let copyparty handle this, look out for red and yellow log messages which explain how to do that. Basically, the log will say this: + +> set `--xff-hdr` to the name of the http-header to read the IP from (usually `x-forwarded-for`, but cloudflare uses `cf-connecting-ip`), and then `--xff-src` to the IP of the reverse-proxy so copyparty will trust the xff-hdr. You will also need to configure `--rproxy` to `1` if the header only contains one IP (the correct one) or to a *negative value* if it contains multiple; `-1` being the rightmost and most trusted IP (the nearest proxy, so usually not the correct one), `-2` being the second-closest hop, and so on + +Note that `--rp-loc` in particular will not work at all unless you configure the above correctly some reverse proxies (such as [Caddy](https://caddyserver.com/)) can automatically obtain a valid https/tls certificate for you, and some support HTTP/2 and QUIC which *could* be a nice speed boost, depending on a lot of factors * **warning:** nginx-QUIC (HTTP/3) is still experimental and can make uploads much slower, so HTTP/1.1 is recommended for now @@ -2615,7 +2619,7 @@ there is a [discord server](https://discord.gg/25J8CdTT6G) with an `@everyone` some notes on hardening -* set `--rproxy 0` if your copyparty is directly facing the internet (not through a reverse-proxy) +* set `--rproxy 0` *if and only if* your copyparty is directly facing the internet (not through a reverse-proxy) * cors doesn't work right otherwise * if you allow anonymous uploads or otherwise don't trust the contents of a volume, you can prevent XSS with volflag `nohtml` * this returns html documents as plaintext, and also disables markdown rendering diff --git a/copyparty/__main__.py b/copyparty/__main__.py index eb44ef4f..81476e7f 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1091,7 +1091,7 @@ def add_network(ap): ap2.add_argument("-i", metavar="IP", type=u, default="::", help="IPs and/or unix-sockets to listen on (comma-separated list; see \033[33m--help-bind\033[0m). Default: all IPv4 and IPv6") ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to listen on (comma/range); ignored for unix-sockets") ap2.add_argument("--ll", action="store_true", help="include link-local IPv4/IPv6 in mDNS replies, even if the NIC has routable IPs (breaks some mDNS clients)") - ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to associate clients with; [\033[32m0\033[0m]=tcp, [\033[32m1\033[0m]=origin (first x-fwd, unsafe), [\033[32m2\033[0m]=outermost-proxy, [\033[32m3\033[0m]=second-proxy, [\033[32m-1\033[0m]=closest-proxy") + ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=9999999, help="which ip to associate clients with; [\033[32m0\033[0m]=tcp, [\033[32m1\033[0m]=origin (first x-fwd, unsafe), [\033[32m-1\033[0m]=closest-proxy, [\033[32m-2\033[0m]=second-hop, [\033[32m-3\033[0m]=third-hop") ap2.add_argument("--xff-hdr", metavar="NAME", type=u, default="x-forwarded-for", help="if reverse-proxied, which http header to read the client's real ip from") ap2.add_argument("--xff-src", metavar="CIDR", type=u, default="127.0.0.0/8, ::1/128", help="list of trusted reverse-proxy CIDRs (comma-separated); only accept the real-ip header (\033[33m--xff-hdr\033[0m) and IdP headers if the incoming connection is from an IP within either of these subnets. Specify [\033[32mlan\033[0m] to allow all LAN / private / non-internet IPs. Can be disabled with [\033[32many\033[0m] if you are behind cloudflare (or similar) and are using \033[32m--xff-hdr=cf-connecting-ip\033[0m (or similar)") ap2.add_argument("--ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m (comma-separated); examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 79003bcc..2986275f 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -384,9 +384,20 @@ class HttpCli(object): try: cli_ip = zsl[n].strip() except: - cli_ip = zsl[0].strip() - t = "rproxy={} oob x-fwd {}" - self.log(t.format(self.args.rproxy, zso), c=3) + cli_ip = self.ip + self.bad_xff = True + if self.args.rproxy != 9999999: + t = "global-option --rproxy %d could not be used (out-of-bounds) for the received header [%s]" + self.log(t % (self.args.rproxy, zso), c=3) + else: + zsl = [ + " rproxy: %d if this client's IP-address is [%s]" + % (-1 - zd, zs.strip()) + for zd, zs in enumerate(zsl) + ] + t = 'could not determine the client\'s IP-address because the global-option --rproxy has not been configured, so the request-header [%s] specified by global-option --xff-hdr cannot be used safely! Please see the "reverse-proxy" section in the readme. The best approach is to configure your reverse-proxy to give copyparty the exact IP-address to assume (perhaps in another header), but you may also try the following:' + t = t % (self.args.xff_hdr,) + self.log("%s\n\n%s\n" % (t, "\n".join(zsl)), 3) pip = self.conn.addr[0] xffs = self.conn.xff_nm From 9e980bb55203c2a73a0a9efc7792c7e0fd379ab2 Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Thu, 7 Aug 2025 17:57:10 +0000 Subject: [PATCH 18/30] try to detect proxies with misbehaving caches (#488) --- copyparty/__main__.py | 1 + copyparty/httpcli.py | 6 ++++++ copyparty/web/browser.js | 4 ++++ tests/util.py | 4 ++-- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 81476e7f..0715af28 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1300,6 +1300,7 @@ def add_yolo(ap): ap2 = ap.add_argument_group("yolo options") ap2.add_argument("--allow-csrf", action="store_true", help="disable csrf protections; let other domains/sites impersonate you through cross-site requests") ap2.add_argument("--cookie-lax", action="store_true", help="allow cookies from other domains (if you follow a link from another website into your server, you will arrive logged-in); this reduces protection against CSRF") + ap2.add_argument("--no-fnugg", action="store_true", help="disable the smoketest for caching-related issues in the web-UI") ap2.add_argument("--getmod", action="store_true", help="permit ?move=[...] and ?delete as GET") ap2.add_argument("--wo-up-readme", action="store_true", help="allow users with write-only access to upload logues and readmes without adding the _wo_ filename prefix (volflag=wo_up_readme)") diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 2986275f..3da299c7 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -6025,6 +6025,12 @@ class HttpCli(object): else: [x.pop(k) for k in ["name", "dt"] for y in [dirs, files] for x in y] + # nonce (tlnote: norwegian for flake as in snowflake) + if self.args.no_fnugg: + ls["fnugg"] = "nei" + elif "fnugg" in self.headers: + ls["fnugg"] = self.headers["fnugg"] + ret = json.dumps(ls) mime = "application/json" diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index b84c39ee..0563d8ee 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -13204,6 +13204,7 @@ var treectl = (function () { xhr.hydrate = hydrate; xhr.ts = Date.now(); xhr.open('GET', xhr.top + '?ls' + uq, true); + xhr.setRequestHeader('Fnugg', '' + xhr.ts); xhr.onload = xhr.onerror = recvls; xhr.send(); @@ -13269,6 +13270,9 @@ var treectl = (function () { if (r.chk_index_html(this.top, res)) return; + if (this.ts != res.fnugg && res.fnugg != 'nei' && sread('no_fnugg') !== '1') + toast.warn(60, "WARNING: A proxy/CDN between your webbrowser and the server is misbehaving, and caching responses it shouldn't. As a result, you are now seeing stale directory listings. There will be many issues.\n\nIf you need to ignore this and stop these messages, you can set the global-option 'no-fnugg' on the server, or click <code>π</code> and run this: <code>STG.no_fnugg=1</code>"); + for (var a = 0; a < res.files.length; a++) if (res.files[a].tags === undefined) res.files[a].tags = {}; diff --git a/tests/util.py b/tests/util.py index ea9bfeb9..7b4ad2aa 100644 --- a/tests/util.py +++ b/tests/util.py @@ -143,7 +143,7 @@ class Cfg(Namespace): def __init__(self, a=None, v=None, c=None, **ka0): ka = {} - ex = "allow_flac allow_wav chpw cookie_lax daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only ih ihead localtime magic nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tail no_tarcmp no_thumb no_vthumb no_zip nrand nsort nw og og_no_head og_s_title ohead q rand re_dirsz reflink rmagic rss smb srch_dbg srch_excl stats uqe vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs" + ex = "allow_flac allow_wav chpw cookie_lax daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only ih ihead localtime magic nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_fnugg no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tail no_tarcmp no_thumb no_vthumb no_zip nrand nsort nw og og_no_head og_s_title ohead q rand re_dirsz reflink rmagic rss smb srch_dbg srch_excl stats uqe vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs" ka.update(**{k: False for k in ex.split()}) ex = "dav_inf dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump re_dhash see_dots plain_ip" @@ -161,7 +161,7 @@ class Cfg(Namespace): ex = "au_vol dl_list mtab_age reg_cap s_thead s_tbody tail_tmax tail_who th_convt ups_who zip_who" ka.update(**{k: 9 for k in ex.split()}) - ex = "db_act forget_ip idp_cookie idp_store k304 loris no304 nosubtle re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow zipmaxn zipmaxs" + ex = "ctl_re db_act forget_ip idp_cookie idp_store k304 loris no304 nosubtle re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow zipmaxn zipmaxs" ka.update(**{k: 0 for k in ex.split()}) ex = "ah_alg bname chmod_f chpw_db doctitle df exit favico idp_h_usr ipa html_head lg_sba lg_sbf log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i shr tcolor textfiles unlist vname xff_src zipmaxt R RS SR" From 34d98e9980c40450d9375ff48e9bdc31110d05c1 Mon Sep 17 00:00:00 2001 From: exci <76759714+icxes@users.noreply.github.com> Date: Thu, 7 Aug 2025 21:10:26 +0300 Subject: [PATCH 19/30] add systemd user services and templated services (#502) * move service files from contrib/package/arch/ to /contrib/systemd/ * add simpler default copyparty.conf that puts users in jail * remove warning about .conf files in ~/.config/copyparty/ * update PKGBUILD with changes * add links to configuration examples in index.md * fix link to the example config * update README.md arch instructions --------- Co-authored-by: icxes <icxes@dev.null@need.moe> Co-authored-by: ed <s@ocv.me> --- README.md | 6 +- contrib/package/arch/PKGBUILD | 58 ++++++++----------- contrib/package/arch/copyparty.conf | 7 --- contrib/package/arch/copyparty.service | 32 ---------- contrib/package/arch/index.md | 3 - contrib/systemd/copyparty-user.service | 26 +++++++++ contrib/systemd/copyparty.conf | 45 +++----------- contrib/systemd/copyparty.example.conf | 42 ++++++++++++++ contrib/systemd/copyparty@.service | 30 ++++++++++ contrib/systemd/index.md | 10 ++++ contrib/systemd/prisonparty-user.service | 33 +++++++++++ .../prisonparty@.service} | 25 ++++---- copyparty/svchub.py | 9 --- 13 files changed, 191 insertions(+), 135 deletions(-) delete mode 100644 contrib/package/arch/copyparty.conf delete mode 100644 contrib/package/arch/copyparty.service delete mode 100644 contrib/package/arch/index.md create mode 100644 contrib/systemd/copyparty-user.service create mode 100644 contrib/systemd/copyparty.example.conf create mode 100644 contrib/systemd/copyparty@.service create mode 100644 contrib/systemd/index.md create mode 100644 contrib/systemd/prisonparty-user.service rename contrib/{package/arch/prisonparty.service => systemd/prisonparty@.service} (51%) diff --git a/README.md b/README.md index 2afc4a81..39bd932f 100644 --- a/README.md +++ b/README.md @@ -2281,11 +2281,9 @@ if your distro/OS is not mentioned below, there might be some hints in the [«on `pacman -S copyparty` (in [arch linux extra](https://archlinux.org/packages/extra/any/copyparty/)) -it comes with a [systemd service](./contrib/package/arch/copyparty.service) and expects to find one or more [config files](./docs/example.conf) in `/etc/copyparty.d/` +it comes with a [systemd service](./contrib/systemd/copyparty@.service) as well as a [user service](./contrib/systemd/copyparty-user.service), and expects to find a [config file](./contrib/systemd/copyparty.example.conf) in `/etc/copyparty/copyparty.conf` or `~/.config/copyparty/copyparty.conf` -after installing it, you may want to `cp /usr/lib/systemd/system/copyparty.service /etc/systemd/system/` and then `vim /etc/systemd/system/copyparty.service` to change what user/group it is running as (you only need to do this once) - -NOTE: there used to be an aur package; this evaporated when copyparty was adopted by the official archlinux repos. If you're still using the aur package, please move +after installing, start either the system service or the user service and navigate to http://127.0.0.1:3923 for further instructions (unless you already edited the config files, in which case you are good to go, probably) ## fedora package diff --git a/contrib/package/arch/PKGBUILD b/contrib/package/arch/PKGBUILD index 9efb5c41..c6dc4687 100644 --- a/contrib/package/arch/PKGBUILD +++ b/contrib/package/arch/PKGBUILD @@ -1,4 +1,7 @@ # Maintainer: icxes <dev.null@need.moe> +# Contributor: Morgan Adamiec <morganamilo@archlinux.org> +# NOTE: You generally shouldn't use this PKGBUILD on Arch, as it is mainly for testing purposes. Install copyparty using pacman instead. + pkgname=copyparty pkgver="1.18.10" pkgrel=1 @@ -6,52 +9,41 @@ pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFT arch=("any") url="https://github.com/9001/${pkgname}" license=('MIT') -depends=("python" "lsof" "python-jinja") +depends=("bash" "python" "lsof" "python-jinja") makedepends=("python-wheel" "python-setuptools" "python-build" "python-installer" "make" "pigz") optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tags" - "cfssl: generate TLS certificates on startup (pointless when reverse-proxied)" - "python-mutagen: music tags (alternative)" - "python-pillow: thumbnails for images" - "python-pyvips: thumbnails for images (higher quality, faster, uses more ram)" - "libkeyfinder-git: detection of musical keys" - "qm-vamp-plugins: BPM detection" - "python-pyopenssl: ftps functionality" - "python-pyzmq: send zeromq messages from event-hooks" - "python-argon2-cffi: hashed passwords in config" - "python-impacket-git: smb support (bad idea)" + "cfssl: generate TLS certificates on startup" + "python-mutagen: music tags (alternative)" + "python-pillow: thumbnails for images" + "python-pyvips: thumbnails for images (higher quality, faster, uses more ram)" + "libkeyfinder: detection of musical keys" + "python-pyopenssl: ftps functionality" + "python-pyzmq: send zeromq messages from event-hooks" + "python-argon2-cffi: hashed passwords in config" ) source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz") -backup=("etc/${pkgname}.d/init" ) +backup=("etc/${pkgname}/copyparty.conf" ) sha256sums=("49c5fedf7619437bc0af125cb4e8360d9bda9d87ef45d6314d7acf163ab4cf99") build() { + cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web" + make + cd "${srcdir}/${pkgname}-${pkgver}" - - pushd copyparty/web - make -j$(nproc) - rm Makefile - popd - - python3 -m build -wn + python -m build --wheel --no-isolation } package() { cd "${srcdir}/${pkgname}-${pkgver}" - python3 -m installer -d "$pkgdir" dist/*.whl + python -m installer --destdir="$pkgdir" dist/*.whl - install -dm755 "${pkgdir}/etc/${pkgname}.d" + install -dm755 "${pkgdir}/etc/${pkgname}" install -Dm755 "bin/prisonparty.sh" "${pkgdir}/usr/bin/prisonparty" - install -Dm644 "contrib/package/arch/${pkgname}.conf" "${pkgdir}/etc/${pkgname}.d/init" - install -Dm644 "contrib/package/arch/${pkgname}.service" "${pkgdir}/usr/lib/systemd/system/${pkgname}.service" - install -Dm644 "contrib/package/arch/prisonparty.service" "${pkgdir}/usr/lib/systemd/system/prisonparty.service" - install -Dm644 "contrib/package/arch/index.md" "${pkgdir}/var/lib/${pkgname}-jail/README.md" + install -Dm644 "contrib/systemd/${pkgname}.conf" "${pkgdir}/etc/${pkgname}/copyparty.conf" + install -Dm644 "contrib/systemd/${pkgname}@.service" "${pkgdir}/usr/lib/systemd/system/${pkgname}@.service" + install -Dm644 "contrib/systemd/${pkgname}-user.service" "${pkgdir}/usr/lib/systemd/user/${pkgname}.service" + install -Dm644 "contrib/systemd/prisonparty@.service" "${pkgdir}/usr/lib/systemd/system/prisonparty@.service" + install -Dm644 "contrib/systemd/prisonparty-user.service" "${pkgdir}/usr/lib/systemd/user/prisonparty.service" + install -Dm644 "contrib/systemd/index.md" "${pkgdir}/var/lib/${pkgname}-jail/README.md" install -Dm644 "LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" - - find /etc/${pkgname}.d -iname '*.conf' 2>/dev/null | grep -qE . && return - echo "┏━━━━━━━━━━━━━━━──-" - echo "┃ Configure ${pkgname} by adding .conf files into /etc/${pkgname}.d/" - echo "┃ and maybe copy+edit one of the following to /etc/systemd/system/:" - echo "┣━♦ /usr/lib/systemd/system/${pkgname}.service (standard)" - echo "┣━♦ /usr/lib/systemd/system/prisonparty.service (chroot)" - echo "┗━━━━━━━━━━━━━━━──-" } diff --git a/contrib/package/arch/copyparty.conf b/contrib/package/arch/copyparty.conf deleted file mode 100644 index 1d90d772..00000000 --- a/contrib/package/arch/copyparty.conf +++ /dev/null @@ -1,7 +0,0 @@ -## import all *.conf files from the current folder (/etc/copyparty.d) -% ./ - -# add additional .conf files to this folder; -# see example config files for reference: -# https://github.com/9001/copyparty/blob/hovudstraum/docs/example.conf -# https://github.com/9001/copyparty/tree/hovudstraum/docs/copyparty.d diff --git a/contrib/package/arch/copyparty.service b/contrib/package/arch/copyparty.service deleted file mode 100644 index 22dac3d6..00000000 --- a/contrib/package/arch/copyparty.service +++ /dev/null @@ -1,32 +0,0 @@ -# this will start `/usr/bin/copyparty-sfx.py` -# and read config from `/etc/copyparty.d/*.conf` -# -# you probably want to: -# change "User=cpp" and "/home/cpp/" to another user -# -# unless you add -q to disable logging, you may want to remove the -# following line to allow buffering (slightly better performance): -# Environment=PYTHONUNBUFFERED=x - -[Unit] -Description=copyparty file server - -[Service] -Type=notify -SyslogIdentifier=copyparty -Environment=PYTHONUNBUFFERED=x -WorkingDirectory=/var/lib/copyparty-jail -ExecReload=/bin/kill -s USR1 $MAINPID - -# user to run as + where the TLS certificate is (if any) -User=cpp -Environment=XDG_CONFIG_HOME=/home/cpp/.config - -# stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running -ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf' - -# run copyparty -ExecStart=/usr/bin/python3 /usr/bin/copyparty -c /etc/copyparty.d/init - -[Install] -WantedBy=multi-user.target diff --git a/contrib/package/arch/index.md b/contrib/package/arch/index.md deleted file mode 100644 index 016c0b0a..00000000 --- a/contrib/package/arch/index.md +++ /dev/null @@ -1,3 +0,0 @@ -this is `/var/lib/copyparty-jail`, the fallback webroot when copyparty has not yet been configured - -please add some `*.conf` files to `/etc/copyparty.d/` diff --git a/contrib/systemd/copyparty-user.service b/contrib/systemd/copyparty-user.service new file mode 100644 index 00000000..c6f61000 --- /dev/null +++ b/contrib/systemd/copyparty-user.service @@ -0,0 +1,26 @@ +# this will start `/usr/bin/copyparty` +# and read config from `$HOME/.config/copyparty.conf` +# +# unless you add -q to disable logging, you may want to remove the +# following line to allow buffering (slightly better performance): +# Environment=PYTHONUNBUFFERED=x + +[Unit] +Description=copyparty file server + +[Service] +Type=notify +SyslogIdentifier=copyparty +WorkingDirectory=/var/lib/copyparty-jail +Environment=PYTHONUNBUFFERED=x +Environment=PRTY_CONFIG=%h/.config/copyparty/copyparty.conf +ExecReload=/bin/kill -s USR1 $MAINPID + +# ensure there is a config +ExecStartPre=/bin/bash -c 'if [[ ! -f %h/.config/copyparty/copyparty.conf ]]; then mkdir -p %h/.config/copyparty; cp /etc/copyparty/copyparty.conf %h/.config/copyparty/copyparty.conf; fi' + +# run copyparty +ExecStart=/usr/bin/python3 /usr/bin/copyparty + +[Install] +WantedBy=default.target diff --git a/contrib/systemd/copyparty.conf b/contrib/systemd/copyparty.conf index 79560d0d..5d9bdbe3 100644 --- a/contrib/systemd/copyparty.conf +++ b/contrib/systemd/copyparty.conf @@ -1,42 +1,13 @@ -# not actually YAML but lets pretend: -# -*- mode: yaml -*- -# vim: ft=yaml: - - -# put this file in /etc/ - - [global] - e2dsa # enable file indexing and filesystem scanning - e2ts # and enable multimedia indexing - ansi # and colors in log messages - - # disable logging to stdout/journalctl and log to a file instead; - # $LOGS_DIRECTORY is usually /var/log/copyparty (comes from systemd) - # and copyparty replaces %Y-%m%d with Year-MonthDay, so the - # full path will be something like /var/log/copyparty/2023-1130.txt - # (note: enable compression by adding .xz at the end) - q, lo: $LOGS_DIRECTORY/%Y-%m%d.log - - # p: 80,443,3923 # listen on 80/443 as well (requires CAP_NET_BIND_SERVICE) - # i: 127.0.0.1 # only allow connections from localhost (reverse-proxies) - # ftp: 3921 # enable ftp server on port 3921 - # p: 3939 # listen on another port - # df: 16 # stop accepting uploads if less than 16 GB free disk space - # ver # show copyparty version in the controlpanel - # grid # show thumbnails/grid-view by default - # theme: 2 # monokai - # name: datasaver # change the server-name that's displayed in the browser - # stats, nos-dup # enable the prometheus endpoint, but disable the dupes counter (too slow) - # no-robots, force-js # make it harder for search engines to read your server - + i: 127.0.0.1 [accounts] - ed: wark # username: password + user: password - -[/] # create a volume at "/" (the webroot), which will - /mnt # share the contents of the "/mnt" folder +[/] + /var/lib/copyparty-jail accs: - rw: * # everyone gets read-write access, but - rwmda: ed # the user "ed" gets read-write-move-delete-admin + r: * + rwdma: user + flags: + grid \ No newline at end of file diff --git a/contrib/systemd/copyparty.example.conf b/contrib/systemd/copyparty.example.conf new file mode 100644 index 00000000..79560d0d --- /dev/null +++ b/contrib/systemd/copyparty.example.conf @@ -0,0 +1,42 @@ +# not actually YAML but lets pretend: +# -*- mode: yaml -*- +# vim: ft=yaml: + + +# put this file in /etc/ + + +[global] + e2dsa # enable file indexing and filesystem scanning + e2ts # and enable multimedia indexing + ansi # and colors in log messages + + # disable logging to stdout/journalctl and log to a file instead; + # $LOGS_DIRECTORY is usually /var/log/copyparty (comes from systemd) + # and copyparty replaces %Y-%m%d with Year-MonthDay, so the + # full path will be something like /var/log/copyparty/2023-1130.txt + # (note: enable compression by adding .xz at the end) + q, lo: $LOGS_DIRECTORY/%Y-%m%d.log + + # p: 80,443,3923 # listen on 80/443 as well (requires CAP_NET_BIND_SERVICE) + # i: 127.0.0.1 # only allow connections from localhost (reverse-proxies) + # ftp: 3921 # enable ftp server on port 3921 + # p: 3939 # listen on another port + # df: 16 # stop accepting uploads if less than 16 GB free disk space + # ver # show copyparty version in the controlpanel + # grid # show thumbnails/grid-view by default + # theme: 2 # monokai + # name: datasaver # change the server-name that's displayed in the browser + # stats, nos-dup # enable the prometheus endpoint, but disable the dupes counter (too slow) + # no-robots, force-js # make it harder for search engines to read your server + + +[accounts] + ed: wark # username: password + + +[/] # create a volume at "/" (the webroot), which will + /mnt # share the contents of the "/mnt" folder + accs: + rw: * # everyone gets read-write access, but + rwmda: ed # the user "ed" gets read-write-move-delete-admin diff --git a/contrib/systemd/copyparty@.service b/contrib/systemd/copyparty@.service new file mode 100644 index 00000000..cd891eab --- /dev/null +++ b/contrib/systemd/copyparty@.service @@ -0,0 +1,30 @@ +# this will start `/usr/bin/copyparty` +# and read config from `/etc/copyparty/copyparty.conf` +# +# the %i refers to whatever you put after the copyparty@ +# so with copyparty@foo.service, %i == foo +# +# unless you add -q to disable logging, you may want to remove the +# following line to allow buffering (slightly better performance): +# Environment=PYTHONUNBUFFERED=x + +[Unit] +Description=copyparty file server + +[Service] +Type=notify +SyslogIdentifier=copyparty +WorkingDirectory=/var/lib/copyparty-jail +Environment=PYTHONUNBUFFERED=x +Environment=PRTY_CONFIG=/etc/copyparty/copyparty.conf +ExecReload=/bin/kill -s USR1 $MAINPID + +# user to run as + where the TLS certificate is (if any) +User=%i +Environment=XDG_CONFIG_HOME=/home/%i/.config + +# run copyparty +ExecStart=/usr/bin/python3 /usr/bin/copyparty + +[Install] +WantedBy=multi-user.target diff --git a/contrib/systemd/index.md b/contrib/systemd/index.md new file mode 100644 index 00000000..cfd1b715 --- /dev/null +++ b/contrib/systemd/index.md @@ -0,0 +1,10 @@ +this is `/var/lib/copyparty-jail`, the fallback webroot when copyparty has not yet been configured + +please edit `/etc/copyparty/copyparty.conf` (if running as a system service) +or `$HOME/.config/copyparty/copyparty.conf` if running as a user service + +a basic configuration example is available at https://github.com/9001/copyparty/blob/hovudstraum/contrib/systemd/copyparty.example.conf +a configuration example that explains most flags is available at https://github.com/9001/copyparty/blob/hovudstraum/docs/chungus.conf + +the full list of configuration options can be seen at https://ocv.me/copyparty/helptext.html +or by running `copyparty --help` diff --git a/contrib/systemd/prisonparty-user.service b/contrib/systemd/prisonparty-user.service new file mode 100644 index 00000000..e0aaeaf8 --- /dev/null +++ b/contrib/systemd/prisonparty-user.service @@ -0,0 +1,33 @@ +# this will start `/usr/bin/copyparty` +# in a chroot, preventing accidental access elsewhere, +# and read copyparty config from `$HOME/.config/copyparty.conf` +# +# expose additional filesystem locations to copyparty +# by listing them between the last `%u` and `--` +# +# unless you add -q to disable logging, you may want to remove the +# following line to allow buffering (slightly better performance): +# Environment=PYTHONUNBUFFERED=x + +[Unit] +Description=copyparty file server + +[Service] +Type=notify +SyslogIdentifier=prisonparty +WorkingDirectory=/var/lib/copyparty-jail +Environment=PYTHONUNBUFFERED=x +Environment=PRTY_CONFIG=%h/.config/copyparty/copyparty.conf +ExecReload=/bin/kill -s USR1 $MAINPID + +# ensure there is a config +ExecStartPre=/bin/bash -c 'if [[ ! -f %h/.config/copyparty/copyparty.conf ]]; then mkdir -p %h/.config/copyparty; cp /etc/copyparty/copyparty.conf %h/.config/copyparty/copyparty.conf; fi' + +# run copyparty +ExecStart=/bin/bash /usr/bin/prisonparty /var/lib/copyparty-jail %u %u \ + %h/.config/copyparty \ + -- \ + /usr/bin/python3 /usr/bin/copyparty + +[Install] +WantedBy=default.target diff --git a/contrib/package/arch/prisonparty.service b/contrib/systemd/prisonparty@.service similarity index 51% rename from contrib/package/arch/prisonparty.service rename to contrib/systemd/prisonparty@.service index cd35ba99..d30010ae 100644 --- a/contrib/package/arch/prisonparty.service +++ b/contrib/systemd/prisonparty@.service @@ -1,11 +1,13 @@ -# this will start `/usr/bin/copyparty-sfx.py` +# this will start `/usr/bin/copyparty` # in a chroot, preventing accidental access elsewhere, -# and read copyparty config from `/etc/copyparty.d/*.conf` +# and read copyparty config from `/etc/copyparty/copyparty.conf` # # expose additional filesystem locations to copyparty -# by listing them between the last `cpp` and `--` +# by listing them between the last `%i` and `--` # -# `cpp cpp` = user/group to run copyparty as; can be IDs (1000 1000) +# `%i %i` = user/group to run copyparty as; can be IDs (1000 1000) +# the %i refers to whatever you put after the prisonparty@ +# so with prisonparty@foo.service, %i == foo # # unless you add -q to disable logging, you may want to remove the # following line to allow buffering (slightly better performance): @@ -15,19 +17,22 @@ Description=copyparty file server [Service] +Type=notify SyslogIdentifier=prisonparty -Environment=PYTHONUNBUFFERED=x WorkingDirectory=/var/lib/copyparty-jail +Environment=PYTHONUNBUFFERED=x +Environment=PRTY_CONFIG=/etc/copyparty/copyparty.conf ExecReload=/bin/kill -s USR1 $MAINPID -# stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running -ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf' +# user to run as + where the TLS certificate is (if any) +User=%i +Environment=XDG_CONFIG_HOME=/home/%i/.config # run copyparty -ExecStart=/bin/bash /usr/bin/prisonparty /var/lib/copyparty-jail cpp cpp \ - /etc/copyparty.d \ +ExecStart=/bin/bash /usr/bin/prisonparty /var/lib/copyparty-jail %i %i \ + /etc/copyparty \ -- \ - /usr/bin/python3 /usr/bin/copyparty -c /etc/copyparty.d/init + /usr/bin/python3 /usr/bin/copyparty [Install] WantedBy=multi-user.target diff --git a/copyparty/svchub.py b/copyparty/svchub.py index 07d85d83..1af13276 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -850,15 +850,6 @@ class SvcHub(object): def _check_env(self) -> None: al = self.args - try: - files = os.listdir(E.cfg) - except: - files = [] - - hits = [x for x in files if x.lower().endswith(".conf")] - if hits: - t = "WARNING: found config files in [%s]: %s\n config files are not expected here, and will NOT be loaded (unless your setup is intentionally hella funky)" - self.log("root", t % (E.cfg, ", ".join(hits)), 3) if self.args.no_bauth: t = "WARNING: --no-bauth disables support for the Android app; you may want to use --bauth-last instead" From 2943c7f2d51c4eb51706e246e67d72624e6d7f45 Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Thu, 7 Aug 2025 18:30:40 +0000 Subject: [PATCH 20/30] move the docker config smoketest over here --- copyparty/authsrv.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index 44629e62..9c52f046 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -1700,6 +1700,7 @@ class AuthSrv(object): if not mount and not self.args.idp_h_usr: # -h says our defaults are CWD at root and read/write for everyone axs = AXS(["*"], ["*"], None, None) + ehint = "" if self.is_lxc: t = "Read-access has been disabled due to failsafe: Docker detected, but %s. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to all of /w/ by adding the following arguments to the docker container: -v .::rw" if len(cfg_files_loaded) == 1: @@ -1709,10 +1710,21 @@ class AuthSrv(object): else: self.log(t % ("the config does not define any volumes",), 1) axs = AXS() + ehint = "; please try moving them up one level, into the parent folder:" elif self.args.c: t = "Read-access has been disabled due to failsafe: No volumes were defined by the config-file. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to the working-directory by adding the following arguments: -v .::rw" self.log(t, 1) axs = AXS() + ehint = ":" + if ehint: + try: + files = os.listdir(E.cfg) + except: + files = [] + hits = [x for x in files if x.lower().endswith(".conf")] + if hits: + t = "Hint: Found some config files in [%s], but these were not automatically loaded because they are in the wrong place%s %s\n" + self.log(t % (E.cfg, ehint, ", ".join(hits)), 3) zvf = {"tcolor": self.args.tcolor} vfs = VFS(self.log_func, absreal("."), "", "", axs, zvf) if not axs.uread: From bcc3b1568e2ceb46a40bff6661fd72970ebf3544 Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Thu, 7 Aug 2025 18:39:22 +0000 Subject: [PATCH 21/30] add qrcode to connectpage; closes #523 --- copyparty/web/svcs.html | 1 + copyparty/web/svcs.js | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/copyparty/web/svcs.html b/copyparty/web/svcs.html index a4b57746..26fd5267 100644 --- a/copyparty/web/svcs.html +++ b/copyparty/web/svcs.html @@ -37,6 +37,7 @@ {% if accs %}<code><b id="pw0">{{ pw }}</b></code>=password, {% endif %}<code><b>mp</b></code>=mountpoint </span> {% if accs %}<a href="#" id="setpw">use real password</a>{% endif %} + <a href="#" id="qr">show qr</a> </p> diff --git a/copyparty/web/svcs.js b/copyparty/web/svcs.js index 0de94523..dad7faab 100644 --- a/copyparty/web/svcs.js +++ b/copyparty/web/svcs.js @@ -49,12 +49,14 @@ function setos(os) { setos(WINDOWS ? 'win' : LINUX ? 'lin' : MACOS ? 'mac' : 'idk'); +var pw = ''; function setpw(e) { ev(e); modal.prompt('password:', '', function (v) { if (!v) return; + pw = v; var pw0 = ebi('pw0').innerHTML, oa = QSA('b'); @@ -67,3 +69,12 @@ function setpw(e) { } if (ebi('setpw')) ebi('setpw').onclick = setpw; + + +ebi('qr').onclick = function () { + var url = ('' + location).split('?')[0]; + if (pw) + url += '?pw=' + pw; + var txt = esc(url) + '<img class="b64" width="100" height="100" src="' + addq(url, 'qr') + '" />'; + modal.alert(txt); +}; From 8b31ed8816506776950cdc44ef7fc4a5ff681fa9 Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Thu, 7 Aug 2025 19:11:28 +0000 Subject: [PATCH 22/30] text-editor: optional EOL conversion; closes #513 --- copyparty/__main__.py | 1 + copyparty/cfg.py | 2 ++ copyparty/httpcli.py | 4 ++++ copyparty/util.py | 12 ++++++++++++ tests/util.py | 2 +- 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 0715af28..d0fc325e 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1528,6 +1528,7 @@ def add_db_metadata(ap): def add_txt(ap): ap2 = ap.add_argument_group("textfile options") ap2.add_argument("--md-hist", metavar="TXT", type=u, default="s", help="where to store old version of markdown files; [\033[32ms\033[0m]=subfolder, [\033[32mv\033[0m]=volume-histpath, [\033[32mn\033[0m]=nope/disabled (volflag=md_hist)") + ap2.add_argument("--txt-eol", metavar="TYPE", type=u, default="", help="enable EOL conversion when writing documents; supported: CRLF, LF (volflag=txt_eol)") ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="the textfile editor will check for serverside changes every \033[33mSEC\033[0m seconds") ap2.add_argument("-emp", action="store_true", help="enable markdown plugins -- neat but dangerous, big XSS risk") ap2.add_argument("--exp", action="store_true", help="enable textfile expansion -- replace {{self.ip}} and such; see \033[33m--help-exp\033[0m (volflag=exp)") diff --git a/copyparty/cfg.py b/copyparty/cfg.py index 2f75ab28..8a07f956 100644 --- a/copyparty/cfg.py +++ b/copyparty/cfg.py @@ -111,6 +111,7 @@ def vf_vmap() -> dict[str, str]: "tail_tmax", "tail_who", "tcolor", + "txt_eol", "unlist", "u2abort", "u2ts", @@ -322,6 +323,7 @@ flagcats = { "exp": "enable textfile expansion; see --help-exp", "exp_md": "placeholders to expand in markdown files; see --help", "exp_lg": "placeholders to expand in prologue/epilogue; see --help", + "txt_eol=lf": "enable EOL conversion when writing docs (LF or CRLF)", }, "tailing": { "notail": "disable ?tail (download a growing file continuously)", diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 3da299c7..e1c91c51 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -62,6 +62,7 @@ from .util import ( alltrace, atomic_move, b64dec, + eol_conv, exclude_dotfiles, formatdate, fsenc, @@ -3679,6 +3680,9 @@ class HttpCli(object): if p_field != "body": raise Pebkac(400, "expected body, got {}".format(p_field)) + if "txt_eol" in vfs.flags: + p_data = eol_conv(p_data, vfs.flags["txt_eol"]) + xbu = vfs.flags.get("xbu") if xbu: if not runhook( diff --git a/copyparty/util.py b/copyparty/util.py index 15b1924b..588028ab 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -2982,6 +2982,18 @@ def justcopy( return tlen, "checksum-disabled", "checksum-disabled" +def eol_conv( + fin: Generator[bytes, None, None], + conv: str +) -> Generator[bytes, None, None]: + crlf = conv.lower() == "crlf" + for buf in fin: + buf = buf.replace(b"\r", b"") + if crlf: + buf = buf.replace(b"\n", b"\r\n") + yield buf + + def hashcopy( fin: Generator[bytes, None, None], fout: Union[typing.BinaryIO, typing.IO[Any]], diff --git a/tests/util.py b/tests/util.py index 7b4ad2aa..5968215a 100644 --- a/tests/util.py +++ b/tests/util.py @@ -164,7 +164,7 @@ class Cfg(Namespace): ex = "ctl_re db_act forget_ip idp_cookie idp_store k304 loris no304 nosubtle re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow zipmaxn zipmaxs" ka.update(**{k: 0 for k in ex.split()}) - ex = "ah_alg bname chmod_f chpw_db doctitle df exit favico idp_h_usr ipa html_head lg_sba lg_sbf log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i shr tcolor textfiles unlist vname xff_src zipmaxt R RS SR" + ex = "ah_alg bname chmod_f chpw_db doctitle df exit favico idp_h_usr ipa html_head lg_sba lg_sbf log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i shr tcolor textfiles txt_eol unlist vname xff_src zipmaxt R RS SR" ka.update(**{k: "" for k in ex.split()}) ex = "ban_403 ban_404 ban_422 ban_pw ban_pwc ban_url spinner" From 13499d2846eb48b40fe6de93d4c7a7a90545db03 Mon Sep 17 00:00:00 2001 From: icxes <dev.null@need.moe> Date: Thu, 7 Aug 2025 22:42:33 +0300 Subject: [PATCH 23/30] remove prisonparty-user.service there is no real point to a prisonparty user service, as chroot requires root --- contrib/systemd/prisonparty-user.service | 33 ------------------------ 1 file changed, 33 deletions(-) delete mode 100644 contrib/systemd/prisonparty-user.service diff --git a/contrib/systemd/prisonparty-user.service b/contrib/systemd/prisonparty-user.service deleted file mode 100644 index e0aaeaf8..00000000 --- a/contrib/systemd/prisonparty-user.service +++ /dev/null @@ -1,33 +0,0 @@ -# this will start `/usr/bin/copyparty` -# in a chroot, preventing accidental access elsewhere, -# and read copyparty config from `$HOME/.config/copyparty.conf` -# -# expose additional filesystem locations to copyparty -# by listing them between the last `%u` and `--` -# -# unless you add -q to disable logging, you may want to remove the -# following line to allow buffering (slightly better performance): -# Environment=PYTHONUNBUFFERED=x - -[Unit] -Description=copyparty file server - -[Service] -Type=notify -SyslogIdentifier=prisonparty -WorkingDirectory=/var/lib/copyparty-jail -Environment=PYTHONUNBUFFERED=x -Environment=PRTY_CONFIG=%h/.config/copyparty/copyparty.conf -ExecReload=/bin/kill -s USR1 $MAINPID - -# ensure there is a config -ExecStartPre=/bin/bash -c 'if [[ ! -f %h/.config/copyparty/copyparty.conf ]]; then mkdir -p %h/.config/copyparty; cp /etc/copyparty/copyparty.conf %h/.config/copyparty/copyparty.conf; fi' - -# run copyparty -ExecStart=/bin/bash /usr/bin/prisonparty /var/lib/copyparty-jail %u %u \ - %h/.config/copyparty \ - -- \ - /usr/bin/python3 /usr/bin/copyparty - -[Install] -WantedBy=default.target From 3c42a34f7b43bdb17ba40432c3a493aedbaf5a88 Mon Sep 17 00:00:00 2001 From: icxes <dev.null@need.moe> Date: Thu, 7 Aug 2025 22:47:46 +0300 Subject: [PATCH 24/30] update PKGBUILD; remove prisonparty user service --- contrib/package/arch/PKGBUILD | 1 - 1 file changed, 1 deletion(-) diff --git a/contrib/package/arch/PKGBUILD b/contrib/package/arch/PKGBUILD index c6dc4687..2673ec2a 100644 --- a/contrib/package/arch/PKGBUILD +++ b/contrib/package/arch/PKGBUILD @@ -43,7 +43,6 @@ package() { install -Dm644 "contrib/systemd/${pkgname}@.service" "${pkgdir}/usr/lib/systemd/system/${pkgname}@.service" install -Dm644 "contrib/systemd/${pkgname}-user.service" "${pkgdir}/usr/lib/systemd/user/${pkgname}.service" install -Dm644 "contrib/systemd/prisonparty@.service" "${pkgdir}/usr/lib/systemd/system/prisonparty@.service" - install -Dm644 "contrib/systemd/prisonparty-user.service" "${pkgdir}/usr/lib/systemd/user/prisonparty.service" install -Dm644 "contrib/systemd/index.md" "${pkgdir}/var/lib/${pkgname}-jail/README.md" install -Dm644 "LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" } From 346515ccf17086392ef0440cad686a4065b6a54c Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Thu, 7 Aug 2025 20:29:44 +0000 Subject: [PATCH 25/30] add optional username login; closes #511 --- README.md | 10 ++++++++++ copyparty/__main__.py | 6 +++++- copyparty/authsrv.py | 11 +++++++++++ copyparty/ftpd.py | 7 ++++++- copyparty/httpcli.py | 14 ++++++++++++++ copyparty/pwhash.py | 4 ++++ copyparty/util.py | 3 +-- copyparty/web/splash.html | 5 +++++ copyparty/web/splash.js | 2 +- tests/util.py | 2 +- 10 files changed, 58 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 39bd932f..e063e4af 100644 --- a/README.md +++ b/README.md @@ -437,6 +437,7 @@ upgrade notes * can I link someone to a password-protected volume/file by including the password in the URL? * yes, by adding `?pw=hunter2` to the end; replace `?` with `&` if there are parameters in the URL already, meaning it contains a `?` near the end + * if you have enabled `--accounts` then do `?pw=username:password` instead * how do I stop `.hist` folders from appearing everywhere on my HDD? * by default, a `.hist` folder is created inside each volume for the filesystem index, thumbnails, audio transcodes, and markdown document history. Use the `--hist` global-option or the `hist` volflag to move it somewhere else; see [database location](#database-location) @@ -1016,6 +1017,7 @@ a feed example: https://cd.ocv.me/a/d2/d22/?rss&fext=mp3 url parameters: * `pw=hunter2` for password auth + * if you enabled `--usernames` then do `pw=username:password` instead * `recursive` to also include subfolders * `title=foo` changes the feed title (default: folder name) * `fext=mp3,opus` only include mp3 and opus files (default: all) @@ -1301,6 +1303,7 @@ an FTP server can be started using `--ftp 3921`, and/or `--ftps` for explicit T * if you enable both `ftp` and `ftps`, the port-range will be divided in half * some older software (filezilla on debian-stable) cannot passive-mode with TLS * login with any username + your password, or put your password in the username field + * unless you enabled `--usernames` some recommended FTP / FTPS clients; `wark` = example password: * https://winscp.net/eng/download.php @@ -1318,6 +1321,7 @@ click the [connect](http://127.0.0.1:3923/?hc) button in the control-panel to se general usage: * login with any username + your password, or put your password in the username field (password field can be empty/whatever) + * unless you enabled `--usernames` on macos, connect from finder: * [Go] -> [Connect to Server...] -> http://192.168.123.1:3923/ @@ -1333,6 +1337,7 @@ using the GUI (winXP or later): * rightclick [my computer] -> [map network drive] -> Folder: `http://192.168.123.1:3923/` * on winXP only, click the `Sign up for online storage` hyperlink instead and put the URL there * providing your password as the username is recommended; the password field can be anything or empty + * unless you enabled `--usernames` the webdav client that's built into windows has the following list of bugs; you can avoid all of these by connecting with rclone instead: * win7+ doesn't actually send the password to the server when reauthenticating after a reboot unless you first try to login with an incorrect password and then switch to the correct password @@ -1390,6 +1395,7 @@ some **BIG WARNINGS** specific to SMB/CIFS, in decreasing importance: * the smb backend is not fully integrated with vfs, meaning there could be security issues (path traversal). Please use `--smb-port` (see below) and [prisonparty](./bin/prisonparty.sh) or [bubbleparty](./bin/bubbleparty.sh) * account passwords work per-volume as expected, and so does account permissions (read/write/move/delete), but `--smbw` must be given to allow write-access from smb * [shadowing](#shadowing) probably works as expected but no guarantees +* not compatible with pw-hashing or `--usernames` and some minor issues, * clients only see the first ~400 files in big folders; @@ -2506,6 +2512,8 @@ you can provide passwords using header `PW: hunter2`, cookie `cppwd=hunter2`, ur > for basic-authentication, all of the following are accepted: `password` / `whatever:password` / `password:whatever` (the username is ignored) +* unless you've enabled `--usernames`, then it's `PW: usr:pwd`, cookie `cppwd=usr:pwd`, url-param `?pw=usr:pwd` + NOTE: curl will not send the original filename if you use `-T` combined with url-params! Also, make sure to always leave a trailing slash in URLs unless you want to override the filename @@ -2721,6 +2729,8 @@ when generating hashes using `--ah-cli` for docker or systemd services, make sur * inspecting the generated salt using `--show-ah-salt` in copyparty service configuration * setting the same `--ah-salt` in both environments +> ⚠️ if you have enabled `--usernames` then provide the password as `username:password` when hashing it, for example `ed:hunter2` + ## https diff --git a/copyparty/__main__.py b/copyparty/__main__.py index d0fc325e..2b55b058 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -918,6 +918,9 @@ def get_sects(): copyparty will also hash and print any passwords that are non-hashed (password which do not start with '+') and then terminate afterwards + if you have enabled --accounts then the password + must be provided as username:password for hashing + \033[36m--ah-alg\033[0m specifies the hashing algorithm and a list of optional comma-separated arguments: @@ -1002,6 +1005,7 @@ def add_general(ap, nc, srvname): ap2.add_argument("-a", metavar="ACCT", type=u, action="append", help="\033[34mREPEATABLE:\033[0m add account, \033[33mUSER\033[0m:\033[33mPASS\033[0m; example [\033[32med:wark\033[0m]") ap2.add_argument("-v", metavar="VOL", type=u, action="append", help="\033[34mREPEATABLE:\033[0m add volume, \033[33mSRC\033[0m:\033[33mDST\033[0m:\033[33mFLAG\033[0m; examples [\033[32m.::r\033[0m], [\033[32m/mnt/nas/music:/music:r:aed\033[0m], see --help-accounts") ap2.add_argument("--grp", metavar="G:N,N", type=u, action="append", help="\033[34mREPEATABLE:\033[0m add group, \033[33mNAME\033[0m:\033[33mUSER1\033[0m,\033[33mUSER2\033[0m,\033[33m...\033[0m; example [\033[32madmins:ed,foo,bar\033[0m]") + ap2.add_argument("--usernames", action="store_true", help="require username and password for login; default is just password") ap2.add_argument("-ed", action="store_true", help="enable the ?dots url parameter / client option which allows clients to see dotfiles / hidden files (volflag=dots)") ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,xm", help="how to handle url-form POSTs; see \033[33m--help-urlform\033[0m") ap2.add_argument("--wintitle", metavar="TXT", type=u, default="cpp @ $pub", help="server terminal title, for example [\033[32m$ip-10.1.2.\033[0m] or [\033[32m$ip-]") @@ -1393,7 +1397,7 @@ def add_logging(ap): ap2.add_argument("--no-voldump", action="store_true", help="do not list volumes and permissions on startup") ap2.add_argument("--log-utc", action="store_true", help="do not use local timezone; assume the TZ env-var is UTC (tiny bit faster)") ap2.add_argument("--log-tdec", metavar="N", type=int, default=3, help="timestamp resolution / number of timestamp decimals") - ap2.add_argument("--log-badpwd", metavar="N", type=int, default=1, help="log failed login attempt passwords: 0=terse, 1=plaintext, 2=hashed") + ap2.add_argument("--log-badpwd", metavar="N", type=int, default=2, help="log failed login attempt passwords: 0=terse, 1=plaintext, 2=hashed") ap2.add_argument("--log-conn", action="store_true", help="debug: print tcp-server msgs") ap2.add_argument("--log-htp", action="store_true", help="debug: print http-server threadpool scaling") ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="print request \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all") diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index 9c52f046..0f1c94f9 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -2642,6 +2642,8 @@ class AuthSrv(object): self.re_pwd = None pwds = [re.escape(x) for x in self.iacct.keys()] pwds.extend(list(self.sesa)) + if self.args.usernames: + pwds.extend([x.split(":", 1)[1] for x in pwds if ":" in x]) if pwds: if self.ah.on: zs = r"(\[H\] pw:.*|[?&]pw=)([^&]+)" @@ -2942,6 +2944,9 @@ class AuthSrv(object): t = "minimum password length: %d characters" return False, t % (self.args.chpw_len,) + if self.args.usernames: + pw = "%s:%s" % (uname, pw) + hpw = self.ah.hash(pw) if self.ah.on else pw if hpw == self.acct[uname]: @@ -3033,6 +3038,12 @@ class AuthSrv(object): self.log("chpw: " + msg, 6) def setup_pwhash(self, acct: dict[str, str]) -> None: + if self.args.usernames: + for uname, pw in list(acct.items())[:]: + if pw.startswith("+") and len(pw) == 33: + continue + acct[uname] = "%s:%s" % (uname, pw) + self.ah = PWHash(self.args) if not self.ah.on: if self.args.ah_cli or self.args.ah_gen: diff --git a/copyparty/ftpd.py b/copyparty/ftpd.py index bace7d28..004fb492 100644 --- a/copyparty/ftpd.py +++ b/copyparty/ftpd.py @@ -83,7 +83,12 @@ class FtpAuth(DummyAuthorizer): uname = "*" if username != "anonymous": uname = "" - for zs in (password, username): + if args.usernames: + alts = ["%s:%s" % (username, password)] + else: + alts = password, username + + for zs in alts: zs = asrv.iacct.get(asrv.ah.hash(zs), "") if zs: uname = zs diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index e1c91c51..cb061ac2 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -2937,12 +2937,16 @@ class HttpCli(object): def handle_chpw(self) -> bool: assert self.parser # !rm + if self.args.usernames: + self.parser.require("uname", 64) pwd = self.parser.require("pw", 64) self.parser.drop() ok, msg = self.asrv.chpw(self.conn.hsrv.broker, self.uname, pwd) if ok: self.cbonk(self.conn.hsrv.gpwc, pwd, "pw", "too many password changes") + if self.args.usernames: + pwd = "%s:%s" % (self.uname, pwd) ok, msg = self.get_pwd_cookie(pwd) if ok: msg = "new password OK" @@ -2955,6 +2959,13 @@ class HttpCli(object): def handle_login(self) -> bool: assert self.parser # !rm + if self.args.usernames: + try: + un = self.parser.require("uname", 256) + except: + un = "" + else: + un = "" pwd = self.parser.require("cppwd", 64) try: uhash = self.parser.require("uhash", 256) @@ -2965,6 +2976,9 @@ class HttpCli(object): if not pwd: raise Pebkac(422, "password cannot be blank") + if un: + pwd = "%s:%s" % (un, pwd) + dst = self.args.SRS if self.vpath: dst += quotep(self.vpaths) diff --git a/copyparty/pwhash.py b/copyparty/pwhash.py index ebbd78f3..d4455ca8 100644 --- a/copyparty/pwhash.py +++ b/copyparty/pwhash.py @@ -147,6 +147,10 @@ class PWHash(object): def cli(self) -> None: import getpass + if self.args.usernames: + t = "since you have enabled --usernames, please provide username:password" + print(t) + while True: try: p1 = getpass.getpass("password> ") diff --git a/copyparty/util.py b/copyparty/util.py index 588028ab..b5a2c45e 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -2983,8 +2983,7 @@ def justcopy( def eol_conv( - fin: Generator[bytes, None, None], - conv: str + fin: Generator[bytes, None, None], conv: str ) -> Generator[bytes, None, None]: crlf = conv.lower() == "crlf" for buf in fin: diff --git a/copyparty/web/splash.html b/copyparty/web/splash.html index 8dd56bab..4363efd2 100644 --- a/copyparty/web/splash.html +++ b/copyparty/web/splash.html @@ -120,7 +120,12 @@ <div> <form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}"> <input type="hidden" id="la" name="act" value="login" /> + {% if this.args.usernames %} + <input type="text" id="lu" name="uname" placeholder=" username" size="12" /> + <input type="password" id="lp" name="cppwd" placeholder=" password" size="12" /> + {% else %} <input type="password" id="lp" name="cppwd" placeholder=" password" /> + {% endif %} <input type="hidden" name="uhash" id="uhash" value="x" /> <input type="submit" id="ls" value="login" /> {% if chpw %} diff --git a/copyparty/web/splash.js b/copyparty/web/splash.js index 1a24beb2..52e666f3 100644 --- a/copyparty/web/splash.js +++ b/copyparty/web/splash.js @@ -416,7 +416,7 @@ try { catch (ex) { } tt.init(); -var o = QS('input[name="cppwd"]'); +var o = QS('input[name="uname"]') || QS('input[name="cppwd"]'); if (!ebi('c') && o.offsetTop + o.offsetHeight < window.innerHeight) o.focus(); diff --git a/tests/util.py b/tests/util.py index 5968215a..d3884ca9 100644 --- a/tests/util.py +++ b/tests/util.py @@ -143,7 +143,7 @@ class Cfg(Namespace): def __init__(self, a=None, v=None, c=None, **ka0): ka = {} - ex = "allow_flac allow_wav chpw cookie_lax daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only ih ihead localtime magic nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_fnugg no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tail no_tarcmp no_thumb no_vthumb no_zip nrand nsort nw og og_no_head og_s_title ohead q rand re_dirsz reflink rmagic rss smb srch_dbg srch_excl stats uqe vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs" + ex = "allow_flac allow_wav chpw cookie_lax daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only ih ihead localtime magic nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_fnugg no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tail no_tarcmp no_thumb no_vthumb no_zip nrand nsort nw og og_no_head og_s_title ohead q rand re_dirsz reflink rmagic rss smb srch_dbg srch_excl stats uqe usernames vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs" ka.update(**{k: False for k in ex.split()}) ex = "dav_inf dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump re_dhash see_dots plain_ip" From 1b71294aab0bfa4be10a71abacc84a397a6d78dc Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Thu, 7 Aug 2025 20:30:09 +0000 Subject: [PATCH 26/30] close #387 --- copyparty/up2k.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/copyparty/up2k.py b/copyparty/up2k.py index 785d0a4f..b233cecb 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -375,11 +375,12 @@ class Up2k(object): if ineed == ihash or not ineed: continue + poke = job["poke"] zt = ( ineed / ihash, job["size"], - int(job["t0c"]), - int(job["poke"]), + int(job.get("t0c", poke)), + int(poke), djoin(vtop, job["prel"], job["name"]), ) ret.append(zt) From d9e3f998d1f5b508f7b57782d44ce43f9dd75e55 Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Thu, 7 Aug 2025 20:53:25 +0000 Subject: [PATCH 27/30] fix zipmaxu (it did nothing) --- copyparty/httpcli.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index cb061ac2..73af0df7 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -4667,7 +4667,9 @@ class HttpCli(object): else: fn = self.host.split(":")[0] - if vn.flags.get("zipmax") and (not self.uname or not "zipmaxu" in vn.flags): + if vn.flags.get("zipmax") and not ( + vn.flags.get("zipmaxu") and self.uname != "*" + ): maxs = vn.flags.get("zipmaxs_v") or 0 maxn = vn.flags.get("zipmaxn_v") or 0 nf = 0 From 1bf23fabc6838c79bd7a95b6c97661e16504e57d Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Thu, 7 Aug 2025 20:59:41 +0000 Subject: [PATCH 28/30] unames: fix shares --- copyparty/httpcli.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 73af0df7..402d623f 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -2959,9 +2959,11 @@ class HttpCli(object): def handle_login(self) -> bool: assert self.parser # !rm - if self.args.usernames: + if self.args.usernames and not ( + self.args.shr and self.vpath.startswith(self.args.shr1) + ): try: - un = self.parser.require("uname", 256) + un = self.parser.require("uname", 64) except: un = "" else: From 36ab323d084d78491814ead1c71af5996ae249d4 Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Thu, 7 Aug 2025 21:26:59 +0000 Subject: [PATCH 29/30] sfx: simplify (remove bruteforce packing) --- scripts/make-sfx.sh | 21 ++++++--------------- scripts/rls.sh | 29 +---------------------------- 2 files changed, 7 insertions(+), 43 deletions(-) diff --git a/scripts/make-sfx.sh b/scripts/make-sfx.sh index deb7bee0..a1ee1a06 100755 --- a/scripts/make-sfx.sh +++ b/scripts/make-sfx.sh @@ -74,7 +74,6 @@ gtar=$(command -v gtar || command -v gnutar) || true sed() { gsed "$@"; } find() { gfind "$@"; } sort() { gsort "$@"; } - shuf() { gshuf "$@"; } nproc() { gnproc; } sha1sum() { shasum "$@"; } unexpand() { gunexpand "$@"; } @@ -157,9 +156,9 @@ stamp=$( done | sort | tail -n 1 | sha1sum | cut -c-16 ) -rm -rf sfx$CSN/* -mkdir -p sfx$CSN build -cd sfx$CSN +rm -rf sfx/* +mkdir -p sfx build +cd sfx tmpdir="$( printf '%s\n' "$TMPDIR" /tmp | @@ -395,7 +394,7 @@ ts=$(date -u +%s) hts=$(date -u +%Y-%m%d-%H%M%S) # --date=@$ts (thx osx) mkdir -p ../dist -sfx_out=../dist/copyparty-sfx$CSN +sfx_out=../dist/copyparty-sfx echo cleanup find -name '*.pyc' -delete @@ -554,7 +553,7 @@ gzres() { } -zdir="$tmpdir/cpp-mksfx$CSN" +zdir="$tmpdir/cpp-mksfx" [ -e "$zdir/$stamp" ] || rm -rf "$zdir" mkdir -p "$zdir" echo a > "$zdir/$stamp" @@ -583,15 +582,7 @@ echo gen tarlist for d in copyparty partftpy magic j2 py2 py37 ftp; do find $d -type f || true; done | # strip_hints sed -r 's/(.*)\.(.*)/\2 \1/' | LC_ALL=C sort | sed -r 's/([^ ]*) (.*)/\2.\1/' | grep -vE '/list1?$' > list1 - -for n in {1..50}; do - (grep -vE '\.gz$' list1; grep -E '\.gz$' list1 | (shuf||gshuf) ) >list || true - s=$( (sha1sum||shasum) < list | cut -c-16) - grep -q $s "$zdir/h" 2>/dev/null && continue - echo $s >> "$zdir/h" - break -done -[ $n -eq 50 ] && exit +(grep -vE '\.gz$' list1; grep -E '\.gz$' list1) >list echo creating tar tar -cf tar "${targs[@]}" --numeric-owner -T list diff --git a/scripts/rls.sh b/scripts/rls.sh index 613b53e0..e72eef0d 100755 --- a/scripts/rls.sh +++ b/scripts/rls.sh @@ -32,11 +32,7 @@ v=$1 rm -f ../dist/copyparty-sfx* shift ./make-sfx.sh "$@" -f=../dist/copyparty-sfx -[ -e $f.py ] && s= || s=-gz -# TODO: the -gz suffix is gone, can drop all the $s stuff probably - -$f$s.py --version >/dev/null +../dist/copyparty-sfx.py --version >/dev/null while [ "$1" ]; do case "$1" in @@ -46,27 +42,4 @@ while [ "$1" ]; do shift done -[ $parallel -gt 1 ] && { - printf '\033[%s' s 2r H "0;1;37;44mbruteforcing sfx size -- press enter to terminate" K u "7m $* " K $'27m\n' - trap "rm -f .sfx-run; printf '\033[%s' s r u" INT TERM EXIT - touch .sfx-run - min=99999999 - for ((a=0; a<$parallel; a++)); do - while [ -e .sfx-run ]; do - CSN=$a ./make-sfx.sh re "$@" - sz=$(wc -c <$f$a$s.py | awk '{print$1}') - [ $sz -ge $min ] && continue - mv $f$a$s.py $f$s.py.$sz - min=$sz - done & - done - read - exit -} - -while true; do - mv $f$s.py $f$s.$(wc -c <$f$s.py | awk '{print$1}').py - ./make-sfx.sh re "$@" -done - # git tag -d v$v; git push --delete origin v$v From 33497e6b11b41c82c1fc5694a46d80f0c0b9b417 Mon Sep 17 00:00:00 2001 From: ed <s@ocv.me> Date: Thu, 7 Aug 2025 21:28:56 +0000 Subject: [PATCH 30/30] sfx: add english-only --- README.md | 2 ++ scripts/rls.sh | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/README.md b/README.md index e063e4af..476d8ddc 100644 --- a/README.md +++ b/README.md @@ -2848,6 +2848,8 @@ these are standalone programs and will never be imported / evaluated by copypart the self-contained "binary" (recommended!) [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) will unpack itself and run copyparty, assuming you have python installed of course +if you only need english, [copyparty-en.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-en.py) is the same thing but smaller + you can reduce the sfx size by repacking it; see [./docs/devnotes.md#sfx-repack](./docs/devnotes.md#sfx-repack) diff --git a/scripts/rls.sh b/scripts/rls.sh index e72eef0d..767ca2de 100755 --- a/scripts/rls.sh +++ b/scripts/rls.sh @@ -33,6 +33,7 @@ rm -f ../dist/copyparty-sfx* shift ./make-sfx.sh "$@" ../dist/copyparty-sfx.py --version >/dev/null +mv ../dist/copyparty-{sfx,int}.py while [ "$1" ]; do case "$1" in @@ -42,4 +43,8 @@ while [ "$1" ]; do shift done +./make-sfx.sh re lang eng "$@" +mv ../dist/copyparty-{sfx,en}.py +mv ../dist/copyparty-{int,sfx}.py + # git tag -d v$v; git push --delete origin v$v