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/__main__.py +85 -32
- copyparty/__version__.py +2 -2
- copyparty/authsrv.py +28 -1
- copyparty/broker_util.py +0 -1
- copyparty/cert.py +1 -0
- copyparty/cfg.py +2 -0
- copyparty/ftpd.py +5 -5
- copyparty/httpcli.py +46 -30
- copyparty/mdns.py +2 -2
- copyparty/multicast.py +3 -3
- copyparty/pwhash.py +1 -0
- copyparty/smbd.py +1 -1
- copyparty/stolen/qrcodegen.py +19 -0
- copyparty/svchub.py +54 -10
- copyparty/tcpsrv.py +37 -4
- copyparty/up2k.py +13 -2
- copyparty/util.py +30 -0
- copyparty/web/a/u2c.py +6 -6
- copyparty/web/browser.css.gz +0 -0
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/md.html +2 -1
- copyparty/web/md.js.gz +0 -0
- copyparty/web/md2.js.gz +0 -0
- copyparty/web/mde.html +2 -1
- copyparty/web/splash.js.gz +0 -0
- copyparty/web/ui.css.gz +0 -0
- copyparty/web/up2k.js.gz +0 -0
- {copyparty-1.19.4.dist-info → copyparty-1.19.6.dist-info}/METADATA +95 -7
- {copyparty-1.19.4.dist-info → copyparty-1.19.6.dist-info}/RECORD +33 -33
- {copyparty-1.19.4.dist-info → copyparty-1.19.6.dist-info}/WHEEL +0 -0
- {copyparty-1.19.4.dist-info → copyparty-1.19.6.dist-info}/entry_points.txt +0 -0
- {copyparty-1.19.4.dist-info → copyparty-1.19.6.dist-info}/licenses/LICENSE +0 -0
- {copyparty-1.19.4.dist-info → copyparty-1.19.6.dist-info}/top_level.txt +0 -0
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 "
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
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(
|
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(
|
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.
|
5
|
-
S_BUILD_DT = "2025-
|
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.
|
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")
|
copyparty/web/browser.css.gz
CHANGED
Binary file
|
copyparty/web/browser.js.gz
CHANGED
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 = {{
|
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
copyparty/web/splash.js.gz
CHANGED
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.
|
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
|
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
|
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
|
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
|
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
|
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
|