copyparty 1.9.25__py3-none-any.whl → 1.9.27__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
copyparty/__main__.py CHANGED
@@ -816,8 +816,8 @@ def add_general(ap, nc, srvname):
816
816
  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]")
817
817
  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]")
818
818
  ap2.add_argument("-ed", action="store_true", help="enable the ?dots url parameter / client option which allows clients to see dotfiles / hidden files")
819
- ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,get", help="how to handle url-form POSTs; see --help-urlform")
820
- ap2.add_argument("--wintitle", metavar="TXT", type=u, default="cpp @ $pub", help="window title, for example [\033[32m$ip-10.1.2.\033[0m] or [\033[32m$ip-]")
819
+ ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,get", help="how to handle url-form POSTs; see \033[33m--help-urlform\033[0m")
820
+ 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-]")
821
821
  ap2.add_argument("--name", metavar="TXT", type=u, default=srvname, help="server name (displayed topleft in browser and in mDNS)")
822
822
  ap2.add_argument("--license", action="store_true", help="show licenses and exit")
823
823
  ap2.add_argument("--version", action="store_true", help="show versions and exit")
@@ -837,12 +837,12 @@ def add_qr(ap, tty):
837
837
 
838
838
  def add_upload(ap):
839
839
  ap2 = ap.add_argument_group('upload options')
840
- ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads, hiding them from clients unless -ed")
840
+ ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads, hiding them from clients unless \033[33m-ed\033[0m")
841
841
  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")
842
- ap2.add_argument("--unpost", metavar="SEC", type=int, default=3600*12, help="grace period where uploads can be deleted by the uploader, even without delete permissions; 0=disabled")
842
+ ap2.add_argument("--unpost", metavar="SEC", type=int, default=3600*12, help="grace period where uploads can be deleted by the uploader, even without delete permissions; 0=disabled, default=12h")
843
843
  ap2.add_argument("--blank-wt", metavar="SEC", type=int, default=300, help="file write grace period (any client can write to a blank file last-modified more recently than \033[33mSEC\033[0m seconds ago)")
844
- ap2.add_argument("--reg-cap", metavar="N", type=int, default=38400, help="max number of uploads to keep in memory when running without -e2d; roughly 1 MiB RAM per 600")
845
- ap2.add_argument("--no-fpool", action="store_true", help="disable file-handle pooling -- instead, repeatedly close and reopen files during upload (very slow on windows)")
844
+ ap2.add_argument("--reg-cap", metavar="N", type=int, default=38400, help="max number of uploads to keep in memory when running without \033[33m-e2d\033[0m; roughly 1 MiB RAM per 600")
845
+ ap2.add_argument("--no-fpool", action="store_true", help="disable file-handle pooling -- instead, repeatedly close and reopen files during upload (bad idea to enable this on windows and/or cow filesystems)")
846
846
  ap2.add_argument("--use-fpool", action="store_true", help="force file-handle pooling, even when it might be dangerous (multiprocessing, filesystems lacking sparse-files support, ...)")
847
847
  ap2.add_argument("--hardlink", action="store_true", help="prefer hardlinks instead of symlinks when possible (within same filesystem) (volflag=hardlink)")
848
848
  ap2.add_argument("--never-symlink", action="store_true", help="do not fallback to symlinks when a hardlink cannot be made (volflag=neversymlink)")
@@ -852,7 +852,7 @@ def add_upload(ap):
852
852
  ap2.add_argument("--snap-wri", metavar="SEC", type=int, default=300, help="write upload state to ./hist/up2k.snap every \033[33mSEC\033[0m seconds; allows resuming incomplete uploads after a server crash")
853
853
  ap2.add_argument("--snap-drop", metavar="MIN", type=float, default=1440, help="forget unfinished uploads after \033[33mMIN\033[0m minutes; impossible to resume them after that (360=6h, 1440=24h)")
854
854
  ap2.add_argument("--u2ts", metavar="TXT", type=u, default="c", help="how to timestamp uploaded files; [\033[32mc\033[0m]=client-last-modified, [\033[32mu\033[0m]=upload-time, [\033[32mfc\033[0m]=force-c, [\033[32mfu\033[0m]=force-u (volflag=u2ts)")
855
- ap2.add_argument("--rand", action="store_true", help="force randomized filenames, --nrand chars long (volflag=rand)")
855
+ ap2.add_argument("--rand", action="store_true", help="force randomized filenames, \033[33m--nrand\033[0m chars long (volflag=rand)")
856
856
  ap2.add_argument("--nrand", metavar="NUM", type=int, default=9, help="randomized filenames length (volflag=nrand)")
857
857
  ap2.add_argument("--magic", action="store_true", help="enable filetype detection on nameless uploads (volflag=magic)")
858
858
  ap2.add_argument("--df", metavar="GiB", type=float, default=0, help="ensure \033[33mGiB\033[0m free disk space by rejecting upload requests")
@@ -866,12 +866,12 @@ def add_network(ap):
866
866
  ap2 = ap.add_argument_group('network options')
867
867
  ap2.add_argument("-i", metavar="IP", type=u, default="::", help="ip to bind (comma-sep.), default: all IPv4 and IPv6")
868
868
  ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to bind (comma/range)")
869
- ap2.add_argument("--ll", action="store_true", help="include link-local IPv4/IPv6 even if the NIC has routable IPs (breaks some mdns clients)")
870
- ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; [\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")
869
+ 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)")
870
+ 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")
871
871
  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 (argument must be lowercase, but not the actual header)")
872
- ap2.add_argument("--xff-src", metavar="IP", type=u, default="127., ::1", help="comma-separated list of trusted reverse-proxy IPs; only accept the real-ip header (--xff-hdr) if the incoming connection is from an IP starting with either of these. Can be disabled with [\033[32many\033[0m] if you are behind cloudflare (or similar) and are using --xff-hdr=cf-connecting-ip (or similar)")
872
+ ap2.add_argument("--xff-src", metavar="IP", type=u, default="127., ::1", help="comma-separated list of trusted reverse-proxy IPs; only accept the real-ip header (\033[33m--xff-hdr\033[0m) if the incoming connection is from an IP starting with either of these. 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)")
873
873
  ap2.add_argument("--ipa", metavar="PREFIX", type=u, default="", help="only accept connections from IP-addresses starting with \033[33mPREFIX\033[0m; example: [\033[32m127., 10.89., 192.168.\033[0m]")
874
- 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 (eg. /foo/bar)")
874
+ 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]")
875
875
  if ANYWIN:
876
876
  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")
877
877
  else:
@@ -900,7 +900,7 @@ def add_cert(ap, cert_path):
900
900
  ap2 = ap.add_argument_group('TLS certificate generator options')
901
901
  ap2.add_argument("--no-crt", action="store_true", help="disable automatic certificate creation")
902
902
  ap2.add_argument("--crt-ns", metavar="N,N", type=u, default="", help="comma-separated list of FQDNs (domains) to add into the certificate")
903
- ap2.add_argument("--crt-exact", action="store_true", help="do not add wildcard entries for each --crt-ns")
903
+ ap2.add_argument("--crt-exact", action="store_true", help="do not add wildcard entries for each \033[33m--crt-ns\033[0m")
904
904
  ap2.add_argument("--crt-noip", action="store_true", help="do not add autodetected IP addresses into cert")
905
905
  ap2.add_argument("--crt-nolo", action="store_true", help="do not add 127.0.0.1 / localhost into cert")
906
906
  ap2.add_argument("--crt-nohn", action="store_true", help="do not add mDNS names / hostname into cert")
@@ -911,7 +911,7 @@ def add_cert(ap, cert_path):
911
911
  ap2.add_argument("--crt-cnc", metavar="TXT", type=u, default="--crt-cn", help="override CA name")
912
912
  ap2.add_argument("--crt-cns", metavar="TXT", type=u, default="--crt-cn cpp", help="override server-cert name")
913
913
  ap2.add_argument("--crt-back", metavar="HRS", type=float, default=72, help="backdate in hours")
914
- ap2.add_argument("--crt-alg", metavar="S-N", type=u, default="ecdsa-256", help="algorithm and keysize; one of these: ecdsa-256 rsa-4096 rsa-2048")
914
+ ap2.add_argument("--crt-alg", metavar="S-N", type=u, default="ecdsa-256", help="algorithm and keysize; one of these: \033[32mecdsa-256 rsa-4096 rsa-2048\033[0m")
915
915
 
916
916
 
917
917
  def add_auth(ap):
@@ -926,19 +926,19 @@ def add_zeroconf(ap):
926
926
  ap2.add_argument("--z-off", metavar="NETS", type=u, default="", help="disable zeroconf on the comma-separated list of subnets and/or interface names/indexes")
927
927
  ap2.add_argument("--z-chk", metavar="SEC", type=int, default=10, help="check for network changes every \033[33mSEC\033[0m seconds (0=disable)")
928
928
  ap2.add_argument("-zv", action="store_true", help="verbose all zeroconf backends")
929
- ap2.add_argument("--mc-hop", metavar="SEC", type=int, default=0, help="rejoin multicast groups every \033[33mSEC\033[0m seconds (workaround for some switches/routers which cause mDNS to suddenly stop working after some time); try [\033[32m300\033[0m] or [\033[32m180\033[0m]")
929
+ ap2.add_argument("--mc-hop", metavar="SEC", type=int, default=0, help="rejoin multicast groups every \033[33mSEC\033[0m seconds (workaround for some switches/routers which cause mDNS to suddenly stop working after some time); try [\033[32m300\033[0m] or [\033[32m180\033[0m]\n └─note: can be due to firewalls; make sure UDP port 5353 is open in both directions (on clients too)")
930
930
 
931
931
 
932
932
  def add_zc_mdns(ap):
933
933
  ap2 = ap.add_argument_group("Zeroconf-mDNS options; also see --help-zm")
934
934
  ap2.add_argument("--zm", action="store_true", help="announce the enabled protocols over mDNS (multicast DNS-SD) -- compatible with KDE, gnome, macOS, ...")
935
- ap2.add_argument("--zm-on", metavar="NETS", type=u, default="", help="enable zeroconf ONLY on the comma-separated list of subnets and/or interface names/indexes")
936
- ap2.add_argument("--zm-off", metavar="NETS", type=u, default="", help="disable zeroconf on the comma-separated list of subnets and/or interface names/indexes")
935
+ ap2.add_argument("--zm-on", metavar="NETS", type=u, default="", help="enable mDNS ONLY on the comma-separated list of subnets and/or interface names/indexes")
936
+ ap2.add_argument("--zm-off", metavar="NETS", type=u, default="", help="disable mDNS on the comma-separated list of subnets and/or interface names/indexes")
937
937
  ap2.add_argument("--zm4", action="store_true", help="IPv4 only -- try this if some clients can't connect")
938
938
  ap2.add_argument("--zm6", action="store_true", help="IPv6 only")
939
939
  ap2.add_argument("--zmv", action="store_true", help="verbose mdns")
940
940
  ap2.add_argument("--zmvv", action="store_true", help="verboser mdns")
941
- ap2.add_argument("--zms", metavar="dhf", type=u, default="", help="list of services to announce -- d=webdav h=http f=ftp s=smb -- lowercase=plaintext uppercase=TLS -- default: all enabled services except http/https (\033[32mDdfs\033[0m if \033[33m--ftp\033[0m and \033[33m--smb\033[0m is set)")
941
+ ap2.add_argument("--zms", metavar="dhf", type=u, default="", help="list of services to announce -- d=webdav h=http f=ftp s=smb -- lowercase=plaintext uppercase=TLS -- default: all enabled services except http/https (\033[32mDdfs\033[0m if \033[33m--ftp\033[0m and \033[33m--smb\033[0m is set, \033[32mDd\033[0m otherwise)")
942
942
  ap2.add_argument("--zm-ld", metavar="PATH", type=u, default="", help="link a specific folder for webdav shares")
943
943
  ap2.add_argument("--zm-lh", metavar="PATH", type=u, default="", help="link a specific folder for http shares")
944
944
  ap2.add_argument("--zm-lf", metavar="PATH", type=u, default="", help="link a specific folder for ftp shares")
@@ -946,14 +946,14 @@ def add_zc_mdns(ap):
946
946
  ap2.add_argument("--zm-mnic", action="store_true", help="merge NICs which share subnets; assume that same subnet means same network")
947
947
  ap2.add_argument("--zm-msub", action="store_true", help="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")
948
948
  ap2.add_argument("--zm-noneg", action="store_true", help="disable NSEC replies -- try this if some clients don't see copyparty")
949
- ap2.add_argument("--zm-spam", metavar="SEC", type=float, default=0, help="send unsolicited announce every \033[33mSEC\033[0m; useful if clients have IPs in a subnet which doesn't overlap with the server")
949
+ ap2.add_argument("--zm-spam", metavar="SEC", type=float, default=0, help="send unsolicited announce every \033[33mSEC\033[0m; useful if clients have IPs in a subnet which doesn't overlap with the server, or to avoid some firewall issues")
950
950
 
951
951
 
952
952
  def add_zc_ssdp(ap):
953
953
  ap2 = ap.add_argument_group("Zeroconf-SSDP options")
954
954
  ap2.add_argument("--zs", action="store_true", help="announce the enabled protocols over SSDP -- compatible with Windows")
955
- ap2.add_argument("--zs-on", metavar="NETS", type=u, default="", help="enable zeroconf ONLY on the comma-separated list of subnets and/or interface names/indexes")
956
- ap2.add_argument("--zs-off", metavar="NETS", type=u, default="", help="disable zeroconf on the comma-separated list of subnets and/or interface names/indexes")
955
+ ap2.add_argument("--zs-on", metavar="NETS", type=u, default="", help="enable SSDP ONLY on the comma-separated list of subnets and/or interface names/indexes")
956
+ ap2.add_argument("--zs-off", metavar="NETS", type=u, default="", help="disable SSDP on the comma-separated list of subnets and/or interface names/indexes")
957
957
  ap2.add_argument("--zsv", action="store_true", help="verbose SSDP")
958
958
  ap2.add_argument("--zsl", metavar="PATH", type=u, default="/?hc", help="location to include in the url (or a complete external URL), for example [\033[32mpriv/?pw=hunter2\033[0m] (goes directly to /priv/ with password hunter2) or [\033[32m?hc=priv&pw=hunter2\033[0m] (shows mounting options for /priv/ with password)")
959
959
  ap2.add_argument("--zsid", metavar="UUID", type=u, default=zsid, help="USN (device identifier) to announce")
@@ -982,7 +982,7 @@ def add_webdav(ap):
982
982
 
983
983
  def add_smb(ap):
984
984
  ap2 = ap.add_argument_group('SMB/CIFS options')
985
- ap2.add_argument("--smb", action="store_true", help="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.\n\033[1;31mWARNING:\033[0m this protocol is dangerous! Never expose to the internet!")
985
+ 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!")
986
986
  ap2.add_argument("--smbw", action="store_true", help="enable write support (please dont)")
987
987
  ap2.add_argument("--smb1", action="store_true", help="disable SMBv2, only enable SMBv1 (CIFS)")
988
988
  ap2.add_argument("--smb-port", metavar="PORT", type=int, default=445, help="port to listen on -- if you change this value, you must NAT from TCP:445 to this port using iptables or similar")
@@ -996,9 +996,9 @@ def add_smb(ap):
996
996
 
997
997
  def add_handlers(ap):
998
998
  ap2 = ap.add_argument_group('handlers (see --help-handlers)')
999
- ap2.add_argument("--on404", metavar="PY", type=u, action="append", help="handle 404s by executing PY file")
1000
- ap2.add_argument("--on403", metavar="PY", type=u, action="append", help="handle 403s by executing PY file")
1001
- ap2.add_argument("--hot-handlers", action="store_true", help="reload handlers on each request -- expensive but convenient when hacking on stuff")
999
+ ap2.add_argument("--on404", metavar="PY", type=u, action="append", help="handle 404s by executing \033[33mPY\033[0m file")
1000
+ ap2.add_argument("--on403", metavar="PY", type=u, action="append", help="handle 403s by executing \033[33mPY\033[0m file")
1001
+ ap2.add_argument("--hot-handlers", action="store_true", help="recompile handlers on each request -- expensive but convenient when hacking on stuff")
1002
1002
 
1003
1003
 
1004
1004
  def add_hooks(ap):
@@ -1033,17 +1033,17 @@ def add_yolo(ap):
1033
1033
  def add_optouts(ap):
1034
1034
  ap2 = ap.add_argument_group('opt-outs')
1035
1035
  ap2.add_argument("-nw", action="store_true", help="never write anything to disk (debug/benchmark)")
1036
- 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 which will deadlock copyparty)")
1036
+ 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)")
1037
1037
  ap2.add_argument("--no-dav", action="store_true", help="disable webdav support")
