copyparty 1.19.4__py3-none-any.whl → 1.19.6__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/svchub.py CHANGED
@@ -128,6 +128,7 @@ class SvcHub(object):
128
128
  self.nsigs = 3
129
129
  self.retcode = 0
130
130
  self.httpsrv_up = 0
131
+ self.qr_tsz = None
131
132
 
132
133
  self.log_mutex = threading.Lock()
133
134
  self.cday = 0
@@ -319,7 +320,7 @@ class SvcHub(object):
319
320
 
320
321
  self._feature_test()
321
322
 
322
- decs = {k: 1 for k in self.args.th_dec.split(",")}
323
+ decs = {k.strip(): 1 for k in self.args.th_dec.split(",")}
323
324
  if not HAVE_VIPS:
324
325
  decs.pop("vips", None)
325
326
  if not HAVE_PIL:
@@ -427,7 +428,7 @@ class SvcHub(object):
427
428
 
428
429
  # create netmaps early to avoid firewall gaps,
429
430
  # but the mutex blocks multiprocessing startup
430
- for zs in "ipu_iu ftp_ipa_nm tftp_ipa_nm".split():
431
+ for zs in "ipu_nm ftp_ipa_nm tftp_ipa_nm".split():
431
432
  try:
432
433
  getattr(args, zs).mutex = threading.Lock()
433
434
  except:
@@ -777,7 +778,27 @@ class SvcHub(object):
777
778
  self.signal_handler(signal.SIGTERM, None)
778
779
 
779
780
  def sticky_qr(self) :
780
- tw, th = termsize()
781
+ self._sticky_qr()
782
+
783
+ def _unsticky_qr(self, flush=True) :
784
+ print("\033[s\033[J\033[r\033[u", file=sys.stderr, end="")
785
+ if flush:
786
+ sys.stderr.flush()
787
+
788
+ def _sticky_qr(self, force = False) :
789
+ sz = termsize()
790
+ if self.qr_tsz == sz:
791
+ if not force:
792
+ return
793
+ else:
794
+ force = False
795
+
796
+ if self.qr_tsz:
797
+ self._unsticky_qr(False)
798
+ else:
799
+ atexit.register(self._unsticky_qr)
800
+
801
+ tw, th = self.qr_tsz = sz
781
802
  zs1, qr = self.tcpsrv.qr.split("\n", 1)
782
803
  url, colr = zs1.split(" ", 1)
783
804
  nl = len(qr.split("\n")) # numlines
@@ -801,13 +822,34 @@ class SvcHub(object):
801
822
  url = "%s\033[%d;%dH%s\033[0m" % (colr, sh + 1, (nl + lp) * 2, url)
802
823
  qr = colr + qr
803
824
 
804
- def unlock():
805
- print("\033[s\033[r\033[u", file=sys.stderr)
806
-
807
- atexit.register(unlock)
808
825
  t = "%s\033[%dA" % ("\n" * nl, nl)
809
826
  t = "%s\033[s\033[1;%dr\033[%dH%s%s\033[u" % (t, sh - 1, sh, qr, url)
810
- self.pr(t, file=sys.stderr)
827
+ if not force:
828
+ self.log("qr", "sticky-qrcode %sx%s,%s" % (tw, th, sh), 6)
829
+ self.pr(t, file=sys.stderr, end="")
830
+
831
+ def _qr_thr(self):
832
+ qr = self.tcpsrv.qr
833
+ w8 = self.args.qr_wait
834
+ if w8:
835
+ time.sleep(w8)
836
+ self.log("qr-code", qr)
837
+ w8 = self.args.qr_every
838
+ msg = "%s\033[%dA" % (qr, len(qr.split("\n")))
839
+ while w8:
840
+ time.sleep(w8)
841
+ if self.stopping:
842
+ break
843
+ if self.args.qr_pin:
844
+ self._sticky_qr(True)
845
+ else:
846
+ self.log("qr-code", msg)
847
+ w8 = self.args.qr_winch
848
+ while w8:
849
+ time.sleep(w8)
850
+ if self.stopping:
851
+ break
852
+ self._sticky_qr()
811
853
 
812
854
  def cb_httpsrv_up(self) :
813
855
  self.httpsrv_up += 1
