Merge branch 'hovudstraum' into hovudstraum

Signed-off-by: ed <s@ocv.me>
This commit is contained in:
ed 2025-08-09 20:21:57 +00:00 committed by GitHub
commit 5d94b8fd77
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 5451 additions and 390 deletions

3
.gitignore vendored
View file

@ -43,3 +43,6 @@ scripts/docker/*.err
# nix build output link # nix build output link
result result
# IDEA config
.idea/

View file

@ -437,6 +437,7 @@ upgrade notes
* can I link someone to a password-protected volume/file by including the password in the URL? * can I link someone to a password-protected volume/file by including the password in the URL?
* yes, by adding `?pw=hunter2` to the end; replace `?` with `&` if there are parameters in the URL already, meaning it contains a `?` near the end * yes, by adding `?pw=hunter2` to the end; replace `?` with `&` if there are parameters in the URL already, meaning it contains a `?` near the end
* if you have enabled `--usernames` then do `?pw=username:password` instead
* how do I stop `.hist` folders from appearing everywhere on my HDD? * how do I stop `.hist` folders from appearing everywhere on my HDD?
* by default, a `.hist` folder is created inside each volume for the filesystem index, thumbnails, audio transcodes, and markdown document history. Use the `--hist` global-option or the `hist` volflag to move it somewhere else; see [database location](#database-location) * by default, a `.hist` folder is created inside each volume for the filesystem index, thumbnails, audio transcodes, and markdown document history. Use the `--hist` global-option or the `hist` volflag to move it somewhere else; see [database location](#database-location)
@ -1016,6 +1017,7 @@ a feed example: https://cd.ocv.me/a/d2/d22/?rss&fext=mp3
url parameters: url parameters:
* `pw=hunter2` for password auth * `pw=hunter2` for password auth
* if you enabled `--usernames` then do `pw=username:password` instead
* `recursive` to also include subfolders * `recursive` to also include subfolders
* `title=foo` changes the feed title (default: folder name) * `title=foo` changes the feed title (default: folder name)
* `fext=mp3,opus` only include mp3 and opus files (default: all) * `fext=mp3,opus` only include mp3 and opus files (default: all)
@ -1301,6 +1303,7 @@ an FTP server can be started using `--ftp 3921`, and/or `--ftps` for explicit T
* if you enable both `ftp` and `ftps`, the port-range will be divided in half * if you enable both `ftp` and `ftps`, the port-range will be divided in half
* some older software (filezilla on debian-stable) cannot passive-mode with TLS * some older software (filezilla on debian-stable) cannot passive-mode with TLS
* login with any username + your password, or put your password in the username field * login with any username + your password, or put your password in the username field
* unless you enabled `--usernames`
some recommended FTP / FTPS clients; `wark` = example password: some recommended FTP / FTPS clients; `wark` = example password:
* https://winscp.net/eng/download.php * https://winscp.net/eng/download.php
@ -1318,6 +1321,7 @@ click the [connect](http://127.0.0.1:3923/?hc) button in the control-panel to se
general usage: general usage:
* login with any username + your password, or put your password in the username field (password field can be empty/whatever) * login with any username + your password, or put your password in the username field (password field can be empty/whatever)
* unless you enabled `--usernames`
on macos, connect from finder: on macos, connect from finder:
* [Go] -> [Connect to Server...] -> http://192.168.123.1:3923/ * [Go] -> [Connect to Server...] -> http://192.168.123.1:3923/
@ -1333,6 +1337,7 @@ using the GUI (winXP or later):
* rightclick [my computer] -> [map network drive] -> Folder: `http://192.168.123.1:3923/` * rightclick [my computer] -> [map network drive] -> Folder: `http://192.168.123.1:3923/`
* on winXP only, click the `Sign up for online storage` hyperlink instead and put the URL there * on winXP only, click the `Sign up for online storage` hyperlink instead and put the URL there
* providing your password as the username is recommended; the password field can be anything or empty * providing your password as the username is recommended; the password field can be anything or empty
* unless you enabled `--usernames`
the webdav client that's built into windows has the following list of bugs; you can avoid all of these by connecting with rclone instead: the webdav client that's built into windows has the following list of bugs; you can avoid all of these by connecting with rclone instead:
* win7+ doesn't actually send the password to the server when reauthenticating after a reboot unless you first try to login with an incorrect password and then switch to the correct password * win7+ doesn't actually send the password to the server when reauthenticating after a reboot unless you first try to login with an incorrect password and then switch to the correct password
@ -1390,6 +1395,7 @@ some **BIG WARNINGS** specific to SMB/CIFS, in decreasing importance:
* the smb backend is not fully integrated with vfs, meaning there could be security issues (path traversal). Please use `--smb-port` (see below) and [prisonparty](./bin/prisonparty.sh) or [bubbleparty](./bin/bubbleparty.sh) * the smb backend is not fully integrated with vfs, meaning there could be security issues (path traversal). Please use `--smb-port` (see below) and [prisonparty](./bin/prisonparty.sh) or [bubbleparty](./bin/bubbleparty.sh)
* account passwords work per-volume as expected, and so does account permissions (read/write/move/delete), but `--smbw` must be given to allow write-access from smb * account passwords work per-volume as expected, and so does account permissions (read/write/move/delete), but `--smbw` must be given to allow write-access from smb
* [shadowing](#shadowing) probably works as expected but no guarantees * [shadowing](#shadowing) probably works as expected but no guarantees
* not compatible with pw-hashing or `--usernames`
and some minor issues, and some minor issues,
* clients only see the first ~400 files in big folders; * clients only see the first ~400 files in big folders;
@ -2058,7 +2064,11 @@ you can either:
* or do location-based proxying, using `--rp-loc=/stuff` to tell copyparty where it is mounted -- has a slight performance cost and higher chance of bugs * or do location-based proxying, using `--rp-loc=/stuff` to tell copyparty where it is mounted -- has a slight performance cost and higher chance of bugs
* if copyparty says `incorrect --rp-loc or webserver config; expected vpath starting with [...]` it's likely because the webserver is stripping away the proxy location from the request URLs -- see the `ProxyPass` in the apache example below * if copyparty says `incorrect --rp-loc or webserver config; expected vpath starting with [...]` it's likely because the webserver is stripping away the proxy location from the request URLs -- see the `ProxyPass` in the apache example below
when running behind a reverse-proxy (this includes services like cloudflare), it is important to configure real-ip correctly, as many features rely on knowing the client's IP. Look out for red and yellow log messages which explain how to do this. But basically, set `--xff-hdr` to the name of the http header to read the IP from (usually `x-forwarded-for`, but cloudflare uses `cf-connecting-ip`), and then `--xff-src` to the IP of the reverse-proxy so copyparty will trust the xff-hdr. Note that `--rp-loc` in particular will not work at all unless you do this when running behind a reverse-proxy (this includes services like cloudflare), it is important to configure real-ip correctly, as many features rely on knowing the client's IP. The best/safest approach is to configure your reverse-proxy so it gives copyparty a header which only contains the client's true/real IP-address, and then setting `--xff-hdr theHeaderName --rproxy 1` but alternatively, if you want/need to let copyparty handle this, look out for red and yellow log messages which explain how to do that. Basically, the log will say this:
> set `--xff-hdr` to the name of the http-header to read the IP from (usually `x-forwarded-for`, but cloudflare uses `cf-connecting-ip`), and then `--xff-src` to the IP of the reverse-proxy so copyparty will trust the xff-hdr. You will also need to configure `--rproxy` to `1` if the header only contains one IP (the correct one) or to a *negative value* if it contains multiple; `-1` being the rightmost and most trusted IP (the nearest proxy, so usually not the correct one), `-2` being the second-closest hop, and so on
Note that `--rp-loc` in particular will not work at all unless you configure the above correctly
some reverse proxies (such as [Caddy](https://caddyserver.com/)) can automatically obtain a valid https/tls certificate for you, and some support HTTP/2 and QUIC which *could* be a nice speed boost, depending on a lot of factors some reverse proxies (such as [Caddy](https://caddyserver.com/)) can automatically obtain a valid https/tls certificate for you, and some support HTTP/2 and QUIC which *could* be a nice speed boost, depending on a lot of factors
* **warning:** nginx-QUIC (HTTP/3) is still experimental and can make uploads much slower, so HTTP/1.1 is recommended for now * **warning:** nginx-QUIC (HTTP/3) is still experimental and can make uploads much slower, so HTTP/1.1 is recommended for now
@ -2277,11 +2287,9 @@ if your distro/OS is not mentioned below, there might be some hints in the [«on
`pacman -S copyparty` (in [arch linux extra](https://archlinux.org/packages/extra/any/copyparty/)) `pacman -S copyparty` (in [arch linux extra](https://archlinux.org/packages/extra/any/copyparty/))
it comes with a [systemd service](./contrib/package/arch/copyparty.service) and expects to find one or more [config files](./docs/example.conf) in `/etc/copyparty.d/` it comes with a [systemd service](./contrib/systemd/copyparty@.service) as well as a [user service](./contrib/systemd/copyparty-user.service), and expects to find a [config file](./contrib/systemd/copyparty.example.conf) in `/etc/copyparty/copyparty.conf` or `~/.config/copyparty/copyparty.conf`
after installing it, you may want to `cp /usr/lib/systemd/system/copyparty.service /etc/systemd/system/` and then `vim /etc/systemd/system/copyparty.service` to change what user/group it is running as (you only need to do this once) after installing, start either the system service or the user service and navigate to http://127.0.0.1:3923 for further instructions (unless you already edited the config files, in which case you are good to go, probably)
NOTE: there used to be an aur package; this evaporated when copyparty was adopted by the official archlinux repos. If you're still using the aur package, please move
## fedora package ## fedora package
@ -2504,6 +2512,8 @@ you can provide passwords using header `PW: hunter2`, cookie `cppwd=hunter2`, ur
> for basic-authentication, all of the following are accepted: `password` / `whatever:password` / `password:whatever` (the username is ignored) > for basic-authentication, all of the following are accepted: `password` / `whatever:password` / `password:whatever` (the username is ignored)
* unless you've enabled `--usernames`, then it's `PW: usr:pwd`, cookie `cppwd=usr:pwd`, url-param `?pw=usr:pwd`
NOTE: curl will not send the original filename if you use `-T` combined with url-params! Also, make sure to always leave a trailing slash in URLs unless you want to override the filename NOTE: curl will not send the original filename if you use `-T` combined with url-params! Also, make sure to always leave a trailing slash in URLs unless you want to override the filename
@ -2615,7 +2625,7 @@ there is a [discord server](https://discord.gg/25J8CdTT6G) with an `@everyone`
some notes on hardening some notes on hardening
* set `--rproxy 0` if your copyparty is directly facing the internet (not through a reverse-proxy) * set `--rproxy 0` *if and only if* your copyparty is directly facing the internet (not through a reverse-proxy)
* cors doesn't work right otherwise * cors doesn't work right otherwise
* if you allow anonymous uploads or otherwise don't trust the contents of a volume, you can prevent XSS with volflag `nohtml` * if you allow anonymous uploads or otherwise don't trust the contents of a volume, you can prevent XSS with volflag `nohtml`
* this returns html documents as plaintext, and also disables markdown rendering * this returns html documents as plaintext, and also disables markdown rendering
@ -2719,6 +2729,8 @@ when generating hashes using `--ah-cli` for docker or systemd services, make sur
* inspecting the generated salt using `--show-ah-salt` in copyparty service configuration * inspecting the generated salt using `--show-ah-salt` in copyparty service configuration
* setting the same `--ah-salt` in both environments * setting the same `--ah-salt` in both environments
> ⚠️ if you have enabled `--usernames` then provide the password as `username:password` when hashing it, for example `ed:hunter2`
## https ## https
@ -2836,6 +2848,8 @@ these are standalone programs and will never be imported / evaluated by copypart
the self-contained "binary" (recommended!) [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) will unpack itself and run copyparty, assuming you have python installed of course the self-contained "binary" (recommended!) [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) will unpack itself and run copyparty, assuming you have python installed of course
if you only need english, [copyparty-en.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-en.py) is the same thing but smaller
you can reduce the sfx size by repacking it; see [./docs/devnotes.md#sfx-repack](./docs/devnotes.md#sfx-repack) you can reduce the sfx size by repacking it; see [./docs/devnotes.md#sfx-repack](./docs/devnotes.md#sfx-repack)

View file

@ -1,57 +1,48 @@
# Maintainer: icxes <dev.null@need.moe> # Maintainer: icxes <dev.null@need.moe>
# Contributor: Morgan Adamiec <morganamilo@archlinux.org>
# NOTE: You generally shouldn't use this PKGBUILD on Arch, as it is mainly for testing purposes. Install copyparty using pacman instead.
pkgname=copyparty pkgname=copyparty
pkgver="1.18.10" pkgver="1.19.0"
pkgrel=1 pkgrel=1
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++" pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
arch=("any") arch=("any")
url="https://github.com/9001/${pkgname}" url="https://github.com/9001/${pkgname}"
license=('MIT') license=('MIT')
depends=("python" "lsof" "python-jinja") depends=("bash" "python" "lsof" "python-jinja")
makedepends=("python-wheel" "python-setuptools" "python-build" "python-installer" "make" "pigz") makedepends=("python-wheel" "python-setuptools" "python-build" "python-installer" "make" "pigz")
optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tags" optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tags"
"cfssl: generate TLS certificates on startup (pointless when reverse-proxied)" "cfssl: generate TLS certificates on startup"
"python-mutagen: music tags (alternative)" "python-mutagen: music tags (alternative)"
"python-pillow: thumbnails for images" "python-pillow: thumbnails for images"
"python-pyvips: thumbnails for images (higher quality, faster, uses more ram)" "python-pyvips: thumbnails for images (higher quality, faster, uses more ram)"
"libkeyfinder-git: detection of musical keys" "libkeyfinder: detection of musical keys"
"qm-vamp-plugins: BPM detection" "python-pyopenssl: ftps functionality"
"python-pyopenssl: ftps functionality" "python-pyzmq: send zeromq messages from event-hooks"
"python-pyzmq: send zeromq messages from event-hooks" "python-argon2-cffi: hashed passwords in config"
"python-argon2-cffi: hashed passwords in config"
"python-impacket-git: smb support (bad idea)"
) )
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz") source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
backup=("etc/${pkgname}.d/init" ) backup=("etc/${pkgname}/copyparty.conf" )
sha256sums=("49c5fedf7619437bc0af125cb4e8360d9bda9d87ef45d6314d7acf163ab4cf99") sha256sums=("179b027d51e4fe7ebdab2b18c07475d52c57e2ce69256292b157a8efacd82118")
build() { build() {
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"
make
cd "${srcdir}/${pkgname}-${pkgver}" cd "${srcdir}/${pkgname}-${pkgver}"
python -m build --wheel --no-isolation
pushd copyparty/web
make -j$(nproc)
rm Makefile
popd
python3 -m build -wn
} }
package() { package() {
cd "${srcdir}/${pkgname}-${pkgver}" cd "${srcdir}/${pkgname}-${pkgver}"
python3 -m installer -d "$pkgdir" dist/*.whl python -m installer --destdir="$pkgdir" dist/*.whl
install -dm755 "${pkgdir}/etc/${pkgname}.d" install -dm755 "${pkgdir}/etc/${pkgname}"
install -Dm755 "bin/prisonparty.sh" "${pkgdir}/usr/bin/prisonparty" install -Dm755 "bin/prisonparty.sh" "${pkgdir}/usr/bin/prisonparty"
install -Dm644 "contrib/package/arch/${pkgname}.conf" "${pkgdir}/etc/${pkgname}.d/init" install -Dm644 "contrib/systemd/${pkgname}.conf" "${pkgdir}/etc/${pkgname}/copyparty.conf"
install -Dm644 "contrib/package/arch/${pkgname}.service" "${pkgdir}/usr/lib/systemd/system/${pkgname}.service" install -Dm644 "contrib/systemd/${pkgname}@.service" "${pkgdir}/usr/lib/systemd/system/${pkgname}@.service"
install -Dm644 "contrib/package/arch/prisonparty.service" "${pkgdir}/usr/lib/systemd/system/prisonparty.service" install -Dm644 "contrib/systemd/${pkgname}-user.service" "${pkgdir}/usr/lib/systemd/user/${pkgname}.service"
install -Dm644 "contrib/package/arch/index.md" "${pkgdir}/var/lib/${pkgname}-jail/README.md" 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 "LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" install -Dm644 "LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
find /etc/${pkgname}.d -iname '*.conf' 2>/dev/null | grep -qE . && return
echo "┏━━━━━━━━━━━━━━━──-"
echo "┃ Configure ${pkgname} by adding .conf files into /etc/${pkgname}.d/"
echo "┃ and maybe copy+edit one of the following to /etc/systemd/system/:"
echo "┣━♦ /usr/lib/systemd/system/${pkgname}.service (standard)"
echo "┣━♦ /usr/lib/systemd/system/prisonparty.service (chroot)"
echo "┗━━━━━━━━━━━━━━━──-"
} }

View file

@ -1,7 +0,0 @@
## import all *.conf files from the current folder (/etc/copyparty.d)
% ./
# add additional .conf files to this folder;
# see example config files for reference:
# https://github.com/9001/copyparty/blob/hovudstraum/docs/example.conf
# https://github.com/9001/copyparty/tree/hovudstraum/docs/copyparty.d

View file

@ -1,32 +0,0 @@
# this will start `/usr/bin/copyparty-sfx.py`
# and read config from `/etc/copyparty.d/*.conf`
#
# you probably want to:
# change "User=cpp" and "/home/cpp/" to another user
#
# unless you add -q to disable logging, you may want to remove the
# following line to allow buffering (slightly better performance):
# Environment=PYTHONUNBUFFERED=x
[Unit]
Description=copyparty file server
[Service]
Type=notify
SyslogIdentifier=copyparty
Environment=PYTHONUNBUFFERED=x
WorkingDirectory=/var/lib/copyparty-jail
ExecReload=/bin/kill -s USR1 $MAINPID
# user to run as + where the TLS certificate is (if any)
User=cpp
Environment=XDG_CONFIG_HOME=/home/cpp/.config
# stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running
ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
# run copyparty
ExecStart=/usr/bin/python3 /usr/bin/copyparty -c /etc/copyparty.d/init
[Install]
WantedBy=multi-user.target

View file

@ -1,3 +0,0 @@
this is `/var/lib/copyparty-jail`, the fallback webroot when copyparty has not yet been configured
please add some `*.conf` files to `/etc/copyparty.d/`

View file

@ -2,7 +2,7 @@
pkgname=copyparty pkgname=copyparty
pkgver=1.18.10 pkgver=1.19.0
pkgrel=1 pkgrel=1
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++" pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
arch=("any") arch=("any")
@ -20,7 +20,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") source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
backup=("/etc/${pkgname}.d/init" ) backup=("/etc/${pkgname}.d/init" )
sha256sums=("49c5fedf7619437bc0af125cb4e8360d9bda9d87ef45d6314d7acf163ab4cf99") sha256sums=("179b027d51e4fe7ebdab2b18c07475d52c57e2ce69256292b157a8efacd82118")
build() { build() {
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web" cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"

View file

@ -1,5 +1,5 @@
{ {
"url": "https://github.com/9001/copyparty/releases/download/v1.18.10/copyparty-sfx.py", "url": "https://github.com/9001/copyparty/releases/download/v1.19.0/copyparty-sfx.py",
"version": "1.18.10", "version": "1.19.0",
"hash": "sha256-2FdQ5aCwNdZ5Jy9mn8rr8g41+QHT5tsEc+GeAKvhGeg=" "hash": "sha256-9A+zPtkVtUuGHB/JJV3fhVtJderLUGxHqvuJQz0/1+Q="
} }

View file

@ -0,0 +1,26 @@
# this will start `/usr/bin/copyparty`
# and read config from `$HOME/.config/copyparty.conf`
#
# unless you add -q to disable logging, you may want to remove the
# following line to allow buffering (slightly better performance):
# Environment=PYTHONUNBUFFERED=x
[Unit]
Description=copyparty file server
[Service]
Type=notify
SyslogIdentifier=copyparty
WorkingDirectory=/var/lib/copyparty-jail
Environment=PYTHONUNBUFFERED=x
Environment=PRTY_CONFIG=%h/.config/copyparty/copyparty.conf
ExecReload=/bin/kill -s USR1 $MAINPID
# ensure there is a config
ExecStartPre=/bin/bash -c 'if [[ ! -f %h/.config/copyparty/copyparty.conf ]]; then mkdir -p %h/.config/copyparty; cp /etc/copyparty/copyparty.conf %h/.config/copyparty/copyparty.conf; fi'
# run copyparty
ExecStart=/usr/bin/python3 /usr/bin/copyparty
[Install]
WantedBy=default.target

View file

@ -1,42 +1,13 @@
# not actually YAML but lets pretend:
# -*- mode: yaml -*-
# vim: ft=yaml:
# put this file in /etc/
[global] [global]
e2dsa # enable file indexing and filesystem scanning i: 127.0.0.1
e2ts # and enable multimedia indexing
ansi # and colors in log messages
# disable logging to stdout/journalctl and log to a file instead;
# $LOGS_DIRECTORY is usually /var/log/copyparty (comes from systemd)
# and copyparty replaces %Y-%m%d with Year-MonthDay, so the
# full path will be something like /var/log/copyparty/2023-1130.txt
# (note: enable compression by adding .xz at the end)
q, lo: $LOGS_DIRECTORY/%Y-%m%d.log
# p: 80,443,3923 # listen on 80/443 as well (requires CAP_NET_BIND_SERVICE)
# i: 127.0.0.1 # only allow connections from localhost (reverse-proxies)
# ftp: 3921 # enable ftp server on port 3921
# p: 3939 # listen on another port
# df: 16 # stop accepting uploads if less than 16 GB free disk space
# ver # show copyparty version in the controlpanel
# grid # show thumbnails/grid-view by default
# theme: 2 # monokai
# name: datasaver # change the server-name that's displayed in the browser
# stats, nos-dup # enable the prometheus endpoint, but disable the dupes counter (too slow)
# no-robots, force-js # make it harder for search engines to read your server
[accounts] [accounts]
ed: wark # username: password user: password
[/]
[/] # create a volume at "/" (the webroot), which will /var/lib/copyparty-jail
/mnt # share the contents of the "/mnt" folder
accs: accs:
rw: * # everyone gets read-write access, but r: *
rwmda: ed # the user "ed" gets read-write-move-delete-admin rwdma: user
flags:
grid

View file

@ -0,0 +1,42 @@
# not actually YAML but lets pretend:
# -*- mode: yaml -*-
# vim: ft=yaml:
# put this file in /etc/
[global]
e2dsa # enable file indexing and filesystem scanning
e2ts # and enable multimedia indexing
ansi # and colors in log messages
# disable logging to stdout/journalctl and log to a file instead;
# $LOGS_DIRECTORY is usually /var/log/copyparty (comes from systemd)
# and copyparty replaces %Y-%m%d with Year-MonthDay, so the
# full path will be something like /var/log/copyparty/2023-1130.txt
# (note: enable compression by adding .xz at the end)
q, lo: $LOGS_DIRECTORY/%Y-%m%d.log
# p: 80,443,3923 # listen on 80/443 as well (requires CAP_NET_BIND_SERVICE)
# i: 127.0.0.1 # only allow connections from localhost (reverse-proxies)
# ftp: 3921 # enable ftp server on port 3921
# p: 3939 # listen on another port
# df: 16 # stop accepting uploads if less than 16 GB free disk space
# ver # show copyparty version in the controlpanel
# grid # show thumbnails/grid-view by default
# theme: 2 # monokai
# name: datasaver # change the server-name that's displayed in the browser
# stats, nos-dup # enable the prometheus endpoint, but disable the dupes counter (too slow)
# no-robots, force-js # make it harder for search engines to read your server
[accounts]
ed: wark # username: password
[/] # create a volume at "/" (the webroot), which will
/mnt # share the contents of the "/mnt" folder
accs:
rw: * # everyone gets read-write access, but
rwmda: ed # the user "ed" gets read-write-move-delete-admin

View file

@ -0,0 +1,30 @@
# this will start `/usr/bin/copyparty`
# and read config from `/etc/copyparty/copyparty.conf`
#
# the %i refers to whatever you put after the copyparty@
# so with copyparty@foo.service, %i == foo
#
# unless you add -q to disable logging, you may want to remove the
# following line to allow buffering (slightly better performance):
# Environment=PYTHONUNBUFFERED=x
[Unit]
Description=copyparty file server
[Service]
Type=notify
SyslogIdentifier=copyparty
WorkingDirectory=/var/lib/copyparty-jail
Environment=PYTHONUNBUFFERED=x
Environment=PRTY_CONFIG=/etc/copyparty/copyparty.conf
ExecReload=/bin/kill -s USR1 $MAINPID
# user to run as + where the TLS certificate is (if any)
User=%i
Environment=XDG_CONFIG_HOME=/home/%i/.config
# run copyparty
ExecStart=/usr/bin/python3 /usr/bin/copyparty
[Install]
WantedBy=multi-user.target

10
contrib/systemd/index.md Normal file
View file

@ -0,0 +1,10 @@
this is `/var/lib/copyparty-jail`, the fallback webroot when copyparty has not yet been configured
please edit `/etc/copyparty/copyparty.conf` (if running as a system service)
or `$HOME/.config/copyparty/copyparty.conf` if running as a user service
a basic configuration example is available at https://github.com/9001/copyparty/blob/hovudstraum/contrib/systemd/copyparty.example.conf
a configuration example that explains most flags is available at https://github.com/9001/copyparty/blob/hovudstraum/docs/chungus.conf
the full list of configuration options can be seen at https://ocv.me/copyparty/helptext.html
or by running `copyparty --help`

View file

@ -1,11 +1,13 @@
# this will start `/usr/bin/copyparty-sfx.py` # this will start `/usr/bin/copyparty`
# in a chroot, preventing accidental access elsewhere, # in a chroot, preventing accidental access elsewhere,
# and read copyparty config from `/etc/copyparty.d/*.conf` # and read copyparty config from `/etc/copyparty/copyparty.conf`
# #
# expose additional filesystem locations to copyparty # expose additional filesystem locations to copyparty
# by listing them between the last `cpp` and `--` # by listing them between the last `%i` and `--`
# #
# `cpp cpp` = user/group to run copyparty as; can be IDs (1000 1000) # `%i %i` = user/group to run copyparty as; can be IDs (1000 1000)
# the %i refers to whatever you put after the prisonparty@
# so with prisonparty@foo.service, %i == foo
# #
# unless you add -q to disable logging, you may want to remove the # unless you add -q to disable logging, you may want to remove the
# following line to allow buffering (slightly better performance): # following line to allow buffering (slightly better performance):
@ -15,19 +17,22 @@
Description=copyparty file server Description=copyparty file server
[Service] [Service]
Type=notify
SyslogIdentifier=prisonparty SyslogIdentifier=prisonparty
Environment=PYTHONUNBUFFERED=x
WorkingDirectory=/var/lib/copyparty-jail WorkingDirectory=/var/lib/copyparty-jail
Environment=PYTHONUNBUFFERED=x
Environment=PRTY_CONFIG=/etc/copyparty/copyparty.conf
ExecReload=/bin/kill -s USR1 $MAINPID ExecReload=/bin/kill -s USR1 $MAINPID
# stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running # user to run as + where the TLS certificate is (if any)
ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf' User=%i
Environment=XDG_CONFIG_HOME=/home/%i/.config
# run copyparty # run copyparty
ExecStart=/bin/bash /usr/bin/prisonparty /var/lib/copyparty-jail cpp cpp \ ExecStart=/bin/bash /usr/bin/prisonparty /var/lib/copyparty-jail %i %i \
/etc/copyparty.d \ /etc/copyparty \
-- \ -- \
/usr/bin/python3 /usr/bin/copyparty -c /etc/copyparty.d/init /usr/bin/python3 /usr/bin/copyparty
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View file

@ -536,7 +536,7 @@ def get_sects():
dedent( dedent(
""" """
\033[33m-i\033[0m takes a comma-separated list of interfaces to listen on; \033[33m-i\033[0m takes a comma-separated list of interfaces to listen on;
IP-addresses and/or unix-sockets (Unix Domain Sockets) IP-addresses, unix-sockets, and/or open file descriptors
the default (\033[32m-i ::\033[0m) means all IPv4 and IPv6 addresses the default (\033[32m-i ::\033[0m) means all IPv4 and IPv6 addresses
@ -562,7 +562,9 @@ def get_sects():
\033[32m-i unix:\033[33m/dev/shm/party.sock\033[0m keeps umask-defined permission \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 (usually \033[33m0600\033[0m) and the same user/group as copyparty
\033[33m-p\033[0m (tcp ports) is ignored for unix sockets \033[32m-i fd:\033[33m3\033[0m uses the socket passed to copyparty on file descriptor 3
\033[33m-p\033[0m (tcp ports) is ignored for unix-sockets and FDs
""" """
), ),
], ],
@ -916,6 +918,9 @@ def get_sects():
copyparty will also hash and print any passwords that are non-hashed copyparty will also hash and print any passwords that are non-hashed
(password which do not start with '+') and then terminate afterwards (password which do not start with '+') and then terminate afterwards
if you have enabled --usernames then the password
must be provided as username:password for hashing
\033[36m--ah-alg\033[0m specifies the hashing algorithm and a \033[36m--ah-alg\033[0m specifies the hashing algorithm and a
list of optional comma-separated arguments: list of optional comma-separated arguments:
@ -993,18 +998,19 @@ def build_flags_desc():
def add_general(ap, nc, srvname): def add_general(ap, nc, srvname):
ap2 = ap.add_argument_group('general options') ap2 = ap.add_argument_group("general options")
ap2.add_argument("-c", metavar="PATH", type=u, default=CFG_DEF, action="append", help="add config file") ap2.add_argument("-c", metavar="PATH", type=u, default=CFG_DEF, action="append", help="\033[34mREPEATABLE:\033[0m add config file")
ap2.add_argument("-nc", metavar="NUM", type=int, default=nc, help="max num clients") ap2.add_argument("-nc", metavar="NUM", type=int, default=nc, help="max num clients")
ap2.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores, 0=all") ap2.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores, 0=all")
ap2.add_argument("-a", metavar="ACCT", type=u, action="append", help="add account, \033[33mUSER\033[0m:\033[33mPASS\033[0m; example [\033[32med:wark\033[0m]") ap2.add_argument("-a", metavar="ACCT", type=u, action="append", help="\033[34mREPEATABLE:\033[0m add account, \033[33mUSER\033[0m:\033[33mPASS\033[0m; example [\033[32med:wark\033[0m]")
ap2.add_argument("-v", metavar="VOL", type=u, action="append", help="add volume, \033[33mSRC\033[0m:\033[33mDST\033[0m:\033[33mFLAG\033[0m; examples [\033[32m.::r\033[0m], [\033[32m/mnt/nas/music:/music:r:aed\033[0m], see --help-accounts") ap2.add_argument("-v", metavar="VOL", type=u, action="append", help="\033[34mREPEATABLE:\033[0m add volume, \033[33mSRC\033[0m:\033[33mDST\033[0m:\033[33mFLAG\033[0m; examples [\033[32m.::r\033[0m], [\033[32m/mnt/nas/music:/music:r:aed\033[0m], see --help-accounts")
ap2.add_argument("--grp", metavar="G:N,N", type=u, action="append", help="add group, \033[33mNAME\033[0m:\033[33mUSER1\033[0m,\033[33mUSER2\033[0m,\033[33m...\033[0m; example [\033[32madmins:ed,foo,bar\033[0m]") ap2.add_argument("--grp", metavar="G:N,N", type=u, action="append", help="\033[34mREPEATABLE:\033[0m add group, \033[33mNAME\033[0m:\033[33mUSER1\033[0m,\033[33mUSER2\033[0m,\033[33m...\033[0m; example [\033[32madmins:ed,foo,bar\033[0m]")
ap2.add_argument("--usernames", action="store_true", help="require username and password for login; default is just password")
ap2.add_argument("-ed", action="store_true", help="enable the ?dots url parameter / client option which allows clients to see dotfiles / hidden files (volflag=dots)") ap2.add_argument("-ed", action="store_true", help="enable the ?dots url parameter / client option which allows clients to see dotfiles / hidden files (volflag=dots)")
ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,xm", help="how to handle url-form POSTs; see \033[33m--help-urlform\033[0m") ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,xm", help="how to handle url-form POSTs; see \033[33m--help-urlform\033[0m")
ap2.add_argument("--wintitle", metavar="TXT", type=u, default="cpp @ $pub", help="server terminal title, for example [\033[32m$ip-10.1.2.\033[0m] or [\033[32m$ip-]") ap2.add_argument("--wintitle", metavar="TXT", type=u, default="cpp @ $pub", help="server terminal title, for example [\033[32m$ip-10.1.2.\033[0m] or [\033[32m$ip-]")
ap2.add_argument("--name", metavar="TXT", type=u, default=srvname, help="server name (displayed topleft in browser and in mDNS)") ap2.add_argument("--name", metavar="TXT", type=u, default=srvname, help="server name (displayed topleft in browser and in mDNS)")
ap2.add_argument("--mime", metavar="EXT=MIME", type=u, action="append", help="map file \033[33mEXT\033[0mension to \033[33mMIME\033[0mtype, for example [\033[32mjpg=image/jpeg\033[0m]") ap2.add_argument("--mime", metavar="EXT=MIME", type=u, action="append", help="\033[34mREPEATABLE:\033[0m map file \033[33mEXT\033[0mension to \033[33mMIME\033[0mtype, for example [\033[32mjpg=image/jpeg\033[0m]")
ap2.add_argument("--mimes", action="store_true", help="list default mimetype mapping and exit") ap2.add_argument("--mimes", action="store_true", help="list default mimetype mapping and exit")
ap2.add_argument("--rmagic", action="store_true", help="do expensive analysis to improve accuracy of returned mimetypes; will make file-downloads, rss, and webdav slower (volflag=rmagic)") ap2.add_argument("--rmagic", action="store_true", help="do expensive analysis to improve accuracy of returned mimetypes; will make file-downloads, rss, and webdav slower (volflag=rmagic)")
ap2.add_argument("--license", action="store_true", help="show licenses and exit") ap2.add_argument("--license", action="store_true", help="show licenses and exit")
@ -1012,7 +1018,7 @@ def add_general(ap, nc, srvname):
def add_qr(ap, tty): def add_qr(ap, tty):
ap2 = ap.add_argument_group('qr options') ap2 = ap.add_argument_group("qr options")
ap2.add_argument("--qr", action="store_true", help="show http:// QR-code on startup") ap2.add_argument("--qr", action="store_true", help="show http:// QR-code on startup")
ap2.add_argument("--qrs", action="store_true", help="show https:// QR-code on startup") ap2.add_argument("--qrs", action="store_true", help="show https:// QR-code on startup")
ap2.add_argument("--qrl", metavar="PATH", type=u, default="", help="location to include in the url, for example [\033[32mpriv/?pw=hunter2\033[0m]") ap2.add_argument("--qrl", metavar="PATH", type=u, default="", help="location to include in the url, for example [\033[32mpriv/?pw=hunter2\033[0m]")
@ -1034,7 +1040,7 @@ def add_fs(ap):
def add_share(ap): def add_share(ap):
db_path = os.path.join(E.cfg, "shares.db") db_path = os.path.join(E.cfg, "shares.db")
ap2 = ap.add_argument_group('share-url options') ap2 = ap.add_argument_group("share-url options")
ap2.add_argument("--shr", metavar="DIR", type=u, default="", help="toplevel virtual folder for shared files/folders, for example [\033[32m/share\033[0m]") ap2.add_argument("--shr", metavar="DIR", type=u, default="", help="toplevel virtual folder for shared files/folders, for example [\033[32m/share\033[0m]")
ap2.add_argument("--shr-db", metavar="FILE", type=u, default=db_path, help="database to store shares in") ap2.add_argument("--shr-db", metavar="FILE", type=u, default=db_path, help="database to store shares in")
ap2.add_argument("--shr-adm", metavar="U,U", type=u, default="", help="comma-separated list of users allowed to view/delete any share") ap2.add_argument("--shr-adm", metavar="U,U", type=u, default="", help="comma-separated list of users allowed to view/delete any share")
@ -1043,7 +1049,7 @@ def add_share(ap):
def add_upload(ap): def add_upload(ap):
ap2 = ap.add_argument_group('upload options') ap2 = ap.add_argument_group("upload options")
ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads, hiding them from clients unless \033[33m-ed\033[0m") ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads, hiding them from clients unless \033[33m-ed\033[0m")
ap2.add_argument("--plain-ip", action="store_true", help="when avoiding filename collisions by appending the uploader's ip to the filename: append the plaintext ip instead of salting and hashing the ip") ap2.add_argument("--plain-ip", action="store_true", help="when avoiding filename collisions by appending the uploader's ip to the filename: append the plaintext ip instead of salting and hashing the ip")
ap2.add_argument("--put-name", metavar="TXT", type=u, default="put-{now.6f}-{cip}.bin", help="filename for nameless uploads (when uploader doesn't provide a name); default is [\033[32mput-UNIXTIME-IP.bin\033[0m] (the \033[32m.6f\033[0m means six decimal places) (volflag=put_name)") ap2.add_argument("--put-name", metavar="TXT", type=u, default="put-{now.6f}-{cip}.bin", help="filename for nameless uploads (when uploader doesn't provide a name); default is [\033[32mput-UNIXTIME-IP.bin\033[0m] (the \033[32m.6f\033[0m means six decimal places) (volflag=put_name)")
@ -1085,14 +1091,14 @@ def add_upload(ap):
def add_network(ap): def add_network(ap):
ap2 = ap.add_argument_group('network options') ap2 = ap.add_argument_group("network options")
ap2.add_argument("-i", metavar="IP", type=u, default="::", help="IPs and/or unix-sockets to listen on (see \033[33m--help-bind\033[0m). Default: all IPv4 and IPv6") ap2.add_argument("-i", metavar="IP", type=u, default="::", help="IPs and/or unix-sockets to listen on (comma-separated list; see \033[33m--help-bind\033[0m). Default: all IPv4 and IPv6")
ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to listen on (comma/range); ignored for unix-sockets") ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to listen on (comma/range); ignored for unix-sockets")
ap2.add_argument("--ll", action="store_true", help="include link-local IPv4/IPv6 in mDNS replies, even if the NIC has routable IPs (breaks some mDNS clients)") ap2.add_argument("--ll", action="store_true", help="include link-local IPv4/IPv6 in mDNS replies, even if the NIC has routable IPs (breaks some mDNS clients)")
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to associate clients with; [\033[32m0\033[0m]=tcp, [\033[32m1\033[0m]=origin (first x-fwd, unsafe), [\033[32m2\033[0m]=outermost-proxy, [\033[32m3\033[0m]=second-proxy, [\033[32m-1\033[0m]=closest-proxy") ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=9999999, help="which ip to associate clients with; [\033[32m0\033[0m]=tcp, [\033[32m1\033[0m]=origin (first x-fwd, unsafe), [\033[32m-1\033[0m]=closest-proxy, [\033[32m-2\033[0m]=second-hop, [\033[32m-3\033[0m]=third-hop")
ap2.add_argument("--xff-hdr", metavar="NAME", type=u, default="x-forwarded-for", help="if reverse-proxied, which http header to read the client's real ip from") ap2.add_argument("--xff-hdr", metavar="NAME", type=u, default="x-forwarded-for", help="if reverse-proxied, which http header to read the client's real ip from")
ap2.add_argument("--xff-src", metavar="CIDR", type=u, default="127.0.0.0/8, ::1/128", help="comma-separated list of trusted reverse-proxy CIDRs; only accept the real-ip header (\033[33m--xff-hdr\033[0m) and IdP headers if the incoming connection is from an IP within either of these subnets. Specify [\033[32mlan\033[0m] to allow all LAN / private / non-internet IPs. Can be disabled with [\033[32many\033[0m] if you are behind cloudflare (or similar) and are using \033[32m--xff-hdr=cf-connecting-ip\033[0m (or similar)") ap2.add_argument("--xff-src", metavar="CIDR", type=u, default="127.0.0.0/8, ::1/128", help="list of trusted reverse-proxy CIDRs (comma-separated); only accept the real-ip header (\033[33m--xff-hdr\033[0m) and IdP headers if the incoming connection is from an IP within either of these subnets. Specify [\033[32mlan\033[0m] to allow all LAN / private / non-internet IPs. Can be disabled with [\033[32many\033[0m] if you are behind cloudflare (or similar) and are using \033[32m--xff-hdr=cf-connecting-ip\033[0m (or similar)")
ap2.add_argument("--ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m; examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") ap2.add_argument("--ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m (comma-separated); examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]")
ap2.add_argument("--rp-loc", metavar="PATH", type=u, default="", help="if reverse-proxying on a location instead of a dedicated domain/subdomain, provide the base location here; example: [\033[32m/foo/bar\033[0m]") ap2.add_argument("--rp-loc", metavar="PATH", type=u, default="", help="if reverse-proxying on a location instead of a dedicated domain/subdomain, provide the base location here; example: [\033[32m/foo/bar\033[0m]")
if ANYWIN: if ANYWIN:
ap2.add_argument("--reuseaddr", action="store_true", help="set reuseaddr on listening sockets on windows; allows rapid restart of copyparty at the expense of being able to accidentally start multiple instances") ap2.add_argument("--reuseaddr", action="store_true", help="set reuseaddr on listening sockets on windows; allows rapid restart of copyparty at the expense of being able to accidentally start multiple instances")
@ -1110,7 +1116,7 @@ def add_network(ap):
def add_tls(ap, cert_path): def add_tls(ap, cert_path):
ap2 = ap.add_argument_group('SSL/TLS options') ap2 = ap.add_argument_group("SSL/TLS options")
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls -- force plaintext") ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls -- force plaintext")
ap2.add_argument("--https-only", action="store_true", help="disable plaintext -- force tls") ap2.add_argument("--https-only", action="store_true", help="disable plaintext -- force tls")
ap2.add_argument("--cert", metavar="PATH", type=u, default=cert_path, help="path to file containing a concatenation of TLS key and certificate chain") ap2.add_argument("--cert", metavar="PATH", type=u, default=cert_path, help="path to file containing a concatenation of TLS key and certificate chain")
@ -1122,7 +1128,7 @@ def add_tls(ap, cert_path):
def add_cert(ap, cert_path): def add_cert(ap, cert_path):
cert_dir = os.path.dirname(cert_path) cert_dir = os.path.dirname(cert_path)
ap2 = ap.add_argument_group('TLS certificate generator options') ap2 = ap.add_argument_group("TLS certificate generator options")
ap2.add_argument("--no-crt", action="store_true", help="disable automatic certificate creation") ap2.add_argument("--no-crt", action="store_true", help="disable automatic certificate creation")
ap2.add_argument("--crt-ns", metavar="N,N", type=u, default="", help="comma-separated list of FQDNs (domains) to add into the certificate") ap2.add_argument("--crt-ns", metavar="N,N", type=u, default="", help="comma-separated list of FQDNs (domains) to add into the certificate")
ap2.add_argument("--crt-exact", action="store_true", help="do not add wildcard entries for each \033[33m--crt-ns\033[0m") ap2.add_argument("--crt-exact", action="store_true", help="do not add wildcard entries for each \033[33m--crt-ns\033[0m")
@ -1142,7 +1148,7 @@ def add_cert(ap, cert_path):
def add_auth(ap): def add_auth(ap):
idp_db = os.path.join(E.cfg, "idp.db") idp_db = os.path.join(E.cfg, "idp.db")
ses_db = os.path.join(E.cfg, "sessions.db") ses_db = os.path.join(E.cfg, "sessions.db")
ap2 = ap.add_argument_group('IdP / identity provider / user authentication options') ap2 = ap.add_argument_group("IdP / identity provider / user authentication options")
ap2.add_argument("--idp-h-usr", metavar="HN", type=u, default="", help="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-h-usr", metavar="HN", type=u, default="", help="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-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-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-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")
@ -1156,14 +1162,14 @@ def add_auth(ap):
ap2.add_argument("--ses-db", metavar="PATH", type=u, default=ses_db, help="where to store the sessions database (if you run multiple copyparty instances, make sure they use different DBs)") ap2.add_argument("--ses-db", metavar="PATH", type=u, default=ses_db, help="where to store the sessions database (if you run multiple copyparty instances, make sure they use different DBs)")
ap2.add_argument("--ses-len", metavar="CHARS", type=int, default=20, help="session key length; default is 120 bits ((20//4)*4*6)") ap2.add_argument("--ses-len", metavar="CHARS", type=int, default=20, help="session key length; default is 120 bits ((20//4)*4*6)")
ap2.add_argument("--no-ses", action="store_true", help="disable sessions; use plaintext passwords in cookies") ap2.add_argument("--no-ses", action="store_true", help="disable sessions; use plaintext passwords in cookies")
ap2.add_argument("--ipu", metavar="CIDR=USR", type=u, action="append", help="users with IP matching \033[33mCIDR\033[0m are auto-authenticated as username \033[33mUSR\033[0m; example: [\033[32m172.16.24.0/24=dave]") ap2.add_argument("--ipu", metavar="CIDR=USR", type=u, action="append", help="\033[34mREPEATABLE:\033[0m users with IP matching \033[33mCIDR\033[0m are auto-authenticated as username \033[33mUSR\033[0m; example: [\033[32m172.16.24.0/24=dave]")
def add_chpw(ap): def add_chpw(ap):
db_path = os.path.join(E.cfg, "chpw.json") db_path = os.path.join(E.cfg, "chpw.json")
ap2 = ap.add_argument_group('user-changeable passwords options') ap2 = ap.add_argument_group("user-changeable passwords options")
ap2.add_argument("--chpw", action="store_true", help="allow users to change their own passwords") ap2.add_argument("--chpw", action="store_true", help="allow users to change their own passwords")
ap2.add_argument("--chpw-no", metavar="U,U,U", type=u, action="append", help="do not allow password-changes for this comma-separated list of usernames") ap2.add_argument("--chpw-no", metavar="U,U,U", type=u, action="append", help="\033[34mREPEATABLE:\033[0m do not allow password-changes for this comma-separated list of usernames")
ap2.add_argument("--chpw-db", metavar="PATH", type=u, default=db_path, help="where to store the passwords database (if you run multiple copyparty instances, make sure they use different DBs)") ap2.add_argument("--chpw-db", metavar="PATH", type=u, default=db_path, help="where to store the passwords database (if you run multiple copyparty instances, make sure they use different DBs)")
ap2.add_argument("--chpw-len", metavar="N", type=int, default=8, help="minimum password length") ap2.add_argument("--chpw-len", metavar="N", type=int, default=8, help="minimum password length")
ap2.add_argument("--chpw-v", metavar="LVL", type=int, default=2, help="verbosity of summary on config load [\033[32m0\033[0m] = nothing at all, [\033[32m1\033[0m] = number of users, [\033[32m2\033[0m] = list users with default-pw, [\033[32m3\033[0m] = list all users") ap2.add_argument("--chpw-v", metavar="LVL", type=int, default=2, help="verbosity of summary on config load [\033[32m0\033[0m] = nothing at all, [\033[32m1\033[0m] = number of users, [\033[32m2\033[0m] = list users with default-pw, [\033[32m3\033[0m] = list all users")
@ -1212,12 +1218,12 @@ def add_zc_ssdp(ap):
def add_ftp(ap): def add_ftp(ap):
ap2 = ap.add_argument_group('FTP options (TCP only)') ap2 = ap.add_argument_group("FTP options (TCP only)")
ap2.add_argument("--ftp", metavar="PORT", type=int, default=0, help="enable FTP server on \033[33mPORT\033[0m, for example \033[32m3921") ap2.add_argument("--ftp", metavar="PORT", type=int, default=0, help="enable FTP server on \033[33mPORT\033[0m, for example \033[32m3921")
ap2.add_argument("--ftps", metavar="PORT", type=int, default=0, help="enable FTPS server on \033[33mPORT\033[0m, for example \033[32m3990") ap2.add_argument("--ftps", metavar="PORT", type=int, default=0, help="enable FTPS server on \033[33mPORT\033[0m, for example \033[32m3990")
ap2.add_argument("--ftpv", action="store_true", help="verbose") ap2.add_argument("--ftpv", action="store_true", help="verbose")
ap2.add_argument("--ftp4", action="store_true", help="only listen on IPv4") ap2.add_argument("--ftp4", action="store_true", help="only listen on IPv4")
ap2.add_argument("--ftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m; specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") ap2.add_argument("--ftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m (comma-separated); specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]")
ap2.add_argument("--ftp-no-ow", action="store_true", help="if target file exists, reject upload instead of overwrite") ap2.add_argument("--ftp-no-ow", action="store_true", help="if target file exists, reject upload instead of overwrite")
ap2.add_argument("--ftp-wt", metavar="SEC", type=int, default=7, help="grace period for resuming interrupted uploads (any client can write to any file last-modified more recently than \033[33mSEC\033[0m seconds ago)") ap2.add_argument("--ftp-wt", metavar="SEC", type=int, default=7, help="grace period for resuming interrupted uploads (any client can write to any file last-modified more recently than \033[33mSEC\033[0m seconds ago)")
ap2.add_argument("--ftp-nat", metavar="ADDR", type=u, default="", help="the NAT address to use for passive connections") ap2.add_argument("--ftp-nat", metavar="ADDR", type=u, default="", help="the NAT address to use for passive connections")
@ -1225,7 +1231,7 @@ def add_ftp(ap):
def add_webdav(ap): def add_webdav(ap):
ap2 = ap.add_argument_group('WebDAV options') ap2 = ap.add_argument_group("WebDAV options")
ap2.add_argument("--daw", action="store_true", help="enable full write support, even if client may not be webdav. \033[1;31mWARNING:\033[0m This has side-effects -- PUT-operations will now \033[1;31mOVERWRITE\033[0m existing files, rather than inventing new filenames to avoid loss of data. You might want to instead set this as a volflag where needed. By not setting this flag, uploaded files can get written to a filename which the client does not expect (which might be okay, depending on client)") ap2.add_argument("--daw", action="store_true", help="enable full write support, even if client may not be webdav. \033[1;31mWARNING:\033[0m This has side-effects -- PUT-operations will now \033[1;31mOVERWRITE\033[0m existing files, rather than inventing new filenames to avoid loss of data. You might want to instead set this as a volflag where needed. By not setting this flag, uploaded files can get written to a filename which the client does not expect (which might be okay, depending on client)")
ap2.add_argument("--dav-inf", action="store_true", help="allow depth:infinite requests (recursive file listing); extremely server-heavy but required for spec compliance -- luckily few clients rely on this") ap2.add_argument("--dav-inf", action="store_true", help="allow depth:infinite requests (recursive file listing); extremely server-heavy but required for spec compliance -- luckily few clients rely on this")
ap2.add_argument("--dav-mac", action="store_true", help="disable apple-garbage filter -- allow macos to create junk files (._* and .DS_Store, .Spotlight-*, .fseventsd, .Trashes, .AppleDouble, __MACOS)") ap2.add_argument("--dav-mac", action="store_true", help="disable apple-garbage filter -- allow macos to create junk files (._* and .DS_Store, .Spotlight-*, .fseventsd, .Trashes, .AppleDouble, __MACOS)")
@ -1235,7 +1241,7 @@ def add_webdav(ap):
def add_tftp(ap): def add_tftp(ap):
ap2 = ap.add_argument_group('TFTP options (UDP only)') ap2 = ap.add_argument_group("TFTP options (UDP only)")
ap2.add_argument("--tftp", metavar="PORT", type=int, default=0, help="enable TFTP server on \033[33mPORT\033[0m, for example \033[32m69 \033[0mor \033[32m3969") ap2.add_argument("--tftp", metavar="PORT", type=int, default=0, help="enable TFTP server on \033[33mPORT\033[0m, for example \033[32m69 \033[0mor \033[32m3969")
ap2.add_argument("--tftp4", action="store_true", help="only listen on IPv4") ap2.add_argument("--tftp4", action="store_true", help="only listen on IPv4")
ap2.add_argument("--tftpv", action="store_true", help="verbose") ap2.add_argument("--tftpv", action="store_true", help="verbose")
@ -1243,12 +1249,12 @@ def add_tftp(ap):
ap2.add_argument("--tftp-no-fast", action="store_true", help="debug: disable optimizations") ap2.add_argument("--tftp-no-fast", action="store_true", help="debug: disable optimizations")
ap2.add_argument("--tftp-lsf", metavar="PTN", type=u, default="\\.?(dir|ls)(\\.txt)?", help="return a directory listing if a file with this name is requested and it does not exist; defaults matches .ls, dir, .dir.txt, ls.txt, ...") ap2.add_argument("--tftp-lsf", metavar="PTN", type=u, default="\\.?(dir|ls)(\\.txt)?", help="return a directory listing if a file with this name is requested and it does not exist; defaults matches .ls, dir, .dir.txt, ls.txt, ...")
ap2.add_argument("--tftp-nols", action="store_true", help="if someone tries to download a directory, return an error instead of showing its directory listing") ap2.add_argument("--tftp-nols", action="store_true", help="if someone tries to download a directory, return an error instead of showing its directory listing")
ap2.add_argument("--tftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m; specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") ap2.add_argument("--tftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m (comma-separated); specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]")
ap2.add_argument("--tftp-pr", metavar="P-P", type=u, default="", help="the range of UDP ports to use for data transfer, for example \033[32m12000-13000") ap2.add_argument("--tftp-pr", metavar="P-P", type=u, default="", help="the range of UDP ports to use for data transfer, for example \033[32m12000-13000")
def add_smb(ap): def add_smb(ap):
ap2 = ap.add_argument_group('SMB/CIFS options') ap2 = ap.add_argument_group("SMB/CIFS options")
ap2.add_argument("--smb", action="store_true", help="enable smb (read-only) -- this requires running copyparty as root on linux and macos unless \033[33m--smb-port\033[0m is set above 1024 and your OS does port-forwarding from 445 to that.\n\033[1;31mWARNING:\033[0m this protocol is DANGEROUS and buggy! Never expose to the internet!") ap2.add_argument("--smb", action="store_true", help="enable smb (read-only) -- this requires running copyparty as root on linux and macos unless \033[33m--smb-port\033[0m is set above 1024 and your OS does port-forwarding from 445 to that.\n\033[1;31mWARNING:\033[0m this protocol is DANGEROUS and buggy! Never expose to the internet!")
ap2.add_argument("--smbw", action="store_true", help="enable write support (please dont)") ap2.add_argument("--smbw", action="store_true", help="enable write support (please dont)")
ap2.add_argument("--smb1", action="store_true", help="disable SMBv2, only enable SMBv1 (CIFS)") ap2.add_argument("--smb1", action="store_true", help="disable SMBv2, only enable SMBv1 (CIFS)")
@ -1262,30 +1268,30 @@ def add_smb(ap):
def add_handlers(ap): def add_handlers(ap):
ap2 = ap.add_argument_group('handlers (see --help-handlers)') ap2 = ap.add_argument_group("handlers (see --help-handlers)")
ap2.add_argument("--on404", metavar="PY", type=u, action="append", help="handle 404s by executing \033[33mPY\033[0m file") ap2.add_argument("--on404", metavar="PY", type=u, action="append", help="\033[34mREPEATABLE:\033[0m handle 404s by executing \033[33mPY\033[0m file")
ap2.add_argument("--on403", metavar="PY", type=u, action="append", help="handle 403s by executing \033[33mPY\033[0m file") ap2.add_argument("--on403", metavar="PY", type=u, action="append", help="\033[34mREPEATABLE:\033[0m handle 403s by executing \033[33mPY\033[0m file")
ap2.add_argument("--hot-handlers", action="store_true", help="recompile handlers on each request -- expensive but convenient when hacking on stuff") ap2.add_argument("--hot-handlers", action="store_true", help="recompile handlers on each request -- expensive but convenient when hacking on stuff")
def add_hooks(ap): def add_hooks(ap):
ap2 = ap.add_argument_group('event hooks (see --help-hooks)') ap2 = ap.add_argument_group("event hooks (see --help-hooks)")
ap2.add_argument("--xbu", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file upload starts") ap2.add_argument("--xbu", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m before a file upload starts")
ap2.add_argument("--xau", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file upload finishes") ap2.add_argument("--xau", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m after a file upload finishes")
ap2.add_argument("--xiu", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after all uploads finish and volume is idle") ap2.add_argument("--xiu", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m after all uploads finish and volume is idle")
ap2.add_argument("--xbc", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file copy") ap2.add_argument("--xbc", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m before a file copy")
ap2.add_argument("--xac", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file copy") ap2.add_argument("--xac", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m after a file copy")
ap2.add_argument("--xbr", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file move/rename") ap2.add_argument("--xbr", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m before a file move/rename")
ap2.add_argument("--xar", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file move/rename") ap2.add_argument("--xar", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m after a file move/rename")
ap2.add_argument("--xbd", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file delete") ap2.add_argument("--xbd", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m before a file delete")
ap2.add_argument("--xad", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file delete") ap2.add_argument("--xad", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m after a file delete")
ap2.add_argument("--xm", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m on message") ap2.add_argument("--xm", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m on message")
ap2.add_argument("--xban", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m if someone gets banned (pw/404/403/url)") ap2.add_argument("--xban", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m if someone gets banned (pw/404/403/url)")
ap2.add_argument("--hook-v", action="store_true", help="verbose hooks") ap2.add_argument("--hook-v", action="store_true", help="verbose hooks")
def add_stats(ap): def add_stats(ap):
ap2 = ap.add_argument_group('grafana/prometheus metrics endpoint') ap2 = ap.add_argument_group("grafana/prometheus metrics endpoint")
ap2.add_argument("--stats", action="store_true", help="enable openmetrics at /.cpr/metrics for admin accounts") ap2.add_argument("--stats", action="store_true", help="enable openmetrics at /.cpr/metrics for admin accounts")
ap2.add_argument("--nos-hdd", action="store_true", help="disable disk-space metrics (used/free space)") ap2.add_argument("--nos-hdd", action="store_true", help="disable disk-space metrics (used/free space)")
ap2.add_argument("--nos-vol", action="store_true", help="disable volume size metrics (num files, total bytes, vmaxb/vmaxn)") ap2.add_argument("--nos-vol", action="store_true", help="disable volume size metrics (num files, total bytes, vmaxb/vmaxn)")
@ -1295,15 +1301,16 @@ def add_stats(ap):
def add_yolo(ap): def add_yolo(ap):
ap2 = ap.add_argument_group('yolo options') ap2 = ap.add_argument_group("yolo options")
ap2.add_argument("--allow-csrf", action="store_true", help="disable csrf protections; let other domains/sites impersonate you through cross-site requests") ap2.add_argument("--allow-csrf", action="store_true", help="disable csrf protections; let other domains/sites impersonate you through cross-site requests")
ap2.add_argument("--cookie-lax", action="store_true", help="allow cookies from other domains (if you follow a link from another website into your server, you will arrive logged-in); this reduces protection against CSRF") ap2.add_argument("--cookie-lax", action="store_true", help="allow cookies from other domains (if you follow a link from another website into your server, you will arrive logged-in); this reduces protection against CSRF")
ap2.add_argument("--no-fnugg", action="store_true", help="disable the smoketest for caching-related issues in the web-UI")
ap2.add_argument("--getmod", action="store_true", help="permit ?move=[...] and ?delete as GET") ap2.add_argument("--getmod", action="store_true", help="permit ?move=[...] and ?delete as GET")
ap2.add_argument("--wo-up-readme", action="store_true", help="allow users with write-only access to upload logues and readmes without adding the _wo_ filename prefix (volflag=wo_up_readme)") ap2.add_argument("--wo-up-readme", action="store_true", help="allow users with write-only access to upload logues and readmes without adding the _wo_ filename prefix (volflag=wo_up_readme)")
def add_optouts(ap): def add_optouts(ap):
ap2 = ap.add_argument_group('opt-outs') ap2 = ap.add_argument_group("opt-outs")
ap2.add_argument("-nw", action="store_true", help="never write anything to disk (debug/benchmark)") ap2.add_argument("-nw", action="store_true", help="never write anything to disk (debug/benchmark)")
ap2.add_argument("--keep-qem", action="store_true", help="do not disable quick-edit-mode on windows (it is disabled to avoid accidental text selection in the terminal window, as this would pause execution)") ap2.add_argument("--keep-qem", action="store_true", help="do not disable quick-edit-mode on windows (it is disabled to avoid accidental text selection in the terminal window, as this would pause execution)")
ap2.add_argument("--no-dav", action="store_true", help="disable webdav support") ap2.add_argument("--no-dav", action="store_true", help="disable webdav support")
@ -1329,7 +1336,7 @@ def add_optouts(ap):
def add_safety(ap): def add_safety(ap):
ap2 = ap.add_argument_group('safety options') ap2 = ap.add_argument_group("safety options")
ap2.add_argument("-s", action="count", default=0, help="increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers.\n └─Alias of\033[32m --dotpart --no-thumb --no-mtag-ff --no-robots --force-js") ap2.add_argument("-s", action="count", default=0, help="increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers.\n └─Alias of\033[32m --dotpart --no-thumb --no-mtag-ff --no-robots --force-js")
ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --hardlink --vague-403 -nih") ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --hardlink --vague-403 -nih")
ap2.add_argument("-sss", action="store_true", help="further increase safety: Enable logging to disk, scan for dangerous symlinks.\n └─Alias of\033[32m -ss --no-dav --no-logues --no-readme -lo=cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz --ls=**,*,ln,p,r") ap2.add_argument("-sss", action="store_true", help="further increase safety: Enable logging to disk, scan for dangerous symlinks.\n └─Alias of\033[32m -ss --no-dav --no-logues --no-readme -lo=cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz --ls=**,*,ln,p,r")
@ -1360,7 +1367,7 @@ def add_safety(ap):
def add_salt(ap, fk_salt, dk_salt, ah_salt): def add_salt(ap, fk_salt, dk_salt, ah_salt):
ap2 = ap.add_argument_group('salting options') ap2 = ap.add_argument_group("salting options")
ap2.add_argument("--ah-alg", metavar="ALG", type=u, default="none", help="account-pw hashing algorithm; one of these, best to worst: \033[32margon2 scrypt sha2 none\033[0m (each optionally followed by alg-specific comma-sep. config)") ap2.add_argument("--ah-alg", metavar="ALG", type=u, default="none", help="account-pw hashing algorithm; one of these, best to worst: \033[32margon2 scrypt sha2 none\033[0m (each optionally followed by alg-specific comma-sep. config)")
ap2.add_argument("--ah-salt", metavar="SALT", type=u, default=ah_salt, help="account-pw salt; ignored if \033[33m--ah-alg\033[0m is none (default)") ap2.add_argument("--ah-salt", metavar="SALT", type=u, default=ah_salt, help="account-pw salt; ignored if \033[33m--ah-alg\033[0m is none (default)")
ap2.add_argument("--ah-gen", metavar="PW", type=u, default="", help="generate hashed password for \033[33mPW\033[0m, or read passwords from STDIN if \033[33mPW\033[0m is [\033[32m-\033[0m]") ap2.add_argument("--ah-gen", metavar="PW", type=u, default="", help="generate hashed password for \033[33mPW\033[0m, or read passwords from STDIN if \033[33mPW\033[0m is [\033[32m-\033[0m]")
@ -1374,14 +1381,14 @@ def add_salt(ap, fk_salt, dk_salt, ah_salt):
def add_shutdown(ap): def add_shutdown(ap):
ap2 = ap.add_argument_group('shutdown options') ap2 = ap.add_argument_group("shutdown options")
ap2.add_argument("--ign-ebind", action="store_true", help="continue running even if it's impossible to listen on some of the requested endpoints") ap2.add_argument("--ign-ebind", action="store_true", help="continue running even if it's impossible to listen on some of the requested endpoints")
ap2.add_argument("--ign-ebind-all", action="store_true", help="continue running even if it's impossible to receive connections at all") ap2.add_argument("--ign-ebind-all", action="store_true", help="continue running even if it's impossible to receive connections at all")
ap2.add_argument("--exit", metavar="WHEN", type=u, default="", help="shutdown after \033[33mWHEN\033[0m has finished; [\033[32mcfg\033[0m] config parsing, [\033[32midx\033[0m] volscan + multimedia indexing") ap2.add_argument("--exit", metavar="WHEN", type=u, default="", help="shutdown after \033[33mWHEN\033[0m has finished; [\033[32mcfg\033[0m] config parsing, [\033[32midx\033[0m] volscan + multimedia indexing")
def add_logging(ap): def add_logging(ap):
ap2 = ap.add_argument_group('logging options') ap2 = ap.add_argument_group("logging options")
ap2.add_argument("-q", action="store_true", help="quiet; disable most STDOUT messages") ap2.add_argument("-q", action="store_true", help="quiet; disable most STDOUT messages")
ap2.add_argument("-lo", metavar="PATH", type=u, default="", help="logfile, example: \033[32mcpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz\033[0m (NB: some errors may appear on STDOUT only)") ap2.add_argument("-lo", metavar="PATH", type=u, default="", help="logfile, example: \033[32mcpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz\033[0m (NB: some errors may appear on STDOUT only)")
ap2.add_argument("--no-ansi", action="store_true", default=not VT100, help="disable colors; same as environment-variable NO_COLOR") ap2.add_argument("--no-ansi", action="store_true", default=not VT100, help="disable colors; same as environment-variable NO_COLOR")
@ -1390,7 +1397,7 @@ def add_logging(ap):
ap2.add_argument("--no-voldump", action="store_true", help="do not list volumes and permissions on startup") ap2.add_argument("--no-voldump", action="store_true", help="do not list volumes and permissions on startup")
ap2.add_argument("--log-utc", action="store_true", help="do not use local timezone; assume the TZ env-var is UTC (tiny bit faster)") ap2.add_argument("--log-utc", action="store_true", help="do not use local timezone; assume the TZ env-var is UTC (tiny bit faster)")
ap2.add_argument("--log-tdec", metavar="N", type=int, default=3, help="timestamp resolution / number of timestamp decimals") ap2.add_argument("--log-tdec", metavar="N", type=int, default=3, help="timestamp resolution / number of timestamp decimals")
ap2.add_argument("--log-badpwd", metavar="N", type=int, default=1, help="log failed login attempt passwords: 0=terse, 1=plaintext, 2=hashed") ap2.add_argument("--log-badpwd", metavar="N", type=int, default=2, help="log failed login attempt passwords: 0=terse, 1=plaintext, 2=hashed")
ap2.add_argument("--log-conn", action="store_true", help="debug: print tcp-server msgs") ap2.add_argument("--log-conn", action="store_true", help="debug: print tcp-server msgs")
ap2.add_argument("--log-htp", action="store_true", help="debug: print http-server threadpool scaling") ap2.add_argument("--log-htp", action="store_true", help="debug: print http-server threadpool scaling")
ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="print request \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all") ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="print request \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all")
@ -1399,7 +1406,7 @@ def add_logging(ap):
def add_admin(ap): def add_admin(ap):
ap2 = ap.add_argument_group('admin panel options') ap2 = ap.add_argument_group("admin panel options")
ap2.add_argument("--no-reload", action="store_true", help="disable ?reload=cfg (reload users/volumes/volflags from config file)") ap2.add_argument("--no-reload", action="store_true", help="disable ?reload=cfg (reload users/volumes/volflags from config file)")
ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)") ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)")
ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks)") ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks)")
@ -1413,7 +1420,7 @@ def add_admin(ap):
def add_thumbnail(ap): def add_thumbnail(ap):
th_ram = (RAM_AVAIL or RAM_TOTAL or 9) * 0.6 th_ram = (RAM_AVAIL or RAM_TOTAL or 9) * 0.6
th_ram = int(max(min(th_ram, 6), 0.3) * 10) / 10 th_ram = int(max(min(th_ram, 6), 0.3) * 10) / 10
ap2 = ap.add_argument_group('thumbnail options') ap2 = ap.add_argument_group("thumbnail options")
ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails (volflag=dthumb)") ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails (volflag=dthumb)")
ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails (volflag=dvthumb)") ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails (volflag=dvthumb)")
ap2.add_argument("--no-athumb", action="store_true", help="disable audio thumbnails (spectrograms) (volflag=dathumb)") ap2.add_argument("--no-athumb", action="store_true", help="disable audio thumbnails (spectrograms) (volflag=dathumb)")
@ -1445,7 +1452,7 @@ def add_thumbnail(ap):
def add_transcoding(ap): def add_transcoding(ap):
ap2 = ap.add_argument_group('transcoding options') ap2 = ap.add_argument_group("transcoding options")
ap2.add_argument("--q-opus", metavar="KBPS", type=int, default=128, help="target bitrate for transcoding to opus; set 0 to disable") ap2.add_argument("--q-opus", metavar="KBPS", type=int, default=128, help="target bitrate for transcoding to opus; set 0 to disable")
ap2.add_argument("--q-mp3", metavar="QUALITY", type=u, default="q2", help="target quality for transcoding to mp3, for example [\033[32m192k\033[0m] (CBR) or [\033[32mq0\033[0m] (CQ/CRF, q0=maxquality, q9=smallest); set 0 to disable") ap2.add_argument("--q-mp3", metavar="QUALITY", type=u, default="q2", help="target quality for transcoding to mp3, for example [\033[32m192k\033[0m] (CBR) or [\033[32mq0\033[0m] (CQ/CRF, q0=maxquality, q9=smallest); set 0 to disable")
ap2.add_argument("--allow-wav", action="store_true", help="allow transcoding to wav (lossless, uncompressed)") ap2.add_argument("--allow-wav", action="store_true", help="allow transcoding to wav (lossless, uncompressed)")
@ -1458,7 +1465,7 @@ def add_transcoding(ap):
def add_tail(ap): def add_tail(ap):
ap2 = ap.add_argument_group('tailing options (realtime streaming of a growing file)') ap2 = ap.add_argument_group("tailing options (realtime streaming of a growing file)")
ap2.add_argument("--tail-who", metavar="LVL", type=int, default=2, help="who can tail? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=authenticated-with-read-access, [\033[32m3\033[0m]=everyone-with-read-access (volflag=tail_who)") ap2.add_argument("--tail-who", metavar="LVL", type=int, default=2, help="who can tail? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=authenticated-with-read-access, [\033[32m3\033[0m]=everyone-with-read-access (volflag=tail_who)")
ap2.add_argument("--tail-cmax", metavar="N", type=int, default=64, help="do not allow starting a new tail if more than \033[33mN\033[0m active downloads") ap2.add_argument("--tail-cmax", metavar="N", type=int, default=64, help="do not allow starting a new tail if more than \033[33mN\033[0m active downloads")
ap2.add_argument("--tail-tmax", metavar="SEC", type=float, default=0, help="terminate connection after \033[33mSEC\033[0m seconds; [\033[32m0\033[0m]=never (volflag=tail_tmax)") ap2.add_argument("--tail-tmax", metavar="SEC", type=float, default=0, help="terminate connection after \033[33mSEC\033[0m seconds; [\033[32m0\033[0m]=never (volflag=tail_tmax)")
@ -1468,7 +1475,7 @@ def add_tail(ap):
def add_rss(ap): def add_rss(ap):
ap2 = ap.add_argument_group('RSS options') ap2 = ap.add_argument_group("RSS options")
ap2.add_argument("--rss", action="store_true", help="enable RSS output (experimental) (volflag=rss)") ap2.add_argument("--rss", action="store_true", help="enable RSS output (experimental) (volflag=rss)")
ap2.add_argument("--rss-nf", metavar="HITS", type=int, default=250, help="default number of files to return (url-param 'nf')") ap2.add_argument("--rss-nf", metavar="HITS", type=int, default=250, help="default number of files to return (url-param 'nf')")
ap2.add_argument("--rss-fext", metavar="E,E", type=u, default="", help="default list of file extensions to include (url-param 'fext'); blank=all") ap2.add_argument("--rss-fext", metavar="E,E", type=u, default="", help="default list of file extensions to include (url-param 'fext'); blank=all")
@ -1477,7 +1484,7 @@ def add_rss(ap):
def add_db_general(ap, hcores): def add_db_general(ap, hcores):
noidx = APPLESAN_TXT if MACOS else "" noidx = APPLESAN_TXT if MACOS else ""
ap2 = ap.add_argument_group('general db options') ap2 = ap.add_argument_group("general db options")
ap2.add_argument("-e2d", action="store_true", help="enable up2k database; this enables file search, upload-undo, improves deduplication") ap2.add_argument("-e2d", action="store_true", help="enable up2k database; this enables file search, upload-undo, improves deduplication")
ap2.add_argument("-e2ds", action="store_true", help="scan writable folders for new files on startup; sets \033[33m-e2d\033[0m") ap2.add_argument("-e2ds", action="store_true", help="scan writable folders for new files on startup; sets \033[33m-e2d\033[0m")
ap2.add_argument("-e2dsa", action="store_true", help="scans all folders on startup; sets \033[33m-e2ds\033[0m") ap2.add_argument("-e2dsa", action="store_true", help="scans all folders on startup; sets \033[33m-e2ds\033[0m")
@ -1506,7 +1513,7 @@ def add_db_general(ap, hcores):
def add_db_metadata(ap): def add_db_metadata(ap):
ap2 = ap.add_argument_group('metadata db options') ap2 = ap.add_argument_group("metadata db options")
ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing; makes it possible to search for artist/title/codec/resolution/...") ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing; makes it possible to search for artist/title/codec/resolution/...")
ap2.add_argument("-e2ts", action="store_true", help="scan newly discovered files for metadata on startup; sets \033[33m-e2t\033[0m") ap2.add_argument("-e2ts", action="store_true", help="scan newly discovered files for metadata on startup; sets \033[33m-e2t\033[0m")
ap2.add_argument("-e2tsr", action="store_true", help="delete all metadata from DB and do a full rescan; sets \033[33m-e2ts\033[0m") ap2.add_argument("-e2tsr", action="store_true", help="delete all metadata from DB and do a full rescan; sets \033[33m-e2ts\033[0m")
@ -1516,15 +1523,16 @@ def add_db_metadata(ap):
ap2.add_argument("--mtag-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for tag scanning") ap2.add_argument("--mtag-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for tag scanning")
ap2.add_argument("--mtag-v", action="store_true", help="verbose tag scanning; print errors from mtp subprocesses and such") ap2.add_argument("--mtag-v", action="store_true", help="verbose tag scanning; print errors from mtp subprocesses and such")
ap2.add_argument("--mtag-vv", action="store_true", help="debug mtp settings and mutagen/FFprobe parsers") ap2.add_argument("--mtag-vv", action="store_true", help="debug mtp settings and mutagen/FFprobe parsers")
ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping") ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="\033[34mREPEATABLE:\033[0m add/replace metadata mapping")
ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.); either an entire replacement list, or add/remove stuff on the default-list with +foo or /bar", default=DEF_MTE) ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.); either an entire replacement list, or add/remove stuff on the default-list with +foo or /bar", default=DEF_MTE)
ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.); assign/add/remove same as \033[33m-mte\033[0m", default=DEF_MTH) ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.); assign/add/remove same as \033[33m-mte\033[0m", default=DEF_MTH)
ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="read tag \033[33mM\033[0m using program \033[33mBIN\033[0m to parse the file") ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="\033[34mREPEATABLE:\033[0m read tag \033[33mM\033[0m using program \033[33mBIN\033[0m to parse the file")
def add_txt(ap): def add_txt(ap):
ap2 = ap.add_argument_group('textfile options') ap2 = ap.add_argument_group("textfile options")
ap2.add_argument("--md-hist", metavar="TXT", type=u, default="s", help="where to store old version of markdown files; [\033[32ms\033[0m]=subfolder, [\033[32mv\033[0m]=volume-histpath, [\033[32mn\033[0m]=nope/disabled (volflag=md_hist)") ap2.add_argument("--md-hist", metavar="TXT", type=u, default="s", help="where to store old version of markdown files; [\033[32ms\033[0m]=subfolder, [\033[32mv\033[0m]=volume-histpath, [\033[32mn\033[0m]=nope/disabled (volflag=md_hist)")
ap2.add_argument("--txt-eol", metavar="TYPE", type=u, default="", help="enable EOL conversion when writing documents; supported: CRLF, LF (volflag=txt_eol)")
ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="the textfile editor will check for serverside changes every \033[33mSEC\033[0m seconds") ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="the textfile editor will check for serverside changes every \033[33mSEC\033[0m seconds")
ap2.add_argument("-emp", action="store_true", help="enable markdown plugins -- neat but dangerous, big XSS risk") ap2.add_argument("-emp", action="store_true", help="enable markdown plugins -- neat but dangerous, big XSS risk")
ap2.add_argument("--exp", action="store_true", help="enable textfile expansion -- replace {{self.ip}} and such; see \033[33m--help-exp\033[0m (volflag=exp)") ap2.add_argument("--exp", action="store_true", help="enable textfile expansion -- replace {{self.ip}} and such; see \033[33m--help-exp\033[0m (volflag=exp)")
@ -1534,7 +1542,7 @@ def add_txt(ap):
def add_og(ap): def add_og(ap):
ap2 = ap.add_argument_group('og / open graph / discord-embed options') ap2 = ap.add_argument_group("og / open graph / discord-embed options")
ap2.add_argument("--og", action="store_true", help="disable hotlinking and return an html document instead; this is required by open-graph, but can also be useful on its own (volflag=og)") ap2.add_argument("--og", action="store_true", help="disable hotlinking and return an html document instead; this is required by open-graph, but can also be useful on its own (volflag=og)")
ap2.add_argument("--og-ua", metavar="RE", type=u, default="", help="only disable hotlinking / engage OG behavior if the useragent matches regex \033[33mRE\033[0m (volflag=og_ua)") ap2.add_argument("--og-ua", metavar="RE", type=u, default="", help="only disable hotlinking / engage OG behavior if the useragent matches regex \033[33mRE\033[0m (volflag=og_ua)")
ap2.add_argument("--og-tpl", metavar="PATH", type=u, default="", help="do not return the regular copyparty html, but instead load the jinja2 template at \033[33mPATH\033[0m (if path contains 'EXT' then EXT will be replaced with the requested file's extension) (volflag=og_tpl)") ap2.add_argument("--og-tpl", metavar="PATH", type=u, default="", help="do not return the regular copyparty html, but instead load the jinja2 template at \033[33mPATH\033[0m (if path contains 'EXT' then EXT will be replaced with the requested file's extension) (volflag=og_tpl)")
@ -1552,7 +1560,7 @@ def add_og(ap):
def add_ui(ap, retry): def add_ui(ap, retry):
ap2 = ap.add_argument_group('ui options') ap2 = ap.add_argument_group("ui options")
ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)") ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)")
ap2.add_argument("--gsel", action="store_true", help="select files in grid by ctrl-click (volflag=gsel)") ap2.add_argument("--gsel", action="store_true", help="select files in grid by ctrl-click (volflag=gsel)")
ap2.add_argument("--localtime", action="store_true", help="default to local timezone instead of UTC") ap2.add_argument("--localtime", action="store_true", help="default to local timezone instead of UTC")
@ -1567,7 +1575,7 @@ def add_ui(ap, retry):
ap2.add_argument("--qdel", metavar="LVL", type=int, default=2, help="number of confirmations to show when deleting files (2/1/0)") ap2.add_argument("--qdel", metavar="LVL", type=int, default=2, help="number of confirmations to show when deleting files (2/1/0)")
ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files/folders matching \033[33mREGEX\033[0m in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)") ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files/folders matching \033[33mREGEX\033[0m in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable") ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable")
ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="use thumbnail-image \033[33mVP\033[0m for file-extension \033[33mE\033[0m, example: [\033[32mexe=/.res/exe.png\033[0m] (volflag=ext_th)") ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="\033[34mREPEATABLE:\033[0m use thumbnail-image \033[33mVP\033[0m for file-extension \033[33mE\033[0m, example: [\033[32mexe=/.res/exe.png\033[0m] (volflag=ext_th)")
ap2.add_argument("--mpmc", type=u, default="", help=argparse.SUPPRESS) ap2.add_argument("--mpmc", type=u, default="", help=argparse.SUPPRESS)
ap2.add_argument("--spinner", metavar="TXT", type=u, default="🌲", help="\033[33memoji\033[0m or \033[33memoji,css\033[0m Example: [\033[32m🥖,padding:0\033[0m]") ap2.add_argument("--spinner", metavar="TXT", type=u, default="🌲", help="\033[33memoji\033[0m or \033[33memoji,css\033[0m Example: [\033[32m🥖,padding:0\033[0m]")
ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include in the filebrowser html") ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include in the filebrowser html")
@ -1583,6 +1591,7 @@ def add_ui(ap, retry):
ap2.add_argument("--ver", action="store_true", help="show version on the control panel (incompatible 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("--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") 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")
ap2.add_argument("--ctl-re", metavar="SEC", type=int, default=1, help="the controlpanel Refresh-button will autorefresh every SEC; [\033[32m0\033[0m] = just once")
ap2.add_argument("--md-sbf", metavar="FLAGS", type=u, default="downloads forms popups scripts top-navigation-by-user-activation", help="list of capabilities to allow in the iframe 'sandbox' attribute for README.md docs (volflag=md_sbf); see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox") ap2.add_argument("--md-sbf", metavar="FLAGS", type=u, default="downloads forms popups scripts top-navigation-by-user-activation", help="list of capabilities to allow in the iframe 'sandbox' attribute for README.md docs (volflag=md_sbf); see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox")
ap2.add_argument("--lg-sbf", metavar="FLAGS", type=u, default="downloads forms popups scripts top-navigation-by-user-activation", help="list of capabilities to allow in the iframe 'sandbox' attribute for prologue/epilogue docs (volflag=lg_sbf)") ap2.add_argument("--lg-sbf", metavar="FLAGS", type=u, default="downloads forms popups scripts top-navigation-by-user-activation", help="list of capabilities to allow in the iframe 'sandbox' attribute for prologue/epilogue docs (volflag=lg_sbf)")
ap2.add_argument("--md-sba", metavar="TXT", type=u, default="", help="the value of the iframe 'allow' attribute for README.md docs, for example [\033[32mfullscreen\033[0m] (volflag=md_sba)") ap2.add_argument("--md-sba", metavar="TXT", type=u, default="", help="the value of the iframe 'allow' attribute for README.md docs, for example [\033[32mfullscreen\033[0m] (volflag=md_sba)")
@ -1593,7 +1602,7 @@ def add_ui(ap, retry):
def add_debug(ap): def add_debug(ap):
ap2 = ap.add_argument_group('debug options') ap2 = ap.add_argument_group("debug options")
ap2.add_argument("--vc", action="store_true", help="verbose config file parser (explain config)") ap2.add_argument("--vc", action="store_true", help="verbose config file parser (explain config)")
ap2.add_argument("--cgen", action="store_true", help="generate config file from current config (best-effort; probably buggy)") ap2.add_argument("--cgen", action="store_true", help="generate config file from current config (best-effort; probably buggy)")
ap2.add_argument("--deps", action="store_true", help="list information about detected optional dependencies") ap2.add_argument("--deps", action="store_true", help="list information about detected optional dependencies")

View file

@ -1,8 +1,8 @@
# coding: utf-8 # coding: utf-8
VERSION = (1, 18, 10) VERSION = (1, 19, 0)
CODENAME = "logtail" CODENAME = "usernames"
BUILD_DT = (2025, 8, 4) BUILD_DT = (2025, 8, 7)
S_VERSION = ".".join(map(str, VERSION)) S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT) S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View file

@ -1700,6 +1700,7 @@ class AuthSrv(object):
if not mount and not self.args.idp_h_usr: if not mount and not self.args.idp_h_usr:
# -h says our defaults are CWD at root and read/write for everyone # -h says our defaults are CWD at root and read/write for everyone
axs = AXS(["*"], ["*"], None, None) axs = AXS(["*"], ["*"], None, None)
ehint = ""
if self.is_lxc: if self.is_lxc:
t = "Read-access has been disabled due to failsafe: Docker detected, but %s. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to all of /w/ by adding the following arguments to the docker container: -v .::rw" t = "Read-access has been disabled due to failsafe: Docker detected, but %s. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to all of /w/ by adding the following arguments to the docker container: -v .::rw"
if len(cfg_files_loaded) == 1: if len(cfg_files_loaded) == 1:
@ -1709,10 +1710,21 @@ class AuthSrv(object):
else: else:
self.log(t % ("the config does not define any volumes",), 1) self.log(t % ("the config does not define any volumes",), 1)
axs = AXS() axs = AXS()
ehint = "; please try moving them up one level, into the parent folder:"
elif self.args.c: elif self.args.c:
t = "Read-access has been disabled due to failsafe: No volumes were defined by the config-file. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to the working-directory by adding the following arguments: -v .::rw" t = "Read-access has been disabled due to failsafe: No volumes were defined by the config-file. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to the working-directory by adding the following arguments: -v .::rw"
self.log(t, 1) self.log(t, 1)
axs = AXS() axs = AXS()
ehint = ":"
if ehint:
try:
files = os.listdir(E.cfg)
except:
files = []
hits = [x for x in files if x.lower().endswith(".conf")]
if hits:
t = "Hint: Found some config files in [%s], but these were not automatically loaded because they are in the wrong place%s %s\n"
self.log(t % (E.cfg, ehint, ", ".join(hits)), 3)
zvf = {"tcolor": self.args.tcolor} zvf = {"tcolor": self.args.tcolor}
vfs = VFS(self.log_func, absreal("."), "", "", axs, zvf) vfs = VFS(self.log_func, absreal("."), "", "", axs, zvf)
if not axs.uread: if not axs.uread:
@ -2630,6 +2642,8 @@ class AuthSrv(object):
self.re_pwd = None self.re_pwd = None
pwds = [re.escape(x) for x in self.iacct.keys()] pwds = [re.escape(x) for x in self.iacct.keys()]
pwds.extend(list(self.sesa)) pwds.extend(list(self.sesa))
if self.args.usernames:
pwds.extend([x.split(":", 1)[1] for x in pwds if ":" in x])
if pwds: if pwds:
if self.ah.on: if self.ah.on:
zs = r"(\[H\] pw:.*|[?&]pw=)([^&]+)" zs = r"(\[H\] pw:.*|[?&]pw=)([^&]+)"
@ -2930,6 +2944,9 @@ class AuthSrv(object):
t = "minimum password length: %d characters" t = "minimum password length: %d characters"
return False, t % (self.args.chpw_len,) return False, t % (self.args.chpw_len,)
if self.args.usernames:
pw = "%s:%s" % (uname, pw)
hpw = self.ah.hash(pw) if self.ah.on else pw hpw = self.ah.hash(pw) if self.ah.on else pw
if hpw == self.acct[uname]: if hpw == self.acct[uname]:
@ -3021,6 +3038,12 @@ class AuthSrv(object):
self.log("chpw: " + msg, 6) self.log("chpw: " + msg, 6)
def setup_pwhash(self, acct: dict[str, str]) -> None: def setup_pwhash(self, acct: dict[str, str]) -> None:
if self.args.usernames:
for uname, pw in list(acct.items())[:]:
if pw.startswith("+") and len(pw) == 33:
continue
acct[uname] = "%s:%s" % (uname, pw)
self.ah = PWHash(self.args) self.ah = PWHash(self.args)
if not self.ah.on: if not self.ah.on:
if self.args.ah_cli or self.args.ah_gen: if self.args.ah_cli or self.args.ah_gen:

View file

@ -111,6 +111,7 @@ def vf_vmap() -> dict[str, str]:
"tail_tmax", "tail_tmax",
"tail_who", "tail_who",
"tcolor", "tcolor",
"txt_eol",
"unlist", "unlist",
"u2abort", "u2abort",
"u2ts", "u2ts",
@ -322,6 +323,7 @@ flagcats = {
"exp": "enable textfile expansion; see --help-exp", "exp": "enable textfile expansion; see --help-exp",
"exp_md": "placeholders to expand in markdown files; see --help", "exp_md": "placeholders to expand in markdown files; see --help",
"exp_lg": "placeholders to expand in prologue/epilogue; see --help", "exp_lg": "placeholders to expand in prologue/epilogue; see --help",
"txt_eol=lf": "enable EOL conversion when writing docs (LF or CRLF)",
}, },
"tailing": { "tailing": {
"notail": "disable ?tail (download a growing file continuously)", "notail": "disable ?tail (download a growing file continuously)",

View file

@ -83,7 +83,12 @@ class FtpAuth(DummyAuthorizer):
uname = "*" uname = "*"
if username != "anonymous": if username != "anonymous":
uname = "" uname = ""
for zs in (password, username): if args.usernames:
alts = ["%s:%s" % (username, password)]
else:
alts = password, username
for zs in alts:
zs = asrv.iacct.get(asrv.ah.hash(zs), "") zs = asrv.iacct.get(asrv.ah.hash(zs), "")
if zs: if zs:
uname = zs uname = zs
@ -607,7 +612,7 @@ class Ftpd(object):
if "::" in ips: if "::" in ips:
ips.append("0.0.0.0") ips.append("0.0.0.0")
ips = [x for x in ips if "unix:" not in x] ips = [x for x in ips if not x.startswith(("unix:", "fd:"))]
if self.args.ftp4: if self.args.ftp4:
ips = [x for x in ips if ":" not in x] ips = [x for x in ips if ":" not in x]

View file

@ -62,6 +62,7 @@ from .util import (
alltrace, alltrace,
atomic_move, atomic_move,
b64dec, b64dec,
eol_conv,
exclude_dotfiles, exclude_dotfiles,
formatdate, formatdate,
fsenc, fsenc,
@ -262,7 +263,8 @@ class HttpCli(object):
def _assert_safe_rem(self, rem: str) -> None: def _assert_safe_rem(self, rem: str) -> None:
# sanity check to prevent any disasters # sanity check to prevent any disasters
if rem.startswith("/") or rem.startswith("../") or "/../" in rem: # (this function hopefully serves no purpose; validation has already happened at this point, this only exists as a last-ditch effort just in case)
if rem.startswith(("/", "../")) or "/../" in rem:
raise Exception("that was close") raise Exception("that was close")
def _gen_fk(self, alg: int, salt: str, fspath: str, fsize: int, inode: int) -> str: def _gen_fk(self, alg: int, salt: str, fspath: str, fsize: int, inode: int) -> str:
@ -383,9 +385,20 @@ class HttpCli(object):
try: try:
cli_ip = zsl[n].strip() cli_ip = zsl[n].strip()
except: except:
cli_ip = zsl[0].strip() cli_ip = self.ip
t = "rproxy={} oob x-fwd {}" self.bad_xff = True
self.log(t.format(self.args.rproxy, zso), c=3) if self.args.rproxy != 9999999:
t = "global-option --rproxy %d could not be used (out-of-bounds) for the received header [%s]"
self.log(t % (self.args.rproxy, zso), c=3)
else:
zsl = [
" rproxy: %d if this client's IP-address is [%s]"
% (-1 - zd, zs.strip())
for zd, zs in enumerate(zsl)
]
t = 'could not determine the client\'s IP-address because the global-option --rproxy has not been configured, so the request-header [%s] specified by global-option --xff-hdr cannot be used safely! Please see the "reverse-proxy" section in the readme. The best approach is to configure your reverse-proxy to give copyparty the exact IP-address to assume (perhaps in another header), but you may also try the following:'
t = t % (self.args.xff_hdr,)
self.log("%s\n\n%s\n" % (t, "\n".join(zsl)), 3)
pip = self.conn.addr[0] pip = self.conn.addr[0]
xffs = self.conn.xff_nm xffs = self.conn.xff_nm
@ -2924,12 +2937,16 @@ class HttpCli(object):
def handle_chpw(self) -> bool: def handle_chpw(self) -> bool:
assert self.parser # !rm assert self.parser # !rm
if self.args.usernames:
self.parser.require("uname", 64)
pwd = self.parser.require("pw", 64) pwd = self.parser.require("pw", 64)
self.parser.drop() self.parser.drop()
ok, msg = self.asrv.chpw(self.conn.hsrv.broker, self.uname, pwd) ok, msg = self.asrv.chpw(self.conn.hsrv.broker, self.uname, pwd)
if ok: if ok:
self.cbonk(self.conn.hsrv.gpwc, pwd, "pw", "too many password changes") self.cbonk(self.conn.hsrv.gpwc, pwd, "pw", "too many password changes")
if self.args.usernames:
pwd = "%s:%s" % (self.uname, pwd)
ok, msg = self.get_pwd_cookie(pwd) ok, msg = self.get_pwd_cookie(pwd)
if ok: if ok:
msg = "new password OK" msg = "new password OK"
@ -2942,6 +2959,15 @@ class HttpCli(object):
def handle_login(self) -> bool: def handle_login(self) -> bool:
assert self.parser # !rm assert self.parser # !rm
if self.args.usernames and not (
self.args.shr and self.vpath.startswith(self.args.shr1)
):
try:
un = self.parser.require("uname", 64)
except:
un = ""
else:
un = ""
pwd = self.parser.require("cppwd", 64) pwd = self.parser.require("cppwd", 64)
try: try:
uhash = self.parser.require("uhash", 256) uhash = self.parser.require("uhash", 256)
@ -2952,6 +2978,9 @@ class HttpCli(object):
if not pwd: if not pwd:
raise Pebkac(422, "password cannot be blank") raise Pebkac(422, "password cannot be blank")
if un:
pwd = "%s:%s" % (un, pwd)
dst = self.args.SRS dst = self.args.SRS
if self.vpath: if self.vpath:
dst += quotep(self.vpaths) dst += quotep(self.vpaths)
@ -3583,7 +3612,7 @@ class HttpCli(object):
rem = "{}/{}".format(rp, fn).strip("/") rem = "{}/{}".format(rp, fn).strip("/")
dbv, vrem = vfs.get_dbv(rem) dbv, vrem = vfs.get_dbv(rem)
if not rem.endswith(".md") and not self.can_delete: if not rem.lower().endswith(".md") and not self.can_delete:
raise Pebkac(400, "only markdown pls") raise Pebkac(400, "only markdown pls")
if nullwrite: if nullwrite:
@ -3667,6 +3696,9 @@ class HttpCli(object):
if p_field != "body": if p_field != "body":
raise Pebkac(400, "expected body, got {}".format(p_field)) raise Pebkac(400, "expected body, got {}".format(p_field))
if "txt_eol" in vfs.flags:
p_data = eol_conv(p_data, vfs.flags["txt_eol"])
xbu = vfs.flags.get("xbu") xbu = vfs.flags.get("xbu")
if xbu: if xbu:
if not runhook( if not runhook(
@ -4637,7 +4669,9 @@ class HttpCli(object):
else: else:
fn = self.host.split(":")[0] fn = self.host.split(":")[0]
if vn.flags.get("zipmax") and (not self.uname or not "zipmaxu" in vn.flags): if vn.flags.get("zipmax") and not (
vn.flags.get("zipmaxu") and self.uname != "*"
):
maxs = vn.flags.get("zipmaxs_v") or 0 maxs = vn.flags.get("zipmaxs_v") or 0
maxn = vn.flags.get("zipmaxn_v") or 0 maxn = vn.flags.get("zipmaxn_v") or 0
nf = 0 nf = 0
@ -5031,7 +5065,7 @@ class HttpCli(object):
wvol = [x for x in wvol if "unlistcw" not in allvols[x[1:-1]].flags] wvol = [x for x in wvol if "unlistcw" not in allvols[x[1:-1]].flags]
fmt = self.uparam.get("ls", "") fmt = self.uparam.get("ls", "")
if not fmt and (self.ua.startswith("curl/") or self.ua.startswith("fetch")): if not fmt and self.ua.startswith(("curl/", "fetch")):
fmt = "v" fmt = "v"
if fmt in ["v", "t", "txt"]: if fmt in ["v", "t", "txt"]:
@ -5071,6 +5105,13 @@ class HttpCli(object):
self.reply(zb, mime="text/plain; charset=utf-8") self.reply(zb, mime="text/plain; charset=utf-8")
return True return True
re_btn = ""
nre = self.args.ctl_re
if "re" in self.uparam:
self.out_headers["Refresh"] = str(nre)
elif nre:
re_btn = "&re=%s" % (nre,)
html = self.j2s( html = self.j2s(
"splash", "splash",
this=self, this=self,
@ -5088,6 +5129,7 @@ class HttpCli(object):
mtpq=vs["mtpq"], mtpq=vs["mtpq"],
dbwt=vs["dbwt"], dbwt=vs["dbwt"],
url_suf=suf, url_suf=suf,
re=re_btn,
k304=self.k304(), k304=self.k304(),
no304=self.no304(), no304=self.no304(),
k304vis=self.args.k304 > 0, k304vis=self.args.k304 > 0,
@ -5133,7 +5175,7 @@ class HttpCli(object):
t = '<h1 id="n">404 not found &nbsp;┐( ´ -`)┌</h1><p><a id="r" href="{}/?h">go home</a></p>' t = '<h1 id="n">404 not found &nbsp;┐( ´ -`)┌</h1><p><a id="r" href="{}/?h">go home</a></p>'
pt = "404 not found ┐( ´ -`)┌" pt = "404 not found ┐( ´ -`)┌"
if self.ua.startswith("curl/") or self.ua.startswith("fetch"): if self.ua.startswith(("curl/", "fetch")):
pt = "# acct: %s\n%s\n" % (self.uname, pt) pt = "# acct: %s\n%s\n" % (self.uname, pt)
self.reply(pt.encode("utf-8"), status=rc) self.reply(pt.encode("utf-8"), status=rc)
return True return True
@ -5447,6 +5489,8 @@ class HttpCli(object):
elif nfi == 3: elif nfi == 3:
if not vp.endswith(vfi): if not vp.endswith(vfi):
continue continue
else:
continue
n -= 1 n -= 1
if not n: if not n:
@ -5571,6 +5615,8 @@ class HttpCli(object):
elif nfi == 3: elif nfi == 3:
if not vp.endswith(vfi): if not vp.endswith(vfi):
continue continue
else:
continue
if not dots and "/." in vp: if not dots and "/." in vp:
continue continue
@ -6001,6 +6047,12 @@ class HttpCli(object):
else: else:
[x.pop(k) for k in ["name", "dt"] for y in [dirs, files] for x in y] [x.pop(k) for k in ["name", "dt"] for y in [dirs, files] for x in y]
# nonce (tlnote: norwegian for flake as in snowflake)
if self.args.no_fnugg:
ls["fnugg"] = "nei"
elif "fnugg" in self.headers:
ls["fnugg"] = self.headers["fnugg"]
ret = json.dumps(ls) ret = json.dumps(ls)
mime = "application/json" mime = "application/json"
@ -6183,7 +6235,8 @@ class HttpCli(object):
if not use_filekey: if not use_filekey:
return self.tx_404(True) return self.tx_404(True)
if add_og and not abspath.lower().endswith(".md"): is_md = abspath.lower().endswith(".md")
if add_og and not is_md:
if og_ua or self.host not in self.headers.get("referer", ""): if og_ua or self.host not in self.headers.get("referer", ""):
self.vpath, og_fn = vsplit(self.vpath) self.vpath, og_fn = vsplit(self.vpath)
vpath = self.vpath vpath = self.vpath
@ -6195,10 +6248,10 @@ class HttpCli(object):
vpnodes.pop() vpnodes.pop()
if ( if (
(abspath.endswith(".md") or self.can_delete) (is_md or self.can_delete)
and "nohtml" not in vn.flags and "nohtml" not in vn.flags
and ( and (
("v" in self.uparam and abspath.endswith(".md")) (is_md and "v" in self.uparam)
or "edit" in self.uparam or "edit" in self.uparam
or "edit2" in self.uparam or "edit2" in self.uparam
) )
@ -6255,11 +6308,7 @@ class HttpCli(object):
is_ls = "ls" in self.uparam is_ls = "ls" in self.uparam
is_js = self.args.force_js or self.cookies.get("js") == "y" is_js = self.args.force_js or self.cookies.get("js") == "y"
if ( if not is_ls and not add_og and self.ua.startswith(("curl/", "fetch")):
not is_ls
and not add_og
and (self.ua.startswith("curl/") or self.ua.startswith("fetch"))
):
self.uparam["ls"] = "v" self.uparam["ls"] = "v"
is_ls = True is_ls = True

View file

@ -183,11 +183,7 @@ class MCast(object):
srv.ips[oth_ip.split("/")[0]] = ipaddress.ip_network(oth_ip, False) srv.ips[oth_ip.split("/")[0]] = ipaddress.ip_network(oth_ip, False)
# gvfs breaks if a linklocal ip appears in a dns reply # gvfs breaks if a linklocal ip appears in a dns reply
ll = { ll = {k: v for k, v in srv.ips.items() if k.startswith(("169.254", "fe80"))}
k: v
for k, v in srv.ips.items()
if k.startswith("169.254") or k.startswith("fe80")
}
rt = {k: v for k, v in srv.ips.items() if k not in ll} rt = {k: v for k, v in srv.ips.items() if k not in ll}
if self.args.ll or not rt: if self.args.ll or not rt:

View file

@ -147,6 +147,10 @@ class PWHash(object):
def cli(self) -> None: def cli(self) -> None:
import getpass import getpass
if self.args.usernames:
t = "since you have enabled --usernames, please provide username:password"
print(t)
while True: while True:
try: try:
p1 = getpass.getpass("password> ") p1 = getpass.getpass("password> ")

View file

@ -850,15 +850,6 @@ class SvcHub(object):
def _check_env(self) -> None: def _check_env(self) -> None:
al = self.args al = self.args
try:
files = os.listdir(E.cfg)
except:
files = []
hits = [x for x in files if x.lower().endswith(".conf")]
if hits:
t = "WARNING: found config files in [%s]: %s\n config files are not expected here, and will NOT be loaded (unless your setup is intentionally hella funky)"
self.log("root", t % (E.cfg, ", ".join(hits)), 3)
if self.args.no_bauth: if self.args.no_bauth:
t = "WARNING: --no-bauth disables support for the Android app; you may want to use --bauth-last instead" t = "WARNING: --no-bauth disables support for the Android app; you may want to use --bauth-last instead"
@ -868,7 +859,7 @@ class SvcHub(object):
have_tcp = False have_tcp = False
for zs in al.i: for zs in al.i:
if not zs.startswith("unix:"): if not zs.startswith(("unix:", "fd:")):
have_tcp = True have_tcp = True
if not have_tcp: if not have_tcp:
zb = False zb = False
@ -878,7 +869,7 @@ class SvcHub(object):
setattr(al, zs, False) setattr(al, zs, False)
zb = True zb = True
if zb: if zb:
t = "only listening on unix-sockets; cannot enable zeroconf/mdns/ssdp as requested" t = "not listening on any ip-addresses (only unix-sockets and/or FDs); cannot enable zeroconf/mdns/ssdp as requested"
self.log("root", t, 3) self.log("root", t, 3)
if not self.args.no_dav: if not self.args.no_dav:

View file

@ -25,8 +25,8 @@ from .util import (
termsize, termsize,
) )
if True: if True: # pylint: disable=using-constant-test
from typing import Generator, Union from typing import Generator, Optional, Union
if TYPE_CHECKING: if TYPE_CHECKING:
from .svchub import SvcHub from .svchub import SvcHub
@ -245,8 +245,10 @@ class TcpSrv(object):
def _listen(self, ip: str, port: int) -> None: def _listen(self, ip: str, port: int) -> None:
uds_perm = uds_gid = -1 uds_perm = uds_gid = -1
bound: Optional[socket.socket] = None
tcp = False
if "unix:" in ip: if "unix:" in ip:
tcp = False
ipv = socket.AF_UNIX ipv = socket.AF_UNIX
uds = ip.split(":") uds = ip.split(":")
ip = uds[-1] ip = uds[-1]
@ -259,7 +261,12 @@ class TcpSrv(object):
import grp import grp
uds_gid = grp.getgrnam(uds[2]).gr_gid uds_gid = grp.getgrnam(uds[2]).gr_gid
elif "fd:" in ip:
fd = ip[3:]
bound = socket.socket(fileno=int(fd))
tcp = bound.proto == socket.IPPROTO_TCP
ipv = bound.family
elif ":" in ip: elif ":" in ip:
tcp = True tcp = True
ipv = socket.AF_INET6 ipv = socket.AF_INET6
@ -267,7 +274,7 @@ class TcpSrv(object):
tcp = True tcp = True
ipv = socket.AF_INET ipv = socket.AF_INET
srv = socket.socket(ipv, socket.SOCK_STREAM) srv = bound or socket.socket(ipv, socket.SOCK_STREAM)
if not ANYWIN or self.args.reuseaddr: if not ANYWIN or self.args.reuseaddr:
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@ -285,6 +292,10 @@ class TcpSrv(object):
if getattr(self.args, "freebind", False): if getattr(self.args, "freebind", False):
srv.setsockopt(socket.SOL_IP, socket.IP_FREEBIND, 1) srv.setsockopt(socket.SOL_IP, socket.IP_FREEBIND, 1)
if bound:
self.srv.append(srv)
return
try: try:
if tcp: if tcp:
srv.bind((ip, port)) srv.bind((ip, port))
@ -437,7 +448,7 @@ class TcpSrv(object):
def detect_interfaces(self, listen_ips: list[str]) -> dict[str, Netdev]: def detect_interfaces(self, listen_ips: list[str]) -> dict[str, Netdev]:
from .stolen.ifaddr import get_adapters from .stolen.ifaddr import get_adapters
listen_ips = [x for x in listen_ips if "unix:" not in x] listen_ips = [x for x in listen_ips if not x.startswith(("unix:", "fd:"))]
nics = get_adapters(True) nics = get_adapters(True)
eps: dict[str, Netdev] = {} eps: dict[str, Netdev] = {}

View file

@ -179,7 +179,7 @@ class Tftpd(object):
if "::" in ips: if "::" in ips:
ips.append("0.0.0.0") ips.append("0.0.0.0")
ips = [x for x in ips if "unix:" not in x] ips = [x for x in ips if not x.startswith(("unix:", "fd:"))]
if self.args.tftp4: if self.args.tftp4:
ips = [x for x in ips if ":" not in x] ips = [x for x in ips if ":" not in x]

View file

@ -375,11 +375,12 @@ class Up2k(object):
if ineed == ihash or not ineed: if ineed == ihash or not ineed:
continue continue
poke = job["poke"]
zt = ( zt = (
ineed / ihash, ineed / ihash,
job["size"], job["size"],
int(job["t0c"]), int(job.get("t0c", poke)),
int(job["poke"]), int(poke),
djoin(vtop, job["prel"], job["name"]), djoin(vtop, job["prel"], job["name"]),
) )
ret.append(zt) ret.append(zt)

View file

@ -2982,6 +2982,17 @@ def justcopy(
return tlen, "checksum-disabled", "checksum-disabled" return tlen, "checksum-disabled", "checksum-disabled"
def eol_conv(
fin: Generator[bytes, None, None], conv: str
) -> Generator[bytes, None, None]:
crlf = conv.lower() == "crlf"
for buf in fin:
buf = buf.replace(b"\r", b"")
if crlf:
buf = buf.replace(b"\n", b"\r\n")
yield buf
def hashcopy( def hashcopy(
fin: Generator[bytes, None, None], fin: Generator[bytes, None, None],
fout: Union[typing.BinaryIO, typing.IO[Any]], fout: Union[typing.BinaryIO, typing.IO[Any]],

View file

@ -1374,6 +1374,7 @@ html.y #ops svg circle {
#op_cfg input[type=text] { #op_cfg input[type=text] {
top: -.3em; top: -.3em;
} }
.opview select,
.opview input[type=text] { .opview input[type=text] {
color: var(--fg); color: var(--fg);
background: var(--txt-bg); background: var(--txt-bg);
@ -1384,6 +1385,10 @@ html.y #ops svg circle {
border-radius: .2em; border-radius: .2em;
padding: .2em .3em; padding: .2em .3em;
} }
.opview select {
padding: .3em;
margin: .2em .4em;
}
.opview input.err { .opview input.err {
color: var(--err-fg); color: var(--err-fg);
background: var(--err-bg); background: var(--err-bg);

File diff suppressed because it is too large Load diff

View file

@ -255,7 +255,7 @@ function Modpoll() {
} }
console.log('modpoll...'); console.log('modpoll...');
var url = (document.location + '').split('?')[0] + '?_=' + Date.now(); var url = (location + '').split('?')[0] + '?_=' + Date.now();
var xhr = new XHR(); var xhr = new XHR();
xhr.open('GET', url, true); xhr.open('GET', url, true);
xhr.responseType = 'text'; xhr.responseType = 'text';
@ -346,7 +346,7 @@ function save(e) {
fd.append("lastmod", (force ? -1 : last_modified)); fd.append("lastmod", (force ? -1 : last_modified));
fd.append("body", txt); fd.append("body", txt);
var url = (document.location + '').split('?')[0]; var url = (location + '').split('?')[0];
var xhr = new XHR(); var xhr = new XHR();
xhr.open('POST', url, true); xhr.open('POST', url, true);
xhr.responseType = 'text'; xhr.responseType = 'text';
@ -404,7 +404,7 @@ function save_cb() {
function run_savechk(lastmod, txt, btn, ntry) { function run_savechk(lastmod, txt, btn, ntry) {
// download the saved doc from the server and compare // download the saved doc from the server and compare
var url = (document.location + '').split('?')[0] + '?_=' + Date.now(); var url = (location + '').split('?')[0] + '?_=' + Date.now();
var xhr = new XHR(); var xhr = new XHR();
xhr.open('GET', url, true); xhr.open('GET', url, true);
xhr.responseType = 'text'; xhr.responseType = 'text';

View file

@ -6,7 +6,7 @@ var dom_doc = ebi('m');
var dom_md = ebi('mt'); var dom_md = ebi('mt');
(function () { (function () {
var n = document.location + ''; var n = location + '';
n = (n.slice(n.indexOf('//') + 2).split('?')[0] + '?v').split('/'); n = (n.slice(n.indexOf('//') + 2).split('?')[0] + '?v').split('/');
n[0] = 'top'; n[0] = 'top';
var loc = []; var loc = [];
@ -113,7 +113,7 @@ function save(mde) {
fd.append("lastmod", (force ? -1 : last_modified)); fd.append("lastmod", (force ? -1 : last_modified));
fd.append("body", txt); fd.append("body", txt);
var url = (document.location + '').split('?')[0]; var url = (location + '').split('?')[0];
var xhr = new XHR(); var xhr = new XHR();
xhr.open('POST', url, true); xhr.open('POST', url, true);
xhr.responseType = 'text'; xhr.responseType = 'text';
@ -166,7 +166,7 @@ function save_cb() {
//alert('save OK -- wrote ' + r.size + ' bytes.\n\nsha512: ' + r.sha512); //alert('save OK -- wrote ' + r.size + ' bytes.\n\nsha512: ' + r.sha512);
// download the saved doc from the server and compare // download the saved doc from the server and compare
var url = (document.location + '').split('?')[0] + '?_=' + Date.now(); var url = (location + '').split('?')[0] + '?_=' + Date.now();
var xhr = new XHR(); var xhr = new XHR();
xhr.open('GET', url, true); xhr.open('GET', url, true);
xhr.responseType = 'text'; xhr.responseType = 'text';

View file

@ -19,14 +19,7 @@
<a href="{{ r }}/?h">control-panel</a> <a href="{{ r }}/?h">control-panel</a>
&nbsp; Filter: <input type="text" id="filter" size="20" placeholder="documents/passwords" /> &nbsp; Filter: <input type="text" id="filter" size="20" placeholder="documents/passwords" />
&nbsp; <span id="hits"></span> &nbsp; <span id="hits"></span>
<table id="tab"><thead><tr> <div id="tw"></div>
<th>size</th>
<th>who</th>
<th>when</th>
<th>age</th>
<th>dir</th>
<th>file</th>
</tr></thead><tbody id="tb"></tbody></table>
</div> </div>
<a href="#" id="repl">π</a> <a href="#" id="repl">π</a>
<script> <script>

View file

@ -1,5 +1,6 @@
function render() { function render() {
var ups = V.ups, now = V.now, html = []; var html = ['<table id="tab"><thead><tr><th>size</th><th>who</th><th>when</th><th>age</th><th>dir</th><th>file</th></tr></thead><tbody>'];
var ups = V.ups, now = V.now;
ebi('filter').value = V.filter; ebi('filter').value = V.filter;
ebi('hits').innerHTML = 'showing ' + ups.length + ' files'; ebi('hits').innerHTML = 'showing ' + ups.length + ' files';
@ -26,7 +27,8 @@ function render() {
var t = V.filter ? ' matching the filter' : ''; var t = V.filter ? ' matching the filter' : '';
html = ['<tr><td colspan="6">there are no uploads' + t + '</td></tr>']; html = ['<tr><td colspan="6">there are no uploads' + t + '</td></tr>'];
} }
ebi('tb').innerHTML = html.join(''); html.push('</tbody></table>');
ebi('tw').innerHTML = html.join('\n');
} }
render(); render();
@ -46,7 +48,7 @@ function ask(e) {
V = JSON.parse(this.responseText) V = JSON.parse(this.responseText)
} }
catch (ex) { catch (ex) {
ebi('tb').innerHTML = '<tr><td colspan="6">failed to decode server response as json: <pre>' + esc(this.responseText) + '</pre></td></tr>'; ebi('tw').innerHTML = 'failed to decode server response as json: <pre>' + esc(this.responseText) + '</pre>';
return; return;
} }
render(); render();

View file

@ -1,11 +1,9 @@
var SRS = SR.trimEnd('/') + '/';
var t = QSA('a[k]'); var t = QSA('a[k]');
for (var a = 0; a < t.length; a++) for (var a = 0; a < t.length; a++)
t[a].onclick = rm; t[a].onclick = rm;
function rm() { function rm() {
var u = SRS + '?eshare=rm&skey=' + uricom_enc(this.getAttribute('k')), var u = SR + '/?eshare=rm&skey=' + uricom_enc(this.getAttribute('k')),
xhr = new XHR(); xhr = new XHR();
xhr.open('POST', u, true); xhr.open('POST', u, true);
@ -15,7 +13,7 @@ function rm() {
function bump() { function bump() {
var k = this.closest('tr').getElementsByTagName('a')[2].getAttribute('k'), var k = this.closest('tr').getElementsByTagName('a')[2].getAttribute('k'),
u = SRS + '?skey=' + uricom_enc(k) + '&eshare=' + this.value, u = SR + '/?skey=' + uricom_enc(k) + '&eshare=' + this.value,
xhr = new XHR(); xhr = new XHR();
xhr.open('POST', u, true); xhr.open('POST', u, true);
@ -27,7 +25,7 @@ function cb() {
if (this.status !== 200) if (this.status !== 200)
return modal.alert('<h6>server error</h6>' + esc(unpre(this.responseText))); return modal.alert('<h6>server error</h6>' + esc(unpre(this.responseText)));
document.location = '?shares'; location = '?shares';
} }
function qr(e) { function qr(e) {

View file

@ -15,7 +15,7 @@
<body> <body>
<div id="wrap"> <div id="wrap">
{%- if not in_shr %} {%- if not in_shr %}
<a id="a" href="{{ r }}/?h" class="af">refresh</a> <a id="a" href="{{ r }}/?h{{ re }}" class="af">refresh</a>
<a id="v" href="{{ r }}/?hc" class="af">connect</a> <a id="v" href="{{ r }}/?hc" class="af">connect</a>
{%- if this.uname == '*' %} {%- if this.uname == '*' %}
@ -120,7 +120,12 @@
<div> <div>
<form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}"> <form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
<input type="hidden" id="la" name="act" value="login" /> <input type="hidden" id="la" name="act" value="login" />
{% if this.args.usernames %}
<input type="text" id="lu" name="uname" placeholder=" username" size="12" />
<input type="password" id="lp" name="cppwd" placeholder=" password" size="12" />
{% else %}
<input type="password" id="lp" name="cppwd" placeholder=" password" /> <input type="password" id="lp" name="cppwd" placeholder=" password" />
{% endif %}
<input type="hidden" name="uhash" id="uhash" value="x" /> <input type="hidden" name="uhash" id="uhash" value="x" />
<input type="submit" id="ls" value="login" /> <input type="submit" id="ls" value="login" />
{% if chpw %} {% if chpw %}

View file

@ -94,6 +94,47 @@ var Ls = {
"af1": "显示最近上传的文件", //m "af1": "显示最近上传的文件", //m
"ag1": "查看已知 IdP 用户", //m "ag1": "查看已知 IdP 用户", //m
}, },
"cze": {
"a1": "obnovit",
"b1": "ahoj cizinče &nbsp; <small>(nejsi přihlášen)</small>",
"c1": "odhlásit se",
"d1": "vypsat zásobníku", // TLNote: "d2" is the tooltip for this button
"d2": "zobrazit stav všech aktivních vláken",
"e1": "znovu načíst konfiguraci",
"e2": "znovu načíst konfigurační soubory (accounts/volumes/volflags),$Na prohledat všechny e2ds úložiště$N$Npoznámka: všechny změny globálních nastavení$Nvyžadují úplné restartování, aby se projevily",
"f1": "můžeš procházet:",
"g1": "můžeš nahrávat do:",
"cc1": "další věci:",
"h1": "zakázat k304", // TLNote: "j1" explains what k304 is
"i1": "povolit k304",
"j1": "povolení k304 odpojí vašeho klienta při každém HTTP 304, což může zabránit některým chybovým proxy serverům, aby se zasekly (náhle nenačítaly stránky), <em>ale</em> také to obecně zpomalí věci",
"k1": "resetovat nastavení klienta",
"l1": "přihlaste se pro více:",
"m1": "vítej zpět,", // TLNote: "welcome back, USERNAME"
"n1": "404 nenalezeno &nbsp;┐( ´ -`)┌",
"o1": 'nebo možná nemáš přístup -- zkus heslo nebo <a href="' + SR + '/?h">jdi domů</a>',
"p1": "403 zakázáno &nbsp;~┻━┻",
"q1": 'použij heslo nebo <a href="' + SR + '/?h">jdi domů</a>',
"r1": "jdi domů",
".s1": "znovu prohledat",
"t1": "akce", // TLNote: this is the header above the "rescan" buttons
"u2": "čas od posledního zápisu na server$N( upload / rename / ... )$N$N17d = 17 dní$N1h23 = 1 hodina 23 minut$N4m56 = 4 minuty 56 sekund",
"v1": "připojit",
"v2": "použít tento server jako místní HDD",
"w1": "přepnout na https",
"x1": "změnit heslo",
"y1": "upravit sdílení", // TLNote: shows the list of folders that the user has decided to share
"z1": "odblokovat toto sdílení:", // TLNote: the password prompt to see a hidden share
"ta1": "nejprve vyplňte své nové heslo",
"ta2": "zopakujte pro potvrzení nového hesla:",
"ta3": "nalezen překlep; zkuste to prosím znovu",
"aa1": "příchozí soubory:",
"ab1": "deaktivovat no304",
"ac1": "povolit no304",
"ad1": "povolení no304 deaktivuje veškeré mezipaměti; zkuste to, pokud k304 nestačilo. To ovšem zapříčíní obrovské množství síťového provozu!",
"ae1": "aktivní stahování:",
"af1": "zobrazit nedávné nahrávání",
},
"deu": { "deu": {
"a1": "Neu laden", "a1": "Neu laden",
"b1": "Tach, wie geht's? &nbsp; <small>(Du bist nicht angemeldet)</small>", "b1": "Tach, wie geht's? &nbsp; <small>(Du bist nicht angemeldet)</small>",
@ -174,9 +215,50 @@ var Ls = {
"ac1": "ota no304 käyttöön", "ac1": "ota no304 käyttöön",
"ad1": "no304:n lopettaa välimuistin käytön kokonaan; kokeile tätä jos k304 ei riittänyt. Tuhlaa valtavan määrän verkkoliikennettä!", "ad1": "no304:n lopettaa välimuistin käytön kokonaan; kokeile tätä jos k304 ei riittänyt. Tuhlaa valtavan määrän verkkoliikennettä!",
"ae1": "lähtevät:", "ae1": "lähtevät:",
"af1": "näytä viimeaikaiset lataukset", "af1": "näytä viimeaikaiset lataukset",
"ag1": "näytä tunnetut IdP-käyttäjät", "ag1": "näytä tunnetut IdP-käyttäjät",
}, },
"grc": {
"a1": "ανανέωση",
"b1": "γεια σου ξένε! &nbsp; <small>(δεν είσαι συνδεδεμένος)</small>",
"c1": "αποσύνδεση",
"d1": "λίστα διεργασιών",
"d2": "εμφανίζει την κατάσταση όλων των ενεργών διεργασιών",
"e1": "επαναφόρτωση του cfg",
"e2": "φορτώνει ξανά τα αρχεία ρυθμίσεων (λογαριασμοί/τόμοι/volflags),$Nκαι κάνει επανεξέταση όλων των τόμων e2ds$N$Nσημείωση: οποιαδήποτε αλλαγή στις καθολικές ρυθμίσεις$Nαπαιτεί πλήρη επανεκκίνηση για να εφαρμοστεί",
"f1": "μπορείς να περιηγηθείς:",
"g1": "μπορείς να εκτελέσεις μεταφόρτωση σε:",
"cc1": "άλλα πράγματα:",
"h1": "απενεργοποίση k304",
"i1": "ενεργοποίηση k304",
"j1": "η ενεργοποίηση του k304 θα αποσυνδέσει το πρόγραμμα πελάτη σου σε κάθε HTTP 304, κάτι που μπορεί να αποτρέψει κάποια προβληματικά proxies από το να κολλάνε (να μην φορτώνουν ξαφνικά σελίδες), <em>αλλά</em> θα κάνει τα πράγματα, γενικά πιο αργά",
"k1": "επαναφορά ρυθμίσεων στο πρόγραμμα πελάτη",
"l1": "συνδέσου για περισσότερα:",
"m1": "καλώς ήρθες,",
"n1": "404 δεν βρέθηκε &nbsp;┐( ´ -`)┌",
"o1": '´η μήπως δεν έχεις πρόσβαση -- δοκίμασε έναν κωδικό <a href="' + SR + '/?h">πήγαινε στην αρχική</a>',
"p1": "403 απαγορευμένο &nbsp;~┻━┻",
"q1": 'δοκίμασε έναν κωδικό <a href="' + SR + '/?h">πήγαινε στην αρχική</a>',
"r1": "πίσω στην αρχική",
".s1": "επανάληψη σάρωσης",
"t1": "ενέργεια",
"u2": "χρόνος από την τελευταία εγγραφή του διακομιστή$N( μεταφόρτωση / μετονομασία / ... )$N$N17d = 17 days$N1ω23 = 1 ώρα 23 λεπτά$N4λ56 = 4 λεπτά 56 δευτερόλεπτα",
"v1": "σύνδεση",
"v2": "χρησιμοποίησε αυτόν το διακομιστή σαν τοπικό δίσκο",
"w1": "εναλλαγή σε https",
"x1": "αλλαγή κωδικού",
"y1": "επεξεργασία κοινόχρηστων φακέλων",
"z1": "ξεκλείδωμα αυτού του κοινόχρηστου φακέλου:",
"ta1": "συμπλήρωσε πρώτα το νέο σου κωδικό",
"ta2": "επανέλαβε για να επιβεβαιώσεις το νέο κωδικό:",
"ta3": "βρέθηκε τυπογραφικό λάθος· δοκίμασε ξανά",
"aa1": "εισερχόμενα αρχεία:",
"ab1": "απενεργοποίηση no304",
"ac1": "ενεργοποίηση no304",
"ad1": "η ενεργοποίηση του no304 θα απενεργοποιήσει όλη την προσωρινή αποθήκευση· δοκίμασέ το αν το k304 δεν ήταν αρκετό. Προσοχή, θα σπαταλήσει τεράστιο όγκο δικτυακής κίνησης!",
"ae1": "ενεργές μεταφορτώσεις:",
"af1": "προβολή πρόσφατων μεταφορτώσεων",
},
"ita": { "ita": {
"a1": "aggiorna", "a1": "aggiorna",
"b1": "ciao &nbsp; <small>(non sei connesso)</small>", "b1": "ciao &nbsp; <small>(non sei connesso)</small>",
@ -218,7 +300,7 @@ var Ls = {
"ae1": "in uscita:", "ae1": "in uscita:",
"af1": "mostra i file caricati di recente", "af1": "mostra i file caricati di recente",
"ag1": "mostra utenti IdP conosciuti" "ag1": "mostra utenti IdP conosciuti"
}, },
"nld": { "nld": {
"a1": "Update", "a1": "Update",
"b1": "Hallo, hoe gaat het met jou? &nbsp; <small>(Je bent niet ingelogd)</small>", "b1": "Hallo, hoe gaat het met jou? &nbsp; <small>(Je bent niet ingelogd)</small>",
@ -261,6 +343,90 @@ var Ls = {
"af1": "Recent geüploade bestanden weergeven", "af1": "Recent geüploade bestanden weergeven",
"ag1": "Bekende IdP-gebruikers weergeven", "ag1": "Bekende IdP-gebruikers weergeven",
}, },
"nno": {
"a1": "oppdatér",
"b1": "heisann &nbsp; <small>(du er ikkje logga inn)</small>",
"c1": "logg ut",
"d1": "tilstand",
"d2": "vis tilstanden åt alle trådar",
"e1": "last innst.",
"e2": "les inn konfigurasjonsfiler på nytt$N(kontoer, volum, volumbrytarar)$Nog kartlegg alle e2ds-volum$N$Nmerk: endringer i globale parametrar$Nkrev ein full restart for å gjelde",
"f1": "du kan sjå på:",
"g1": "du kan laste opp åt:",
"cc1": "brytarar og slikt:",
"h1": "skru av k304",
"i1": "skru på k304",
"j1": "k304 bryt tilkoplinga for kvar HTTP 304. Dette hjelp mot visse mellomtjenarar som kan sette seg fast / plutselig sluttar å laste sider, men det sett óg ytinga ned betydelig",
"k1": "nullstill innstillinger",
"l1": "logg inn:",
"m1": "velkomen attende,",
"n1": "404: filen finnast ikkje &nbsp;┐( ´ -`)┌",
"o1": 'eller kanskje du ikkje har høve? prøv eit passord eller <a href="' + SR + '/?h">gå heim</a>',
"p1": "403: tilgang nektet &nbsp;~┻━┻",
"q1": 'prøv eit passord eller <a href="' + SR + '/?h">gå heim</a>',
"r1": "gå heim",
".s1": "kartlegg",
"t1": "handling",
"u2": "tid sidan nokon sist skreiv åt serveren$N( opplastning / namnendring / ... )$N$N17d = 17 dagar$N1h23 = 1 time 23 minutt$N4m56 = 4 minutt 56 sekund",
"v1": "kople åt",
"v2": "bruk denne serveren som ein lokal harddisk",
"w1": "bytt åt https",
"x1": "bytt passord",
"y1": "dine delinger",
"z1": "lås opp område:",
"ta1": "du må skrive eit nytt passord først",
"ta2": "gjenta for å stadfeste nytt passord:",
"ta3": "fant ein skrivefeil; vennligst prøv igjen",
"aa1": "innkommande:",
"ab1": "skru av no304",
"ac1": "skru på no304",
"ad1": "no304 stoppar all bruk av cache. Hvis ikkje k304 var nok, prøv denne. Vil mangedoble dataforbruk!",
"ae1": "utgående:",
"af1": "vis nylig opplasta filer",
"ag1": "vis kjente IdP-brukarar",
},
"pol": {
"a1": "odśwież",
"b1": "witaj, nieznajomy &nbsp; <small>(nie jesteś zalogowany)</small>",
"c1": "wyloguj się",
"d1": "zrzut stosu",
"d2": "pokazuje status wszystkich aktywnych wątków",
"e1": "przeładuj konfigurację",
"e2": "przeładuj pliki konfiguracyjne (konta/wolumeny/flagi wolumenów),$Ni przeskanuje wszystkie wolumeny e2ds$N$Nnotka: zmiany konfiguracji globalnej$Nwymagają pełnego uruchomienia ponownie serwera, aby zaczęły obowiązywać",
"f1": "możesz przeglądać:",
"g1": "możesz przesyłać do:",
"cc1": "inne:",
"h1": "wyłącz k304",
"i1": "włącz k304",
"j1": "włączenie k304 będzie odłączało klienta przy każdorazowym otrzymaniu kodu HTTP 304, co może zapobiec wieszaniu się wadliwych proxy, <em>ale</em> spowolni ogólne działanie",
"k1": "zresetuj ustawienia klienta",
"l1": "zaloguj się po więcej:",
"m1": "Witaj,",
"n1": "404 nie znaleziono &nbsp;┐( ´ -`)┌",
"o1": 'lub możesz nie mieć dostępu -- spróbuj wprowadzić hasło lub <a href="' + SR + '/?h">przejdź do strony głównej</a>',
"p1": "403 odmowa dostępu &nbsp;~┻━┻",
"q1": 'użyj hasła lub <a href="' + SR + '/?h">przejdź do strony głównej</a>',
"r1": "idź do strony głównej",
".s1": "przeskanuj ponownie",
"t1": "akcje",
"u2": "czas od ostatniej interakcji z serwerem$N( przesyłania / zmiany nazwy / ... )$N$N17d = 17 dni$N1h23 = 1 godzina 23 minuty$N4m56 = 4 minuty 56 sekund",
"v1": "połącz",
"v2": "używaj tego serwera jako dysku lokalnego",
"w1": "przejdź na HTTPS",
"x1": "zmień hasło",
"y1": "edytuj udostępnione",
"z1": "odblokuj udostępnienie:",
"ta1": "najpierw wprowadź nowe hasło",
"ta2": "powtórz hasło dla potwierdzenia:",
"ta3": "znaleziono literówkę, spróbuj ponownie",
"aa1": "pliki przychodzące:",
"ab1": "wyłącz no304",
"ac1": "włącz no304",
"ad1": "włączenie no304 wyłączy przechowywanie jakiejkolwiek pamięci podręcznej. Zmarnuje to olbrzymią ilość ruchu sieciowego!",
"ae1": "trwające pobierania:",
"af1": "pokaż ostatnio przesłane pliki",
"ag1": "pokaż znanych użytkowników IdP",
},
"spa": { "spa": {
"a1": "actualizar", "a1": "actualizar",
"b1": "hola &nbsp; <small>(no has iniciado sesión)</small>", "b1": "hola &nbsp; <small>(no has iniciado sesión)</small>",
@ -303,6 +469,48 @@ var Ls = {
"af1": "mostrar subidas recientes", "af1": "mostrar subidas recientes",
"ag1": "mostrar usuarios IdP conocidos" "ag1": "mostrar usuarios IdP conocidos"
}, },
"ukr": {
"a1": "оновити",
"b1": "привітик, незнайомцю &nbsp; <small>(ви не авторизовані)</small>",
"c1": "вийти",
"d1": "трасування стека",
"d2": "показує стан усіх активних потоків",
"e1": "перезавантажити конфіг",
"e2": "перезавантажити файли конфігурації (облікові записи/томи/прапорці),$Nта пересканувати всі томи e2ds$N$Nувага: будь-які зміни глобальних налаштувань$Nвимагають повного перезапуску",
"f1": "ви можете бачити:",
"g1": "ви можете завантажувати файли в:",
"cc1": "всяка всячина:",
"h1": "вимкнути k304",
"i1": "увімкнути k304",
"j1": "увімкнення k304 буде відключати ваш клієнт при кожному HTTP 304, що може запобігти зависанню деяких глючних проксі (раптово перестають завантажувати сторінки), <em>але</em> це також зробить усе повільнішим загалом",
"k1": "скинути налаштування клієнта",
"l1": "авторизуйтесь для інших опцій:",
"m1": "з поверненням,",
"n1": "404 не знайдено &nbsp;┐( ´ -`)┌",
"o1": 'або у вас немає доступу -- спробуйте авторизуватися або <a href="' + SR + '/?h">повернутися на головну</a>',
"p1": "403 доступ заборонений &nbsp;~┻━┻",
"q1": 'авторизуйтесь або <a href="' + SR + '/?h">поверніться на головну</a>',
"r1": "повернутися на головну",
".s1": "пересканувати",
"t1": "дія",
"u2": "час з останнього запису сервера$N( завантаження / перейменування / ... )$N$N17d = 17 днів$N1h23 = 1 година 23 хвилини$N4m56 = 4 хвилини 56 секунд",
"v1": "підключити",
"v2": "використовувати цей сервер як локальний HDD",
"w1": "перейти на https",
"x1": "змінити пароль",
"y1": "керування доступом",
"z1": "розблокувати:",
"ta1": "спочатку заповніть ваш новий пароль",
"ta2": "повторіть для підтвердження нового пароля:",
"ta3": "описка; спробуйте знову",
"aa1": "вхідні файли:",
"ab1": "вимкнути no304",
"ac1": "увімкнути no304",
"ad1": "увімкнення no304 вимкне все кешування; спробуйте це, якщо k304 було недостатньо. Це витратить величезну кількість мережевого трафіку!",
"ae1": "активні завантаження:",
"af1": "показати нещодавні завантаження",
"ag1": "показати відомих IdP-користувачів"
},
"rus": { "rus": {
"a1": "обновить", "a1": "обновить",
"b1": "приветик, незнакомец &nbsp; <small>(вы не авторизованы)</small>", "b1": "приветик, незнакомец &nbsp; <small>(вы не авторизованы)</small>",
@ -375,7 +583,7 @@ try {
catch (ex) { } catch (ex) { }
tt.init(); tt.init();
var o = QS('input[name="cppwd"]'); var o = QS('input[name="uname"]') || QS('input[name="cppwd"]');
if (!ebi('c') && o.offsetTop + o.offsetHeight < window.innerHeight) if (!ebi('c') && o.offsetTop + o.offsetHeight < window.innerHeight)
o.focus(); o.focus();
@ -385,6 +593,9 @@ if (o && /[0-9]+$/.exec(o.innerHTML))
ebi('uhash').value = '' + location.hash; ebi('uhash').value = '' + location.hash;
if (/\&re=/.test('' + location))
ebi('a').className = 'af g';
(function() { (function() {
if (!ebi('x')) if (!ebi('x'))
return; return;

View file

@ -37,6 +37,7 @@
{% if accs %}<code><b id="pw0">{{ pw }}</b></code>=password, {% endif %}<code><b>mp</b></code>=mountpoint {% if accs %}<code><b id="pw0">{{ pw }}</b></code>=password, {% endif %}<code><b>mp</b></code>=mountpoint
</span> </span>
{% if accs %}<a href="#" id="setpw">use real password</a>{% endif %} {% if accs %}<a href="#" id="setpw">use real password</a>{% endif %}
<a href="#" id="qr">show qr</a>
</p> </p>

View file

@ -49,12 +49,14 @@ function setos(os) {
setos(WINDOWS ? 'win' : LINUX ? 'lin' : MACOS ? 'mac' : 'idk'); setos(WINDOWS ? 'win' : LINUX ? 'lin' : MACOS ? 'mac' : 'idk');
var pw = '';
function setpw(e) { function setpw(e) {
ev(e); ev(e);
modal.prompt('password:', '', function (v) { modal.prompt('password:', '', function (v) {
if (!v) if (!v)
return; return;
pw = v;
var pw0 = ebi('pw0').innerHTML, var pw0 = ebi('pw0').innerHTML,
oa = QSA('b'); oa = QSA('b');
@ -67,3 +69,12 @@ function setpw(e) {
} }
if (ebi('setpw')) if (ebi('setpw'))
ebi('setpw').onclick = setpw; ebi('setpw').onclick = setpw;
ebi('qr').onclick = function () {
var url = ('' + location).split('?')[0];
if (pw)
url += '?pw=' + pw;
var txt = esc(url) + '<img class="b64" width="100" height="100" src="' + addq(url, 'qr') + '" />';
modal.alert(txt);
};

View file

@ -964,6 +964,7 @@ function up2k_init(subtle) {
"t": 0 "t": 0
}, },
"car": 0, "car": 0,
"nre": 0,
"slow_io": null, "slow_io": null,
"oserr": false, "oserr": false,
"modn": 0, "modn": 0,
@ -1572,7 +1573,7 @@ function up2k_init(subtle) {
function linklist() { function linklist() {
var ret = [], var ret = [],
base = document.location.origin.replace(/\/$/, ''); base = location.origin.replace(/\/$/, '');
for (var a = 0; a < st.files.length; a++) { for (var a = 0; a < st.files.length; a++) {
var t = st.files[a], var t = st.files[a],
@ -1595,7 +1596,7 @@ function up2k_init(subtle) {
ev(e); ev(e);
var txt = linklist(); var txt = linklist();
cliptxt(txt + '\n', function () { cliptxt(txt + '\n', function () {
toast.inf(5, un_clip.format(txt.split('\n').length)); toast.inf(5, L.un_clip.format(txt.split('\n').length));
}); });
}; };
@ -1783,8 +1784,7 @@ function up2k_init(subtle) {
} }
var tasker = (function () { var tasker = (function () {
var running = false, var running = false;
was_busy = false;
var defer = function () { var defer = function () {
running = false; running = false;
@ -1801,7 +1801,17 @@ function up2k_init(subtle) {
while (true) { while (true) {
var now = Date.now(), var now = Date.now(),
blocktime = now - r.tact, blocktime = now - r.tact,
is_busy = st.car < st.files.length; was_busy = st.is_busy,
is_busy = !!( // gzip take the wheel
st.car < st.files.length ||
st.busy.hash.length ||
st.todo.hash.length ||
st.busy.handshake.length ||
st.todo.handshake.length ||
st.busy.upload.length ||
st.todo.upload.length ||
st.busy.head.length ||
st.todo.head.length);
if (blocktime > 2500) if (blocktime > 2500)
console.log('main thread blocked for ' + blocktime); console.log('main thread blocked for ' + blocktime);
@ -1809,7 +1819,16 @@ function up2k_init(subtle) {
r.tact = now; r.tact = now;
if (was_busy && !is_busy) { if (was_busy && !is_busy) {
for (var a = 0; a < st.files.length; a++) { var nre = 0, nf = 0;
for (var a = 0; a < st.files.length; a++)
if (st.files[a].want_recheck)
nre++;
console.log('nre', nre, 'st', st.nre);
if (st.nre != nre) {
st.nre = nre;
nf = st.files.length;
}
for (var a = 0; a < nf; a++) {
var t = st.files[a]; var t = st.files[a];
if (t.want_recheck) { if (t.want_recheck) {
t.rechecks++; t.rechecks++;
@ -1817,7 +1836,7 @@ function up2k_init(subtle) {
push_t(st.todo.handshake, t); push_t(st.todo.handshake, t);
} }
} }
is_busy = st.todo.handshake.length; is_busy = !!st.todo.handshake.length;
try { try {
if (!is_busy && !uc.fsearch && !msel.getsel().length && (!mp.au || mp.au.paused)) if (!is_busy && !uc.fsearch && !msel.getsel().length && (!mp.au || mp.au.paused))
treectl.goto(); treectl.goto();
@ -1826,7 +1845,7 @@ function up2k_init(subtle) {
} }
if (was_busy != is_busy) { if (was_busy != is_busy) {
st.is_busy = was_busy = is_busy; st.is_busy = is_busy;
window[(is_busy ? "add" : "remove") + window[(is_busy ? "add" : "remove") +
"EventListener"]("beforeunload", warn_uploader_busy); "EventListener"]("beforeunload", warn_uploader_busy);
@ -1947,7 +1966,7 @@ function up2k_init(subtle) {
for (var a = 0; a < st.files.length; a++) { for (var a = 0; a < st.files.length; a++) {
var t = st.files[a]; var t = st.files[a];
if (t.want_recheck && !t.rechecks) if (t.want_recheck && t.rechecks < 999)
return; return;
} }
@ -2693,8 +2712,9 @@ function up2k_init(subtle) {
if (ofs !== -1) { if (ofs !== -1) {
err = err.slice(0, ofs + 1) + linksplit(err.slice(ofs + 2).trimEnd()).join(' / '); err = err.slice(0, ofs + 1) + linksplit(err.slice(ofs + 2).trimEnd()).join(' / ');
} }
if (!t.rechecks && (err_pend || err_srcb)) { if (!t.rechecks)
t.rechecks = 0; t.rechecks = 0;
if (t.rechecks < 999 && (err_pend || err_srcb)) {
t.want_recheck = true; t.want_recheck = true;
if (st.busy.upload.length || st.busy.handshake.length || st.bytes.uploaded) { if (st.busy.upload.length || st.busy.handshake.length || st.bytes.uploaded) {
err = L.u_dupdefer; err = L.u_dupdefer;

View file

@ -120,7 +120,7 @@ function esc(txt) {
function basenames(txt) { function basenames(txt) {
return (txt + '').replace(/https?:\/\/[^ \/]+\//g, '/').replace(/js\?_=[a-zA-Z]{4}/g, 'js'); return (txt + '').replace(/https?:\/\/[^ \/]+\//g, '/').replace(/js\?_=[a-zA-Z]{4}/g, 'js');
} }
if ((document.location + '').indexOf(',rej,') + 1) if ((location + '').indexOf(',rej,') + 1)
window.onunhandledrejection = function (e) { window.onunhandledrejection = function (e) {
var err = e.reason; var err = e.reason;
try { try {
@ -741,7 +741,7 @@ function assert_vp(path) {
if (path.indexOf('//') + 1) if (path.indexOf('//') + 1)
throw 'nonlocal1: ' + path; throw 'nonlocal1: ' + path;
var o = window.location.origin; var o = location.origin;
if (have_URL && (new URL(path, o)).origin != o) if (have_URL && (new URL(path, o)).origin != o)
throw 'nonlocal2: ' + path; throw 'nonlocal2: ' + path;
} }
@ -893,7 +893,7 @@ function uricom_adec(arr, li) {
function get_evpath() { function get_evpath() {
var ret = document.location.pathname; var ret = location.pathname;
if (ret.indexOf('/') !== 0) if (ret.indexOf('/') !== 0)
ret = '/' + ret; ret = '/' + ret;
@ -1020,9 +1020,13 @@ function lhumantime(v) {
if (!L || tp.length < 2 || tp[1].indexOf('$') + 1) if (!L || tp.length < 2 || tp[1].indexOf('$') + 1)
return t; return t;
var ret = ''; var u, n, ret = '';
for (var a = 0; a < tp.length; a += 2) for (var a = 0; a < tp.length; a += 2) {
ret += tp[a] + ' ' + L['ht_' + tp[a + 1] + (tp[a]==1?1:2)] + L.ht_and; n = tp[a];
u = L.ht_h5 ? (n==1 ? 1 : (n>1&&n<5) ? 2 : 5) :
(n==1 ? 1 : 2);
ret += tp[a] + ' ' + L['ht_' + tp[a + 1] + u] + L.ht_and;
}
return ret.slice(0, -L.ht_and.length); return ret.slice(0, -L.ht_and.length);
} }
@ -1249,10 +1253,10 @@ function hist_replace(url) {
function sethash(hv) { function sethash(hv) {
if (window.history && history.replaceState) { if (window.history && history.replaceState) {
hist_replace(document.location.pathname + document.location.search + '#' + hv); hist_replace(location.pathname + location.search + '#' + hv);
} }
else { else {
document.location.hash = hv; location.hash = hv;
} }
} }

View file

@ -1,3 +1,39 @@
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0804-0013 `v1.18.10` idp speedboost
## 🧪 new features
* #426 add Dutch translation (thx @DeStilleGast!) 3798e19a
* #458 add Italian translation (thx @AOTREVAI!) a38e6e65
* #456 transcode to flac/wav (thx @missaustraliana!) b469db3c b2d48c64 0d09fb68
* #439 config-file can be provided through `PRTY_CONFIG` (thx @icxes!) 971360e9
* #459 videos can become folder thumbnails 16bbcce5
* add `--idp-cookie`, session-tickets for IdP auth (performance boost) f9502c3d
* useful when the IdP-server becomes a bottleneck
## 🩹 bugfixes
* #412 fix PUT-uploads into volumes with `nosub` volflag 47fa4a92
* #435 ignore spurious exceptions from browser extensions 39e55824
* #449 IPv6 QR-Code didn't include port 66a5bf36
* #295 do not force `d2d` in blank vfs (introduced in v1.18.3) 848315c0
## 🔧 other changes
* #440 improved finnish translation (thx @icxes!) a68d5b03
* point to the `-nc` option in the "at max connections" warning 153d240d
* the play-button now indicates "play-as-audio" for video-files 40d56bb3
* docs:
* #411 improve password-hashing instructions (thx @chinponya!) c69c7c8a
* #429 improve `--cert` helptext (thx @kzshantonu!) 7e3825f8
* #413 copyparty is Wii Internet Channel compatible! (thx @techflashYT!) 50f16293
* #461 how to use groups without IdP e85a7107
* mention that WebDAV and OpenGraph are incompatible by default (and how to fix that) 0bc1b8f7
* #345 short explanation about the sfx in quickstart ae5eefc5
* #398 pypi-package now has extra-group `all` 6eaf8af1
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0801-2056 `v1.18.9` fix Denial-of-Service # 2025-0801-2056 `v1.18.9` fix Denial-of-Service

2138
docs/chungus.conf Normal file

File diff suppressed because it is too large Load diff

View file

@ -74,7 +74,6 @@ gtar=$(command -v gtar || command -v gnutar) || true
sed() { gsed "$@"; } sed() { gsed "$@"; }
find() { gfind "$@"; } find() { gfind "$@"; }
sort() { gsort "$@"; } sort() { gsort "$@"; }
shuf() { gshuf "$@"; }
nproc() { gnproc; } nproc() { gnproc; }
sha1sum() { shasum "$@"; } sha1sum() { shasum "$@"; }
unexpand() { gunexpand "$@"; } unexpand() { gunexpand "$@"; }
@ -157,9 +156,9 @@ stamp=$(
done | sort | tail -n 1 | sha1sum | cut -c-16 done | sort | tail -n 1 | sha1sum | cut -c-16
) )
rm -rf sfx$CSN/* rm -rf sfx/*
mkdir -p sfx$CSN build mkdir -p sfx build
cd sfx$CSN cd sfx
tmpdir="$( tmpdir="$(
printf '%s\n' "$TMPDIR" /tmp | printf '%s\n' "$TMPDIR" /tmp |
@ -395,7 +394,7 @@ ts=$(date -u +%s)
hts=$(date -u +%Y-%m%d-%H%M%S) # --date=@$ts (thx osx) hts=$(date -u +%Y-%m%d-%H%M%S) # --date=@$ts (thx osx)
mkdir -p ../dist mkdir -p ../dist
sfx_out=../dist/copyparty-sfx$CSN sfx_out=../dist/copyparty-sfx
echo cleanup echo cleanup
find -name '*.pyc' -delete find -name '*.pyc' -delete
@ -554,7 +553,7 @@ gzres() {
} }
zdir="$tmpdir/cpp-mksfx$CSN" zdir="$tmpdir/cpp-mksfx"
[ -e "$zdir/$stamp" ] || rm -rf "$zdir" [ -e "$zdir/$stamp" ] || rm -rf "$zdir"
mkdir -p "$zdir" mkdir -p "$zdir"
echo a > "$zdir/$stamp" echo a > "$zdir/$stamp"
@ -583,15 +582,7 @@ echo gen tarlist
for d in copyparty partftpy magic j2 py2 py37 ftp; do find $d -type f || true; done | # strip_hints for d in copyparty partftpy magic j2 py2 py37 ftp; do find $d -type f || true; done | # strip_hints
sed -r 's/(.*)\.(.*)/\2 \1/' | LC_ALL=C sort | sed -r 's/(.*)\.(.*)/\2 \1/' | LC_ALL=C sort |
sed -r 's/([^ ]*) (.*)/\2.\1/' | grep -vE '/list1?$' > list1 sed -r 's/([^ ]*) (.*)/\2.\1/' | grep -vE '/list1?$' > list1
(grep -vE '\.gz$' list1; grep -E '\.gz$' list1) >list
for n in {1..50}; do
(grep -vE '\.gz$' list1; grep -E '\.gz$' list1 | (shuf||gshuf) ) >list || true
s=$( (sha1sum||shasum) < list | cut -c-16)
grep -q $s "$zdir/h" 2>/dev/null && continue
echo $s >> "$zdir/h"
break
done
[ $n -eq 50 ] && exit
echo creating tar echo creating tar
tar -cf tar "${targs[@]}" --numeric-owner -T list tar -cf tar "${targs[@]}" --numeric-owner -T list

View file

@ -30,5 +30,5 @@ a726fb46cce24f781fc8b55a3e6dea0a884ebc3b2b400ea74aa02333699f4955a5dc1e2ec5927ac7
3e39ea6e16b502d99a2e6544579095d0f7c6097761cd85135d5e929b9dec1b32e80669a846f94ee8c2cca9be2f5fe728625d09453988864c04e16bb8445c3f91 pillow-11.3.0-cp313-cp313-win_amd64.whl 3e39ea6e16b502d99a2e6544579095d0f7c6097761cd85135d5e929b9dec1b32e80669a846f94ee8c2cca9be2f5fe728625d09453988864c04e16bb8445c3f91 pillow-11.3.0-cp313-cp313-win_amd64.whl
59fbbcae044f4ee73d203ac74b553b27bfad3e6b2f3fb290fd3f8774753c6b545176b6b3399c240b092d131d152290ce732750accd962dc1e48e930be85f5e53 pyinstaller-6.14.1-py3-none-win_amd64.whl 59fbbcae044f4ee73d203ac74b553b27bfad3e6b2f3fb290fd3f8774753c6b545176b6b3399c240b092d131d152290ce732750accd962dc1e48e930be85f5e53 pyinstaller-6.14.1-py3-none-win_amd64.whl
fc6f3e144c5f5b662412de07cb8bf0c2eb3b3be21d19ec448aef3c4244d779b9ab8027fd67a4871e6e13823b248ea0f5a7a9241a53aef30f3b51a6d3cb5bdb3f pyinstaller_hooks_contrib-2025.5-py3-none-any.whl fc6f3e144c5f5b662412de07cb8bf0c2eb3b3be21d19ec448aef3c4244d779b9ab8027fd67a4871e6e13823b248ea0f5a7a9241a53aef30f3b51a6d3cb5bdb3f pyinstaller_hooks_contrib-2025.5-py3-none-any.whl
2c7a52e223b8186c21009d3fa5ed6a856d8eb4ef3b98f5d24c378c6a1afbfa1378bd7a51d6addc500e263d7989efb544c862bf920055e740f137c702dfd9d18b python-3.13.5-amd64.exe 3c37ea72ab062f65116a5faaaccbaa960a971797c78dbe6a504f41e562b018e7f28924647b5577fdb4fedfa61ffe0f1153842a8585f93b0332ed4d97905f6609 python-3.13.6-amd64.exe
2a0420f7faaa33d2132b82895a8282688030e939db0225ad8abb95a47bdb87b45318f10985fc3cee271a9121441c1526caa363d7f2e4a4b18b1a674068766e87 setuptools-80.9.0-py3-none-any.whl 2a0420f7faaa33d2132b82895a8282688030e939db0225ad8abb95a47bdb87b45318f10985fc3cee271a9121441c1526caa363d7f2e4a4b18b1a674068766e87 setuptools-80.9.0-py3-none-any.whl

View file

@ -40,7 +40,7 @@ fns=(
pillow-11.3.0-cp313-cp313-win_amd64.whl pillow-11.3.0-cp313-cp313-win_amd64.whl
pyinstaller-6.14.1-py3-none-win_amd64.whl pyinstaller-6.14.1-py3-none-win_amd64.whl
pyinstaller_hooks_contrib-2025.5-py3-none-any.whl pyinstaller_hooks_contrib-2025.5-py3-none-any.whl
python-3.13.5-amd64.exe python-3.13.6-amd64.exe
setuptools-80.9.0-py3-none-any.whl setuptools-80.9.0-py3-none-any.whl
) )
[ $w7 ] && fns+=( [ $w7 ] && fns+=(

View file

@ -32,11 +32,8 @@ v=$1
rm -f ../dist/copyparty-sfx* rm -f ../dist/copyparty-sfx*
shift shift
./make-sfx.sh "$@" ./make-sfx.sh "$@"
f=../dist/copyparty-sfx ../dist/copyparty-sfx.py --version >/dev/null
[ -e $f.py ] && s= || s=-gz mv ../dist/copyparty-{sfx,int}.py
# TODO: the -gz suffix is gone, can drop all the $s stuff probably
$f$s.py --version >/dev/null
while [ "$1" ]; do while [ "$1" ]; do
case "$1" in case "$1" in
@ -46,27 +43,8 @@ while [ "$1" ]; do
shift shift
done done
[ $parallel -gt 1 ] && { ./make-sfx.sh re lang eng "$@"
printf '\033[%s' s 2r H "0;1;37;44mbruteforcing sfx size -- press enter to terminate" K u "7m $* " K $'27m\n' mv ../dist/copyparty-{sfx,en}.py
trap "rm -f .sfx-run; printf '\033[%s' s r u" INT TERM EXIT mv ../dist/copyparty-{int,sfx}.py
touch .sfx-run
min=99999999
for ((a=0; a<$parallel; a++)); do
while [ -e .sfx-run ]; do
CSN=$a ./make-sfx.sh re "$@"
sz=$(wc -c <$f$a$s.py | awk '{print$1}')
[ $sz -ge $min ] && continue
mv $f$a$s.py $f$s.py.$sz
min=$sz
done &
done
read
exit
}
while true; do
mv $f$s.py $f$s.$(wc -c <$f$s.py | awk '{print$1}').py
./make-sfx.sh re "$@"
done
# git tag -d v$v; git push --delete origin v$v # git tag -d v$v; git push --delete origin v$v

View file

@ -143,7 +143,7 @@ class Cfg(Namespace):
def __init__(self, a=None, v=None, c=None, **ka0): def __init__(self, a=None, v=None, c=None, **ka0):
ka = {} ka = {}
ex = "allow_flac allow_wav 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 localtime 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" ex = "allow_flac allow_wav 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 localtime 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_fnugg 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 usernames vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs"
ka.update(**{k: False for k in ex.split()}) 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" 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"
@ -161,10 +161,10 @@ class Cfg(Namespace):
ex = "au_vol dl_list mtab_age reg_cap s_thead s_tbody tail_tmax tail_who th_convt ups_who zip_who" ex = "au_vol dl_list mtab_age reg_cap s_thead s_tbody tail_tmax tail_who th_convt ups_who zip_who"
ka.update(**{k: 9 for k in ex.split()}) ka.update(**{k: 9 for k in ex.split()})
ex = "db_act forget_ip idp_cookie idp_store k304 loris no304 nosubtle re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow zipmaxn zipmaxs" ex = "ctl_re db_act forget_ip idp_cookie idp_store k304 loris no304 nosubtle 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()}) ka.update(**{k: 0 for k in ex.split()})
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" 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 txt_eol unlist vname xff_src zipmaxt R RS SR"
ka.update(**{k: "" for k in ex.split()}) ka.update(**{k: "" for k in ex.split()})
ex = "ban_403 ban_404 ban_422 ban_pw ban_pwc ban_url spinner" ex = "ban_403 ban_404 ban_422 ban_pw ban_pwc ban_url spinner"