1038
1038
  ap2.add_argument("--no-del", action="store_true", help="disable delete operations")
1039
1039
  ap2.add_argument("--no-mv", action="store_true", help="disable move/rename operations")
1040
- ap2.add_argument("-nth", action="store_true", help="no title hostname; don't show --name in <title>")
1040
+ ap2.add_argument("-nth", action="store_true", help="no title hostname; don't show \033[33m--name\033[0m in <title>")
1041
1041
  ap2.add_argument("-nih", action="store_true", help="no info hostname -- don't show in UI")
1042
1042
  ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI")
1043
1043
  ap2.add_argument("-nb", action="store_true", help="no powered-by-copyparty branding in UI")
1044
1044
  ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
1045
1045
  ap2.add_argument("--no-tarcmp", action="store_true", help="disable download as compressed tar (?tar=gz, ?tar=bz2, ?tar=xz, ?tar=gz:9, ...)")
1046
- ap2.add_argument("--no-lifetime", action="store_true", help="disable automatic deletion of uploads after a certain time (as specified by the 'lifetime' volflag)")
1046
+ ap2.add_argument("--no-lifetime", action="store_true", help="do not allow clients (or server config) to schedule an upload to be deleted after a given time")
1047
1047
 
1048
1048
 
1049
1049
  def add_safety(ap):
