diff --git a/README.md b/README.md index 9746581b..476d8ddc 100644 --- a/README.md +++ b/README.md @@ -437,6 +437,7 @@ upgrade notes * 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 + * if you have enabled `--accounts` then do `?pw=username:password` instead * 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) @@ -1016,6 +1017,7 @@ a feed example: https://cd.ocv.me/a/d2/d22/?rss&fext=mp3 url parameters: * `pw=hunter2` for password auth + * if you enabled `--usernames` then do `pw=username:password` instead * `recursive` to also include subfolders * `title=foo` changes the feed title (default: folder name) * `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 * 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 + * unless you enabled `--usernames` some recommended FTP / FTPS clients; `wark` = example password: * 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: * 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: * [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/` * 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 + * 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: * 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) * 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 +* not compatible with pw-hashing or `--usernames` and some minor issues, * 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 * 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 * **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/)) -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) - -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 +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) ## 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) +* 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 @@ -2615,7 +2625,7 @@ there is a [discord server](https://discord.gg/25J8CdTT6G) with an `@everyone` 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 * 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 @@ -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 * 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 @@ -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 +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) diff --git a/contrib/package/arch/PKGBUILD b/contrib/package/arch/PKGBUILD index 9efb5c41..2673ec2a 100644 --- a/contrib/package/arch/PKGBUILD +++ b/contrib/package/arch/PKGBUILD @@ -1,4 +1,7 @@ # Maintainer: icxes +# Contributor: Morgan Adamiec +# NOTE: You generally shouldn't use this PKGBUILD on Arch, as it is mainly for testing purposes. Install copyparty using pacman instead. + pkgname=copyparty pkgver="1.18.10" pkgrel=1 @@ -6,52 +9,40 @@ pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFT arch=("any") url="https://github.com/9001/${pkgname}" license=('MIT') -depends=("python" "lsof" "python-jinja") +depends=("bash" "python" "lsof" "python-jinja") makedepends=("python-wheel" "python-setuptools" "python-build" "python-installer" "make" "pigz") optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tags" - "cfssl: generate TLS certificates on startup (pointless when reverse-proxied)" - "python-mutagen: music tags (alternative)" - "python-pillow: thumbnails for images" - "python-pyvips: thumbnails for images (higher quality, faster, uses more ram)" - "libkeyfinder-git: detection of musical keys" - "qm-vamp-plugins: BPM detection" - "python-pyopenssl: ftps functionality" - "python-pyzmq: send zeromq messages from event-hooks" - "python-argon2-cffi: hashed passwords in config" - "python-impacket-git: smb support (bad idea)" + "cfssl: generate TLS certificates on startup" + "python-mutagen: music tags (alternative)" + "python-pillow: thumbnails for images" + "python-pyvips: thumbnails for images (higher quality, faster, uses more ram)" + "libkeyfinder: detection of musical keys" + "python-pyopenssl: ftps functionality" + "python-pyzmq: send zeromq messages from event-hooks" + "python-argon2-cffi: hashed passwords in config" ) 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") build() { + cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web" + make + cd "${srcdir}/${pkgname}-${pkgver}" - - pushd copyparty/web - make -j$(nproc) - rm Makefile - popd - - python3 -m build -wn + python -m build --wheel --no-isolation } package() { 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 -Dm644 "contrib/package/arch/${pkgname}.conf" "${pkgdir}/etc/${pkgname}.d/init" - install -Dm644 "contrib/package/arch/${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/package/arch/index.md" "${pkgdir}/var/lib/${pkgname}-jail/README.md" + install -Dm644 "contrib/systemd/${pkgname}.conf" "${pkgdir}/etc/${pkgname}/copyparty.conf" + install -Dm644 "contrib/systemd/${pkgname}@.service" "${pkgdir}/usr/lib/systemd/system/${pkgname}@.service" + install -Dm644 "contrib/systemd/${pkgname}-user.service" "${pkgdir}/usr/lib/systemd/user/${pkgname}.service" + install -Dm644 "contrib/systemd/prisonparty@.service" "${pkgdir}/usr/lib/systemd/system/prisonparty@.service" + install -Dm644 "contrib/systemd/index.md" "${pkgdir}/var/lib/${pkgname}-jail/README.md" install -Dm644 "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 "┗━━━━━━━━━━━━━━━──-" } diff --git a/contrib/package/arch/copyparty.conf b/contrib/package/arch/copyparty.conf deleted file mode 100644 index 1d90d772..00000000 --- a/contrib/package/arch/copyparty.conf +++ /dev/null @@ -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 diff --git a/contrib/package/arch/copyparty.service b/contrib/package/arch/copyparty.service deleted file mode 100644 index 22dac3d6..00000000 --- a/contrib/package/arch/copyparty.service +++ /dev/null @@ -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 diff --git a/contrib/package/arch/index.md b/contrib/package/arch/index.md deleted file mode 100644 index 016c0b0a..00000000 --- a/contrib/package/arch/index.md +++ /dev/null @@ -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/` diff --git a/contrib/systemd/copyparty-user.service b/contrib/systemd/copyparty-user.service new file mode 100644 index 00000000..c6f61000 --- /dev/null +++ b/contrib/systemd/copyparty-user.service @@ -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 diff --git a/contrib/systemd/copyparty.conf b/contrib/systemd/copyparty.conf index 79560d0d..5d9bdbe3 100644 --- a/contrib/systemd/copyparty.conf +++ b/contrib/systemd/copyparty.conf @@ -1,42 +1,13 @@ -# 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 - + i: 127.0.0.1 [accounts] - ed: wark # username: password + user: password - -[/] # create a volume at "/" (the webroot), which will - /mnt # share the contents of the "/mnt" folder +[/] + /var/lib/copyparty-jail accs: - rw: * # everyone gets read-write access, but - rwmda: ed # the user "ed" gets read-write-move-delete-admin + r: * + rwdma: user + flags: + grid \ No newline at end of file diff --git a/contrib/systemd/copyparty.example.conf b/contrib/systemd/copyparty.example.conf new file mode 100644 index 00000000..79560d0d --- /dev/null +++ b/contrib/systemd/copyparty.example.conf @@ -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 diff --git a/contrib/systemd/copyparty@.service b/contrib/systemd/copyparty@.service new file mode 100644 index 00000000..cd891eab --- /dev/null +++ b/contrib/systemd/copyparty@.service @@ -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 diff --git a/contrib/systemd/index.md b/contrib/systemd/index.md new file mode 100644 index 00000000..cfd1b715 --- /dev/null +++ b/contrib/systemd/index.md @@ -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` diff --git a/contrib/package/arch/prisonparty.service b/contrib/systemd/prisonparty@.service similarity index 51% rename from contrib/package/arch/prisonparty.service rename to contrib/systemd/prisonparty@.service index cd35ba99..d30010ae 100644 --- a/contrib/package/arch/prisonparty.service +++ b/contrib/systemd/prisonparty@.service @@ -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, -# and read copyparty config from `/etc/copyparty.d/*.conf` +# and read copyparty config from `/etc/copyparty/copyparty.conf` # # 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 # following line to allow buffering (slightly better performance): @@ -15,19 +17,22 @@ Description=copyparty file server [Service] +Type=notify SyslogIdentifier=prisonparty -Environment=PYTHONUNBUFFERED=x WorkingDirectory=/var/lib/copyparty-jail +Environment=PYTHONUNBUFFERED=x +Environment=PRTY_CONFIG=/etc/copyparty/copyparty.conf ExecReload=/bin/kill -s USR1 $MAINPID -# 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' +# user to run as + where the TLS certificate is (if any) +User=%i +Environment=XDG_CONFIG_HOME=/home/%i/.config # run copyparty -ExecStart=/bin/bash /usr/bin/prisonparty /var/lib/copyparty-jail cpp cpp \ - /etc/copyparty.d \ +ExecStart=/bin/bash /usr/bin/prisonparty /var/lib/copyparty-jail %i %i \ + /etc/copyparty \ -- \ - /usr/bin/python3 /usr/bin/copyparty -c /etc/copyparty.d/init + /usr/bin/python3 /usr/bin/copyparty [Install] WantedBy=multi-user.target diff --git a/copyparty/__main__.py b/copyparty/__main__.py index d92089d0..2b55b058 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -536,7 +536,7 @@ def get_sects(): dedent( """ \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 @@ -562,7 +562,9 @@ def get_sects(): \033[32m-i unix:\033[33m/dev/shm/party.sock\033[0m keeps umask-defined permission (usually \033[33m0600\033[0m) and the same user/group as copyparty - \033[33m-p\033[0m (tcp ports) is ignored for unix sockets + \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 (password which do not start with '+') and then terminate afterwards + if you have enabled --accounts then the password + must be provided as username:password for hashing + \033[36m--ah-alg\033[0m specifies the hashing algorithm and a list of optional comma-separated arguments: @@ -993,18 +998,19 @@ def build_flags_desc(): def add_general(ap, nc, srvname): - 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 = ap.add_argument_group("general options") + 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("-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("-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("--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("-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="\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="\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("--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("--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("--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") @@ -1012,7 +1018,7 @@ def add_general(ap, nc, srvname): 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("--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]") @@ -1034,7 +1040,7 @@ def add_fs(ap): def add_share(ap): 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-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") @@ -1043,7 +1049,7 @@ def add_share(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("--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)") @@ -1085,14 +1091,14 @@ def add_upload(ap): def add_network(ap): - 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 = ap.add_argument_group("network options") + 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("--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-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("--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("--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 (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]") 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") @@ -1110,7 +1116,7 @@ def add_network(ap): 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("--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") @@ -1122,7 +1128,7 @@ def add_tls(ap, cert_path): def add_cert(ap, 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("--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") @@ -1142,7 +1148,7 @@ def add_cert(ap, cert_path): def add_auth(ap): idp_db = os.path.join(E.cfg, "idp.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-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") @@ -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-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("--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): 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-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-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") @@ -1212,12 +1218,12 @@ def add_zc_ssdp(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("--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("--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-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") @@ -1225,7 +1231,7 @@ def add_ftp(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("--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)") @@ -1235,7 +1241,7 @@ def add_webdav(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("--tftp4", action="store_true", help="only listen on IPv4") 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-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-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") 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("--smbw", action="store_true", help="enable write support (please dont)") 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): - 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("--on403", metavar="PY", type=u, action="append", help="handle 403s by executing \033[33mPY\033[0m file") + ap2 = ap.add_argument_group("handlers (see --help-handlers)") + 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="\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") def add_hooks(ap): - 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("--xau", metavar="CMD", type=u, action="append", help="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("--xbc", metavar="CMD", type=u, action="append", help="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("--xbr", metavar="CMD", type=u, action="append", help="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("--xbd", metavar="CMD", type=u, action="append", help="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("--xm", metavar="CMD", type=u, action="append", help="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 = ap.add_argument_group("event hooks (see --help-hooks)") + 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="\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="\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="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m before 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="\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="\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="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m before 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="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m on message") + 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") 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("--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)") @@ -1295,15 +1301,16 @@ def add_stats(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("--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("--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): - 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("--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") @@ -1329,7 +1336,7 @@ def add_optouts(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("-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") @@ -1360,7 +1367,7 @@ def add_safety(ap): 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-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]") @@ -1374,14 +1381,14 @@ def add_salt(ap, fk_salt, dk_salt, ah_salt): 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-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") 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("-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") @@ -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("--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-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-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") @@ -1399,7 +1406,7 @@ def add_logging(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-rescan", action="store_true", help="disable ?scan (volume reindexing)") 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): th_ram = (RAM_AVAIL or RAM_TOTAL or 9) * 0.6 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-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)") @@ -1445,7 +1452,7 @@ def add_thumbnail(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-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)") @@ -1458,7 +1465,7 @@ def add_transcoding(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-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)") @@ -1468,7 +1475,7 @@ def add_tail(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-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") @@ -1477,7 +1484,7 @@ def add_rss(ap): def add_db_general(ap, hcores): 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("-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") @@ -1506,7 +1513,7 @@ def add_db_general(ap, hcores): 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("-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") @@ -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-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("-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("-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): - 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("--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("-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)") @@ -1534,7 +1542,7 @@ def add_txt(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-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)") @@ -1552,7 +1560,7 @@ def add_og(ap): 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("--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") @@ -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("--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("--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("--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") @@ -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("--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("--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("--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)") @@ -1593,7 +1602,7 @@ def add_ui(ap, retry): 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("--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") diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index 44629e62..0f1c94f9 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -1700,6 +1700,7 @@ class AuthSrv(object): if not mount and not self.args.idp_h_usr: # -h says our defaults are CWD at root and read/write for everyone axs = AXS(["*"], ["*"], None, None) + ehint = "" 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" if len(cfg_files_loaded) == 1: @@ -1709,10 +1710,21 @@ class AuthSrv(object): else: self.log(t % ("the config does not define any volumes",), 1) axs = AXS() + ehint = "; please try moving them up one level, into the parent folder:" 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" self.log(t, 1) 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} vfs = VFS(self.log_func, absreal("."), "", "", axs, zvf) if not axs.uread: @@ -2630,6 +2642,8 @@ class AuthSrv(object): self.re_pwd = None pwds = [re.escape(x) for x in self.iacct.keys()] 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 self.ah.on: zs = r"(\[H\] pw:.*|[?&]pw=)([^&]+)" @@ -2930,6 +2944,9 @@ class AuthSrv(object): t = "minimum password length: %d characters" 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 if hpw == self.acct[uname]: @@ -3021,6 +3038,12 @@ class AuthSrv(object): self.log("chpw: " + msg, 6) 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) if not self.ah.on: if self.args.ah_cli or self.args.ah_gen: diff --git a/copyparty/cfg.py b/copyparty/cfg.py index 2f75ab28..8a07f956 100644 --- a/copyparty/cfg.py +++ b/copyparty/cfg.py @@ -111,6 +111,7 @@ def vf_vmap() -> dict[str, str]: "tail_tmax", "tail_who", "tcolor", + "txt_eol", "unlist", "u2abort", "u2ts", @@ -322,6 +323,7 @@ flagcats = { "exp": "enable textfile expansion; see --help-exp", "exp_md": "placeholders to expand in markdown files; 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": { "notail": "disable ?tail (download a growing file continuously)", diff --git a/copyparty/ftpd.py b/copyparty/ftpd.py index 2f45c3f4..004fb492 100644 --- a/copyparty/ftpd.py +++ b/copyparty/ftpd.py @@ -83,7 +83,12 @@ class FtpAuth(DummyAuthorizer): uname = "*" if username != "anonymous": 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), "") if zs: uname = zs @@ -607,7 +612,7 @@ class Ftpd(object): if "::" in ips: 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: ips = [x for x in ips if ":" not in x] diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index d562485b..402d623f 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -62,6 +62,7 @@ from .util import ( alltrace, atomic_move, b64dec, + eol_conv, exclude_dotfiles, formatdate, fsenc, @@ -262,7 +263,8 @@ class HttpCli(object): def _assert_safe_rem(self, rem: str) -> None: # 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") def _gen_fk(self, alg: int, salt: str, fspath: str, fsize: int, inode: int) -> str: @@ -383,9 +385,20 @@ class HttpCli(object): try: cli_ip = zsl[n].strip() except: - cli_ip = zsl[0].strip() - t = "rproxy={} oob x-fwd {}" - self.log(t.format(self.args.rproxy, zso), c=3) + cli_ip = self.ip + self.bad_xff = True + 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] xffs = self.conn.xff_nm @@ -2924,12 +2937,16 @@ class HttpCli(object): def handle_chpw(self) -> bool: assert self.parser # !rm + if self.args.usernames: + self.parser.require("uname", 64) pwd = self.parser.require("pw", 64) self.parser.drop() ok, msg = self.asrv.chpw(self.conn.hsrv.broker, self.uname, pwd) if ok: self.cbonk(self.conn.hsrv.gpwc, pwd, "pw", "too many password changes") + if self.args.usernames: + pwd = "%s:%s" % (self.uname, pwd) ok, msg = self.get_pwd_cookie(pwd) if ok: msg = "new password OK" @@ -2942,6 +2959,15 @@ class HttpCli(object): def handle_login(self) -> bool: 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) try: uhash = self.parser.require("uhash", 256) @@ -2952,6 +2978,9 @@ class HttpCli(object): if not pwd: raise Pebkac(422, "password cannot be blank") + if un: + pwd = "%s:%s" % (un, pwd) + dst = self.args.SRS if self.vpath: dst += quotep(self.vpaths) @@ -3583,7 +3612,7 @@ class HttpCli(object): rem = "{}/{}".format(rp, fn).strip("/") 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") if nullwrite: @@ -3667,6 +3696,9 @@ class HttpCli(object): if p_field != "body": 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") if xbu: if not runhook( @@ -4637,7 +4669,9 @@ class HttpCli(object): else: 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 maxn = vn.flags.get("zipmaxn_v") or 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] 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" if fmt in ["v", "t", "txt"]: @@ -5071,6 +5105,13 @@ class HttpCli(object): self.reply(zb, mime="text/plain; charset=utf-8") 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( "splash", this=self, @@ -5088,6 +5129,7 @@ class HttpCli(object): mtpq=vs["mtpq"], dbwt=vs["dbwt"], url_suf=suf, + re=re_btn, k304=self.k304(), no304=self.no304(), k304vis=self.args.k304 > 0, @@ -5133,7 +5175,7 @@ class HttpCli(object): t = '

