copyparty 1.19.17__py3-none-any.whl → 1.19.19__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.
Files changed (61) hide show
  1. copyparty/__init__.py +6 -1
  2. copyparty/__main__.py +1 -0
  3. copyparty/__version__.py +2 -2
  4. copyparty/authsrv.py +47 -25
  5. copyparty/ftpd.py +1 -1
  6. copyparty/httpcli.py +61 -25
  7. copyparty/httpsrv.py +2 -2
  8. copyparty/web/a/partyfuse.py.gz +0 -0
  9. copyparty/web/a/u2c.py.gz +0 -0
  10. copyparty/web/a/webdav-cfg.txt.gz +0 -0
  11. copyparty/web/baguettebox.js.gz +0 -0
  12. copyparty/web/browser.css.gz +0 -0
  13. copyparty/web/browser.js.gz +0 -0
  14. copyparty/web/dbg-audio.js.gz +0 -0
  15. copyparty/web/md.css.gz +0 -0
  16. copyparty/web/md.js.gz +0 -0
  17. copyparty/web/md2.css.gz +0 -0
  18. copyparty/web/md2.js.gz +0 -0
  19. copyparty/web/mde.css.gz +0 -0
  20. copyparty/web/mde.js.gz +0 -0
  21. copyparty/web/msg.css.gz +0 -0
  22. copyparty/web/rups.css.gz +0 -0
  23. copyparty/web/rups.js.gz +0 -0
  24. copyparty/web/shares.css.gz +0 -0
  25. copyparty/web/shares.js.gz +0 -0
  26. copyparty/web/splash.css.gz +0 -0
  27. copyparty/web/splash.js.gz +0 -0
  28. copyparty/web/svcs.html +1 -1
  29. copyparty/web/svcs.js.gz +0 -0
  30. copyparty/web/tl/chi.js.gz +0 -0
  31. copyparty/web/tl/cze.js.gz +0 -0
  32. copyparty/web/tl/deu.js.gz +0 -0
  33. copyparty/web/tl/epo.js.gz +0 -0
  34. copyparty/web/tl/fin.js.gz +0 -0
  35. copyparty/web/tl/fra.js.gz +0 -0
  36. copyparty/web/tl/grc.js.gz +0 -0
  37. copyparty/web/tl/ita.js.gz +0 -0
  38. copyparty/web/tl/kor.js.gz +0 -0
  39. copyparty/web/tl/nld.js.gz +0 -0
  40. copyparty/web/tl/nno.js.gz +0 -0
  41. copyparty/web/tl/nor.js.gz +0 -0
  42. copyparty/web/tl/pol.js.gz +0 -0
  43. copyparty/web/tl/por.js.gz +0 -0
  44. copyparty/web/tl/rus.js.gz +0 -0
  45. copyparty/web/tl/spa.js.gz +0 -0
  46. copyparty/web/tl/swe.js.gz +0 -0
  47. copyparty/web/tl/tur.js.gz +0 -0
  48. copyparty/web/tl/ukr.js.gz +0 -0
  49. copyparty/web/ui.css.gz +0 -0
  50. copyparty/web/up2k.js.gz +0 -0
  51. copyparty/web/util.js.gz +0 -0
  52. copyparty/web/w.hash.js.gz +0 -0
  53. {copyparty-1.19.17.dist-info → copyparty-1.19.19.dist-info}/METADATA +17 -3
  54. {copyparty-1.19.17.dist-info → copyparty-1.19.19.dist-info}/RECORD +58 -58
  55. copyparty/web/a/partyfuse.py +0 -947
  56. copyparty/web/a/u2c.py +0 -1718
  57. copyparty/web/a/webdav-cfg.bat +0 -45
  58. {copyparty-1.19.17.dist-info → copyparty-1.19.19.dist-info}/WHEEL +0 -0
  59. {copyparty-1.19.17.dist-info → copyparty-1.19.19.dist-info}/entry_points.txt +0 -0
  60. {copyparty-1.19.17.dist-info → copyparty-1.19.19.dist-info}/licenses/LICENSE +0 -0
  61. {copyparty-1.19.17.dist-info → copyparty-1.19.19.dist-info}/top_level.txt +0 -0