@@ -1051,36 +1051,36 @@ def add_safety(ap):
1051
1051
  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")
1052
1052
  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")
1053
1053
  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")
1054
- ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, help="do a sanity/safety check of all volumes on startup; arguments \033[33mUSER\033[0m,\033[33mVOL\033[0m,\033[33mFLAGS\033[0m; example [\033[32m**,*,ln,p,r\033[0m]")
1054
+ ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, help="do a sanity/safety check of all volumes on startup; arguments \033[33mUSER\033[0m,\033[33mVOL\033[0m,\033[33mFLAGS\033[0m (see \033[33m--help-ls\033[0m); example [\033[32m**,*,ln,p,r\033[0m]")
1055
1055
  ap2.add_argument("--xvol", action="store_true", help="never follow symlinks leaving the volume root, unless the link is into another volume where the user has similar access (volflag=xvol)")
1056
1056
  ap2.add_argument("--xdev", action="store_true", help="stay within the filesystem of the volume root; do not descend into other devices (symlink or bind-mount to another HDD, ...) (volflag=xdev)")
1057
1057
  ap2.add_argument("--no-dot-mv", action="store_true", help="disallow moving dotfiles; makes it impossible to move folders containing dotfiles")
1058
- ap2.add_argument("--no-dot-ren", action="store_true", help="disallow renaming dotfiles; makes it impossible to make something a dotfile")
1058
+ ap2.add_argument("--no-dot-ren", action="store_true", help="disallow renaming dotfiles; makes it impossible to turn something into a dotfile")
1059
1059
  ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings")
1060
1060
  ap2.add_argument("--no-readme", action="store_true", help="disable rendering readme.md into directory listings")
1061
1061
  ap2.add_argument("--vague-403", action="store_true", help="send 404 instead of 403 (security through ambiguity, very enterprise)")
1062
- ap2.add_argument("--force-js", action="store_true", help="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")
1062
+ ap2.add_argument("--force-js", action="store_true", help="don't send folder listings as HTML, force clients to use the embedded json instead -- slight protection against misbehaving search engines which ignore \033[33m--no-robots\033[0m")
1063
1063
  ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything (volflag=norobots)")
1064
1064
  ap2.add_argument("--logout", metavar="H", type=float, default="8086", help="logout clients after \033[33mH\033[0m hours of inactivity; [\033[32m0.0028\033[0m]=10sec, [\033[32m0.1\033[0m]=6min, [\033[32m24\033[0m]=day, [\033[32m168\033[0m]=week, [\033[32m720\033[0m]=month, [\033[32m8760\033[0m]=year)")
1065
1065
  ap2.add_argument("--ban-pw", metavar="N,W,B", type=u, default="9,60,1440", help="more than \033[33mN\033[0m wrong passwords in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; disable with [\033[32mno\033[0m]")
1066
1066
  ap2.add_argument("--ban-404", metavar="N,W,B", type=u, default="50,60,1440", help="hitting more than \033[33mN\033[0m 404's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; only affects users who cannot see directory listings because their access is either g/G/h")
1067
1067
  ap2.add_argument("--ban-403", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m 403's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; [\033[32m1440\033[0m]=day, [\033[32m10080\033[0m]=week, [\033[32m43200\033[0m]=month")
1068
- ap2.add_argument("--ban-422", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m 422's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes (422 is server fuzzing, invalid POSTs and so)")
1069
- ap2.add_argument("--ban-url", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m sus URL's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; applies only to access g/G/h (decent replacement for --ban-404 if that can't be used)")
1068
+ ap2.add_argument("--ban-422", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m 422's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes (invalid requests, attempted exploits ++)")
1069
+ ap2.add_argument("--ban-url", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m sus URL's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; applies only to permissions g/G/h (decent replacement for \033[33m--ban-404\033[0m if that can't be used)")
1070
1070
  ap2.add_argument("--sus-urls", metavar="R", type=u, default=r"\.php$|(^|/)wp-(admin|content|includes)/", help="URLs which are considered sus / eligible for banning; disable with blank or [\033[32mno\033[0m]")
1071
1071
  ap2.add_argument("--nonsus-urls", metavar="R", type=u, default=r"^(favicon\.ico|robots\.txt)$|^apple-touch-icon|^\.well-known", help="harmless URLs ignored from 404-bans; disable with blank or [\033[32mno\033[0m]")
1072
1072
  ap2.add_argument("--aclose", metavar="MIN", type=int, default=10, help="if a client maxes out the server connection limit, downgrade it from connection:keep-alive to connection:close for \033[33mMIN\033[0m minutes (and also kill its active connections) -- disable with 0")
1073
1073
  ap2.add_argument("--loris", metavar="B", type=int, default=60, help="if a client maxes out the server connection limit without sending headers, ban it for \033[33mB\033[0m minutes; disable with [\033[32m0\033[0m]")
1074
1074
  ap2.add_argument("--acao", metavar="V[,V]", type=u, default="*", help="Access-Control-Allow-Origin; list of origins (domains/IPs without port) to accept requests from; [\033[32mhttps://1.2.3.4\033[0m]. Default [\033[32m*\033[0m] allows requests from all sites but removes cookies and http-auth; only ?pw=hunter2 survives")
1075
- ap2.add_argument("--acam", metavar="V[,V]", type=u, default="GET,HEAD", help="Access-Control-Allow-Methods; list of methods to accept from offsite ('*' behaves like described in --acao)")
1075
+ ap2.add_argument("--acam", metavar="V[,V]", type=u, default="GET,HEAD", help="Access-Control-Allow-Methods; list of methods to accept from offsite ('*' behaves like \033[33m--acao\033[0m's description)")
1076
1076
 
1077
1077
 
1078
1078
  def add_salt(ap, fk_salt, ah_salt):
1079
1079
  ap2 = ap.add_argument_group('salting options')
1080
- ap2.add_argument("--ah-alg", metavar="ALG", type=u, default="none", help="account-pw hashing algorithm; one of these, best to worst: argon2 scrypt sha2 none (each optionally followed by alg-specific comma-sep. config)")
1081
- ap2.add_argument("--ah-salt", metavar="SALT", type=u, default=ah_salt, help="account-pw salt; ignored if --ah-alg is none (default)")
1080
+ 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)")
1081
+ 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)")
1082
1082
  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]")
