copyparty 1.13.6__py3-none-any.whl → 1.13.7__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 +25 -7
- copyparty/__version__.py +2 -2
- copyparty/authsrv.py +9 -6
- copyparty/cert.py +1 -1
- copyparty/fsutil.py +3 -3
- copyparty/ftpd.py +15 -2
- copyparty/httpcli.py +221 -81
- copyparty/httpconn.py +3 -0
- copyparty/httpsrv.py +35 -11
- copyparty/ico.py +1 -1
- copyparty/mtag.py +15 -6
- copyparty/pwhash.py +10 -0
- copyparty/smbd.py +20 -2
- copyparty/ssdp.py +3 -3
- copyparty/stolen/dnslib/dns.py +6 -0
- copyparty/stolen/ifaddr/__init__.py +15 -1
- copyparty/stolen/ifaddr/_shared.py +1 -0
- copyparty/stolen/qrcodegen.py +6 -0
- copyparty/sutil.py +1 -1
- copyparty/svchub.py +72 -3
- copyparty/szip.py +1 -3
- copyparty/tcpsrv.py +60 -8
- copyparty/tftpd.py +30 -4
- copyparty/th_srv.py +22 -1
- copyparty/u2idx.py +4 -1
- copyparty/up2k.py +221 -93
- copyparty/util.py +166 -31
- copyparty/web/a/u2c.py +10 -3
- copyparty/web/browser.css.gz +0 -0
- copyparty/web/browser2.html +0 -1
- copyparty/web/md.html +3 -0
- copyparty/web/mde.html +3 -0
- copyparty/web/msg.html +3 -0
- copyparty/web/splash.html +3 -0
- copyparty/web/svcs.html +3 -0
- copyparty/web/up2k.js.gz +0 -0
- {copyparty-1.13.6.dist-info → copyparty-1.13.7.dist-info}/METADATA +64 -14
- {copyparty-1.13.6.dist-info → copyparty-1.13.7.dist-info}/RECORD +42 -42
- {copyparty-1.13.6.dist-info → copyparty-1.13.7.dist-info}/LICENSE +0 -0
- {copyparty-1.13.6.dist-info → copyparty-1.13.7.dist-info}/WHEEL +0 -0
- {copyparty-1.13.6.dist-info → copyparty-1.13.7.dist-info}/entry_points.txt +0 -0
- {copyparty-1.13.6.dist-info → copyparty-1.13.7.dist-info}/top_level.txt +0 -0
copyparty/util.py
CHANGED
@@ -26,7 +26,6 @@ import threading
|
|
26
26
|
import time
|
27
27
|
import traceback
|
28
28
|
from collections import Counter
|
29
|
-
from email.utils import formatdate
|
30
29
|
|
31
30
|
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
|
32
31
|
from queue import Queue
|
@@ -60,6 +59,10 @@ except:
|
|
60
59
|
UTC = _UTC()
|
61
60
|
|
62
61
|
|
62
|
+
if PY2:
|
63
|
+
range = xrange # type: ignore
|
64
|
+
|
65
|
+
|
63
66
|
if sys.version_info >= (3, 7) or (
|
64
67
|
sys.version_info >= (3, 6) and platform.python_implementation() == "CPython"
|
65
68
|
):
|
@@ -99,6 +102,9 @@ except:
|
|
99
102
|
pass
|
100
103
|
|
101
104
|
try:
|
105
|
+
if os.environ.get("PRTY_NO_SQLITE"):
|
106
|
+
raise Exception()
|
107
|
+
|
102
108
|
HAVE_SQLITE3 = True
|
103
109
|
import sqlite3
|
104
110
|
|
@@ -107,6 +113,9 @@ except:
|
|
107
113
|
HAVE_SQLITE3 = False
|
108
114
|
|
109
115
|
try:
|
116
|
+
if os.environ.get("PRTY_NO_PSUTIL"):
|
117
|
+
raise Exception()
|
118
|
+
|
110
119
|
HAVE_PSUTIL = True
|
111
120
|
import psutil
|
112
121
|
except:
|
@@ -116,10 +125,15 @@ if TYPE_CHECKING:
|
|
116
125
|
import magic
|
117
126
|
|
118
127
|
from .authsrv import VFS
|
128
|
+
from .broker_util import BrokerCli
|
129
|
+
from .up2k import Up2k
|
119
130
|
|
120
131
|
FAKE_MP = False
|
121
132
|
|
122
133
|
try:
|
134
|
+
if os.environ.get("PRTY_NO_MP"):
|
135
|
+
raise ImportError()
|
136
|
+
|
123
137
|
import multiprocessing as mp
|
124
138
|
|
125
139
|
# import multiprocessing.dummy as mp
|
@@ -138,6 +152,9 @@ else:
|
|
138
152
|
|
139
153
|
|
140
154
|
try:
|
155
|
+
if os.environ.get("PRTY_NO_IPV6"):
|
156
|
+
raise Exception()
|
157
|
+
|
141
158
|
socket.inet_pton(socket.AF_INET6, "::1")
|
142
159
|
HAVE_IPV6 = True
|
143
160
|
except:
|
@@ -773,7 +790,7 @@ class CachedSet(object):
|
|
773
790
|
|
774
791
|
c = self.c = {k: v for k, v in self.c.items() if now - v < self.maxage}
|
775
792
|
try:
|
776
|
-
self.oldest = c[min(c, key=c.get)]
|
793
|
+
self.oldest = c[min(c, key=c.get)] # type: ignore
|
777
794
|
except:
|
778
795
|
self.oldest = now
|
779
796
|
|
@@ -1800,10 +1817,21 @@ def gen_filekey_dbg(
|
|
1800
1817
|
return ret
|
1801
1818
|
|
1802
1819
|
|
1820
|
+
WKDAYS = "Mon Tue Wed Thu Fri Sat Sun".split()
|
1821
|
+
MONTHS = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split()
|
1822
|
+
RFC2822 = "%s, %02d %s %04d %02d:%02d:%02d GMT"
|
1823
|
+
|
1824
|
+
|
1825
|
+
def formatdate(ts = None) :
|
1826
|
+
# gmtime ~= datetime.fromtimestamp(ts, UTC).timetuple()
|
1827
|
+
y, mo, d, h, mi, s, wd, _, _ = time.gmtime(ts)
|
1828
|
+
return RFC2822 % (WKDAYS[wd], d, MONTHS[mo - 1], y, h, mi, s)
|
1829
|
+
|
1830
|
+
|
1803
1831
|
def gencookie(k , v , r , tls , dur = 0, txt = "") :
|
1804
1832
|
v = v.replace("%", "%25").replace(";", "%3B")
|
1805
1833
|
if dur:
|
1806
|
-
exp = formatdate(time.time() + dur
|
1834
|
+
exp = formatdate(time.time() + dur)
|
1807
1835
|
else:
|
1808
1836
|
exp = "Fri, 15 Aug 1997 01:00:00 GMT"
|
1809
1837
|
|
@@ -1818,12 +1846,10 @@ def humansize(sz , terse = False) :
|
|
1818
1846
|
|
1819
1847
|
sz /= 1024.0
|
1820
1848
|
|
1821
|
-
|
1822
|
-
|
1823
|
-
|
1824
|
-
return
|
1825
|
-
|
1826
|
-
return ret.replace("iB", "").replace(" ", "")
|
1849
|
+
if terse:
|
1850
|
+
return "%s%s" % (str(sz)[:4].rstrip("."), unit[:1])
|
1851
|
+
else:
|
1852
|
+
return "%s %s" % (str(sz)[:4].rstrip("."), unit)
|
1827
1853
|
|
1828
1854
|
|
1829
1855
|
def unhumanize(sz ) :
|
@@ -1875,7 +1901,7 @@ def uncyg(path ) :
|
|
1875
1901
|
def undot(path ) :
|
1876
1902
|
ret = []
|
1877
1903
|
for node in path.split("/"):
|
1878
|
-
if node
|
1904
|
+
if node == "." or not node:
|
1879
1905
|
continue
|
1880
1906
|
|
1881
1907
|
if node == "..":
|
@@ -2028,7 +2054,7 @@ def _quotep2(txt ) :
|
|
2028
2054
|
"""url quoter which deals with bytes correctly"""
|
2029
2055
|
btxt = w8enc(txt)
|
2030
2056
|
quot = quote(btxt, safe=b"/")
|
2031
|
-
return w8dec(quot.replace(b" ", b"+"))
|
2057
|
+
return w8dec(quot.replace(b" ", b"+")) # type: ignore
|
2032
2058
|
|
2033
2059
|
|
2034
2060
|
def _quotep3(txt ) :
|
@@ -2072,6 +2098,72 @@ def ujoin(rd , fn ) :
|
|
2072
2098
|
return rd or fn
|
2073
2099
|
|
2074
2100
|
|
2101
|
+
def log_reloc(
|
2102
|
+
log ,
|
2103
|
+
re ,
|
2104
|
+
pm ,
|
2105
|
+
ap ,
|
2106
|
+
vp ,
|
2107
|
+
fn ,
|
2108
|
+
vn ,
|
2109
|
+
rem ,
|
2110
|
+
) :
|
2111
|
+
nap, nvp, nfn, (nvn, nrem) = pm
|
2112
|
+
t = "reloc %s:\nold ap [%s]\nnew ap [%s\033[36m/%s\033[0m]\nold vp [%s]\nnew vp [%s\033[36m/%s\033[0m]\nold fn [%s]\nnew fn [%s]\nold vfs [%s]\nnew vfs [%s]\nold rem [%s]\nnew rem [%s]"
|
2113
|
+
log(t % (re, ap, nap, nfn, vp, nvp, nfn, fn, nfn, vn.vpath, nvn.vpath, rem, nrem))
|
2114
|
+
|
2115
|
+
|
2116
|
+
def pathmod(
|
2117
|
+
vfs , ap , vp , mod
|
2118
|
+
) :
|
2119
|
+
# vfs: authsrv.vfs
|
2120
|
+
# ap: original abspath to a file
|
2121
|
+
# vp: original urlpath to a file
|
2122
|
+
# mod: modification (ap/vp/fn)
|
2123
|
+
|
2124
|
+
nvp = "\n" # new vpath
|
2125
|
+
ap = os.path.dirname(ap)
|
2126
|
+
vp, fn = vsplit(vp)
|
2127
|
+
if mod.get("fn"):
|
2128
|
+
fn = mod["fn"]
|
2129
|
+
nvp = vp
|
2130
|
+
|
2131
|
+
for ref, k in ((ap, "ap"), (vp, "vp")):
|
2132
|
+
if k not in mod:
|
2133
|
+
continue
|
2134
|
+
|
2135
|
+
ms = mod[k].replace(os.sep, "/")
|
2136
|
+
if ms.startswith("/"):
|
2137
|
+
np = ms
|
2138
|
+
elif k == "vp":
|
2139
|
+
np = undot(vjoin(ref, ms))
|
2140
|
+
else:
|
2141
|
+
np = os.path.abspath(os.path.join(ref, ms))
|
2142
|
+
|
2143
|
+
if k == "vp":
|
2144
|
+
nvp = np.lstrip("/")
|
2145
|
+
continue
|
2146
|
+
|
2147
|
+
# try to map abspath to vpath
|
2148
|
+
np = np.replace("/", os.sep)
|
2149
|
+
for vn_ap, vn in vfs.all_aps:
|
2150
|
+
if not np.startswith(vn_ap):
|
2151
|
+
continue
|
2152
|
+
zs = np[len(vn_ap) :].replace(os.sep, "/")
|
2153
|
+
nvp = vjoin(vn.vpath, zs)
|
2154
|
+
break
|
2155
|
+
|
2156
|
+
if nvp == "\n":
|
2157
|
+
return None
|
2158
|
+
|
2159
|
+
vn, rem = vfs.get(nvp, "*", False, False)
|
2160
|
+
if not vn.realpath:
|
2161
|
+
raise Exception("unmapped vfs")
|
2162
|
+
|
2163
|
+
ap = vn.canonical(rem)
|
2164
|
+
return ap, nvp, fn, (vn, rem)
|
2165
|
+
|
2166
|
+
|
2075
2167
|
def _w8dec2(txt ) :
|
2076
2168
|
"""decodes filesystem-bytes to wtf8"""
|
2077
2169
|
return surrogateescape.decodefilename(txt)
|
@@ -2688,30 +2780,30 @@ def rmdirs_up(top , stop ) :
|
|
2688
2780
|
|
2689
2781
|
def unescape_cookie(orig ) :
|
2690
2782
|
# mw=idk; doot=qwe%2Crty%3Basd+fgh%2Bjkl%25zxc%26vbn # qwe,rty;asd fgh+jkl%zxc&vbn
|
2691
|
-
ret =
|
2783
|
+
ret = []
|
2692
2784
|
esc = ""
|
2693
2785
|
for ch in orig:
|
2694
2786
|
if ch == "%":
|
2695
|
-
if
|
2696
|
-
ret
|
2787
|
+
if esc:
|
2788
|
+
ret.append(esc)
|
2697
2789
|
esc = ch
|
2698
2790
|
|
2699
|
-
elif
|
2791
|
+
elif esc:
|
2700
2792
|
esc += ch
|
2701
2793
|
if len(esc) == 3:
|
2702
2794
|
try:
|
2703
|
-
ret
|
2795
|
+
ret.append(chr(int(esc[1:], 16)))
|
2704
2796
|
except:
|
2705
|
-
ret
|
2797
|
+
ret.append(esc)
|
2706
2798
|
esc = ""
|
2707
2799
|
|
2708
2800
|
else:
|
2709
|
-
ret
|
2801
|
+
ret.append(ch)
|
2710
2802
|
|
2711
|
-
if
|
2712
|
-
ret
|
2803
|
+
if esc:
|
2804
|
+
ret.append(esc)
|
2713
2805
|
|
2714
|
-
return ret
|
2806
|
+
return "".join(ret)
|
2715
2807
|
|
2716
2808
|
|
2717
2809
|
def guess_mime(url , fallback = "application/octet-stream") :
|
@@ -3085,6 +3177,7 @@ def runihook(
|
|
3085
3177
|
|
3086
3178
|
def _runhook(
|
3087
3179
|
log ,
|
3180
|
+
src ,
|
3088
3181
|
cmd ,
|
3089
3182
|
ap ,
|
3090
3183
|
vp ,
|
@@ -3096,14 +3189,16 @@ def _runhook(
|
|
3096
3189
|
ip ,
|
3097
3190
|
at ,
|
3098
3191
|
txt ,
|
3099
|
-
)
|
3192
|
+
) :
|
3193
|
+
ret = {"rc": 0}
|
3100
3194
|
areq, chk, fork, jtxt, wait, sp_ka, acmd = _parsehook(log, cmd)
|
3101
3195
|
if areq:
|
3102
3196
|
for ch in areq:
|
3103
3197
|
if ch not in perms:
|
3104
3198
|
t = "user %s not allowed to run hook %s; need perms %s, have %s"
|
3105
|
-
log
|
3106
|
-
|
3199
|
+
if log:
|
3200
|
+
log(t % (uname, cmd, areq, perms))
|
3201
|
+
return ret # fallthrough to next hook
|
3107
3202
|
if jtxt:
|
3108
3203
|
ja = {
|
3109
3204
|
"ap": ap,
|
@@ -3115,6 +3210,7 @@ def _runhook(
|
|
3115
3210
|
"host": host,
|
3116
3211
|
"user": uname,
|
3117
3212
|
"perms": perms,
|
3213
|
+
"src": src,
|
3118
3214
|
"txt": txt,
|
3119
3215
|
}
|
3120
3216
|
arg = json.dumps(ja)
|
@@ -3133,18 +3229,34 @@ def _runhook(
|
|
3133
3229
|
else:
|
3134
3230
|
rc, v, err = runcmd(bcmd, **sp_ka) # type: ignore
|
3135
3231
|
if chk and rc:
|
3232
|
+
ret["rc"] = rc
|
3136
3233
|
retchk(rc, bcmd, err, log, 5)
|
3137
|
-
|
3234
|
+
else:
|
3235
|
+
try:
|
3236
|
+
ret = json.loads(v)
|
3237
|
+
except:
|
3238
|
+
ret = {}
|
3239
|
+
|
3240
|
+
try:
|
3241
|
+
if "stdout" not in ret:
|
3242
|
+
ret["stdout"] = v
|
3243
|
+
if "rc" not in ret:
|
3244
|
+
ret["rc"] = rc
|
3245
|
+
except:
|
3246
|
+
ret = {"rc": rc, "stdout": v}
|
3138
3247
|
|
3139
3248
|
wait -= time.time() - t0
|
3140
3249
|
if wait > 0:
|
3141
3250
|
time.sleep(wait)
|
3142
3251
|
|
3143
|
-
return
|
3252
|
+
return ret
|
3144
3253
|
|
3145
3254
|
|
3146
3255
|
def runhook(
|
3147
3256
|
log ,
|
3257
|
+
broker ,
|
3258
|
+
up2k ,
|
3259
|
+
src ,
|
3148
3260
|
cmds ,
|
3149
3261
|
ap ,
|
3150
3262
|
vp ,
|
@@ -3156,19 +3268,42 @@ def runhook(
|
|
3156
3268
|
ip ,
|
3157
3269
|
at ,
|
3158
3270
|
txt ,
|
3159
|
-
)
|
3271
|
+
) :
|
3272
|
+
assert broker or up2k
|
3273
|
+
asrv = (broker or up2k).asrv
|
3274
|
+
args = (broker or up2k).args
|
3160
3275
|
vp = vp.replace("\\", "/")
|
3276
|
+
ret = {"rc": 0}
|
3161
3277
|
for cmd in cmds:
|
3162
3278
|
try:
|
3163
|
-
|
3164
|
-
|
3279
|
+
hr = _runhook(
|
3280
|
+
log, src, cmd, ap, vp, host, uname, perms, mt, sz, ip, at, txt
|
3281
|
+
)
|
3282
|
+
if log and args.hook_v:
|
3283
|
+
log("hook(%s) [%s] => \033[32m%s" % (src, cmd, hr), 6)
|
3284
|
+
if not hr:
|
3285
|
+
return {}
|
3286
|
+
for k, v in hr.items():
|
3287
|
+
if k in ("idx", "del") and v:
|
3288
|
+
if broker:
|
3289
|
+
broker.say("up2k.hook_fx", k, v, vp)
|
3290
|
+
else:
|
3291
|
+
up2k.fx_backlog.append((k, v, vp))
|
3292
|
+
elif k == "reloc" and v:
|
3293
|
+
# idk, just take the last one ig
|
3294
|
+
ret["reloc"] = v
|
3295
|
+
elif k in ret:
|
3296
|
+
if k == "rc" and v:
|
3297
|
+
ret[k] = v
|
3298
|
+
else:
|
3299
|
+
ret[k] = v
|
3165
3300
|
except Exception as ex:
|
3166
3301
|
(log or print)("hook: {}".format(ex))
|
3167
3302
|
if ",c," in "," + cmd:
|
3168
|
-
return
|
3303
|
+
return {}
|
3169
3304
|
break
|
3170
3305
|
|
3171
|
-
return
|
3306
|
+
return ret
|
3172
3307
|
|
3173
3308
|
|
3174
3309
|
def loadpy(ap , hot ) :
|
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.
|
5
|
-
S_BUILD_DT = "2024-
|
4
|
+
S_VERSION = "1.22"
|
5
|
+
S_BUILD_DT = "2024-08-08"
|
6
6
|
|
7
7
|
"""
|
8
8
|
u2c.py: upload to copyparty
|
@@ -660,8 +660,15 @@ def upload(fsl, pw, stats):
|
|
660
660
|
# type: (FileSlice, str, str) -> None
|
661
661
|
"""upload a range of file data, defined by one or more `cid` (chunk-hash)"""
|
662
662
|
|
663
|
+
ctxt = fsl.cids[0]
|
664
|
+
if len(fsl.cids) > 1:
|
665
|
+
n = 192 // len(fsl.cids)
|
666
|
+
n = 9 if n > 9 else 2 if n < 2 else n
|
667
|
+
zsl = [zs[:n] for zs in fsl.cids[1:]]
|
668
|
+
ctxt += ",%d,%s" % (n, "".join(zsl))
|
669
|
+
|
663
670
|
headers = {
|
664
|
-
"X-Up2k-Hash":
|
671
|
+
"X-Up2k-Hash": ctxt,
|
665
672
|
"X-Up2k-Wark": fsl.file.wark,
|
666
673
|
"Content-Type": "application/octet-stream",
|
667
674
|
}
|
copyparty/web/browser.css.gz
CHANGED
Binary file
|
copyparty/web/browser2.html
CHANGED
copyparty/web/md.html
CHANGED
copyparty/web/mde.html
CHANGED
@@ -53,5 +53,8 @@ try { l.light = drk? 0:1; } catch (ex) { }
|
|
53
53
|
<script src="{{ r }}/.cpr/deps/marked.js?_={{ ts }}"></script>
|
54
54
|
<script src="{{ r }}/.cpr/deps/easymde.js?_={{ ts }}"></script>
|
55
55
|
<script src="{{ r }}/.cpr/mde.js?_={{ ts }}"></script>
|
56
|
+
{%- if js %}
|
57
|
+
<script src="{{ js }}_={{ ts }}"></script>
|
58
|
+
{%- endif %}
|
56
59
|
</body></html>
|
57
60
|
|
copyparty/web/msg.html
CHANGED
copyparty/web/splash.html
CHANGED
@@ -119,6 +119,9 @@ document.documentElement.className = (STG && STG.cpp_thm) || "{{ this.args.theme
|
|
119
119
|
</script>
|
120
120
|
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
121
121
|
<script src="{{ r }}/.cpr/splash.js?_={{ ts }}"></script>
|
122
|
+
{%- if js %}
|
123
|
+
<script src="{{ js }}_={{ ts }}"></script>
|
124
|
+
{%- endif %}
|
122
125
|
</body>
|
123
126
|
</html>
|
124
127
|
|
copyparty/web/svcs.html
CHANGED
@@ -245,6 +245,9 @@ document.documentElement.className = (STG && STG.cpp_thm) || "{{ args.theme }}";
|
|
245
245
|
</script>
|
246
246
|
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
247
247
|
<script src="{{ r }}/.cpr/svcs.js?_={{ ts }}"></script>
|
248
|
+
{%- if js %}
|
249
|
+
<script src="{{ js }}_={{ ts }}"></script>
|
250
|
+
{%- endif %}
|
248
251
|
</body>
|
249
252
|
</html>
|
250
253
|
|
copyparty/web/up2k.js.gz
CHANGED
Binary file
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: copyparty
|
3
|
-
Version: 1.13.
|
3
|
+
Version: 1.13.7
|
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
|
@@ -37,20 +37,20 @@ Description-Content-Type: text/markdown
|
|
37
37
|
License-File: LICENSE
|
38
38
|
Requires-Dist: Jinja2
|
39
39
|
Provides-Extra: audiotags
|
40
|
-
Requires-Dist: mutagen
|
40
|
+
Requires-Dist: mutagen; extra == "audiotags"
|
41
41
|
Provides-Extra: ftpd
|
42
|
-
Requires-Dist: pyftpdlib
|
42
|
+
Requires-Dist: pyftpdlib; extra == "ftpd"
|
43
43
|
Provides-Extra: ftps
|
44
|
-
Requires-Dist: pyftpdlib
|
45
|
-
Requires-Dist: pyopenssl
|
44
|
+
Requires-Dist: pyftpdlib; extra == "ftps"
|
45
|
+
Requires-Dist: pyopenssl; extra == "ftps"
|
46
46
|
Provides-Extra: pwhash
|
47
|
-
Requires-Dist: argon2-cffi
|
47
|
+
Requires-Dist: argon2-cffi; extra == "pwhash"
|
48
48
|
Provides-Extra: tftpd
|
49
|
-
Requires-Dist: partftpy
|
49
|
+
Requires-Dist: partftpy>=0.4.0; extra == "tftpd"
|
50
50
|
Provides-Extra: thumbnails
|
51
|
-
Requires-Dist: Pillow
|
51
|
+
Requires-Dist: Pillow; extra == "thumbnails"
|
52
52
|
Provides-Extra: thumbnails2
|
53
|
-
Requires-Dist: pyvips
|
53
|
+
Requires-Dist: pyvips; extra == "thumbnails2"
|
54
54
|
|
55
55
|
# 💾🎉 copyparty
|
56
56
|
|
@@ -139,6 +139,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|
139
139
|
* [prometheus](#prometheus) - metrics/stats can be enabled
|
140
140
|
* [other extremely specific features](#other-extremely-specific-features) - you'll never find a use for these
|
141
141
|
* [custom mimetypes](#custom-mimetypes) - change the association of a file extension
|
142
|
+
* [feature chickenbits](#feature-chickenbits) - buggy feature? rip it out
|
142
143
|
* [packages](#packages) - the party might be closer than you think
|
143
144
|
* [arch package](#arch-package) - now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
|
144
145
|
* [fedora package](#fedora-package) - does not exist yet
|
@@ -165,6 +166,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|
165
166
|
* [HTTP API](#HTTP-API) - see [devnotes](./docs/devnotes.md#http-api)
|
166
167
|
* [dependencies](#dependencies) - mandatory deps
|
167
168
|
* [optional dependencies](#optional-dependencies) - install these to enable bonus features
|
169
|
+
* [dependency chickenbits](#dependency-chickenbits) - prevent loading an optional dependency
|
168
170
|
* [optional gpl stuff](#optional-gpl-stuff)
|
169
171
|
* [sfx](#sfx) - the self-contained "binary" (recommended!)
|
170
172
|
* [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+)
|
@@ -178,7 +180,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|
178
180
|
|
179
181
|
just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
|
180
182
|
|
181
|
-
* or install through pypi: `python3 -m pip install --user -U copyparty`
|
183
|
+
* or install through [pypi](https://pypi.org/project/copyparty/): `python3 -m pip install --user -U copyparty`
|
182
184
|
* or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
|
183
185
|
* or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
|
184
186
|
* or if you are on android, [install copyparty in termux](#install-on-android)
|
@@ -248,7 +250,7 @@ firewall-cmd --reload
|
|
248
250
|
also see [comparison to similar software](./docs/versus.md)
|
249
251
|
|
250
252
|
* backend stuff
|
251
|
-
* ☑ IPv6
|
253
|
+
* ☑ IPv6 + unix-sockets
|
252
254
|
* ☑ [multiprocessing](#performance) (actual multithreading)
|
253
255
|
* ☑ volumes (mountpoints)
|
254
256
|
* ☑ [accounts](#accounts-and-volumes)
|
@@ -633,9 +635,6 @@ images with the following names (see `--th-covers`) become the thumbnail of the
|
|
633
635
|
* the order is significant, so if both `cover.png` and `folder.jpg` exist in a folder, it will pick the first matching `--th-covers` entry (`folder.jpg`)
|
634
636
|
* and, if you enable [file indexing](#file-indexing), it will also try those names as dotfiles (`.folder.jpg` and so), and then fallback on the first picture in the folder (if it has any pictures at all)
|
635
637
|
|
636
|
-
in the grid/thumbnail view, if the audio player panel is open, songs will start playing when clicked
|
637
|
-
* indicated by the audio files having the ▶ icon instead of 💾
|
638
|
-
|
639
638
|
enabling `multiselect` lets you click files to select them, and then shift-click another file for range-select
|
640
639
|
* `multiselect` is mostly intended for phones/tablets, but the `sel` option in the `[⚙️] settings` tab is better suited for desktop use, allowing selection by CTRL-clicking and range-selection with SHIFT-click, all without affecting regular clicking
|
641
640
|
* the `sel` option can be made default globally with `--gsel` or per-volume with volflag `gsel`
|
@@ -1514,6 +1513,8 @@ some reverse proxies (such as [Caddy](https://caddyserver.com/)) can automatical
|
|
1514
1513
|
* **warning:** nginx-QUIC (HTTP/3) is still experimental and can make uploads much slower, so HTTP/1.1 is recommended for now
|
1515
1514
|
* depending on server/client, HTTP/1.1 can also be 5x faster than HTTP/2
|
1516
1515
|
|
1516
|
+
for improved security (and a tiny performance boost) consider listening on a unix-socket with `-i /tmp/party.sock` instead of `-i 127.0.0.1`
|
1517
|
+
|
1517
1518
|
example webserver configs:
|
1518
1519
|
|
1519
1520
|
* [nginx config](contrib/nginx/copyparty.conf) -- entire domain/subdomain
|
@@ -1614,6 +1615,23 @@ in a config-file, this is the same as:
|
|
1614
1615
|
run copyparty with `--mimes` to list all the default mappings
|
1615
1616
|
|
1616
1617
|
|
1618
|
+
### feature chickenbits
|
1619
|
+
|
1620
|
+
buggy feature? rip it out by setting any of the following environment variables to disable its associated bell or whistle,
|
1621
|
+
|
1622
|
+
| env-var | what it does |
|
1623
|
+
| -------------------- | ------------ |
|
1624
|
+
| `PRTY_NO_IFADDR` | disable ip/nic discovery by poking into your OS with ctypes |
|
1625
|
+
| `PRTY_NO_IPV6` | disable some ipv6 support (should not be necessary since windows 2000) |
|
1626
|
+
| `PRTY_NO_LZMA` | disable streaming xz compression of incoming uploads |
|
1627
|
+
| `PRTY_NO_MP` | disable all use of the python `multiprocessing` module (actual multithreading, cpu-count for parsers/thumbnailers) |
|
1628
|
+
| `PRTY_NO_SQLITE` | disable all database-related functionality (file indexing, metadata indexing, most file deduplication logic) |
|
1629
|
+
| `PRTY_NO_TLS` | disable native HTTPS support; if you still want to accept HTTPS connections then TLS must now be terminated by a reverse-proxy |
|
1630
|
+
| `PRTY_NO_TPOKE` | disable systemd-tmpfilesd avoider |
|
1631
|
+
|
1632
|
+
example: `PRTY_NO_IFADDR=1 python3 copyparty-sfx.py`
|
1633
|
+
|
1634
|
+
|
1617
1635
|
# packages
|
1618
1636
|
|
1619
1637
|
the party might be closer than you think
|
@@ -1936,6 +1954,7 @@ some notes on hardening
|
|
1936
1954
|
* cors doesn't work right otherwise
|
1937
1955
|
* if you allow anonymous uploads or otherwise don't trust the contents of a volume, you can prevent XSS with volflag `nohtml`
|
1938
1956
|
* this returns html documents as plaintext, and also disables markdown rendering
|
1957
|
+
* when running behind a reverse-proxy, listen on a unix-socket with `-i /tmp/party.sock` instead of `-i 127.0.0.1` for tighter access control (plus you get a tiny performance boost for free)
|
1939
1958
|
|
1940
1959
|
safety profiles:
|
1941
1960
|
|
@@ -2100,6 +2119,37 @@ enable [smb](#smb-server) support (**not** recommended):
|
|
2100
2119
|
`pyvips` gives higher quality thumbnails than `Pillow` and is 320% faster, using 270% more ram: `sudo apt install libvips42 && python3 -m pip install --user -U pyvips`
|
2101
2120
|
|
2102
2121
|
|
2122
|
+
### dependency chickenbits
|
2123
|
+
|
2124
|
+
prevent loading an optional dependency , for example if:
|
2125
|
+
|
2126
|
+
* you have an incompatible version installed and it causes problems
|
2127
|
+
* you just don't want copyparty to use it, maybe to save ram
|
2128
|
+
|
2129
|
+
set any of the following environment variables to disable its associated optional feature,
|
2130
|
+
|
2131
|
+
| env-var | what it does |
|
2132
|
+
| -------------------- | ------------ |
|
2133
|
+
| `PRTY_NO_ARGON2` | disable argon2-cffi password hashing |
|
2134
|
+
| `PRTY_NO_CFSSL` | never attempt to generate self-signed certificates using [cfssl](https://github.com/cloudflare/cfssl) |
|
2135
|
+
| `PRTY_NO_FFMPEG` | **audio transcoding** goes byebye, **thumbnailing** must be handled by Pillow/libvips |
|
2136
|
+
| `PRTY_NO_FFPROBE` | **audio transcoding** goes byebye, **thumbnailing** must be handled by Pillow/libvips, **metadata-scanning** must be handled by mutagen |
|
2137
|
+
| `PRTY_NO_MUTAGEN` | do not use [mutagen](https://pypi.org/project/mutagen/) for reading metadata from media files; will fallback to ffprobe |
|
2138
|
+
| `PRTY_NO_PIL` | disable all [Pillow](https://pypi.org/project/pillow/)-based thumbnail support; will fallback to libvips or ffmpeg |
|
2139
|
+
| `PRTY_NO_PILF` | disable Pillow `ImageFont` text rendering, used for folder thumbnails |
|
2140
|
+
| `PRTY_NO_PIL_AVIF` | disable 3rd-party Pillow plugin for [AVIF support](https://pypi.org/project/pillow-avif-plugin/) |
|
2141
|
+
| `PRTY_NO_PIL_HEIF` | disable 3rd-party Pillow plugin for [HEIF support](https://pypi.org/project/pyheif-pillow-opener/) |
|
2142
|
+
| `PRTY_NO_PIL_WEBP` | disable use of native webp support in Pillow |
|
2143
|
+
| `PRTY_NO_PSUTIL` | do not use [psutil](https://pypi.org/project/psutil/) for reaping stuck hooks and plugins on Windows |
|
2144
|
+
| `PRTY_NO_VIPS` | disable all [libvips](https://pypi.org/project/pyvips/)-based thumbnail support; will fallback to Pillow or ffmpeg |
|
2145
|
+
|
2146
|
+
example: `PRTY_NO_PIL=1 python3 copyparty-sfx.py`
|
2147
|
+
|
2148
|
+
* `PRTY_NO_PIL` saves ram
|
2149
|
+
* `PRTY_NO_VIPS` saves ram and startup time
|
2150
|
+
* python2.7 on windows: `PRTY_NO_FFMPEG` + `PRTY_NO_FFPROBE` saves startup time
|
2151
|
+
|
2152
|
+
|
2103
2153
|
## optional gpl stuff
|
2104
2154
|
|
2105
2155
|
some bundled tools have copyleft dependencies, see [./bin/#mtag](bin/#mtag)
|