404 not found  ┐( ´ -`)┌

go home

' 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) self.reply(pt.encode("utf-8"), status=rc) return True @@ -5447,6 +5489,8 @@ class HttpCli(object): elif nfi == 3: if not vp.endswith(vfi): continue + else: + continue n -= 1 if not n: @@ -5571,6 +5615,8 @@ class HttpCli(object): elif nfi == 3: if not vp.endswith(vfi): continue + else: + continue if not dots and "/." in vp: continue @@ -6001,6 +6047,12 @@ class HttpCli(object): else: [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) mime = "application/json" @@ -6183,7 +6235,8 @@ class HttpCli(object): if not use_filekey: 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", ""): self.vpath, og_fn = vsplit(self.vpath) vpath = self.vpath @@ -6195,10 +6248,10 @@ class HttpCli(object): vpnodes.pop() if ( - (abspath.endswith(".md") or self.can_delete) + (is_md or self.can_delete) and "nohtml" not in vn.flags and ( - ("v" in self.uparam and abspath.endswith(".md")) + (is_md and "v" in self.uparam) or "edit" in self.uparam or "edit2" in self.uparam ) @@ -6255,11 +6308,7 @@ class HttpCli(object): is_ls = "ls" in self.uparam is_js = self.args.force_js or self.cookies.get("js") == "y" - if ( - not is_ls - and not add_og - and (self.ua.startswith("curl/") or self.ua.startswith("fetch")) - ): + if not is_ls and not add_og and self.ua.startswith(("curl/", "fetch")): self.uparam["ls"] = "v" is_ls = True diff --git a/copyparty/multicast.py b/copyparty/multicast.py index 6d6507f8..766dc15d 100644 --- a/copyparty/multicast.py +++ b/copyparty/multicast.py @@ -183,11 +183,7 @@ class MCast(object): srv.ips[oth_ip.split("/")[0]] = ipaddress.ip_network(oth_ip, False) # gvfs breaks if a linklocal ip appears in a dns reply - ll = { - k: v - for k, v in srv.ips.items() - if k.startswith("169.254") or k.startswith("fe80") - } + ll = {k: v for k, v in srv.ips.items() if k.startswith(("169.254", "fe80"))} rt = {k: v for k, v in srv.ips.items() if k not in ll} if self.args.ll or not rt: diff --git a/copyparty/pwhash.py b/copyparty/pwhash.py index ebbd78f3..d4455ca8 100644 --- a/copyparty/pwhash.py +++ b/copyparty/pwhash.py @@ -147,6 +147,10 @@ class PWHash(object): def cli(self) -> None: import getpass + if self.args.usernames: + t = "since you have enabled --usernames, please provide username:password" + print(t) + while True: try: p1 = getpass.getpass("password> ") diff --git a/copyparty/svchub.py b/copyparty/svchub.py index bd7562e4..1af13276 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -850,15 +850,6 @@ class SvcHub(object): def _check_env(self) -> None: 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: 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 for zs in al.i: - if not zs.startswith("unix:"): + if not zs.startswith(("unix:", "fd:")): have_tcp = True if not have_tcp: zb = False @@ -878,7 +869,7 @@ class SvcHub(object): setattr(al, zs, False) zb = True 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) if not self.args.no_dav: diff --git a/copyparty/tcpsrv.py b/copyparty/tcpsrv.py index 53388c5e..6c61fe61 100644 --- a/copyparty/tcpsrv.py +++ b/copyparty/tcpsrv.py @@ -25,8 +25,8 @@ from .util import ( termsize, ) -if True: - from typing import Generator, Union +if True: # pylint: disable=using-constant-test + from typing import Generator, Optional, Union if TYPE_CHECKING: from .svchub import SvcHub @@ -245,8 +245,10 @@ class TcpSrv(object): def _listen(self, ip: str, port: int) -> None: uds_perm = uds_gid = -1 + bound: Optional[socket.socket] = None + tcp = False + if "unix:" in ip: - tcp = False ipv = socket.AF_UNIX uds = ip.split(":") ip = uds[-1] @@ -259,7 +261,12 @@ class TcpSrv(object): import grp 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: tcp = True ipv = socket.AF_INET6 @@ -267,7 +274,7 @@ class TcpSrv(object): tcp = True 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: srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) @@ -285,6 +292,10 @@ class TcpSrv(object): if getattr(self.args, "freebind", False): srv.setsockopt(socket.SOL_IP, socket.IP_FREEBIND, 1) + if bound: + self.srv.append(srv) + return + try: if tcp: srv.bind((ip, port)) @@ -437,7 +448,7 @@ class TcpSrv(object): def detect_interfaces(self, listen_ips: list[str]) -> dict[str, Netdev]: 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) eps: dict[str, Netdev] = {} diff --git a/copyparty/tftpd.py b/copyparty/tftpd.py index 6f5726b3..4ef01103 100644 --- a/copyparty/tftpd.py +++ b/copyparty/tftpd.py @@ -179,7 +179,7 @@ class Tftpd(object): if "::" in ips: 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: ips = [x for x in ips if ":" not in x] diff --git a/copyparty/up2k.py b/copyparty/up2k.py index 785d0a4f..b233cecb 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -375,11 +375,12 @@ class Up2k(object): if ineed == ihash or not ineed: continue + poke = job["poke"] zt = ( ineed / ihash, job["size"], - int(job["t0c"]), - int(job["poke"]), + int(job.get("t0c", poke)), + int(poke), djoin(vtop, job["prel"], job["name"]), ) ret.append(zt) diff --git a/copyparty/util.py b/copyparty/util.py index 15b1924b..b5a2c45e 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -2982,6 +2982,17 @@ def justcopy( 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( fin: Generator[bytes, None, None], fout: Union[typing.BinaryIO, typing.IO[Any]], diff --git a/copyparty/web/browser.css b/copyparty/web/browser.css index 1f5a7da6..b5506f39 100644 --- a/copyparty/web/browser.css +++ b/copyparty/web/browser.css @@ -1374,6 +1374,7 @@ html.y #ops svg circle { #op_cfg input[type=text] { top: -.3em; } +.opview select, .opview input[type=text] { color: var(--fg); background: var(--txt-bg); @@ -1384,6 +1385,10 @@ html.y #ops svg circle { border-radius: .2em; padding: .2em .3em; } +.opview select { + padding: .3em; + margin: .2em .4em; +} .opview input.err { color: var(--err-fg); background: var(--err-bg); diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 4ca3750d..1b95ced2 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -3072,7 +3072,7 @@ var Ls = { "u_https1": "für bessere Performance solltest du", "u_https2": "auf HTTPS wechseln", - "u_https3": "", + "u_https3": " ", "u_ancient": 'Dein Browser ist verdammt antik -- vielleicht solltest du stattdessen bup benutzen', "u_nowork": "Benötigt Firefox 53+ oder Chrome 57+ oder iOS 11+", "tail_2old": "Benötigt Firefox 105+ oder Chrome 71+ oder iOS 14.5+", @@ -3122,7 +3122,7 @@ var Ls = { "u_ehsdf": "Server hat kein Speicherplatz mehr!\n\nwerde es erneut versuchen, falls jemand\ngenug Platz schafft um fortzufahren", "u_emtleak1": "scheint, als ob dein Browser ein Memory Leak hätte;\nbitte", "u_emtleak2": ' wechsle auf HTTPS (empfohlen) oder ', - "u_emtleak3": '', + "u_emtleak3": ' ', "u_emtleakc": 'versuche folgendes:\nUploads werden etwas langsamer sein, aber man kann ja nicht alles haben.\nSorry für die Umstände !\n\nPS: Chrome v107 hat ein Bugfix dafür', "u_emtleakf": 'versuche folgendes:\n\nPS: Firefox hat hoffentlich irgendwann ein Bugfix', "u_s404": "nicht auf dem Server gefunden", @@ -3490,7 +3490,7 @@ var Ls = { "mm_uncache": "välimuisti tyhjennetty; kaikki kappaleet ladataan uudelleen seuraavalla toistolla", "mm_hnf": "tuota kappaletta ei enää ole olemassa", -" im_hnf": "tuota kuvaa ei enää ole olemassa", + "im_hnf": "tuota kuvaa ei enää ole olemassa", "f_empty": 'tämä hakemisto on tyhjä', "f_chide": 'tämä piilottaa sarakkeen «{0}»\n\nvoit palauttaa sarakkeet asetuksista', @@ -3785,6 +3785,635 @@ var Ls = { "lang_set": "ladataanko sivu uudestaan kielen vaihtamiseksi?", }, + "grc": { + "tt": "Ελληνικά", + + "cols": { + "c": "κουμπιά ενεργειών", + "dur": "διάρκεια", + "q": "ποιότητα / bitrate", + "Ac": "κωδικοποιητής ήχου", + "Vc": "κωδικοποιητής βίντεο", + "Fmt": "μορφή / container", + "Ahash": "checksum ήχου", + "Vhash": "checksum βίντεο", + "Res": "ανάλυση", + "T": "τύπος αρχείου", + "aq": "ποιότητα ήχου / bitrate", + "vq": "ποιότητα βίντεο / bitrate", + "pixfmt": "subsampling / δομή εικονοστοιχείων", + "resw": "οριζόντια ανάλυση", + "resh": "κάθετη ανάλυση", + "chs": "κανάλια ήχου", + "hz": "συχνότητα δειγματοληψίας" + }, + + "hks": [ + [ + "διάφορα", + ["ESC", "κλείσιμο διαφόρων λειτουργιών"], + + "διαχειριστής αρχείων", + ["G", "εναλλαγή λίστας / πλέγματος"], + ["T", "εναλλαγή μικρογραφιών / εικονιδίων"], + ["⇧ A/D", "μέγεθος μικρογραφιών"], + ["ctrl-K", "διαγραφή επιλεγμένων"], + ["ctrl-X", "αποκοπή επιλογής στο πρόχειρο"], + ["ctrl-C", "αντιγραφή επιλογής στο πρόχειρο"], + ["ctrl-V", "επικόλληση (μετακίνηση/αντιγραφή) εδώ"], + ["Y", "λήψη επιλεγμένων"], + ["F2", "μετονομασία επιλεγμένων"], + + "λίστα αρχείων", + ["space", "εναλλαγή επιλογής αρχείου"], + ["↑/↓", "μετακίνηση δείκτη επιλογής"], + ["ctrl ↑/↓", "μετακίνηση δείκτη και προβολής"], + ["⇧ ↑/↓", "επιλογή προηγούμενου/επόμενου αρχείου"], + ["ctrl-A", "επιλογή όλων των αρχείων / φακέλων"] + ], [ + "πλοήγηση", + ["B", "εναλλαγή σε καρτέλες διαδρομών / δέντρο διαδρομών"], + ["I/K", "προηγούμενος/επόμενος φάκελος"], + ["M", "γονικός φάκελος (ή σμίκρυνση τρέχοντος)"], + ["V", "εναλλαγή φακέλων / δέντρο αρχείων κειμένου"], + ["A/D", "μέγεθος πίνακα πλοήγησης"] + ], [ + "μουσική", + ["J/L", "προηγούμενο/επόμενο τραγούδι"], + ["U/O", "μετάβαση 10δευτ πίσω/μπροστά"], + ["0..9", "μετάβαση στο 0%..90%"], + ["P", "αναπαραγωγή/παύση (ξεκινάει κιόλας)"], + ["S", "επιλογή αναπαραγόμενου τραγουδιού"], + ["Y", "λήψη τραγουδιού"] + ], [ + "εικόνες", + ["J/L, ←/→", "προηγούμενη/επόμενη εικόνα"], + ["Home/End", "πρώτη/τελευταία εικόνα"], + ["F", "πλήρης οθόνη"], + ["R", "περιστροφή δεξιόστροφα"], + ["⇧ R", "περιστροφή αριστερόστροφα"], + ["S", "επιλογή εικόνας"], + ["Y", "λήψη εικόνας"] + ], [ + "βίντεο", + ["U/O", "μετάβαση 10δευτ πίσω/μπροστά"], + ["P/K/Space", "αναπαραγωγή/παύση"], + ["C", "συνέχεια στο επόμενο"], + ["V", "επανάληψη"], + ["M", "σίγαση"], + ["[ και ]", "ορισμός διαστήματος επανάληψης"] + ], [ + "αρχεία κειμένου", + ["I/K", "προηγούμενο/επόμενο αρχείο"], + ["M", "κλείσιμο αρχείου"], + ["E", "επεξεργασία αρχείου"], + ["S", "επιλογή αρχείου (για αποκοπή/αντιγραφή/μετονομασία)"] + ] + ], + + "m_ok": "Εντάξει", + "m_ng": "Άκυρο", + + "enable": "Ενεργοποίηση", + "danger": "ΚΙΝΔΥΝΟΣ", + "clipped": "αντιγράφηκε στο πρόχειρο", + + "ht_s1": "δευτερόλεπτο", + "ht_s2": "δευτερόλεπτα", + "ht_m1": "λεπτό", + "ht_m2": "λεπτά", + "ht_h1": "ώρα", + "ht_h2": "ώρες", + "ht_d1": "μέρα", + "ht_d2": "μέρες", + "ht_and": " και ", + + "goh": "πίνακας ελέγχου", + "gop": 'προηγούμενος φάκελος στο ίδιο επίπεδο">προηγούμενο', + "gou": 'γονικός φάκελος">πάνω', + "gon": 'επόμενος φάκελος">επόμενο', + "logout": "Αποσύνδεση ", + "access": " πρόσβαση", + "ot_close": "κλείσιμο υπομενού", + "ot_search": "αναζήτηση αρχείων με βάση χαρακτηριστικά, διαδρομή / όνομα, μουσικά tags ή οποιονδήποτε συνδυασμό$N$N<code>foo bar</code> = πρέπει να περιέχει και τα «foo» και «bar»,$N<code>foo -bar</code> = πρέπει να περιέχει το «foo» αλλά όχι το «bar»,$N<code>^yana .opus$</code> = να ξεκινά με «yana» και να είναι αρχείο «opus»$N<code>"try unite"</code> = να περιέχει ακριβώς «try unite»$N$Nη μορφή ημερομηνίας είναι iso-8601, όπως$N<code>2009-12-31</code> ή <code>2020-09-12 23:30:00</code>", + "ot_unpost": "unpost: διαγραφή πρόσφατων μεταφορτώσεων ή ακύρωση ανολοκλήρωτων", + "ot_bup": "bup: βασικός uploader, υποστηρίζει μέχρι και netscape 4.0", + "ot_mkdir": "mkdir: δημιουργία νέου φακέλου", + "ot_md": "new-md: δημιουργία νέου markdown εγγράφου", + "ot_msg": "msg: αποστολή μηνύματος στο server log", + "ot_mp": "επιλογές media player", + "ot_cfg": "επιλογές ρυθμίσεων", + "ot_u2i": 'up2k: ανέβασε αρχεία (αν έχεις δικαίωμα εγγραφής) ή ενεργοποίησε τη λειτουργία αναζήτησης για να δεις αν υπάρχουν ήδη στο server$N$Nοι μεταφορτώσεις συνεχίζονται αν διακοπούν, είναι πολυνηματικές και διατηρούν τις χρονοσφραγίδες, αλλά καταναλώνουν περισσότερο CPU από τον [🎈]  (βασικός uploader)

κατά τη διάρκεια της μεταφόρτωσης, αυτό το εικονίδιο δείχνει την πρόοδό της!', + "ot_u2w": 'up2k: ανέβασε αρχεία με υποστήριξη συνέχισης (κλείσε τον browser και ρίξε τα ίδια αρχεία ξανά μετά)$N$Nπολυνηματικό, διατηρεί τις χρονοσφραγίδες, αλλά καταναλώνει περισσότερο CPU από τον [🎈]  (βασικός uploader)

κατά τη διάρκεια της μεταφόρτωσης, αυτό το εικονίδιο δείχνει την πρόοδό της!', + "ot_noie": 'Χρησιμοποίησε Chrome / Firefox / Edge', + + "ab_mkdir": "δημιουργία φακέλου", + "ab_mkdoc": "νέο markdown έγγραφο", + "ab_msg": "στείλε μήνυμα στο server log", + + "ay_path": "πήγαινε σε φακέλους", + "ay_files": "πήγαινε σε αρχεία", + + "wt_ren": "μετονομασία επιλεγμένων$NΣυντόμευση: F2", + "wt_del": "διαγραφή επιλεγμένων$NΣυντόμευση: ctrl-K", + "wt_cut": "αποκοπή επιλεγμένων <small>(και επικόλληση αλλού)</small>$NΣυντόμευση: ctrl-X", + "wt_cpy": "αντιγραφή επιλεγμένων στο πρόχειρο$N(για επικόλληση αλλού)$NΣυντόμευση: ctrl-C", + "wt_pst": "επικόλληση αποκομμένων / αντεγραμμένων$NΣυντόμευση: ctrl-V", + "wt_selall": "επιλογή όλων$NΣυντόμευση: ctrl-A (με το αρχείο επιλεγμένο)", + "wt_selinv": "αντιστροφή επιλογής", + "wt_zip1": "κατέβασμα φακέλου ως συμπιεσμένο αρχείο", + "wt_selzip": "κατέβασμα επιλογής ως συμπιεσμένο αρχείο", + "wt_seldl": "κατέβασμα επιλογής ως μεμονωμένα αρχεία$NΣυντόμευση: Y", + "wt_npirc": "αντιγραφή πληροφοριών τραγουδιού σε μορφή irc", + "wt_nptxt": "αντιγραφή πληροφοριών τραγουδιού ως κείμενο", + "wt_m3ua": "προσθήκη σε m3u λίστα αναπαραγωγής (μετά πάτησε 📻αντιγραφή)", + "wt_m3uc": "αντιγραφή m3u λίστας αναπαραγωγής στο πρόχειρο", + "wt_grid": "εναλλαγή πλέγματος / λίστας$NΣυντόμευση: G", + "wt_prev": "προηγούμενο κομμάτι$NΣυντόμευση: J", + "wt_play": "αναπαραγωγή / παύση$NΣυντόμευση: P", + "wt_next": "επόμενο κομμάτι$NΣυντόμευση: L", + + "ul_par": "παράλληλες μεταφορτώσεις:", + "ut_rand": "τυχαιοποίηση ονομάτων αρχείων", + "ut_u2ts": "αντιγραφή της τελευταίας τροποποιημένης χρονοσφραγίδας αλλαγής$Nαπό το σύστημά σου στον server\">📅", + "ut_ow": "αντικατάσταση σε ήδη υπάρχοντα αρχεία του server?$N🛡️: ποτέ (θα δημιουργηθεί νέο όνομα)$N🕒: αν το αρχείο του server είναι παλαιότερο$N♻️: πάντα να αντικαθίστανται αν διαφέρουν", + "ut_mt": "συνέχιση υπολογισμού hash για άλλα αρχεία κατά τη μεταφόρτωση$N$Nαπενεργοποίησέ το αν η CPU ή ο δίσκος σου ζορίζονται", + "ut_ask": 'επιβεβαίωση πριν ξεκινήσει η μεταφόρτωση">💭', + "ut_pot": "βελτίωση ταχύτητας μεταφόρτωσης σε αργές συσκευές$Nμε απλοποίηση του UI", + "ut_srch": "μην ανεβάζεις, έλεγξε αν τα αρχεία$Nυπάρχουν ήδη στον server (ψάχνει σε όλους τους φακέλους που έχεις πρόσβαση)", + "ut_par": "κάνε παύση στις μεταφορτώσεις βάζοντάς το 0$N$Nαύξησε το αν έχεις αργή/μεγάλη καθυστέρηση σύνδεσης$N$Nκράτα το 1 σε LAN ή αν ο server έχει αργό δίσκο", + "ul_btn": "ρίξε αρχεία / φακέλους
εδώ (ή κάνε κλικ σε μένα)", + "ul_btnu": "Μ Ε Τ Α Φ Ο Ρ Τ Ω Σ Η", + "ul_btns": "Α Ν Α Ζ Η Τ Η Σ Η", + + "ul_hash": "υπολογισμός hash", + "ul_send": "αποστολή", + "ul_done": "ολοκληρώθηκε", + "ul_idle1": "καμία μεταφόρτωση στην ουρά για την ώρα", + "ut_etah": "μέση ταχύτητα <em>υπολογισμού hash</em> και εκτίμηση χρόνου μέχρι την ολοκλήρωση", + "ut_etau": "μέση ταχύτητα <em>μεταφόρτωσης</em> και εκτίμηση χρόνου μέχρι την ολοκλήρωση", + "ut_etat": "μέση <em>συνολική</em> ταχύτητα και εκτίμηση χρόνου μέχρι την ολοκλήρωση", + + "uct_ok": "ολοκληρώθηκε επιτυχώς", + "uct_ng": "no-good: απέτυχε / απορρίφθηκε / δεν βρέθηκε", + "uct_done": "ολοκληρωμένα και αποτυχημένα", + "uct_bz": "κάνει hash ή μεταφορτώνει", + "uct_q": "σε αναμονή, εκκρεμεί", + + "utl_name": "όνομα αρχείου", + "utl_ulist": "λίστα", + "utl_ucopy": "αντιγραφή", + "utl_links": "σύνδεσμοι", + "utl_stat": "κατάσταση", + "utl_prog": "πρόοδος", + + // keep short: + "utl_404": "404", + "utl_err": "ΣΦΑΛΜΑ", + "utl_oserr": "ΣΦ-ΛΕ", + "utl_found": "βρέθηκε", + "utl_defer": "αναβολή", + "utl_yolo": "YOLO", + "utl_done": "έγινε", + + "ul_flagblk": "τα αρχεία προστέθηκαν στην ουρά
αλλά υπάρχει άλλη ενεργή μεταφόρτωση σε άλλη καρτέλα,
οπότε περίμενε να τελειώσει αυτό πρώτα", + "ul_btnlk": "ο διακομιστής έχει κλειδώσει αυτήν την επιλογή σε αυτήν την κατάσταση", + + "udt_up": "Μεταφόρτωση", + "udt_srch": "Αναζήτηση", + "udt_drop": "ρίξ' το εδώ", + + "u_nav_m": '
οκ, τι έχουμε εδώ;
Enter = Αρχεία (ένα ή περισσότερα)\nESC = Ένας φάκελος (μαζί με υποφακέλους)', + "u_nav_b": 'ΑρχείαΈνας φάκελος', + + "cl_opts": "διακόπτες", + "cl_themes": "θέμα", + "cl_langs": "γλώσσα", + "cl_ziptype": "λήψη φακέλου", + "cl_uopts": "διακόπτες μεταφόρτωσης", + "cl_favico": "favicon", + "cl_bigdir": "μεγάλοι φάκελοι", + "cl_hsort": "#ταξινόμηση", + "cl_keytype": "σημείωση πλήκτρων", + "cl_hiddenc": "κρυφές στήλες", + "cl_hidec": "κρύψε", + "cl_reset": "επανεκκίνηση", + "cl_hpick": "πάτησε στις κεφαλίδες στηλών για να τις κρύψεις στον πίνακα παρακάτω", + "cl_hcancel": "η απόκρυψη στηλών ακυρώθηκε", + + "ct_grid": '田 το πλέγμα', + "ct_ttips": '◔ ◡ ◔">ℹ️ συμβουλές εργαλείων', + "ct_thumb": 'σε προβολή πλέγματος, εναλλαγή εικονιδίων ή μικρογραφιών$NΠλήκτρο συντόμευσης: T">🖼️ μικρογραφίες', + "ct_csel": 'χρησιμοποίησε CTRL και SHIFT για επιλογή αρχείων σε προβολή πλέγματος">επιλογή', + "ct_ihop": 'όταν η προβολή εικόνων κλείνει, κάνε scroll στο τελευταίο προβολόμενο αρχείο">g⮯', + "ct_dots": 'εμφάνιση κρυφών αρχείων (αν το επιτρέπει ο server)">dotfiles', + "ct_qdel": 'όταν διαγράφεις αρχεία, ζήτα επιβεβαίωση μόνο μία φορά">γρήγορη διαγραφή', + "ct_dir1st": 'ταξινόμηση φακέλων πριν από τα αρχεία">📁 πρώτα', + "ct_nsort": 'φυσική ταξινόμηση (για ονόματα αρχείων με αριθμούς στην αρχή)">φυσική ταξινόμηση', + "ct_utc": 'εμφάνιση όλων των ημερομηνιών σε UTC">UTC', + "ct_readme": 'εμφάνιση README.md στις λίστες φακέλων">📜 πληροφορίες', + "ct_idxh": 'εμφάνιση index.html αντί για λίστα φακέλων">html', + "ct_sbars": 'εμφάνιση μπαρών κύλισης">⟊', + + "cut_umod": "αν το αρχείο υπάρχει ήδη στον server, ενημέρωσέ το με την τελευταία χρονοσφραγίδα τροποποίησης για να ταιριάζει με το τοπικό αρχείο (απαιτεί δικαιώματα εγγραφής+διαγραφής)\">re📅", + + "cut_turbo": "το κουμπί yolo, πιθανόν να ΜΗΝ ΘΕΛΕΙΣ να το ενεργοποιήσεις:$N$Nχρησιμοποίησέ το αν μεταφορτώνεις πολλά αρχεία και χρειάστηκε να ξαναρχίσεις, και θες να συνεχίσεις τη μεταφότρωση όσο το δυνατόν πιο γρήγορα$N$Nαντικαθιστά τον έλεγχο hash με απλό "έχει το ίδιο μέγεθος αρχείου στον server?" οπότε αν το περιεχόμενο είναι διαφορετικό, ΔΕΝ θα ανέβει$N$Nπρέπει να το κλείσεις όταν τελειώσει η μεταφόρτωση και μετά να "μεταφορτώσεις" πάλι τα ίδια αρχεία για να τα επιβεβαιώσει το τοπικό σου πρόγραμμα\">turbo", + + "cut_datechk": "δεν επηρεάζει τίποτα εκτός αν το turbo είναι ενεργοποιημένο$N$Nμειώνει λίγο τον παράγοντα yolo; ελέγχει αν οι χρονοσφραγίδες στο διακομιστή ταιριάζουν με τα δικά σου$N$Nπιάνει θεωρητικά τις περισσότερες μισοτελειωμένες/κατεστραμμένες μεταφορτώσεις, αλλά δεν αντικαθιστά τον έλεγχο με το turbo απενεργοποιημένο μετέπειτα\">έλεγχος ημερομηνίας", + + "cut_u2sz": "μέγεθος (σε MiB) κάθε κομματιού μεταφόρτωσης; μεγάλες τιμές λειτουργούν καλύτερα σε μεγαλύτερες αποστάσεις διακομιστή-πελάτη. Δοκίμασε μικρές τιμές σε πολύ άστατες συνδέσεις", + + "cut_flag": "εξασφαλίζει ότι μόνο μία καρτέλα μεταφορτώνει κάθε φορά $N -- οι άλλες καρτέλες πρέπει να το έχουν κι αυτές ενεργό $N -- επηρεάζει μόνο τις καρτέλες που βρίσκονται στο ίδιο διεύθυνση", + + "cut_az": "μεταφόρτωσε τα αρχεία αλφαβητικά, αντί για το μικρότερο αρχείο, πρώτα$N$Nη αλφαβητική σειρά βοηθά να καταλάβεις αν κάτι χάλασε στο διακομιστή αλλά κάνει το ανέβασμα λίγο πιο αργό σε fiber / LAN", + + "cut_nag": "ειδοποίηση λειτουργικού συστήματος όταν τελειώσει η μεταφόρτωση$N(μόνο αν ο browser ή η καρτέλα δεν είναι ενεργά)", + "cut_sfx": "ηχητική ειδοποίηση όταν τελειώσει η μεταφόρτωση$N(μόνο αν ο browser ή η καρτέλα δεν είναι ενεργά)", + + "cut_mt": "χρησιμοποίησε multithreading για να επιταχύνεις το hashing των αρχείων$N$Nχρησιμοποιεί web-workers και χρειάζεται$Nπερισσότερη RAM (μέχρι 512 MiB επιπλέον)$N$Nκάνει το https 30% πιο γρήγορο, το http 4.5x πιο γρήγορο\">mt", + + "cut_wasm": "χρησιμοποίησε wasm αντί για τον ενσωματωμένο hasher του browser; βελτιώνει την ταχύτητα σε chrome-based browsers αλλά αυξάνει το φορτίο της CPU, και παλιές εκδόσεις chrome έχουν bugs που κάνουν το browser να τρώει όλη τη RAM και να κρασάρει αν ενεργοποιηθεί\">wasm", + + "cft_text": "κείμενο favicon (κενό και ανανέωση για απενεργοποίηση)", + "cft_fg": "χρώμα προσκηνίου", + "cft_bg": "χρώμα παρασκηνίου", + + "cdt_lim": "μέγιστος αριθμός αρχείων προς εμφάνιση σε ένα φάκελο", + "cdt_ask": "όταν φτάνεις στο τέλος,$Nαντί να φορτώσει περισσότερα αρχεία,$Nρώτα τι να κάνει", + "cdt_hsort": "πόσους κανόνες ταξινόμησης (<code>,sorthref</code>) να συμπεριλάβει σε URLs πολυμέσων. Αν το βάλεις 0 αγνοεί και κανόνες ταξινόμησης στους συνδέσμους πολυμέσων", + + "tt_entree": "εμφάνιση navpane (δέντρο διαδρομών)$NΠλήκτρο συντόμευσης: B", + "tt_detree": "εμφάνιση breadcrumbs (καρτέλες διαδρομών)$NΠλήκτρο συντόμευσης: B", + "tt_visdir": "κύλιση στον επιλεγμένο φάκελο", + "tt_ftree": "εναλλαγή δέντρου διαδρομών / αρχείων κειμένου$NΠλήκτρο συντόμευσης: V", + "tt_pdock": "εμφάνιση γονικών φακέλων σε σταθερή μπάρα επάνω", + "tt_dynt": "αυτόματη επέκταση καθώς επεκτείνεται το δέντρο διαδρομών", + "tt_wrap": "αναδίπλωση λέξεων", + "tt_hover": "αποκάλυψη των γραμμών που ξεπερνούν το πλάτος με το ποντίκι πάνω τους$N( σπάει το scroll εκτός αν το ποντίκι $N  είναι στην αριστερή στήλη )", + + "ml_pmode": "στο τέλος του φακέλου...", + "ml_btns": "εντολές", + "ml_tcode": "μετακωδικοποίηση", + "ml_tcode2": "μετακωδικοποίηση σε", + "ml_tint": "φίλτρο χρώματος", + "ml_eq": "ισοσταθμιστής ήχου", + "ml_drc": "συμπιεστής δυναμικής εμβέλειας", + + "mt_loop": "επανάληψη ενός τραγουδιού\">🔁", + "mt_one": "σταμάτα μετά από ένα τραγούδι\">1️⃣", + "mt_shuf": "τυχαία σειρά τραγουδιών σε κάθε φάκελο\">🔀", + "mt_aplay": "αυτόματη αναπαραγωγή αν υπάρχει song-ID στη διεύθυνση που μπήκες στο διακομιστή$N$Nη απενεργοποίηση αυτού, σταματά το URL από το να ενημερώνεται με τα song-ID ενώ παίζει η μουσική για να αποτραπεί η αυτόματη αναπαραγωγή αν χαθούν αυτές οι ρυθμίσεις αλλά το URL παραμείνει το ίδιο\">a▶", + "mt_preload": "ξεκίνα τη φόρτωση του επόμενου τραγουδιού κοντά στο τέλος για συνεχόμενη ακρόαση\">προφόρτωση", + "mt_prescan": "πήγαινε στον επόμενο φάκελο πριν τελειώσει το τελευταίο τραγούδι$Nγια να μη σταματήσει το πρόγραμμα περιήγησης να παίζει μουσική\">nav", + "mt_fullpre": "προσπάθησε να προφορτώσεις ολόκληρο το τραγούδι;$N✅ ενεργό σε αναξιόπιστες συνδέσεις,$N❌ πιθανότατα απενεργοποιημένο σε αργές συνδέσεις\">πλήρες", + "mt_fau": "σε κινητά, πρόλαβε να μην σταματήσει η μουσική αν το επόμενο τραγούδι δεν προφορτώθηκε γρήγορα (μπορεί να προκαλέσει πρόβλημα στην εμφάνιση των ετικετών)\">☕️", + "mt_waves": "γραμμή αναζήτησης κυματομορφής:$Nεμφάνιση έντασης ήχου στην μπάρα αναζήτησης\">~s", + "mt_npclip": "εμφάνισε κουμπιά για αντιγραφή του τρέχοντος τραγουδιού\">/np", + "mt_m3u_c": "εμφάνισε κουμπιά για αντιγραφή των$Nεπιλεγμένων τραγουδιών ως καταχωρήσεις λίστας m3u8\">📻", + "mt_octl": "ενσωμάτωση στο λειτουργικό σύστημα (σηντομεύσεις πλήκτρων πολυμέσων / osd)\">έλεγχος-OS", + "mt_oseek": "επιτρέπει την αναζήτηση μέσω ενσωμάτωσης του λειτουργικού συστήματος$N$Nσημείωση: σε μερικές συσκευές (iPhones),$Nαντικαθιστά το κουμπί επόμενου τραγουδιού\">αναζήτηση", + "mt_oscv": "εμφάνιση εξωφύλλου άλμπουμ σε osd\">εξώφυλλο", + "mt_follow": "κρατά το τρέχον κομμάτι ορατό κατά την κύλιση\">🎯", + "mt_compact": "συμπαγή κουμπιά ελέγχου\">⟎", + "mt_uncache": "καθάρισε την προσωρινή μνήμη  (δοκίμασε αυτό αν ο browser έχει αποθηκεύσει$Nχαλασμένο αντίγραφο τραγουδιού και αρνείται να παίξει)\">εκκαθάριση", + "mt_mloop": "τυχαία αναπαραγωγή στον ανοικτό φάκελο\">🔁 τυχαία αναπαραγωγή", + "mt_mnext": "φόρτωση επόμενου φακέλου και συνέχιση\">📂 επόμενο", + "mt_mstop": "σταμάτησε την αναπαραγωγή\">⏸ σταμάτημα", + "mt_cflac": "μετατροπή flac / wav σε opus\">flac", + "mt_caac": "μετατροπή aac / m4a σε opus\">aac", + "mt_coth": "μετατροπή όλων των άλλων (εκτός των mp3) σε opus\">άλλο", + "mt_c2opus": "καλύτερη επιλογή για desktop, laptop, android\">opus", + "mt_c2owa": "opus-weba, για iOS 17.5 και νεότερα\">owa", + "mt_c2caf": "opus-caf, για iOS 11 έως 17\">caf", + "mt_c2mp3": "χρησιμοποίησε αυτό σε πολύ παλιές συσκευές\">mp3", + "mt_c2flac": "βέλτιστη ποιότητα ήχου αλλά τεράστιο αρχείο για μεταφόρτωση\">flac", + "mt_c2wav": "ασυμπίεστη αναπαραγωγή (ακόμα μεγαλύτερο αρχείο)\">wav", + "mt_c2ok": "μια χαρά, σοφή επιλογή", + "mt_c2nd": "δεν είναι η προτεινόμενη μορφή εξόδου για τη συσκευή σου, αλλά αυτό είναι ok", + "mt_c2ng": "η συσκευή σου φαίνεται να μην υποστηρίζει αυτήν τη μορφή εξόδου, αλλά ας το δοκιμάσουμε ούτως ή άλλως", + "mt_xowa": "υπάρχουν bugs σε iOS που εμποδίζουν την αναπαραγωγή στο παρασκήνιο με αυτήν τη μορφή· χρησιμοποίησε caf ή mp3 αντ’ αυτού", + "mt_tint": "επίπεδο φόντου (0-100) στην μπάρα αναζήτησης$Nγια να κάνεις το buffering λιγότερο ενοχλητικό", + "mt_eq": "ενεργοποιεί τον ισοσταθμιστή και τον έλεγχο ενίσχυσης;$N$Nενίσχυση <code>0</code> = στάνταρ 100% ένταση (απαράλλαχτη)$N$Nεύρος <code>1  </code> = στάνταρ στερεοφωνικό (απαράλλαχτο)$Nεύρος <code>0.5</code> = 50% αριστερά-δεξιά μίξη ήχου$Nεύρος <code>0  </code> = μονοφωνικό$N$Nενίσχυση <code>-0.8</code> & εύρος <code>10</code> = αφαίρεση φωνής :^)$N$Nη ενεργοποίηση του ισοσταθμιστή κάνει τα άλμπουμ χωρίς κενά, να παίζουν χωρίς καθόλου κενά, οπότε άφησέ το ενεργό με όλες τις τιμές στο μηδέν (εκτός από εύρος = 1) αν σε νοιάζει", + "mt_drc": "ενεργοποιεί τον συμπιεστή δυναμικής εμβέλειας (εξομάλυνση έντασης / ακραία συμπίεση έντασης); θα ενεργοποιήσει και τον ισοσταθμιστή για να ισορροπήσει τον ήχο, οπότε βάλε όλα τα πεδία ισοσταθμιστή εκτός από το 'εύρος' στο 0 αν δεν το θες$N$Nχαμηλώνει την ένταση του ήχου πάνω από το όριο (THRESHOLD) dB; για κάθε RATIO dB πέρα από το όριο υπάρχει 1 dB εξόδου, οπότε οι προεπιλεγμένες τιμές όριο -24 και 'λόγος' 12 σημαίνουν ότι δεν θα ξεπεράσει ποτέ τα -22 dB και είναι ασφαλές να αυξήσεις την ενίσχυση ισοσταθμιστή σε 0.8, ή και 1.8 με ATK 0 και μεγάλο RLS όπως 90 (δουλεύει μόνο σε firefox· το RLS είναι max 1 σε άλλους browsers)$N$N(δες wikipedia, το εξηγούν καλύτερα)", + + "mb_play": "παίξε", + "mm_hashplay": "να παίξω αυτό το αρχείο ήχου;", + "mm_m3u": "πάτα Enter/OK για Αναπαραγωγή\nπάτα ESC/Άκυρο για Επεξεργασία", + "mp_breq": "χρειάζεται firefox 82+ ή chrome 73+ ή iOS 15+", + "mm_bload": "φορτώνει...", + "mm_bconv": "μετατροπή σε {0}, περίμενε...", + "mm_opusen": "ο browser σου δεν παίζει αρχεία aac / m4a;\nη μετατροπή σε opus είναι τώρα ενεργή", + "mm_playerr": "η αναπαραγωγή, απέτυχε: ", + "mm_eabrt": "Η προσπάθεια αναπαραγωγής ακυρώθηκε", + "mm_enet": "Η σύνδεση του ίντερνέτ σου είναι χάλια", + "mm_edec": "Το αρχείο αυτό είναι μάλλον κατεστραμμένο;;", + "mm_esupp": "Ο browser σου δεν καταλαβαίνει αυτή τη μορφή ήχου", + "mm_eunk": "Άγνωστο σφάλμα", + "mm_e404": "Αδύνατη η αναπαραγωγή ήχου; σφάλμα 404: Το αρχείο δεν βρέθηκε.", + "mm_e403": "Αδύνατη η αναπαραγωγή ήχου; σφάλμα 403: Άρνηση πρόσβασης.\n\nΔοκίμασε F5 για επαναφόρτωση, ίσως να έχεις αποσυνδεθεί", + "mm_e500": "Αδύνατη η αναπαραγωγή ήχου; σφάλμα 500: Έλεγξε τα logs του διακομιστή.", + "mm_e5xx": "Αδύνατη η αναπαραγωγή ήχου; σφάλμα διακομιστή", + "mm_nof": "δεν βρέθηκαν άλλα αρχεία ήχου τριγύρω", + "mm_prescan": "Αναζήτηση μουσικής για επόμενο τραγούδι...", + "mm_scank": "Βρέθηκε το επόμενο τραγούδι:", + "mm_uncache": "κρυφή μνήμη καθαρίστηκε· όλα τα τραγούδια θα ξανακατεβούν στην επόμενη αναπαραγωγή", + "mm_hnf": "το τραγούδι αυτό πλέον δεν υπάρχει", + + "im_hnf": "η εικόνα αυτή πλέον δεν υπάρχει", + + "f_empty": "αυτός ο φάκελος είναι άδειος", + "f_chide": "αυτό θα κρύψει τη στήλη «{0}»\n\nμπορείς να εμφανίσεις τις στήλες από τις ρυθμίσεις", + "f_bigtxt": "αυτό το αρχείο είναι {0} MiB σε μέγεθος — σίγουρα θέλεις να το δεις ως κείμενο;", + "f_bigtxt2": "να δεις μόνο το τέλος του αρχείου αντί για όλο; αυτό ενεργοποιεί και το following/tailing, που δείχνει νέες γραμμές που προστίθενται ζωντανά", + "fbd_more": '
εμφανίζονται {0} από {1} αρχεία; δείξε {2} ή δείξε τα όλα
', + "fbd_all": '
εμφανίζονται {0} από {1} αρχεία; δείξε όλα
', + "f_anota": "μόνο {0} από τα {1} αντικείμενα επιλέχθηκαν;\nγια να επιλέξεις ολόκληρο το φάκελο, κύλησε πρώτα μέχρι κάτω", + + "f_dls": 'οι σύνδεσμοι αρχείων στον τρέχοντα φάκελο έχουν\nμετατραπεί σε συνδέσμους λήψης', + + "f_partial": "Για να κατεβάσεις με ασφάλεια ένα αρχείο που ανεβαίνει, κλίκαρε το αρχείο με το ίδιο όνομα, αλλά χωρίς την κατάληξη .PARTIAL. Πάτα Άκυρο ή Escape για να σταματήσεις.\n\nΠάτα OK / Enter αν αγνοείς την προειδοποίηση και κατέβασε το .PARTIAL αρχείο, που σχεδόν σίγουρα θα είναι κατεστραμμένο.", + + "ft_paste": "επικόλλησε {0} αντικείμενα$NΠλήκτρο συντόμευσης: ctrl-V", + "fr_eperm": 'δεν μπορεί να μετονομαστεί:\nδεν έχεις δικαίωμα “μετακίνησης” σε αυτόν το φάκελο', + "fd_eperm": 'δεν μπορεί να διαγραφεί:\nδεν έχεις δικαίωμα “διαγραφής” σε αυτόν το φάκελο', + "fc_eperm": 'δεν μπορεί να κοπεί:\nδεν έχεις δικαίωμα “μετακίνησης” σε αυτόν το φάκελο', + "fp_eperm": 'δεν μπορεί να επικολληθεί:\nδεν έχεις δικαίωμα “εγγραφής” σε αυτόν το φάκελο', + "fr_emore": "επίλεξε τουλάχιστον ένα αντικείμενο για μετονομασία", + "fd_emore": "επίλεξε τουλάχιστον ένα αντικείμενο για διαγραφή", + "fc_emore": "επίλεξε τουλάχιστον ένα αντικείμενο για αποκοπή", + "fcp_emore": "επίλεξε τουλάχιστον ένα αντικείμενο για αντιγραφή στο πρόχειρο", + + "fs_sc": "μοιράσου το φάκελο που βρίσκεσαι", + "fs_ss": "μοιράσου τα επιλεγμένα αρχεία", + "fs_just1d": "δεν μπορείς να επιλέξεις περισσότερους από έναν φακέλους,\nή να αναμείξεις αρχεία και φακέλους στην ίδια επιλογή", + "fs_abrt": "❌ ακύρωση", + "fs_rand": "🎲 τυχαίο όνομα", + "fs_go": "✅ δημιούργησε κοινή χρήση", + "fs_name": "όνομα", + "fs_src": "πηγή", + "fs_pwd": "κωδικός", + "fs_exp": "λήξη", + "fs_tmin": "λεπτά", + "fs_thrs": "ώρες", + "fs_tdays": "ημέρες", + "fs_never": "αιώνιο", + "fs_pname": "προαιρετικό όνομα συνδέσμου; αν είναι κενό, θα είναι τυχαίο", + "fs_tsrc": "το αρχείο ή ο φάκελος προς κοινή χρήση", + "fs_ppwd": "προαιρετικός κωδικός", + "fs_w8": "δημιουργία κοινής χρήσης...", + "fs_ok": "πάτα Enter/OK για Πρόχειρο\nπάτα ESC/Άκυρο για Κλείσιμο", + + "frt_dec": "μπορεί να διορθώσει μερικές περιπτώσεις κατεστραμμένων ονομάτων αρχείων\">αποκωδικοποίηση url", + "frt_rst": "επανέφερε τα ονόματα αρχείων στα αρχικά τους\">↺ επαναφορά", + "frt_abrt": "ακύρωσε και κλείσε αυτό το παράθυρο\">❌ ακύρωση", + "frb_apply": "ΕΦΑΡΜΟΓΗ ΜΕΤΟΝΟΜΑΣΙΑΣ", + "fr_adv": "μαζική / μεταδεδομένα / μετονομασία με πρότυπα\">προχωρημένη", + "fr_case": "regex με διάκριση πεζών/κεφαλαίων\">case", + "fr_win": "ασφαλή ονόματα για windows; αντικαθιστά <>:"\\|?* με ιαπωνικούς χαρακτήρες πλήρους πλάτους\">win", + "fr_slash": "αντικαθίσταται / με χαρακτήρα που δεν δημιουργεί νέους φακέλους\">όχι /", + "fr_re": "μοτίβα αναζήτησης (regex) για αναζήτηση στα αρχικά ονόματα; τα καταγραφόμενα groups μπορούν να χρησιμοποιηθούν στο πεδίο μορφοποίησης παρακάτω όπως <code>(1)</code> και <code>(2)</code> και ούτω καθεξής", + "fr_fmt": "εμπνευσμένο από foobar2000:$N<code>(title)</code> αντικαθίσταται από τίτλο τραγουδιού,$N<code>[(artist) - ](title)</code> παραλείπει το [this] αν το artist είναι κενό$N<code>$lpad((tn),2,0)</code> γεμίζει τον αριθμό κομματιού σε 2 ψηφία", + "fr_pdel": "διαγραφή", + "fr_pnew": "αποθήκευση ως", + "fr_pname": "δώσε όνομα για τη νέα προεπιλογή", + "fr_aborted": "ακυρώθηκε", + "fr_lold": "παλιό όνομα", + "fr_lnew": "νέο όνομα", + "fr_tags": "ετικέτες για τα επιλεγμένα αρχεία (μόνο για ανάγνωση):", + "fr_busy": "μετονομασία {0} αντικειμένων...\n\n{1}", + "fr_efail": "αποτυχία μετονομασίας:\n", + "fr_nchg": "{0} από τα νέα ονόματα άλλαξαν λόγω win και/ή όχι /\n\nΕίναι ΟΚ να συνεχίσουμε με αυτά τα ονόματα;", + + "fd_ok": "διαγραφή OK", + "fd_err": "αποτυχία διαγραφής:\n", + "fd_none": "δεν διαγράφηκε τίποτα; ίσως μπλοκαρισμένο από τις ρυθμίσεις του διακομιστή (xbd);", + "fd_busy": "διαγραφή {0} αντικειμένων...\n\n{1}", + "fd_warn1": "ΔΙΑΓΡΑΦΗ αυτών των {0} αντικειμένων;", + "fd_warn2": "Τελευταία ευκαιρία! Δεν υπάρχει αναίρεση. Διαγραφή;", + + "fc_ok": "αποκοπή {0} αντικειμένων", + "fc_warn": 'αποκοπή {0} αντικειμένων\n\nαλλά: μόνο αυτή η καρτέλα browser μπορεί να τα επικολλήσει\n(λόγω πολύ μεγάλης επιλογής)', + + "fcc_ok": "αντιγράφηκαν {0} αντικείμενα στο πρόχειρο", + "fcc_warn": "αντιγράφηκαν {0} αντικείμενα στο πρόχειρο\n\nαλλά: μόνο αυτή η καρτέλα browser μπορεί να τα επικολλήσει\n(λόγω πολύ μεγάλης επιλογής)", + + "fp_apply": "χρησιμοποίησε αυτά τα ονόματα", + "fp_ecut": "πρώτα κάνε αποκοπή ή αντιγραφή κάποιων αρχείων / φακέλων για επικόλληση / μετακίνηση\n\nσημείωση: μπορείς να αποκόπτεις / επικολλάς ανάμεσα σε διαφορετικές καρτέλες browser", + "fp_ename": "τα {0} αντικείμενα δεν μπορούν να μετακινηθούν εδώ γιατί τα ονόματα υπάρχουν ήδη. Δώσε νέα ονόματα παρακάτω για να συνεχίσεις, ή άφησε κενό για να τα αγνοήσεις:", + "fcp_ename": "τα {0} αντικείμενα δεν μπορούν να αντιγραφούν εδώ γιατί τα ονόματα υπάρχουν ήδη. Δώσε νέα ονόματα παρακάτω για να συνεχίσεις, ή άφησε κενό για να τα αγνοήσεις:", + "fp_emore": "υπάρχουν ακόμα συγκρούσεις ονομάτων που πρέπει να διορθωθούν", + "fp_ok": "μετακίνηση OK", + "fcp_ok": "αντιγραφή OK", + "fp_busy": "μετακίνηση {0} αντικειμένων...\n\n{1}", + "fcp_busy": "αντιγραφή {0} αντικειμένων...\n\n{1}", + "fp_err": "αποτυχία μετακίνησης:\n", + "fcp_err": "αποτυχία αντιγραφής:\n", + "fp_confirm": "να μετακινηθούν αυτά τα {0} αντικείμενα εδώ;", + "fcp_confirm": "να αντιγραφούν αυτά τα {0} αντικείμενα εδώ;", + "fp_etab": "αποτυχία ανάγνωσης πρόχειρου από άλλη καρτέλα browser", + "fp_name": "μεταφόρτωση αρχείου από τη συσκευή σου. Δώσε του όνομα:", + "fp_both_m": '
διάλεξε τι θα επικολλήσεις
Enter = Μετακίνηση {0} αρχείων από «{1}»\nESC = Μεταφόρτωση {2} αρχείων από τη συσκευή σου', + "fcp_both_m": '
διάλεξε τι θα επικολλήσεις
Enter = Αντιγραφή {0} αρχείων από «{1}»\nESC = Μεταφόρτωση {2} αρχείων από τη συσκευή σου', + "fp_both_b": 'ΜετακίνησηΜεταφόρτωση', + "fcp_both_b": 'ΑντιγραφήΜεταφόρτωση', + + "mk_noname": "γράψε ένα όνομα στο πεδίο κειμένου αριστερά πριν το κάνεις :p", + + "tv_load": "Φόρτωση αρχείου κειμένου:\n\n{0}\n\n{1}% ({2} από {3} MiB φορτωμένα)", + "tv_xe1": "αδυναμία φόρτωσης αρχείου κειμένου:\n\nσφάλμα ", + "tv_xe2": "404, αρχείο δεν βρέθηκε", + "tv_lst": "λίστα αρχείων κειμένου σε", + "tvt_close": "επιστροφή στην προβολή φακέλου$NΣυντόμευση: M (ή Esc)\">❌ κλείσιμο", + "tvt_dl": "κατέβασε αυτό το αρχείο$NΣυντόμευση: Y\">💾 λήψη", + "tvt_prev": "προβολή προηγούμενου εγγράφου$NΣυντόμευση: i\">⬆ προηγούμενο", + "tvt_next": "προβολή επόμενου εγγράφου$NΣυντόμευση: K\">⬇ επόμενο", + "tvt_sel": "επέλεξε αρχείο   (για αποκοπή / αντιγραφή / διαγραφή / ...)$NΣυντόμευση: S\">επιλογή", + "tvt_edit": "άνοιγμα αρχείου στον επεξεργαστή κειμένου$NΣυντόμευση: E\">✏️ επεξεργασία", + "tvt_tail": "παρακολούθηση αρχείου για αλλαγές; εμφάνιση νέων γραμμών σε πραγματικό χρόνο\">📡 παρακολούθηση", + "tvt_wrap": "αναδίπλωση λέξεων\">↵", + "tvt_atail": "κλείδωμα κύλισης στο κάτω μέρος\">⚓", + "tvt_ctail": "αποκωδικοποίηση χρωμάτων τερματικού (ansi escape codes)\">🌈", + "tvt_ntail": "όριο κύλισης (πόσα bytes κειμένου να κρατούνται φορτωμένα)", + + "m3u_add1": "το τραγούδι προστέθηκε στη λίστα m3u", + "m3u_addn": "προστέθηκαν {0} τραγούδια στη λίστα m3u", + "m3u_clip": "η λίστα m3u αντιγράφηκε στο πρόχειρο\n\nπρέπει να φτιάξεις ένα νέο αρχείο κειμένου με όνομα η_λίστα_μου.m3u και να επικολλήσεις τη λίστα μέσα· αυτό θα το καταστήσει αναπαράξιμο", + + "gt_vau": "μην δείχνεις το βίντεο, παίξε μόνο τον ήχο\">🎧", + "gt_msel": "ενεργοποίηση επιλογής αρχείων; ctrl-κλικ σε αρχείο για παράκαμψη$N$N<em>όταν είναι ενεργό: διπλό κλικ σε αρχείο / φάκελο το ανοίγει</em>$N$NΣυντόμευση: S\">πολλαπλή επιλογή", + "gt_crop": "κεντραρισμένη περικοπή μικρογραφιών\">περικοπή", + "gt_3x": "μικρογραφίες υψηλής ανάλυσης\">3x", + "gt_zoom": "ζουμ", + "gt_chop": "κόψε", + "gt_sort": "ταξινόμηση κατά", + "gt_name": "όνομα", + "gt_sz": "μέγεθος", + "gt_ts": "ημερομηνία", + "gt_ext": "τύπος", + "gt_c1": "μεγαλύτερη περικοπή ονομάτων αρχείων (δείξε λιγότερα)", + "gt_c2": "μικρότερη περικοπή ονομάτων αρχείων (δείξε περισσότερα)", + + "sm_w8": "αναζήτηση...", + "sm_prev": "τα παρακάτω αποτελέσματα αναζήτησης προέρχονται από προηγούμενη αναζήτηση:\n ", + "sl_close": "κλείσιμο αποτελεσμάτων αναζήτησης", + "sl_hits": "εμφανίζονται {0} αποτελέσματα", + "sl_moar": "φόρτωσε περισσότερα", + + "s_sz": "μέγεθος", + "s_dt": "ημερομηνία", + "s_rd": "μονοπάτι", + "s_fn": "όνομα", + "s_ta": "ετικέτες", + "s_ua": "ανέβηκε@", + "s_ad": "προχωρ.", + "s_s1": "ελάχιστο σε MiB", + "s_s2": "μέγιστο σε MiB", + "s_d1": "ελάχιστο iso8601", + "s_d2": "μέγιστο iso8601", + "s_u1": "μεταφορτώθηκε αργότερα", + "s_u2": "και/ή πριν", + "s_r1": "το μονοπάτι περιέχει   (χωρισμένα με κενό)", + "s_f1": "το όνομα περιέχει   (άρνηση με -nope)", + "s_t1": "οι ετικέτες περιέχουν   (^=αρχή, τέλος=$)", + "s_a1": "συγκεκριμένες ιδιότητες μεταδεδομένων", + + "md_eshow": "δεν μπορεί να εμφανιστεί ", + "md_off": "[📜readme] απενεργοποιημένο στο [⚙️] -- κρυμμένο έγγραφο", + + "badreply": "Αποτυχία ανάλυσης απάντησης από το διακομιστή", + + "xhr403": "403: Πρόσβαση αρνήθηκε\n\nδοκίμασε το F5, ίσως αποσυνδέθηκες", + "xhr0": "άγνωστο (πιθανόν αποσύνδεση από το διακομιστή ή ο διακομιστής είναι εκτός σύνδεσης)", + "cf_ok": "συγγνώμη γι' αυτό -- η προστασία DD" + wah + "oS ενεργοποιήθηκε\n\nοι διαδικασίες θα συνεχιστούν σε περίπου 30 δευτερόλεπτα\n\nαν δεν γίνει τίποτα, πάτα F5 για επαναφόρτωση", + "tl_xe1": "αδύνατη η λίστα υποφακέλων:\n\nσφάλμα ", + "tl_xe2": "404: Ο φάκελος δεν βρέθηκε", + "fl_xe1": "αδύνατη η λίστα αρχείων σε φάκελο:\n\nσφάλμα ", + "fl_xe2": "404: Ο φάκελος δεν βρέθηκε", + "fd_xe1": "αδύνατη η δημιουργία υποφακέλου:\n\nσφάλμα ", + "fd_xe2": "404: Ο γονικός φάκελος δεν βρέθηκε", + "fsm_xe1": "αδύνατη η αποστολή μηνύματος:\n\nσφάλμα ", + "fsm_xe2": "404: Ο γονικός φάκελος δεν βρέθηκε", + "fu_xe1": "αποτυχία φόρτωσης λίστας unpost από το διακομιστή:\n\nσφάλμα ", + "fu_xe2": "404: Το αρχείο δεν βρέθηκε??", + + "fz_tar": "μη συμπιεσμένο αρχείο gnu-tar (linux / mac)", + "fz_pax": "μη συμπιεσμένο pax-format tar (πιο αργό)", + "fz_targz": "gnu-tar με συμπίεση gzip επίπεδο 3$N$Nσυνήθως πολύ αργό, οπότε$Nχρησιμοποίησε καλύτερα μη συμπιεσμένο tar", + "fz_tarxz": "gnu-tar με συμπίεση xz επίπεδο 1$N$Nσυνήθως πολύ αργό, οπότε$Nχρησιμοποίησε καλύτερα μη συμπιεσμένο tar", + "fz_zip8": "zip με ονόματα αρχείων utf8 (ίσως να κολλάει σε windows 7 και παλιότερα)", + "fz_zipd": "zip με παραδοσιακά ονόματα cp437, για πολύ παλιό λογισμικό", + "fz_zipc": "cp437 με crc32 υπολογισμένο νωρίτερα,$Nγια MS-DOS PKZIP v2.04g (οκτώβριος 1993)$N(παίρνει παραπάνω χρόνο πριν ξεκινήσει η μεταφόρτωση)", + + "un_m1": "μπορείς να διαγράψεις τα πρόσφατα αρχεία που μεταφόρτωσες (ή να ακυρώσεις τα μισοτελειωμένα) παρακάτω", + "un_upd": "ανανέωση", + "un_m4": "ή μοιράσου τα αρχεία που βλέπεις παρακάτω:", + "un_ulist": "εμφάνιση", + "un_ucopy": "αντιγραφή", + "un_flt": "προαιρετικό φίλτρο:  η διεύθυνση πρέπει να περιέχει", + "un_fclr": "καθαρισμός φίλτρου", + "un_derr": "αποτυχία διαγραφής unpost:\n", + "un_f5": "κάτι χάλασε, δοκίμασε την ανανέωση ή πάτα F5", + "un_uf5": "συγγνώμη αλλά πρέπει να ανανεώσεις τη σελίδα (πχ με F5 ή CTRL-R) πριν ακυρώσεις αυτήν την αποστολή", + "un_nou": "προσοχή: ο διακομιστής είναι πολύ φορτωμένος για να δείξει μισοτελειωμένες αποστολές· πάτα την ανανέωση, σε λίγο", + "un_noc": "προσοχή: το unpost των ολοκληρωμένων αρχείων δεν επιτρέπεται από τη ρύθμιση του διακομιστή", + "un_max": "εμφανίζονται τα πρώτα 2000 αρχεία (χρησιμοποίησε φίλτρο)", + "un_avail": "μπορείς να διαγράψεις {0} πρόσφατα αρχεία
μπορείς να ακυρώσεις {1} μισοτελειωμένες αποστολές", + "un_m2": "ταξινομημένα κατά χρόνο μεταφόρτωσης; τα πιο πρόσφατα πρώτα:", + "un_no1": "άκυρο! καμία μεταφόρτωση δεν είναι αρκετά πρόσφατη", + "un_no2": "άκυρο! καμία μεταφόρτωση με αυτό το φίλτρο δεν είναι αρκετά πρόσφατη", + "un_next": "διάγραψε τα επόμενα {0} αρχεία παρακάτω", + "un_abrt": "άκυρο", + "un_del": "διαγραφή", + "un_m3": "φορτώνω τις πρόσφατες μεταφορτώσεις σου...", + "un_busy": "διαγράφω {0} αρχεία...", + "un_clip": "αντιγράφηκαν {0} σύνδεσμοι στο πρόχειρο", + + "u_https1": "πρέπει", + "u_https2": "μετάβαση σε https", + "u_https3": "για καλύτερη απόδοση", + "u_ancient": 'ο browser σου είναι εντυπωσιακά απαρχαιωμένος — ίσως να χρησιμοποιήσεις το bup αντί γι\' αυτό', + "u_nowork": "χρειάζεται firefox 53+ ή chrome 57+ ή iOS 11+", + "tail_2old": "χρειάζεται firefox 105+ ή chrome 71+ ή iOS 14.5+", + "u_nodrop": "ο browser σου είναι πολύ παλιός για drag&drop μεταφορτώσεις", + "u_notdir": "αυτός δεν είναι φάκελος!\n\nο browser σου είναι πολύ παλιός,\nδοκίμασε drag&drop αντ' αυτού", + "u_uri": "για να κάνεις drag&drop εικόνων από άλλα παράθυρα browser,\nρίξ' τες πάνω στο μεγάλο κουμπί μεταφόρτωσης", + "u_enpot": 'άλλαξε στο potato UI (ίσως ανεβάζει πιο γρήγορα)', + "u_depot": 'άλλαξε στο fancy UI (ίσως ανεβάζει πιο αργά)', + "u_gotpot": "αλλάζω στο potato UI για πιο γρήγορη μεταφόρτωση,\n\nμπορείς να το αλλάξεις πάλι αν θες!", + "u_pott": "

αρχεία:   {0} ολοκληρωμένα,   {1} αποτυχημένα,   {2} σε εξέλιξη,   {3} σε ουρά

", + "u_ever": "αυτός είναι ο βασικός uploader; το up2k θέλει τουλάχιστον
chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1", + "u_su2k": 'αυτός είναι ο βασικός uploader; το up2k είναι καλύτερο', + "u_uput": "βελτιστοποίηση για ταχύτητα (παράλειψη ελέγχου ακεραιότητας)", + "u_ewrite": "δεν έχεις δικαίωμα εγγραφής σε αυτόν τον φάκελο", + "u_eread": "δεν έχεις δικαίωμα ανάγνωσης σε αυτόν τον φάκελο", + "u_enoi": "η αναζήτηση αρχείων δεν είναι ενεργοποιημένη στο αρχείο ρυθμίσεων του διακομιστή", + "u_enoow": "δεν μπορείς να κάνεις αντικατάσταση εδώ· χρειάζεται δικαίωμα Διαγραφής", + "u_badf": "Αυτά τα {0} αρχεία (από {1} συνολικά) παραλείφθηκαν, πιθανώς λόγω δικαιωμάτων συστήματος αρχείων:\n\n", + "u_blankf": "Αυτά τα {0} αρχεία (από {1} συνολικά) είναι άδεια / κενά· να τα μεταφορτώσω έτσι κι αλλιώς;\n\n", + "u_applef": "Αυτά τα {0} αρχεία (από {1} συνολικά) πιθανώς δεν είναι επιθυμητά;\nΠάτα OK/Enter για ΝΑ ΑΓΝΟΗΘΟΥΝ τα παρακάτω αρχεία,\nΠάτα Cancel/ESC για ΝΑ ΜΗΝ ΑΠΟΚΛΕΙΣΤΟΥΝ και να ΜΕΤΑΦΟΡΤΩΘΟΎΝ κι αυτά:\n\n", + "u_just1": "\nΊσως δουλέψει καλύτερα αν επιλέξεις μόνο ένα αρχείο", + "u_ff_many": "αν χρησιμοποιείς Linux / MacOS / Android, τότε τόσα αρχεία μπορεί να κατάρρευση του Firefox!\nαν γίνει αυτό, δοκίμασε ξανά (ή χρησιμοποίησε τον Chrome).", + "u_up_life": "Αυτή η μεταφόρτωση θα διαγραφεί από το διακομιστή\n{0} μετά την ολοκλήρωσή της", + "u_asku": "μεταφόρτωση αυτών των {0} αρχείων στο {1}", + "u_unpt": "μπορείς να αναιρέσεις / διαγράψεις αυτήν τη μεταφόρτωση χρησιμοποιώντας το 🧯, πάνω αριστερά", + "u_bigtab": "θα εμφανιστούν {0} αρχεία\n\nαυτό μπορεί να κάνει τον browser σου να κολλήσει, είσαι σίγουρος;", + "u_scan": "Σάρωση αρχείων...", + "u_dirstuck": "ο επεξεργαστής φακέλων κόλλησε προσπαθώντας να προσπελάσει τα εξής {0} αντικείμενα· θα τα παραλείψει:", + "u_etadone": "Ολοκληρώθηκε ({0}, {1} αρχεία)", + "u_etaprep": "(προετοιμασία για μεταφόρτωση)", + "u_hashdone": "το hashing ολοκληρώθηκε", + "u_hashing": "υπολογισμός hash", + "u_hs": "handshaking...", + "u_started": "τα αρχεία ανεβαίνουν τώρα· δες τα στο [🚀]", + "u_dupdefer": "διπλότυπο; θα επεξεργαστεί μετά από όλα τα άλλα αρχεία", + "u_actx": "πάτα αυτό το κείμενο για να μην χάσεις
απόδοση όταν αλλάζεις παράθυρα/καρτέλες", + "u_fixed": "ΟΚ!  Το διόρθωσα 👍", + "u_cuerr": "αποτυχία μεταφόρτωσης τμήματοςς {0} από {1};\nπιθανώς ακίνδυνο, συνεχίζω\n\nαρχείο: {2}", + "u_cuerr2": "ο διακομιστής απέρριψε τη μεταφόρτωση (τμήμα {0} από {1});\nθα ξαναδοκιμάσει αργότερα\n\nαρχείο: {2}\n\nσφάλμα ", + "u_ehstmp": "θα ξαναδοκιμάσει; δες κάτω δεξιά", + "u_ehsfin": "ο διακομιστής απέρριψε το αίτημα ολοκλήρωσης της μεταφόρτωσης; ξαναδοκιμάζει...", + "u_ehssrch": "ο διακομιστής απέρριψε το αίτημα αναζήτησης; ξαναδοκιμάζει...", + "u_ehsinit": "ο διακομιστής απέρριψε το αίτημα για εκκίνηση μεταφόρτωσης; ξαναδοκιμάζει...", + "u_eneths": "σφάλμα δικτύου κατά το handshake μεταφόρτωσης; ξαναδοκιμάζει...", + "u_enethd": "σφάλμα δικτύου κατά τον έλεγχο ύπαρξης στόχου; ξαναδοκιμάζει...", + "u_cbusy": "ο διακομιστής περιμένει να μας εμπιστευτεί ξανά μετά από πρόβλημα δικτύου...", + "u_ehsdf": "ο διακομιστής έμεινε από χώρο στο δίσκο!\n\nθα συνεχίσει να ξαναδοκιμάζει,\nσε περίπτωση που κάποιος\nελευθερώσει αρκετό χώρο για συνέχεια", + "u_emtleak1": "φαίνεται πως ο browser σου έχει διαρροή μνήμης;\nπαρακαλώ", + "u_emtleak2": ' αλλαγή σε https (συνιστάται) ή ', + "u_emtleak3": ' ', + "u_emtleakc": 'δοκίμασε τα εξής:\nΟι μεταφορτώσιες θα είναι λίγο πιο αργές, αλλά ok.\nΣυγγνώμη για την ταλαιπωρία!\n\nPS: το chrome v107 έχει διόρθωση γι\' αυτό', + "u_emtleakf": 'δοκίμασε τα εξής:\n\nPS: ο firefox ελπίζει να φτιάξει αυτό το bug κάποια στιγμή', + "u_s404": "δεν βρέθηκε στο διακομιστή", + "u_expl": "επεξήγηση", + "u_maxconn": "οι περισσότεροι browser το περιορίζουν στα 6, αλλά ο firefox σου επιτρέπει να το αυξήσεις με connections-per-server στο about:config", + "u_tu": '

ΠΡΟΕΙΔΟΠΟΙΗΣΗ: το turbo είναι ενεργοποιημένο,  το πρόγραμμα πελάτη ίσως να μην ανιχνεύσει και να μην ξαναεκκινήσει μισοτελειωμένες μεταφορτώσεις; δες τα tooltip του κουμπιού turbo

', + "u_ts": '

ΠΡΟΕΙΔΟΠΟΙΗΣΗ: το turbo είναι ενεργοποιημένο,  τα αποτελέσματα αναζήτησης μπορεί να είναι λάθος; δες τα tooltip του κουμπιού turbo

', + "u_turbo_c": "το turbo είναι απενεργοποιημένο στο αρχείο ρυθμίσεων του διακομιστή", + "u_turbo_g": "απενεργοποιώ το turbo επειδή δεν έχεις δικαίωμα\nγια τη λίστα φακέλων σε αυτόν τον τόμο", + "u_life_cfg": 'αυτόματη διαγραφή μετά από λεπτά (ή ώρες)', + "u_life_est": 'η μεταφόρτωση θα διαγραφεί ---', + "u_life_max": 'αυτός ο φάκελος επιβάλλει\nμέγιστη διάρκεια ζωής {0}', + "u_unp_ok": "επιτρέπεται το unpost για {0}", + "u_unp_ng": "δεν επιτρέπεται το unpost", + "ue_ro": "έχεις μόνο δικαίωμα ανάγνωσης σε αυτόν το φάκελο\n\n", + "ue_nl": "δεν είσαι συνδεδεμένος τώρα", + "ue_la": 'είσαι συνδεδεμένος ως "{0}"', + "ue_sr": "είσαι σε λειτουργία αναζήτησης αρχείων\n\nπήγαινε σε λειτουργία μεταφόρτωσης πατώντας το 🔎 (δίπλα στο μεγάλο κουμπί ΑΝΑΖΗΤΗΣΗΣ) και δοκίμασε πάλι\n\nσυγγνώμη", + "ue_ta": "δοκίμασε να μεταφορτώσεις εκ νέου, θα πρέπει να δουλέψει τώρα", + "ue_ab": "αυτό το αρχείο ανεβαίνει σε άλλο φάκελο και η μεταφόρτωση πρέπει να ολοκληρωθεί πριν ανέβει αλλού.\n\nΜπορείς να ακυρώσεις και να ξεχάσεις την αρχική μεταφόρτωση με το κουμπί 🧯 πάνω αριστερά", + "ur_1uo": "ΟΚ: Το αρχείο ανέβηκε επιτυχώς", + "ur_auo": "ΟΚ: Και τα {0} αρχεία ανέβηκαν επιτυχώς", + "ur_1so": "ΟΚ: Το αρχείο βρέθηκε στο διακομιστή", + "ur_aso": "ΟΚ: Και τα {0} αρχεία βρέθηκαν στο διακομιστή", + "ur_1un": "Η μεταφόρτωση απέτυχε, συγγνώμη", + "ur_aun": "Και οι {0} μεταφορτώσεις απέτυχαν, συγγνώμη", + "ur_1sn": "Το αρχείο ΔΕΝ βρέθηκε στο διακομιστή", + "ur_asn": "Τα {0} αρχεία ΔΕΝ βρέθηκαν στο διακομιστή", + "ur_um": "Ολοκληρώθηκε;\n{0} μεταφορτώσεις είναι OK,\n{1} μεταφορτώσεις απέτυχαν, συγγνώμη", + "ur_sm": "Ολοκληρώθηκε;\n{0} αρχεία βρέθηκαν στο διακομιστή,\n{1} αρχεία ΔΕΝ βρέθηκαν στο διακομιστή", + + "lang_set": "ανανέωση σελίδας για εφαρμογή της αλλαγής;" + }, "ita": { "tt": "Italiano", @@ -6931,7 +7560,7 @@ var Ls = { }, }; -var LANGS = ["eng", "nor", "chi", "cze", "deu", "fin", "ita", "nld", "rus", "spa", "ukr"]; +var LANGS = ["eng", "nor", "chi", "cze", "deu", "fin", "grc", "ita", "nld", "rus", "spa", "ukr"]; if (window.langmod) langmod(); @@ -7155,13 +7784,12 @@ ebi('op_cfg').innerHTML = ( '\n' + '
\n' + '

' + L.cl_themes + '

\n' + - '
\n' + + '
\n' + '
\n' + '
\n' + '
\n' + '

' + L.cl_langs + '

\n' + - '
\n' + - '
\n' + + '
\n' + '
\n' + (have_zip ? ( '

' + L.cl_ziptype + '

\n' @@ -7208,7 +7836,7 @@ ebi('op_cfg').innerHTML = ( ' \n' + ' \n' + '\n' + - '

' + L.cl_keytype + '

\n' + + '

' + L.cl_keytype + '

\n' + '

' + L.cl_hiddenc + '  ' + (MOBILE ? '' + L.cl_hidec + ' / ' : '') + '' + L.cl_reset + '

' ); @@ -7375,6 +8003,10 @@ var ACtx = !IPHONE && (window.AudioContext || window.webkitAudioContext), dk, mp; +if (location.pathname.indexOf('//') === 0) + hist_replace(location.pathname.replace(/^\/+/, '/')); + + if (window.og_fn) { hash0 = 1; hist_replace(vsplit(get_evpath())[0]); @@ -12590,7 +13222,7 @@ function ev_load_m3u(e) { function () { load_m3u(url); }, function () { if (has(perms, 'write') && has(perms, 'delete')) - window.location = url + '?edit'; + location = url + '?edit'; else showfile.show(url); } @@ -13189,7 +13821,7 @@ var treectl = (function () { r.reqls = function (url, hpush, back, hydrate) { if (IE && !history.pushState) - return window.location = url; + return location = url; var xhr = new XHR(), m = /[?&](k=[^&#]+)/.exec(url), @@ -13205,6 +13837,7 @@ var treectl = (function () { xhr.hydrate = hydrate; xhr.ts = Date.now(); xhr.open('GET', xhr.top + '?ls' + uq, true); + xhr.setRequestHeader('Fnugg', '' + xhr.ts); xhr.onload = xhr.onerror = recvls; xhr.send(); @@ -13270,6 +13903,9 @@ var treectl = (function () { if (r.chk_index_html(this.top, res)) return; + if (this.ts != res.fnugg && res.fnugg != 'nei' && sread('no_fnugg') !== '1') + toast.warn(60, "WARNING: A proxy/CDN between your webbrowser and the server is misbehaving, and caching responses it shouldn't. As a result, you are now seeing stale directory listings. There will be many issues.\n\nIf you need to ignore this and stop these messages, you can set the global-option 'no-fnugg' on the server, or click π and run this: STG.no_fnugg=1"); + for (var a = 0; a < res.files.length; a++) if (res.files[a].tags === undefined) res.files[a].tags = {}; @@ -14109,25 +14745,21 @@ var mukey = (function () { defnot = 'rekobo_alnum'; var map = {}, - html = []; + html = [], + cb = ebi('key_notation'); for (var k in maps) { if (!maps.hasOwnProperty(k)) continue; - html.push( - '' + - ''); - + html.push(''.format(k)); for (var a = 0; a < 24; a++) maps[k][a] = maps[k][a].trim(); } - ebi('key_notation').innerHTML = html.join('\n'); + cb.innerHTML = html.join(''); - function set_key_notation(e) { - ev(e); - var notation = this.getAttribute('value'); - load_notation(notation); + function set_key_notation() { + load_notation(cb.value); try_render(); } @@ -14184,14 +14816,10 @@ var mukey = (function () { if (!maps[notation]) notation = defnot; - ebi('key_' + notation).checked = true; + cb.value = notation; + cb.onchange = set_key_notation; load_notation(notation); - var o = QSA('#key_notation input'); - for (var a = 0; a < o.length; a++) { - o[a].onchange = set_key_notation; - } - return { "render": try_render }; @@ -14230,17 +14858,17 @@ var settheme = (function () { showfile.setstyle(); bchrome(); - var html = [], itheme = ax.indexOf(theme[0]) * 2 + (light ? 1 : 0), + var html = [], + cb = ebi('themes'), + itheme = ax.indexOf(theme[0]) * 2 + (light ? 1 : 0), names = ['classic dark', 'classic light', 'pm-monokai', 'flat light', 'vice', 'hotdog stand', 'hacker', 'hi-con']; for (var a = 0; a < themes; a++) - html.push('' + a + ''); + html.push(''.format(a, names[a] || 'custom')); ebi('themes').innerHTML = html.join(''); - var btns = QSA('#themes a'); - for (var a = 0; a < themes; a++) - btns[a].onclick = r.go; + cb.value = itheme; + cb.onchange = r.onsel; if (chldr) { var x = r.ldr[itheme] || [tre]; @@ -14249,12 +14877,13 @@ var settheme = (function () { } bcfg_set('light', light); - tt.att(ebi('themes')); } - r.go = function (e) { - var i = e; - try { ev(e); i = e.target.textContent; } catch (ex) { } + r.onsel = function () { + r.go(parseInt(ebi('themes').value)); + }; + + r.go = function (i) { light = i % 2 == 1; var c = ax[Math.floor(i / 2)], l = light ? 'y' : 'z'; @@ -14262,7 +14891,7 @@ var settheme = (function () { themen = c + l; swrite('cpp_thm', theme); freshen(); - } + }; freshen(); return r; @@ -14272,26 +14901,25 @@ var settheme = (function () { (function () { function freshen() { lang = sread("cpp_lang", LANGS) || lang; - var k, html = []; + var k, cb = ebi('langs'), html = []; for (var a = 0; a < LANGS.length; a++) { k = LANGS[a]; - html.push('' + k + ''); + html.push(''.format(k, Ls[k].tt)); } - ebi('langs').innerHTML = html.join(''); - var btns = QSA('#langs a'); - for (var a = 0, aa = btns.length; a < aa; a++) - btns[a].onclick = setlang; + cb.innerHTML = html.join(''); + cb.onchange = setlang; + cb.value = lang; } function setlang(e) { ev(e); var t = L.lang_set; - L = Ls[this.textContent]; - swrite("cpp_lang", this.textContent); + lang = ebi('langs').value; + L = Ls[lang]; + swrite("cpp_lang", lang); freshen(); modal.confirm(L.lang_set + "\n\n" + t, location.reload.bind(location), null); - }; + } freshen(); })(); diff --git a/copyparty/web/md2.js b/copyparty/web/md2.js index f7d0b59c..26b1e14b 100644 --- a/copyparty/web/md2.js +++ b/copyparty/web/md2.js @@ -255,7 +255,7 @@ function Modpoll() { } console.log('modpoll...'); - var url = (document.location + '').split('?')[0] + '?_=' + Date.now(); + var url = (location + '').split('?')[0] + '?_=' + Date.now(); var xhr = new XHR(); xhr.open('GET', url, true); xhr.responseType = 'text'; @@ -346,7 +346,7 @@ function save(e) { fd.append("lastmod", (force ? -1 : last_modified)); fd.append("body", txt); - var url = (document.location + '').split('?')[0]; + var url = (location + '').split('?')[0]; var xhr = new XHR(); xhr.open('POST', url, true); xhr.responseType = 'text'; @@ -404,7 +404,7 @@ function save_cb() { function run_savechk(lastmod, txt, btn, ntry) { // 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(); xhr.open('GET', url, true); xhr.responseType = 'text'; diff --git a/copyparty/web/mde.js b/copyparty/web/mde.js index 5c2872df..058825c9 100644 --- a/copyparty/web/mde.js +++ b/copyparty/web/mde.js @@ -6,7 +6,7 @@ var dom_doc = ebi('m'); var dom_md = ebi('mt'); (function () { - var n = document.location + ''; + var n = location + ''; n = (n.slice(n.indexOf('//') + 2).split('?')[0] + '?v').split('/'); n[0] = 'top'; var loc = []; @@ -113,7 +113,7 @@ function save(mde) { fd.append("lastmod", (force ? -1 : last_modified)); fd.append("body", txt); - var url = (document.location + '').split('?')[0]; + var url = (location + '').split('?')[0]; var xhr = new XHR(); xhr.open('POST', url, true); xhr.responseType = 'text'; @@ -166,7 +166,7 @@ function save_cb() { //alert('save OK -- wrote ' + r.size + ' bytes.\n\nsha512: ' + r.sha512); // 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(); xhr.open('GET', url, true); xhr.responseType = 'text'; diff --git a/copyparty/web/rups.html b/copyparty/web/rups.html index af90eefe..2cc0443a 100644 --- a/copyparty/web/rups.html +++ b/copyparty/web/rups.html @@ -19,14 +19,7 @@ control-panel   Filter:   - - - - - - - -
sizewhowhenagedirfile
+
π # hint; default is unset + + # if a folder contains index.html, show that instead of the directory listing by default (can be changed in the client settings UI, or add ?v to URL for override) + ih + + # file extensions to present as plaintext + textfiles: txt,nfo,diz,cue,readme # default + + # max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS) + txt-max: 64 # default + + # title / service-name to show in html documents + doctitle: copyparty @ --name # default (--name will copy from global-option `name`) + + # server name (displayed in filebrowser document title) + bname: --name # default (copy global-option `name`) + + # powered-by link; disable with -nb + pb-url: https://github.com/9001/copyparty # default + + # show version on the control panel (incompatible with -nb) + ver + + # configure the option to enable/disable k304 on the controlpanel (workaround for buggy reverse-proxies); [0] = hidden and default-off, [1] = visible and default-off, [2] = visible and default-on + k304: 0 # default + + # configure the option to enable/disable no304 on the controlpanel (workaround for buggy caching in browsers); [0] = hidden and default-off, [1] = visible and default-off, [2] = visible and default-on + no304: 0 # default + + # 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 + md-sbf: downloads forms popups scripts top-navigation-by-user-activation # default + + # list of capabilities to allow in the iframe 'sandbox' attribute for prologue/epilogue docs + # 📂 also available as volflag "lg_sbf" + lg-sbf: downloads forms popups scripts top-navigation-by-user-activation # default + + # the value of the iframe 'allow' attribute for README.md docs, for example [fullscreen] + # 📂 also available as volflag "md_sba" + md-sba: fullscreen # hint; default is blank + + # the value of the iframe 'allow' attribute for prologue/epilogue docs (volflag=lg_sba); see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy#iframes + lg-sba: TXT # placeholder + + # don't sandbox README/PREADME.md documents (volflags: no_sb_md | sb_md) + no-sb-md + + # don't sandbox prologue/epilogue docs (volflags: no_sb_lg | sb_lg); enables non-js support + no-sb-lg + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// debug options \\000000000000000000000000000000000000000000000000000000000000000000000000\ + + # verbose config file parser (explain config) + vc + + # generate config file from current config (best-effort; probably buggy) + cgen + + # list information about detected optional dependencies + deps + + # kernel-bug workaround: disable poll; use select instead (limits max num clients to ~700) + no-poll + + # kernel-bug workaround: disable sendfile; do a safe and slow read-send-loop instead + no-sendfile + + # kernel-bug workaround: disable scandir; do a listdir + stat on each file instead + no-scandir + + # wait for initial filesystem indexing before accepting client requests + no-fastboot + + # disable httpserver threadpool, create threads as-needed instead + no-htp + + # when listening on unix-sockets, do a basic delete+bind instead of the default atomic bind + rm-sck + + # explain search processing, and do some extra expensive sanity checks + srch-dbg + + # use mdns-domain instead of server-ip on /?hc + rclone-mdns + + # write stacktrace to Path every S second, for example --stackmon=./st/%Y-%m/%d/%H%M.xz,60 + stackmon: P,S # placeholder + + # list active threads every SEC + log-thrs: 0 # default + + # log filekey params for files where path matches REGEX; [.] (a single dot) = all files + log-fk: /mnt/a-problematic-fuse/this-folder-breaks-filekeys/ # hint; default is unset + + # [up2k] if a client uploads a bitflipped/corrupted chunk, store a copy according to --bf-nc and --bf-dir + bak-flips + + # bak-flips: stop if there's more than NUM files at --kf-dir already; default: 6.3 GiB max (200*32M) + bf-nc: 200 # default + + # bak-flips: store corrupted chunks at PATH; default: folder named 'bf' wherever copyparty was started + bf-dir: /srv/bitflips/ # hint; default = bf + + # bak-flips: log corruption info to a textfile at PATH + bf-log: /srv/bitflips/the-history.txt # hint; default is unset + + ############################################################################################### + ############################################################################################### + ############################################################################################### + ############################################################################################### + ############################################################################################### + ############################################################################################### + #####/ + ##### This is the end of the [global] config section + ####/ + +[accounts] + foo: bar # username foo, password bar + +[groups] + g1: u1, u2, u3 # group "g1" with users u1, u2, and u3 + +[/the/url/to/share/this/volume/on/] + /the/actual/filesystem/path/ + accs: + r: username_who_gets_Read_access + w: username_who_gets_Write_access + m: username_who_gets_Move_access + d: username_who_gets_Delete_access + .: username_who_can_see_Dotfiles + g: username_who_gets_Get_access + G: username_who_gets_upGet_access + h: username_who_gets_html_access + a: username_who_gets_admin_access + A: username_who_gets_ReadWriteMoveDeleteDotfileAdmin_access + rwm: @g1 # the group "g1" gets Read+Write+Move + flags: + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// uploads, general \\000000000000000000000000000000000000000000000000000000000000000000000\ + + # enable symlink-based file deduplication + dedup + + # enable hardlink-based file deduplication, with fallback on symlinks when that is impossible + hardlink + + # dedup with hardlink only, never symlink; make a full copy if hardlink is impossible + hardlinkonly + + # enable reflink-based file deduplication, with fallback on full copy when that is impossible + reflink + + # verify on-disk data before using it for dedup + safededup + + # take dupe data from clients, even if available on HDD + noclone + + # rejects existing files (instead of linking/cloning them) + nodupe + + # unix-permission for new dirs/folders + chmod_d: 755 + + # unix-permission for new files + chmod_f: 644 + + # change owner of new files/folders to unix-user 573 + uid: 573 + + # change owner of new files/folders to unix-group 999 + gid: 999 + + # force use of sparse files, mainly for s3-backed storage + sparse + + # deny use of sparse files, mainly for slow storage + nosparse + + # enable full WebDAV write support (dangerous); PUT-operations will now OVERWRITE existing files + daw + + # forces all uploads into the top folder of the vfs + nosub + + # enables filetype detection for nameless uploads + magic + + # fallback filename for nameless uploads + put_name + + # default checksum-hasher for PUT/WebDAV uploads + put_ck + + # default checksum-hasher for bup/basic uploads + bup_ck + + # allows server-side gzip compression of uploads with ?gz + gz + + # allows server-side lzma compression of uploads with ?xz + xz + + # forces server-side compression, optional arg: xz,9 + pk + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// upload rules \\0000000000000000000000000000000000000000000000000000000000000000000000000\ + + # max 250 uploads over 15min + maxn: 250,600 + + # max 1 GiB over 5min (suffixes: b, k, m, g, t) + maxb: 1g,300 + + # total volume size max 1 GiB (suffixes: b, k, m, g, t) + vmaxb: 1g + + # max 4096 files in volume (suffixes: b, k, m, g, t) + vmaxn: 4k + + # return medialinks for non-up2k uploads (not hotlinks) + medialinks + + # write-only users can upload logues without getting renamed + wo_up_readme + + # force randomized filenames, 9 chars long by default + rand + + # randomized filenames are N chars long + nrand: N + + # overwrite existing files? 0=no 1=if-older 2=always + u2ow: N + + # [f]orce [c]lient-last-modified or [u]pload-time + u2ts: fc + + # allow aborting unfinished uploads? 0=no 1=strict 2=ip-chk 3=acct-chk + u2abort: 1 + + # allow filesizes between 1 KiB and 3MiB + sz: 1k-3m + + # ensure 1 GiB free disk space + df: 1g + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// upload rotation -- (moves all uploads into the specified folder structure) \\00000000000\ + + # 3 levels of subfolders with 100 entries in each + rotn: 100,3 + + # date-formatted organizing + rotf: %Y-%m/%d-%H + + # uploads are deleted after 1 hour + lifetime: 3600 + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// database, general \\00000000000000000000000000000000000000000000000000000000000000000000\ + + # enable database; makes files searchable + enables upload-undo + e2d + + # scan writable folders for new files on startup; also sets -e2d + e2ds + + # scans all folders for new files on startup; also sets -e2d + e2dsa + + # enable multimedia indexing; makes it possible to search for tags + e2t + + # scan existing files for tags on startup; also sets -e2t + e2ts + + # delete all metadata from DB (full rescan); also sets -e2ts + e2tsr + + # disables metadata collection for existing files + d2ts + + # verify integrity on startup by hashing files and comparing to db + e2v + + # when e2v fails, update the db (assume on-disk files are good) + e2vu + + # when e2v fails, panic and quit copyparty + e2vp + + # disables onboot indexing, overrides -e2ds* + d2ds + + # disables metadata collection, overrides -e2t* + d2t + + # disables file verification, overrides -e2v* + d2v + + # disables all database stuff, overrides -e2* + d2d + + # puts thumbnails and indexes at that location + hist: /tmp/cdb + + # puts indexes at that location + dbpath: /tmp/cdb + + # disable db if file foo doesn't exist + landmark: foo + + # scan for new files every 60sec, same as --re-maxage + scan: 60 + + # skips hashing file contents if path matches *.iso + nohash: \.iso$ + + # fully ignores the contents at paths matching *.iso + noidx: \.iso$ + + # don't forget files when deleted from disk + noforget + + # forget uploader-IP after 30 days (GDPR) + forget_ip: 43200 + + # never store uploader-IP in the db; disables unpost + no_db_ip + + # avoid excessive reindexing on android sdcardfs + fat32 + + # database speed-durability tradeoff + dbd: [acid|swal|wal|yolo] + + # cross-volume dupe detection / linking (dangerous) + xlink + + # do not descend into other filesystems + xdev + + # do not follow symlinks leaving the volume root + xvol + + # show dotfiles in search results + dotsrch + + # hide dotfiles in search results (default) + nodotsrch + + # exclude search results with URL matching this regex + srch_excl + + # media-tags to index/display + mte: artist,title + + # media-tags to hide by default + mth: fmt,res,ac + + # uses the "audio-bpm.py" program to generate ".bpm" tags from uploads (f = overwrite tags) + mtp: .bpm=f,audio-bpm.py + + # collects two tags at once + mtp: ahash,vhash=media-hash.py + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// thumbnails \\000000000000000000000000000000000000000000000000000000000000000000000000000\ + + # disables all thumbnails + dthumb + + # disables video thumbnails + dvthumb + + # disables audio thumbnails (spectrograms) + dathumb + + # disables image thumbnails + dithumb + + # compress audio waveforms 33% better + pngquant + + # thumbnail res; WxH + thsize + + # center-cropping (y/n/fy/fn) + crop + + # 3x resolution (y/n/fy/fn) + th3x + + # conversion timeout in seconds + convt + + # use /b.png as thumbnail for file-extension s + ext_th: s=/b.png + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// handlers -- (better explained in --help-handlers) \\000000000000000000000000000000000000\ + + # handle 404s by executing PY file + on404: ~/bin/hook.py + + # handle 403s by executing PY file + on403: ~/bin/hook.py + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// event hooks -- (better explained in --help-hooks) \\000000000000000000000000000000000000\ + + # execute CMD before a file upload starts + xbu: ~/bin/hook.py + + # execute CMD after a file upload finishes + xau: ~/bin/hook.py + + # execute CMD after all uploads finish and volume is idle + xiu: ~/bin/hook.py + + # execute CMD before a file copy + xbc: ~/bin/hook.py + + # execute CMD after a file copy + xac: ~/bin/hook.py + + # execute CMD before a file rename/move + xbr: ~/bin/hook.py + + # execute CMD after a file rename/move + xar: ~/bin/hook.py + + # execute CMD before a file delete + xbd: ~/bin/hook.py + + # execute CMD after a file delete + xad: ~/bin/hook.py + + # execute CMD on message + xm: ~/bin/hook.py + + # execute CMD if someone gets banned + xban: ~/bin/hook.py + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// client and ux \\000000000000000000000000000000000000000000000000000000000000000000000000\ + + # show grid/thumbnails by default + grid + + # select files in grid by ctrl-click + gsel + + # default sort order + sort + + # natural-sort of leading digits in filenames + nsort + + # number of sort-rules to add to media URLs + hsortn + + # dont list files matching REGEX + unlist + + # includes TXT in the , or @PATH for file at PATH + html_head: + + # theme color (a hint for webbrowsers, discord, etc.) + tcolor: #fc0 + + # don't show total folder size + nodirsz + + # allows indexing by search engines (default) + robots + + # kindly asks search engines to leave + norobots + + # don't list read-access in controlpanel + unlistcr + + # don't list write-access in controlpanel + unlistcw + + # disable js sandbox for markdown files + no_sb_md + + # disable js sandbox for prologue/epilogue + no_sb_lg + + # enable js sandbox for markdown files (default) + sb_md + + # enable js sandbox for prologue/epilogue (default) + sb_lg + + # list of markdown-sandbox safeguards to disable + md_sbf + + # list of *logue-sandbox safeguards to disable + lg_sbf + + # value of iframe allow-prop for markdown-sandbox + md_sba + + # value of iframe allow-prop for *logue-sandbox + lg_sba + + # return html and markdown as text/html + nohtml + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// opengraph (discord embeds) \\00000000000000000000000000000000000000000000000000000000000\ + + # enable OG (disables hotlinking) + og + + # sitename; defaults to --name, disable with '-' + og_site + + # description text for all files; disable with '-' + og_desc + + # thumbnail format; j / jf / jf3 / w / w3 / ... + og_th: jf + + # audio title format; default: {{ artist }} - {{ title }} + og_title_a + + # video title format; default: {{ title }} + og_title_v + + # image title format; default: {{ title }} + og_title_i + + # fallback title if there's nothing in the db + og_title: foo + + # force default title; do not read from tags + og_s_title + + # custom html; see --og-tpl in --help + og_tpl + + # you want to add tags manually with og_tpl + og_no_head + + # if defined: only send OG html if useragent matches this regex + og_ua + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// textfiles \\0000000000000000000000000000000000000000000000000000000000000000000000000000\ + + # where to put markdown backups; s=subfolder, v=volHist, n=nope + md_hist + + # enable textfile expansion; see --help-exp + exp + + # placeholders to expand in markdown files; see --help + exp_md + + # placeholders to expand in prologue/epilogue; see --help + exp_lg + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// tailing \\000000000000000000000000000000000000000000000000000000000000000000000000000000\ + + # disable ?tail (download a growing file continuously) + notail + + # check if file was replaced (new fd) every 1 sec + tail_fd: 1 + + # check for new data every 0.2 sec + tail_rate: 0.2 + + # kill connection after 30 sec + tail_tmax: 30 + + # restrict ?tail access (1=admins,2=authed,3=everyone) + tail_who: 2 + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// others \\0000000000000000000000000000000000000000000000000000000000000000000000000000000\ + + # allow all users with read-access to enable the option to show dotfiles in listings + dots + + # generates per-file accesskeys, which are then required at the "g" permission; keys are invalidated if filesize or inode changes + fk: 8 + + # generates slightly weaker per-file accesskeys, which are then required at the "g" permission; not affected by filesize or inode numbers + fka: 8 + + # generates per-directory accesskeys, which are then required at the "g" permission; keys are invalidated if filesize or inode changes + dk: 8 + + # per-directory accesskeys allow browsing into subdirs + dks + + # allow seeing files (not folders) inside a specific folder with "g" perm, and does not require a valid dirkey to do so + dky + + # allow '?rss' URL suffix (experimental) + rss + + # expensive analysis for mimetype accuracy + rmagic + + # restrict viewing the list of recent uploads + ups_who: 2 + + # restrict access to download-as-zip/tar + zip_who: 2 + + # reject download-as-zip if more than 9000 files + zipmaxn: 9k + + # reject download-as-zip if size over 2 GiB + zipmaxs: 2g + + # reply with 'no' if download-as-zip exceeds max + zipmaxt: no + + # zip-size-limit does not apply to authenticated users + zipmaxu + + # disable race-the-beam (download unfinished uploads) + nopipe + + # ms-windows: timeout for renaming busy files + mv_retry + + # ms-windows: timeout for deleting busy files + rm_retry + + # ask webdav clients to login for all folders + davauth + + # show lastmod time of symlink destination, not the link itself (note: this option is always enabled for recursive listings) + davrt diff --git a/scripts/make-sfx.sh b/scripts/make-sfx.sh index deb7bee0..a1ee1a06 100755 --- a/scripts/make-sfx.sh +++ b/scripts/make-sfx.sh @@ -74,7 +74,6 @@ gtar=$(command -v gtar || command -v gnutar) || true sed() { gsed "$@"; } find() { gfind "$@"; } sort() { gsort "$@"; } - shuf() { gshuf "$@"; } nproc() { gnproc; } sha1sum() { shasum "$@"; } unexpand() { gunexpand "$@"; } @@ -157,9 +156,9 @@ stamp=$( done | sort | tail -n 1 | sha1sum | cut -c-16 ) -rm -rf sfx$CSN/* -mkdir -p sfx$CSN build -cd sfx$CSN +rm -rf sfx/* +mkdir -p sfx build +cd sfx tmpdir="$( 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) mkdir -p ../dist -sfx_out=../dist/copyparty-sfx$CSN +sfx_out=../dist/copyparty-sfx echo cleanup find -name '*.pyc' -delete @@ -554,7 +553,7 @@ gzres() { } -zdir="$tmpdir/cpp-mksfx$CSN" +zdir="$tmpdir/cpp-mksfx" [ -e "$zdir/$stamp" ] || rm -rf "$zdir" mkdir -p "$zdir" 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 sed -r 's/(.*)\.(.*)/\2 \1/' | LC_ALL=C sort | sed -r 's/([^ ]*) (.*)/\2.\1/' | grep -vE '/list1?$' > list1 - -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 +(grep -vE '\.gz$' list1; grep -E '\.gz$' list1) >list echo creating tar tar -cf tar "${targs[@]}" --numeric-owner -T list diff --git a/scripts/rls.sh b/scripts/rls.sh index 613b53e0..767ca2de 100755 --- a/scripts/rls.sh +++ b/scripts/rls.sh @@ -32,11 +32,8 @@ v=$1 rm -f ../dist/copyparty-sfx* shift ./make-sfx.sh "$@" -f=../dist/copyparty-sfx -[ -e $f.py ] && s= || s=-gz -# TODO: the -gz suffix is gone, can drop all the $s stuff probably - -$f$s.py --version >/dev/null +../dist/copyparty-sfx.py --version >/dev/null +mv ../dist/copyparty-{sfx,int}.py while [ "$1" ]; do case "$1" in @@ -46,27 +43,8 @@ while [ "$1" ]; do shift done -[ $parallel -gt 1 ] && { - printf '\033[%s' s 2r H "0;1;37;44mbruteforcing sfx size -- press enter to terminate" K u "7m $* " K $'27m\n' - trap "rm -f .sfx-run; printf '\033[%s' s r u" INT TERM EXIT - 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 +./make-sfx.sh re lang eng "$@" +mv ../dist/copyparty-{sfx,en}.py +mv ../dist/copyparty-{int,sfx}.py # git tag -d v$v; git push --delete origin v$v diff --git a/tests/util.py b/tests/util.py index ea9bfeb9..d3884ca9 100644 --- a/tests/util.py +++ b/tests/util.py @@ -143,7 +143,7 @@ class Cfg(Namespace): def __init__(self, a=None, v=None, c=None, **ka0): ka = {} - ex = "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()}) 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" 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()}) - 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()}) ex = "ban_403 ban_404 ban_422 ban_pw ban_pwc ban_url spinner"