1083
- ap2.add_argument("--ah-cli", action="store_true", help="interactive shell which hashes passwords without ever storing or displaying the original passwords")
1083
+ ap2.add_argument("--ah-cli", action="store_true", help="launch an interactive shell which hashes passwords without ever storing or displaying the original passwords")
1084
1084
  ap2.add_argument("--fk-salt", metavar="SALT", type=u, default=fk_salt, help="per-file accesskey salt; used to generate unpredictable URLs for hidden files")
1085
1085
  ap2.add_argument("--warksalt", metavar="SALT", type=u, default="hunter2", help="up2k file-hash salt; serves no purpose, no reason to change this (but delete all databases if you do)")
1086
1086
 
@@ -1094,8 +1094,8 @@ def add_shutdown(ap):
1094
1094
 
1095
1095
  def add_logging(ap):
1096
1096
  ap2 = ap.add_argument_group('logging options')
1097
- ap2.add_argument("-q", action="store_true", help="quiet")
1098
- ap2.add_argument("-lo", metavar="PATH", type=u, help="logfile, example: \033[32mcpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz")
1097
+ ap2.add_argument("-q", action="store_true", help="quiet; disable most STDOUT messages")
1098
+ ap2.add_argument("-lo", metavar="PATH", type=u, help="logfile, example: \033[32mcpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz\033[0m (NB: some errors may appear on STDOUT only)")
1099
1099
  ap2.add_argument("--no-ansi", action="store_true", default=not VT100, help="disable colors; same as environment-variable NO_COLOR")
1100
1100
  ap2.add_argument("--ansi", action="store_true", help="force colors; overrides environment-variable NO_COLOR")
1101
1101
  ap2.add_argument("--no-logflush", action="store_true", help="don't flush the logfile after each write; tiny bit faster")
@@ -1104,7 +1104,7 @@ def add_logging(ap):
1104
1104
  ap2.add_argument("--log-badpwd", metavar="N", type=int, default=1, help="log failed login attempt passwords: 0=terse, 1=plaintext, 2=hashed")
1105
1105
  ap2.add_argument("--log-conn", action="store_true", help="debug: print tcp-server msgs")
1106
1106
  ap2.add_argument("--log-htp", action="store_true", help="debug: print http-server threadpool scaling")
1107
- ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="dump incoming header")
1107
+ ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="print request \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all")
1108
1108
  ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|\?th=[wj]$|/\.(_|ql_|DS_Store$|localized$)", help="dont log URLs matching regex \033[33mRE\033[0m")
1109
1109
 
1110
1110
 
@@ -1123,16 +1123,16 @@ def add_thumbnail(ap):
1123
1123
  ap2.add_argument("--th-size", metavar="WxH", default="320x256", help="thumbnail res (volflag=thsize)")
1124
1124
  ap2.add_argument("--th-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for generating thumbnails")
1125
1125
  ap2.add_argument("--th-convt", metavar="SEC", type=float, default=60, help="conversion timeout in seconds (volflag=convt)")
1126
- ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image by default (volflag=nocrop)")
1126
+ ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image by default (client can override in UI) (volflag=nocrop)")
1127
1127
  ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,ff", help="image decoders, in order of preference")
1128
1128
  ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
1129
1129
  ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
1130
- ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg output for video thumbs")
1131
- ap2.add_argument("--th-ff-swr", action="store_true", help="use swresample instead of soxr for audio thumbs")
1130
+ ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg output for video thumbs (avoids issues on some FFmpeg builds)")
1131
+ ap2.add_argument("--th-ff-swr", action="store_true", help="use swresample instead of soxr for audio thumbs (faster, lower accuracy, avoids issues on some FFmpeg builds)")
1132
1132
  ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown -- avoids doing keepalive pokes (updating the mtime) on thumbnail folders more often than \033[33mSEC\033[0m seconds")
1133
1133
  ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval; 0=disabled")
1134
- ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age -- folders which haven't been poked for longer than --th-poke seconds will get deleted every --th-clean seconds")
1135
- ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat/look for; enabling -e2d will make these case-insensitive, and also automatically select thumbnails for all folders that contain pics, even if none match this pattern")
1134
+ ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age -- folders which haven't been poked for longer than \033[33m--th-poke\033[0m seconds will get deleted every \033[33m--th-clean\033[0m seconds")
1135
+ ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat/look for; enabling \033[33m-e2d\033[0m will make these case-insensitive, and also automatically select thumbnails for all folders that contain pics, even if none match this pattern")
1136
1136
  # https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
1137
1137
  # https://github.com/libvips/libvips
1138
1138
  # ffmpeg -hide_banner -demuxers | awk '/^ D /{print$2}' | while IFS= read -r x; do ffmpeg -hide_banner -h demuxer=$x; done | grep -E '^Demuxer |extensions:'
@@ -1153,8 +1153,8 @@ def add_transcoding(ap):
1153
1153
  def add_db_general(ap, hcores):
1154
1154
  ap2 = ap.add_argument_group('general db options')
1155
1155
  ap2.add_argument("-e2d", action="store_true", help="enable up2k database, making files searchable + enables upload deduplication")
1156
- ap2.add_argument("-e2ds", action="store_true", help="scan writable folders for new files on startup; sets -e2d")
1157
- ap2.add_argument("-e2dsa", action="store_true", help="scans all folders on startup; sets -e2ds")
1156
+ ap2.add_argument("-e2ds", action="store_true", help="scan writable folders for new files on startup; sets \033[33m-e2d\033[0m")
1157
+ ap2.add_argument("-e2dsa", action="store_true", help="scans all folders on startup; sets \033[33m-e2ds\033[0m")
1158
1158
  ap2.add_argument("-e2v", action="store_true", help="verify file integrity; rehash all files and compare with db")
1159
1159
  ap2.add_argument("-e2vu", action="store_true", help="on hash mismatch: update the database with the new hash")
1160
1160
  ap2.add_argument("-e2vp", action="store_true", help="on hash mismatch: panic and quit copyparty")
@@ -1163,11 +1163,11 @@ def add_db_general(ap, hcores):
1163
1163
  ap2.add_argument("--no-idx", metavar="PTN", type=u, help="regex: disable indexing of matching absolute-filesystem-paths during e2ds folder scans (volflag=noidx)")
1164
1164
  ap2.add_argument("--no-dhash", action="store_true", help="disable rescan acceleration; do full database integrity check -- makes the db ~5%% smaller and bootup/rescans 3~10x slower")
1165
1165
  ap2.add_argument("--re-dhash", action="store_true", help="rebuild the cache if it gets out of sync (for example crash on startup during metadata scanning)")
1166
- ap2.add_argument("--no-forget", action="store_true", help="never forget indexed files, even when deleted from disk -- makes it impossible to ever upload the same file twice (volflag=noforget)")
1167
- ap2.add_argument("--dbd", metavar="PROFILE", default="wal", help="database durability profile; sets the tradeoff between robustness and speed, see --help-dbd (volflag=dbd)")
1166
+ ap2.add_argument("--no-forget", action="store_true", help="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 (volflag=noforget)")
1167
+ ap2.add_argument("--dbd", metavar="PROFILE", default="wal", help="database durability profile; sets the tradeoff between robustness and speed, see \033[33m--help-dbd\033[0m (volflag=dbd)")
1168
1168
  ap2.add_argument("--xlink", action="store_true", help="on upload: check all volumes for dupes, not just the target volume (volflag=xlink)")
1169
1169
  ap2.add_argument("--hash-mt", metavar="CORES", type=int, default=hcores, help="num cpu cores to use for file hashing; set 0 or 1 for single-core hashing")
1170
- ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="disk rescan volume interval, 0=off (volflag=scan)")
1170
+ ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="rescan filesystem for changes every \033[33mSEC\033[0m seconds; 0=off (volflag=scan)")
1171
1171
  ap2.add_argument("--db-act", metavar="SEC", type=float, default=10, help="defer any scheduled volume reindexing until \033[33mSEC\033[0m seconds after last db write (uploads, renames, ...)")
1172
1172
  ap2.add_argument("--srch-time", metavar="SEC", type=int, default=45, help="search deadline -- terminate searches running for more than \033[33mSEC\033[0m seconds")
1173
1173
  ap2.add_argument("--srch-hits", metavar="N", type=int, default=7999, help="max search results to allow clients to fetch; 125 results will be shown initially")
@@ -1177,25 +1177,25 @@ def add_db_general(ap, hcores):
1177
1177
  def add_db_metadata(ap):
1178
1178
  ap2 = ap.add_argument_group('metadata db options')
1179
1179
  ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing; makes it possible to search for artist/title/codec/resolution/...")
1180
- ap2.add_argument("-e2ts", action="store_true", help="scan existing files on startup; sets -e2t")
1181
- ap2.add_argument("-e2tsr", action="store_true", help="delete all metadata from DB and do a full rescan; sets -e2ts")
1182
- ap2.add_argument("--no-mutagen", action="store_true", help="use FFprobe for tags instead; will catch more tags")
1180
+ ap2.add_argument("-e2ts", action="store_true", help="scan newly discovered files for metadata on startup; sets \033[33m-e2t\033[0m")
1181
+ ap2.add_argument("-e2tsr", action="store_true", help="delete all metadata from DB and do a full rescan; sets \033[33m-e2ts\033[0m")
1182
+ ap2.add_argument("--no-mutagen", action="store_true", help="use FFprobe for tags instead; will detect more tags")
1183
1183
  ap2.add_argument("--no-mtag-ff", action="store_true", help="never use FFprobe as tag reader; is probably safer")
1184
- ap2.add_argument("--mtag-to", metavar="SEC", type=int, default=60, help="timeout for ffprobe tag-scan")
1184
+ ap2.add_argument("--mtag-to", metavar="SEC", type=int, default=60, help="timeout for FFprobe tag-scan")
1185
1185
  ap2.add_argument("--mtag-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for tag scanning")
1186
1186
  ap2.add_argument("--mtag-v", action="store_true", help="verbose tag scanning; print errors from mtp subprocesses and such")
1187
- ap2.add_argument("--mtag-vv", action="store_true", help="debug mtp settings and mutagen/ffprobe parsers")
1187
+ ap2.add_argument("--mtag-vv", action="store_true", help="debug mtp settings and mutagen/FFprobe parsers")
1188
1188
  ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping")
1189
1189
  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)
1190
- ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.); assign/add/remove same as -mte", default=DEF_MTH)
1190
+ 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)
1191
1191
  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")
1192
1192
 
1193
1193
 
1194
1194
  def add_txt(ap):
1195
1195
  ap2 = ap.add_argument_group('textfile options')
1196
- ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="textfile editor checks for serverside changes every \033[33mSEC\033[0m seconds")
1196
+ 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")
1197
1197
  ap2.add_argument("-emp", action="store_true", help="enable markdown plugins -- neat but dangerous, big XSS risk")