copyparty/__init__.py CHANGED
@@ -52,7 +52,7 @@ except:
52
52
  zs = """
53
53
  web/a/partyfuse.py
54
54
  web/a/u2c.py
55
- web/a/webdav-cfg.bat
55
+ web/a/webdav-cfg.txt
56
56
  web/baguettebox.js
57
57
  web/browser.css
58
58
  web/browser.html
@@ -122,6 +122,11 @@ web/util.js
122
122
  web/w.hash.js
123
123
  """
124
124
  RES = set(zs.strip().split("\n"))
125
+ RESM = {
126
+ "web/a/partyfuse.txt": "web/a/partyfuse.py",
127
+ "web/a/u2c.txt": "web/a/u2c.py",
128
+ "web/a/webdav-cfg.bat": "web/a/webdav-cfg.txt",
129
+ }
125
130
 
126
131
 
127
132
  class EnvParams(object):
copyparty/__main__.py CHANGED
@@ -1788,6 +1788,7 @@ def add_ui(ap, retry ):
1788
1788
  ap2.add_argument("--ufavico", metavar="TXT", type=u, default="", help="URL to .ico/png/gif/svg file; \033[33m--favico\033[0m takes precedence unless disabled (volflag=ufavico)")
1789
1789
  ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="\033[34mREPEATABLE:\033[0m use thumbnail-image \033[33mVP\033[0m for file-extension \033[33mE\033[0m, example: [\033[32mexe=/.res/exe.png\033[0m] (volflag=ext_th)")
1790
1790
  ap2.add_argument("--mpmc", type=u, default="", help=argparse.SUPPRESS)
1791
+ ap2.add_argument("--notooltips", action="store_true", help="tooltips disabled as default")
1791
1792
  ap2.add_argument("--spinner", metavar="TXT", type=u, default="🌲", help="\033[33memoji\033[0m or \033[33memoji,css\033[0m Example: [\033[32m🥖,padding:0\033[0m]")
1792
1793
  ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include in the filebrowser html")
1793
1794
  ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include in the filebrowser html")
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 19, 17)
3
+ VERSION = (1, 19, 19)
4
4
  CODENAME = "usernames"
5
- BUILD_DT = (2025, 10, 17)
5
+ BUILD_DT = (2025, 10, 25)
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/authsrv.py CHANGED
@@ -391,6 +391,9 @@ class VFS(object):
391
391
  self.vpath = vpath # absolute path in the virtual filesystem
392
392
  self.vpath0 = vpath0 # original vpath (before idp expansion)
393
393
  self.axs = axs
394
+ self.uaxs = {}
395
+
396
+
394
397
  self.flags = flags # config options
395
398
  self.root = self
396
399
  self.dev = 0 # st_dev
@@ -548,29 +551,19 @@ class VFS(object):
548
551
 
549
552
  def can_access(
550
553
  self, vpath , uname
551
- ) :
552
- """can Read,Write,Move,Delete,Get,Upget,Admin,Dot"""
554
+ ) :
555
+ """can Read,Write,Move,Delete,Get,Upget,Html,Admin,Dot"""
556
+ # NOTE: only used by get_perms, which is only used by hooks; the lowest of fruits
553
557
  if vpath:
554
558
  vn, _ = self._find(undot(vpath))
555
559
  else:
556
560
  vn = self
557
561
 
558
- c = vn.axs
559
- return (
560
- uname in c.uread,
561
- uname in c.uwrite,
562
- uname in c.umove,
563
- uname in c.udel,
564
- uname in c.uget,
565
- uname in c.upget,
566
- uname in c.uadmin,
567
- uname in c.udot,
568
- )
569
- # skip uhtml because it's rarely needed
562
+ return vn.uaxs[uname]
570
563
 