@@ -823,7 +865,9 @@ class SvcHub(object):
823
865
  if self.tcpsrv.qr:
824
866
  if self.args.qr_pin:
825
867
  self.sticky_qr()
826
- else:
868
+ if self.args.qr_wait or self.args.qr_every or self.args.qr_winch:
869
+ Daemon(self._qr_thr, "qr")
870
+ elif not self.args.qr_pin:
827
871
  self.log("qr-code", self.tcpsrv.qr)
828
872
  else:
829
873
  self.log("root", "workers OK\n")
@@ -1078,7 +1122,7 @@ class SvcHub(object):
1078
1122
  al.tcolor = "".join([x * 2 for x in al.tcolor])
1079
1123
 
1080
1124
  zs = al.u2sz
1081
- zsl = zs.split(",")
1125
+ zsl = [x.strip() for x in zs.split(",")]
1082
1126
  if len(zsl) not in (1, 3):
1083
1127
  t = "invalid --u2sz; must be either one number, or a comma-separated list of three numbers (min,default,max)"
1084
1128
  raise Exception(t)
copyparty/tcpsrv.py CHANGED
@@ -9,13 +9,14 @@ import time
9
9
 
10
10
  from .__init__ import ANYWIN, PY2, TYPE_CHECKING, unicode
11
11
  from .cert import gencert
