Compare commits

...
24 Commits
Author SHA1 Message Date
ed 5ccc999f39 v1.20.24 2026-09-19 21:33:56 +00:00
ed 16a3f5d51a add --help-redup + failsafe 2026-09-19 20:48:28 +00:00
ed 35af7c9fef js: add msel fn 2026-09-19 19:14:06 +00:00
ed 4d1f2a9301 js: fix contrived crash (closes #1516);
view textfile, sel, delete, sel, boom
2026-09-19 19:13:41 +00:00
ed 229159027b add --help-dedup 2026-09-19 17:43:51 +00:00
ed ec4a95e1a5 up2k nosub; closes #1611 2026-09-19 17:42:39 +00:00
ed eb4f1dddce lint 2026-09-18 20:59:16 +00:00
Mica Whiteanded 097b83056e textfile-viewer: parse shebang (#1643)
detect filetype based on #!/interpreter

---------

Signed-off-by: ed <s@ocv.me>
Co-authored-by: ed <s@ocv.me>
2026-09-18 19:47:09 +00:00
ed c688bb2e46 xiu: add trailing newline 2026-09-17 22:00:24 +00:00
ed c92435c2d2 add -i no 2026-09-17 21:53:50 +00:00
ed 22afaa7586 nocow histpath iff btrfs 2026-09-17 21:46:12 +00:00
ed 5ddbce31fc xlink-reflink only same-fs;
avoid waking other disks which wont work anyways
2026-09-17 21:24:10 +00:00
ed ec8ec0f1a6 un-cargocult bools 2026-09-17 21:12:46 +00:00
ed 558f6aee44 shallowclone up2k.flags;
update flags on config-reload while keeping up2k coherent
2026-09-17 19:56:58 +00:00
ed 6867ccc511 fix PRTY_NO_IFADDR + cleanup 2026-09-16 22:38:23 +00:00
ed 947d73eeb6 check xlink on copy 2026-09-16 22:35:11 +00:00
ed 735d6e00de up2k: reflink is xlink-safe 2026-09-16 22:34:19 +00:00
ed fd4f434192 add --redup 2026-09-16 22:33:38 +00:00
ed 971a62cc89 up2k: iOS: reduce jitter;
* xhr.abort() to close/free tcp
* onfoo=null fixes done-flicker
2026-09-15 19:29:47 +00:00
ed bfc3cff848 unsanctioned: sourceforge 2026-09-08 06:01:30 +02:00
ed 8967726f61 update pkgs to 1.20.23 2026-09-06 22:53:14 +00:00
ed ad9994bef3 v1.20.23 2026-09-06 22:49:08 +00:00
ed e5e2e97910 archlinux: add post_upgrade 2026-09-06 22:08:42 +00:00
ed e42e01563e update pkgs to 1.20.22 2026-09-06 21:18:21 +00:00
25 changed files with 572 additions and 109 deletions

No files matched your search

+2
View File
@@ -219,6 +219,8 @@ other places to download copyparty from (non-github links):
* https://copyparty.eu/enz = https://copyparty.eu/copyparty-en.pyz = the enterprise pyz
* https://copyparty.eu/cli = online cli helptext
DO NOT download from [sourceforge](https://copyparty.eu/unsanctioned/sourceforge.txt)
### at home
+6 -6
View File
@@ -3,7 +3,7 @@
# NOTE: You generally shouldn't use this PKGBUILD on Arch, as it is mainly for testing purposes. Install copyparty using pacman instead.
pkgname=copyparty
pkgver="1.20.21"
pkgver="1.20.23"
pkgrel=1
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, SFTP, FTP, TFTP, zeroconf, media indexer, thumbnails++"
arch=("any")
@@ -24,8 +24,9 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag
"python-argon2-cffi: hashed passwords in config"
)
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
backup=("etc/${pkgname}/copyparty.conf" )
sha256sums=("654ef47555bb402e27d8905c900c99f54b7f1d6db874a749effdf9ab23cf4223")
backup=("etc/copyparty.conf")
install=copyparty.install
sha256sums=("c0957ce077b0494292541116b9473fdbda1e6597b5d9158b2fc470db0a1834dd")
build() {
cd "${srcdir}/${pkgname}-${pkgver}"
@@ -36,12 +37,11 @@ package() {
cd "${srcdir}/${pkgname}-${pkgver}"
python -m installer --destdir="$pkgdir" dist/*.whl
install -dm755 "${pkgdir}/etc/${pkgname}"
install -Dm755 "bin/prisonparty.sh" "${pkgdir}/usr/bin/prisonparty"
install -Dm644 "contrib/systemd/${pkgname}.conf" "${pkgdir}/etc/${pkgname}/copyparty.conf"
install -Dm644 "contrib/systemd/${pkgname}.conf" "${pkgdir}/etc/copyparty.conf"
install -Dm644 "contrib/systemd/${pkgname}@.service" "${pkgdir}/usr/lib/systemd/system/${pkgname}@.service"
install -Dm644 "contrib/systemd/${pkgname}-user.service" "${pkgdir}/usr/lib/systemd/user/${pkgname}.service"
install -Dm644 "contrib/systemd/prisonparty@.service" "${pkgdir}/usr/lib/systemd/system/prisonparty@.service"
install -Dm644 "contrib/systemd/index.md" "${pkgdir}/var/lib/${pkgname}-jail/README.md"
install -Dm644 "contrib/systemd/index.md" "${pkgdir}/var/lib/${pkgname}-jail/srv/README.md"
install -Dm644 "LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
}
+27
View File
@@ -0,0 +1,27 @@
post_upgrade() {
# v1.20.22 moved the config-file, affecting the system-service only
if [[ $(vercmp "$2" 1.20.22) -lt 0 ]]; then
oldpath="/etc/copyparty/copyparty.conf"
newpath="/etc/copyparty.conf"
echo "==> Migrating config-file from $oldpath to $newpath"
if [[ ! -f "$oldpath" ]]; then
oldpath="$oldpath.pacsave"
fi
if [[ -f "$newpath.pacnew" ]]; then
echo "==> WARNING: A config-file already existed in the new location [$newpath]. The existing file will be used as-is."
elif [[ -f "$oldpath" ]]; then
mv -v "$oldpath" "$newpath"
rmdir /etc/copyparty 2>/dev/null || true
fi
systemctl cat copyparty@.service | awk '/=PRTY_CONFIG=/ && !/=\/etc\/copyparty.conf/ {exit 1}' || {
echo "==> WARNING: The copyparty system-service has been edited by hand since installation, so it will not read config from the new default config path [$newpath]. Please use PRTY_CONFIG=$newpath if that is correct for your setup."
}
fi
}
+2 -2
View File
@@ -2,7 +2,7 @@
pkgname=copyparty
pkgver=1.20.21
pkgver=1.20.23
pkgrel=1
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, SFTP, FTP, TFTP, zeroconf, media indexer, thumbnails++"
arch=("any")
@@ -21,7 +21,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=("654ef47555bb402e27d8905c900c99f54b7f1d6db874a749effdf9ab23cf4223")
sha256sums=("c0957ce077b0494292541116b9473fdbda1e6597b5d9158b2fc470db0a1834dd")
build() {
cd "${srcdir}/${pkgname}-${pkgver}"
+3 -3
View File
@@ -1,5 +1,5 @@
{
"url": "https://github.com/9001/copyparty/releases/download/v1.20.21/copyparty-1.20.21.tar.gz",
"version": "1.20.21",
"hash": "sha256-ZU70dVW7QC4n2JBckAyZ9Ut/HW24dKdJ7/35qyPPQiM="
"url": "https://github.com/9001/copyparty/releases/download/v1.20.23/copyparty-1.20.23.tar.gz",
"version": "1.20.23",
"hash": "sha256-wJV84HewSUKSVBEWuUc/29oeZZe12RWLL8Rw2woYNN0="
}
+84 -13
View File
@@ -687,6 +687,7 @@ def get_sects():
\033[32m-i 127.0.0.1\033[0m listens on IPv4 localhost only
\033[32m-i 127.1\033[0m listens on IPv4 localhost only
\033[32m-i 127.1,192.168.123.1\033[0m = IPv4 localhost and 192.168.123.1
\033[32m-i no\033[0m disable webserver
\033[33m-p\033[0m takes a comma-separated list of tcp ports to listen on;
the default is \033[32m-p 3923\033[0m but as root you can \033[32m-p 80,443,3923\033[0m
@@ -847,6 +848,74 @@ def get_sects():
).rstrip()
+ build_flags_desc(),
],
[
"dedup",
"file deduplication on upload",
dedent(
"""
when just \033[36m--dedup\033[0m or volflag "\033[36mdedup\033[0m" is enabled, files will be
deduplicated by means of \033[1msymlinks,\033[0m which demand some caution:
the available dedup approaches:
* symlinks work on most filesystems, but if another software
moves either the symlink itself or the file that it points to,
the link will break and the file "disappears";
only safe if only copyparty is used to rename/move/delete
* hardlinks work on Linux filesystems and \033[1mlook\033[0m like regular files,
except if the contents of one copy of that file is edited, then
this will affect \033[1mall the dupes too!\033[0m
and they share the same last-modified-time
* reflinks only work on btrfs/zfs/xfs but is the safest/best choice;
deduplicated files behave just like regular files,
and can be modified without affecting other copies/dupes
to change approach, add one of the following in addition to "\033[36mdedup\033[0m":
\033[36m--reflink\033[0m or volflag "\033[36mreflink\033[0m"
if reflink is not possible (unsupp/cross-fs), a full copy is made
\033[36m--hardlink-only\033[0m or volflag "\033[36mhardlinkonly\033[0m"
if hardlink is not possible (unsupp/cross-fs), a full copy is made
\033[36m--hardlink\033[0m or volflag "\033[36mhardlink\033[0m"
if hardlink is not possible (unsupp/cross-fs), a symlink is made
note: dedup works best when combined with e2ds or e2dsa
to deduplicate existing files on-disk, see \033[36m--help-redup\033[0m
"""
),
],
[
"redup",
"deduplicating existing files on disk",
dedent(
"""
\033[36m--redup\033[0m can convert on-disk files between deduplication-approaches,
for example turning redundant full copies into reflinks to save space
converts \033[1mall files\033[0m in each volume! not just copyparty-created ones
example: --redup \033[32mno,sym,hard\033[0m=\033[35mref\033[0m will find all files of type
\033[32mfullcopy/symlink/hardlink\033[0m, and convert them into \033[35mreflinks\033[0m
(see \033[36m--help-dedup\033[0m for a summary on the types)
converting \033[1mfrom\033[0m symlinks/hardlinks is fairly fast and simple;
in this case, the obvious target type/approach is reflinks
(which only works on btrfs/zfs/xfs)
converting \033[1mto\033[0m symlinks or hardlinks is \033[3mpossible\033[0m, but
not-recommended due to risks explained in \033[36m--help-dedup\033[0m
(interaction with other softwares can result in data loss)
converting \033[1mfrom\033[0m 'no' (full redundant copies) is hella slow;
checksums each file to avoid corruption from bad RAM
"""
),
],
[
"thumb-ex",
"extract thumbnails from custom file formats with python scripts",
@@ -1212,9 +1281,9 @@ def get_sects():
"file/folder permissions",
dedent(
"""
global-option \033[33m--chmod-f\033[0m and volflag \033[33mchmod_f\033[0m specifies the unix-permission to use when creating a new file
global-option \033[33m--chmod-f\033[0m and volflag "\033[33mchmod_f\033[0m" specifies the unix-permission to use when creating a new file
similarly, \033[33m--chmod-d\033[0m and \033[33mchmod_d\033[0m sets the directory/folder perm
similarly, \033[33m--chmod-d\033[0m and "\033[33mchmod_d\033[0m" sets the directory/folder perm
the value is a three- or four-digit octal number such as \033[32m755\033[0m, \033[32m0644\033[0m, \033[32m2750\033[0m, etc.
@@ -1381,8 +1450,8 @@ def add_qr(ap, tty):
ap2.add_argument("--qrz", metavar="N", type=int, default=0, help="[\033[32m1\033[0m]=1x, [\033[32m2\033[0m]=2x, [\033[32m0\033[0m]=auto (try [\033[32m2\033[0m] on broken fonts)")
ap2.add_argument("--qr-pin", metavar="N", type=int, default=0, help="sticky/pin the qr-code to always stay on-screen; [\033[32m0\033[0m]=disabled, [\033[32m1\033[0m]=with-url, [\033[32m2\033[0m]=just-qr")
ap2.add_argument("--qr-wait", metavar="SEC", type=float, default=0, help="wait \033[33mSEC\033[0m before printing the qr-code to the log")
ap2.add_argument("--qr-every", metavar="SEC", type=float, default=0, help="print the qr-code every \033[33mSEC\033[0m (try this with/without --qr-pin in case of issues)")
ap2.add_argument("--qr-winch", metavar="SEC", type=float, default=0, help="when --qr-pin is enabled, check for terminal size change every \033[33mSEC\033[0m")
ap2.add_argument("--qr-every", metavar="SEC", type=float, default=0, help="print the qr-code every \033[33mSEC\033[0m (try this with/without \033[33m--qr-pin\033[0m in case of issues)")
ap2.add_argument("--qr-winch", metavar="SEC", type=float, default=0, help="when \033[33m--qr-pin\033[0m is enabled, check for terminal size change every \033[33mSEC\033[0m")
ap2.add_argument("--qr-file", metavar="TXT", type=u, action="append", help="\033[34mREPEATABLE:\033[0m write qr-code to file.\n └─To create txt or svg, \033[33mTXT\033[0m is Filepath:Zoom:Pad, for example [\033[32mqr.txt:1:2\033[0m]\n └─To create png or gif, \033[33mTXT\033[0m is Filepath:Zoom:Pad:Foreground:Background, for example [\033[32mqr.png:8:2:333333:ffcc55\033[0m], or [\033[32mqr.png:8:2::ffcc55\033[0m] for transparent")
ap2.add_argument("--qr-stdout", action="store_true", help="always display the QR-code on STDOUT in the terminal, even if \033[33m-q\033[0m")
ap2.add_argument("--qr-stderr", action="store_true", help="always display the QR-code on STDERR in the terminal, even if \033[33m-q\033[0m")
@@ -1427,16 +1496,18 @@ def add_upload(ap):
ap2.add_argument("--reg-cap", metavar="N", type=int, default=38400, help="max number of uploads to keep in memory when running without \033[33m-e2d\033[0m; roughly 1 MiB RAM per 600")
ap2.add_argument("--no-fpool", action="store_true", help="disable file-handle pooling -- instead, repeatedly close and reopen files during upload (bad idea to enable this on windows and/or cow filesystems)")
ap2.add_argument("--use-fpool", action="store_true", help="force file-handle pooling, even when it might be dangerous (multiprocessing, filesystems lacking sparse-files support, ...)")
ap2.add_argument("--chmod-f", metavar="UGO", type=u, default="", help="unix file permissions to use when creating files; default is probably 644 (OS-decided), see --help-chmod. Examples: [\033[32m644\033[0m] = owner-RW + all-R, [\033[32m755\033[0m] = owner-RWX + all-RX, [\033[32m777\033[0m] = full-yolo (volflag=chmod_f)")
ap2.add_argument("--chmod-d", metavar="UGO", type=u, default="755", help="unix file permissions to use when creating directories; see --help-chmod. Examples: [\033[32m755\033[0m] = owner-RW + all-R, [\033[32m2750\033[0m] = setgid + owner-RW + group-R, [\033[32m777\033[0m] = full-yolo (volflag=chmod_d)")
ap2.add_argument("--chmod-f", metavar="UGO", type=u, default="", help="unix file permissions to use when creating files; default is probably 644 (OS-decided), see \033[33m--help-chmod\033[0m. Examples: [\033[32m644\033[0m] = owner-RW + all-R, [\033[32m755\033[0m] = owner-RWX + all-RX, [\033[32m777\033[0m] = full-yolo (volflag=chmod_f)")
ap2.add_argument("--chmod-d", metavar="UGO", type=u, default="755", help="unix file permissions to use when creating directories; see \033[33m--help-chmod\033[0m. Examples: [\033[32m755\033[0m] = owner-RW + all-R, [\033[32m2750\033[0m] = setgid + owner-RW + group-R, [\033[32m777\033[0m] = full-yolo (volflag=chmod_d)")
ap2.add_argument("--uid", metavar="N", type=int, default=-1, help="unix user-id to chown new files/folders to; default = -1 = do-not-change (volflag=uid)")
ap2.add_argument("--gid", metavar="N", type=int, default=-1, help="unix group-id to chown new files/folders to; default = -1 = do-not-change (volflag=gid)")
ap2.add_argument("--wram", action="store_true", help="allow uploading even if a volume is inside a ramdisk, meaning that all data will be lost on the next server reboot (volflag=wram)")
ap2.add_argument("--dedup", action="store_true", help="enable symlink-based upload deduplication (volflag=dedup)")
ap2.add_argument("--dedup", action="store_true", help="enable symlink-based upload deduplication; see \033[33m--help-dedup\033[0m (volflag=dedup)")
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("--redup", metavar="A[,B]=T", help="convert dedup-type for existing files; \033[33mA,B\033[0m is dedup-types to convert from (no/ref/sym/hard), \033[33mT\033[0m is target type; converting from sym/hard is fast-ish, from no/ref is slooow, to sym/hard is jank; see \033[33m--help-redup\033[0m. Example: [\033[32msym,hard=ref\033[0m] (volflag=redup)")
ap2.add_argument("--redup-dry", action="store_true", help="dry-run; makes \033[33m--redup\033[0m not apply changes (volflag=redup_dry)")
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-dupe-m", action="store_true", help="also reject dupes when moving a file into another volume (volflag=nodupem)")
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)")
@@ -1530,7 +1601,7 @@ def add_auth(ap):
ses_db = os.path.join(E.cfg, "sessions.db")
ap2 = ap.add_argument_group("IdP / identity provider / user authentication options")
ap2.add_argument("--idp-h-usr", metavar="HN", type=u, action="append", help="\033[34mREPEATABLE:\033[0m bypass the copyparty authentication checks if the request-header \033[33mHN\033[0m contains a username to associate the request with (for use with authentik/oauth/...)\n\033[1;31mWARNING:\033[0m if you enable this, make sure clients are unable to specify this header themselves; must be washed away and replaced by a reverse-proxy")
ap2.add_argument("--idp-hm-usr", metavar="T", type=u, action="append", help="\033[34mREPEATABLE:\033[0m bypass the copyparty authentication checks if the request-header \033[33mT\033[0m is provided, and its value exists in a mapping defined by this option; see --help-idp")
ap2.add_argument("--idp-hm-usr", metavar="T", type=u, action="append", help="\033[34mREPEATABLE:\033[0m bypass the copyparty authentication checks if the request-header \033[33mT\033[0m is provided, and its value exists in a mapping defined by this option; see \033[33m--help-idp\033[0m")
ap2.add_argument("--idp-h-grp", metavar="HN", type=u, default="", help="assume the request-header \033[33mHN\033[0m contains the groupname of the requesting user; can be referenced in config files for group-based access control")
ap2.add_argument("--idp-h-key", metavar="HN", type=u, default="", help="optional but recommended safeguard; your reverse-proxy will insert a secret header named \033[33mHN\033[0m into all requests, and the other IdP headers will be ignored if this header is not present")
ap2.add_argument("--idp-gsep", metavar="RE", type=u, default="|:;+,", help="if there are multiple groups in \033[33m--idp-h-grp\033[0m, they are separated by one of the characters in \033[33mRE\033[0m")
@@ -1542,7 +1613,7 @@ def add_auth(ap):
ap2.add_argument("--idp-login", metavar="L", type=u, default="", help="replace all login-buttons with a link to URL \033[33mL\033[0m (unless \033[32mpw\033[0m is in \033[33m--auth-ord\033[0m then both will be shown); [\033[32m{dst}\033[0m] expands to url of current page")
ap2.add_argument("--idp-login-t", metavar="T", type=u, default="Login with SSO", help="the label/text for the idp-login button")
ap2.add_argument("--idp-logout", metavar="L", type=u, default="", help="replace all logout-buttons with a link to URL \033[33mL\033[0m")
ap2.add_argument("--auth-ord", metavar="TXT", type=u, default="idp,ipu", help="controls auth precedence; examples: [\033[32mpw,idp,ipu\033[0m], [\033[32mipu,pw,idp\033[0m], see --help-auth-ord")
ap2.add_argument("--auth-ord", metavar="TXT", type=u, default="idp,ipu", help="controls auth precedence; examples: [\033[32mpw,idp,ipu\033[0m], [\033[32mipu,pw,idp\033[0m], see \033[33m--help-auth-ord\033[0m")
ap2.add_argument("--pw-hdr", metavar="NAME", type=u, default="pw", help="lowercase name of password-header (NAME: foo); \033[1;31mWARNING:\033[0m Changing this will break support for many clients")
ap2.add_argument("--pw-urlp", metavar="NAME", type=u, default="pw", help="lowercase name of password url-param (?NAME=foo); \033[1;31mWARNING:\033[0m Changing this will break support for many clients")
ap2.add_argument("--no-bauth", action="store_true", help="disable basic-authentication support; do not accept passwords from the 'Authenticate' header at all. NOTE: This breaks support for the android app")
@@ -1824,7 +1895,7 @@ def add_safety(ap):
ap2.add_argument("--csp-ui", metavar="TXT", default="script-src 'unsafe-eval' 'nonce-{{ js_nonce }}'; worker-src 'self'", help="content-security-policy to apply for the web-UI; default helps prevent XSS by blocking <script> / onclick / ... (volflag=csp_ui)")
ap2.add_argument("--csp-dl", metavar="TXT", default="", help="content-security-policy to apply for static files (volflag=csp_dl)")
ap2.add_argument("--no-script", action="store_true", help="disables javascript in html files; helps prevent XSS but kills interactive websites; this will override \033[33m--csp-dl\033[0m with [\033[32mscript-src 'none'\033[0m] (volflag=noscript)")
ap2.add_argument("--no-html", action="store_true", help="show html-files as plain text; helps prevent XSS but kills websites/blogs, also enables --no-script (volflag=nohtml)")
ap2.add_argument("--no-html", action="store_true", help="show html-files as plain text; helps prevent XSS but kills websites/blogs, also enables \033[33m--no-script\033[0m (volflag=nohtml)")
ap2.add_argument("--no-mime", action="store_true", help="disallow changing the response mimetype with url-parameter ?mime=... (volflag=nomime)")
ap2.add_argument("--vague-403", action="store_true", help="send 404 instead of 403 (security through ambiguity, very enterprise). \033[1;31mWARNING:\033[0m Not compatible with WebDAV")
ap2.add_argument("--force-js", action="store_true", help="don't send folder listings as HTML, force clients to use the embedded json instead -- slight protection against misbehaving search engines which ignore \033[33m--no-robots\033[0m")
@@ -1969,7 +2040,7 @@ def add_thumbnail(ap):
ap2.add_argument("--th-spec-cnv", metavar="T", type=u, default="it,itgz,itxz,itz,mdgz,mdxz,mdz,mo3,mod,s3m,s3gz,s3xz,s3z,xm,xmgz,xmxz,xmz,xpk", help="audio formats which provoke https://trac.ffmpeg.org/ticket/10797 (huge ram usage for s3xmodit spectrograms)")
ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz, epub=jpg.epub, kra=png.kra, ora=png.ora", help="audio/image formats to decompress before passing to ffmpeg")
ap2.add_argument("--th-extract", metavar="T", type=u, action="append", help="\033[34mREPEATABLE:\033[0m list of file extensions to thumbnail using a custom plugin (a python script); example: [\033[32mmdf,iso,dmg=/thumbs/diskimg.py\033[0m], see --help-thumb-ex")
ap2.add_argument("--th-extract", metavar="T", type=u, action="append", help="\033[34mREPEATABLE:\033[0m list of file extensions to thumbnail using a custom plugin (a python script); example: [\033[32mmdf,iso,dmg=/thumbs/diskimg.py\033[0m], see \033[33m--help-thumb-ex\033[0m")
ap2.add_argument("--th-extr-sz", metavar="M", type=int, default=16, help="max num megabytes to allow \033[33m--th-extract\033[0m plugins to extract from each file")
ap2.add_argument("--hot-th-extr", action="store_true", help="recompile extractors on each thumbnail extraction -- expensive but convenient when hacking on stuff")
@@ -2126,7 +2197,7 @@ def add_ui(ap, retry: int):
ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include in the filebrowser html")
ap2.add_argument("--js-other", metavar="L", type=u, default="", help="URL to additional JS to include in all other pages")
ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages (except for basic-browser); can be @PATH to send the contents of a file at PATH, and/or begin with %% to render as jinja2 template; \033[32m<script>\033[0m will not work, use \033[32m<script nonce=\"{{ js_nonce }}\">\033[0m (volflag=html_head)")
ap2.add_argument("--html-head-s", metavar="T", type=u, default="", help="text to append to the <head> of all HTML pages (except for basic-browser); similar to (and can be combined with) --html-head but only accepts static text (volflag=html_head_s)")
ap2.add_argument("--html-head-s", metavar="T", type=u, default="", help="text to append to the <head> of all HTML pages (except for basic-browser); similar to (and can be combined with) \033[33m--html-head\033[0m but only accepts static text (volflag=html_head_s)")
ap2.add_argument("--ih", action="store_true", help="if a folder contains index.html, show that instead of the directory listing by default (can be changed in the client settings UI, or add ?v to URL for override)")
ap2.add_argument("--textfiles", metavar="CSV", type=u, default="txt,nfo,diz,cue,readme", help="file extensions to present as plaintext")
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)")
@@ -2137,7 +2208,7 @@ def add_ui(ap, retry: int):
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-nb\033[0m")
ap2.add_argument("--ver", action="store_true", help="show version on the control panel (incompatible with \033[33m-nb\033[0m). This is the same as --ver-who all")
ap2.add_argument("--ver", action="store_true", help="show version on the control panel (incompatible with \033[33m-nb\033[0m). This is the same as \033[33m--ver-who all\033[0m")
ap2.add_argument("--ver-who", metavar="TXT", type=u, default="no", help="only show version for: [\033[32ma\033[0m]=admin-permission-anywhere, [\033[32mauth\033[0m]=authenticated, [\033[32mall\033[0m]=anyone")
ap2.add_argument("--du-who", metavar="TXT", type=u, default="all", help="only show disk usage for: [\033[32mno\033[0m]=nobody, [\033[32ma\033[0m]=admin-permission, [\033[32mrw\033[0m]=read-write, [\033[32mw\033[0m]=write, [\033[32mauth\033[0m]=authenticated, [\033[32mall\033[0m]=anyone (volflag=du_who)")
ap2.add_argument("--ver-iwho", type=int, default=0, help=argparse.SUPPRESS)
+2 -2
View File
@@ -1,8 +1,8 @@
# coding: utf-8
VERSION = (1, 20, 22)
VERSION = (1, 20, 24)
CODENAME = "sftp is fine too"
BUILD_DT = (2026, 9, 6)
BUILD_DT = (2026, 9, 19)
S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
+17 -2
View File
@@ -107,6 +107,7 @@ SEESLOG = " (see fileserver log for details)"
SSEELOG = " ({})".format(SEE_LOG)
BAD_CFG = "invalid config; {}".format(SEE_LOG)
SBADCFG = " ({})".format(BAD_CFG)
REDUP_E2 = "WARNING: [/%s] needs 'e2ds' for redup from 'ref' and/or to 'sym'"
PTN_U_GRP = re.compile(r"\$\{u(%[+-][^}]+)\}")
PTN_G_GRP = re.compile(r"\$\{g(%[+-][^}]+)\}")
@@ -2915,6 +2916,17 @@ class AuthSrv(object):
if ccs == "y":
vol.flags["bcasechk"] = True
ptn = re.compile(r"^(,no|,sym|,hard|,ref)+=(sym|hard|ref)$")
ptn2 = re.compile(r"(no|ref).*=|=sym")
for vol in vfs.all_nodes.values():
zs = vol.flags.get("redup")
if zs and not ptn.match("," + zs):
t = "invalid redup for volume /%s: %r"
self.log(t % (vol.vpath, zs), 1)
errors = True
if zs and "e2ds" not in vol.flags and ptn2.search(zs):
self.log(REDUP_E2 % (vol.vpath,), 3)
tags = self.args.mtp or []
tags = [x.split("=")[0] for x in tags]
tags = [y for x in tags for y in x.split(",")]
@@ -2957,6 +2969,9 @@ class AuthSrv(object):
errors = True
for vol in vfs.all_nodes.values():
for zs in vf_bmap().values():
if vol.flags.get(zs) in (None, False, ""):
vol.flags.pop(zs, None)
for k in list(vol.flags.keys()):
if re.match("^-[^-]+$", k):
vol.flags.pop(k)
@@ -3294,7 +3309,7 @@ class AuthSrv(object):
"dth3x": vf["th3x"],
"u2ts": vf["u2ts"],
"shr_who": vf["shr_who"],
"frand": bool(vf.get("rand")),
"frand": "rand" in vf,
"lifetime": vf.get("lifetime") or 0,
"unlist": vf.get("unlist") or "",
"sb_lg": "" if "no_sb_lg" in vf else (vf.get("lg_sbf") or "y"),
@@ -3352,7 +3367,7 @@ class AuthSrv(object):
"u2sz": self.args.u2sz,
"u2ts": vf["u2ts"],
"u2ow": vf["u2ow"],
"frand": bool(vf.get("rand")),
"frand": "rand" in vf,
"lifetime": vn.js_ls["lifetime"],
"u2sort": self.args.u2sort,
}
+4
View File
@@ -66,6 +66,7 @@ def vf_bmap() -> dict[str, str]:
"og_s_title",
"opds",
"rand",
"redup_dry",
"reflink",
"rm_partial",
"rmagic",
@@ -146,6 +147,7 @@ def vf_vmap() -> dict[str, str]:
"put_ck",
"put_name",
"readmes",
"redup",
"rotf_tz",
"mv_retry",
"rm_retry",
@@ -229,6 +231,8 @@ flagcats = {
"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",
"redup=A,B=T": "convert dedup-type for existing files",
"redup_dry": "dry-run mode for redup",
"noclone": "take dupe data from clients, even if available on HDD",
"nodupe": "rejects existing files (instead of linking/cloning them)",
"nodupem": "rejects existing files during moves as well",
+9 -5
View File
@@ -3251,6 +3251,8 @@ class HttpCli(object):
dst = vfs.canonical(rem)
try:
if not bos.path.isdir(dst):
if "nosub" in vfs.flags:
raise Pebkac(500, "no subdirectories allowed")
bos.makedirs(dst, vf=vfs.flags)
except OSError as ex:
self.log("makedirs failed %r" % (dst,))
@@ -3262,6 +3264,8 @@ class HttpCli(object):
raise Pebkac(400, "some file got your folder name")
raise Pebkac(500, min_ex())
except Pebkac:
raise
except:
raise Pebkac(500, min_ex())
@@ -3888,7 +3892,7 @@ class HttpCli(object):
rnd = 0
else:
rnd = int(self.uparam.get("rand") or self.headers.get("rand") or 0)
if vfs.flags.get("rand"): # force-enable
if "rand" in vfs.flags: # force-enable
rnd = max(rnd, vfs.flags["nrand"])
zs = self.uparam.get("life", self.headers.get("life", ""))
@@ -7079,17 +7083,16 @@ class HttpCli(object):
e2d = "e2d" in vn.flags
e2t = "e2t" in vn.flags
og_fn = ""
add_og = "og" in vn.flags
if add_og:
if "th" in self.uparam or "raw" in self.uparam or "opds" in self.uparam:
add_og = False
elif vn.flags["og_ua"]:
add_og = vn.flags["og_ua"].search(self.ua)
og_fn = ""
if "v" in self.uparam:
add_og = True
og_fn = ""
if "b" in self.uparam and "norobots" not in vn.flags:
self.out_headers["X-Robots-Tag"] = "noindex, nofollow"
@@ -7610,6 +7613,7 @@ class HttpCli(object):
return self.tx_file("oh_f", ap) # is no-cache
if icur:
assert idx # type: ignore # !rm
mte = vn.flags.get("mte") or {}
tagset: set[str] = set()
rd = vrem
@@ -7978,7 +7982,7 @@ class HttpCli(object):
else:
j2a["og_url"] = j2a["og_raw"] = url_base
if not vn.flags.get("og_no_head"):
if "og_no_head" not in vn.flags:
ogh = {"twitter:card": "summary"}
title = str(vn.flags.get("og_title") or "")
@@ -8047,7 +8051,7 @@ class HttpCli(object):
while title.endswith(" - "):
title = title[:3]
if vn.flags.get("og_s_title") or not title:
if "og_s_title" in vn.flags or not title:
title = str(vn.flags.get("og_title") or "")
for tag, hname in tagmap.items():
+3
View File
@@ -87,6 +87,9 @@ class TcpSrv(object):
successful_binds = 0
try:
for ipa in ips:
if ipa == "no":
self.args.ign_ebind_all = True
continue
try:
self._listen(ipa, port)
ok[ipa].append(port)
+2 -2
View File
@@ -201,11 +201,11 @@ try:
# reduces glibc RAM usage from 4.7 to 3.5 GiB ...yep, still bonkers
os.environ["VIPS_CONCURRENCY"] = "1"
HAVE_VIPS = True
import pyvips
pyvips.cache_set_max(0)
logging.getLogger("pyvips").setLevel(logging.WARNING)
HAVE_VIPS = True
except Exception as e:
HAVE_VIPS = False
if not isinstance(e, ImportError):
@@ -219,10 +219,10 @@ try:
if PRTY_NO_RAWPY:
raise Exception()
HAVE_RAWPY = True
import rawpy
logging.getLogger("rawpy").setLevel(logging.WARNING)
HAVE_RAWPY = True
except:
HAVE_RAWPY = False
+191 -41
View File
@@ -17,7 +17,7 @@ from copy import deepcopy
from queue import Queue
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, UNIX, WINDOWS, E
from .authsrv import LEELOO_DALLAS, SEESLOG, VFS, AuthSrv
from .authsrv import LEELOO_DALLAS, REDUP_E2, SEESLOG, VFS, AuthSrv
from .bos import bos
from .cfg import vf_bmap, vf_cmap, vf_vmap
from .fsutil import Fstab
@@ -887,13 +887,10 @@ class Up2k(object):
def _expr_idx_filter(self, flags: dict[str, Any]) -> tuple[bool, dict[str, Any]]:
if not self.no_expr_idx:
return False, flags
return False, dict(flags)
ret = {k: v for k, v in flags.items() if not k.startswith("e2t")}
if len(ret) == len(flags):
return False, flags
return True, ret
return len(ret) != len(flags), flags
def init_indexes(
self, all_vols: dict[str, VFS], scan_vols: list[str], fscan: bool, gid: int = 0
@@ -912,7 +909,7 @@ class Up2k(object):
with self.mutex:
if gid != self.gid:
return False
return
if self.pp:
continue
@@ -951,6 +948,9 @@ class Up2k(object):
with self.mutex, self.reg_mutex:
# only need to protect register_vpath but all in one go feels right
for vol in vols:
if vol.realpath:
self.flags[vol.realpath] = dict(vol.flags)
if bos.path.isfile(vol.realpath):
self.volstate[vol.vpath] = "online (just-a-file)"
t = "NOTE: volume [/%s] is a file, not a folder"
@@ -1000,6 +1000,21 @@ class Up2k(object):
with self.mutex, self.reg_mutex:
self._drop_caches()
def setstate(vol, n):
t = ""
if n < 1 and "e2v" in vol.flags:
t += "+e2v"
if n < 2 and "redup" in vol.flags:
t += "+redup"
if n < 3 and "e2ts" in vol.flags:
t += "+tags"
if t:
t = "online (%s pending)" % (t[1:],)
else:
t = "online, idle"
self.volstate[vol.vpath] = t
for vol in vols:
if self.stop or gid != self.gid:
break
@@ -1016,26 +1031,16 @@ class Up2k(object):
if vac:
need_vac[vol] = True
if "e2v" in vol.flags:
t = "online (integrity-check pending)"
elif "e2ts" in vol.flags:
t = "online (tags pending)"
else:
t = "online, idle"
self.volstate[vol.vpath] = t
setstate(vol, 0)
self._unblock()
# file contents verification
for vol in vols:
if self.stop:
break
if "e2v" not in vol.flags:
if self.stop or "e2v" not in vol.flags:
continue
t = "online (verifying integrity)"
t = "online (doing e2v)"
self.volstate[vol.vpath] = t
self.log("{} [{}]".format(t, vol.realpath))
@@ -1044,12 +1049,23 @@ class Up2k(object):
self.log("modified {} entries in the db".format(nmod), 3)
need_vac[vol] = True
if "e2ts" in vol.flags:
t = "online (tags pending)"
else:
t = "online, idle"
setstate(vol, 1)
# convert dedup type
for vol in vols:
if self.stop or not vol.flags.get("redup"):
continue
t = "online (doing redup)"
self.volstate[vol.vpath] = t
self.log("{} [{}]".format(t, vol.realpath))
try:
with self.mutex, self.reg_mutex:
self._redup(vol, vols)
except:
self.log("redup failed: " + min_ex(), 1)
setstate(vol, 2)
# open the rest + do any e2ts(a)
needed_mutagen = False
@@ -1084,7 +1100,7 @@ class Up2k(object):
cur.execute("vacuum")
if self.stop:
return False
return
for vol in all_vols.values():
if vol.flags["dbd"] == "acid":
@@ -1114,7 +1130,7 @@ class Up2k(object):
self.log("checkpoint failed: {}".format(ex), 3)
if self.stop:
return False
return
self.pp.end = True
@@ -1158,7 +1174,7 @@ class Up2k(object):
vpath = k
_, flags = self._expr_idx_filter(flags)
n4g = bool(flags.get("noforget"))
n4g = "noforget" in flags
ft = "\033[0;32m{}{:.0}"
ff = "\033[0;35m{}{:.0}"
@@ -1297,11 +1313,14 @@ class Up2k(object):
self.log(t % (vpath, ex), 1)
return None
if dir_is_empty(self.log_func, not self.args.no_scandir, histpath) and not (
ANYWIN or UNIX or "hist_cow" in flags
if not (
ANYWIN or UNIX or "hist_cow" in flags or "up2k.db" in os.listdir(histpath)
):
try:
runcmd([b"chattr", b"+C", fsenc(histpath)], 1)
if self.fstab.get(ptop)[0] != "btrfs":
raise Exception()
runcmd([b"chattr", b"-c", fsenc(histpath)], 1) # compression and...
runcmd([b"chattr", b"+C", fsenc(histpath)], 1) # no-cow are incompat
except:
pass
@@ -1388,14 +1407,15 @@ class Up2k(object):
top = vol.realpath
rei = vol.flags.get("noidx")
reh = vol.flags.get("nohash")
n4g = bool(vol.flags.get("noforget"))
n4g = "noforget" in vol.flags
ffat = "fat32" in vol.flags
cst = bos.stat(top)
dev = cst.st_dev if vol.flags.get("xdev") else 0
dev = cst.st_dev if "xdev" in vol.flags else 0
with self.mutex:
with self.reg_mutex:
reg = self.register_vpath(top, vol.flags)
self.flags[vol.realpath] = dict(vol.flags)
assert reg and self.pp # !rm
cur, db_path = reg
@@ -1450,7 +1470,7 @@ class Up2k(object):
[],
cst,
dev,
bool(vol.flags.get("xvol")),
"xvol" in vol.flags,
)
if not n4g:
n_rm = self._drop_lost(db.c, top, excl)
@@ -2088,6 +2108,127 @@ class Up2k(object):
return len(rewark) + len(f404)
def _redup(self, vol: VFS, vols: list[VFS]) -> None:
logmsg = "redup: replacing %r with %r"
dry = "redup_dry" in vol.flags
if dry:
logmsg += " #dry"
zs, to = vol.flags["redup"].split("=")
zsl = zs.split(",")
cref = "no" in zsl or "ref" in zsl
csym = "sym" in zsl
chard = "hard" in zsl
chard0 = chard
vf = vol.flags.copy()
vf["dedup"] = 1
for zs in "hardlink hardlinkonly reflink".split():
vf.pop(zs, None)
if to == "ref":
vf["reflink"] = 1
elif to == "hard":
vf["hardlinkonly"] = vf["hardlink"] = 1
elif to == "sym":
pass
else:
raise Exception("target type not implemented: " + to)
if (csym or chard) and to != "sym":
self.log("redup: stage 1 begin")
if to == "hard":
chard = False
for x in vol.walk("", "", [], LEELOO_DALLAS, [[]], 2, True, True, True):
vn, _, _, atop, lsf, lsd, lsv = x
if vn is not vol:
lsd[:] = []
lsv.clear()
continue
for fn, st in lsf:
if (csym and stat.S_ISLNK(st.st_mode)) or (
chard and st.st_nlink > 1
):
ap = os.path.join(atop, fn)
self.log(logmsg % (ap, absreal(ap)))
if dry:
continue
ap2 = tempfile.NamedTemporaryFile(
prefix="r,", dir=atop, delete=False
).name
self._symlink(ap, ap2, vf, True, True, st.st_mtime)
st1 = bos.stat(ap)
st2 = bos.lstat(ap2)
if st1.st_size != st2.st_size or stat.S_ISLNK(st2.st_mode):
wunlink(self.log, ap2, vf)
raise Exception("redup: clone failed; giving up")
wunlink(self.log, ap, vf)
bos.rename(ap2, ap)
if cref or to == "sym":
self.log("redup: stage 2 begin")
if "e2ds" not in vol.flags:
self.log(REDUP_E2 % (vol.vpath,), 3)
cur = self.cur[vol.realpath]
curs = [(vol, cur.connection.cursor())]
if to != "sym":
for v2 in vols:
if vol is not v2 and v2.realpath in self.cur:
curs += [(v2, self.cur[v2.realpath])]
nrem = cur.execute("select count(w) from up").fetchone()[0]
self.log("redup [/%s]: %d files left" % (vol.vpath, nrem))
q2 = "select rd, fn from up where substr(w,1,16) = ? and w=?"
for w, rd, fn in cur.execute("select w, rd, fn from up"):
w16 = w[:16]
for v2, c2 in curs:
hit = c2.execute(q2, (w16, w)).fetchone()
if not hit:
continue
rd2, fn2 = hit
if fn == fn2 and rd == rd2 and vol is v2:
continue
apt = ""
try:
rd, fn = s3dec(rd, fn)
rd2, fn2 = s3dec(rd2, fn2)
fp1 = os.path.join(vol.realpath, rd, fn)
fp2 = os.path.join(v2.realpath, rd2, fn2)
st1 = bos.lstat(fp1)
if fp1 == fp2 or stat.S_ISLNK(st1.st_mode):
continue # self(?), or redup failed during walk
ap1 = absreal(fp1)
ap2 = absreal(fp2)
st2 = bos.lstat(ap2)
if stat.S_ISLNK(st2.st_mode):
continue # dead hit
for ap in (ap1,) if ap1 == ap2 else (ap1, ap2):
self.log("redup: integrity-checking %r" % (ap,))
zsl, st = self._hashlist_from_file(ap)
w2 = up2k_wark_from_hashlist(self.salt, st.st_size, zsl)
if w2 != w:
t = "db desync:\n%s %r\n%s db"
raise Exception(t % (w2, ap, w))
self.log(logmsg % (ap1, ap2))
if dry:
continue
apt = tempfile.NamedTemporaryFile(
prefix="r,", dir=os.path.dirname(fp1), delete=False
).name
self._symlink(ap2, apt, vf, True, True, st1.st_mtime)
zsl, st = self._hashlist_from_file(apt)
w2 = up2k_wark_from_hashlist(self.salt, st.st_size, zsl)
if w != w2:
raise Exception("bad checksum after copy?!")
wunlink(self.log, ap1, vf)
bos.rename(apt, ap1)
break
except Exception as ex:
self.log("redup: skipping match due to " + str(ex))
if apt and os.path.exists(apt):
os.unlink(apt)
continue
def _build_tags_index(self, vol: VFS) -> tuple[int, int, bool]:
ptop = vol.realpath
with self.mutex, self.reg_mutex:
@@ -3075,17 +3216,20 @@ class Up2k(object):
jcur = self.cur.get(ptop)
reg = self.registry[ptop]
vfs = self.vfs.all_vols[cj["vtop"]]
n4g = bool(vfs.flags.get("noforget"))
noclone = bool(vfs.flags.get("noclone"))
rand = vfs.flags.get("rand") or cj.get("rand")
n4g = "noforget" in vfs.flags
noclone = "noclone" in vfs.flags
rand = "rand" in vfs.flags or cj.get("rand")
lost: list[tuple["sqlite3.Cursor", str, str]] = []
safe_dedup = vfs.flags.get("safededup") or 50
data_ok = safe_dedup < 10 or n4g
vols = [(ptop, jcur)] if jcur else []
if vfs.flags.get("xlink"):
if vols and ("xlink" in vfs.flags or "reflink" in vfs.flags):
vols += [(k, v) for k, v in self.cur.items() if k != ptop]
if "xlink" not in vfs.flags:
zs = self.fstab.get(ptop)[1]
vols = [x for x in vols if self.fstab.get(x[0])[1] == zs]
if noclone:
wark = up2k_wark_from_metadata(
@@ -3575,7 +3719,7 @@ class Up2k(object):
st = bos.stat(fp)
try:
vrel = vjoin(job["prel"], fname)
xlink = bool(vf.get("xlink"))
xlink = "xlink" in vf
cur, wark, _, _, _, _, _ = self._find_from_vpath(ptop, vrel)
self._forget_file(ptop, vrel, vf, cur, wark, True, st.st_size, xlink)
except Exception as ex:
@@ -3630,7 +3774,7 @@ class Up2k(object):
if rm and bos.path.exists(dst):
wunlink(self.log, dst, flags)
if not is_mv and not flags.get("dedup"):
if not is_mv and "dedup" not in flags:
raise Exception("dedup is disabled in config")
if "reflink" in flags:
@@ -4331,7 +4475,7 @@ class Up2k(object):
cur = None
try:
ptop = dbv.realpath
xlink = bool(dbv.flags.get("xlink"))
xlink = "xlink" in dbv.flags
cur, wark, _, _, _, _, _ = self._find_from_vpath(ptop, volpath)
self._forget_file(
ptop, volpath, dbv.flags, cur, wark, True, st.st_size, xlink
@@ -4535,6 +4679,12 @@ class Up2k(object):
self.log("not found in src db: %r" % (svp,))
try:
if svn_dbv != dvn and not (
"xlink" in dvn.flags
or "reflink" in dvn.flags
or "hardlinkonly" in dvn.flags
):
raise OSError(errno.EXDEV, "different volumes")
if is_link and st != stl:
# relink non-broken symlinks to still work after the move,
# but only resolve 1st level to maintain relativity
@@ -4812,7 +4962,7 @@ class Up2k(object):
raise Pebkac(400, t)
self._copy_tags(c1, c2, w)
xlink = bool(svn.flags.get("xlink"))
xlink = "xlink" in svn.flags
with self.reg_mutex:
has_dupes = self._forget_file(
+7 -4
View File
@@ -188,6 +188,7 @@ try:
HAVE_IFADDR = True
except:
HAVE_IFADDR = False
IFADDR_VND = False
def get_adapters(include_unconfigured=False):
return []
@@ -288,7 +289,6 @@ try:
raise Exception()
socket.inet_pton(socket.AF_INET6, "::1")
HAVE_IPV6 = True
if GRAAL:
try:
@@ -303,13 +303,15 @@ try:
return _inet_pton(fam, ip)
socket.inet_pton = inet_pton
HAVE_IPV6 = True
except:
HAVE_IPV6 = False
def inet_pton(fam, ip):
return socket.inet_aton(ip)
socket.inet_pton = inet_pton
HAVE_IPV6 = False
try:
@@ -450,6 +452,7 @@ IMPLICATIONS = [
["no_logue", "no_logues"], # user-typo
["nologues", "no_logues"], # user-typo
["nologue", "no_logues"], # user-typo
["nw", "redup_dry"],
["plainlogue", "plainlogues"], # user-typo
["plainreadmes", "plainreadme"], # user-typo
["noscript", "plainlogues"],
@@ -4049,9 +4052,9 @@ def runihook(
}
for x, vp, ap in zip(ups, vps, aps)
]
sp_ka["sin"] = json.dumps(ja).encode("utf-8", "replace")
sp_ka["sin"] = json.dumps(ja).encode("utf-8", "replace") + b"\n"
else:
sp_ka["sin"] = b"\n".join(fsenc(x) for x in aps)
sp_ka["sin"] = b"\n".join(fsenc(x) for x in aps) + b"\n"
if acmd[0].startswith("zmq:"):
try:
+31 -13
View File
@@ -3559,7 +3559,7 @@ function eval_hash() {
if (hash0 && window.og_fn) {
var all = msel.getall(), mi;
for (var a = 0; a < all.length; a++)
if (og_fn == uricom_dec(vsplit(all[a].vp)[1].split('?')[0])) {
if (og_fn == all[a].fn) {
mi = all[a];
break;
}
@@ -4254,9 +4254,6 @@ var fileman = (function () {
s2d[a] = all.indexOf(sel[a]);
var vp = sel[a].vp;
if (vp.endsWith('/'))
vp = vp.slice(0, -1);
var vsp = vsplit(vp);
if (base != vsp[0])
return toast.err(0, esc('bug:\n' + base + '\n' + vsp[0]));
@@ -4601,6 +4598,9 @@ var fileman = (function () {
if (err !== 'xbd')
toast.ok(2, L.fd_ok);
if (QS('#seldoc.sel'))
thegrid.setvis(true);
treectl.goto();
return;
}
@@ -5077,11 +5077,22 @@ var showfile = (function () {
r.nmap = {
'dockerfile': 'docker'
};
r.pmap = {
'sh': 'bash',
'ash': 'bash',
'dash': 'bash',
'luajit': 'lua',
'node': 'js',
};
var x = txt_ext + ' ans c cfg conf cpp cs css diff glsl go html ini java js json jsx kt kts latex less lisp lua makefile md nasm nim nix py r rss rb ruby sass scss sql svg swift tex toml ts vhdl xml yaml zig';
x = x.split(/ +/g);
for (var a = 0; a < x.length; a++)
if (!r.map["." + x[a]])
r.map["." + x[a]] = x[a];
x = 'awk bash lua make perl python ruby swift';
x = x.split(/ +/g);
for (var a = 0; a < x.length; a++)
r.pmap[x[a]] = x[a];
r.sname = function (srch) {
return srch.split(/[?&]doc=/)[1].split('&')[0];
@@ -5090,7 +5101,7 @@ var showfile = (function () {
if (window.og_fn) {
var ext = og_fn.split(/\./g).pop();
if (r.map['.' + ext])
hist_replace(get_evpath() + '?doc=' + og_fn);
hist_replace(get_evpath() + '?doc=' + uricom_enc(og_fn));
}
window.Prism = { 'manual': true };
@@ -5101,7 +5112,7 @@ var showfile = (function () {
var m = /[?&]doc=([^&]+)/.exec(location.search);
if (m) {
setTimeout(function () {
r.show(uricom_dec(m[1]), true);
r.show(m[1], true);
}, 1);
}
}
@@ -5121,11 +5132,17 @@ var showfile = (function () {
return !!/[?&]doc=/.exec(location.search);
};
r.getlang = function (fn) {
r.getlang = function (fn, txt) {
fn = fn.toLowerCase();
var ext = fn.slice(fn.lastIndexOf('.'));
return r.map[ext] || r.nmap[fn];
}
var prog = txt && txt.startsWith('#!') && txt.split('\n')[0].replace(
'#!/usr/bin/env', '').replace(
'#!/usr/bin/', '').replace(
'#!/bin/', '').trim().split(' ')[0];
if (prog)
prog = r.pmap[prog] || r.pmap[prog.replace(/[0-9].*/, '')];
return r.map[ext] || r.nmap[fn] || prog;
};
r.addlinks = function () {
r.files = [];
@@ -5138,7 +5155,7 @@ var showfile = (function () {
if (!lang)
continue;
r.files.push({ 'id': link.id, 'name': uricom_dec(fn) });
r.files.push({ 'id': link.id, 'name': link.fn });
var ah = ebi(link.id),
td = ah.closest('tr').getElementsByTagName('td')[0];
@@ -5274,7 +5291,7 @@ var showfile = (function () {
txt = doc[2],
name = url.split('?')[0].split('/').pop(),
tname = uricom_dec(name),
lang = r.getlang(name),
lang = r.getlang(name, txt),
is_md = lang == 'md';
ebi('files').style.display = ebi('gfiles').style.display = ebi('lazy').style.display = ebi('pro').style.display = ebi('epi').style.display = 'none';
@@ -5463,7 +5480,7 @@ var showfile = (function () {
sel = false;
for (var b = 0; b < sels.length; b++)
if (vsplit(sels[b].vp)[1] == lin)
if (sels[b].fn == lin)
sel = true;
clmod(lis[a], 'hl', lin == fn);
@@ -8768,6 +8785,7 @@ var msel = (function () {
item.id = links[a].getAttribute('id');
item.sel = clgot(links[a].closest('tr'), 'sel');
item.vp = href.indexOf('/') !== -1 ? href : vbase + href;
item.fn = uricom_dec(href.split('/').pop());
if (dk) {
var m = /[?&](k=[^&#]+)/.exec(qhref);
@@ -9912,7 +9930,7 @@ var rcm = (function () {
clmod(ebi('rtxt'), 'hide', !selFile.id);
clmod(ebi('rs1'), 'hide', !selFile.path);
clmod(ebi('rmd'), 'hide', !selFile.name || ext != "md");
clmod(ebi('rwopi'), 'hide', !selFile.name || !wopi_set.has(ext));
clmod(ebi('rwopi'), 'hide', !selFile.name || !wopi_set || !wopi_set.has(ext));
clmod(ebi('rcpl'), 'hide', !selFile.path);
clmod(ebi('rdl'), 'hide', !has_sel);
clmod(ebi('rzip'), 'hide', !has_sel);
+14 -9
View File
@@ -1012,6 +1012,11 @@ function up2k_init(subtle) {
return true;
}
function unxhr(xhr) {
xhr.onload = xhr.onerror = xhr.ontimeout = null;
apop(st.xhr, xhr);
}
var pvis = new U2pvis("bz", '#u2cards', uc, st),
donut = new Donut(uc, st);
@@ -1934,10 +1939,8 @@ function up2k_init(subtle) {
if (!st.deaf)
console.log('xhr: bad apple');
xhr.onerror = xhr.ontimeout = null;
xhr.onload();
xhr.abort();
st.deaf = true;
xhr.onload();
}
if (st.bytes.inflight && (st.bytes.inflight < 0 || !st.busy.upload.length)) {
@@ -2498,12 +2501,12 @@ function up2k_init(subtle) {
if (!toast.visible)
toast.warn(9.98, L.u_enethd + "\n\nfile: " + esc(t.name), t);
apop(st.xhr, xhr);
unxhr(xhr);
apop(st.busy.head, t);
st.todo.head.unshift(t);
};
function orz(e) {
apop(st.xhr, xhr);
unxhr(xhr);
if (t.done)
return console.log('done; skip head2', t.name, t);
@@ -2571,7 +2574,7 @@ function up2k_init(subtle) {
var xhr = new XHR();
xhr.onerror = xhr.ontimeout = function () {
apop(st.xhr, xhr);
unxhr(xhr);
if (t.t_busied != me) // t.done ok
return console.log('zombie handshake onerror', t.name, t);
@@ -2584,7 +2587,7 @@ function up2k_init(subtle) {
t.keepalive = keepalive;
};
var orz = function (e) {
apop(st.xhr, xhr);
unxhr(xhr);
if (t.t_busied != me || t.done)
return console.log('zombie handshake onload', t.name, t);
@@ -2963,7 +2966,7 @@ function up2k_init(subtle) {
pcar == pcdr ? pcar : ('' + pcar + '~' + pcdr);
var orz = function (xhr) {
apop(st.xhr, xhr);
unxhr(xhr);
st.bytes.inflight -= xhr.bsent;
var txt = unpre((xhr.response && xhr.response.err) || xhr.responseText);
if (txt.indexOf('upload blocked by x') + 1) {
@@ -3014,7 +3017,9 @@ function up2k_init(subtle) {
orz2(xhr);
}
var orz2 = function (xhr) {
apop(st.xhr, xhr);
unxhr(xhr);
if (st.deaf)
xhr.abort();
apop(st.busy.upload, upt);
for (var a = pcar; a <= pcdr; a++)
apop(t.postlist, a);
+54
View File
@@ -1,3 +1,57 @@
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2026-0906-2249 `v1.20.23` rcm once again
## 🩹 bugfixes
* the custom right-click-menu didn't like being enabled ad9994be
* this release is a hotfix for that; see v1.20.22 for all the other new stuff
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2026-0906-2112 `v1.20.22` hello fedora
## 🧪 new features
* iPhone: new bug in iOS breaks uploading; add workaround 3ea671ff
* apple broke XHR/fetch in a recent iOS version by introducing some wtf race-conditions in response handling; under high network load, the browser simply forgets to tell js that there's a response, so we're basically flying blind
* this workaround makes iOS uploads 50x faster than before but still not perfect (impossible given the situation); will be stuttery until apple fixes iOS
* apple will probably fix it very soon given the severity of the bug, but at least one copyparty user is forever-stuck on iOS-18.x which will never be fixed by apple, so a workaround is justified
* #1617 new plugin to thumbnail office documents with collabora (thx @kamaeff!) d4a1ce48
* new hook: [phonecam-sorter.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/phonecam-sorter.py) to automate organizing of pics/vids synced from phone to nas 9de09026
* dirkeys: allow non-recursive download-as-zip with just `dk` 3e3401f4
* `--no-mime` / volflag `nomime` disables `?mime=` for specifying custom response mimetype 370a05ab
* btrfs-specific: nocow .hist to improve sqlite performance dd9cf645
## 🩹 bugfixes
* two low-severity vulns in different components, but surprisingly similar synopses:
* GHSA-mc69-pxc8-4xf4 dirkeys (volflag `dk`) did not prevent descending into subdirs if an attacker could guess the name of the subdir 55969b8e
* GHSA-3fhv-rhjw-7hrg sftp did not fully enforce volflags xvol/xdev; an attacker could read a file inside the symlink destination if they could guess the name inside b80a210e
* not important enough to be listed in "recent important news", but will be detected by the (default-disabled) [version-checker](https://github.com/9001/copyparty/#version-checker)
* #1628 fix http206 range-request for last-n-bytes bdf8b793
* #1610 autogrid didn't count jxl images (thx @sylfn!) 7796c2d0
* when running without `e2d`, a config-reload would block uploads 0767d956
* really old chrome versions (before ver.62) was only able to upload over https ab791921
## 🔧 other changes
* #1632 connect-page: adjust rclone commands to support long passwords d7d30f26
* packaging: fix jank in source tarballs b854b8f5
* packaging: don't list licenses of unvendored modules 704af697
* #1631 systemd-examples: move config to `/etc/copyparty.conf` 185087ee
* reduce binary-garbage in logs from scrapers/scanners f00d19c5
* sfx: prefer `~/.cache/` (set `PRTY_XD=/tmp` to override) f870cf3c
* sfx: mention https://copyparty.eu/sfx-wtf/ in the header 80165120
* reduce complaining in log about default/unsafe tls-certs when not relevant 80cf7bfd
## 🌠 fun facts
* #887 copyparty has been packaged for Fedora 44! And EPEL-10 is on the way too... Thx @supakeen o/
* [verified at RevSpace NL](https://a.ocv.me/pub/g/2026/09/20260901_151949.jpg?cache)
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2026-0817-2157 `v1.20.21` thumbex
+1 -1
View File
@@ -49,7 +49,7 @@ echo warming up cache
cat 1 >/dev/null
echo ok lets go
$pybin "$sfx" -p39204 -e2dsa --dbd=yolo --exit=idx -lo=t -q "$@" && err= || err=$?
$pybin "$sfx" -i no -e2dsa --dbd=yolo --exit=idx -lo=t -q "$@" && err= || err=$?
[ $win ] && [ $err = 15 ] && err= # sigterm doesn't hook on windows, ah whatever
[ $err ] && echo ERROR $err && exit $err
+1 -1
View File
@@ -2,7 +2,7 @@ FROM alpine:3.23
WORKDIR /z
ENV ver_hashwasm=4.12.0 \
ver_marked=4.3.0 \
ver_dompf=3.4.6 \
ver_dompf=3.4.15 \
ver_mde=2.18.0 \
ver_codemirror=5.65.18 \
ver_fontawesome=5.13.0 \
+1 -1
View File
@@ -15,7 +15,7 @@ command -v gsort >/dev/null && {
cd ../dist
kwds='-bind -accounts -auth -auth-ord -flags -thumb-ex -handlers -hooks -idp -urlform -exp -rlo -ls -dbd -chmod -pwhash -zm'
kwds='-bind -accounts -auth -auth-ord -flags -dedup -redup -thumb-ex -handlers -hooks -idp -urlform -exp -rlo -ls -dbd -chmod -pwhash -zm'
export PRTY_FULL_HELP=1
export PRTY_NO_PARAMIKO=1
+1 -1
View File
@@ -8,7 +8,7 @@ import subprocess as sp
# to convert the copyparty --help to html, run this in xfce4-terminal @ 140x43:
_ = r""""
echo; for a in '' -bind -accounts -auth -auth-ord -flags -handlers -hooks -idp -urlform -exp -ls -dbd -chmod -pwhash -zm; do
echo; for a in '' -bind -accounts -auth -auth-ord -flags -dedup -redup -thumb-ex -handlers -hooks -idp -urlform -exp -rlo -ls -dbd -chmod -pwhash -zm; do
./copyparty-sfx.py --help$a 2>/dev/null; printf '\n\n\n%0139d\n\n\n'; done # xfce4-terminal @ 140x43
"""
# click [edit] => [select all]
+1 -1
View File
@@ -29,7 +29,7 @@ exit 0
# first open an infinitely wide console (this is why you own an ultrawide) and copypaste this into it:
for a in '' -bind -accounts -auth -auth-ord -flags -handlers -hooks -idp -urlform -exp -ls -dbd -chmod -pwhash -zm; do
for a in '' -bind -accounts -auth -auth-ord -flags -dedup -redup -thumb-ex -handlers -hooks -idp -urlform -exp -rlo -ls -dbd -chmod -pwhash -zm; do
./copyparty-sfx.py --help$a 2>/dev/null; printf '\n\n\n%0255d\n\n\n'; done
# then copypaste all of the output by pressing ctrl-shift-a, ctrl-shift-c
+3
View File
@@ -70,6 +70,9 @@ done
[ "$1" ] || {
python3 ../scripts/test/smoketest.py &
pids+=($!)
bash ../scripts/test/redup.sh &
pids+=($!)
}
for pid in ${pids[@]}; do
+104
View File
@@ -0,0 +1,104 @@
#!/bin/bash
set -e
td=redump-test
st_orig() { cat <<'EOF'
f1,1,'f1',3660
f2,1,'f2',3720
f3,2,'f3',3960
f4,1,'f4',3840
f5,1,'f5' -> 'f2',3900
f6,2,'f6',3960
f7,1,'f7',4020
f8,1,'f8',4080
f9,1,'f9',4140
EOF
}
st_ref() { cat <<'EOF'
f1,1,'f1',3660
f2,1,'f2',3720
f3,1,'f3',3960
f4,1,'f4',3840
f5,1,'f5',3900
f6,1,'f6',3960
f7,1,'f7',4020
f8,1,'f8',4080
f9,1,'f9',4140
EOF
}
st_sym() { cat <<'EOF'
f1,1,'f1',3660
f2,1,'f2',3720
f3,1,'f3',3960
f4,1,'f4',3840
f5,1,'f5' -> 'f2',3900
f6,1,'f6' -> 'f3',3960
f7,1,'f7' -> 'f4',4020
f8,1,'f8' -> 'f1',4080
f9,1,'f9' -> 'f1',4140
EOF
}
st_hard() { cat <<'EOF'
f1,3,'f1',3600
f2,2,'f2',3600
f3,2,'f3',3600
f4,2,'f4',3600
f5,2,'f5',3600
f6,2,'f6',3600
f7,2,'f7',3600
f8,3,'f8',3600
f9,3,'f9',3600
EOF
}
statchk() {
(cd $td;stat -c%n,%h,%N,%Y * | sort -n | diff -U9 <($1) -) && return
cat $td/* | grep -vE '^f1f2f3f4f2f3f4f1f1$' || return
cat $td.l
exit 1
}
setup() {
rm -rf $td
mkdir $td
th=19700101010 #..M
tu=978310800 #unix
( cd "$td"
for n in {1..4}; do echo -n f$n > f$n; done
ln -s f2 f5
ln f3 f6
cp --reflink=always f4 f7
cp --reflink=never f1 f8
cp --reflink=never f1 f9
for n in {1..9}; do touch -ht $th$n f$n; done
);statchk st_orig
}
export PRTY_NO_ARGON2=1
export PRTY_NO_CFSSL=1
export PRTY_NO_FFMPEG=1
export PRTY_NO_FFPROBE=1
export PRTY_NO_IFADDR=1
export PRTY_NO_IMPRESO=1
export PRTY_NO_MAGIC=1
export PRTY_NO_PARAMIKO=1
export PRTY_NO_PARTFTPY=1
export PRTY_NO_PIL=1
export PRTY_NO_PSUTIL=1
export PRTY_NO_PYFTPD=1
export PRTY_NO_RAW=1
export PRTY_NO_VIPS=1
cmd="python -m copyparty -i no --ign-ebind-all --no-ses --no-fastboot --no-voldump --exit=idx --ansi -v $td::A"
fs=$(stat -fc%T .)
if [ "$fs" = btrfs ]; then
echo ref:; setup; $cmd -e2dsa --redup sym,hard,ref=ref >$td.l; statchk st_ref
else
printf 'detected filesystem [%s] which is not btrfs, will NOT run reflink test\n' "$fs"
fi
echo sym:; setup; $cmd -e2dsa --redup sym,hard,ref=sym >$td.l; statchk st_sym
echo hard:; setup; $cmd -e2dsa --redup sym,hard,ref=hard >$td.l; touch -ht 197001010100 $td/*; statchk st_hard
# `- ign hard mtimes because redup is order-undefined (or sqlite-row-order rather but ye)
rm -rf $td $td.l
+2 -2
View File
@@ -145,7 +145,7 @@ class Cfg(Namespace):
def __init__(self, a=None, v=None, c=None, **ka0):
ka = {}
ex = "allow_flac allow_wav allow_svg_js chpw cookie_lax daw dav_auth dav_mac dav_rt dlni dothidden e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only http_no_tcp ih ihead localtime log_badxml magic md_no_br nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_dupe_m no_fnugg no_html no_lifetime no_logues no_mime no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_script no_tail no_tarcmp no_thumb no_vthumb no_u2abrt no_zip no_zls nrand nsort nw og og_no_head og_s_title ohead opds q rand re_dirsz reflink rm_partial rmagic rss show_hist smb srch_dbg srch_excl srch_nfkc srch_icase stats ui_noacci ui_nocpla ui_noctxb ui_nolbar ui_nombar ui_nonav ui_notree ui_norepl ui_nosrvi uqe usernames vague_403 vc ver vol_nospawn vol_or_crash wo_up_readme wopi write_uplog xdev xlink xvol zipmaxu zs"
ex = "allow_flac allow_wav allow_svg_js chpw cookie_lax daw dav_auth dav_mac dav_rt dlni dothidden e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only http_no_tcp ih ihead localtime log_badxml magic md_no_br nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_dupe_m no_fnugg no_html no_lifetime no_logues no_mime no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_script no_tail no_tarcmp no_thumb no_vthumb no_u2abrt no_zip no_zls nrand nsort nw og og_no_head og_s_title ohead opds q rand re_dirsz redup_dry reflink rm_partial rmagic rss show_hist smb srch_dbg srch_excl srch_nfkc srch_icase stats ui_noacci ui_nocpla ui_noctxb ui_nolbar ui_nombar ui_nonav ui_notree ui_norepl ui_nosrvi uqe usernames vague_403 vc ver vol_nospawn vol_or_crash wo_up_readme wopi write_uplog xdev xlink xvol zipmaxu zs"
ka.update(**{k: False for k in ex.split()})
ex = "dav_inf dedup dotpart dotsrch hist_cow hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump wram re_dhash see_dots plain_ip"
@@ -166,7 +166,7 @@ class Cfg(Namespace):
ex = "ctl_re db_act forget_ip gauto idp_cookie idp_store k304 loris md_nhist no304 nosubtle qr_pin qr_wait re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow zipmaxn zipmaxs"
ka.update(**{k: 0 for k in ex.split()})
ex = "ah_alg bname chdir chmod_f chpw_db csp_dl csp_ui db_xattr doctitle df epilogues exit favico fika ipa ipar html_head html_head_d html_head_s idp_login idp_logout lg_sba lg_sbf log_date log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i opds_exts preadmes prologues readmes shr shr1 shr_site site smsg tcolor textfiles th_pregen txt_eol ufavico ufavico_h unlist up_site vc_url vname xff_src zipmaxt R RS SR"
ex = "ah_alg bname chdir chmod_f chpw_db csp_dl csp_ui db_xattr doctitle df epilogues exit favico fika ipa ipar html_head html_head_d html_head_s idp_login idp_logout lg_sba lg_sbf log_date log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i opds_exts preadmes prologues readmes redup shr shr1 shr_site site smsg tcolor textfiles th_pregen txt_eol ufavico ufavico_h unlist up_site vc_url vname xff_src zipmaxt R RS SR"
ka.update(**{k: "" for k in ex.split()})
ex = "apnd_who ban_403 ban_404 ban_422 ban_pw ban_pwc ban_url dont_ban cachectl http_vary rcm rss_fmt_d rss_fmt_t spinner"