1198
- ap2.add_argument("--exp", action="store_true", help="enable textfile expansion -- replace {{self.ip}} and such; see --help-exp (volflag=exp)")
1198
+ 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)")
1199
1199
  ap2.add_argument("--exp-md", metavar="V,V,V", type=u, default=DEF_EXP, help="comma/space-separated list of placeholders to expand in markdown files; add/remove stuff on the default list with +hdr_foo or /vf.scan (volflag=exp_md)")
1200
1200
  ap2.add_argument("--exp-lg", metavar="V,V,V", type=u, default=DEF_EXP, help="comma/space-separated list of placeholders to expand in prologue/epilogue files (volflag=exp_lg)")
1201
1201
 
@@ -1203,8 +1203,8 @@ def add_txt(ap):
1203
1203
  def add_ui(ap, retry):
1204
1204
  ap2 = ap.add_argument_group('ui options')
1205
1205
  ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)")
1206
- ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language; one of the following: eng nor")
1207
- ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use")
1206
+ ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language; one of the following: \033[32meng nor\033[0m")
1207
+ ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..7)")
1208
1208
  ap2.add_argument("--themes", metavar="NUM", type=int, default=8, help="number of themes installed")
1209
1209
  ap2.add_argument("--sort", metavar="C,C,C", type=u, default="href", help="default sort order, comma-separated column IDs (see header tooltips), prefix with '-' for descending. Examples: \033[32mhref -href ext sz ts tags/Album tags/.tn\033[0m (volflag=sort)")
1210
1210
  ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files matching \033[33mREGEX\033[0m in file list. Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
@@ -1218,8 +1218,8 @@ def add_ui(ap, retry):
1218
1218
  ap2.add_argument("--txt-max", metavar="KiB", type=int, default=64, help="max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS)")
1219
1219
  ap2.add_argument("--doctitle", metavar="TXT", type=u, default="copyparty @ --name", help="title / service-name to show in html documents")
1220
1220
  ap2.add_argument("--bname", metavar="TXT", type=u, default="--name", help="server name (displayed in filebrowser document title)")
1221
- ap2.add_argument("--pb-url", metavar="URL", type=u, default="https://github.com/9001/copyparty", help="powered-by link; disable with -np")
1222
- ap2.add_argument("--ver", action="store_true", help="show version on the control panel (incompatible with -nb)")
1221
+ ap2.add_argument("--pb-url", metavar="URL", type=u, default="https://github.com/9001/copyparty", help="powered-by link; disable with \033[33m-np\033[0m")
1222
+ ap2.add_argument("--ver", action="store_true", help="show version on the control panel (incompatible with \033[33m-nb\033[0m)")
1223
1223
  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 for README.md docs (volflag=md_sbf); see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox")
1224
1224
  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 for prologue/epilogue docs (volflag=lg_sbf)")
1225
1225
  ap2.add_argument("--no-sb-md", action="store_true", help="don't sandbox README.md documents (volflags: no_sb_md | sb_md)")
@@ -1230,17 +1230,17 @@ def add_debug(ap):
1230
1230
  ap2 = ap.add_argument_group('debug options')
1231
1231
  ap2.add_argument("--vc", action="store_true", help="verbose config file parser (explain config)")
1232
1232
  ap2.add_argument("--cgen", action="store_true", help="generate config file from current config (best-effort; probably buggy)")
1233
- ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile; instead using a traditional file read loop")
1234
- ap2.add_argument("--no-scandir", action="store_true", help="disable scandir; instead using listdir + stat on each file")
1235
- ap2.add_argument("--no-fastboot", action="store_true", help="wait for up2k indexing before starting the httpd")
1233
+ ap2.add_argument("--no-sendfile", action="store_true", help="kernel-bug workaround: disable sendfile; do a safe and slow read-send-loop instead")
1234
+ ap2.add_argument("--no-scandir", action="store_true", help="kernel-bug workaround: disable scandir; do a listdir + stat on each file instead")
1235
+ ap2.add_argument("--no-fastboot", action="store_true", help="wait for initial filesystem indexing before accepting client requests")
1236
1236
  ap2.add_argument("--no-htp", action="store_true", help="disable httpserver threadpool, create threads as-needed instead")
1237
1237
  ap2.add_argument("--srch-dbg", action="store_true", help="explain search processing, and do some extra expensive sanity checks")
1238
1238
  ap2.add_argument("--rclone-mdns", action="store_true", help="use mdns-domain instead of server-ip on /?hc")
1239
1239
  ap2.add_argument("--stackmon", metavar="P,S", type=u, help="write stacktrace to \033[33mP\033[0math every \033[33mS\033[0m second, for example --stackmon=\033[32m./st/%%Y-%%m/%%d/%%H%%M.xz,60")
1240
1240
  ap2.add_argument("--log-thrs", metavar="SEC", type=float, help="list active threads every \033[33mSEC\033[0m")
1241
1241
  ap2.add_argument("--log-fk", metavar="REGEX", type=u, default="", help="log filekey params for files where path matches \033[33mREGEX\033[0m; [\033[32m.\033[0m] (a single dot) = all files")
1242
- ap2.add_argument("--bak-flips", action="store_true", help="[up2k] if a client uploads a bitflipped/corrupted chunk, store a copy according to --bf-nc and --bf-dir")
1243
- ap2.add_argument("--bf-nc", metavar="NUM", type=int, default=200, help="bak-flips: stop if there's more than \033[33mNUM\033[0m files at --kf-dir already; default: 6.3 GiB max (200*32M)")
1242
+ ap2.add_argument("--bak-flips", action="store_true", help="[up2k] if a client uploads a bitflipped/corrupted chunk, store a copy according to \033[33m--bf-nc\033[0m and \033[33m--bf-dir\033[0m")
1243
+ ap2.add_argument("--bf-nc", metavar="NUM", type=int, default=200, help="bak-flips: stop if there's more than \033[33mNUM\033[0m files at \033[33m--kf-dir\033[0m already; default: 6.3 GiB max (200*32M)")
1244
1244
  ap2.add_argument("--bf-dir", metavar="PATH", type=u, default="bf", help="bak-flips: store corrupted chunks at \033[33mPATH\033[0m; default: folder named 'bf' wherever copyparty was started")
1245
1245
 
1246
1246
 
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 9, 25)
3
+ VERSION = (1, 9, 27)
4
4
  CODENAME = "prometheable"
5
- BUILD_DT = (2023, 12, 1)
5
+ BUILD_DT = (2023, 12, 8)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
copyparty/bos/bos.py CHANGED
@@ -40,6 +40,10 @@ def open(p , *a, **ka) :
40
40
  return os.open(fsenc(p), *a, **ka)
41
41
 
42
42
 
43
+ def readlink(p ) :
44
+ return fsdec(os.readlink(fsenc(p)))
45
+
46
+
43
47
  def rename(src , dst ) :
44
48
  return os.rename(fsenc(src), fsenc(dst))
45
49
 
copyparty/up2k.py CHANGED
@@ -2928,7 +2928,6 @@ class Up2k(object):
2928
2928
 
2929
2929
  self._symlink(dst, d2, self.flags[ptop], lmod=lmod)
2930
2930
  if cur:
2931
- self.db_rm(cur, rd, fn, job["size"])
2932
2931
  self.db_add(cur, vflags, rd, fn, lmod, *z2[3:])
2933
2932
 
2934
2933
  if cur:
@@ -2971,7 +2970,6 @@ class Up2k(object):
2971
2970
 
2972
2971
  self.db_act = self.vol_act[ptop] = time.time()
2973
2972
  try:
2974
- self.db_rm(cur, rd, fn, sz)
2975
2973
  self.db_add(
2976
2974
  cur,
2977
2975
  vflags,
@@ -3030,6 +3028,8 @@ class Up2k(object):
3030
3028
  at ,
3031
3029
  skip_xau = False,
3032
3030
  ) :
3031
+ self.db_rm(db, rd, fn, sz)
3032
+
3033
3033
  sql = "insert into up values (?,?,?,?,?,?,?)"
3034
3034
  v = (wark, int(ts), sz, rd, fn, ip or "", int(at or 0))
3035
3035
  try:
@@ -3191,7 +3191,13 @@ class Up2k(object):
3191
3191
  break
3192
3192
 
3193
3193
  abspath = djoin(adir, fn)
3194
- st = bos.stat(abspath)
3194
+ st = stl = bos.lstat(abspath)
3195
+ if stat.S_ISLNK(st.st_mode):
3196
+ try:
3197
+ st = bos.stat(abspath)
3198
+ except:
3199
+ pass
3200
+
3195
3201
  volpath = "{}/{}".format(vrem, fn).strip("/")
3196
3202
  vpath = "{}/{}".format(dbv.vpath, volpath).strip("/")
3197
3203
  self.log("rm {}\n {}".format(vpath, abspath))
@@ -3204,7 +3210,7 @@ class Up2k(object):
3204
3210
  vpath,
3205
3211
  "",
3206
3212
  uname,
3207
- st.st_mtime,
3213
+ stl.st_mtime,
3208
3214
  st.st_size,
3209
3215
  ip,
3210
3216
  0,
@@ -3234,7 +3240,7 @@ class Up2k(object):
3234
3240
  vpath,
3235
3241
  "",
3236
3242
  uname,
3237
- st.st_mtime,
3243
+ stl.st_mtime,
3238
3244
  st.st_size,
3239
3245
  ip,
3240
3246
  0,
@@ -3351,28 +3357,27 @@ class Up2k(object):
3351
3357
  if bos.path.exists(dabs):
3352
3358
  raise Pebkac(400, "mv2: target file exists")
3353
3359
 
3354
- stl = bos.lstat(sabs)
3355
- try:
3356
- st = bos.stat(sabs)
3357
- except:
3358
- st = stl
3360
+ is_link = is_dirlink = False
3361
+ st = stl = bos.lstat(sabs)
3362
+ if stat.S_ISLNK(stl.st_mode):
3363
+ is_link = True
3364
+ try:
3365
+ st = bos.stat(sabs)
3366
+ is_dirlink = stat.S_ISDIR(st.st_mode)
3367
+ except:
3368
+ pass # broken symlink; keep as-is
3359
3369
 
3360
3370
  xbr = svn.flags.get("xbr")
3361
3371
  xar = dvn.flags.get("xar")
3362
3372
  if xbr:
3363
3373
  if not runhook(
3364
- self.log, xbr, sabs, svp, "", uname, st.st_mtime, st.st_size, "", 0, ""
3374
+ self.log, xbr, sabs, svp, "", uname, stl.st_mtime, st.st_size, "", 0, ""
3365
3375
  ):
3366
3376
  t = "move blocked by xbr server config: {}".format(svp)
3367
3377
  self.log(t, 1)
3368
3378
  raise Pebkac(405, t)
3369
3379
 
3370
3380
  is_xvol = svn.realpath != dvn.realpath
3371
- if stat.S_ISLNK(stl.st_mode):
3372
- is_dirlink = stat.S_ISDIR(st.st_mode)
3373
- is_link = True
3374
- else:
3375
- is_link = is_dirlink = False
3376
3381
 
3377
3382
  bos.makedirs(os.path.dirname(dabs))
3378
3383
 
@@ -3399,7 +3404,7 @@ class Up2k(object):
3399
3404
  c2 = self.cur.get(dvn.realpath)
3400
3405
 
3401
3406
  if ftime_ is None:
3402
- ftime = st.st_mtime
3407
+ ftime = stl.st_mtime
3403
3408
  fsize = st.st_size
3404
3409
  else:
3405
3410
  ftime = ftime_
@@ -3441,7 +3446,16 @@ class Up2k(object):
3441
3446
  if is_xvol and has_dupes:
3442
3447
  raise OSError(errno.EXDEV, "src is symlink")
3443
3448
 
3444
- atomic_move(sabs, dabs)
3449
+ if is_link and st != stl:
3450
+ # relink non-broken symlinks to still work after the move,
3451
+ # but only resolve 1st level to maintain relativity
3452
+ dlink = bos.readlink(sabs)
3453
+ dlink = os.path.join(os.path.dirname(sabs), dlink)
3454
+ dlink = bos.path.abspath(dlink)
3455
+ self._symlink(dlink, dabs, dvn.flags, lmod=ftime)
3456
+ bos.unlink(sabs)
3457
+ else:
3458
+ atomic_move(sabs, dabs)
3445
3459
 
3446
3460
  except OSError as ex:
3447
3461
  if ex.errno != errno.EXDEV:
@@ -3608,6 +3622,8 @@ class Up2k(object):
3608
3622
  except:
3609
3623
  self.log("relink: not found: [{}]".format(ap))
3610
3624
 
3625
+ # self.log("full:\n" + "\n".join(" {:90}: {}".format(*x) for x in full.items()))
3626
+ # self.log("links:\n" + "\n".join(" {:90}: {}".format(*x) for x in links.items()))
3611
3627
  if not dabs and not full and links:
3612
3628
  # deleting final remaining full copy; swap it with a symlink
3613
3629
  slabs = list(sorted(links.keys()))[0]
@@ -3625,12 +3641,45 @@ class Up2k(object):
3625
3641
  dabs = list(sorted(full.keys()))[0]
3626
3642
 
3627
3643
  for alink, parts in links.items():
3628
- lmod = None
3644
+ lmod = 0.0
3629
3645
  try:
3630
- if alink != sabs and absreal(alink) != sabs:
3631
- continue
3646
+ faulty = False
3647
+ ldst = alink
3648
+ try:
3649
+ for n in range(40): # MAXSYMLINKS
3650
+ zs = bos.readlink(ldst)
3651
+ ldst = os.path.join(os.path.dirname(ldst), zs)
3652
+ ldst = bos.path.abspath(ldst)
3653
+ if not bos.path.islink(ldst):
3654
+ break
3655
+
3656
+ if ldst == sabs:
3657
+ t = "relink because level %d would break:"
3658
+ self.log(t % (n,), 6)
3659
+ faulty = True
3660
+ except Exception as ex:
3661
+ self.log("relink because walk failed: %s; %r" % (ex, ex), 3)
3662
+ faulty = True
3632
3663
 
3633
- self.log("relinking [{}] to [{}]".format(alink, dabs))
3664
+ zs = absreal(alink)
3665
+ if ldst != zs:
3666
+ t = "relink because computed != actual destination:\n %s\n %s"
3667
+ self.log(t % (ldst, zs), 3)
3668
+ ldst = zs
3669
+ faulty = True
3670
+
3671
+ if bos.path.islink(ldst):
3672
+ raise Exception("broken symlink: %s" % (alink,))
3673
+
3674
+ if alink != sabs and ldst != sabs and not faulty:
3675
+ continue # original symlink OK; leave it be
3676
+
3677
+ except Exception as ex:
3678
+ t = "relink because symlink verification failed: %s; %r"
3679
+ self.log(t % (ex, ex), 3)
3680
+
3681
+ self.log("relinking [%s] to [%s]" % (alink, dabs))
3682
+ try:
3634
3683
  lmod = bos.path.getmtime(alink, False)
3635
3684
  bos.unlink(alink)
3636
3685
  except:
copyparty/web/a/u2c.py CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env python3
2
2
  from __future__ import print_function, unicode_literals
3
3
 
4
- S_VERSION = "1.11"
5
- S_BUILD_DT = "2023-11-11"
4
+ S_VERSION = "1.12"
5
+ S_BUILD_DT = "2023-12-08"
6
6
 
7
7
  """
8
8
  u2c.py: upload to copyparty
@@ -907,12 +907,23 @@ class Ctl(object):
907
907
  dp = os.path.join(top, rd)
908
908
  lnodes = set(os.listdir(dp))
909
909
  bnames = [x for x in ls if x not in lnodes]
910
- if bnames:
911
- vpath = self.ar.url.split("://")[-1].split("/", 1)[-1]
912
- names = [x.decode("utf-8", "replace") for x in bnames]
913
- locs = [vpath + srd + "/" + x for x in names]
914
- print("DELETING ~{0}/#{1}".format(srd, len(names)))
915
- req_ses.post(self.ar.url + "?delete", json=locs)
910
+ vpath = self.ar.url.split("://")[-1].split("/", 1)[-1]
911
+ names = [x.decode("utf-8", "replace") for x in bnames]
912
+ locs = [vpath + srd + "/" + x for x in names]
913
+ while locs:
914
+ req = locs
915
+ while req:
916
+ print("DELETING ~%s/#%s" % (srd, len(req)))
917
+ r = req_ses.post(self.ar.url + "?delete", json=req)
918
+ if r.status_code == 413 and "json 2big" in r.text:
919
+ print(" (delete request too big; slicing...)")
920
+ req = req[: len(req) // 2]
921
+ continue
922
+ elif not r:
923
+ t = "delete request failed: %r %s"
924
+ raise Exception(t % (r, r.text))
925
+ break
926
+ locs = locs[len(req) :]
916
927
 
917
928
  if isdir:
918
929
  continue
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: copyparty
3
- Version: 1.9.25
3
+ Version: 1.9.27
4
4
  Summary: Portable file server with accelerated resumable uploads, deduplication, WebDAV, FTP, zeroconf, media indexer, video thumbnails, audio transcoding, and write-only folders
5
5
  Author-email: ed <copyparty@ocv.me>
6
6
  License: MIT
@@ -199,9 +199,10 @@ you may also want these, especially on servers:
199
199
 
200
200
  * [contrib/systemd/copyparty.service](contrib/systemd/copyparty.service) to run copyparty as a systemd service (see guide inside)
201
201
  * [contrib/systemd/prisonparty.service](contrib/systemd/prisonparty.service) to run it in a chroot (for extra security)
202
+ * [contrib/openrc/copyparty](contrib/openrc/copyparty) to run copyparty on Alpine / Gentoo
202
203
  * [contrib/rc/copyparty](contrib/rc/copyparty) to run copyparty on FreeBSD
203
- * [contrib/nginx/copyparty.conf](contrib/nginx/copyparty.conf) to [reverse-proxy](#reverse-proxy) behind nginx (for better https)
204
204
  * [nixos module](#nixos-module) to run copyparty on NixOS hosts
205
+ * [contrib/nginx/copyparty.conf](contrib/nginx/copyparty.conf) to [reverse-proxy](#reverse-proxy) behind nginx (for better https)
205
206
 
206
207
  and remember to open the ports you want; here's a complete example including every feature copyparty has to offer:
207
208
  ```
@@ -392,7 +393,7 @@ upgrade notes
392
393
  * yes, using [hooks](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/wget.py)
393
394
 
394
395
  * i want to learn python and/or programming and am considering looking at the copyparty source code in that occasion
395
- ```bash
396
+ * ```bash
396
397
  _| _ __ _ _|_
397
398
  (_| (_) | | (_) |_
398
399
  ```
@@ -874,6 +875,9 @@ using arguments or config files, or a mix of both:
874
875
  * or click the `[reload cfg]` button in the control-panel if the user has `a`/admin in any volume
875
876
  * changes to the `[global]` config section requires a restart to take effect
876
877
 
878
+ **NB:** as humongous as this readme is, there is also a lot of undocumented features. Run copyparty with `--help` to see all available global options; all of those can be used in the `[global]` section of config files, and everything listed in `--help-flags` can be used in volumes as volflags.
879
+ * if running in docker/podman, try this: `docker run --rm -it copyparty/ac --help`
880
+
877
881
 
878
882
  ## zeroconf
879
883
 
@@ -1416,15 +1420,19 @@ note: the following metrics are counted incorrectly if multiprocessing is enable
1416
1420
 
1417
1421
  the party might be closer than you think
1418
1422
 
1423
+ if your distro/OS is not mentioned below, there might be some hints in the [«on servers»](#on-servers) section
1424
+
1419
1425
 
1420
1426
  ## arch package
1421
1427
 
1422
1428
  now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
1423
1429
 
1430
+ 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/`
1431
+
1424
1432
 
1425
1433
  ## fedora package
1426
1434
 
1427
- now [available on copr-pypi](https://copr.fedorainfracloud.org/coprs/g/copr/PyPI/) , maintained autonomously -- [track record](https://copr.fedorainfracloud.org/coprs/g/copr/PyPI/package/python-copyparty/) seems OK
1435
+ now [available on copr-pypi](https://copr.fedorainfracloud.org/coprs/g/copr/PyPI/) , autogenerated from pypi by fedora -- [track record](https://copr.fedorainfracloud.org/coprs/g/copr/PyPI/package/python-copyparty/) seems OK
1428
1436
 
1429
1437
  ```bash
1430
1438
  dnf copr enable @copr/PyPI
@@ -1,6 +1,6 @@
1
1
  copyparty/__init__.py,sha256=34xcU8AoRRQscgVSx2gC6DeUyu7ZLmEVlXjttdQgXnI,1752
2
- copyparty/__main__.py,sha256=AORNbxB_DGg05sy5tmjlK7VpvdnLgfZdslj3uW9FJXs,88030
3
- copyparty/__version__.py,sha256=r3PIeFM74tptZtprqcB7l0jux2zE_pFHG9VtgZJaR1c,254
2
+ copyparty/__main__.py,sha256=hBzc1ISxFcZH3BpmdOsduCKBdmbPQAhvUUyuqmUvyOU,89329
3
+ copyparty/__version__.py,sha256=o3Ox4FVfBf649e-YKX-zqh1ZF5NFlIWL2G164kcrwp0,254
4
4
  copyparty/authsrv.py,sha256=PrMej_hmFWkV8t4Txr2tc8GY7JD_vb7L3alWTuGe8hU,71292
5
5
  copyparty/broker_mp.py,sha256=4mEZC5tiHUazJMgYuwInNo2dxS7jrbzrGb1qs2UBt9k,3948
6
6
  copyparty/broker_mpw.py,sha256=GlSn4PRd_OqqeG39FiXgNvPzXVQW6UCiAcqmBSr2q6g,3200
@@ -30,10 +30,10 @@ copyparty/tcpsrv.py,sha256=vz94zy-eUg5-U1lskN3VAQiTXVr80PPnwJfwzybVcW4,17169
30
30
  copyparty/th_cli.py,sha256=MSp2kpoAPiX1bndMthv6JK2gt3K6CjrloWuJsI_CL94,3869
31
31
  copyparty/th_srv.py,sha256=ClG82DOri2Z8iJeMn1q886uMfxF8ZdJenDWIsiL5gYM,23288
32
32
  copyparty/u2idx.py,sha256=HMEnpmbH-TXDrci4wsKNFvtEW4TxPtVl9hO5MS_EFVs,12799
33
- copyparty/up2k.py,sha256=2GrnM4XwBFg6-HYcrkPM5O4Xp7FgCsgbaL4co79B_B8,130576
33
+ copyparty/up2k.py,sha256=c9R_fb18xwtLKpP9swicATKp-DmJGCuRYR07aoUAKto,132716
34
34
  copyparty/util.py,sha256=vfCbQv8QUIEQbu65BIuCkuGkqSoUhnCI6xPOFQevKxQ,75998
35
35
  copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
- copyparty/bos/bos.py,sha256=Md2RwMauEdOS7SfK5aiHby8T1KgQoTaoEjVCYU98_4I,1560
36
+ copyparty/bos/bos.py,sha256=Wb7eWsXJgR5AFlBR9ZOyKrLTwy-Kct9RrGiOu4Jo37Y,1622
37
37
  copyparty/bos/path.py,sha256=yEjCq2ki9CvxA5sCT8pS0keEXwugs0ZeUyUhdBziOCI,777
38
38
  copyparty/res/COPYING.txt,sha256=087-rNDMk0NijtO0F7QZ_y1qiy7Za-rsLbImihEgQ-M,10465
39
39
  copyparty/res/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -81,7 +81,7 @@ copyparty/web/util.js.gz,sha256=hGmBv9Ibt4PcRKea3HkIG2G0NIFsn_UJf7bKasymR90,1386
81
81
  copyparty/web/w.hash.js.gz,sha256=P9469QknH8-1aKwI_1n1_S4yKvIGOu7bGoty9N3zYMI,1060
82
82
  copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
83
  copyparty/web/a/partyfuse.py,sha256=MuRkaSuYsdfWfBFMOkbPwDXqSvNTw3sd7QhhlKCDZ8I,32311
84
- copyparty/web/a/u2c.py,sha256=Usk_2t4UMIZMt6YgpdoHAclIrqmxy53lqtv-6od-mZs,37201
84
+ copyparty/web/a/u2c.py,sha256=3dRtYfNh_kDTHQvHACZ5VotJsEY9SPclYyfMC6hDsdo,37819
85
85
  copyparty/web/a/webdav-cfg.bat,sha256=Y4NoGZlksAIg4cBMb7KdJrpKC6Nx97onaTl6yMjaimk,1449
86
86
  copyparty/web/dd/2.png,sha256=gJ14XFPzaw95L6z92fSq9eMPikSQyu-03P1lgiGe0_I,258
87
87
  copyparty/web/dd/3.png,sha256=4lho8Koz5tV7jJ4ODo6GMTScZfkqsT05yp48EDFIlyg,252
@@ -98,9 +98,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
98
98
  copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
99
99
  copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
100
100
  copyparty/web/deps/sha512.hw.js.gz,sha256=km3_b5IoaVwg02Ex6PxnhDLZxKiOMP2cHW35j9CSWFA,8107
101
- copyparty-1.9.25.dist-info/LICENSE,sha256=yyzj1id78vWoLs8zbMRJY7xkkLz0lv-9dfyeIauqdfM,1059
102
- copyparty-1.9.25.dist-info/METADATA,sha256=7444qzJcD2oDm9fdtvAqpkXnBNCnVhtLoAJ0rZVY03Y,108177
103
- copyparty-1.9.25.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
104
- copyparty-1.9.25.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
105
- copyparty-1.9.25.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
106
- copyparty-1.9.25.dist-info/RECORD,,
101
+ copyparty-1.9.27.dist-info/LICENSE,sha256=yyzj1id78vWoLs8zbMRJY7xkkLz0lv-9dfyeIauqdfM,1059
102
+ copyparty-1.9.27.dist-info/METADATA,sha256=v7eaPBfBOdji7cRJms62zqEQyPuZsk-AhRAbyGdNz-Y,108940
103
+ copyparty-1.9.27.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
104
+ copyparty-1.9.27.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
105
+ copyparty-1.9.27.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
106
+ copyparty-1.9.27.dist-info/RECORD,,