12
- from .stolen.qrcodegen import QrCode
12
+ from .stolen.qrcodegen import QrCode, qr2svg
13
13
  from .util import (
14
14
  E_ACCESS,
15
15
  E_ADDR_IN_USE,
16
16
  E_ADDR_NOT_AVAIL,
17
17
  E_UNREACH,
18
18
  HAVE_IPV6,
19
+ IP6_LL,
19
20
  IP6ALL,
20
21
  VF_CAREFUL,
21
22
  Netdev,
@@ -137,12 +138,12 @@ class TcpSrv(object):
137
138
  # keep IPv6 LL-only nics
138
139
  ll_ok = set()
139
140
  for ip, nd in self.netdevs.items():
140
- if not ip.startswith("fe80"):
141
+ if not ip.startswith(IP6_LL):
141
142
  continue
142
143
 
143
144
  just_ll = True
144
145
  for ip2, nd2 in self.netdevs.items():
145
- if nd == nd2 and ":" in ip2 and not ip2.startswith("fe80"):
146
+ if nd == nd2 and ":" in ip2 and not ip2.startswith(IP6_LL):
146
147
  just_ll = False
147
148
 
148
149
  if just_ll or self.args.ll:
@@ -161,7 +162,7 @@ class TcpSrv(object):
161
162
  title_vars = [x[1:] for x in self.args.wintitle.split(" ") if x.startswith("$")]
162
163
  t = "available @ {}://{}:{}/ (\033[33m{}\033[0m)"
163
164
  for ip, desc in sorted(eps.items(), key=lambda x: x[1]):
164
- if ip.startswith("fe80") and ip not in ll_ok:
165
+ if ip.startswith(IP6_LL) and ip not in ll_ok:
165
166
  continue
166
167
 
167
168
  for port in sorted(self.args.p):
@@ -618,6 +619,10 @@ class TcpSrv(object):
618
619
  pad = self.args.qrp
619
620
  zoom = self.args.qrz
620
621
  qrc = QrCode.encode_binary(btxt)
622
+
623
+ for zs in self.args.qr_file or []:
624
+ self._qr2file(qrc, zs)
625
+
621
626
  if zoom == 0:
622
627
  try:
623
628
  tw, th = termsize()
@@ -633,6 +638,8 @@ class TcpSrv(object):
633
638
  halfc = "\033[40;48;5;{0}m{1}\033[47;48;5;{2}m"
634
639
  if not fg:
635
640
  halfc = "\033[0;40m{1}\033[0;47m"
641
+ if nocolor:
642
+ halfc = "\033[0;7m{1}\033[0m"
636
643
 
637
644
  def ansify(m ) :
638
645
  return halfc.format(fg, " " * len(m.group(1)), bg)
@@ -651,3 +658,29 @@ class TcpSrv(object):
651
658
  t = t.replace("\n", "`\n`")
652
659
 
653
660
  return txt + t
661
+
662
+ def _qr2file(self, qrc , txt ):
663
+ if ".txt:" in txt or ".svg:" in txt:
664
+ ap, zs1, zs2 = txt.rsplit(":", 2)
665
+ bg = fg = ""
666
+ else:
667
+ ap, zs1, zs2, bg, fg = txt.rsplit(":", 4)
668
+ zoom = int(zs1)
669
+ pad = int(zs2)
670
+
671
+ if ap.endswith(".txt"):
672
+ if zoom not in (1, 2):
673
+ raise Exception("invalid zoom for qr.txt; must be 1 or 2")
674
+ with open(ap, "wb") as f:
675
+ f.write(qrc.render(zoom, pad).encode("utf-8"))
676
+ elif ap.endswith(".svg"):
677
+ with open(ap, "wb") as f:
678
+ f.write(qr2svg(qrc, pad).encode("utf-8"))
679
+ else:
680
+ qrc.to_png(zoom, pad, self._h2i(bg), self._h2i(fg), ap)
681
+
682
+ def _h2i(self, hs):
683
+ try:
684
+ return tuple(int(hs[i : i + 2], 16) for i in (0, 2, 4))
685
+ except:
686
+ return None
copyparty/up2k.py CHANGED
@@ -88,6 +88,9 @@ ICV_EXTS = set(zsg.split(","))
88
88
  zsg = "3gp,asf,av1,avc,avi,flv,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,vob,webm,wmv"
89
89
  VCV_EXTS = set(zsg.split(","))
90
90
 
91
+ zsg = "aif,aiff,alac,ape,flac,m4a,mp3,oga,ogg,opus,tak,tta,wav,wma,wv"
92
+ ACV_EXTS = set(zsg.split(","))
93
+
91
94
  zsg = "nohash noidx xdev xvol"
92
95
  VF_AFFECTS_INDEXING = set(zsg.split(" "))
93
96
 
@@ -918,6 +921,12 @@ class Up2k(object):
918
921
  with self.mutex, self.reg_mutex:
919
922
  # only need to protect register_vpath but all in one go feels right
920
923
  for vol in vols:
924
+ if bos.path.isfile(vol.realpath):
925
+ self.volstate[vol.vpath] = "online (just-a-file)"
926
+ t = "NOTE: volume [/%s] is a file, not a folder"
927
+ self.log(t % (vol.vpath,))
928
+ continue
929
+
921
930
  try:
922
931
  # mkdir gonna happen at snap anyways;
923
932
  bos.makedirs(vol.realpath, vf=vol.flags)
@@ -1475,7 +1484,7 @@ class Up2k(object):
1475
1484
  unreg = []
1476
1485
  files = []
1477
1486
  fat32 = True
1478
- cv = vcv = ""
1487
+ cv = vcv = acv = ""
1479
1488
 
1480
1489
  th_cvd = self.args.th_coversd
1481
1490
  th_cvds = self.args.th_coversd_set
@@ -1584,9 +1593,11 @@ class Up2k(object):
1584
1593
  cv = iname
1585
1594
  elif not vcv and ext in VCV_EXTS and not iname.startswith("."):
1586
1595
  vcv = iname
1596
+ elif not acv and ext in ACV_EXTS and not iname.startswith("."):
1597
+ acv = iname
1587
1598
 
1588
1599
  if not cv:
1589
- cv = vcv
1600
+ cv = vcv or acv
1590
1601
 
1591
1602
  if not self.args.no_dirsz:
1592
1603
  tnf += len(files)
copyparty/util.py CHANGED
@@ -52,6 +52,7 @@ from .__init__ import (
52
52
  VT100,
53
53
  WINDOWS,
54
54
  EnvParams,
55
+ unicode,
55
56
  )
56
57
  from .__version__ import S_BUILD_DT, S_VERSION
57
58
  from .stolen import surrogateescape
@@ -112,7 +113,13 @@ E_ACCESS = _ens("EACCES WSAEACCES")
112
113
  E_UNREACH = _ens("EHOSTUNREACH WSAEHOSTUNREACH ENETUNREACH WSAENETUNREACH")
113
114
 
114
115
  IP6ALL = "0:0:0:0:0:0:0:0"
116
+ IP6_LL = ("fe8", "fe9", "fea", "feb")
117
+ IP64_LL = ("fe8", "fe9", "fea", "feb", "169.254")
115
118
 
119
+ UC_CDISP = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._"
120
+ BC_CDISP = UC_CDISP.encode("ascii")
121
+ UC_CDISP_SET = set(UC_CDISP)
122
+ BC_CDISP_SET = set(BC_CDISP)
116
123
 
117
124
  try:
118
125
  import fcntl
@@ -1985,6 +1992,29 @@ def gencookie(
1985
1992
  )
1986
1993
 
1987
1994
 
1995
+ def gen_content_disposition(fn ) :
1996
+ safe = UC_CDISP_SET
1997
+ bsafe = BC_CDISP_SET
1998
+ fn = fn.replace("/", "_").replace("\\", "_")
1999
+ zb = fn.encode("utf-8", "xmlcharrefreplace")
2000
+ if not PY2:
2001
+ zbl = [
2002
+ chr(x).encode("utf-8")
2003
+ if x in bsafe
2004
+ else "%{:02X}".format(x).encode("ascii")
2005
+ for x in zb
2006
+ ]
2007
+ else:
2008
+ zbl = [unicode(x) if x in bsafe else "%{:02X}".format(ord(x)) for x in zb]
2009
+
2010
+ ufn = b"".join(zbl).decode("ascii")
2011
+ afn = "".join([x if x in safe else "_" for x in fn]).lstrip(".")
2012
+ while ".." in afn:
2013
+ afn = afn.replace("..", ".")
2014
+
2015
+ return "attachment; filename=\"%s\"; filename*=UTF-8''%s" % (afn, ufn)
2016
+
2017
+
1988
2018
  def humansize(sz , terse = False) :
1989
2019
  for unit in HUMANSIZE_UNITS:
1990
2020
  if sz < 1024:
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 = "2.11"
5
- S_BUILD_DT = "2025-05-18"
4
+ S_VERSION = "2.12"
5
+ S_BUILD_DT = "2025-08-26"
6
6
 
7
7
  """
8
8
  u2c.py: upload to copyparty
@@ -10,7 +10,7 @@ u2c.py: upload to copyparty
10
10
  https://github.com/9001/copyparty/blob/hovudstraum/bin/u2c.py
11
11
 
12
12
  - dependencies: no
13
- - supports python 2.6, 2.7, and 3.3 through 3.12
13
+ - supports python 2.6, 2.7, and 3.3 through 3.14
14
14
  - if something breaks just try again and it'll autoresume
15
15
  """
16
16
 
@@ -675,7 +675,7 @@ def walkdirs(err, tops, excl):
675
675
  yield stop, ap[len(stop) :].lstrip(sep), inf
676
676
  else:
677
677
  d, n = top.rsplit(sep, 1)
678
- yield d, n, os.stat(top)
678
+ yield d or b"/", n, os.stat(top)
679
679
 
680
680
 
681
681
  # mostly from copyparty/util.py
@@ -1525,10 +1525,10 @@ def main():
1525
1525
 
1526
1526
  # fmt: off
1527
1527
  ap = app = argparse.ArgumentParser(formatter_class=APF, description="copyparty up2k uploader / filesearch tool " + ver, epilog="""
1528
- NOTE:
1529
- source file/folder selection uses rsync syntax, meaning that:
1528
+ NOTE: source file/folder selection uses rsync syntax, meaning that:
1530
1529
  "foo" uploads the entire folder to URL/foo/
1531
1530
  "foo/" uploads the CONTENTS of the folder into URL/
1531
+ NOTE: if server has --usernames enabled, then password is "username:password"
1532
1532
  """)
1533
1533
 
1534
1534
  ap.add_argument("url", type=unicode, help="server url, including destination folder")
Binary file
Binary file
copyparty/web/md.html CHANGED
@@ -130,7 +130,8 @@ write markdown (most html is 🙆 too)
130
130
 
131
131
  var SR = "{{ r }}",
132
132
  last_modified = {{ lastmod }},
133
- have_emp = {{ "true" if have_emp else "false" }},
133
+ have_emp = {{ have_emp }},
134
+ md_no_br = {{ md_no_br }},
134
135
  dfavico = "{{ favico }}";
135
136
 
136
137
  var md_opt = {
copyparty/web/md.js.gz CHANGED
Binary file
copyparty/web/md2.js.gz CHANGED
Binary file
copyparty/web/mde.html CHANGED
@@ -28,7 +28,8 @@
28
28
 
29
29
  var SR = "{{ r }}",
30
30
  last_modified = {{ lastmod }},
31
- have_emp = {{ "true" if have_emp else "false" }},
31
+ have_emp = {{ have_emp }},
32
+ md_no_br = {{ md_no_br }},
32
33
  dfavico = "{{ favico }}";
33
34
 
34
35
  var md_opt = {
Binary file
copyparty/web/ui.css.gz CHANGED
Binary file
copyparty/web/up2k.js.gz CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copyparty
3
- Version: 1.19.4
3
+ Version: 1.19.6
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
@@ -177,6 +177,7 @@ made in Norway 🇳🇴
177
177
  * [packages](#packages) - the party might be closer than you think
178
178
  * [arch package](#arch-package) - `pacman -S copyparty` (in [arch linux extra](https://archlinux.org/packages/extra/any/copyparty/))
179
179
  * [fedora package](#fedora-package) - does not exist yet
180
+ * [homebrew formulae](#homebrew-formulae) - `brew install copyparty ffmpeg`
180
181
  * [nix package](#nix-package) - `nix profile install github:9001/copyparty`
181
182
  * [nixos module](#nixos-module)
182
183
  * [browser support](#browser-support) - TLDR: yes
@@ -206,6 +207,7 @@ made in Norway 🇳🇴
206
207
  * [copyparty.exe](#copypartyexe) - download [copyparty.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty.exe) (win8+) or [copyparty32.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty32.exe) (win7+)
207
208
  * [zipapp](#zipapp) - another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz)
208
209
  * [install on android](#install-on-android)
210
+ * [install on iOS](#install-on-iOS)
209
211
  * [reporting bugs](#reporting-bugs) - ideas for context to include, and where to submit them
210
212
  * [devnotes](#devnotes) - for build instructions etc, see [./docs/devnotes.md](./docs/devnotes.md)
211
213
 
@@ -220,6 +222,7 @@ just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/
220
222
  * or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
221
223
  * or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
222
224
  * or if you are on android, [install copyparty in termux](#install-on-android)
225
+ * or maybe an iPhone or iPad? [install in a-Shell on iOS](#install-on-iOS)
223
226
  * or maybe you have a [synology nas / dsm](./docs/synology-dsm.md)
224
227
  * or if you have [uv](https://docs.astral.sh/uv/) installed, run `uv tool run copyparty`
225
228
  * or if your computer is messed up and nothing else works, [try the pyz](#zipapp)
@@ -306,7 +309,7 @@ also see [comparison to similar software](./docs/versus.md)
306
309
  * ☑ [upnp / zeroconf / mdns / ssdp](#zeroconf)
307
310
  * ☑ [event hooks](#event-hooks) / script runner
308
311
  * ☑ [reverse-proxy support](https://github.com/9001/copyparty#reverse-proxy)
309
- * ☑ cross-platform (Windows, Linux, Macos, Android, FreeBSD, arm32/arm64, ppc64le, s390x, risc-v/riscv64)
312
+ * ☑ cross-platform (Windows, Linux, Macos, Android, iOS, FreeBSD, arm32/arm64, ppc64le, s390x, risc-v/riscv64)
310
313
  * upload
311
314
  * ☑ basic: plain multipart, ie6 support
312
315
  * ☑ [up2k](#uploading): js, resumable, multithreaded
@@ -329,7 +332,7 @@ also see [comparison to similar software](./docs/versus.md)
329
332
  * ☑ play video files as audio (converted on server)
330
333
  * ☑ create and play [m3u8 playlists](#playlists)
331
334
  * ☑ image gallery with webm player
332
- * ☑ [textfile browser](#textfile-viewer) with syntax hilighting
335
+ * ☑ [textfile browser](#textfile-viewer) with syntax highlighting
333
336
  * ☑ realtime streaming of growing files (logfiles and such)
334
337
  * ☑ [thumbnails](#thumbnails)
335
338
  * ☑ ...of images using Pillow, pyvips, or FFmpeg
@@ -634,6 +637,8 @@ for example `-v /mnt::r -v /var/empty:web/certs:r` mounts the server folder `/mn
634
637
 
635
638
  the example config file right above this section may explain this better; the first volume `/` is mapped to `/srv` which means http://127.0.0.1:3923/music would try to read `/srv/music` on the server filesystem, but since there's another volume at `/music` mapped to `/mnt/music` then it'll go to `/mnt/music` instead
636
639
 
640
+ > ℹ️ this also works for single files, because files can also be volumes
641
+
637
642
 
638
643
  ## dotfiles
639
644
 
@@ -896,7 +901,7 @@ the up2k UI is the epitome of polished intuitive experiences:
896
901
  * `[🔎]` switch between upload and [file-search](#file-search) mode
897
902
  * ignore `[🔎]` if you add files by dragging them into the browser
898
903
 
899
- and then theres the tabs below it,
904
+ and then there's the tabs below it,
900
905
  * `[ok]` is the files which completed successfully
901
906
  * `[ng]` is the ones that failed / got rejected (already exists, ...)
902
907
  * `[done]` shows a combined list of `[ok]` and `[ng]`, chronological order
@@ -1128,7 +1133,7 @@ plays almost every audio format there is (if the server has FFmpeg installed fo
1128
1133
 
1129
1134
  the following audio formats are usually always playable, even without FFmpeg: `aac|flac|m4a|mp3|ogg|opus|wav`
1130
1135
 
1131
- some hilights:
1136
+ some highlights:
1132
1137
  * OS integration; control playback from your phone's lockscreen ([windows](https://user-images.githubusercontent.com/241032/233213022-298a98ba-721a-4cf1-a3d4-f62634bc53d5.png) // [iOS](https://user-images.githubusercontent.com/241032/142711926-0700be6c-3e31-47b3-9928-53722221f722.png) // [android](https://user-images.githubusercontent.com/241032/233212311-a7368590-08c7-4f9f-a1af-48ccf3f36fad.png))
1133
1138
  * shows the audio waveform in the seekbar
1134
1139
  * not perfectly gapless but can get really close (see settings + eq below); good enough to enjoy gapless albums as intended
@@ -1359,6 +1364,12 @@ print a qr-code [(screenshot)](https://user-images.githubusercontent.com/241032/
1359
1364
  * `--qrl lootbox/?pw=hunter2` appends to the url, linking to the `lootbox` folder with password `hunter2`
1360
1365
  * `--qrz 1` forces 1x zoom instead of autoscaling to fit the terminal size
1361
1366
  * 1x may render incorrectly on some terminals/fonts, but 2x should always work
1367
+ * `--qr-pin 1` makes the qr-code stick to the bottom of the console (never scrolls away)
1368
+ * `--qr-file qr.txt:1:2` writes a small qr-code to `qr.txt`
1369
+ * `--qr-file qr.txt:2:2` writes a big qr-code to `qr.txt`
1370
+ * `--qr-file qr.svg:1:2` writes a vector-graphics qr-code to `qr.svg`
1371
+ * `--qr-file qr.png:8:4:333333:ffcc55` writes an 8x-magnified yellow-on-gray `qr.png`
1372
+ * `--qr-file qr.png:8:4::ffffff` writes an 8x-magnified white-on-transparent `qr.png`
1362
1373
 
1363
1374
  it uses the server hostname if [mdns](#mdns) is enabled, otherwise it'll use your external ip (default route) unless `--qri` specifies a specific ip-prefix or domain
1364
1375
 
@@ -1383,6 +1394,14 @@ some recommended FTP / FTPS clients; `wark` = example password:
1383
1394
  * `lftp -u k,wark -p 3921 127.0.0.1 -e ls`
1384
1395
  * `lftp -u k,wark -p 3990 127.0.0.1 -e 'set ssl:verify-certificate no; ls'`
1385
1396
 
1397
+ config file example, which restricts FTP to only use ports 3921 and 12000-12099 so all of those ports must be opened in your firewall:
1398
+
1399
+ ```yaml
1400
+ [global]
1401
+ ftp: 3921
1402
+ ftp-pr: 12000-12099
1403
+ ```
1404
+
1386
1405
 
1387
1406
  ## webdav server
1388
1407
 
@@ -1477,6 +1496,7 @@ and some minor issues,
1477
1496
  * win10 onwards does not allow connecting anonymously / without accounts
1478
1497
  * python3 only
1479
1498
  * slow (the builtin webdav support in windows is 5x faster, and rclone-webdav is 30x faster)
1499
+ * those numbers are specifically for copyparty's smb-server (because it sucks); other smb-servers should be similar to webdav
1480
1500
 
1481
1501
  known client bugs:
1482
1502
  * on win7 only, `--smb1` is much faster than smb2 (default) because it keeps rescanning folders on smb2
@@ -2225,7 +2245,7 @@ when connecting the reverse-proxy to `127.0.0.1` instead (the basic and/or old-f
2225
2245
 
2226
2246
  in summary, `haproxy > caddy > traefik > nginx > apache > lighttpd`, and use uds when possible (traefik does not support it yet)
2227
2247
 
2228
- * if these results are bullshit because my config exampels are bad, please submit corrections!
2248
+ * if these results are bullshit because my config examples are bad, please submit corrections!
2229
2249
 
2230
2250
 
2231
2251
  ## permanent cloudflare tunnel
@@ -2396,6 +2416,15 @@ after installing, start either the system service or the user service and naviga
2396
2416
  does not exist yet; there are rumours that it is being packaged! keep an eye on this space...
2397
2417
 
2398
2418
 
2419
+ ## homebrew formulae
2420
+
2421
+ `brew install copyparty ffmpeg` -- https://formulae.brew.sh/formula/copyparty
2422
+
2423
+ should work on all macs (both intel and apple silicon) and all relevant macos versions
2424
+
2425
+ the homebrew package is maintained by the homebrew team (thanks!)
2426
+
2427
+
2399
2428
  ## nix package
2400
2429
 
2401
2430
  `nix profile install github:9001/copyparty`
@@ -2409,7 +2438,7 @@ some recommended dependencies are enabled by default; [override the package](htt
2409
2438
 
2410
2439
  ## nixos module
2411
2440
 
2412
- for this setup, you will need a [flake-enabled](https://nixos.wiki/wiki/Flakes) installation of NixOS.
2441
+ for [flake-enabled](https://nixos.wiki/wiki/Flakes) installations of NixOS:
2413
2442
 
2414
2443
  ```nix
2415
2444
  {
@@ -2436,6 +2465,33 @@ for this setup, you will need a [flake-enabled](https://nixos.wiki/wiki/Flakes)
2436
2465
  }
2437
2466
  ```
2438
2467
 
2468
+ if you don't use a flake in your configuration, you can use other dependency management tools like [npins](https://github.com/andir/npins), [niv](https://github.com/nmattia/niv), or even plain [`fetchTarball`](https://nix.dev/manual/nix/stable/language/builtins#builtins-fetchTarball), like so:
2469
+
2470
+ ```nix
2471
+ { pkgs, ... }:
2472
+
2473
+ let
2474
+ # npins example, adjust for your setup. copyparty should be a path to the downloaded repo
2475
+ # for niv, just replace the npins folder import with the sources.nix file
2476
+ copyparty = (import ./npins).copyparty;
2477
+
2478
+ # or with fetchTarball:
2479
+ copyparty = fetchTarball "https://github.com/9001/copyparty/archive/hovudstraum.tar.gz";
2480
+ in
2481
+
2482
+ {
2483
+ # load the copyparty NixOS module
2484
+ imports = [ "${copyparty}/contrib/nixos/modules/copyparty.nix" ];
2485
+
2486
+ # add the copyparty overlay to expose the package to the module
2487
+ nixpkgs.overlays = [ (import "${copyparty}/contrib/package/nix/overlay.nix") ];
2488
+ # (optional) install the package globally
2489
+ environment.systemPackages = [ pkgs.copyparty ];
2490
+ # configure the copyparty module
2491
+ services.copyparty.enable = true;
2492
+ }
2493
+ ```
2494
+
2439
2495
  copyparty on NixOS is configured via `services.copyparty` options, for example:
2440
2496
  ```nix
2441
2497
  services.copyparty = {
@@ -2622,11 +2678,20 @@ sync folders to/from copyparty
2622
2678
 
2623
2679
  NOTE: full bidirectional sync, like what [nextcloud](https://docs.nextcloud.com/server/latest/user_manual/sv/files/desktop_mobile_sync.html) and [syncthing](https://syncthing.net/) does, will never be supported! Only single-direction sync (server-to-client, or client-to-server) is possible with copyparty
2624
2680
 
2681
+ * if you want bidirectional sync, then copyparty and syncthing *should* be entirely safe to combine; they should be able to collaborate on the same folders without causing any trouble for eachother. Many people do this, and there have been no issues so far. But, if you *do* encounter any problems, please [file a copyparty bug](https://github.com/9001/copyparty/issues/new/choose) and I'll try to help -- just keep in mind I've never used syncthing before :-)
2682
+
2625
2683
  the commandline uploader [u2c.py](https://github.com/9001/copyparty/tree/hovudstraum/bin#u2cpy) with `--dr` is the best way to sync a folder to copyparty; verifies checksums and does files in parallel, and deletes unexpected files on the server after upload has finished which makes file-renames really cheap (it'll rename serverside and skip uploading)
2626
2684
 
2685
+ if you want to sync with `u2c.py` then:
2686
+ * the `e2dsa` option (either globally or volflag) must be enabled on the server for the volumes you're syncing into
2687
+ * ...but DON'T enable global-options `no-hash` or `no-idx` (or volflags `nohash` / `noidx`), or at least make sure they are configured so they do not affect anything you are syncing into
2688
+ * ...and u2c needs the delete-permission, so either `rwd` at minimum, or just `A` which is the same as `rwmd.a`
2689
+ * quick reminder that `a` and `A` are different permissions, and `.` is very useful for sync
2690
+
2627
2691
  alternatively there is [rclone](./docs/rclone.md) which allows for bidirectional sync and is *way* more flexible (stream files straight from sftp/s3/gcs to copyparty, ...), although there is no integrity check and it won't work with files over 100 MiB if copyparty is behind cloudflare
2628
2692
 
2629
2693
  * starting from rclone v1.63, rclone is faster than u2c.py on low-latency connections
2694
+ * but this is only true for the initial upload; u2c will be faster for periodic syncing
2630
2695
 
2631
2696
 
2632
2697
  ## mount as drive
@@ -2668,6 +2733,8 @@ there is no iPhone app, but the following shortcuts are almost as good:
2668
2733
  * can download links and rehost the target file on copyparty (see first comment inside the shortcut)
2669
2734
  * pics become lowres if you share from gallery to shortcut, so better to launch the shortcut and pick stuff from there
2670
2735
 
2736
+ if you want to run the copyparty server on your iPhone or iPad, see [install on iOS](#install-on-iOS)
2737
+
2671
2738
 
2672
2739
  # performance
2673
2740
 
@@ -3001,6 +3068,27 @@ if you want thumbnails (photos+videos) and you're okay with spending another 132
3001
3068
  * or if you want to use `vips` for photo-thumbs instead, `pkg install libvips && python -m pip install --user -U wheel && python -m pip install --user -U pyvips && (cd /data/data/com.termux/files/usr/lib/; ln -s libgobject-2.0.so{,.0}; ln -s libvips.so{,.42})`
3002
3069
 
3003
3070
 
3071
+ # install on iOS
3072
+
3073
+ first install one of the following:
3074
+ * [a-Shell mini](https://apps.apple.com/us/app/a-shell-mini/id1543537943) gives you the essential features
3075
+ * [a-Shell](https://apps.apple.com/us/app/a-shell/id1473805438) also enables audio transcoding and better thubmnails
3076
+
3077
+ and then copypaste the following command into `a-Shell`:
3078
+
3079
+ ```sh
3080
+ curl https://github.com/9001/copyparty/raw/refs/heads/hovudstraum/contrib/setup-ashell.sh | sh
3081
+ ```
3082
+
3083
+ what this does:
3084
+ * creates a basic [config file](#accounts-and-volumes) named `cpc` which you can edit with `vim cpc`
3085
+ * adds the command `cpp` to launch copyparty with that config file
3086
+
3087
+ known issues:
3088
+ * cannot run in the background; it needs to be on-screen to accept connections / uploads / downloads
3089
+ * the best way to exit copyparty is to swipe away the app
3090
+
3091
+
3004
3092
  # reporting bugs
3005
3093
 
3006
3094
  ideas for context to include, and where to submit them