From a27edc7b006c1ed36730f1b984f3799da2a1ed8d Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 28 Jul 2025 17:15:19 +0000 Subject: [PATCH 01/24] chpw ratelimit --- copyparty/__main__.py | 1 + copyparty/httpcli.py | 1 + copyparty/httpsrv.py | 1 + copyparty/svchub.py | 1 + tests/util.py | 2 +- 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index be5c3b98..c86f6c30 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1336,6 +1336,7 @@ def add_safety(ap): ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything (volflag=norobots)") ap2.add_argument("--logout", metavar="H", type=float, default=8086.0, help="logout clients after \033[33mH\033[0m hours of inactivity; [\033[32m0.0028\033[0m]=10sec, [\033[32m0.1\033[0m]=6min, [\033[32m24\033[0m]=day, [\033[32m168\033[0m]=week, [\033[32m720\033[0m]=month, [\033[32m8760\033[0m]=year)") ap2.add_argument("--ban-pw", metavar="N,W,B", type=u, default="9,60,1440", help="more than \033[33mN\033[0m wrong passwords in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; disable with [\033[32mno\033[0m]") + ap2.add_argument("--ban-pwc", metavar="N,W,B", type=u, default="5,60,1440", help="more than \033[33mN\033[0m password-changes in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; disable with [\033[32mno\033[0m]") ap2.add_argument("--ban-404", metavar="N,W,B", type=u, default="50,60,1440", help="hitting more than \033[33mN\033[0m 404's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; only affects users who cannot see directory listings because their access is either g/G/h") ap2.add_argument("--ban-403", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m 403's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; [\033[32m1440\033[0m]=day, [\033[32m10080\033[0m]=week, [\033[32m43200\033[0m]=month") ap2.add_argument("--ban-422", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m 422's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes (invalid requests, attempted exploits ++)") diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 9d12facb..02e56a22 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -2912,6 +2912,7 @@ class HttpCli(object): ok, msg = self.asrv.chpw(self.conn.hsrv.broker, self.uname, pwd) if ok: + self.cbonk(self.conn.hsrv.gpwc, pwd, "pw", "too many password changes") ok, msg = self.get_pwd_cookie(pwd) if ok: msg = "new password OK" diff --git a/copyparty/httpsrv.py b/copyparty/httpsrv.py index 2e5cc69e..56638003 100644 --- a/copyparty/httpsrv.py +++ b/copyparty/httpsrv.py @@ -123,6 +123,7 @@ class HttpSrv(object): self.nm = NetMap([], []) self.ssdp: Optional["SSDPr"] = None self.gpwd = Garda(self.args.ban_pw) + self.gpwc = Garda(self.args.ban_pwc) self.g404 = Garda(self.args.ban_404) self.g403 = Garda(self.args.ban_403) self.g422 = Garda(self.args.ban_422, False) diff --git a/copyparty/svchub.py b/copyparty/svchub.py index e7c293cc..751fd566 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -168,6 +168,7 @@ class SvcHub(object): # for non-http clients (ftp, tftp) self.bans: dict[str, int] = {} self.gpwd = Garda(self.args.ban_pw) + self.gpwc = Garda(self.args.ban_pwc) self.g404 = Garda(self.args.ban_404) self.g403 = Garda(self.args.ban_403) self.g422 = Garda(self.args.ban_422, False) diff --git a/tests/util.py b/tests/util.py index 147c3b64..1f732920 100644 --- a/tests/util.py +++ b/tests/util.py @@ -164,7 +164,7 @@ class Cfg(Namespace): ex = "ah_alg bname chmod_f chpw_db doctitle df exit favico idp_h_usr ipa html_head lg_sba lg_sbf log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i shr tcolor textfiles unlist vname xff_src zipmaxt R RS SR" ka.update(**{k: "" for k in ex.split()}) - ex = "ban_403 ban_404 ban_422 ban_pw ban_url spinner" + ex = "ban_403 ban_404 ban_422 ban_pw ban_pwc ban_url spinner" ka.update(**{k: "no" for k in ex.split()}) ex = "ext_th grp on403 on404 xac xad xar xau xban xbc xbd xbr xbu xiu xm" From 2484df274349967d838bad3ca3f458c169c81018 Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 28 Jul 2025 17:28:51 +0000 Subject: [PATCH 02/24] make nginx example less confusing --- contrib/nginx/copyparty.conf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/nginx/copyparty.conf b/contrib/nginx/copyparty.conf index 3c59a6f5..121e52ab 100644 --- a/contrib/nginx/copyparty.conf +++ b/contrib/nginx/copyparty.conf @@ -85,13 +85,13 @@ server { proxy_buffer_size 16k; proxy_busy_buffers_size 24k; + proxy_set_header Connection "Keep-Alive"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - # NOTE: with cloudflare you want this instead: - #proxy_set_header X-Forwarded-For $http_cf_connecting_ip; proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Connection "Keep-Alive"; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # NOTE: with cloudflare you want this X-Forwarded-For instead: + #proxy_set_header X-Forwarded-For $http_cf_connecting_ip; } } From dab71310666b82fe809c3d666158dfd938871b1c Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 28 Jul 2025 19:41:03 +0000 Subject: [PATCH 03/24] add reflink-based dedup; closes #201 --- README.md | 7 ++++++- copyparty/__main__.py | 1 + copyparty/authsrv.py | 11 +++++++++++ copyparty/cfg.py | 2 ++ copyparty/up2k.py | 5 ++++- tests/util.py | 2 +- 6 files changed, 25 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 48c5f6fa..f106ef80 100644 --- a/README.md +++ b/README.md @@ -1439,12 +1439,17 @@ if you enable deduplication with `--dedup` then it'll create a symlink instead o **warning:** when enabling dedup, you should also: * enable indexing with `-e2dsa` or volflag `e2dsa` (see [file indexing](#file-indexing) section below); strongly recommended * ...and/or `--hardlink-only` to use hardlink-based deduplication instead of symlinks; see explanation below +* ...and/or `--reflink` to use CoW/reflink-based dedup (much safer than hardlink, but OS/FS-dependent) it will not be safe to rename/delete files if you only enable dedup and none of the above; if you enable indexing then it is not *necessary* to also do hardlinks (but you may still want to) by default, deduplication is done based on symlinks (symbolic links); these are tiny files which are pointers to the nearest full copy of the file -you can choose to use hardlinks instead of softlinks, globally with `--hardlink-only` or volflag `hardlinkonly`; +you can choose to use hardlinks instead of softlinks, globally with `--hardlink-only` or volflag `hardlinkonly`, and you can choose to use reflinks with `--reflink` or volflag `reflink` + +advantages of using reflinks (CoW, copy-on-write): +* entirely safe (when your filesystem supports it correctly); either file can be edited or deleted without affecting other copies +* only linux 5.3 or newer, only python 3.14 or newer, only some filesystems (btrfs probably ok, maybe xfs too, but zfs had bugs) advantages of using hardlinks: * hardlinks are more compatible with other software; they behave entirely like regular files diff --git a/copyparty/__main__.py b/copyparty/__main__.py index c86f6c30..8aef06df 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1056,6 +1056,7 @@ def add_upload(ap): ap2.add_argument("--safe-dedup", metavar="N", type=int, default=50, help="how careful to be when deduplicating files; [\033[32m1\033[0m] = just verify the filesize, [\033[32m50\033[0m] = verify file contents have not been altered (volflag=safededup)") ap2.add_argument("--hardlink", action="store_true", help="enable hardlink-based dedup; will fallback on symlinks when that is impossible (across filesystems) (volflag=hardlink)") ap2.add_argument("--hardlink-only", action="store_true", help="do not fallback to symlinks when a hardlink cannot be made (volflag=hardlinkonly)") + ap2.add_argument("--reflink", action="store_true", help="enable reflink-based dedup; will fallback on full copies when that is impossible (non-CoW filesystem) (volflag=reflink)") ap2.add_argument("--no-dupe", action="store_true", help="reject duplicate files during upload; only matches within the same volume (volflag=nodupe)") ap2.add_argument("--no-clone", action="store_true", help="do not use existing data on disk to satisfy dupe uploads; reduces server HDD reads in exchange for much more network load (volflag=noclone)") ap2.add_argument("--no-snap", action="store_true", help="disable snapshots -- forget unfinished uploads on shutdown; don't create .hist/up2k.snap files -- abandoned/interrupted uploads must be cleaned up manually") diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index 7feedc64..bdc65640 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -2124,6 +2124,7 @@ class AuthSrv(object): all_mte = {} errors = False free_umask = False + have_reflink = False for vol in vfs.all_nodes.values(): if (self.args.e2ds and vol.axs.uwrite) or self.args.e2dsa: vol.flags["e2ds"] = True @@ -2207,6 +2208,9 @@ class AuthSrv(object): if "unlistcr" in vol.flags or "unlistcw" in vol.flags: self.args.have_unlistc = True + if "reflink" in vol.flags: + have_reflink = True + zs = str(vol.flags.get("tcolor", "")).lstrip("#") if len(zs) == 3: # fc5 => ffcc55 vol.flags["tcolor"] = "".join([x * 2 for x in zs]) @@ -2571,6 +2575,13 @@ class AuthSrv(object): t = "WARNING! The following IdP volumes are mounted below another volume where other users can read and/or write files. This is a SECURITY HAZARD!! When copyparty is restarted, it will not know about these IdP volumes yet. These volumes will then be accessible by an unexpected set of permissions UNTIL one of the users associated with their volume sends a request to the server. RECOMMENDATION: You should create a restricted volume where nobody can read/write files, and make sure that all IdP volumes are configured to appear somewhere below that volume." self.log(t + "".join(self.idp_err), 1) + if have_reflink: + t = "WARNING: Reflink-based dedup was requested, but %s. This will not work; files will be full copies instead." + if sys.version_info < (3, 14): + self.log(t % "your python version is not new enough", 1) + if not sys.platform.startswith("linux"): + self.log(t % "your OS is not Linux", 1) + self.vfs = vfs self.acct = acct self.defpw = defpw diff --git a/copyparty/cfg.py b/copyparty/cfg.py index 640259ed..cee8214b 100644 --- a/copyparty/cfg.py +++ b/copyparty/cfg.py @@ -52,6 +52,7 @@ def vf_bmap() -> dict[str, str]: "og_no_head", "og_s_title", "rand", + "reflink", "rmagic", "rss", "wo_up_readme", @@ -168,6 +169,7 @@ flagcats = { "dedup": "enable symlink-based file deduplication", "hardlink": "enable hardlink-based file deduplication,\nwith fallback on symlinks when that is impossible", "hardlinkonly": "dedup with hardlink only, never symlink;\nmake a full copy if hardlink is impossible", + "reflink": "enable reflink-based file deduplication,\nwith fallback on full copy when that is impossible", "safededup": "verify on-disk data before using it for dedup", "noclone": "take dupe data from clients, even if available on HDD", "nodupe": "rejects existing files (instead of linking/cloning them)", diff --git a/copyparty/up2k.py b/copyparty/up2k.py index 42b44f79..907347f5 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -3476,6 +3476,8 @@ class Up2k(object): linked = False try: + if "reflink" in flags: + raise Exception("reflink") if not is_mv and not flags.get("dedup"): raise Exception("dedup is disabled in config") @@ -3532,7 +3534,8 @@ class Up2k(object): linked = True except Exception as ex: - self.log("cannot link; creating copy: " + repr(ex)) + if str(ex) != "reflink": + self.log("cannot link; creating copy: " + repr(ex)) if bos.path.isfile(src): csrc = src elif fsrc and bos.path.isfile(fsrc): diff --git a/tests/util.py b/tests/util.py index 1f732920..17c1d06d 100644 --- a/tests/util.py +++ b/tests/util.py @@ -143,7 +143,7 @@ class Cfg(Namespace): def __init__(self, a=None, v=None, c=None, **ka0): ka = {} - ex = "chpw cookie_lax daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only ih ihead magic nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tail no_tarcmp no_thumb no_vthumb no_zip nrand nsort nw og og_no_head og_s_title ohead q rand re_dirsz rmagic rss smb srch_dbg srch_excl stats uqe vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs" + ex = "chpw cookie_lax daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only ih ihead magic nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tail no_tarcmp no_thumb no_vthumb no_zip nrand nsort nw og og_no_head og_s_title ohead q rand re_dirsz reflink rmagic rss smb srch_dbg srch_excl stats uqe vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs" ka.update(**{k: False for k in ex.split()}) ex = "dav_inf dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump re_dhash see_dots plain_ip" From cb15eaf0d222bb1b5137e44a5deea658986b3515 Mon Sep 17 00:00:00 2001 From: ptweezy Date: Sun, 27 Jul 2025 16:21:34 -0400 Subject: [PATCH 04/24] Update docker-compose.yml The version attribute is deprecated, resolves error "the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion" when building with Docker Signed-off-by: ptweezy --- docs/examples/docker/basic-docker-compose/docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/examples/docker/basic-docker-compose/docker-compose.yml b/docs/examples/docker/basic-docker-compose/docker-compose.yml index ba5e74d8..cfac59b6 100644 --- a/docs/examples/docker/basic-docker-compose/docker-compose.yml +++ b/docs/examples/docker/basic-docker-compose/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3' services: copyparty: From 48a53106c608b7541d8561837ae75d5b87ff9a16 Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 28 Jul 2025 20:29:40 +0000 Subject: [PATCH 05/24] standardize on /dev/shm/party.sock; closes #229 --- README.md | 2 +- copyparty/__main__.py | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f106ef80..eee9ec9c 100644 --- a/README.md +++ b/README.md @@ -2027,7 +2027,7 @@ some reverse proxies (such as [Caddy](https://caddyserver.com/)) can automatical * **warning:** nginx-QUIC (HTTP/3) is still experimental and can make uploads much slower, so HTTP/1.1 is recommended for now * depending on server/client, HTTP/1.1 can also be 5x faster than HTTP/2 -for improved security (and a 10% performance boost) consider listening on a unix-socket with `-i unix:770:www:/tmp/party.sock` (permission `770` means only members of group `www` can access it) +for improved security (and a 10% performance boost) consider listening on a unix-socket with `-i unix:770:www:/dev/shm/party.sock` (permission `770` means only members of group `www` can access it) example webserver / reverse-proxy configs: diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 8aef06df..03e01883 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -547,14 +547,15 @@ def get_sects(): when running behind a reverse-proxy, it's recommended to use unix-sockets for improved performance and security; - \033[32m-i unix:770:www:\033[33m/tmp/a.sock\033[0m listens on \033[33m/tmp/a.sock\033[0m with - permissions \033[33m0770\033[0m; only accessible to members of the \033[33mwww\033[0m - group. This is the best approach. Alternatively, + \033[32m-i unix:770:www:\033[33m/dev/shm/party.sock\033[0m listens on + \033[33m/dev/shm/party.sock\033[0m with permissions \033[33m0770\033[0m; + only accessible to members of the \033[33mwww\033[0m group. + This is the best approach. Alternatively, - \033[32m-i unix:777:\033[33m/tmp/a.sock\033[0m sets perms \033[33m0777\033[0m so anyone can - access it; bad unless it's inside a restricted folder + \033[32m-i unix:777:\033[33m/dev/shm/party.sock\033[0m sets perms \033[33m0777\033[0m so anyone + can access it; bad unless it's inside a restricted folder - \033[32m-i unix:\033[33m/tmp/a.sock\033[0m keeps umask-defined permissions + \033[32m-i unix:\033[33m/dev/shm/party.sock\033[0m keeps umask-defined permission (usually \033[33m0600\033[0m) and the same user/group as copyparty \033[33m-p\033[0m (tcp ports) is ignored for unix sockets From f4b30c7f541e3087b252dc231a75853040ddc3f8 Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 28 Jul 2025 20:43:34 +0000 Subject: [PATCH 06/24] improve chmod helptext --- copyparty/__main__.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 03e01883..dcfa2138 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -873,31 +873,31 @@ def get_sects(): similarly, \033[33m--chmod-d\033[0m and \033[33mchmod_d\033[0m sets the directory/folder perm - the value is a three-digit octal number such as 755, 750, 644, etc. + the value is a three-digit octal number such as \033[32m755\033[0m, \033[32m750\033[0m, \033[32m644\033[0m, etc. first digit = "User"; permission for the unix-user second digit = "Group"; permission for the unix-group third digit = "Other"; permission for all other users/groups for files: - 0 = --- = no access - 1 = --x = can execute the file as a program - 2 = -w- = can write - 3 = -wx = can write and execute - 4 = r-- = can read - 5 = r-x = can read and execute - 6 = rw- = can read and write - 7 = rwx = can read, write, execute + \033[32m0\033[0m = \033[35m---\033[0m = no access + \033[32m1\033[0m = \033[35m--x\033[0m = can execute the file as a program + \033[32m2\033[0m = \033[35m-w-\033[0m = can write + \033[32m3\033[0m = \033[35m-wx\033[0m = can write and execute + \033[32m4\033[0m = \033[35mr--\033[0m = can read + \033[32m5\033[0m = \033[35mr-x\033[0m = can read and execute + \033[32m6\033[0m = \033[35mrw-\033[0m = can read and write + \033[32m7\033[0m = \033[35mrwx\033[0m = can read, write, execute for directories/folders: - 0 = --- = no access - 1 = --x = can read files in folder but not list contents - 2 = -w- = n/a - 3 = -wx = can create files but not list - 4 = r-- = can list, but not read/write - 5 = r-x = can list and read files - 6 = rw- = n/a - 7 = rwx = can read, write, list + \033[32m0\033[0m = \033[35m---\033[0m = no access + \033[32m1\033[0m = \033[35m--x\033[0m = can read files in folder but not list contents + \033[32m2\033[0m = \033[35m-w-\033[0m = n/a + \033[32m3\033[0m = \033[35m-wx\033[0m = can create files but not list + \033[32m4\033[0m = \033[35mr--\033[0m = can list, but not read/write + \033[32m5\033[0m = \033[35mr-x\033[0m = can list and read files + \033[32m6\033[0m = \033[35mrw-\033[0m = n/a + \033[32m7\033[0m = \033[35mrwx\033[0m = can read, write, list """ ), ], From 492c772bbb8648f73148d37e974711c2c8f0ff6c Mon Sep 17 00:00:00 2001 From: AppleTheGolden Date: Mon, 28 Jul 2025 23:42:23 +0200 Subject: [PATCH 07/24] cbz thumbnails: sort alphabetically Comic readers will sort alphabetically, but that isn't always the order in which the files are stored in the zip. --- copyparty/mtag.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/copyparty/mtag.py b/copyparty/mtag.py index a972ef33..cacfda39 100644 --- a/copyparty/mtag.py +++ b/copyparty/mtag.py @@ -166,12 +166,13 @@ def au_unpk( znil = [x for x in znil if "cover" in x[0]] or znil znil = [x for x in znil if CBZ_01.search(x[0])] or znil t = "cbz: %d files, %d hits" % (nf, len(znil)) + using = sorted(znil)[0][1].filename if znil: - t += ", using " + znil[0][1].filename + t += ", using " + using log(t) if not znil: raise Exception("no images inside cbz") - fi = zf.open(znil[0][1]) + fi = zf.open(using) else: raise Exception("unknown compression %s" % (pk,)) From 3bcc30c769e5771c6bf06b6592dcfd1dbc4ad4b3 Mon Sep 17 00:00:00 2001 From: Adam <134429563+RustoMCSpit@users.noreply.github.com> Date: Mon, 28 Jul 2025 23:19:01 +0100 Subject: [PATCH 08/24] add demo video link (#190) * add feature showcase video Signed-off-by: Adam <134429563+RustoMCSpit@users.noreply.github.com> * add youtube link too Signed-off-by: ed --------- Signed-off-by: Adam <134429563+RustoMCSpit@users.noreply.github.com> Signed-off-by: ed Co-authored-by: ed --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eee9ec9c..50f944af 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ turn almost any device into a file server with resumable uploads/downloads using ๐Ÿ“ท **screenshots:** [browser](#the-browser) // [upload](#uploading) // [unpost](#unpost) // [thumbnails](#thumbnails) // [search](#searching) // [fsearch](#file-search) // [zip-DL](#zip-downloads) // [md-viewer](#markdown-viewer) -๐ŸŽฌ **videos:** [upload](https://a.ocv.me/pub/demo/pics-vids/up2k.webm) // [cli-upload](https://a.ocv.me/pub/demo/pics-vids/u2cli.webm) // [race-the-beam](https://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm) +๐ŸŽฌ **videos:** [upload](https://a.ocv.me/pub/demo/pics-vids/up2k.webm) // [cli-upload](https://a.ocv.me/pub/demo/pics-vids/u2cli.webm) // [race-the-beam](https://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm) // ๐Ÿ‘‰ **[feature-showcase](https://a.ocv.me/pub/demo/showcase-hq.mp4)** ([youtube](https://www.youtube.com/watch?v=15_-hgsX2V0)) made in Norway ๐Ÿ‡ณ๐Ÿ‡ด From 9be7420f11c1cc912ba63126ca15b94f60d3dac4 Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 28 Jul 2025 22:20:50 +0000 Subject: [PATCH 09/24] wait lol --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 50f944af..c6f766f6 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ turn almost any device into a file server with resumable uploads/downloads using ๐Ÿ“ท **screenshots:** [browser](#the-browser) // [upload](#uploading) // [unpost](#unpost) // [thumbnails](#thumbnails) // [search](#searching) // [fsearch](#file-search) // [zip-DL](#zip-downloads) // [md-viewer](#markdown-viewer) -๐ŸŽฌ **videos:** [upload](https://a.ocv.me/pub/demo/pics-vids/up2k.webm) // [cli-upload](https://a.ocv.me/pub/demo/pics-vids/u2cli.webm) // [race-the-beam](https://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm) // ๐Ÿ‘‰ **[feature-showcase](https://a.ocv.me/pub/demo/showcase-hq.mp4)** ([youtube](https://www.youtube.com/watch?v=15_-hgsX2V0)) +๐ŸŽฌ **videos:** [upload](https://a.ocv.me/pub/demo/pics-vids/up2k.webm) // [cli-upload](https://a.ocv.me/pub/demo/pics-vids/u2cli.webm) // [race-the-beam](https://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm) // ๐Ÿ‘‰ **[feature-showcase](https://a.ocv.me/pub/demo/showcase-hq.webm)** ([youtube](https://www.youtube.com/watch?v=15_-hgsX2V0)) made in Norway ๐Ÿ‡ณ๐Ÿ‡ด From 4e02daee90d21d17292353db1e340f536635c437 Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 28 Jul 2025 22:24:14 +0000 Subject: [PATCH 10/24] fix helptext typo; closes #244 --- copyparty/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index dcfa2138..f6c20b8c 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1569,7 +1569,7 @@ def add_ui(ap, retry): ap2.add_argument("--txt-max", metavar="KiB", type=int, default=64, help="max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS)") ap2.add_argument("--doctitle", metavar="TXT", type=u, default="copyparty @ --name", help="title / service-name to show in html documents") ap2.add_argument("--bname", metavar="TXT", type=u, default="--name", help="server name (displayed in filebrowser document title)") - ap2.add_argument("--pb-url", metavar="URL", type=u, default=URL_PRJ, help="powered-by link; disable with \033[33m-np\033[0m") + ap2.add_argument("--pb-url", metavar="URL", type=u, default=URL_PRJ, help="powered-by link; disable with \033[33m-nb\033[0m") ap2.add_argument("--ver", action="store_true", help="show version on the control panel (incompatible with \033[33m-nb\033[0m)") ap2.add_argument("--k304", metavar="NUM", type=int, default=0, help="configure the option to enable/disable k304 on the controlpanel (workaround for buggy reverse-proxies); [\033[32m0\033[0m] = hidden and default-off, [\033[32m1\033[0m] = visible and default-off, [\033[32m2\033[0m] = visible and default-on") ap2.add_argument("--no304", metavar="NUM", type=int, default=0, help="configure the option to enable/disable no304 on the controlpanel (workaround for buggy caching in browsers); [\033[32m0\033[0m] = hidden and default-off, [\033[32m1\033[0m] = visible and default-off, [\033[32m2\033[0m] = visible and default-on") From 205755d55f8ba5566b7b0b125677516f17dcd711 Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 28 Jul 2025 22:36:05 +0000 Subject: [PATCH 11/24] readme: fedora package is happening --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c6f766f6..05661ed4 100644 --- a/README.md +++ b/README.md @@ -2248,7 +2248,7 @@ NOTE: there used to be an aur package; this evaporated when copyparty was adopte ## fedora package -does not exist yet; using the [copr-pypi](https://copr.fedorainfracloud.org/coprs/g/copr/PyPI/) builds is **NOT recommended** because updates can be delayed by [several months](https://github.com/fedora-copr/copr/issues/3056) +does not exist yet; there are rumours that it is being packaged! keep an eye on this space... ## nix package From deeb1ff091d698994ca088dba460c3420ce73d6e Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 28 Jul 2025 23:08:41 +0000 Subject: [PATCH 12/24] nosub should prevent mkdir --- copyparty/httpcli.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 02e56a22..2976a7c1 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -3026,6 +3026,9 @@ class HttpCli(object): self.gctx = vpath vpath = undot(vpath) vfs, rem = self.asrv.vfs.get(vpath, self.uname, False, True) + if "nosub" in vfs.flags: + raise Pebkac(403, "mkdir is forbidden below this folder") + rem = sanitize_vpath(rem, "/") fn = vfs.canonical(rem) From 3b5eff24a8f973dc9fd99b2bdc5ce1f8d4af3673 Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 28 Jul 2025 23:20:07 +0000 Subject: [PATCH 13/24] v1.18.6 --- copyparty/__version__.py | 2 +- copyparty/web/svcs.js | 2 +- docs/changelog.md | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/copyparty/__version__.py b/copyparty/__version__.py index 43b182e8..3cbb3b4a 100644 --- a/copyparty/__version__.py +++ b/copyparty/__version__.py @@ -1,6 +1,6 @@ # coding: utf-8 -VERSION = (1, 18, 5) +VERSION = (1, 18, 6) CODENAME = "logtail" BUILD_DT = (2025, 7, 28) diff --git a/copyparty/web/svcs.js b/copyparty/web/svcs.js index dc9b07dc..0de94523 100644 --- a/copyparty/web/svcs.js +++ b/copyparty/web/svcs.js @@ -49,7 +49,7 @@ function setos(os) { setos(WINDOWS ? 'win' : LINUX ? 'lin' : MACOS ? 'mac' : 'idk'); -function setpw() { +function setpw(e) { ev(e); modal.prompt('password:', '', function (v) { if (!v) diff --git a/docs/changelog.md b/docs/changelog.md index 175bc791..7a9571f1 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,39 @@ +โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€ +# 2025-0727-2305 `v1.18.5` SECURITY: fix XSS in media tags + +## โš ๏ธ ATTN: this release fixes an XSS vulnerability + +[GHSA-9q4r-x2hj-jmvr](https://github.com/9001/copyparty/security/advisories/GHSA-9q4r-x2hj-jmvr), exploitable in two different ways, could let an attacker execute arbitrary javascript on other users: +* either: tricking someone into clicking a malicious URL to load and execute javascript +* or: uploading a malicious audio file to the server, affecting any successive visitors + +so, with new and curious eyes on the project, we are starting off with a bang. Huge thanks to @altperfect for finding and reporting this earlier today. + +## recent important news + +* [v1.18.5 (2025-07-28)](https://github.com/9001/copyparty/releases/tag/v1.18.5) fixed XSS in display of media tags +* [v1.15.0 (2024-09-08)](https://github.com/9001/copyparty/releases/tag/v1.15.0) changed upload deduplication to be default-disabled +* [v1.14.3 (2024-08-30)](https://github.com/9001/copyparty/releases/tag/v1.14.3) fixed a bug that was introduced in v1.13.8 (2024-08-13); this bug could lead to **data loss** -- see the v1.14.3 release-notes for details + +## ๐Ÿงช new features + +* #214 option to stop playback after one song, and/or at end of folder 6bb27e60 + +## ๐Ÿฉน bugfixes + +* GHSA-9q4r-x2hj-jmvr 895880ae +* block external m3u files 2228f81f +* #202 the connect-page could show IP-address when it should have used hostnames/domains b0dec83a +* scrolling locked after tailing a file and closing it creatively d197e754 + +## ๐Ÿ”ง other changes + +* #189 the `SameSite` cookie parameter now defaults to `Strict`, increasing CSRF protection ca6d0b8d + * new option `--cookie-lax` reverts to previous value `Lax` +* docker: add FTPS support b4199847 + + + โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€ # 2025-0725-1841 `v1.18.4` Landmarks From 37e355b401839746762aac23dc17b00e11fbb92e Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 28 Jul 2025 23:45:26 +0000 Subject: [PATCH 14/24] update pkgs to 1.18.6 --- contrib/package/arch/PKGBUILD | 4 ++-- contrib/package/nix/copyparty/pin.json | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/package/arch/PKGBUILD b/contrib/package/arch/PKGBUILD index 236fc0f8..959a9821 100644 --- a/contrib/package/arch/PKGBUILD +++ b/contrib/package/arch/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: icxes pkgname=copyparty -pkgver="1.18.5" +pkgver="1.18.6" pkgrel=1 pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++" arch=("any") @@ -22,7 +22,7 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag ) source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz") backup=("etc/${pkgname}.d/init" ) -sha256sums=("30dd1bbb479187a44f3e44c8322856873c0022485237d457fadfeb5a6af51f7a") +sha256sums=("80762d91ac88815e73d0ca2806c6391dcf8ccd521bc402cc312349f3bc8e8b28") build() { cd "${srcdir}/${pkgname}-${pkgver}" diff --git a/contrib/package/nix/copyparty/pin.json b/contrib/package/nix/copyparty/pin.json index fdfcacd8..eae921c7 100644 --- a/contrib/package/nix/copyparty/pin.json +++ b/contrib/package/nix/copyparty/pin.json @@ -1,5 +1,5 @@ { - "url": "https://github.com/9001/copyparty/releases/download/v1.18.5/copyparty-sfx.py", - "version": "1.18.5", - "hash": "sha256-rEYjxJwzTzN+upo5UQ8hdYonQiNK1c+SfduS6M/QXw0=" + "url": "https://github.com/9001/copyparty/releases/download/v1.18.6/copyparty-sfx.py", + "version": "1.18.6", + "hash": "sha256-No89mzKHHZZH19ws9dqfvQO0pnZw7jKDMGhNa4LOFlY=" } \ No newline at end of file From 99856f4fa794278a222ac233870f95ca8f9fbd76 Mon Sep 17 00:00:00 2001 From: Tom van Dijk <18gatenmaker6@gmail.com> Date: Tue, 29 Jul 2025 00:16:30 +0000 Subject: [PATCH 15/24] various improvements to the nix files (#228) * nix: allow passing extra packages in PATH * nix: allow passing extra python packages I wanted to use https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/notify.py but that wasn't really possible without this under the nix package. * nix: format all nix files with nixfmt * nix: reduce redundancy in the package For readability * nix: remove unused pyftpdlib import * nix: put makeWrapper into the correct inputs * nix: fill out all of meta * nix: set formatter in flake for nix files This allows contributors to format their nix changes with the `nix fmt` command. * nix: add u2c * nix: add partyfuse One downside of the way the nix ecosystem works is that MacFUSE needs to be installed manually. Luckily the script tells you that already! * nix: add missing cfssl import * nix: add flake check that makes sure it builds with all flags Because sometimes an import might be missing, and if it is an optional then you'll only figure out that it's broken if you set the flag. * nix: use correct overlay argument names Or `nix flake check` will refuse to run the copyparty-full check --- contrib/nixos/modules/copyparty.nix | 397 +++++++++++----------- contrib/package/nix/copyparty/default.nix | 108 ++++-- contrib/package/nix/partyfuse/default.nix | 26 ++ contrib/package/nix/u2c/default.nix | 24 ++ flake.nix | 55 ++- 5 files changed, 373 insertions(+), 237 deletions(-) create mode 100644 contrib/package/nix/partyfuse/default.nix create mode 100644 contrib/package/nix/u2c/default.nix diff --git a/contrib/nixos/modules/copyparty.nix b/contrib/nixos/modules/copyparty.nix index 2f13f07b..cd4fef40 100644 --- a/contrib/nixos/modules/copyparty.nix +++ b/contrib/nixos/modules/copyparty.nix @@ -4,28 +4,31 @@ lib, ... }: -with lib; let - mkKeyValue = key: value: - if value == true - then +with lib; +let + mkKeyValue = + key: value: + if value == true then # sets with a true boolean value are coerced to just the key name key - else if value == false - then + else if value == false then # or omitted completely when false "" - else (generators.mkKeyValueDefault {inherit mkValueString;} ": " key value); + else + (generators.mkKeyValueDefault { inherit mkValueString; } ": " key value); - mkAttrsString = value: (generators.toKeyValue {inherit mkKeyValue;} value); + mkAttrsString = value: (generators.toKeyValue { inherit mkKeyValue; } value); - mkValueString = value: - if isList value - then (concatStringsSep ", " (map mkValueString value)) - else if isAttrs value - then "\n" + (mkAttrsString value) - else (generators.mkValueStringDefault {} value); + mkValueString = + value: + if isList value then + (concatStringsSep ", " (map mkValueString value)) + else if isAttrs value then + "\n" + (mkAttrsString value) + else + (generators.mkValueStringDefault { } value); - mkSectionName = value: "[" + (escape ["[" "]"] value) + "]"; + mkSectionName = value: "[" + (escape [ "[" "]" ] value) + "]"; mkSection = name: attrs: '' ${mkSectionName name} @@ -57,7 +60,8 @@ with lib; let externalCacheDir = "/var/cache/copyparty"; externalStateDir = "/var/lib/copyparty"; defaultShareDir = "${externalStateDir}/data"; -in { +in +{ options.services.copyparty = { enable = mkEnableOption "web-based file manager"; @@ -128,22 +132,27 @@ in { }; accounts = mkOption { - type = types.attrsOf (types.submodule ({...}: { - options = { - passwordFile = mkOption { - type = types.str; - description = '' - Runtime file path to a file containing the user password. - Must be readable by the copyparty user. - ''; - example = "/run/keys/copyparty/ed"; - }; - }; - })); + type = types.attrsOf ( + types.submodule ( + { ... }: + { + options = { + passwordFile = mkOption { + type = types.str; + description = '' + Runtime file path to a file containing the user password. + Must be readable by the copyparty user. + ''; + example = "/run/keys/copyparty/ed"; + }; + }; + } + ) + ); description = '' A set of copyparty accounts to create. ''; - default = {}; + default = { }; example = literalExpression '' { ed.passwordFile = "/run/keys/copyparty/ed"; @@ -152,74 +161,81 @@ in { }; volumes = mkOption { - type = types.attrsOf (types.submodule ({...}: { - options = { - path = mkOption { - type = types.path; - description = '' - Path of a directory to share. - ''; - }; - access = mkOption { - type = types.attrs; - description = '' - Attribute list of permissions and the users to apply them to. - - The key must be a string containing any combination of allowed permission: - "r" (read): list folder contents, download files - "w" (write): upload files; need "r" to see the uploads - "m" (move): move files and folders; need "w" at destination - "d" (delete): permanently delete files and folders - "g" (get): download files, but cannot see folder contents - "G" (upget): "get", but can see filekeys of their own uploads - "h" (html): "get", but folders return their index.html - "a" (admin): can see uploader IPs, config-reload - - For example: "rwmd" - - The value must be one of: - an account name, defined in `accounts` - a list of account names - "*", which means "any account" - ''; - example = literalExpression '' - { - # wG = write-upget = see your own uploads only - wG = "*"; - # read-write-modify-delete for users "ed" and "k" - rwmd = ["ed" "k"]; + type = types.attrsOf ( + types.submodule ( + { ... }: + { + options = { + path = mkOption { + type = types.path; + description = '' + Path of a directory to share. + ''; }; - ''; - }; - flags = mkOption { - type = types.attrs; - description = '' - Attribute list of volume flags to apply. - See `${getExe cfg.package} --help-flags` for more details. - ''; - example = literalExpression '' - { - # "fk" enables filekeys (necessary for upget permission) (4 chars long) - fk = 4; - # scan for new files every 60sec - scan = 60; - # volflag "e2d" enables the uploads database - e2d = true; - # "d2t" disables multimedia parsers (in case the uploads are malicious) - d2t = true; - # skips hashing file contents if path matches *.iso - nohash = "\.iso$"; + access = mkOption { + type = types.attrs; + description = '' + Attribute list of permissions and the users to apply them to. + + The key must be a string containing any combination of allowed permission: + "r" (read): list folder contents, download files + "w" (write): upload files; need "r" to see the uploads + "m" (move): move files and folders; need "w" at destination + "d" (delete): permanently delete files and folders + "g" (get): download files, but cannot see folder contents + "G" (upget): "get", but can see filekeys of their own uploads + "h" (html): "get", but folders return their index.html + "a" (admin): can see uploader IPs, config-reload + + For example: "rwmd" + + The value must be one of: + an account name, defined in `accounts` + a list of account names + "*", which means "any account" + ''; + example = literalExpression '' + { + # wG = write-upget = see your own uploads only + wG = "*"; + # read-write-modify-delete for users "ed" and "k" + rwmd = ["ed" "k"]; + }; + ''; }; - ''; - default = {}; - }; - }; - })); + flags = mkOption { + type = types.attrs; + description = '' + Attribute list of volume flags to apply. + See `${getExe cfg.package} --help-flags` for more details. + ''; + example = literalExpression '' + { + # "fk" enables filekeys (necessary for upget permission) (4 chars long) + fk = 4; + # scan for new files every 60sec + scan = 60; + # volflag "e2d" enables the uploads database + e2d = true; + # "d2t" disables multimedia parsers (in case the uploads are malicious) + d2t = true; + # skips hashing file contents if path matches *.iso + nohash = "\.iso$"; + }; + ''; + default = { }; + }; + }; + } + ) + ); description = "A set of copyparty volumes to create"; default = { "/" = { path = defaultShareDir; - access = {r = "*";}; + access = { + r = "*"; + }; }; }; example = literalExpression '' @@ -238,93 +254,90 @@ in { }; }; - config = mkIf cfg.enable (let - command = "${getExe cfg.package} -c ${runtimeConfigPath}"; - in { - systemd.services.copyparty = { - description = "http file sharing hub"; - wantedBy = ["multi-user.target"]; + config = mkIf cfg.enable ( + let + command = "${getExe cfg.package} -c ${runtimeConfigPath}"; + in + { + systemd.services.copyparty = { + description = "http file sharing hub"; + wantedBy = [ "multi-user.target" ]; - environment = { - PYTHONUNBUFFERED = "true"; - XDG_CONFIG_HOME = externalStateDir; - }; + environment = { + PYTHONUNBUFFERED = "true"; + XDG_CONFIG_HOME = externalStateDir; + }; - preStart = let - replaceSecretCommand = name: attrs: "${getExe pkgs.replace-secret} '${ - passwordPlaceholder name - }' '${attrs.passwordFile}' ${runtimeConfigPath}"; - in '' - set -euo pipefail - install -m 600 ${configFile} ${runtimeConfigPath} - ${concatStringsSep "\n" - (mapAttrsToList replaceSecretCommand cfg.accounts)} - ''; + preStart = + let + replaceSecretCommand = + name: attrs: + "${getExe pkgs.replace-secret} '${passwordPlaceholder name}' '${attrs.passwordFile}' ${runtimeConfigPath}"; + in + '' + set -euo pipefail + install -m 600 ${configFile} ${runtimeConfigPath} + ${concatStringsSep "\n" (mapAttrsToList replaceSecretCommand cfg.accounts)} + ''; - serviceConfig = { - Type = "simple"; - ExecStart = command; - # Hardening options - User = cfg.user; - Group = cfg.group; - RuntimeDirectory = ["copyparty"]; - RuntimeDirectoryMode = "0700"; - StateDirectory = ["copyparty"]; - StateDirectoryMode = "0700"; - CacheDirectory = lib.mkIf (cfg.settings ? hist) ["copyparty"]; - CacheDirectoryMode = lib.mkIf (cfg.settings ? hist) "0700"; - WorkingDirectory = externalStateDir; - BindReadOnlyPaths = - [ + serviceConfig = { + Type = "simple"; + ExecStart = command; + # Hardening options + User = cfg.user; + Group = cfg.group; + RuntimeDirectory = [ "copyparty" ]; + RuntimeDirectoryMode = "0700"; + StateDirectory = [ "copyparty" ]; + StateDirectoryMode = "0700"; + CacheDirectory = lib.mkIf (cfg.settings ? hist) [ "copyparty" ]; + CacheDirectoryMode = lib.mkIf (cfg.settings ? hist) "0700"; + WorkingDirectory = externalStateDir; + BindReadOnlyPaths = [ "/nix/store" "-/etc/resolv.conf" "-/etc/nsswitch.conf" "-/etc/group" "-/etc/hosts" "-/etc/localtime" - ] - ++ (mapAttrsToList (k: v: "-${v.passwordFile}") cfg.accounts); - BindPaths = - ( - if cfg.settings ? hist - then [cfg.settings.hist] - else [] - ) - ++ [externalStateDir] - ++ (mapAttrsToList (k: v: v.path) cfg.volumes); - # ProtectSystem = "strict"; - # Note that unlike what 'ro' implies, - # this actually makes it impossible to read anything in the root FS, - # except for things explicitly mounted via `RuntimeDirectory`, `StateDirectory`, `CacheDirectory`, and `BindReadOnlyPaths`. - # This is because TemporaryFileSystem creates a *new* *empty* filesystem for the process, so only bindmounts are visible. - TemporaryFileSystem = "/:ro"; - PrivateTmp = true; - PrivateDevices = true; - ProtectKernelTunables = true; - ProtectControlGroups = true; - RestrictSUIDSGID = true; - PrivateMounts = true; - ProtectKernelModules = true; - ProtectKernelLogs = true; - ProtectHostname = true; - ProtectClock = true; - ProtectProc = "invisible"; - ProcSubset = "pid"; - RestrictNamespaces = true; - RemoveIPC = true; - UMask = "0077"; - LimitNOFILE = cfg.openFilesLimit; - NoNewPrivileges = true; - LockPersonality = true; - RestrictRealtime = true; - MemoryDenyWriteExecute = true; + ] ++ (mapAttrsToList (k: v: "-${v.passwordFile}") cfg.accounts); + BindPaths = + (if cfg.settings ? hist then [ cfg.settings.hist ] else [ ]) + ++ [ externalStateDir ] + ++ (mapAttrsToList (k: v: v.path) cfg.volumes); + # ProtectSystem = "strict"; + # Note that unlike what 'ro' implies, + # this actually makes it impossible to read anything in the root FS, + # except for things explicitly mounted via `RuntimeDirectory`, `StateDirectory`, `CacheDirectory`, and `BindReadOnlyPaths`. + # This is because TemporaryFileSystem creates a *new* *empty* filesystem for the process, so only bindmounts are visible. + TemporaryFileSystem = "/:ro"; + PrivateTmp = true; + PrivateDevices = true; + ProtectKernelTunables = true; + ProtectControlGroups = true; + RestrictSUIDSGID = true; + PrivateMounts = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectHostname = true; + ProtectClock = true; + ProtectProc = "invisible"; + ProcSubset = "pid"; + RestrictNamespaces = true; + RemoveIPC = true; + UMask = "0077"; + LimitNOFILE = cfg.openFilesLimit; + NoNewPrivileges = true; + LockPersonality = true; + RestrictRealtime = true; + MemoryDenyWriteExecute = true; + }; }; - }; - # ensure volumes exist: - systemd.tmpfiles.settings."copyparty" = ( - lib.attrsets.mapAttrs' ( - name: value: + # ensure volumes exist: + systemd.tmpfiles.settings."copyparty" = ( + lib.attrsets.mapAttrs' ( + name: value: lib.attrsets.nameValuePair (value.path) { d = { #: in front of things means it wont change it if the directory already exists. @@ -333,32 +346,30 @@ in { mode = ":755"; }; } - ) - cfg.volumes - ); + ) cfg.volumes + ); - users.groups.copyparty = lib.mkIf (cfg.user == "copyparty" && cfg.group == "copyparty") {}; - users.users.copyparty = lib.mkIf (cfg.user == "copyparty" && cfg.group == "copyparty") { - description = "Service user for copyparty"; - group = "copyparty"; - home = externalStateDir; - isSystemUser = true; - }; - environment.systemPackages = lib.mkIf cfg.mkHashWrapper [ - (pkgs.writeShellScriptBin - "copyparty-hash" - '' - set -a # automatically export variables - # set same environment variables as the systemd service - ${lib.pipe config.systemd.services.copyparty.environment [ - (lib.filterAttrs (n: v: v != null && n != "PATH")) - (lib.mapAttrs (_: v: "${v}")) - (lib.toShellVars) - ]} - PATH=${config.systemd.services.copyparty.environment.PATH}:$PATH + users.groups.copyparty = lib.mkIf (cfg.user == "copyparty" && cfg.group == "copyparty") { }; + users.users.copyparty = lib.mkIf (cfg.user == "copyparty" && cfg.group == "copyparty") { + description = "Service user for copyparty"; + group = "copyparty"; + home = externalStateDir; + isSystemUser = true; + }; + environment.systemPackages = lib.mkIf cfg.mkHashWrapper [ + (pkgs.writeShellScriptBin "copyparty-hash" '' + set -a # automatically export variables + # set same environment variables as the systemd service + ${lib.pipe config.systemd.services.copyparty.environment [ + (lib.filterAttrs (n: v: v != null && n != "PATH")) + (lib.mapAttrs (_: v: "${v}")) + (lib.toShellVars) + ]} + PATH=${config.systemd.services.copyparty.environment.PATH}:$PATH - exec ${command} --ah-cli - '') - ]; - }); + exec ${command} --ah-cli + '') + ]; + } + ); } diff --git a/contrib/package/nix/copyparty/default.nix b/contrib/package/nix/copyparty/default.nix index d34f9ae7..28d733ab 100644 --- a/contrib/package/nix/copyparty/default.nix +++ b/contrib/package/nix/copyparty/default.nix @@ -1,41 +1,67 @@ -{ lib, stdenv, makeWrapper, fetchurl, util-linux, python, jinja2, impacket, pyftpdlib, pyopenssl, argon2-cffi, pillow, pyvips, pyzmq, ffmpeg, mutagen, +{ + lib, + stdenv, + makeWrapper, + fetchurl, + util-linux, + python, + jinja2, + impacket, + pyopenssl, + cfssl, + argon2-cffi, + pillow, + pyvips, + pyzmq, + ffmpeg, + mutagen, -# use argon2id-hashed passwords in config files (sha2 is always available) -withHashedPasswords ? true, + # use argon2id-hashed passwords in config files (sha2 is always available) + withHashedPasswords ? true, -# generate TLS certificates on startup (pointless when reverse-proxied) -withCertgen ? false, + # generate TLS certificates on startup (pointless when reverse-proxied) + withCertgen ? false, -# create thumbnails with Pillow; faster than FFmpeg / MediaProcessing -withThumbnails ? true, + # create thumbnails with Pillow; faster than FFmpeg / MediaProcessing + withThumbnails ? true, -# create thumbnails with PyVIPS; even faster, uses more memory -# -- can be combined with Pillow to support more filetypes -withFastThumbnails ? false, + # create thumbnails with PyVIPS; even faster, uses more memory + # -- can be combined with Pillow to support more filetypes + withFastThumbnails ? false, -# enable FFmpeg; thumbnails for most filetypes (also video and audio), extract audio metadata, transcode audio to opus -# -- possibly dangerous if you allow anonymous uploads, since FFmpeg has a huge attack surface -# -- can be combined with Thumbnails and/or FastThumbnails, since FFmpeg is slower than both -withMediaProcessing ? true, + # enable FFmpeg; thumbnails for most filetypes (also video and audio), extract audio metadata, transcode audio to opus + # -- possibly dangerous if you allow anonymous uploads, since FFmpeg has a huge attack surface + # -- can be combined with Thumbnails and/or FastThumbnails, since FFmpeg is slower than both + withMediaProcessing ? true, -# if MediaProcessing is not enabled, you probably want this instead (less accurate, but much safer and faster) -withBasicAudioMetadata ? false, + # if MediaProcessing is not enabled, you probably want this instead (less accurate, but much safer and faster) + withBasicAudioMetadata ? false, -# send ZeroMQ messages from event-hooks -withZeroMQ ? true, + # send ZeroMQ messages from event-hooks + withZeroMQ ? true, -# enable FTPS support in the FTP server -withFTPS ? false, + # enable FTPS support in the FTP server + withFTPS ? false, -# samba/cifs server; dangerous and buggy, enable if you really need it -withSMB ? false, + # samba/cifs server; dangerous and buggy, enable if you really need it + withSMB ? false, + + # extra packages to add to the PATH + extraPackages ? [ ], + + # function that accepts a python packageset and returns a list of packages to + # be added to the python venv. useful for scripts and such that require + # additional dependencies + extraPythonPackages ? (_p: [ ]), }: let pinData = lib.importJSON ./pin.json; - pyEnv = python.withPackages (ps: - with ps; [ + pyEnv = python.withPackages ( + ps: + with ps; + [ jinja2 ] ++ lib.optional withSMB impacket @@ -47,22 +73,36 @@ let ++ lib.optional withBasicAudioMetadata mutagen ++ lib.optional withHashedPasswords argon2-cffi ++ lib.optional withZeroMQ pyzmq - ); -in stdenv.mkDerivation { + ++ (extraPythonPackages ps) + ); + + runtimeDeps = ([ util-linux ] ++ extraPackages ++ lib.optional withMediaProcessing ffmpeg); +in +stdenv.mkDerivation { pname = "copyparty"; - version = pinData.version; + inherit (pinData) version; src = fetchurl { - url = pinData.url; - hash = pinData.hash; + inherit (pinData) url hash; }; - buildInputs = [ makeWrapper ]; + nativeBuildInputs = [ makeWrapper ]; dontUnpack = true; - dontBuild = true; installPhase = '' install -Dm755 $src $out/share/copyparty-sfx.py makeWrapper ${pyEnv.interpreter} $out/bin/copyparty \ - --set PATH '${lib.makeBinPath ([ util-linux ] ++ lib.optional withMediaProcessing ffmpeg)}:$PATH' \ - --add-flags "$out/share/copyparty-sfx.py" + --prefix PATH : ${lib.makeBinPath runtimeDeps} \ + --add-flag $out/share/copyparty-sfx.py ''; - meta.mainProgram = "copyparty"; + meta = { + description = "Turn almost any device into a file server"; + longDescription = '' + Portable file server with accelerated resumable uploads, dedup, WebDAV, + FTP, TFTP, zeroconf, media indexer, thumbnails++ all in one file, no deps + ''; + homepage = "https://github.com/9001/copyparty"; + changelog = "https://github.com/9001/copyparty/releases/tag/v${pinData.version}"; + license = lib.licenses.mit; + inherit (python.meta) platforms; + mainProgram = "copyparty"; + sourceProvenance = [ lib.sourceTypes.binaryBytecode ]; + }; } diff --git a/contrib/package/nix/partyfuse/default.nix b/contrib/package/nix/partyfuse/default.nix new file mode 100644 index 00000000..59faa914 --- /dev/null +++ b/contrib/package/nix/partyfuse/default.nix @@ -0,0 +1,26 @@ +{ + stdenvNoCC, + copyparty, + python3, + makeBinaryWrapper, +}: +let + python = python3.withPackages (p: [ p.fusepy ]); +in +stdenvNoCC.mkDerivation { + pname = "partyfuse"; + inherit (copyparty) version meta; + src = ../../../..; + + nativeBuildInputs = [ makeBinaryWrapper ]; + + installPhase = '' + runHook preInstall + + install -Dm444 bin/partyfuse.py -t $out/share/copyparty + makeWrapper ${python.interpreter} $out/bin/partyfuse \ + --add-flag $out/share/copyparty/partyfuse.py + + runHook postInstall + ''; +} diff --git a/contrib/package/nix/u2c/default.nix b/contrib/package/nix/u2c/default.nix new file mode 100644 index 00000000..dc1e4c56 --- /dev/null +++ b/contrib/package/nix/u2c/default.nix @@ -0,0 +1,24 @@ +{ + stdenvNoCC, + copyparty, + python312, + makeBinaryWrapper, +}: +stdenvNoCC.mkDerivation { + pname = "u2c"; + inherit (copyparty) version meta; + src = ../../../..; + + nativeBuildInputs = [ makeBinaryWrapper ]; + + installPhase = '' + runHook preInstall + + install -Dm444 bin/u2c.py -t $out/share/copyparty + mkdir $out/bin + makeWrapper ${python312.interpreter} $out/bin/u2c \ + --add-flag $out/share/copyparty/u2c.py + + runHook postInstall + ''; +} diff --git a/flake.nix b/flake.nix index be9d0678..5f39d605 100644 --- a/flake.nix +++ b/flake.nix @@ -4,16 +4,30 @@ flake-utils.url = "github:numtide/flake-utils"; }; - outputs = { self, nixpkgs, flake-utils }: + outputs = + { + self, + nixpkgs, + flake-utils, + }: { nixosModules.default = ./contrib/nixos/modules/copyparty.nix; - overlays.default = self: super: { - copyparty = - self.python3.pkgs.callPackage ./contrib/package/nix/copyparty { - ffmpeg = self.ffmpeg-full; - }; + overlays.default = final: prev: rec { + copyparty = final.python3.pkgs.callPackage ./contrib/package/nix/copyparty { + ffmpeg = final.ffmpeg-full; + }; + + partyfuse = prev.callPackage ./contrib/package/nix/partyfuse { + inherit copyparty; + }; + + u2c = prev.callPackage ./contrib/package/nix/u2c { + inherit copyparty; + }; }; - } // flake-utils.lib.eachDefaultSystem (system: + } + // flake-utils.lib.eachDefaultSystem ( + system: let pkgs = import nixpkgs { inherit system; @@ -22,10 +36,31 @@ }; overlays = [ self.overlays.default ]; }; - in { + in + { + # check that copyparty builds with all optionals turned on + checks.copyparty-full = self.packages.${system}.copyparty.override { + withHashedPasswords = true; + withCertgen = true; + withThumbnails = true; + withFastThumbnails = true; + withMediaProcessing = true; + withBasicAudioMetadata = true; + withZeroMQ = true; + withFTPS = true; + withSMB = true; + }; + packages = { - inherit (pkgs) copyparty; + inherit (pkgs) + copyparty + partyfuse + u2c + ; default = self.packages.${system}.copyparty; }; - }); + + formatter = pkgs.nixfmt-tree; + } + ); } From b02d53c43c7d556d6d106c87fa70127e5034395a Mon Sep 17 00:00:00 2001 From: ed Date: Tue, 29 Jul 2025 17:13:34 +0000 Subject: [PATCH 16/24] docker-compose: PYTHONUNBUFFERED=1 almost zero performance impact with podman in kitty --- docs/examples/docker/basic-docker-compose/docker-compose.yml | 5 ++++- docs/examples/docker/idp-authelia-traefik/docker-compose.yml | 3 +++ .../examples/docker/idp-authentik-traefik/docker-compose.yml | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/examples/docker/basic-docker-compose/docker-compose.yml b/docs/examples/docker/basic-docker-compose/docker-compose.yml index cfac59b6..dd3c1f9a 100644 --- a/docs/examples/docker/basic-docker-compose/docker-compose.yml +++ b/docs/examples/docker/basic-docker-compose/docker-compose.yml @@ -10,9 +10,12 @@ services: - ./:/cfg:z - /path/to/your/fileshare/top/folder:/w:z - # enabling mimalloc by replacing "NOPE" with "2" will make some stuff twice as fast, but everything will use twice as much ram: environment: LD_PRELOAD: /usr/lib/libmimalloc-secure.so.NOPE + # enable mimalloc by replacing "NOPE" with "2" for a nice speed-boost (will use twice as much ram) + + PYTHONUNBUFFERED: 1 + # ensures log-messages are not delayed (but can reduce speed a tiny bit) stop_grace_period: 15s # thumbnailer is allowed to continue finishing up for 10s after the shutdown signal healthcheck: diff --git a/docs/examples/docker/idp-authelia-traefik/docker-compose.yml b/docs/examples/docker/idp-authelia-traefik/docker-compose.yml index 5fe25a15..9ebd73ba 100644 --- a/docs/examples/docker/idp-authelia-traefik/docker-compose.yml +++ b/docs/examples/docker/idp-authelia-traefik/docker-compose.yml @@ -27,6 +27,9 @@ services: LD_PRELOAD: /usr/lib/libmimalloc-secure.so.NOPE # enable mimalloc by replacing "NOPE" with "2" for a nice speed-boost (will use twice as much ram) + PYTHONUNBUFFERED: 1 + # ensures log-messages are not delayed (but can reduce speed a tiny bit) + authelia: image: authelia/authelia:v4.38.0-beta3 # the config files in the authelia folder use the new syntax container_name: idp_authelia diff --git a/docs/examples/docker/idp-authentik-traefik/docker-compose.yml b/docs/examples/docker/idp-authentik-traefik/docker-compose.yml index ee10f0f9..7ddf1a9d 100644 --- a/docs/examples/docker/idp-authentik-traefik/docker-compose.yml +++ b/docs/examples/docker/idp-authentik-traefik/docker-compose.yml @@ -27,6 +27,9 @@ services: LD_PRELOAD: /usr/lib/libmimalloc-secure.so.NOPE # enable mimalloc by replacing "NOPE" with "2" for a nice speed-boost (will use twice as much ram) + PYTHONUNBUFFERED: 1 + # ensures log-messages are not delayed (but can reduce speed a tiny bit) + traefik: image: traefik:v2.11 container_name: traefik From 7a13728dbb55dd59daa371ee1c0d9831ed18c2a4 Mon Sep 17 00:00:00 2001 From: ed Date: Tue, 29 Jul 2025 18:14:51 +0000 Subject: [PATCH 17/24] apply unlist to navpane too --- copyparty/web/browser.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index ce204757..f1368912 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -7946,6 +7946,17 @@ var treectl = (function () { return toast.err(30, "bad ?tree reply;\nexpected json, got this:\n\n" + esc(this.responseText + '')); } r.rendertree(res, this.ts, this.top, this.dst, this.rst); + + if (r.lsc && r.lsc.unlist) + r.prunetree(r.lsc); + }; + + r.prunetree = function (res) { + var ptn = new RegExp(res.unlist); + var els = QSA('#treeul li>a+a'); + for (var a = els.length - 1; a >= 0; a--) + if (ptn.exec(els[a].textContent) && !els[a].className) + els[a].closest('ul').removeChild(els[a].closest('li')); }; r.rendertree = function (res, ts, top0, dst, rst) { @@ -8233,6 +8244,8 @@ var treectl = (function () { } r.rendertree({ "a": dirs }, this.ts, ".", get_evpath() + (dk ? '?k=' + dk : '')); + if (res.unlist) + r.prunetree(res); } r.gentab(this.top, res); @@ -8314,7 +8327,7 @@ var treectl = (function () { if (res.unlist) { var ptn = new RegExp(res.unlist); for (var a = nodes.length - 1; a >= 0; a--) - if (ptn.exec(nodes[a].href.split('?')[0])) + if (ptn.exec(uricom_dec(nodes[a].href.split('?')[0]))) nodes.splice(a, 1); } nodes = sortfiles(nodes); From 0476950ac909c0e0bd2f2d068923a4c4a38ec76d Mon Sep 17 00:00:00 2001 From: ed Date: Tue, 29 Jul 2025 20:03:42 +0000 Subject: [PATCH 18/24] disk-info: both free+total on windows too (#272) --- copyparty/httpcli.py | 8 ++++---- copyparty/util.py | 13 +++++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 2976a7c1..9052acc0 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -6157,13 +6157,13 @@ class HttpCli(object): self.log("#wow #whoa") if not self.args.nid: - free, total, _ = get_df(abspath, False) - if total is not None: + free, total, zs = get_df(abspath, False) + if total: h1 = humansize(free or 0) h2 = humansize(total) srv_info.append("{} free of {}".format(h1, h2)) - elif free is not None: - srv_info.append(humansize(free, True) + " free") + elif zs: + self.log("diskfree(%r): %s" % (abspath, zs), 3) srv_infot = " // ".join(srv_info) diff --git a/copyparty/util.py b/copyparty/util.py index 0b2dbcc4..86cf046b 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -2662,7 +2662,7 @@ def wunlink(log: "NamedLogger", abspath: str, flags: dict[str, Any]) -> bool: return _fs_mvrm(log, abspath, "", False, flags) -def get_df(abspath: str, prune: bool) -> tuple[Optional[int], Optional[int], str]: +def get_df(abspath: str, prune: bool) -> tuple[int, int, str]: try: ap = fsenc(abspath) while prune and not os.path.isdir(ap) and BOS_SEP in ap: @@ -2673,17 +2673,22 @@ def get_df(abspath: str, prune: bool) -> tuple[Optional[int], Optional[int], str assert ctypes # type: ignore # !rm abspath = fsdec(ap) bfree = ctypes.c_ulonglong(0) + btotal = ctypes.c_ulonglong(0) + bavail = ctypes.c_ulonglong(0) ctypes.windll.kernel32.GetDiskFreeSpaceExW( # type: ignore - ctypes.c_wchar_p(abspath), None, None, ctypes.pointer(bfree) + ctypes.c_wchar_p(abspath), + ctypes.pointer(bavail), + ctypes.pointer(btotal), + ctypes.pointer(bfree), ) - return (bfree.value, None, "") + return (bavail.value, btotal.value, "") else: sv = os.statvfs(ap) free = sv.f_frsize * sv.f_bfree total = sv.f_frsize * sv.f_blocks return (free, total, "") except Exception as ex: - return (None, None, repr(ex)) + return (0, 0, repr(ex)) if not ANYWIN and not MACOS: From 0408b3564a18eaaaba461cde2ba3426727997ba6 Mon Sep 17 00:00:00 2001 From: ed Date: Tue, 29 Jul 2025 20:07:11 +0000 Subject: [PATCH 19/24] webdav: send diskfree; closes #272 --- copyparty/httpcli.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 9052acc0..8bb12870 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -1575,6 +1575,18 @@ class HttpCli(object): self.log("inaccessible: %r" % ("/" + self.vpath,)) raise Pebkac(401, "authenticate") + if "quota-available-bytes" in props and not self.args.nid: + bfree, btot, _ = get_df(vn.realpath, False) + if btot: + df = { + "quota-available-bytes": str(bfree), + "quota-used-bytes": str(btot - bfree), + } + else: + df = {} + else: + df = {} + fgen = itertools.chain([topdir], fgen) vtop = vjoin(self.args.R, vjoin(vn.vpath, rem)) @@ -1617,6 +1629,9 @@ class HttpCli(object): ap = os.path.join(tap, x["vp"]) pvs["getcontenttype"] = html_escape(guess_mime(rp, ap)) pvs["getcontentlength"] = str(st.st_size) + elif df: + pvs.update(df) + df = {} for k, v in pvs.items(): if k not in props: From 4241be0c85e2fddbbdd0fbe915205bd164ee6c11 Mon Sep 17 00:00:00 2001 From: Jo <141064017+Arklaum@users.noreply.github.com> Date: Tue, 29 Jul 2025 21:24:17 +0100 Subject: [PATCH 20/24] diskfree without root-reserved space (#285) Signed-off-by: Jo <141064017+Arklaum@users.noreply.github.com> --- copyparty/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copyparty/util.py b/copyparty/util.py index 86cf046b..14768e6e 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -2684,7 +2684,7 @@ def get_df(abspath: str, prune: bool) -> tuple[int, int, str]: return (bavail.value, btotal.value, "") else: sv = os.statvfs(ap) - free = sv.f_frsize * sv.f_bfree + free = sv.f_frsize * sv.f_bavail total = sv.f_frsize * sv.f_blocks return (free, total, "") except Exception as ex: From 336c19d47a916efb59023f025d8e8d0b67a2695e Mon Sep 17 00:00:00 2001 From: ed Date: Wed, 30 Jul 2025 17:26:58 +0000 Subject: [PATCH 21/24] explain what Leeloo Dallas is doing here (closes #316) also makes rejections from IdP auths less confusing; it was handled by the config-parser throwing "invalid config" --- copyparty/authsrv.py | 19 +++++++++++++++++++ copyparty/httpcli.py | 5 ++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index bdc65640..03dea807 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -70,6 +70,25 @@ if PY2: LEELOO_DALLAS = "leeloo_dallas" +## +## you might be curious what Leeloo Dallas is doing here, so let me explain: +## +## certain daemonic tasks, namely: +## * deletion of expired files, running on a timer +## * deletion of sidecar files, initiated by plugins +## need to skip the usual permission-checks to do their thing, +## so we let Leeloo handle these +## +## and also, the smb-server has really shitty support for user-accounts +## so one popular way to avoid issues is by running copyparty without users; +## this makes all smb-clients identify as LD to gain unrestricted access +## +## Leeloo, being a fictional character from The Fifth Element, +## obviously does not exist and will never be able to access any copyparty +## instances from the outside (the username is rejected at every entrypoint) +## +## thanks for coming to my ted talk + SEE_LOG = "see log for details" SEESLOG = " (see serverlog for details)" diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 8bb12870..f90f4d93 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -33,7 +33,7 @@ except: from .__init__ import ANYWIN, PY2, RES, TYPE_CHECKING, EnvParams, unicode from .__version__ import S_VERSION -from .authsrv import VFS # typechk +from .authsrv import LEELOO_DALLAS, VFS # typechk from .bos import bos from .star import StreamTar from .stolen.qrcodegen import QrCode, qr2svg @@ -622,6 +622,9 @@ class HttpCli(object): ) or self.args.idp_h_key in self.headers if trusted_key and trusted_xff: + if idp_usr.lower() == LEELOO_DALLAS: + self.loud_reply("send her back", status=403) + return False self.asrv.idp_checkin(self.conn.hsrv.broker, idp_usr, idp_grp) else: if not trusted_key: From 0d7b9defaa0b83be46e336f41cfa600e9f59255e Mon Sep 17 00:00:00 2001 From: ed Date: Wed, 30 Jul 2025 18:02:11 +0000 Subject: [PATCH 22/24] disable libmagic on windows; probably closes #276 --- README.md | 1 + copyparty/util.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 05661ed4..8dad60c6 100644 --- a/README.md +++ b/README.md @@ -2226,6 +2226,7 @@ force-enable features with known issues on your OS/env by setting any of the fo | env-var | what it does | | ------------------------ | ------------ | | `PRTY_FORCE_MP` | force-enable multiprocessing (real multithreading) on MacOS and other broken platforms | +| `PRTY_FORCE_MAGIC` | use [magic](https://pypi.org/project/python-magic/) on Windows (you will segfault) | # packages diff --git a/copyparty/util.py b/copyparty/util.py index 14768e6e..cf07ed5a 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -155,7 +155,9 @@ except: HAVE_PSUTIL = False try: - if os.environ.get("PRTY_NO_MAGIC"): + if os.environ.get("PRTY_NO_MAGIC") or ( + ANYWIN and not os.environ.get("PRTY_FORCE_MAGIC") + ): raise Exception() import magic From a5d03c4fccf5ce811361c12a7f7afbdeafaee416 Mon Sep 17 00:00:00 2001 From: luzpaz Date: Mon, 28 Jul 2025 18:18:42 +0000 Subject: [PATCH 23/24] fix various typos Found via `codespell -q 3 -L ascript,caf,clen,fo,nd,oen,pres,som,te,tread,warks -S "*.patch"` This PR complies with the DCO; https://developercertificate.org/ --- README.md | 10 +++++----- bin/dbtool.py | 2 +- bin/hooks/notify.py | 2 +- bin/hooks/wget.py | 2 +- bin/mtag/guestbook-read.py | 2 +- bin/mtag/guestbook.py | 2 +- bin/mtag/very-bad-idea.py | 2 +- bin/mtag/wget.py | 2 +- contrib/nginx/copyparty.conf | 2 +- contrib/plugins/meadup.js | 4 ++-- copyparty/__main__.py | 2 +- copyparty/stolen/ifaddr/_shared.py | 2 +- copyparty/web/md.js | 2 +- copyparty/web/up2k.js | 2 +- docs/changelog.md | 18 +++++++++--------- docs/devnotes.md | 2 +- .../docker/idp-authelia-traefik/README.md | 2 +- docs/notes.sh | 2 +- docs/up2k.txt | 2 +- docs/versus.md | 10 +++++----- scripts/copyparty-repack.sh | 2 +- scripts/docker/README.md | 2 +- scripts/docker/make.sh | 4 ++-- scripts/make-sfx.sh | 2 +- srv/extend.md | 4 ++-- 25 files changed, 44 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 8dad60c6..4b6a9f49 100644 --- a/README.md +++ b/README.md @@ -258,7 +258,7 @@ also see [comparison to similar software](./docs/versus.md) * โ˜‘ play video files as audio (converted on server) * โ˜‘ create and play [m3u8 playlists](#playlists) * โ˜‘ image gallery with webm player - * โ˜‘ [textfile browser](#textfile-viewer) with syntax hilighting + * โ˜‘ [textfile browser](#textfile-viewer) with syntax highlighting * โ˜‘ realtime streaming of growing files (logfiles and such) * โ˜‘ [thumbnails](#thumbnails) * โ˜‘ ...of images using Pillow, pyvips, or FFmpeg @@ -814,7 +814,7 @@ the up2k UI is the epitome of polished intuitive experiences: * `[๐Ÿ”Ž]` switch between upload and [file-search](#file-search) mode * ignore `[๐Ÿ”Ž]` if you add files by dragging them into the browser -and then theres the tabs below it, +and then there's the tabs below it, * `[ok]` is the files which completed successfully * `[ng]` is the ones that failed / got rejected (already exists, ...) * `[done]` shows a combined list of `[ok]` and `[ng]`, chronological order @@ -939,7 +939,7 @@ specify `--shr /foobar` to enable this feature; a toplevel virtual folder named * you can name it whatever, `foobar` is just an example * if you're using config files, put `shr: /foobar` inside the `[global]` section instead -users can delete their own shares in the controlpanel, and a list of privileged users (`--shr-adm`) are allowed to see and/or delet any share on the server +users can delete their own shares in the controlpanel, and a list of privileged users (`--shr-adm`) are allowed to see and/or delete any share on the server after a share has expired, it remains visible in the controlpanel for `--shr-rt` minutes (default is 1 day), and the owner can revive it by extending the expiration time there @@ -1045,7 +1045,7 @@ plays almost every audio format there is (if the server has FFmpeg installed fo the following audio formats are usually always playable, even without FFmpeg: `aac|flac|m4a|mp3|ogg|opus|wav` -some hilights: +some highlights: * 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)) * shows the audio waveform in the seekbar * not perfectly gapless but can get really close (see settings + eq below); good enough to enjoy gapless albums as intended @@ -2079,7 +2079,7 @@ when connecting the reverse-proxy to `127.0.0.1` instead (the basic and/or old-f in summary, `haproxy > caddy > traefik > nginx > apache > lighttpd`, and use uds when possible (traefik does not support it yet) -* if these results are bullshit because my config exampels are bad, please submit corrections! +* if these results are bullshit because my config examples are bad, please submit corrections! ## permanent cloudflare tunnel diff --git a/bin/dbtool.py b/bin/dbtool.py index ff92fca3..b946f0d2 100755 --- a/bin/dbtool.py +++ b/bin/dbtool.py @@ -39,7 +39,7 @@ def ls(db): print(f"{nfiles} files") print(f"{ntags} tags\n") - print("number of occurences for each tag,") + print("number of occurrences for each tag,") print(" 'x' = file has no tags") print(" 't:mtp' = the mtp flag (file not mtp processed yet)") print() diff --git a/bin/hooks/notify.py b/bin/hooks/notify.py index 6e5dda86..bc12269d 100755 --- a/bin/hooks/notify.py +++ b/bin/hooks/notify.py @@ -9,7 +9,7 @@ from plyer import notification _ = r""" show os notification on upload; works on windows, linux, macos, android -depdencies: +dependencies: windows: python3 -m pip install --user -U plyer linux: python3 -m pip install --user -U plyer macos: python3 -m pip install --user -U plyer pyobjus diff --git a/bin/hooks/wget.py b/bin/hooks/wget.py index ad0c71c0..1f5a824b 100755 --- a/bin/hooks/wget.py +++ b/bin/hooks/wget.py @@ -66,7 +66,7 @@ def main(): try: sp.check_call(cmd) except: - t = "-- FAILED TO DONWLOAD " + name + t = "-- FAILED TO DOWNLOAD " + name print(f"{t}\n", end="") open(t, "wb").close() diff --git a/bin/mtag/guestbook-read.py b/bin/mtag/guestbook-read.py index 704addbe..e9119903 100755 --- a/bin/mtag/guestbook-read.py +++ b/bin/mtag/guestbook-read.py @@ -7,7 +7,7 @@ example copyparty config to use this: --urlform save,get -vsrv/hello:hello:w:c,e2ts,mtp=guestbook=t10,ad,p,bin/mtag/guestbook-read.py:mte=+guestbook explained: - for realpath srv/hello (served at /hello), write-only for eveyrone, + for realpath srv/hello (served at /hello), write-only for everyone, enable file analysis on upload (e2ts), use mtp plugin "bin/mtag/guestbook-read.py" to provide metadata tag "guestbook", do this on all uploads regardless of extension, diff --git a/bin/mtag/guestbook.py b/bin/mtag/guestbook.py index 437289b6..84f8fa53 100644 --- a/bin/mtag/guestbook.py +++ b/bin/mtag/guestbook.py @@ -11,7 +11,7 @@ example copyparty config to use this: --urlform save,get -vsrv/hello:hello:w:c,e2ts,mtp=xgb=ebin,t10,ad,p,bin/mtag/guestbook.py:mte=+xgb explained: - for realpath srv/hello (served at /hello),write-only for eveyrone, + for realpath srv/hello (served at /hello),write-only for everyone, enable file analysis on upload (e2ts), use mtp plugin "bin/mtag/guestbook.py" to provide metadata tag "xgb", do this on all uploads with the file extension "bin", diff --git a/bin/mtag/very-bad-idea.py b/bin/mtag/very-bad-idea.py index 50cd390d..f6fe2dfa 100755 --- a/bin/mtag/very-bad-idea.py +++ b/bin/mtag/very-bad-idea.py @@ -151,7 +151,7 @@ def open_url(txt): for _ in range(20): sp.call(["xdotool", "key", "ctrl+w"]) # closes the open tab correctly # else: - # sp.call(["xdotool", "getactivewindow", "windowminimize"]) # minimizes the focused windo + # sp.call(["xdotool", "getactivewindow", "windowminimize"]) # minimizes the focused window # mpv is probably smart enough to use streamlink automatically if try_mpv(txt): diff --git a/bin/mtag/wget.py b/bin/mtag/wget.py index 26a1fa45..d706052b 100644 --- a/bin/mtag/wget.py +++ b/bin/mtag/wget.py @@ -84,7 +84,7 @@ def main(): # on success, delete the .bin file which contains the URL os.unlink(fp) except: - open("-- FAILED TO DONWLOAD " + name, "wb").close() + open("-- FAILED TO DOWNLOAD " + name, "wb").close() os.unlink(tfn) print(url) diff --git a/contrib/nginx/copyparty.conf b/contrib/nginx/copyparty.conf index 121e52ab..f0d382cc 100644 --- a/contrib/nginx/copyparty.conf +++ b/contrib/nginx/copyparty.conf @@ -31,7 +31,7 @@ # generate the list of permitted IP ranges like so: # (curl -s https://www.cloudflare.com/ips-v{4,6} | sed 's/^/allow /; s/$/;/'; echo; echo "deny all;") > /etc/nginx/cloudflare-only.conf # -# and then enable it below by uncomenting the cloudflare-only.conf line +# and then enable it below by uncommenting the cloudflare-only.conf line # # ====================================================================== diff --git a/contrib/plugins/meadup.js b/contrib/plugins/meadup.js index cd5e123a..bc75d9b5 100644 --- a/contrib/plugins/meadup.js +++ b/contrib/plugins/meadup.js @@ -11,7 +11,7 @@ var hambagas = [ "https://www.youtube.com/watch?v=pFA3KGp4GuU" ]; -// keybaord, +// keyboard, // onscreen keyboard by @steinuil function initKeybaord(BASE_URL, HAMBAGA, consoleLog, consoleError) { document.querySelector('.keybaord-container').innerHTML = ` @@ -373,7 +373,7 @@ function initKeybaord(BASE_URL, HAMBAGA, consoleLog, consoleError) { } -// keybaord integration +// keyboard integration (function () { var o = mknod('div'); clmod(o, 'keybaord-container', 1); diff --git a/copyparty/__main__.py b/copyparty/__main__.py index f6c20b8c..0ad682d7 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -713,7 +713,7 @@ def get_sects(): \033[36mc0\033[35m show all process output (default) \033[36mc1\033[35m show only stderr \033[36mc2\033[35m show only stdout - \033[36mc3\033[35m mute all process otput + \033[36mc3\033[35m mute all process output \033[0m examples: diff --git a/copyparty/stolen/ifaddr/_shared.py b/copyparty/stolen/ifaddr/_shared.py index 1f8ae931..d8969685 100644 --- a/copyparty/stolen/ifaddr/_shared.py +++ b/copyparty/stolen/ifaddr/_shared.py @@ -41,7 +41,7 @@ class Adapter(object): #: `{846EE342-7039-11DE-9D20-806E6F6E6963}`. self.name = name - #: Human readable name of the adpater. On Linux this + #: Human readable name of the adapter. On Linux this #: is currently the same as :attr:`name`. On Windows #: this is the name of the device. self.nice_name = nice_name diff --git a/copyparty/web/md.js b/copyparty/web/md.js index 0109d453..9ab5a2fc 100644 --- a/copyparty/web/md.js +++ b/copyparty/web/md.js @@ -422,7 +422,7 @@ function init_toc() { } } - // hilight the correct toc items + scroll into view + // highlight the correct toc items + scroll into view function freshen_toclist() { if (anchors.length == 0) return; diff --git a/copyparty/web/up2k.js b/copyparty/web/up2k.js index edf0eee8..c0d118f8 100644 --- a/copyparty/web/up2k.js +++ b/copyparty/web/up2k.js @@ -50,7 +50,7 @@ catch (ex) { } catch (ex) { console.log('up2k init failed:', ex); - toast.err(10, 'could not initialze up2k\n\n' + basenames(ex)); + toast.err(10, 'could not initialize up2k\n\n' + basenames(ex)); } } treectl.onscroll(); diff --git a/docs/changelog.md b/docs/changelog.md index 7a9571f1..d65c63cb 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -366,7 +366,7 @@ get the party going anywhere, anytime, no OS required! [download flashdrive imag * option to specify max-size for download-as-zip/tar 494179bd 0a33336d * either the total download size (`--zipmaxs 500M`), and/or max number of files (`--zipmaxn 9k`) - * applies to all uesrs by default; can also ignore limits for authorized users (`--zipmaxu`) + * applies to all users by default; can also ignore limits for authorized users (`--zipmaxu`) * errormessage can be customized with `--zipmaxt "winter is coming... but this download isn't"` * [appledoubles](https://a.ocv.me/pub/stuff/?doc=appledoubles-and-friends.txt) are detected and skipped when uploading with the browser-UI 78208405 * IdP-volumes can be filtered by group 9c2c4237 @@ -821,7 +821,7 @@ this release includes a build of [copyparty-winpe64.exe](https://github.com/9001 * webdav: support listing unmapped root with infinite recursion (Depth:0) 21a3f369 * embed current sort config into media URLs (gallery/music) 0f257c93 4cfdc4c5 01670827 * ensures that anyone clicking your link will see the files in the same order as you - * can be confgured serverside (`--hsortn`, volflag `hsortn`) and clientside (`#sort` in settings) + * can be configured serverside (`--hsortn`, volflag `hsortn`) and clientside (`#sort` in settings) * URL and UI options to disable checksum calculation of PUT, bup, basic uploads c5a000d2 * also allows [choosing either md5, sha1, sha256, or blake2](https://github.com/9001/copyparty/blob/hovudstraum/docs/devnotes.md#write) instead of the default sha512 * can give uploads a nice speed boost when copyparty is running on a potato @@ -2014,7 +2014,7 @@ probably last release before v1.10 (IdP), please watch warmly * now possible to POST files without having to set the `act: bput` multipart field 9bc09ce9 * mainly to support [igloo irc](https://github.com/9001/copyparty#client-examples) and other simplistic upload clients * try to point the linux oom-killer at FFmpeg so it doesn't kill innocent processes instead dc8e621d - * only works if copyparty has acces to /proc, so not in prisonparty, and maybe not in docker (todo) + * only works if copyparty has access to /proc, so not in prisonparty, and maybe not in docker (todo) * UX: * do another search immediately if a search-filter gets unchecked a4239a46 * several ie11 fixes (keyboard hotkeys and a working text editor) 2fd2c6b9 @@ -3484,7 +3484,7 @@ named after [that other thing](https://en.wikipedia.org/wiki/Tower_of_Babel), no * display a server [qr-code](https://github.com/9001/copyparty#qr-code) [(screenshot)](https://user-images.githubusercontent.com/241032/194728533-6f00849b-c6ac-43c6-9359-83e454d11e00.png) on startup * primarily for running copyparty on a phone and accessing it from another * optionally specify a path or password with `--qrl lootbox/?pw=hunter2` - * uses the server's exteral ip (default route) unless `--qri` specifies a domain / ip-prefix + * uses the server's external ip (default route) unless `--qri` specifies a domain / ip-prefix * classic cp437 `โ–„` `โ–€` for space efficiency; some misbehaving terminals / fonts need `--qrz 2` * new permission `G` returns the filekey of uploaded files for users without read-access * when combined with permission `w` and volflag `fk`, uploaded files will not be accessible unless the filekey is provided in the url, and `G` provides the filekey to the uploader unlike `g` @@ -4712,7 +4712,7 @@ for future releases, you can use a script to automatically grab the latest sfx a * latest gzip edition of the sfx: [v0.11.18](https://github.com/9001/copyparty/releases/tag/v0.11.18) ## bugfixes -* currently-playing song didn't hilight correctly +* currently-playing song didn't highlight correctly @@ -4764,7 +4764,7 @@ thx to @Bevinsky and @icxes for the ux suggestions * and `?raw` POST without content-type is now allowed * file-listing is refreshed when all up2k uploads complete * new option `--ign-ebind` to continue startup even if one of the IPs / ports couldn't be listened on -* new option `--ign-ebind-all` to run even if copyparty can't receieve any connections at all +* new option `--ign-ebind-all` to run even if copyparty can't receive any connections at all * maybe useful for monitoring folders and hashing new files on a timer or something ## bugfixes @@ -6078,7 +6078,7 @@ unless the upload was paused for 6 hours or more, it can probably be resumed by ## new features suggested by kipu * pause uploads by setting `parallel uploads` to `0` -* increase max `parallel uploads` to 16 (using +/- buttons) and 64 (by manual text entry) to accomodate sad american internet connections +* increase max `parallel uploads` to 16 (using +/- buttons) and 64 (by manual text entry) to accommodate sad american internet connections * also look for `cover.jpg` and `cover.png` as folder thumbnails by default, adjustable with `--th-covers` * change the description in the sfx so the corruption warning is the first plaintext you see @@ -6232,7 +6232,7 @@ reason: [v0.11.12](https://github.com/9001/copyparty/releases/tag/v0.11.12) chan ## new features * much faster filesearch in chrome -* skip hidden colums in the /np text +* skip hidden columns in the /np text * support cygpaths when pointing to mtag tools ## bugfixes @@ -7243,7 +7243,7 @@ valvrave-stop.jpg โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€โ–€ # 2020-1117-2258 `v0.5.4` edovprim -(get it? becasue reverse proxy haha) +(get it? because reverse proxy haha) * reverse-proxy support * filetype column in the browser diff --git a/docs/devnotes.md b/docs/devnotes.md index 3db6b201..0cc50a8e 100644 --- a/docs/devnotes.md +++ b/docs/devnotes.md @@ -328,7 +328,7 @@ if you don't need all the features, you can repack the sfx and save a bunch of s the features you can opt to drop are * `cm`/easymde, the "fancy" markdown editor, saves ~89k -* `hl`, prism, the syntax hilighter, saves ~41k +* `hl`, prism, the syntax highlighter, saves ~41k * `fnt`, source-code-pro, the monospace font, saves ~9k * `dd`, the custom mouse cursor for the media player tray tab, saves ~2k diff --git a/docs/examples/docker/idp-authelia-traefik/README.md b/docs/examples/docker/idp-authelia-traefik/README.md index 7667cb00..0453854f 100644 --- a/docs/examples/docker/idp-authelia-traefik/README.md +++ b/docs/examples/docker/idp-authelia-traefik/README.md @@ -47,4 +47,4 @@ currently **not optimal,** at least when compared to running the python sfx outs authelia is behaving strangely, handling 340 requests per second for a while, but then it suddenly drops to 75 and stays there... -I'm assuming all of the performance issues is due to a misconfiguration of authelia/traefik/docker on my end, but I don't relly know where to start +I'm assuming all of the performance issues is due to a misconfiguration of authelia/traefik/docker on my end, but I don't really know where to start diff --git a/docs/notes.sh b/docs/notes.sh index 3c8798cc..1959a6fb 100644 --- a/docs/notes.sh +++ b/docs/notes.sh @@ -71,7 +71,7 @@ avg() { awk 'function pr(ncsz) {if (nsmp>0) {printf "%3s %s\n", csz, sum/nsmp} c python3 -um copyparty -nw -v srv::rw -i 127.0.0.1 2>&1 | tee log cat log | awk '!/"purl"/{next} {s=$1;sub(/[^m]+m/,"");gsub(/:/," ");t=60*(60*$1+$2)+$3} t1{printf "%.3f += %.3f - %.3f (%.3f) # %.3f -> %.3f\n",r,p,a,p-a,p,t;r+=p-a;a=t} {p=t} END {print r+p-a}' diff --git a/docs/up2k.txt b/docs/up2k.txt index 5c07b4e4..35c387ec 100644 --- a/docs/up2k.txt +++ b/docs/up2k.txt @@ -17,7 +17,7 @@ server creates session id and replies with the same json: secretsalt, name, size, *hash ]))[:32].replace('+','-').replace('/','_') -cilent uploads each chunk: +client uploads each chunk: POST application/octet-stream X-Up2k-Hash: fUGShzwcSAmw5IbQ3y_2TUrI8a89LYQO-kW0o0rRcU0 X-Up2k-Wark: CVNt9EYhgTFHU3xiK6gL-0ciJFopshvo diff --git a/docs/versus.md b/docs/versus.md index 34d4d4b0..82f0c4b8 100644 --- a/docs/versus.md +++ b/docs/versus.md @@ -82,9 +82,9 @@ currently up to date with [awesome-selfhosted](https://github.com/awesome-selfho <&Kethsar> copyparty is very much bloat ed, so yeah ``` -the table headers in the matrixes below are the different softwares, with a quick review of each software in the next section +the table headers in the matrixes below are the different software, with a quick review of each software in the next section -the softwares, +the software, * `a` = [copyparty](https://github.com/9001/copyparty) * `b` = [hfs2](https://github.com/rejetto/hfs2/) ๐Ÿ”ฅ * `c` = [hfs3](https://rejetto.com/hfs/) @@ -99,7 +99,7 @@ the softwares, * `l` = [sftpgo](https://github.com/drakkan/sftpgo) * `m` = [arozos](https://github.com/tobychui/arozos) -some softwares not in the matrixes, +some software not in the matrixes, * [updog](#updog) * [goshs](#goshs) * [gimme-that](#gimmethat) @@ -242,7 +242,7 @@ symbol legend, * you can successfully play `$'\355\221'` with mpv through mounting a remote copyparty server with rclone, pog * `a`/copyparty remarks: * extremely minimal samba/cifs server - * netscape 4 / ie6 support is mostly listed as a joke altho some people have actually found it useful ([ie4 tho](https://user-images.githubusercontent.com/241032/118192791-fb31fe00-b446-11eb-9647-898ea8efc1f7.png)) + * netscape 4 / ie6 support is mostly listed as a joke although some people have actually found it useful ([ie4 tho](https://user-images.githubusercontent.com/241032/118192791-fb31fe00-b446-11eb-9647-898ea8efc1f7.png)) * `l`/sftpgo translates mojibake filenames into valid utf-8 (information loss) * `m`/arozos has readonly-support for older browsers; no uploading @@ -575,7 +575,7 @@ symbol legend, * โœ… file tags; file discussions!? * โœ… video transcoding * โœ… unzip uploaded archives -* โœ… IDE with syntax hilighting +* โœ… IDE with syntax highlighting * โœ… wysiwyg editor for openoffice files ## [filebrowser](https://github.com/filebrowser/filebrowser) diff --git a/scripts/copyparty-repack.sh b/scripts/copyparty-repack.sh index bfe18cea..86897226 100755 --- a/scripts/copyparty-repack.sh +++ b/scripts/copyparty-repack.sh @@ -27,7 +27,7 @@ set -e # 270004 copyparty-extras/sfx-lite/copyparty-sfx.py # 293159 copyparty-extras/sfx-lite/copyparty-sfx-gz.py # `- also removed the codemirror markdown editor -# and the text-viewer syntax hilighting, +# and the text-viewer syntax highlighting, # only essential features remaining # # 646297 copyparty-extras/copyparty-1.0.14.tar.gz diff --git a/scripts/docker/README.md b/scripts/docker/README.md index e7ca2a7b..c4f9e0f0 100644 --- a/scripts/docker/README.md +++ b/scripts/docker/README.md @@ -1,4 +1,4 @@ -copyparty is availabe in these repos: +copyparty is available in these repos: * https://hub.docker.com/u/copyparty * https://github.com/9001?tab=packages&repo_name=copyparty diff --git a/scripts/docker/make.sh b/scripts/docker/make.sh index 88758c2a..5e649678 100755 --- a/scripts/docker/make.sh +++ b/scripts/docker/make.sh @@ -115,7 +115,7 @@ filt= ) 2> >(tee $a.err | sed "s/^/$aa:/" >&2) > >(tee $a.out | sed "s/^/$aa:/") & done [ -e err ] && { - echo somethign died, + echo something died, cat err pkill -P $$ exit 1 @@ -126,7 +126,7 @@ filt= done wait [ -e err ] && { - echo somethign died, + echo something died, cat err pkill -P $$ exit 1 diff --git a/scripts/make-sfx.sh b/scripts/make-sfx.sh index 1ef87943..a785b120 100755 --- a/scripts/make-sfx.sh +++ b/scripts/make-sfx.sh @@ -41,7 +41,7 @@ help() { exec cat <<'EOF' # `no-cm` saves ~89k by removing easymde/codemirror # (the fancy markdown editor) # -# `no-hl` saves ~41k by removing syntax hilighting in the text viewer +# `no-hl` saves ~41k by removing syntax highlighting in the text viewer # # `no-fnt` saves ~9k by removing the source-code-pro font # (browsers will try to use 'Consolas' instead) diff --git a/srv/extend.md b/srv/extend.md index bc9911b9..6392d2da 100644 --- a/srv/extend.md +++ b/srv/extend.md @@ -30,7 +30,7 @@ finally ./except/these/ones.md ### also-this.md -whic hshoud be ./except/also-this.md +which should be ./except/also-this.md @@ -62,7 +62,7 @@ the difference is that with `copyparty_pre` you'll probably break various copypa # heres the plugins -if there is anything below ths line in the preview then the plugin feature is disabled (good) +if there is anything below this line in the preview then the plugin feature is disabled (good) From 4116d830eb25c1345a62b2d3384c434d467fe23f Mon Sep 17 00:00:00 2001 From: luzpaz Date: Wed, 30 Jul 2025 18:33:17 +0000 Subject: [PATCH 24/24] fix typos in brower.js and tl.js --- copyparty/web/browser.js | 2 +- scripts/tl.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index f1368912..3a8709c2 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -324,7 +324,7 @@ var Ls = { "mm_enet": "Your internet connection is wonky", "mm_edec": "This file is supposedly corrupted??", "mm_esupp": "Your browser does not understand this audio format", - "mm_eunk": "Unknown Errol", + "mm_eunk": "Unknown Error", "mm_e404": "Could not play audio; error 404: File not found.", "mm_e403": "Could not play audio; error 403: Access denied.\n\nTry pressing F5 to reload, maybe you got logged out", "mm_e500": "Could not play audio; error 500: Check server logs.", diff --git a/scripts/tl.js b/scripts/tl.js index 40ffe8a4..05518cbe 100644 --- a/scripts/tl.js +++ b/scripts/tl.js @@ -410,7 +410,7 @@ var tl_browser = { "mm_enet": "Your internet connection is wonky", "mm_edec": "This file is supposedly corrupted??", "mm_esupp": "Your browser does not understand this audio format", - "mm_eunk": "Unknown Errol", + "mm_eunk": "Unknown Error", "mm_e404": "Could not play audio; error 404: File not found.", "mm_e403": "Could not play audio; error 403: Access denied.\n\nTry pressing F5 to reload, maybe you got logged out", "mm_e500": "Could not play audio; error 500: Check server logs.",