571
564
  def get_perms(self, vpath , uname ) :
572
565
  zbl = self.can_access(vpath, uname)
573
- ret = "".join(ch for ch, ok in zip("rwmdgGa.", zbl) if ok)
566
+ ret = "".join(ch for ch, ok in zip("rwmdgGha.", zbl) if ok)
574
567
  if "rwmd" in ret and "a." in ret:
575
568
  ret += "A"
576
569
  return ret
@@ -763,20 +756,17 @@ class VFS(object):
763
756
  virt_vis[name] = vn2
764
757
  continue
765
758
 
766
- ok = False
767
- zx = vn2.axs
768
- axs = [zx.uread, zx.uwrite, zx.umove, zx.udel, zx.uget]
759
+ u_has = vn2.uaxs.get(uname) or [False] * 9
769
760
  for pset in permsets:
770
761
  ok = True
771
- for req, lst in zip(pset, axs):
772
- if req and uname not in lst:
762
+ for req, zb in zip(pset, u_has):
763
+ if req and not zb:
773
764
  ok = False
765
+ break
774
766
  if ok:
767
+ virt_vis[name] = vn2
775
768
  break
776
769
 
777
- if ok:
778
- virt_vis[name] = vn2
779
-
780
770
  if ".hist" in abspath:
781
771
  p = abspath.replace("\\", "/") if WINDOWS else abspath
782
772
  if p.endswith("/.hist"):
@@ -1984,6 +1974,23 @@ class AuthSrv(object):
1984
1974
  umap[usr].sort()
1985
1975
  setattr(vfs, "a" + perm, umap)
1986
1976
 
1977
+ for vol in vfs.all_nodes.values():
1978
+ za = vol.axs
1979
+ vol.uaxs = {
1980
+ un: (
1981
+ un in za.uread,
1982
+ un in za.uwrite,
1983
+ un in za.umove,
1984
+ un in za.udel,
1985
+ un in za.uget,
1986
+ un in za.upget,
1987
+ un in za.uhtml,
1988
+ un in za.uadmin,
1989
+ un in za.udot,
1990
+ )
1991
+ for un in unames
1992
+ }
1993
+
1987
1994
  all_users = {}
1988
1995
  missing_users = {}
1989
1996
  associated_users = {}
@@ -2317,6 +2324,10 @@ class AuthSrv(object):
2317
2324
  free_umask = False
2318
2325
  have_reflink = False
2319
2326
  for vol in vfs.all_nodes.values():
2327
+ if os.path.isfile(vol.realpath):
2328
+ vol.flags["is_file"] = True
2329
+ vol.flags["d2d"] = True
2330
+
2320
2331
  if (self.args.e2ds and vol.axs.uwrite) or self.args.e2dsa:
2321
2332
  vol.flags["e2ds"] = True
2322
2333
 
@@ -2631,7 +2642,14 @@ class AuthSrv(object):
2631
2642
  errors = True
2632
2643
 
2633
2644
  for vol in vfs.all_nodes.values():
2634
- if not vol.realpath or os.path.isfile(vol.realpath):
2645
+ if not vol.flags.get("is_file"):
2646
+ continue
2647
+ zs = "og opds xlink"
2648
+ for zs in zs.split():
2649
+ vol.flags.pop(zs, None)
2650
+
2651
+ for vol in vfs.all_nodes.values():
2652
+ if not vol.realpath or vol.flags.get("is_file"):
2635
2653
  continue
2636
2654
  ccs = vol.flags["casechk"][:1].lower()
2637
2655
  if ccs in ("y", "n"):
@@ -3077,6 +3095,10 @@ class AuthSrv(object):
3077
3095
  for zs in zs.split():
3078
3096
  if vf.get(zs):
3079
3097
  js_htm[zs] = 1
3098
+ zs = "notooltips"
3099
+ for zs in zs.split():
3100
+ if getattr(self.args, zs, False):
3101
+ js_htm[zs] = 1
3080
3102
  vn.js_htm = json_hesc(json.dumps(js_htm))
3081
3103
 
3082
3104
  vols = list(vfs.all_nodes.values())
@@ -3421,7 +3443,7 @@ class AuthSrv(object):
3421
3443
  raise Exception("volume not found: " + zs)
3422
3444
 
3423
3445
  self.log(str({"users": users, "vols": vols, "flags": flags}))
3424
- t = "/{}: read({}) write({}) move({}) del({}) dots({}) get({}) upGet({}) uadmin({})"
3446
+ t = "/{}: read({}) write({}) move({}) del({}) dots({}) get({}) upGet({}) html({}) uadmin({})"
3425
3447
  for k, zv in self.vfs.all_vols.items():
3426
3448
  vc = zv.axs
3427
3449
  vs = [
copyparty/ftpd.py CHANGED
@@ -194,7 +194,7 @@ class FtpFs(AbstractedFS):
194
194
  if not avfs:
195
195
  raise FSE(t.format(vpath), 1)
196
196
 
197
- cr, cw, cm, cd, _, _, _, _ = avfs.can_access("", self.h.uname)
197
+ cr, cw, cm, cd, _, _, _, _, _ = avfs.uaxs[self.h.uname]
198
198
  if r and not cr or w and not cw or m and not cm or d and not cd:
199
199
  raise FSE(t.format(vpath), 1)
200
200
 
copyparty/httpcli.py CHANGED
@@ -30,7 +30,7 @@ try:
30
30
  except:
31
31
  pass
32
32
 
33
- from .__init__ import ANYWIN, RES, TYPE_CHECKING, EnvParams, unicode
33
+ from .__init__ import ANYWIN, RES, RESM, TYPE_CHECKING, EnvParams, unicode
34
34
  from .__version__ import S_VERSION
35
35
  from .authsrv import LEELOO_DALLAS, VFS # typechk
36
36
  from .bos import bos
@@ -161,6 +161,12 @@ RE_MDV = re.compile(r"(.*)\.([0-9]+\.[0-9]{3})(\.[Mm][Dd])$")
161
161
 
162
162
  UPARAM_CC_OK = set("doc move tree".split())
163
163
 
164
+ PERMS_rwh = [
165
+ [True, False],
166
+ [False, True],
167
+ [False, False, False, False, False, False, True],
168
+ ]
169
+
164
170
 
165
171
  class HttpCli(object):
166
172
  """
@@ -224,6 +230,7 @@ class HttpCli(object):
224
230
  self.can_delete = False
225
231
  self.can_get = False
226
232
  self.can_upget = False
233
+ self.can_html = False
227
234
  self.can_admin = False
228
235
  self.can_dot = False
229
236
  self.out_headerlist = []
@@ -732,18 +739,21 @@ class HttpCli(object):
732
739
  if "bcasechk" in vn.flags and not vn.casechk(rem, True):
733
740
  return self.tx_404() and False
734
741
 
735
- (
736
- self.can_read,
737
- self.can_write,
738
- self.can_move,
739
- self.can_delete,
740
- self.can_get,
741
- self.can_upget,
742
- self.can_admin,
743
- self.can_dot,
744
- ) = (
745
- avn.can_access("", self.uname) if avn else [False] * 8
746
- )
742
+ try:
743
+ (
744
+ self.can_read,
745
+ self.can_write,
746
+ self.can_move,
747
+ self.can_delete,
748
+ self.can_get,
749
+ self.can_upget,
750
+ self.can_html,
751
+ self.can_admin,
752
+ self.can_dot,
753
+ ) = avn.uaxs[self.uname]
754
+ except:
755
+ pass # default is all-false
756
+
747
757
  self.avn = avn
748
758
  self.vn = vn # note: do not dbv due to walk/zipgen
749
759
  self.rem = rem
@@ -1270,6 +1280,20 @@ class HttpCli(object):
1270
1280
  else:
1271
1281
  return self.tx_res(res_path)
1272
1282
 
1283
+ if res_path in RESM:
1284
+ ap = self.E.mod_ + RESM[res_path]
1285
+ if (
1286
+ "txt" not in self.uparam
1287
+ and "mime" not in self.uparam
1288
+ and not self.ouparam.get("dl")
1289
+ ):
1290
+ # return mimetype matching request extension
1291
+ self.ouparam["dl"] = res_path.split("/")[-1]
1292
+ if bos.path.exists(ap) or bos.path.exists(ap + ".gz"):
1293
+ return self.tx_file(ap)
1294
+ else:
1295
+ return self.tx_res(res_path)
1296
+
1273
1297
  self.tx_404()
1274
1298
  return False
1275
1299
 
@@ -4161,8 +4185,11 @@ class HttpCli(object):
4161
4185
  # force download
4162
4186
 
4163
4187
  if "dl" in self.ouparam:
4164
- cdis = gen_content_disposition(os.path.basename(req_path))
4165
- self.out_headers["Content-Disposition"] = cdis
4188
+ cdis = self.ouparam["dl"] or req_path
4189
+ zs = gen_content_disposition(os.path.basename(cdis))
4190
+ self.out_headers["Content-Disposition"] = zs
4191
+ else:
4192
+ cdis = req_path
4166
4193
 
4167
4194
  #
4168
4195
  # if-modified
@@ -4228,7 +4255,7 @@ class HttpCli(object):
4228
4255
  elif "mime" in self.uparam:
4229
4256
  mime = str(self.uparam.get("mime"))
4230
4257
  else:
4231
- mime = guess_mime(req_path)
4258
+ mime = guess_mime(cdis)
4232
4259
 
4233
4260
  logmsg += unicode(status) + logtail
4234
4261
 
@@ -4335,8 +4362,11 @@ class HttpCli(object):
4335
4362
  # force download
4336
4363
 
4337
4364
  if "dl" in self.ouparam:
4338
- cdis = gen_content_disposition(os.path.basename(req_path))
4339
- self.out_headers["Content-Disposition"] = cdis
4365
+ cdis = self.ouparam["dl"] or req_path
4366
+ zs = gen_content_disposition(os.path.basename(cdis))
4367
+ self.out_headers["Content-Disposition"] = zs
4368
+ else:
4369
+ cdis = req_path
4340
4370
 
4341
4371
  #
4342
4372
  # if-modified
@@ -4464,7 +4494,7 @@ class HttpCli(object):
4464
4494
  elif "rmagic" in self.vn.flags:
4465
4495
  mime = guess_mime(req_path, fs_path)
4466
4496
  else:
4467
- mime = guess_mime(req_path)
4497
+ mime = guess_mime(cdis)
4468
4498
 
4469
4499
  if "nohtml" in self.vn.flags and "html" in mime:
4470
4500
  mime = "text/plain; charset=utf-8"
@@ -5238,7 +5268,7 @@ class HttpCli(object):
5238
5268
  dls.append((perc, hsent, spd, eta, idle, usr, erd, rds, fn))
5239
5269
 
5240
5270
  if self.args.have_unlistc:
5241
- allvols = self.asrv.vfs.all_vols
5271
+ allvols = self.asrv.vfs.all_nodes
5242
5272
  rvol = [x for x in rvol if "unlistcr" not in allvols[x[1:-1]].flags]
5243
5273
  wvol = [x for x in wvol if "unlistcw" not in allvols[x[1:-1]].flags]
5244
5274
 
@@ -5517,7 +5547,7 @@ class HttpCli(object):
5517
5547
  rem,
5518
5548
  self.uname,
5519
5549
  not self.args.no_scandir,
5520
- [[True, False], [False, True]],
5550
+ PERMS_rwh,
5521
5551
  )
5522
5552
  dots = self.uname in vn.axs.udot
5523
5553
  dk_sz = vn.flags.get("dk")
@@ -5549,7 +5579,13 @@ class HttpCli(object):
5549
5579
  for x in vfs_virt:
5550
5580
  if x != excl:
5551
5581
  try:
5552
- dvn, drem = vfs.get(vjoin(top, x), self.uname, True, False)
5582
+ dvn, drem = vfs.get(vjoin(top, x), self.uname, False, False)
5583
+ if (
5584
+ self.uname not in dvn.axs.uread
5585
+ and self.uname not in dvn.axs.uwrite
5586
+ and self.uname not in dvn.axs.uhtml
5587
+ ):
5588
+ raise Exception()
5553
5589
  bos.stat(dvn.canonical(drem, False))
5554
5590
  except:
5555
5591
  x += "\n"
@@ -6474,8 +6510,7 @@ class HttpCli(object):
6474
6510
  return self.tx_svg("upload\nonly")
6475
6511
 
6476
6512
  if not self.can_read and self.can_get and self.avn:
6477
- axs = self.avn.axs
6478
- if self.uname not in axs.uhtml:
6513
+ if not self.can_html:
6479
6514
  pass
6480
6515
  elif is_dir:
6481
6516
  for fn in ("index.htm", "index.html"):
@@ -6496,6 +6531,7 @@ class HttpCli(object):
6496
6531
 
6497
6532
  fk_pass = True
6498
6533
  is_dir = False
6534
+ add_og = False
6499
6535
  rem = vjoin(rem, fn)
6500
6536
  vrem = vjoin(vrem, fn)
6501
6537
  abspath = ap2
@@ -6689,7 +6725,7 @@ class HttpCli(object):
6689
6725
  rem,
6690
6726
  self.uname,
6691
6727
  not self.args.no_scandir,
6692
- [[True, False], [False, True]],
6728
+ PERMS_rwh,
6693
6729
  lstat="lt" in self.uparam,
6694
6730
  throw=True,
6695
6731
  )
copyparty/httpsrv.py CHANGED
@@ -378,8 +378,8 @@ class HttpSrv(object):
378
378
  if nloris < nconn / 2:
379
379
  continue
380
380
 
381
- t = "slowloris (idle-conn): {} banned for {} min"
382
- self.log(self.name, t.format(ip, self.args.loris, nclose), 1)
381
+ t = "slow%s (idle-conn): %s banned for %d min" # slowloris
382
+ self.log(self.name, t % ("loris", ip, self.args.loris), 1)
383
383
  self.bans[ip] = int(time.time() + self.args.loris * 60)
384
384
 
385
385
  if self.args.log_conn:
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
copyparty/web/md.css.gz CHANGED
Binary file
copyparty/web/md.js.gz CHANGED
Binary file
copyparty/web/md2.css.gz CHANGED
Binary file
copyparty/web/md2.js.gz CHANGED
Binary file
copyparty/web/mde.css.gz CHANGED
Binary file
copyparty/web/mde.js.gz CHANGED
Binary file
copyparty/web/msg.css.gz CHANGED
Binary file
copyparty/web/rups.css.gz CHANGED
Binary file
copyparty/web/rups.js.gz CHANGED
Binary file
Binary file
Binary file
Binary file
Binary file
copyparty/web/svcs.html CHANGED
@@ -64,7 +64,7 @@
64
64
  <li>old version of rclone? replace all <code>=</code> with <code>&nbsp;</code> (space)</li>
65
65
  </ul>
66
66
 
67
- <p>if you want to use the native WebDAV client in windows instead (slow and buggy), first run <a href="{{ r }}/.cpr/a/webdav-cfg.bat">webdav-cfg.bat</a> to remove the 47 MiB filesize limit (also fixes latency and password login), then connect:</p>
67
+ <p>if you want to use the native WebDAV client in windows instead (slow and buggy), first run <a href="{{ r }}/.cpr/a/webdav-cfg.txt?dl=webdav-cfg.bat">webdav-cfg.bat</a> to remove the 47 MiB filesize limit (also fixes latency and password login), then connect:</p>
68
68
  <pre>
69
69
  {%- if un %}
70
70
  net use <b>w:</b> http{{ s }}://{{ ep }}/{{ rvp }}{% if accs %} <b>{{ pw }}</b> /user:{{ b_un }}{% endif %}
copyparty/web/svcs.js.gz CHANGED
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
copyparty/web/ui.css.gz CHANGED
Binary file
copyparty/web/up2k.js.gz CHANGED
Binary file
copyparty/web/util.js.gz CHANGED
Binary file
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copyparty
3
- Version: 1.19.17
3
+ Version: 1.19.19
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
@@ -86,6 +86,7 @@ made in Norway 🇳🇴
86
86
 
87
87
  * top
88
88
  * [quickstart](#quickstart) - just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
89
+ * [mirrors](#mirrors) - other places to download copyparty from
89
90
  * [at home](#at-home) - make it accessible over the internet
90
91
  * [on servers](#on-servers) - you may also want these, especially on servers
91
92
  * [features](#features) - also see [comparison to similar software](./docs/versus.md)
@@ -222,7 +223,7 @@ just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/
222
223
 
223
224
  * or install through [pypi](https://pypi.org/project/copyparty/): `python3 -m pip install --user -U copyparty`
224
225
  * or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
225
- * or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
226
+ * or install [on arch](#arch-package) / [homebrew](#homebrew-formulae) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
226
227
  * or if you are on android, [install copyparty in termux](#install-on-android)
227
228
  * or maybe an iPhone or iPad? [install in a-Shell on iOS](#install-on-iOS)
228
229
  * or maybe you have a [synology nas / dsm](./docs/synology-dsm.md)
@@ -260,6 +261,18 @@ some recommended options:
260
261
  * see [accounts and volumes](#accounts-and-volumes) (or `--help-accounts`) for the syntax and other permissions
261
262
 
262
263
 
264
+ ### mirrors
265
+
266
+ other places to download copyparty from (non-github links):
267
+
268
+ * https://copyparty.eu/ (hetzner, finland, official mirror):
269
+ * https://copyparty.eu/py = https://copyparty.eu/copyparty-sfx.py = the sfx
270
+ * https://copyparty.eu/en = https://copyparty.eu/copyparty-en.py = the english-only sfx
271
+ * https://copyparty.eu/pyz = https://copyparty.eu/copyparty.pyz = the zipapp
272
+ * https://copyparty.eu/enz = https://copyparty.eu/copyparty-en.pyz = the enterprise pyz
273
+ * https://copyparty.eu/cli = online cli helptext
274
+
275
+
263
276
  ### at home
264
277
 
265
278
  make it accessible over the internet by starting a [cloudflare quicktunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/) like so:
@@ -3131,7 +3144,8 @@ another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty
3131
3144
  run it by doubleclicking it, or try typing `python copyparty.pyz` in your terminal/console/commandline/telex if that fails
3132
3145
 
3133
3146
  it is a python [zipapp](https://docs.python.org/3/library/zipapp.html) meaning it doesn't have to unpack its own python code anywhere to run, so if the filesystem is busted it has a better chance of getting somewhere
3134
- * but note that it currently still needs to extract the web-resources somewhere (they'll land in the default TEMP-folder of your OS)
3147
+
3148
+ > there is also [copyparty-en.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty-en.pyz), english-only and without smb support (enterprise-friendly)
3135
3149
 
3136
3150
 
3137
3151
  # install on android