Compare commits
82 Commits
v1.18.10
...
hovudstrau
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f4a3fba29c | ||
![]() |
3aa8b7aa2d | ||
![]() |
d56230573d | ||
![]() |
af8620da92 | ||
![]() |
2961dea5bb | ||
![]() |
4e878d2f1e | ||
![]() |
7f44875061 | ||
![]() |
68907eaf48 | ||
![]() |
c4a4fddd27 | ||
![]() |
5b62742512 | ||
![]() |
554cc2f3ee | ||
![]() |
6303effe59 | ||
![]() |
659f351c65 | ||
![]() |
d676a86f3f | ||
![]() |
715d374ee4 | ||
![]() |
c9fd608732 | ||
![]() |
c32a672a68 | ||
![]() |
69d9878acd | ||
![]() |
d8662aeb0e | ||
![]() |
a407eb9269 | ||
![]() |
1ebe06f51e | ||
![]() |
88243ac8d6 | ||
![]() |
6ccc9224f3 | ||
![]() |
0177a9b402 | ||
![]() |
9435e6b2e2 | ||
![]() |
0da93659a4 | ||
![]() |
db2a03409c | ||
![]() |
c2cee222bd | ||
![]() |
b87f8f1b01 | ||
![]() |
a01870b744 | ||
![]() |
3560eeb10e | ||
![]() |
03acd65e96 | ||
![]() |
e5e822951d | ||
![]() |
347cf6a546 | ||
![]() |
8ba98877ee | ||
![]() |
3c78c6a880 | ||
![]() |
7aa21483c5 | ||
![]() |
074e106e24 | ||
![]() |
e9ddfccfb6 | ||
![]() |
91ce7a29aa | ||
![]() |
392a4db55b | ||
![]() |
3931bc2779 | ||
![]() |
bd514f0666 | ||
![]() |
f8a7c02f23 | ||
![]() |
0dd5987250 | ||
![]() |
4eca4885f3 | ||
![]() |
e9ecb2edc5 | ||
![]() |
f0b1c82b44 | ||
![]() |
c955658332 | ||
![]() |
a98360f213 | ||
![]() |
33497e6b11 | ||
![]() |
36ab323d08 | ||
![]() |
1bf23fabc6 | ||
![]() |
d9e3f998d1 | ||
![]() |
1b71294aab | ||
![]() |
346515ccf1 | ||
![]() |
3c42a34f7b | ||
![]() |
13499d2846 | ||
![]() |
8b31ed8816 | ||
![]() |
bcc3b1568e | ||
![]() |
2943c7f2d5 | ||
![]() |
34d98e9980 | ||
![]() |
9e980bb552 | ||
![]() |
3f8cb7e877 | ||
![]() |
4a04356814 | ||
![]() |
8a0746c6af | ||
![]() |
54caf63f6a | ||
![]() |
9b9d2a92ca | ||
![]() |
a9ee4f24d5 | ||
![]() |
29a4e54799 | ||
![]() |
3b26884c69 | ||
![]() |
392abd0675 | ||
![]() |
50f46187f1 | ||
![]() |
7ae84dea1a | ||
![]() |
a57f7cc2f8 | ||
![]() |
0f55a1ae86 | ||
![]() |
00cb1f74e2 | ||
![]() |
b664ebb01f | ||
![]() |
c2ac57a2a8 | ||
![]() |
0df1901fc0 | ||
![]() |
8c000fd683 | ||
![]() |
d4397e7217 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -43,3 +43,6 @@ scripts/docker/*.err
|
||||
|
||||
# nix build output link
|
||||
result
|
||||
|
||||
# IDEA config
|
||||
.idea/
|
||||
|
38
README.md
38
README.md
@ -266,6 +266,7 @@ also see [comparison to similar software](./docs/versus.md)
|
||||
* ☑ realtime streaming of growing files (logfiles and such)
|
||||
* ☑ [thumbnails](#thumbnails)
|
||||
* ☑ ...of images using Pillow, pyvips, or FFmpeg
|
||||
* ☑ ...of RAW images using rawpy
|
||||
* ☑ ...of videos using FFmpeg
|
||||
* ☑ ...of audio (spectrograms) using FFmpeg
|
||||
* ☑ cache eviction (max-age; maybe max-size eventually)
|
||||
@ -437,6 +438,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 `--usernames` 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)
|
||||
@ -511,6 +513,8 @@ examples:
|
||||
* replacing the `g` permission with `wg` would let anonymous users upload files, but not see the required filekey to access it
|
||||
* replacing the `g` permission with `wG` would let anonymous users upload files, receiving a working direct link in return
|
||||
|
||||
if you want to grant access to all users who are logged in, the group `acct` will always contain all known users, so for example `-v /mnt/music:music:r,@acct`
|
||||
|
||||
anyone trying to bruteforce a password gets banned according to `--ban-pw`; default is 24h ban for 9 failed attempts in 1 hour
|
||||
|
||||
and if you want to use config files instead of commandline args (good!) then here's the same examples as a configfile; save it as `foobar.conf` and use it like this: `python copyparty-sfx.py -c foobar.conf`
|
||||
@ -536,6 +540,7 @@ and if you want to use config files instead of commandline args (good!) then her
|
||||
accs:
|
||||
r: u1, u2 # only these accounts can read,
|
||||
r: @g1 # (exactly the same, just with a group instead)
|
||||
r: @acct # (alternatively, ALL users who are logged in)
|
||||
rw: u3 # and only u3 can read-write
|
||||
|
||||
[/inc]
|
||||
@ -1016,6 +1021,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)
|
||||
@ -1229,7 +1235,7 @@ using arguments or config files, or a mix of both:
|
||||
|
||||
**NB:** as humongous as this readme is, there is also a lot of undocumented features. Run copyparty with `--help` to see all available global options; all of those can be used in the `[global]` section of config files, and everything listed in `--help-flags` can be used in volumes as volflags.
|
||||
* if running in docker/podman, try this: `docker run --rm -it copyparty/ac --help`
|
||||
* or see this (probably outdated): https://ocv.me/copyparty/helptext.html
|
||||
* or see this: https://ocv.me/copyparty/helptext.html
|
||||
* or if you prefer plaintext, https://ocv.me/copyparty/helptext.txt
|
||||
|
||||
|
||||
@ -1301,6 +1307,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 +1325,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 +1341,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 +1399,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 +2068,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 +2291,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 +2516,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 +2629,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 +2733,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
|
||||
|
||||
@ -2780,9 +2796,10 @@ enable [music tags](#metadata-from-audio-files):
|
||||
enable [thumbnails](#thumbnails) of...
|
||||
* **images:** `Pillow` and/or `pyvips` and/or `ffmpeg` (requires py2.7 or py3.5+)
|
||||
* **videos/audio:** `ffmpeg` and `ffprobe` somewhere in `$PATH`
|
||||
* **HEIF pictures:** `pyvips` or `ffmpeg` or `pyheif-pillow-opener` (requires Linux or a C compiler)
|
||||
* **HEIF pictures:** `pyvips` or `ffmpeg` or `pillow-heif`
|
||||
* **AVIF pictures:** `pyvips` or `ffmpeg` or `pillow-avif-plugin` or pillow v11.3+
|
||||
* **JPEG XL pictures:** `pyvips` or `ffmpeg`
|
||||
* **RAW images:** `rawpy`, plus one of `pyvips` or `Pillow` (for some formats)
|
||||
|
||||
enable sending [zeromq messages](#zeromq) from event-hooks: `pyzmq`
|
||||
|
||||
@ -2813,9 +2830,10 @@ set any of the following environment variables to disable its associated optiona
|
||||
| `PRTY_NO_PIL` | disable all [Pillow](https://pypi.org/project/pillow/)-based thumbnail support; will fallback to libvips or ffmpeg |
|
||||
| `PRTY_NO_PILF` | disable Pillow `ImageFont` text rendering, used for folder thumbnails |
|
||||
| `PRTY_NO_PIL_AVIF` | disable Pillow avif support (internal and/or [plugin](https://pypi.org/project/pillow-avif-plugin/)) |
|
||||
| `PRTY_NO_PIL_HEIF` | disable 3rd-party Pillow plugin for [HEIF support](https://pypi.org/project/pyheif-pillow-opener/) |
|
||||
| `PRTY_NO_PIL_HEIF` | disable 3rd-party Pillow plugin for [HEIF support](https://pypi.org/project/pillow-heif/) |
|
||||
| `PRTY_NO_PIL_WEBP` | disable use of native webp support in Pillow |
|
||||
| `PRTY_NO_PSUTIL` | do not use [psutil](https://pypi.org/project/psutil/) for reaping stuck hooks and plugins on Windows |
|
||||
| `PRTY_NO_RAW` | disable all [rawpy](https://pypi.org/project/rawpy/)-based thumbnail support for RAW images |
|
||||
| `PRTY_NO_VIPS` | disable all [libvips](https://pypi.org/project/pyvips/)-based thumbnail support; will fallback to Pillow or ffmpeg |
|
||||
|
||||
example: `PRTY_NO_PIL=1 python3 copyparty-sfx.py`
|
||||
@ -2836,6 +2854,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)
|
||||
|
||||
|
||||
|
@ -1,57 +1,48 @@
|
||||
# Maintainer: icxes <dev.null@need.moe>
|
||||
# Contributor: Morgan Adamiec <morganamilo@archlinux.org>
|
||||
# NOTE: You generally shouldn't use this PKGBUILD on Arch, as it is mainly for testing purposes. Install copyparty using pacman instead.
|
||||
|
||||
pkgname=copyparty
|
||||
pkgver="1.18.9"
|
||||
pkgver="1.19.1"
|
||||
pkgrel=1
|
||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
||||
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)"
|
||||
"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-git: detection of musical keys"
|
||||
"qm-vamp-plugins: BPM detection"
|
||||
"libkeyfinder: detection of musical keys"
|
||||
"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)"
|
||||
)
|
||||
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||
backup=("etc/${pkgname}.d/init" )
|
||||
sha256sums=("d5d33b50d6717e52427956beb1687061a6f28b467997506505151e3ae18c58e5")
|
||||
backup=("etc/${pkgname}/copyparty.conf" )
|
||||
sha256sums=("bbc250db23eb80bc96c27b2efa456ce1e7f49c7dfaabadb91a571f70064b6f91")
|
||||
|
||||
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 "┗━━━━━━━━━━━━━━━──-"
|
||||
}
|
||||
|
@ -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
|
@ -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
|
@ -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/`
|
@ -2,7 +2,7 @@
|
||||
|
||||
|
||||
pkgname=copyparty
|
||||
pkgver=1.18.9
|
||||
pkgver=1.19.1
|
||||
pkgrel=1
|
||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
||||
arch=("any")
|
||||
@ -20,7 +20,7 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag
|
||||
)
|
||||
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||
backup=("/etc/${pkgname}.d/init" )
|
||||
sha256sums=("d5d33b50d6717e52427956beb1687061a6f28b467997506505151e3ae18c58e5")
|
||||
sha256sums=("bbc250db23eb80bc96c27b2efa456ce1e7f49c7dfaabadb91a571f70064b6f91")
|
||||
|
||||
build() {
|
||||
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"url": "https://github.com/9001/copyparty/releases/download/v1.18.9/copyparty-sfx.py",
|
||||
"version": "1.18.9",
|
||||
"hash": "sha256-R1OVx4f8GERAG80ZcHAIP6HK2TlBbKJZpvnJmJbGPRY="
|
||||
"url": "https://github.com/9001/copyparty/releases/download/v1.19.1/copyparty-sfx.py",
|
||||
"version": "1.19.1",
|
||||
"hash": "sha256-Orn0N//DD5/5rIWK9yYRcvyOnt/bKCE9CeoxkfNO76s="
|
||||
}
|
62
contrib/package/rpm/copyparty.spec
Normal file
62
contrib/package/rpm/copyparty.spec
Normal file
@ -0,0 +1,62 @@
|
||||
Name: copyparty
|
||||
Version: $pkgver
|
||||
Release: $pkgrel
|
||||
License: MIT
|
||||
Group: Utilities
|
||||
URL: https://github.com/9001/copyparty
|
||||
Source0: copyparty-$pkgver.tar.gz
|
||||
Summary: File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++
|
||||
BuildArch: noarch
|
||||
BuildRequires: python3, python3-devel, pyproject-rpm-macros, python-setuptools, python-wheel, make
|
||||
Requires: python3, (python3-jinja2 or python-jinja2), lsof
|
||||
Recommends: ffmpeg, (golang-github-cloudflare-cfssl or cfssl), python-mutagen, python-pillow, python-pyvips
|
||||
Recommends: qm-vamp-plugins, python-argon2-cffi, (python-pyopenssl or pyopenssl), python-impacket
|
||||
|
||||
%description
|
||||
Portable file server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++ all in one file, no deps
|
||||
|
||||
See release at https://github.com/9001/copyparty/releases
|
||||
|
||||
%global debug_package %{nil}
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires
|
||||
|
||||
%prep
|
||||
%setup -q
|
||||
|
||||
%build
|
||||
cd "copyparty/web"
|
||||
make
|
||||
cd -
|
||||
%pyproject_wheel
|
||||
|
||||
%install
|
||||
mkdir -p %{buildroot}%{_bindir}
|
||||
mkdir -p %{buildroot}%{_libdir}/systemd/{system,user}
|
||||
mkdir -p %{buildroot}/etc/%{name}
|
||||
mkdir -p %{buildroot}/var/lib/%{name}-jail
|
||||
mkdir -p %{buildroot}%{_datadir}/licenses/%{name}
|
||||
|
||||
%pyproject_install
|
||||
%pyproject_save_files copyparty
|
||||
|
||||
install -m 0755 bin/prisonparty.sh %{buildroot}%{_bindir}/prisonpary.sh
|
||||
install -m 0644 contrib/systemd/%{name}.conf %{buildroot}/etc/%{name}/%{name}.conf
|
||||
install -m 0644 contrib/systemd/%{name}@.service %{buildroot}%{_libdir}/systemd/system/%{name}@.service
|
||||
install -m 0644 contrib/systemd/%{name}-user.service %{buildroot}%{_libdir}/systemd/user/%{name}.service
|
||||
install -m 0644 contrib/systemd/prisonparty@.service %{buildroot}%{_libdir}/systemd/system/prisonparty@.service
|
||||
install -m 0644 contrib/systemd/index.md %{buildroot}/var/lib/%{name}-jail/README.md
|
||||
install -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/%{name}/LICENSE
|
||||
|
||||
%files -n copyparty -f %{pyproject_files}
|
||||
%license LICENSE
|
||||
%{_bindir}/copyparty
|
||||
%{_bindir}/partyfuse
|
||||
%{_bindir}/u2c
|
||||
%{_bindir}/prisonpary.sh
|
||||
/etc/%{name}/%{name}.conf
|
||||
%{_libdir}/systemd/system/%{name}@.service
|
||||
%{_libdir}/systemd/user/%{name}.service
|
||||
%{_libdir}/systemd/system/prisonparty@.service
|
||||
/var/lib/%{name}-jail/README.md
|
26
contrib/systemd/copyparty-user.service
Normal file
26
contrib/systemd/copyparty-user.service
Normal file
@ -0,0 +1,26 @@
|
||||
# this will start `/usr/bin/copyparty`
|
||||
# and read config from `$HOME/.config/copyparty.conf`
|
||||
#
|
||||
# unless you add -q to disable logging, you may want to remove the
|
||||
# following line to allow buffering (slightly better performance):
|
||||
# Environment=PYTHONUNBUFFERED=x
|
||||
|
||||
[Unit]
|
||||
Description=copyparty file server
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
SyslogIdentifier=copyparty
|
||||
WorkingDirectory=/var/lib/copyparty-jail
|
||||
Environment=PYTHONUNBUFFERED=x
|
||||
Environment=PRTY_CONFIG=%h/.config/copyparty/copyparty.conf
|
||||
ExecReload=/bin/kill -s USR1 $MAINPID
|
||||
|
||||
# ensure there is a config
|
||||
ExecStartPre=/bin/bash -c 'if [[ ! -f %h/.config/copyparty/copyparty.conf ]]; then mkdir -p %h/.config/copyparty; cp /etc/copyparty/copyparty.conf %h/.config/copyparty/copyparty.conf; fi'
|
||||
|
||||
# run copyparty
|
||||
ExecStart=/usr/bin/python3 /usr/bin/copyparty
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
@ -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
|
42
contrib/systemd/copyparty.example.conf
Normal file
42
contrib/systemd/copyparty.example.conf
Normal file
@ -0,0 +1,42 @@
|
||||
# not actually YAML but lets pretend:
|
||||
# -*- mode: yaml -*-
|
||||
# vim: ft=yaml:
|
||||
|
||||
|
||||
# put this file in /etc/
|
||||
|
||||
|
||||
[global]
|
||||
e2dsa # enable file indexing and filesystem scanning
|
||||
e2ts # and enable multimedia indexing
|
||||
ansi # and colors in log messages
|
||||
|
||||
# disable logging to stdout/journalctl and log to a file instead;
|
||||
# $LOGS_DIRECTORY is usually /var/log/copyparty (comes from systemd)
|
||||
# and copyparty replaces %Y-%m%d with Year-MonthDay, so the
|
||||
# full path will be something like /var/log/copyparty/2023-1130.txt
|
||||
# (note: enable compression by adding .xz at the end)
|
||||
q, lo: $LOGS_DIRECTORY/%Y-%m%d.log
|
||||
|
||||
# p: 80,443,3923 # listen on 80/443 as well (requires CAP_NET_BIND_SERVICE)
|
||||
# i: 127.0.0.1 # only allow connections from localhost (reverse-proxies)
|
||||
# ftp: 3921 # enable ftp server on port 3921
|
||||
# p: 3939 # listen on another port
|
||||
# df: 16 # stop accepting uploads if less than 16 GB free disk space
|
||||
# ver # show copyparty version in the controlpanel
|
||||
# grid # show thumbnails/grid-view by default
|
||||
# theme: 2 # monokai
|
||||
# name: datasaver # change the server-name that's displayed in the browser
|
||||
# stats, nos-dup # enable the prometheus endpoint, but disable the dupes counter (too slow)
|
||||
# no-robots, force-js # make it harder for search engines to read your server
|
||||
|
||||
|
||||
[accounts]
|
||||
ed: wark # username: password
|
||||
|
||||
|
||||
[/] # create a volume at "/" (the webroot), which will
|
||||
/mnt # share the contents of the "/mnt" folder
|
||||
accs:
|
||||
rw: * # everyone gets read-write access, but
|
||||
rwmda: ed # the user "ed" gets read-write-move-delete-admin
|
30
contrib/systemd/copyparty@.service
Normal file
30
contrib/systemd/copyparty@.service
Normal file
@ -0,0 +1,30 @@
|
||||
# this will start `/usr/bin/copyparty`
|
||||
# and read config from `/etc/copyparty/copyparty.conf`
|
||||
#
|
||||
# the %i refers to whatever you put after the copyparty@
|
||||
# so with copyparty@foo.service, %i == foo
|
||||
#
|
||||
# unless you add -q to disable logging, you may want to remove the
|
||||
# following line to allow buffering (slightly better performance):
|
||||
# Environment=PYTHONUNBUFFERED=x
|
||||
|
||||
[Unit]
|
||||
Description=copyparty file server
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
SyslogIdentifier=copyparty
|
||||
WorkingDirectory=/var/lib/copyparty-jail
|
||||
Environment=PYTHONUNBUFFERED=x
|
||||
Environment=PRTY_CONFIG=/etc/copyparty/copyparty.conf
|
||||
ExecReload=/bin/kill -s USR1 $MAINPID
|
||||
|
||||
# user to run as + where the TLS certificate is (if any)
|
||||
User=%i
|
||||
Environment=XDG_CONFIG_HOME=/home/%i/.config
|
||||
|
||||
# run copyparty
|
||||
ExecStart=/usr/bin/python3 /usr/bin/copyparty
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
10
contrib/systemd/index.md
Normal file
10
contrib/systemd/index.md
Normal file
@ -0,0 +1,10 @@
|
||||
this is `/var/lib/copyparty-jail`, the fallback webroot when copyparty has not yet been configured
|
||||
|
||||
please edit `/etc/copyparty/copyparty.conf` (if running as a system service)
|
||||
or `$HOME/.config/copyparty/copyparty.conf` if running as a user service
|
||||
|
||||
a basic configuration example is available at https://github.com/9001/copyparty/blob/hovudstraum/contrib/systemd/copyparty.example.conf
|
||||
a configuration example that explains most flags is available at https://github.com/9001/copyparty/blob/hovudstraum/docs/chungus.conf
|
||||
|
||||
the full list of configuration options can be seen at https://ocv.me/copyparty/helptext.html
|
||||
or by running `copyparty --help`
|
@ -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
|
@ -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
|
||||
"""
|
||||
),
|
||||
],
|
||||
@ -607,6 +609,9 @@ def get_sects():
|
||||
if no accounts or volumes are configured,
|
||||
current folder will be read/write for everyone
|
||||
|
||||
the group @acct will always have every user with an account
|
||||
(the name of that group can be changed with --grp-all)
|
||||
|
||||
consider the config file for more flexible account/volume management,
|
||||
including dynamic reload at runtime (and being more readable w)
|
||||
"""
|
||||
@ -916,6 +921,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 --usernames 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 +1001,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,15 +1021,16 @@ def add_general(ap, nc, srvname):
|
||||
|
||||
|
||||
def add_qr(ap, tty):
|
||||
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 = ap.add_argument_group("qr options")
|
||||
ap2.add_argument("--qr", action="store_true", help="show QR-code on startup")
|
||||
ap2.add_argument("--qrs", action="store_true", help="change the QR-code URL to https://")
|
||||
ap2.add_argument("--qrl", metavar="PATH", type=u, default="", help="location to include in the url, for example [\033[32mpriv/?pw=hunter2\033[0m]")
|
||||
ap2.add_argument("--qri", metavar="PREFIX", type=u, default="", help="select IP which starts with \033[33mPREFIX\033[0m; [\033[32m.\033[0m] to force default IP when mDNS URL would have been used instead")
|
||||
ap2.add_argument("--qr-fg", metavar="COLOR", type=int, default=0 if tty else 16, help="foreground; try [\033[32m0\033[0m] if the qr-code is unreadable")
|
||||
ap2.add_argument("--qr-fg", metavar="COLOR", type=int, default=0 if tty else 16, help="foreground; try [\033[32m0\033[0m] or [\033[32m-1\033[0m] if the qr-code is unreadable")
|
||||
ap2.add_argument("--qr-bg", metavar="COLOR", type=int, default=229, help="background (white=255)")
|
||||
ap2.add_argument("--qrp", metavar="CELLS", type=int, default=4, help="padding (spec says 4 or more, but 1 is usually fine)")
|
||||
ap2.add_argument("--qrz", metavar="N", type=int, default=0, help="[\033[32m1\033[0m]=1x, [\033[32m2\033[0m]=2x, [\033[32m0\033[0m]=auto (try [\033[32m2\033[0m] on broken fonts)")
|
||||
ap2.add_argument("--qr-pin", metavar="N", type=int, default=0, help="sticky/pin the qr-code to always stay on-screen; [\033[32m0\033[0m]=disabled, [\033[32m1\033[0m]=with-url, [\033[32m2\033[0m]=just-qr")
|
||||
|
||||
|
||||
def add_fs(ap):
|
||||
@ -1034,7 +1044,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 +1053,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 +1095,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 +1120,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 +1132,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 +1152,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 +1166,15 @@ 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("--grp-all", metavar="NAME", type=u, default="acct", help="the name of the auto-generated group which contains every username which is known")
|
||||
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")
|
||||
@ -1195,6 +1206,7 @@ def add_zc_mdns(ap):
|
||||
ap2.add_argument("--zm-lh", metavar="PATH", type=u, default="", help="link a specific folder for http shares")
|
||||
ap2.add_argument("--zm-lf", metavar="PATH", type=u, default="", help="link a specific folder for ftp shares")
|
||||
ap2.add_argument("--zm-ls", metavar="PATH", type=u, default="", help="link a specific folder for smb shares")
|
||||
ap2.add_argument("--zm-fqdn", metavar="FQDN", type=u, default="--name.local", help="the domain to announce; NOTE: using anything other than .local is nonstandard and could cause problems")
|
||||
ap2.add_argument("--zm-mnic", action="store_true", help="merge NICs which share subnets; assume that same subnet means same network")
|
||||
ap2.add_argument("--zm-msub", action="store_true", help="merge subnets on each NIC -- always enabled for ipv6 -- reduces network load, but gnome-gvfs clients may stop working, and clients cannot be in subnets that the server is not")
|
||||
ap2.add_argument("--zm-noneg", action="store_true", help="disable NSEC replies -- try this if some clients don't see copyparty")
|
||||
@ -1212,12 +1224,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 +1237,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 +1247,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 +1255,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 +1274,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,21 +1307,23 @@ 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")
|
||||
ap2.add_argument("--no-del", action="store_true", help="disable delete operations")
|
||||
ap2.add_argument("--no-mv", action="store_true", help="disable move/rename operations")
|
||||
ap2.add_argument("--no-cp", action="store_true", help="disable copy operations")
|
||||
ap2.add_argument("--no-fs-abrt", action="store_true", help="disable ability to abort ongoing copy/move")
|
||||
ap2.add_argument("-nth", action="store_true", help="no title hostname; don't show \033[33m--name\033[0m in <title>")
|
||||
ap2.add_argument("-nih", action="store_true", help="no info hostname -- don't show in UI")
|
||||
ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI")
|
||||
@ -1329,7 +1343,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")
|
||||
@ -1353,6 +1367,8 @@ def add_safety(ap):
|
||||
ap2.add_argument("--sus-urls", metavar="R", type=u, default=r"\.php$|(^|/)wp-(admin|content|includes)/", help="URLs which are considered sus / eligible for banning; disable with blank or [\033[32mno\033[0m]")
|
||||
ap2.add_argument("--nonsus-urls", metavar="R", type=u, default=r"^(favicon\.ico|robots\.txt)$|^apple-touch-icon|^\.well-known", help="harmless URLs ignored from 404-bans; disable with blank or [\033[32mno\033[0m]")
|
||||
ap2.add_argument("--early-ban", action="store_true", help="if a client is banned, reject its connection as soon as possible; not a good idea to enable when proxied behind cloudflare since it could ban your reverse-proxy")
|
||||
ap2.add_argument("--cookie-nmax", metavar="N", type=int, default=50, help="reject HTTP-request from client if they send more than N cookies")
|
||||
ap2.add_argument("--cookie-cmax", metavar="N", type=int, default=8192, help="reject HTTP-request from client if more than N characters in Cookie header")
|
||||
ap2.add_argument("--aclose", metavar="MIN", type=int, default=10, help="if a client maxes out the server connection limit, downgrade it from connection:keep-alive to connection:close for \033[33mMIN\033[0m minutes (and also kill its active connections) -- disable with 0")
|
||||
ap2.add_argument("--loris", metavar="B", type=int, default=60, help="if a client maxes out the server connection limit without sending headers, ban it for \033[33mB\033[0m minutes; disable with [\033[32m0\033[0m]")
|
||||
ap2.add_argument("--acao", metavar="V[,V]", type=u, default="*", help="Access-Control-Allow-Origin; list of origins (domains/IPs without port) to accept requests from; [\033[32mhttps://1.2.3.4\033[0m]. Default [\033[32m*\033[0m] allows requests from all sites but removes cookies and http-auth; only ?pw=hunter2 survives")
|
||||
@ -1360,7 +1376,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,23 +1390,23 @@ 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("-lo", metavar="PATH", type=u, default="", help="logfile; use .txt for plaintext or .xz for compressed. Example: \033[32mcpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz\033[0m (NB: some errors may appear on STDOUT only)")
|
||||
ap2.add_argument("--no-ansi", action="store_true", default=not VT100, help="disable colors; same as environment-variable NO_COLOR")
|
||||
ap2.add_argument("--ansi", action="store_true", help="force colors; overrides environment-variable NO_COLOR")
|
||||
ap2.add_argument("--no-logflush", action="store_true", help="don't flush the logfile after each write; tiny bit faster")
|
||||
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 +1415,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,17 +1429,18 @@ 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)")
|
||||
ap2.add_argument("--th-size", metavar="WxH", default="320x256", help="thumbnail res (volflag=thsize)")
|
||||
ap2.add_argument("--th-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for generating thumbnails")
|
||||
ap2.add_argument("--th-convt", metavar="SEC", type=float, default=60.0, help="conversion timeout in seconds (volflag=convt)")
|
||||
ap2.add_argument("--th-convt", metavar="SEC", type=float, default=60.0, help="convert-to-image timeout in seconds (volflag=convt)")
|
||||
ap2.add_argument("--ac-convt", metavar="SEC", type=float, default=150.0, help="convert-to-audio timeout in seconds (volflag=aconvt)")
|
||||
ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=th_ram, help="max memory usage (GiB) permitted by thumbnailer; not very accurate")
|
||||
ap2.add_argument("--th-crop", metavar="TXT", type=u, default="y", help="crop thumbnails to 4:3 or keep dynamic height; client can override in UI unless force. [\033[32my\033[0m]=crop, [\033[32mn\033[0m]=nocrop, [\033[32mfy\033[0m]=force-y, [\033[32mfn\033[0m]=force-n (volflag=crop)")
|
||||
ap2.add_argument("--th-x3", metavar="TXT", type=u, default="n", help="show thumbs at 3x resolution; client can override in UI unless force. [\033[32my\033[0m]=yes, [\033[32mn\033[0m]=no, [\033[32mfy\033[0m]=force-yes, [\033[32mfn\033[0m]=force-no (volflag=th3x)")
|
||||
ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,ff", help="image decoders, in order of preference")
|
||||
ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,raw,ff", help="image decoders, in order of preference")
|
||||
ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
|
||||
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
|
||||
ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg output for video thumbs (avoids issues on some FFmpeg builds)")
|
||||
@ -1434,18 +1451,20 @@ def add_thumbnail(ap):
|
||||
ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat/look for; enabling \033[33m-e2d\033[0m will make these case-insensitive, and try them as dotfiles (.folder.jpg), and also automatically select thumbnails for all folders that contain pics, even if none match this pattern")
|
||||
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
|
||||
# https://github.com/libvips/libvips
|
||||
# https://stackoverflow.com/a/47612661
|
||||
# ffmpeg -hide_banner -demuxers | awk '/^ D /{print$2}' | while IFS= read -r x; do ffmpeg -hide_banner -h demuxer=$x; done | grep -E '^Demuxer |extensions:'
|
||||
ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow")
|
||||
ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,epub,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow")
|
||||
ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="avif,exr,fit,fits,fts,gif,hdr,heic,jp2,jpeg,jpg,jpx,jxl,nii,pfm,pgm,png,ppm,svg,tif,tiff,webp", help="image formats to decode using pyvips")
|
||||
ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
|
||||
ap2.add_argument("--th-r-raw", metavar="T,T", type=u, default="arw,cr2,cr3,crw,dcr,dng,erf,k25,kdc,mrw,nef,orf,pef,raf,raw,sr2,srf,x3f", help="image formats to decode using rawpy")
|
||||
ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,epub,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
|
||||
ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg")
|
||||
ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,oga,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
|
||||
ap2.add_argument("--th-spec-cnv", metavar="T", type=u, default="it,itgz,itxz,itz,mdgz,mdxz,mdz,mo3,mod,s3m,s3gz,s3xz,s3z,xm,xmgz,xmxz,xmz,xpk", help="audio formats which provoke https://trac.ffmpeg.org/ticket/10797 (huge ram usage for s3xmodit spectrograms)")
|
||||
ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz", help="audio/image formats to decompress before passing to ffmpeg")
|
||||
ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz, epub=jpg.epub", help="audio/image formats to decompress before passing to ffmpeg")
|
||||
|
||||
|
||||
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 +1477,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 +1487,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 +1496,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")
|
||||
@ -1486,8 +1505,8 @@ def add_db_general(ap, hcores):
|
||||
ap2.add_argument("-e2vp", action="store_true", help="on hash mismatch: panic and quit copyparty")
|
||||
ap2.add_argument("--hist", metavar="PATH", type=u, default="", help="where to store volume data (db, thumbs); default is a folder named \".hist\" inside each volume (volflag=hist)")
|
||||
ap2.add_argument("--dbpath", metavar="PATH", type=u, default="", help="override where the volume databases are to be placed; default is the same as \033[33m--hist\033[0m (volflag=dbpath)")
|
||||
ap2.add_argument("--no-hash", metavar="PTN", type=u, default="", help="regex: disable hashing of matching absolute-filesystem-paths during e2ds folder scans (volflag=nohash)")
|
||||
ap2.add_argument("--no-idx", metavar="PTN", type=u, default=noidx, help="regex: disable indexing of matching absolute-filesystem-paths during e2ds folder scans (volflag=noidx)")
|
||||
ap2.add_argument("--no-hash", metavar="PTN", type=u, default="", help="regex: disable hashing of matching absolute-filesystem-paths during e2ds folder scans (must be specified as one big regex, not multiple times) (volflag=nohash)")
|
||||
ap2.add_argument("--no-idx", metavar="PTN", type=u, default=noidx, help="regex: disable indexing of matching absolute-filesystem-paths during e2ds folder scan (must be specified as one big regex, not multiple times) (volflag=noidx)")
|
||||
ap2.add_argument("--no-dirsz", action="store_true", help="do not show total recursive size of folders in listings, show inode size instead; slightly faster (volflag=nodirsz)")
|
||||
ap2.add_argument("--re-dirsz", action="store_true", help="if the directory-sizes in the UI are bonkers, use this along with \033[33m-e2dsa\033[0m to rebuild the index from scratch")
|
||||
ap2.add_argument("--no-dhash", action="store_true", help="disable rescan acceleration; do full database integrity check -- makes the db ~5%% smaller and bootup/rescans 3~10x slower")
|
||||
@ -1506,7 +1525,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 +1535,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 +1554,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,13 +1572,13 @@ 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")
|
||||
ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language; one of the following: \033[32meng nor chi\033[0m")
|
||||
ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language, for example \033[32meng\033[0m / \033[32mnor\033[0m / ...")
|
||||
ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..7)")
|
||||
ap2.add_argument("--themes", metavar="NUM", type=int, default=8, help="number of themes installed")
|
||||
ap2.add_argument("--themes", metavar="NUM", type=int, default=10, help="number of themes installed")
|
||||
ap2.add_argument("--au-vol", metavar="0-100", type=int, default=50, choices=range(0, 101), help="default audio/video volume percent")
|
||||
ap2.add_argument("--sort", metavar="C,C,C", type=u, default="href", help="default sort order, comma-separated column IDs (see header tooltips), prefix with '-' for descending. Examples: \033[32mhref -href ext sz ts tags/Album tags/.tn\033[0m (volflag=sort)")
|
||||
ap2.add_argument("--nsort", action="store_true", help="default-enable natural sort of filenames with leading numbers (volflag=nsort)")
|
||||
@ -1567,7 +1587,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 +1603,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 +1614,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")
|
||||
@ -1758,6 +1779,10 @@ def main(argv: Optional[list[str]] = None) -> None:
|
||||
|
||||
ensure_webdeps()
|
||||
|
||||
if CFG_DEF:
|
||||
supp = args_from_cfg(CFG_DEF[0])
|
||||
argv.extend(supp)
|
||||
|
||||
for k, v in zip(argv[1:], argv[2:]):
|
||||
if k == "-c" and os.path.isfile(v):
|
||||
supp = args_from_cfg(v)
|
||||
|
@ -1,8 +1,8 @@
|
||||
# coding: utf-8
|
||||
|
||||
VERSION = (1, 18, 10)
|
||||
CODENAME = "logtail"
|
||||
BUILD_DT = (2025, 8, 4)
|
||||
VERSION = (1, 19, 1)
|
||||
CODENAME = "usernames"
|
||||
BUILD_DT = (2025, 8, 10)
|
||||
|
||||
S_VERSION = ".".join(map(str, VERSION))
|
||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||
|
@ -1099,6 +1099,9 @@ class AuthSrv(object):
|
||||
if rejected:
|
||||
continue
|
||||
|
||||
if gn == self.args.grp_all:
|
||||
gn = ""
|
||||
|
||||
# if ap/vp has a user/group placeholder, make sure to keep
|
||||
# track so the same user/group is mapped when setting perms;
|
||||
# otherwise clear un/gn to indicate it's a regular volume
|
||||
@ -1208,6 +1211,7 @@ class AuthSrv(object):
|
||||
self.load_idp_db(bool(self.idp_accs))
|
||||
ret = {un: gns[:] for un, gns in self.idp_accs.items()}
|
||||
ret.update({zs: [""] for zs in acct if zs not in ret})
|
||||
grps[self.args.grp_all] = list(ret.keys())
|
||||
for gn, uns in grps.items():
|
||||
for un in uns:
|
||||
try:
|
||||
@ -1700,6 +1704,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 +1714,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:
|
||||
@ -1869,6 +1885,16 @@ class AuthSrv(object):
|
||||
if LEELOO_DALLAS in all_users:
|
||||
raise Exception("sorry, reserved username: " + LEELOO_DALLAS)
|
||||
|
||||
zsl = []
|
||||
for usr in list(acct)[:]:
|
||||
zs = acct[usr].strip()
|
||||
if not zs:
|
||||
zs = ub64enc(os.urandom(48)).decode("ascii")
|
||||
zsl.append(usr)
|
||||
acct[usr] = zs
|
||||
if zsl:
|
||||
self.log("generated random passwords for users %r" % (zsl,), 6)
|
||||
|
||||
seenpwds = {}
|
||||
for usr, pwd in acct.items():
|
||||
if pwd in seenpwds:
|
||||
@ -2194,7 +2220,7 @@ class AuthSrv(object):
|
||||
if k in vol.flags:
|
||||
vol.flags[k] = int(vol.flags[k])
|
||||
|
||||
zs = "convt tail_fd tail_rate tail_tmax"
|
||||
zs = "aconvt convt tail_fd tail_rate tail_tmax"
|
||||
for k in zs.split():
|
||||
if k in vol.flags:
|
||||
vol.flags[k] = float(vol.flags[k])
|
||||
@ -2630,6 +2656,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 +2958,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 +3052,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:
|
||||
|
@ -68,6 +68,7 @@ def vf_bmap() -> dict[str, str]:
|
||||
def vf_vmap() -> dict[str, str]:
|
||||
"""argv-to-volflag: simple values"""
|
||||
ret = {
|
||||
"ac_convt": "aconvt",
|
||||
"no_hash": "nohash",
|
||||
"no_idx": "noidx",
|
||||
"re_maxage": "scan",
|
||||
@ -111,6 +112,7 @@ def vf_vmap() -> dict[str, str]:
|
||||
"tail_tmax",
|
||||
"tail_who",
|
||||
"tcolor",
|
||||
"txt_eol",
|
||||
"unlist",
|
||||
"u2abort",
|
||||
"u2ts",
|
||||
@ -259,7 +261,8 @@ flagcats = {
|
||||
"thsize": "thumbnail res; WxH",
|
||||
"crop": "center-cropping (y/n/fy/fn)",
|
||||
"th3x": "3x resolution (y/n/fy/fn)",
|
||||
"convt": "conversion timeout in seconds",
|
||||
"convt": "convert-to-image timeout in seconds",
|
||||
"aconvt": "convert-to-audio timeout in seconds",
|
||||
"ext_th=s=/b.png": "use /b.png as thumbnail for file-extension s",
|
||||
},
|
||||
"handlers\n(better explained in --help-handlers)": {
|
||||
@ -322,6 +325,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)",
|
||||
|
@ -65,6 +65,9 @@ DXMLParser = _DXMLParser
|
||||
|
||||
|
||||
def parse_xml(txt: str) -> ET.Element:
|
||||
"""
|
||||
Parse XML into an xml.etree.ElementTree.Element while defusing some unsafe parts.
|
||||
"""
|
||||
parser = DXMLParser()
|
||||
parser.feed(txt)
|
||||
return parser.close() # type: ignore
|
||||
|
@ -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
|
||||
@ -280,9 +285,12 @@ class FtpFs(AbstractedFS):
|
||||
# returning 550 is library-default and suitable
|
||||
raise FSE("No such file or directory")
|
||||
|
||||
if vfs.realpath:
|
||||
avfs = vfs.chk_ap(ap, st)
|
||||
if not avfs:
|
||||
raise FSE("Permission denied", 1)
|
||||
else:
|
||||
avfs = vfs
|
||||
|
||||
self.cwd = nwd
|
||||
(
|
||||
@ -487,7 +495,11 @@ class FtpHandler(FTPHandler):
|
||||
def ftp_STOR(self, file: str, mode: str = "w") -> Any:
|
||||
# Optional[str]
|
||||
vp = join(self.fs.cwd, file).lstrip("/")
|
||||
try:
|
||||
ap, vfs, rem = self.fs.v2a(vp, w=True)
|
||||
except Exception as ex:
|
||||
self.respond("550 %s" % (ex,), logging.info)
|
||||
return
|
||||
self.vfs_map[ap] = vp
|
||||
xbu = vfs.flags.get("xbu")
|
||||
if xbu and not runhook(
|
||||
@ -607,7 +619,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]
|
||||
|
@ -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
|
||||
@ -549,7 +562,7 @@ class HttpCli(object):
|
||||
|
||||
zso = self.headers.get("cookie")
|
||||
if zso:
|
||||
if len(zso) > 8192:
|
||||
if len(zso) > self.args.cookie_cmax:
|
||||
self.loud_reply("cookie header too big", status=400)
|
||||
return False
|
||||
zsll = [x.split("=", 1) for x in zso.split(";") if "=" in x]
|
||||
@ -557,11 +570,15 @@ class HttpCli(object):
|
||||
cookie_pw = cookies.get("cppws") or cookies.get("cppwd") or ""
|
||||
if "b" in cookies and "b" not in uparam:
|
||||
uparam["b"] = cookies["b"]
|
||||
if len(cookies) > self.args.cookie_nmax:
|
||||
self.loud_reply("too many cookies", status=400)
|
||||
else:
|
||||
cookies = {}
|
||||
cookie_pw = ""
|
||||
|
||||
if len(uparam) > 10 or len(cookies) > 50:
|
||||
if len(uparam) > 12:
|
||||
t = "http-request rejected; num.params: %d %r"
|
||||
self.log(t % (len(uparam), self.req), 3)
|
||||
self.loud_reply("u wot m8", status=400)
|
||||
return False
|
||||
|
||||
@ -687,7 +704,7 @@ class HttpCli(object):
|
||||
cookies["b"] = ""
|
||||
|
||||
vn, rem = self.asrv.vfs.get(self.vpath, self.uname, False, False)
|
||||
if "xdev" in vn.flags or "xvol" in vn.flags:
|
||||
if vn.realpath and ("xdev" in vn.flags or "xvol" in vn.flags):
|
||||
ap = vn.canonical(rem)
|
||||
avn = vn.chk_ap(ap)
|
||||
else:
|
||||
@ -1586,6 +1603,10 @@ class HttpCli(object):
|
||||
"quota-available-bytes": str(bfree),
|
||||
"quota-used-bytes": str(btot - bfree),
|
||||
}
|
||||
if "quotaused" in props: # macos finder crazytalk
|
||||
df["quotaused"] = df["quota-used-bytes"]
|
||||
if "quota" in props:
|
||||
df["quota"] = df["quota-available-bytes"] # idk, makes it happy
|
||||
else:
|
||||
df = {}
|
||||
else:
|
||||
@ -1970,6 +1991,9 @@ class HttpCli(object):
|
||||
if "eshare" in self.uparam:
|
||||
return self.handle_eshare()
|
||||
|
||||
if "fs_abrt" in self.uparam:
|
||||
return self.handle_fs_abrt()
|
||||
|
||||
if "application/octet-stream" in ctype:
|
||||
return self.handle_post_binary()
|
||||
|
||||
@ -2924,12 +2948,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 +2970,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 +2989,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 +3623,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 +3707,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 +4680,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
|
||||
@ -4937,7 +4982,7 @@ class HttpCli(object):
|
||||
rip = host
|
||||
|
||||
vp = (self.uparam["hc"] or "").lstrip("/")
|
||||
pw = self.pw or "hunter2"
|
||||
pw = self.ouparam.get("pw") or "hunter2"
|
||||
if pw in self.asrv.sesa:
|
||||
pw = "hunter2"
|
||||
|
||||
@ -5031,7 +5076,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 +5116,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 +5140,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 +5186,7 @@ class HttpCli(object):
|
||||
t = '<h1 id="n">404 not found ┐( ´ -`)┌</h1><p><a id="r" href="{}/?h">go home</a></p>'
|
||||
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
|
||||
@ -5439,13 +5492,15 @@ class HttpCli(object):
|
||||
q = "select sz, rd, fn, at from up where ip=? and at>? order by at desc"
|
||||
for sz, rd, fn, at in cur.execute(q, (self.ip, lim)):
|
||||
vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
|
||||
if nfi == 0 or (nfi == 1 and vfi in vp):
|
||||
if nfi == 0 or (nfi == 1 and vfi in vp.lower()):
|
||||
pass
|
||||
elif nfi == 2:
|
||||
if not vp.startswith(vfi):
|
||||
if not vp.lower().startswith(vfi):
|
||||
continue
|
||||
elif nfi == 3:
|
||||
if not vp.endswith(vfi):
|
||||
if not vp.lower().endswith(vfi):
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
|
||||
n -= 1
|
||||
@ -5563,13 +5618,15 @@ class HttpCli(object):
|
||||
q = "select sz, rd, fn, ip, at from up where at>0 order by at desc"
|
||||
for sz, rd, fn, ip, at in cur.execute(q):
|
||||
vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
|
||||
if nfi == 0 or (nfi == 1 and vfi in vp):
|
||||
if nfi == 0 or (nfi == 1 and vfi in vp.lower()):
|
||||
pass
|
||||
elif nfi == 2:
|
||||
if not vp.startswith(vfi):
|
||||
if not vp.lower().startswith(vfi):
|
||||
continue
|
||||
elif nfi == 3:
|
||||
if not vp.endswith(vfi):
|
||||
if not vp.lower().endswith(vfi):
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
|
||||
if not dots and "/." in vp:
|
||||
@ -5908,7 +5965,9 @@ class HttpCli(object):
|
||||
self.asrv.vfs.get(vdst, self.uname, False, True, False, True)
|
||||
wunlink(self.log, dabs, dvn.flags)
|
||||
|
||||
x = self.conn.hsrv.broker.ask("up2k.handle_mv", self.uname, self.ip, vsrc, vdst)
|
||||
x = self.conn.hsrv.broker.ask(
|
||||
"up2k.handle_mv", self.ouparam.get("akey"), self.uname, self.ip, vsrc, vdst
|
||||
)
|
||||
self.loud_reply(x.get(), status=201)
|
||||
return True
|
||||
|
||||
@ -5938,10 +5997,21 @@ class HttpCli(object):
|
||||
self.asrv.vfs.get(vdst, self.uname, False, True, False, True)
|
||||
wunlink(self.log, dabs, dvn.flags)
|
||||
|
||||
x = self.conn.hsrv.broker.ask("up2k.handle_cp", self.uname, self.ip, vsrc, vdst)
|
||||
x = self.conn.hsrv.broker.ask(
|
||||
"up2k.handle_cp", self.ouparam.get("akey"), self.uname, self.ip, vsrc, vdst
|
||||
)
|
||||
self.loud_reply(x.get(), status=201)
|
||||
return True
|
||||
|
||||
def handle_fs_abrt(self):
|
||||
if self.args.no_fs_abrt:
|
||||
t = "aborting an ongoing copy/move is disabled in server config"
|
||||
raise Pebkac(403, t)
|
||||
|
||||
self.conn.hsrv.broker.say("up2k.handle_fs_abrt", self.uparam["fs_abrt"])
|
||||
self.loud_reply("aborting", status=200)
|
||||
return True
|
||||
|
||||
def tx_ls(self, ls: dict[str, Any]) -> bool:
|
||||
dirs = ls["dirs"]
|
||||
files = ls["files"]
|
||||
@ -6001,6 +6071,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 +6259,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 +6272,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 +6332,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
|
||||
|
||||
|
@ -76,7 +76,8 @@ class MDNS(MCast):
|
||||
if not self.args.zm_nwa_1:
|
||||
set_avahi_379()
|
||||
|
||||
zs = self.args.name + ".local."
|
||||
zs = self.args.zm_fqdn or (self.args.name + ".local")
|
||||
zs = zs.replace("--name", self.args.name).rstrip(".") + "."
|
||||
zs = zs.encode("ascii", "replace").decode("ascii", "replace")
|
||||
self.hn = "-".join(x for x in zs.split("?") if x) or (
|
||||
"vault-{}".format(random.randint(1, 255))
|
||||
|
@ -29,7 +29,7 @@ from .util import (
|
||||
)
|
||||
|
||||
if True: # pylint: disable=using-constant-test
|
||||
from typing import Any, Optional, Union
|
||||
from typing import IO, Any, Optional, Union
|
||||
|
||||
from .util import NamedLogger, RootLogger
|
||||
|
||||
@ -176,6 +176,9 @@ def au_unpk(
|
||||
raise Exception("no images inside cbz")
|
||||
fi = zf.open(using)
|
||||
|
||||
elif pk == "epub":
|
||||
fi = get_cover_from_epub(log, abspath)
|
||||
|
||||
else:
|
||||
raise Exception("unknown compression %s" % (pk,))
|
||||
|
||||
@ -365,6 +368,76 @@ def parse_ffprobe(txt: str) -> tuple[dict[str, tuple[int, Any]], dict[str, list[
|
||||
return zd, md
|
||||
|
||||
|
||||
def get_cover_from_epub(log: "NamedLogger", abspath: str) -> Optional[IO[bytes]]:
|
||||
import zipfile
|
||||
|
||||
from .dxml import parse_xml
|
||||
|
||||
try:
|
||||
from urlparse import urljoin # Python2
|
||||
except ImportError:
|
||||
from urllib.parse import urljoin # Python3
|
||||
|
||||
with zipfile.ZipFile(abspath, "r") as z:
|
||||
# First open the container file to find the package document (.opf file)
|
||||
try:
|
||||
container_root = parse_xml(z.read("META-INF/container.xml").decode())
|
||||
except KeyError:
|
||||
log("epub: no container file found in %s" % (abspath,))
|
||||
return None
|
||||
|
||||
# https://www.w3.org/TR/epub-33/#sec-container.xml-rootfile-elem
|
||||
container_ns = {"": "urn:oasis:names:tc:opendocument:xmlns:container"}
|
||||
# One file could contain multiple package documents, default to the first one
|
||||
rootfile_path = container_root.find("./rootfiles/rootfile", container_ns).get(
|
||||
"full-path"
|
||||
)
|
||||
|
||||
# Then open the first package document to find the path of the cover image
|
||||
try:
|
||||
package_root = parse_xml(z.read(rootfile_path).decode())
|
||||
except KeyError:
|
||||
log("epub: no package document found in %s" % (abspath,))
|
||||
return None
|
||||
|
||||
# https://www.w3.org/TR/epub-33/#sec-package-doc
|
||||
package_ns = {"": "http://www.idpf.org/2007/opf"}
|
||||
# https://www.w3.org/TR/epub-33/#sec-cover-image
|
||||
coverimage_path_node = package_root.find(
|
||||
"./manifest/item[@properties='cover-image']", package_ns
|
||||
)
|
||||
if coverimage_path_node is not None:
|
||||
coverimage_path = coverimage_path_node.get("href")
|
||||
else:
|
||||
# This might be an EPUB2 file, try the legacy way of specifying covers
|
||||
coverimage_path = _get_cover_from_epub2(log, package_root, package_ns)
|
||||
|
||||
# This url is either absolute (in the .epub) or relative to the package document
|
||||
adjusted_cover_path = urljoin(rootfile_path, coverimage_path)
|
||||
|
||||
return z.open(adjusted_cover_path)
|
||||
|
||||
|
||||
def _get_cover_from_epub2(
|
||||
log: "NamedLogger", package_root, package_ns
|
||||
) -> Optional[str]:
|
||||
# <meta name="cover" content="id-to-cover-image"> in <metadata>, then
|
||||
# <item> in <manifest>
|
||||
cover_id = package_root.find("./metadata/meta[@name='cover']", package_ns).get(
|
||||
"content"
|
||||
)
|
||||
|
||||
if not cover_id:
|
||||
return None
|
||||
|
||||
for node in package_root.iterfind("./manifest/item", package_ns):
|
||||
if node.get("id") == cover_id:
|
||||
cover_path = node.get("href")
|
||||
return cover_path
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class MTag(object):
|
||||
def __init__(self, log_func: "RootLogger", args: argparse.Namespace) -> None:
|
||||
self.log_func = log_func
|
||||
|
@ -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:
|
||||
|
@ -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> ")
|
||||
|
@ -2,6 +2,7 @@
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import argparse
|
||||
import atexit
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
@ -38,6 +39,7 @@ from .th_srv import (
|
||||
HAVE_FFPROBE,
|
||||
HAVE_HEIF,
|
||||
HAVE_PIL,
|
||||
HAVE_RAW,
|
||||
HAVE_VIPS,
|
||||
HAVE_WEBP,
|
||||
ThumbSrv,
|
||||
@ -72,6 +74,7 @@ from .util import (
|
||||
pybin,
|
||||
start_log_thrs,
|
||||
start_stackmon,
|
||||
termsize,
|
||||
ub64enc,
|
||||
)
|
||||
|
||||
@ -321,6 +324,8 @@ class SvcHub(object):
|
||||
decs.pop("vips", None)
|
||||
if not HAVE_PIL:
|
||||
decs.pop("pil", None)
|
||||
if not HAVE_RAW:
|
||||
decs.pop("raw", None)
|
||||
if not HAVE_FFMPEG or not HAVE_FFPROBE:
|
||||
decs.pop("ff", None)
|
||||
|
||||
@ -772,6 +777,39 @@ class SvcHub(object):
|
||||
def sigterm(self) -> None:
|
||||
self.signal_handler(signal.SIGTERM, None)
|
||||
|
||||
def sticky_qr(self) -> None:
|
||||
tw, th = termsize()
|
||||
zs1, qr = self.tcpsrv.qr.split("\n", 1)
|
||||
url, colr = zs1.split(" ", 1)
|
||||
nl = len(qr.split("\n")) # numlines
|
||||
lp = 3 if nl * 2 + 4 < tw else 0 # leftpad
|
||||
lp0 = lp
|
||||
if self.args.qr_pin == 2:
|
||||
url = ""
|
||||
else:
|
||||
while lp and (nl + lp) * 2 + len(url) + 1 > tw:
|
||||
lp -= 1
|
||||
if (nl + lp) * 2 + len(url) + 1 > tw:
|
||||
qr = url + "\n" + qr
|
||||
url = ""
|
||||
nl += 1
|
||||
lp = lp0
|
||||
sh = 1 + th - nl
|
||||
if lp:
|
||||
zs = " " * lp
|
||||
qr = zs + qr.replace("\n", "\n" + zs)
|
||||
if url:
|
||||
url = "%s\033[%d;%dH%s\033[0m" % (colr, sh + 1, (nl + lp) * 2, url)
|
||||
qr = colr + qr
|
||||
|
||||
def unlock():
|
||||
print("\033[s\033[r\033[u", file=sys.stderr)
|
||||
|
||||
atexit.register(unlock)
|
||||
t = "%s\033[%dA" % ("\n" * nl, nl)
|
||||
t = "%s\033[s\033[1;%dr\033[%dH%s%s\033[u" % (t, sh - 1, sh, qr, url)
|
||||
self.pr(t, file=sys.stderr)
|
||||
|
||||
def cb_httpsrv_up(self) -> None:
|
||||
self.httpsrv_up += 1
|
||||
if self.httpsrv_up != self.broker.num_workers:
|
||||
@ -784,6 +822,9 @@ class SvcHub(object):
|
||||
break
|
||||
|
||||
if self.tcpsrv.qr:
|
||||
if self.args.qr_pin:
|
||||
self.sticky_qr()
|
||||
else:
|
||||
self.log("qr-code", self.tcpsrv.qr)
|
||||
else:
|
||||
self.log("root", "workers OK\n")
|
||||
@ -811,6 +852,7 @@ class SvcHub(object):
|
||||
(HAVE_ZMQ, "pyzmq", "send zeromq messages from event-hooks"),
|
||||
(HAVE_HEIF, "pillow-heif", "read .heif images with pillow (rarely useful)"),
|
||||
(HAVE_AVIF, "pillow-avif", "read .avif images with pillow (rarely useful)"),
|
||||
(HAVE_RAW, "rawpy", "read RAW images"),
|
||||
]
|
||||
if ANYWIN:
|
||||
to_check += [
|
||||
@ -850,15 +892,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 +901,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 +911,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:
|
||||
|
@ -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
|
||||
if "unix:" in ip:
|
||||
bound: Optional[socket.socket] = None
|
||||
tcp = False
|
||||
|
||||
if "unix:" in ip:
|
||||
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] = {}
|
||||
@ -603,6 +614,10 @@ class TcpSrv(object):
|
||||
|
||||
fg = self.args.qr_fg
|
||||
bg = self.args.qr_bg
|
||||
nocolor = fg == -1
|
||||
if nocolor:
|
||||
fg = 0
|
||||
|
||||
pad = self.args.qrp
|
||||
zoom = self.args.qrz
|
||||
qrc = QrCode.encode_binary(btxt)
|
||||
@ -630,6 +645,8 @@ class TcpSrv(object):
|
||||
|
||||
qr = qr.replace("\n", "\033[K\n") + "\033[K" # win10do
|
||||
cc = " \033[0;38;5;{0};47;48;5;{1}m" if fg else " \033[0;30;47m"
|
||||
if nocolor:
|
||||
cc = " \033[0m"
|
||||
t = cc + "\n{2}\033[999G\033[0m\033[J"
|
||||
t = t.format(fg, bg, qr)
|
||||
if ANYWIN:
|
||||
|
@ -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]
|
||||
|
@ -36,11 +36,15 @@ class ThumbCli(object):
|
||||
if not c:
|
||||
raise Exception()
|
||||
except:
|
||||
c = {k: set() for k in ["thumbable", "pil", "vips", "ffi", "ffv", "ffa"]}
|
||||
c = {
|
||||
k: set()
|
||||
for k in ["thumbable", "pil", "vips", "raw", "ffi", "ffv", "ffa"]
|
||||
}
|
||||
|
||||
self.thumbable = c["thumbable"]
|
||||
self.fmt_pil = c["pil"]
|
||||
self.fmt_vips = c["vips"]
|
||||
self.fmt_raw = c["raw"]
|
||||
self.fmt_ffi = c["ffi"]
|
||||
self.fmt_ffv = c["ffv"]
|
||||
self.fmt_ffa = c["ffa"]
|
||||
|
@ -2,6 +2,7 @@
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import hashlib
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
@ -85,6 +86,9 @@ try:
|
||||
if os.environ.get("PRTY_NO_PIL_HEIF"):
|
||||
raise Exception()
|
||||
|
||||
try:
|
||||
from pillow_heif import register_heif_opener
|
||||
except ImportError:
|
||||
from pyheif_pillow_opener import register_heif_opener
|
||||
|
||||
register_heif_opener()
|
||||
@ -112,14 +116,28 @@ except:
|
||||
|
||||
try:
|
||||
if os.environ.get("PRTY_NO_VIPS"):
|
||||
raise Exception()
|
||||
raise ImportError()
|
||||
|
||||
HAVE_VIPS = True
|
||||
import pyvips
|
||||
|
||||
logging.getLogger("pyvips").setLevel(logging.WARNING)
|
||||
except:
|
||||
except Exception as e:
|
||||
HAVE_VIPS = False
|
||||
if not isinstance(e, ImportError):
|
||||
logging.warning("libvips found, but failed to load: " + str(e))
|
||||
|
||||
|
||||
try:
|
||||
if os.environ.get("PRTY_NO_RAW"):
|
||||
raise Exception()
|
||||
|
||||
HAVE_RAW = True
|
||||
import rawpy
|
||||
|
||||
logging.getLogger("rawpy").setLevel(logging.WARNING)
|
||||
except:
|
||||
HAVE_RAW = False
|
||||
|
||||
|
||||
th_dir_cache = {}
|
||||
@ -205,11 +223,19 @@ class ThumbSrv(object):
|
||||
if self.args.th_clean:
|
||||
Daemon(self.cleaner, "thumb.cln")
|
||||
|
||||
self.fmt_pil, self.fmt_vips, self.fmt_ffi, self.fmt_ffv, self.fmt_ffa = [
|
||||
(
|
||||
self.fmt_pil,
|
||||
self.fmt_vips,
|
||||
self.fmt_raw,
|
||||
self.fmt_ffi,
|
||||
self.fmt_ffv,
|
||||
self.fmt_ffa,
|
||||
) = [
|
||||
set(y.split(","))
|
||||
for y in [
|
||||
self.args.th_r_pil,
|
||||
self.args.th_r_vips,
|
||||
self.args.th_r_raw,
|
||||
self.args.th_r_ffi,
|
||||
self.args.th_r_ffv,
|
||||
self.args.th_r_ffa,
|
||||
@ -232,6 +258,9 @@ class ThumbSrv(object):
|
||||
if "vips" in self.args.th_dec:
|
||||
self.thumbable |= self.fmt_vips
|
||||
|
||||
if "raw" in self.args.th_dec:
|
||||
self.thumbable |= self.fmt_raw
|
||||
|
||||
if "ff" in self.args.th_dec:
|
||||
for zss in [self.fmt_ffi, self.fmt_ffv, self.fmt_ffa]:
|
||||
self.thumbable |= zss
|
||||
@ -313,6 +342,7 @@ class ThumbSrv(object):
|
||||
"thumbable": self.thumbable,
|
||||
"pil": self.fmt_pil,
|
||||
"vips": self.fmt_vips,
|
||||
"raw": self.fmt_raw,
|
||||
"ffi": self.fmt_ffi,
|
||||
"ffv": self.fmt_ffv,
|
||||
"ffa": self.fmt_ffa,
|
||||
@ -368,6 +398,8 @@ class ThumbSrv(object):
|
||||
funs.append(self.conv_pil)
|
||||
elif lib == "vips" and ext in self.fmt_vips:
|
||||
funs.append(self.conv_vips)
|
||||
elif lib == "raw" and ext in self.fmt_raw:
|
||||
funs.append(self.conv_raw)
|
||||
elif can_au and (want_png or want_au):
|
||||
if want_opus:
|
||||
funs.append(self.conv_opus)
|
||||
@ -480,9 +512,7 @@ class ThumbSrv(object):
|
||||
|
||||
return im
|
||||
|
||||
def conv_pil(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||
self.wait4ram(0.2, tpath)
|
||||
with Image.open(fsenc(abspath)) as im:
|
||||
def conv_image_pil(self, im: "Image.Image", tpath: str, fmt: str, vn: VFS) -> None:
|
||||
try:
|
||||
im = self.fancy_pillow(im, fmt, vn)
|
||||
except Exception as ex:
|
||||
@ -510,6 +540,11 @@ class ThumbSrv(object):
|
||||
|
||||
im.save(tpath, **args)
|
||||
|
||||
def conv_pil(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||
self.wait4ram(0.2, tpath)
|
||||
with Image.open(fsenc(abspath)) as im:
|
||||
self.conv_image_pil(im, tpath, fmt, vn)
|
||||
|
||||
def conv_vips(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||
self.wait4ram(0.2, tpath)
|
||||
crops = ["centre", "none"]
|
||||
@ -531,6 +566,50 @@ class ThumbSrv(object):
|
||||
assert img # type: ignore # !rm
|
||||
img.write_to_file(tpath, Q=40)
|
||||
|
||||
def conv_raw(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||
self.wait4ram(0.2, tpath)
|
||||
with rawpy.imread(abspath) as raw:
|
||||
thumb = raw.extract_thumb()
|
||||
if thumb.format == rawpy.ThumbFormat.JPEG and tpath.endswith(".jpg"):
|
||||
# if we have a jpg thumbnail and no webp output is available,
|
||||
# just write the jpg directly (it'll be the wrong size, but it's fast)
|
||||
with open(tpath, "wb") as f:
|
||||
f.write(thumb.data)
|
||||
if HAVE_VIPS:
|
||||
crops = ["centre", "none"]
|
||||
if "f" in fmt:
|
||||
crops = ["none"]
|
||||
w, h = self.getres(vn, fmt)
|
||||
kw = {"height": h, "size": "down", "intent": "relative"}
|
||||
|
||||
for c in crops:
|
||||
try:
|
||||
kw["crop"] = c
|
||||
if thumb.format == rawpy.ThumbFormat.BITMAP:
|
||||
img = pyvips.Image.new_from_array(
|
||||
thumb.data, interpretation="rgb"
|
||||
)
|
||||
img = img.thumbnail_image(w, **kw)
|
||||
else:
|
||||
img = pyvips.Image.thumbnail_buffer(thumb.data, w, **kw)
|
||||
break
|
||||
except:
|
||||
if c == crops[-1]:
|
||||
raise
|
||||
|
||||
assert img # type: ignore # !rm
|
||||
img.write_to_file(tpath, Q=40)
|
||||
elif HAVE_PIL:
|
||||
if thumb.format == rawpy.ThumbFormat.BITMAP:
|
||||
im = Image.fromarray(thumb.data, "RGB")
|
||||
else:
|
||||
im = Image.open(io.BytesIO(thumb.data))
|
||||
self.conv_image_pil(im, tpath, fmt, vn)
|
||||
else:
|
||||
raise Exception(
|
||||
"either pil or vips is needed to process embedded bitmap thumbnails in raw files"
|
||||
)
|
||||
|
||||
def conv_ffmpeg(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||
self.wait4ram(0.2, tpath)
|
||||
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||
@ -583,11 +662,11 @@ class ThumbSrv(object):
|
||||
]
|
||||
|
||||
cmd += [fsenc(tpath)]
|
||||
self._run_ff(cmd, vn)
|
||||
self._run_ff(cmd, vn, "convt")
|
||||
|
||||
def _run_ff(self, cmd: list[bytes], vn: VFS, oom: int = 400) -> None:
|
||||
def _run_ff(self, cmd: list[bytes], vn: VFS, kto: str, oom: int = 400) -> None:
|
||||
# self.log((b" ".join(cmd)).decode("utf-8"))
|
||||
ret, _, serr = runcmd(cmd, timeout=vn.flags["convt"], nice=True, oom=oom)
|
||||
ret, _, serr = runcmd(cmd, timeout=vn.flags[kto], nice=True, oom=oom)
|
||||
if not ret:
|
||||
return
|
||||
|
||||
@ -669,7 +748,7 @@ class ThumbSrv(object):
|
||||
# fmt: on
|
||||
|
||||
cmd += [fsenc(tpath)]
|
||||
self._run_ff(cmd, vn)
|
||||
self._run_ff(cmd, vn, "convt")
|
||||
|
||||
if "pngquant" in vn.flags:
|
||||
wtpath = tpath + ".png"
|
||||
@ -730,7 +809,7 @@ class ThumbSrv(object):
|
||||
b"-y", fsenc(infile),
|
||||
]
|
||||
# fmt: on
|
||||
self._run_ff(cmd, vn)
|
||||
self._run_ff(cmd, vn, "convt")
|
||||
|
||||
fc = "[0:a:0]aresample=48000{},showspectrumpic=s="
|
||||
if "3" in fmt:
|
||||
@ -772,7 +851,7 @@ class ThumbSrv(object):
|
||||
]
|
||||
|
||||
cmd += [fsenc(tpath)]
|
||||
self._run_ff(cmd, vn)
|
||||
self._run_ff(cmd, vn, "convt")
|
||||
|
||||
def conv_mp3(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||
quality = self.args.q_mp3.lower()
|
||||
@ -811,7 +890,7 @@ class ThumbSrv(object):
|
||||
fsenc(tpath)
|
||||
]
|
||||
# fmt: on
|
||||
self._run_ff(cmd, vn, oom=300)
|
||||
self._run_ff(cmd, vn, "aconvt", oom=300)
|
||||
|
||||
def conv_flac(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||
if self.args.no_acode or not self.args.allow_flac:
|
||||
@ -836,7 +915,7 @@ class ThumbSrv(object):
|
||||
fsenc(tpath)
|
||||
]
|
||||
# fmt: on
|
||||
self._run_ff(cmd, vn, oom=300)
|
||||
self._run_ff(cmd, vn, "aconvt", oom=300)
|
||||
|
||||
def conv_wav(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||
if self.args.no_acode or not self.args.allow_wav:
|
||||
@ -871,7 +950,7 @@ class ThumbSrv(object):
|
||||
fsenc(tpath)
|
||||
]
|
||||
# fmt: on
|
||||
self._run_ff(cmd, vn, oom=300)
|
||||
self._run_ff(cmd, vn, "aconvt", oom=300)
|
||||
|
||||
def conv_opus(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||
if self.args.no_acode or not self.args.q_opus:
|
||||
@ -927,7 +1006,7 @@ class ThumbSrv(object):
|
||||
fsenc(tpath)
|
||||
]
|
||||
# fmt: on
|
||||
self._run_ff(cmd, vn, oom=300)
|
||||
self._run_ff(cmd, vn, "aconvt", oom=300)
|
||||
|
||||
def _conv_caf(
|
||||
self,
|
||||
@ -967,7 +1046,7 @@ class ThumbSrv(object):
|
||||
fsenc(tmp_opus)
|
||||
]
|
||||
# fmt: on
|
||||
self._run_ff(cmd, vn, oom=300)
|
||||
self._run_ff(cmd, vn, "aconvt", oom=300)
|
||||
|
||||
# iOS fails to play some "insufficiently complex" files
|
||||
# (average file shorter than 8 seconds), so of course we
|
||||
@ -994,7 +1073,7 @@ class ThumbSrv(object):
|
||||
fsenc(tpath)
|
||||
]
|
||||
# fmt: on
|
||||
self._run_ff(cmd, vn, oom=300)
|
||||
self._run_ff(cmd, vn, "aconvt", oom=300)
|
||||
|
||||
else:
|
||||
# simple remux should be safe
|
||||
@ -1013,7 +1092,7 @@ class ThumbSrv(object):
|
||||
fsenc(tpath)
|
||||
]
|
||||
# fmt: on
|
||||
self._run_ff(cmd, vn, oom=300)
|
||||
self._run_ff(cmd, vn, "aconvt", oom=300)
|
||||
|
||||
try:
|
||||
wunlink(self.log, tmp_opus, vn.flags)
|
||||
|
@ -144,6 +144,7 @@ class Up2k(object):
|
||||
|
||||
self.salt = self.args.warksalt
|
||||
self.r_hash = re.compile("^[0-9a-zA-Z_-]{44}$")
|
||||
self.abrt_key = ""
|
||||
|
||||
self.gid = 0
|
||||
self.gt0 = 0
|
||||
@ -375,11 +376,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)
|
||||
@ -3987,6 +3989,9 @@ class Up2k(object):
|
||||
except:
|
||||
pass
|
||||
|
||||
def handle_fs_abrt(self, akey: str) -> None:
|
||||
self.abrt_key = akey
|
||||
|
||||
def handle_rm(
|
||||
self,
|
||||
uname: str,
|
||||
@ -4196,7 +4201,7 @@ class Up2k(object):
|
||||
|
||||
return n_files, ok + ok2, ng + ng2
|
||||
|
||||
def handle_cp(self, uname: str, ip: str, svp: str, dvp: str) -> str:
|
||||
def handle_cp(self, abrt: str, uname: str, ip: str, svp: str, dvp: str) -> str:
|
||||
if svp == dvp or dvp.startswith(svp + "/"):
|
||||
raise Pebkac(400, "cp: cannot copy parent into subfolder")
|
||||
|
||||
@ -4243,6 +4248,8 @@ class Up2k(object):
|
||||
|
||||
dvpf = dvp + svpf[len(svp) :]
|
||||
self._cp_file(uname, ip, svpf, dvpf, curs)
|
||||
if abrt and abrt == self.abrt_key:
|
||||
raise Pebkac(400, "filecopy aborted by http-api")
|
||||
|
||||
for v in curs:
|
||||
v.connection.commit()
|
||||
@ -4410,7 +4417,7 @@ class Up2k(object):
|
||||
|
||||
return "k"
|
||||
|
||||
def handle_mv(self, uname: str, ip: str, svp: str, dvp: str) -> str:
|
||||
def handle_mv(self, abrt: str, uname: str, ip: str, svp: str, dvp: str) -> str:
|
||||
if svp == dvp or dvp.startswith(svp + "/"):
|
||||
raise Pebkac(400, "mv: cannot move parent into subfolder")
|
||||
|
||||
@ -4465,6 +4472,8 @@ class Up2k(object):
|
||||
|
||||
dvpf = dvp + svpf[len(svp) :]
|
||||
self._mv_file(uname, ip, svpf, dvpf, curs)
|
||||
if abrt and abrt == self.abrt_key:
|
||||
raise Pebkac(400, "filemove aborted by http-api")
|
||||
|
||||
for v in curs:
|
||||
v.connection.commit()
|
||||
|
@ -399,6 +399,9 @@ application swf=x-shockwave-flash m3u=vnd.apple.mpegurl db3=vnd.sqlite3 sqlite=v
|
||||
text ass=plain ssa=plain
|
||||
image jpg=jpeg xpm=x-xpixmap psd=vnd.adobe.photoshop jpf=jpx tif=tiff ico=x-icon djvu=vnd.djvu
|
||||
image heic=heic-sequence heif=heif-sequence hdr=vnd.radiance svg=svg+xml
|
||||
image arw=x-sony-arw cr2=x-canon-cr2 crw=x-canon-crw dcr=x-kodak-dcr dng=x-adobe-dng erf=x-epson-erf
|
||||
image k25=x-kodak-k25 kdc=x-kodak-kdc mrw=x-minolta-mrw nef=x-nikon-nef orf=x-olympus-orf
|
||||
image pef=x-pentax-pef raf=x-fuji-raf raw=x-panasonic-raw sr2=x-sony-sr2 srf=x-sony-srf x3f=x-sigma-x3f
|
||||
audio caf=x-caf mp3=mpeg m4a=mp4 mid=midi mpc=musepack aif=aiff au=basic qcp=qcelp
|
||||
video mkv=x-matroska mov=quicktime avi=x-msvideo m4v=x-m4v ts=mp2t
|
||||
video asf=x-ms-asf flv=x-flv 3gp=3gpp 3g2=3gpp2 rmvb=vnd.rn-realmedia-vbr
|
||||
@ -2982,6 +2985,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]],
|
||||
|
@ -1374,9 +1374,10 @@ 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);
|
||||
background: var(--bg-u5);
|
||||
border: none;
|
||||
box-shadow: 0 0 2px var(--txt-sh);
|
||||
border-bottom: 1px solid #999;
|
||||
@ -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);
|
||||
@ -1925,6 +1930,11 @@ html.y #tree.nowrap .ntree a+a:hover {
|
||||
padding: 0;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
#fs_abrt {
|
||||
margin-top: 1em;
|
||||
text-shadow: 0;
|
||||
box-shadow: 1px 1px 0 var(--bg-d3);
|
||||
}
|
||||
#doc {
|
||||
overflow: visible;
|
||||
background: #fff;
|
||||
@ -3267,3 +3277,790 @@ html.d #treepar {
|
||||
transition: background-color .3s ease, color .3s ease;
|
||||
}
|
||||
}
|
||||
html.ey {
|
||||
--negative-space: 0em; /* Use this to change the global spacing of the 95 theme */
|
||||
--font-main: consolas;
|
||||
--font-serif: consolas;
|
||||
--font-mono: consolas;
|
||||
--w: #fff;
|
||||
--w2: #dfdfdf;
|
||||
--w3: grey;
|
||||
--fg: #000;
|
||||
--fg-max: #0000ff;
|
||||
--fg-weak: #0000ff;
|
||||
--bg: #c6c3c6;
|
||||
--bg-d3: #ff0;
|
||||
--bg-d2: var(--w3);
|
||||
--bg-d1: var(--bg);
|
||||
--bg-u2: var(--bg);
|
||||
--bg-u3: var(--bg);
|
||||
--bg-u5: var(--shadow-color-2);
|
||||
--tab-alt: #00f;
|
||||
--g-fsel-bg: #00f;
|
||||
--g-sel-bg: #00f;
|
||||
--g-fsel-b1: #fff;
|
||||
--row-alt: var(--w);
|
||||
--scroll: var(--silver);
|
||||
--f-sel-sh: transparent;
|
||||
--a: #000;
|
||||
--a-b: #fff;
|
||||
--a-hil: #fff;
|
||||
--a-h-bg: var(--bg);
|
||||
--a-dark: var(--a);
|
||||
--a-gray: var(--fg-weak);
|
||||
--btn-fg: var(--fg);
|
||||
--btn-bg: var(--bg);
|
||||
--btn-h-fg: var(--fg);
|
||||
--btn-h-bg: var(--bg);
|
||||
--btn-1-fg: var(--fg);
|
||||
--btn-1-bg: var(--bg);
|
||||
--btn-1h-bg: var(--bg-d3);
|
||||
--txt-sh: a;
|
||||
--txt-bg: var(--white);
|
||||
--u2-b1-bg: var(--w2);
|
||||
--u2-b2-bg: var(--w2);
|
||||
--u2-txt-bg: var(--w2);
|
||||
--u2-tab-bg: a;
|
||||
--u2-tab-1-bg: var(--w2);
|
||||
--sort-1: var(--fg-weak);
|
||||
--tree-bg: var(--w);
|
||||
--g-b1: a;
|
||||
--g-b2: a;
|
||||
--g-f-bg: var(--w2);
|
||||
--f-sh1: 0.1;
|
||||
--f-sh2: 0.02;
|
||||
--f-sh3: 0.1;
|
||||
--f-h-b1: a;
|
||||
--srv-1: var(--w);
|
||||
--srv-3: var(--a);
|
||||
--mp-sh: a;
|
||||
--black: #000;
|
||||
--white: #fff;
|
||||
--grey: grey;
|
||||
--silver: silver;
|
||||
--transparent: transparent;
|
||||
--shadow-color-1: #0a0a0a;
|
||||
--shadow-color-2: #808080;
|
||||
--border-dashed-black: 1px dashed var(--black);
|
||||
--radius: 0;
|
||||
--focus-outline: 1px dashed var(--black);
|
||||
--hover-outline: 1px dotted var(--black);
|
||||
--fm-off: var(--w3);
|
||||
--ttlbar: linear-gradient(90deg, navy, #1084d0);
|
||||
--inset-bg: var(--white);
|
||||
--scroll-bkg: var(--white);
|
||||
|
||||
/*All sides*/
|
||||
--shadow-outset: inset -1px -1px var(--shadow-color-1),
|
||||
inset 1px 1px var(--white), inset -2px -2px var(--grey),
|
||||
inset 2px 2px var(--w2);
|
||||
|
||||
--shadow-inset: inset -1px -1px var(--white),
|
||||
inset 1px 1px var(--shadow-color-1), inset -2px -2px var(--w2),
|
||||
inset 2px 2px var(--shadow-color-2);
|
||||
|
||||
--shadow-input: inset -1px -1px var(--white), inset 1px 1px var(--grey),
|
||||
inset -2px -2px var(--w2), inset 2px 2px var(--shadow-color-1);
|
||||
|
||||
/*Indiv sides*/
|
||||
--shadow-outset-bottom: inset 0 -1px var(--shadow-color-1),
|
||||
inset 0 -2px var(--grey);
|
||||
--shadow-outset-right: inset -1px 0 var(--shadow-color-1),
|
||||
inset -2px 0 var(--grey);
|
||||
--shadow-outset-left: inset 1px 0 var(--white), inset 2px 0 var(--w2);
|
||||
--shadow-outset-top: inset 0 1px var(--white), inset 0 2px var(--w2);
|
||||
|
||||
--shadow-inset-bottom: inset 0 -1px var(--white), inset 0 -2px var(--w2);
|
||||
--shadow-inset-right: inset -1px 0 var(--white), inset -2px 0 var(--w2);
|
||||
--shadow-inset-left: inset 1px 0 var(--shadow-color-1),
|
||||
inset 2px 0 var(--shadow-color-2);
|
||||
--shadow-inset-top: inset 0 1px var(--shadow-color-1),
|
||||
inset 0 2px var(--shadow-color-2);
|
||||
}
|
||||
html.ez {
|
||||
--negative-space: 0em; /* Use this to change the global spacing of your theme :) */
|
||||
--font-main: consolas;
|
||||
--font-serif: consolas;
|
||||
--font-mono: consolas;
|
||||
--w: #fff;
|
||||
--w2: var(--inset-bg);
|
||||
--w3: grey;
|
||||
--fg: #cfcfcf;
|
||||
--fg-max: #47b8ff;
|
||||
--fg-weak: #47b8ff;
|
||||
--bg: #383838;
|
||||
--bg-d3: #600000;
|
||||
--bg-d2: var(--shadow-color-1);
|
||||
--bg-d1: var(--bg);
|
||||
--u2-tab-1-fg: #ff0;
|
||||
--bg-u2: var(--bg);
|
||||
--bg-u3: var(--bg);
|
||||
--bg-u5: var(--shadow-color-2);
|
||||
--tab-alt: #47b8ff;
|
||||
--g-fsel-bg: #0000b7;
|
||||
--g-sel-bg: #00f;
|
||||
--g-fsel-b1: #fff;
|
||||
--row-alt: #555555;
|
||||
--scroll: #555555;
|
||||
--f-sel-sh: transparent;
|
||||
--a: var(--fg);
|
||||
--a-b: var(--fg);
|
||||
--a-hil: var(--fg);
|
||||
--btn-1h-bg: var(--bg-d3);
|
||||
--a-h-bg: var(--bg);
|
||||
--a-dark: var(--a);
|
||||
--a-gray: var(--fg-weak);
|
||||
--btn-fg: var(--white);
|
||||
--btn-bg: var(--bg);
|
||||
--btn-h-fg: var(--white);
|
||||
--btn-h-bg: var(--bg);
|
||||
--btn-1-fg: var(--white);
|
||||
--btn-1-bg: var(--bg);
|
||||
--txt-sh: a;
|
||||
--u2-b1-bg: var(--w2);
|
||||
--u2-b2-bg: var(--w2);
|
||||
--u2-txt-bg: var(--w2);
|
||||
--u2-tab-bg: a;
|
||||
--u2-tab-1-bg: var(--w2);
|
||||
--sort-1: var(--fg-weak);
|
||||
--g-b1: a;
|
||||
--g-b2: a;
|
||||
--g-f-bg: var(--w2);
|
||||
--f-sh1: 0.1;
|
||||
--f-sh2: 0.02;
|
||||
--f-sh3: 0.1;
|
||||
--f-h-b1: a;
|
||||
--srv-1: var(--w);
|
||||
--srv-3: var(--a);
|
||||
--mp-sh: a;
|
||||
--black: #000;
|
||||
--white: #fff;
|
||||
--grey: grey;
|
||||
--silver: #858585;
|
||||
--transparent: transparent;
|
||||
--shadow-color-1: #101010;
|
||||
--shadow-color-2: #1f1f1f;
|
||||
--border-dashed-black: 1px dashed var(--shadow-color-1);
|
||||
--radius: 0;
|
||||
--focus-outline: 1px dashed var(--white);
|
||||
--hover-outline: 1px dotted var(--white);
|
||||
--fm-off: var(--w3);
|
||||
--ttlbar: linear-gradient(90deg, var(--shadow-color-1) 20%, #888888);
|
||||
--inset-bg: #3f3f3f;
|
||||
--tree-bg: var(--inset-bg);
|
||||
--txt-bg: var(--inset-bg);
|
||||
--scroll-bkg: var(--black);
|
||||
|
||||
/*All sides*/
|
||||
--shadow-outset: inset -1px -1px var(--shadow-color-1), inset 1px 1px #878787,
|
||||
inset -2px -2px var(--shadow-color-2), inset 2px 2px #575757;
|
||||
|
||||
--shadow-inset: inset -1px -1px #878787, inset 1px 1px var(--shadow-color-1),
|
||||
inset -2px -2px #575757, inset 2px 2px var(--shadow-color-2);
|
||||
|
||||
--shadow-input: inset -1px -1px var(--white),
|
||||
inset 1px 1px var(--shadow-color-2), inset -2px -2px #575757,
|
||||
inset 2px 2px var(--shadow-color-1);
|
||||
|
||||
--shadow-outset-bottom: inset 0 -1px var(--shadow-color-1),
|
||||
inset 0 -2px var(--shadow-color-2);
|
||||
--shadow-outset-right: inset -1px 0 var(--shadow-color-1),
|
||||
inset -2px 0 var(--shadow-color-2);
|
||||
--shadow-outset-left: inset 1px 0 #878787, inset 2px 0 #575757;
|
||||
--shadow-outset-top: inset 0 1px #878787, inset 0 2px #575757;
|
||||
|
||||
--shadow-inset-bottom: inset 0 -1px #878787, inset 0 -2px #575757;
|
||||
--shadow-inset-right: inset -1px 0 #878787, inset -2px 0 #575757;
|
||||
--shadow-inset-left: inset 1px 0 var(--shadow-color-1),
|
||||
inset 2px 0 var(--shadow-color-2);
|
||||
--shadow-inset-top: inset 0 1px var(--shadow-color-1),
|
||||
inset 0 2px var(--shadow-color-2);
|
||||
}
|
||||
|
||||
html.e {
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
html.e #files,
|
||||
html.e #u2conf input[type="checkbox"]:hover + label,
|
||||
html.e .tgl.btn.on:hover,
|
||||
html.e body {
|
||||
background: var(--bg);
|
||||
}
|
||||
html.e #pctl a,
|
||||
html.e #repl,
|
||||
html.e #u2conf a,
|
||||
html.e #u2conf input[type="checkbox"] + label,
|
||||
html.e #wfp a,
|
||||
html.e .btn,
|
||||
html.e .eq_step,
|
||||
html.e input[type="submit"] {
|
||||
box-shadow: var(--shadow-outset);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg);
|
||||
border: 0;
|
||||
}
|
||||
a.s0r,
|
||||
html.e #ghead a.s0,
|
||||
html.e #u2conf input[type="checkbox"]:checked + label,
|
||||
html.e .tgl.btn.on,
|
||||
html.e input[type="submit"]:active {
|
||||
box-shadow: var(--shadow-inset) !important;
|
||||
}
|
||||
html.e #ops a:hover,
|
||||
html.e #pctl a:hover,
|
||||
html.e #repl:hover,
|
||||
html.e #u2conf a:hover,
|
||||
html.e #u2conf input[type="checkbox"]:hover + label,
|
||||
html.e #wfp a:hover,
|
||||
html.e .btn:hover,
|
||||
html.e .eq_step:hover,
|
||||
html.e input[type="submit"]:hover {
|
||||
outline: var(--hover-outline);
|
||||
outline-offset: -4px;
|
||||
}
|
||||
html.e .ntree a:hover,
|
||||
html.e :focus,
|
||||
html.e :focus + label,
|
||||
html.e a:active,
|
||||
html.e tr:focus,
|
||||
input[type="text"]:focus {
|
||||
outline: var(--focus-outline) !important;
|
||||
}
|
||||
html.e tr:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
html.e #pctl a:focus,
|
||||
html.e #repl:hover,
|
||||
html.e #u2conf input[type="checkbox"]:focus + label,
|
||||
html.e #wfp a:focus,
|
||||
html.e .btn:focus,
|
||||
html.e .eq_step:focus {
|
||||
border: 0 !important;
|
||||
outline: var(--focus-outline) !important;
|
||||
outline-offset: 2px;
|
||||
box-shadow: var(--shadow-outset) !important;
|
||||
}
|
||||
html.e #files tbody,
|
||||
html.e #u2cards a.act {
|
||||
box-shadow: var(--shadow-inset);
|
||||
}
|
||||
html.e #files {
|
||||
border: 2px groove var(--transparent);
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 0.3em;
|
||||
top: 0;
|
||||
border: 0;
|
||||
}
|
||||
html.e #files tbody tr td,
|
||||
html.e #files thead th {
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
#files td {
|
||||
background: var(--w2);
|
||||
}
|
||||
html.e #files tr {
|
||||
background-color: var(--black);
|
||||
}
|
||||
html.e #srv_info span,
|
||||
html.e label {
|
||||
color: var(--btn-fg) !important;
|
||||
}
|
||||
html.e #acc_info {
|
||||
background: var(--transparent);
|
||||
color: var(--white);
|
||||
height: 2em;
|
||||
left: 1em;
|
||||
width: fit-content;
|
||||
}
|
||||
html.e #acc_info,
|
||||
html.e #ops,
|
||||
html.e #srv_info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
html.e #flogout:before {
|
||||
padding-left: 0.2em;
|
||||
padding-right: 0.4em;
|
||||
content: " | ";
|
||||
}
|
||||
html.e #blogout {
|
||||
color: var(--w);
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
}
|
||||
html.e .opwide > div {
|
||||
border-left: 1px solid var(--fg);
|
||||
}
|
||||
html.e #srv_info {
|
||||
background: var(--transparent);
|
||||
color: var(--white);
|
||||
height: fit-content;
|
||||
top: 3.2em;
|
||||
left: 1em;
|
||||
gap: 0.2em;
|
||||
}
|
||||
html.e #u2cards a.act {
|
||||
padding: 0.2em 1em;
|
||||
}
|
||||
html.e #u2btn {
|
||||
border: var(--border-dashed-black);
|
||||
border-radius: var(--border-radius);
|
||||
transform: translateY(30%);
|
||||
}
|
||||
html.e #ops,
|
||||
html.e #ops a {
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
@media only screen and (max-width: 600px) {
|
||||
html.e #acc_info {
|
||||
background: var(--transparent);
|
||||
color: var(--white);
|
||||
height: fit-content;
|
||||
align-items: center;
|
||||
top: 3.2em;
|
||||
right: 1em;
|
||||
left: auto;
|
||||
display: flex;
|
||||
gap: 0.2em;
|
||||
}
|
||||
html.e #u2btn {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
html.e #ops {
|
||||
background: var(--ttlbar);
|
||||
/*HC*/
|
||||
box-shadow: inset 0-1px grey, inset 0-2px var(--shadow-color-1);
|
||||
height: 2em;
|
||||
gap: 0.6em;
|
||||
padding: 0.2em;
|
||||
flex-direction: row-reverse;
|
||||
margin-bottom: 1.2em;
|
||||
}
|
||||
html.e #srch_form,
|
||||
html.e .opbox {
|
||||
padding-bottom: 1em;
|
||||
padding-top: 1em;
|
||||
max-width: 100vw;
|
||||
}
|
||||
html.e #ghead,
|
||||
html.e #ops a {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
html.e #ops a {
|
||||
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.5);
|
||||
height: 1.4em;
|
||||
padding: 0;
|
||||
box-shadow: var(--shadow-outset);
|
||||
background: var(--bg);
|
||||
aspect-ratio: 1/1;
|
||||
justify-content: center;
|
||||
font-size: 1.25em;
|
||||
z-index: 4;
|
||||
}
|
||||
html.e #blogout:focus,
|
||||
html.e #ops a:focus {
|
||||
outline: 1px dashed var(--w) !important;
|
||||
}
|
||||
|
||||
html.e #blogout:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
html.e #ops > a:not(:first-child).act {
|
||||
height: 1.4em;
|
||||
width: 1.4em;
|
||||
padding-bottom: 0.3em;
|
||||
margin-top: 0.3em;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
box-shadow: var(--shadow-inset-left), var(--shadow-inset-top),
|
||||
var(--shadow-inset-right);
|
||||
z-index: 6;
|
||||
}
|
||||
html.e #ops a.act {
|
||||
box-shadow: var(--shadow-inset);
|
||||
border-bottom: 0;
|
||||
}
|
||||
html.e a:active {
|
||||
border: 0;
|
||||
}
|
||||
html.e :focus,
|
||||
html.e :focus + label {
|
||||
border: 0 !important;
|
||||
outline-offset: 1px;
|
||||
border-radius: var(--radius) !important;
|
||||
box-shadow: inherit;
|
||||
}
|
||||
html.e #opa_x {
|
||||
text-shadow: 0 0 0 var(--transparent) !important;
|
||||
color: var(--bg) !important;
|
||||
display: flex;
|
||||
}
|
||||
html.e #opa_x:before {
|
||||
content: "⨯";
|
||||
color: var(--fg) !important;
|
||||
margin-top: -0.1em;
|
||||
font-size: 1.75em;
|
||||
position: absolute;
|
||||
}
|
||||
html.e .opbox {
|
||||
margin: -1.2em 0 0;
|
||||
box-shadow: var(--shadow-inset-bottom), var(--shadow-inset-left),
|
||||
var(--shadow-inset-right);
|
||||
border-radius: var(--radius);
|
||||
z-index: 5;
|
||||
background: var(--bg);
|
||||
}
|
||||
html.e #srch_form {
|
||||
margin: 0;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
html.e #op_unpost {
|
||||
max-width: 100vw;
|
||||
margin: 0;
|
||||
}
|
||||
html.e label:focus {
|
||||
box-shadow: 0 0;
|
||||
}
|
||||
html.e #tree {
|
||||
box-shadow: none;
|
||||
padding-right: 5px;
|
||||
}
|
||||
html.e #tt {
|
||||
background: var(--w2);
|
||||
}
|
||||
html.e .mdo a {
|
||||
background: 0 0;
|
||||
text-decoration: underline;
|
||||
}
|
||||
html.e .mdo code,
|
||||
html.e .mdo pre {
|
||||
color: var(--white);
|
||||
background: var(--w2);
|
||||
border: 0;
|
||||
}
|
||||
html.e .mdo h1,
|
||||
html.e .mdo h2 {
|
||||
background: 0 0;
|
||||
border-color: var(--w2);
|
||||
}
|
||||
html.e #tt,
|
||||
html.e .mdo ol ol,
|
||||
html.e .mdo ol ul,
|
||||
html.e .mdo ul ol,
|
||||
html.e .mdo ul ul {
|
||||
border-color: var(--w2);
|
||||
}
|
||||
html.e .mdo li > em,
|
||||
html.e .mdo p > em,
|
||||
html.e .mdo td > em {
|
||||
color: #fd0;
|
||||
}
|
||||
html.e input.txtbox,
|
||||
html.e input[type="text"],
|
||||
html.e select {
|
||||
background-color: var(--txt-bg);
|
||||
box-shadow: var(--shadow-input) !important;
|
||||
box-sizing: border-box;
|
||||
padding: 3px 4px;
|
||||
border-radius: var(--radius);
|
||||
border: 0;
|
||||
}
|
||||
html.e #gfiles {
|
||||
box-shadow: var(--shadow-outset);
|
||||
background: var(--bg);
|
||||
padding: 0.4em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3em;
|
||||
}
|
||||
html.e #ggrid {
|
||||
background-color: var(--inset-bg);
|
||||
box-shadow: var(--shadow-input);
|
||||
padding: 1.5em;
|
||||
margin: 0;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
html.e #ghead {
|
||||
margin: 0;
|
||||
justify-content: flex-end;
|
||||
gap: 0.4em;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
top: 0px;
|
||||
border-radius: 0px;
|
||||
}
|
||||
html.e #ghead a {
|
||||
margin: 0;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
html.e ::-webkit-scrollbar,
|
||||
html.e::-webkit-scrollbar {
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
background: var(--transparent) !important;
|
||||
}
|
||||
html.e ::-webkit-scrollbar-button,
|
||||
html.e ::-webkit-scrollbar-thumb,
|
||||
html.e::-webkit-scrollbar-button,
|
||||
html.e::-webkit-scrollbar-thumb {
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
background: var(--scroll) !important;
|
||||
/*HC*/
|
||||
box-shadow: var(--shadow-outset);
|
||||
border: 1px solid !important;
|
||||
border-color: var(--silver) var(--black) var(--black) var(--silver) !important;
|
||||
}
|
||||
html.e ::-webkit-scrollbar-track,
|
||||
html.e::-webkit-scrollbar-track {
|
||||
image-rendering: optimize-contrast !important;
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgLTAuNSAyIDIiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyI+CjxtZXRhZGF0YT5NYWRlIHdpdGggUGl4ZWxzIHRvIFN2ZyBodHRwczovL2NvZGVwZW4uaW8vc2hzaGF3L3Blbi9YYnh2Tmo8L21ldGFkYXRhPgo8cGF0aCBzdHJva2U9IiNjMGMwYzAiIGQ9Ik0wIDBoMU0xIDFoMSIgLz4KPC9zdmc+) !important;
|
||||
background-position: 0 0 !important;
|
||||
background-repeat: repeat !important;
|
||||
background-size: 2px !important;
|
||||
background: var(--scroll-bkg);
|
||||
}
|
||||
#tree::-webkit-scrollbar,
|
||||
#tree::-webkit-scrollbar-track {
|
||||
background: var(--scroll-bkg);
|
||||
}
|
||||
html.e ::-webkit-scrollbar-button,
|
||||
html.e::-webkit-scrollbar-button {
|
||||
background-repeat: no-repeat !important;
|
||||
background-size: 16px !important;
|
||||
}
|
||||
html.e ::-webkit-scrollbar-button:single-button:vertical:decrement,
|
||||
html.e::-webkit-scrollbar-button:single-button:vertical:decrement {
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgLTAuNSAxNiAxNiIgc2hhcGUtcmVuZGVyaW5nPSJjcmlzcEVkZ2VzIj4KPG1ldGFkYXRhPk1hZGUgd2l0aCBQaXhlbHMgdG8gU3ZnIGh0dHBzOi8vY29kZXBlbi5pby9zaHNoYXcvcGVuL1hieHZOajwvbWV0YWRhdGE+CjxwYXRoIHN0cm9rZT0iIzAwMDAwMCIgZD0iTTcgNWgxTTYgNmgzTTUgN2g1TTQgOGg3IiAvPgo8L3N2Zz4=) !important;
|
||||
}
|
||||
html.e ::-webkit-scrollbar-button:single-button:vertical:increment,
|
||||
html.e::-webkit-scrollbar-button:single-button:vertical:increment {
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgLTAuNSAxNiAxNiIgc2hhcGUtcmVuZGVyaW5nPSJjcmlzcEVkZ2VzIj4KPG1ldGFkYXRhPk1hZGUgd2l0aCBQaXhlbHMgdG8gU3ZnIGh0dHBzOi8vY29kZXBlbi5pby9zaHNoYXcvcGVuL1hieHZOajwvbWV0YWRhdGE+CjxwYXRoIHN0cm9rZT0iIzAwMDAwMCIgZD0iTTQgNWg3TTUgNmg1TTYgN2gzTTcgOGgxIiAvPgo8L3N2Zz4=) !important;
|
||||
}
|
||||
html.e ::-webkit-scrollbar-button:single-button:horizontal:decrement,
|
||||
html.e::-webkit-scrollbar-button:single-button:horizontal:decrement {
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgLTAuNSAxNiAxNiIgc2hhcGUtcmVuZGVyaW5nPSJjcmlzcEVkZ2VzIj4KPG1ldGFkYXRhPk1hZGUgd2l0aCBQaXhlbHMgdG8gU3ZnIGh0dHBzOi8vY29kZXBlbi5pby9zaHNoYXcvcGVuL1hieHZOajwvbWV0YWRhdGE+CjxwYXRoIHN0cm9rZT0iIzAwMDAwMCIgZD0iTTggM2gxTTcgNGgyTTYgNWgzTTUgNmg0TTYgN2gzTTcgOGgyTTggOWgxIiAvPgo8L3N2Zz4=) !important;
|
||||
}
|
||||
html.e ::-webkit-scrollbar-button:single-button:horizontal:increment,
|
||||
html.e::-webkit-scrollbar-button:single-button:horizontal:increment {
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgLTAuNSAxNiAxNiIgc2hhcGUtcmVuZGVyaW5nPSJjcmlzcEVkZ2VzIj4KPG1ldGFkYXRhPk1hZGUgd2l0aCBQaXhlbHMgdG8gU3ZnIGh0dHBzOi8vY29kZXBlbi5pby9zaHNoYXcvcGVuL1hieHZOajwvbWV0YWRhdGE+CjxwYXRoIHN0cm9rZT0iIzAwMDAwMCIgZD0iTTYgM2gxTTYgNGgyTTYgNWgzTTYgNmg0TTYgN2gzTTYgOGgyTTYgOWgxIiAvPgo8L3N2Zz4=) !important;
|
||||
}
|
||||
html.e ::-webkit-scrollbar-corner,
|
||||
html.e::-webkit-scrollbar-corner {
|
||||
background: var(--silver) !important;
|
||||
}
|
||||
html,
|
||||
html.e #tree {
|
||||
scrollbar-color: inherit !important;
|
||||
}
|
||||
html.e #tree {
|
||||
background: var(--bg);
|
||||
padding-left: 0.4em;
|
||||
padding-top: 0;
|
||||
margin-left: var(--negative-space);
|
||||
}
|
||||
html.e.noscroll #tree {
|
||||
/*HC*/
|
||||
box-shadow: 1px 1px var(--grey), 2px 2px var(--shadow-color-1),
|
||||
var(--shadow-outset-bottom);
|
||||
}
|
||||
html.e #treeh {
|
||||
background: var(--bg);
|
||||
box-shadow: var(--shadow-outset-top), var(--shadow-outset-bottom);
|
||||
width: calc(1.5em + var(--nav-sz) - var(--sbw));
|
||||
height: 2.4em;
|
||||
border: none;
|
||||
top: -2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6em;
|
||||
}
|
||||
html.e #treeh .btn {
|
||||
margin: 0px;
|
||||
top: auto;
|
||||
}
|
||||
html.e #tree ul {
|
||||
border-left: var(--border-dashed-black);
|
||||
margin-left: 2.15em;
|
||||
}
|
||||
html.e .ntree a:first-child {
|
||||
font-family: scp, monospace, monospace;
|
||||
font-size: 1.2em;
|
||||
line-height: 0;
|
||||
background: var(--inset-bg);
|
||||
aspect-ratio: 1/1;
|
||||
text-align: center;
|
||||
align-content: center;
|
||||
border-radius: var(--radius) !important;
|
||||
padding: 0.057em;
|
||||
border: 1px solid var(--black);
|
||||
}
|
||||
html.e .ntree a:first-child:after {
|
||||
content: ".";
|
||||
position: absolute;
|
||||
border-top: var(--border-dashed-black);
|
||||
color: var(--transparent);
|
||||
font-size: 0.9em;
|
||||
margin-left: 0.13em;
|
||||
}
|
||||
html.e #treeul {
|
||||
border: 0 !important;
|
||||
position: static;
|
||||
margin: 0 !important;
|
||||
min-height: 100%;
|
||||
height: max-content;
|
||||
}
|
||||
html.e .ntree a:last-of-type:before {
|
||||
content: "📁";
|
||||
margin-left: 0.3em;
|
||||
}
|
||||
html.e .ntree {
|
||||
padding-left: 1em !important;
|
||||
padding-top: 0.3em !important;
|
||||
background: var(--inset-bg);
|
||||
box-shadow: var(--shadow-inset-left), var(--shadow-inset-bottom);
|
||||
}
|
||||
html.e #tree li {
|
||||
margin-left: -0.5em;
|
||||
border-top: 0;
|
||||
}
|
||||
html.e .ntree a:hover {
|
||||
outline-offset: -2px;
|
||||
color: var(--fg);
|
||||
border-radius: var(--radius) !important;
|
||||
}
|
||||
html.e #treepar {
|
||||
width: calc(-1em + var(--nav-sz) - var(--sbw));
|
||||
overflow: hidden;
|
||||
left: -0.7em;
|
||||
box-shadow: var(--shadow-inset-left), var(--shadow-inset-top);
|
||||
border-left: 0 !important;
|
||||
border-bottom: var(--border-dashed-black);
|
||||
margin-left: calc(2.1em - (1em - var(--negative-space))) !important;
|
||||
}
|
||||
html.e #path,
|
||||
html.e #widgeti,
|
||||
html.e #wtoggle,
|
||||
html.e #wtoggle a,
|
||||
html.e #files,
|
||||
html.e #files thead th,
|
||||
html.e #ghead a,
|
||||
html.e #tree {
|
||||
box-shadow: var(--shadow-outset);
|
||||
}
|
||||
html.e.noscroll #treepar {
|
||||
width: calc(var(--nav-sz) - 1em);
|
||||
}
|
||||
html.e #docul {
|
||||
border-left: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
html.e #wrap {
|
||||
transform: translateX(calc((var(--negative-space) * 2) - 1.2em));
|
||||
padding-right: var(--negative-space);
|
||||
position: relative;
|
||||
margin-right: calc((var(--negative-space) * 2) - 1.2em);
|
||||
margin-top: var(--negative-space);
|
||||
margin-left: 1.2em;
|
||||
/*overflow-x: auto; fix for OOB table when screen space is limited (mobile), but removes sticky header*/
|
||||
}
|
||||
html.e input[type="radio"] {
|
||||
accent-color: #232323;
|
||||
}
|
||||
html.e #path {
|
||||
width: calc(100% - 0.4em);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: 0.2em;
|
||||
overflow-x: auto;
|
||||
}
|
||||
html.e #path i {
|
||||
border: 1px solid var(--w);
|
||||
border-color: var(--w);
|
||||
margin: 0;
|
||||
border-width: 0.1em 0.1em 0 0;
|
||||
height: 0.5em;
|
||||
width: 0.5em;
|
||||
}/*
|
||||
html.e #hovertree:after {
|
||||
color: red;
|
||||
content: "BUGGY";
|
||||
html.ez #hovertree:after {
|
||||
color: rgb(255 98 98);
|
||||
content: "BUGGY";
|
||||
}
|
||||
}*/
|
||||
html.e #widget {
|
||||
box-shadow: 0 0;
|
||||
border: 0 !important;
|
||||
}
|
||||
html.e #wtico,
|
||||
html.e #zip1 {
|
||||
box-shadow: 0 0 !important;
|
||||
}
|
||||
html.e #wtgrid {
|
||||
top: -0.09em;
|
||||
}
|
||||
html.e #wfs,
|
||||
html.e #wm3u,
|
||||
html.e #wnp,
|
||||
html.e #wzip {
|
||||
border-width: 0 1px 0 0;
|
||||
}
|
||||
html.e #wfm.act + #wzip1 + #wzip,
|
||||
html.e #wfm.act + #wzip1 + #wzip + #wnp {
|
||||
border-left-width: 1px;
|
||||
}
|
||||
html.e #barpos {
|
||||
/* border-radius: var(--radius); */
|
||||
box-shadow: var(--shadow-inset);
|
||||
}
|
||||
html.e #goh + span {
|
||||
border-left: 0.1em solid var(--bg-u5);
|
||||
}
|
||||
html.e #wfp {
|
||||
margin: var(--negative-space);
|
||||
font-size: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
html.e #wfp a {
|
||||
font-size: large;
|
||||
display: inline-block;
|
||||
}
|
||||
html.e #repl {
|
||||
font-size: large;
|
||||
padding: 0.33em;
|
||||
right: calc(var(--negative-space) * 0.89);
|
||||
position: absolute;
|
||||
}
|
||||
html.e #epi {
|
||||
text-align: center;
|
||||
text-wrap-mode: nowrap;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
html.e #epi.logue:not(.mdo) {
|
||||
padding: 0.8em;
|
||||
box-shadow: var(--shadow-outset);
|
||||
}
|
||||
|
||||
html.e #epi.logue.mdo {
|
||||
padding-left: 3px;
|
||||
}
|
||||
|
||||
html.e #doc {
|
||||
box-shadow: var(--shadow-inset);
|
||||
background: var(--inset-bg);
|
||||
margin: 0.2em;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
html.e #detree {
|
||||
padding: 0px;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -19,14 +19,7 @@
|
||||
<a href="{{ r }}/?h">control-panel</a>
|
||||
Filter: <input type="text" id="filter" size="20" placeholder="documents/passwords" />
|
||||
<span id="hits"></span>
|
||||
<table id="tab"><thead><tr>
|
||||
<th>size</th>
|
||||
<th>who</th>
|
||||
<th>when</th>
|
||||
<th>age</th>
|
||||
<th>dir</th>
|
||||
<th>file</th>
|
||||
</tr></thead><tbody id="tb"></tbody></table>
|
||||
<div id="tw"></div>
|
||||
</div>
|
||||
<a href="#" id="repl">π</a>
|
||||
<script>
|
||||
|
@ -1,5 +1,6 @@
|
||||
function render() {
|
||||
var ups = V.ups, now = V.now, html = [];
|
||||
var html = ['<table id="tab"><thead><tr><th>size</th><th>who</th><th>when</th><th>age</th><th>dir</th><th>file</th></tr></thead><tbody>'];
|
||||
var ups = V.ups, now = V.now;
|
||||
ebi('filter').value = V.filter;
|
||||
ebi('hits').innerHTML = 'showing ' + ups.length + ' files';
|
||||
|
||||
@ -26,7 +27,8 @@ function render() {
|
||||
var t = V.filter ? ' matching the filter' : '';
|
||||
html = ['<tr><td colspan="6">there are no uploads' + t + '</td></tr>'];
|
||||
}
|
||||
ebi('tb').innerHTML = html.join('');
|
||||
html.push('</tbody></table>');
|
||||
ebi('tw').innerHTML = html.join('\n');
|
||||
}
|
||||
render();
|
||||
|
||||
@ -46,7 +48,7 @@ function ask(e) {
|
||||
V = JSON.parse(this.responseText)
|
||||
}
|
||||
catch (ex) {
|
||||
ebi('tb').innerHTML = '<tr><td colspan="6">failed to decode server response as json: <pre>' + esc(this.responseText) + '</pre></td></tr>';
|
||||
ebi('tw').innerHTML = 'failed to decode server response as json: <pre>' + esc(this.responseText) + '</pre>';
|
||||
return;
|
||||
}
|
||||
render();
|
||||
|
@ -1,11 +1,9 @@
|
||||
var SRS = SR.trimEnd('/') + '/';
|
||||
|
||||
var t = QSA('a[k]');
|
||||
for (var a = 0; a < t.length; a++)
|
||||
t[a].onclick = rm;
|
||||
|
||||
function rm() {
|
||||
var u = SRS + '?eshare=rm&skey=' + uricom_enc(this.getAttribute('k')),
|
||||
var u = SR + '/?eshare=rm&skey=' + uricom_enc(this.getAttribute('k')),
|
||||
xhr = new XHR();
|
||||
|
||||
xhr.open('POST', u, true);
|
||||
@ -15,7 +13,7 @@ function rm() {
|
||||
|
||||
function bump() {
|
||||
var k = this.closest('tr').getElementsByTagName('a')[2].getAttribute('k'),
|
||||
u = SRS + '?skey=' + uricom_enc(k) + '&eshare=' + this.value,
|
||||
u = SR + '/?skey=' + uricom_enc(k) + '&eshare=' + this.value,
|
||||
xhr = new XHR();
|
||||
|
||||
xhr.open('POST', u, true);
|
||||
@ -27,7 +25,7 @@ function cb() {
|
||||
if (this.status !== 200)
|
||||
return modal.alert('<h6>server error</h6>' + esc(unpre(this.responseText)));
|
||||
|
||||
document.location = '?shares';
|
||||
location = '?shares';
|
||||
}
|
||||
|
||||
function qr(e) {
|
||||
|
@ -24,6 +24,7 @@ h1 {
|
||||
li {
|
||||
margin: 1em 0;
|
||||
}
|
||||
#lo,
|
||||
a {
|
||||
color: #047;
|
||||
background: #fff;
|
||||
@ -47,6 +48,7 @@ td a {
|
||||
float: right;
|
||||
margin: -.2em 0 0 .8em;
|
||||
}
|
||||
#lo,
|
||||
.logout,
|
||||
a.r {
|
||||
color: #c04;
|
||||
@ -176,12 +178,14 @@ html.z {
|
||||
html.z h1 {
|
||||
border-color: #777;
|
||||
}
|
||||
html.z #lo,
|
||||
html.z a {
|
||||
color: #fff;
|
||||
background: #057;
|
||||
border-color: #37a;
|
||||
}
|
||||
html.z .logout,
|
||||
html.z #lo,
|
||||
html.z a.r {
|
||||
background: #804;
|
||||
border-color: #c28;
|
||||
|
@ -15,14 +15,14 @@
|
||||
<body>
|
||||
<div id="wrap">
|
||||
{%- if not in_shr %}
|
||||
<a id="a" href="{{ r }}/?h" class="af">refresh</a>
|
||||
<a id="a" href="{{ r }}/?h{{ re }}" class="af">refresh</a>
|
||||
<a id="v" href="{{ r }}/?hc" class="af">connect</a>
|
||||
|
||||
{%- if this.uname == '*' %}
|
||||
<p id="b">howdy stranger <small>(you're not logged in)</small></p>
|
||||
{%- else %}
|
||||
<a id="c" href="{{ r }}/?pw=x" class="logout">logout</a>
|
||||
<p><span id="m">welcome back,</span> <strong>{{ this.uname|e }}</strong></p>
|
||||
<p><span id="m">welcome back,</span> <strong id="un">{{ this.uname|e }}</strong></p>
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
|
||||
@ -120,7 +120,12 @@
|
||||
<div>
|
||||
<form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
|
||||
<input type="hidden" id="la" name="act" value="login" />
|
||||
{% if this.args.usernames %}
|
||||
<input type="text" id="lu" name="uname" placeholder=" username" size="12" />
|
||||
<input type="password" id="lp" name="cppwd" placeholder=" password" size="12" />
|
||||
{% else %}
|
||||
<input type="password" id="lp" name="cppwd" placeholder=" password" />
|
||||
{% endif %}
|
||||
<input type="hidden" name="uhash" id="uhash" value="x" />
|
||||
<input type="submit" id="ls" value="login" />
|
||||
{% if chpw %}
|
||||
@ -163,6 +168,13 @@
|
||||
|
||||
<li><a id="af" href="{{ r }}/?ru">show recent uploads</a></li>
|
||||
<li><a id="k" href="{{ r }}/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a></li>
|
||||
|
||||
{%- if this.uname != '*' %}
|
||||
<li><form method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="act" value="logout" />
|
||||
<input type="submit" id="lo" value="logout “{{ this.uname|e }}” everywhere" />
|
||||
</form></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
@ -17,6 +17,11 @@ var Ls = {
|
||||
"j1": "k304 bryter tilkoplingen for hver HTTP 304. Dette hjelper mot visse mellomtjenere som kan sette seg fast / plutselig slutter å laste sider, men det reduserer også ytelsen betydelig",
|
||||
"k1": "nullstill innstillinger",
|
||||
"l1": "logg inn:",
|
||||
"ls3": "logg inn",
|
||||
"lu4": "brukernavn",
|
||||
"lp4": "passord",
|
||||
"lo3": "logg ut “{0}” overalt",
|
||||
"lo2": "avslutter økten på alle nettlesere",
|
||||
"m1": "velkommen tilbake,",
|
||||
"n1": "404: filen finnes ikke ┐( ´ -`)┌",
|
||||
"o1": 'eller kanskje du ikke har tilgang? prøv et passord eller <a href="' + SR + '/?h">gå hjem</a>',
|
||||
@ -46,6 +51,7 @@ var Ls = {
|
||||
"eng": {
|
||||
"d2": "shows the state of all active threads",
|
||||
"e2": "reload config files (accounts/volumes/volflags),$Nand rescan all e2ds volumes$N$Nnote: any changes to global settings$Nrequire a full restart to take effect",
|
||||
"lo2": "ends the session on all browsers",
|
||||
"u2": "time since the last server write$N( upload / rename / ... )$N$N17d = 17 days$N1h23 = 1 hour 23 minutes$N4m56 = 4 minutes 56 seconds",
|
||||
"v2": "use this server as a local HDD",
|
||||
"ta1": "fill in your new password first",
|
||||
@ -68,6 +74,11 @@ var Ls = {
|
||||
"j1": "k304 会在每个 HTTP 304 时断开连接。这有助于避免某些代理服务器卡住或突然停止加载页面,但也会显著降低性能。",
|
||||
"k1": "重置设置",
|
||||
"l1": "登录:",
|
||||
"ls3": "登录", //m
|
||||
"lu4": "用户名", //m
|
||||
"lp4": "密码", //m
|
||||
"lo3": "在所有地方注销 {0}", //m
|
||||
"lo2": "这将结束在所有浏览器中的会话", //m
|
||||
"m1": "欢迎回来,",
|
||||
"n1": "404: 文件不存在 ┐( ´ -`)┌",
|
||||
"o1": '或者你可能没有权限?尝试输入密码或 <a href="' + SR + '/?h">回家</a>',
|
||||
@ -94,6 +105,52 @@ var Ls = {
|
||||
"af1": "显示最近上传的文件", //m
|
||||
"ag1": "查看已知 IdP 用户", //m
|
||||
},
|
||||
"cze": {
|
||||
"a1": "obnovit",
|
||||
"b1": "ahoj cizinče <small>(nejsi přihlášen)</small>",
|
||||
"c1": "odhlásit se",
|
||||
"d1": "vypsat zásobníku",
|
||||
"d2": "zobrazit stav všech aktivních vláken",
|
||||
"e1": "znovu načíst konfiguraci",
|
||||
"e2": "znovu načíst konfigurační soubory (accounts/volumes/volflags),$Na prohledat všechny e2ds úložiště$N$Npoznámka: všechny změny globálních nastavení$Nvyžadují úplné restartování, aby se projevily",
|
||||
"f1": "můžeš procházet:",
|
||||
"g1": "můžeš nahrávat do:",
|
||||
"cc1": "další věci:",
|
||||
"h1": "zakázat k304",
|
||||
"i1": "povolit k304",
|
||||
"j1": "povolení k304 odpojí vašeho klienta při každém HTTP 304, což může zabránit některým chybovým proxy serverům, aby se zasekly (náhle nenačítaly stránky), <em>ale</em> také to obecně zpomalí věci",
|
||||
"k1": "resetovat nastavení klienta",
|
||||
"l1": "přihlaste se pro více:",
|
||||
"ls3": "přihlásit se", //m
|
||||
"lu4": "uživatelské jméno", //m
|
||||
"lp4": "heslo", //m
|
||||
"lo3": "odhlásit “{0}” všude", //m
|
||||
"lo2": "tímto ukončíte relaci ve všech prohlížečích", //m
|
||||
"m1": "vítej zpět,",
|
||||
"n1": "404 nenalezeno ┐( ´ -`)┌",
|
||||
"o1": 'nebo možná nemáš přístup -- zkus heslo nebo <a href="' + SR + '/?h">jdi domů</a>',
|
||||
"p1": "403 zakázáno ~┻━┻",
|
||||
"q1": 'použij heslo nebo <a href="' + SR + '/?h">jdi domů</a>',
|
||||
"r1": "jdi domů",
|
||||
".s1": "znovu prohledat",
|
||||
"t1": "akce",
|
||||
"u2": "čas od posledního zápisu na server$N( upload / rename / ... )$N$N17d = 17 dní$N1h23 = 1 hodina 23 minut$N4m56 = 4 minuty 56 sekund",
|
||||
"v1": "připojit",
|
||||
"v2": "použít tento server jako místní HDD",
|
||||
"w1": "přepnout na https",
|
||||
"x1": "změnit heslo",
|
||||
"y1": "upravit sdílení",
|
||||
"z1": "odblokovat toto sdílení:",
|
||||
"ta1": "nejprve vyplňte své nové heslo",
|
||||
"ta2": "zopakujte pro potvrzení nového hesla:",
|
||||
"ta3": "nalezen překlep; zkuste to prosím znovu",
|
||||
"aa1": "příchozí soubory:",
|
||||
"ab1": "deaktivovat no304",
|
||||
"ac1": "povolit no304",
|
||||
"ad1": "povolení no304 deaktivuje veškeré mezipaměti; zkuste to, pokud k304 nestačilo. To ovšem zapříčíní obrovské množství síťového provozu!",
|
||||
"ae1": "aktivní stahování:",
|
||||
"af1": "zobrazit nedávné nahrávání",
|
||||
},
|
||||
"deu": {
|
||||
"a1": "Neu laden",
|
||||
"b1": "Tach, wie geht's? <small>(Du bist nicht angemeldet)</small>",
|
||||
@ -110,6 +167,11 @@ var Ls = {
|
||||
"j1": "k304 trennt die Clientverbindung bei jedem HTTP 304, was Bugs mit problematischen Proxies vorbeugen kann (z.B. nicht ladenden Seiten), macht Dinge aber generell langsamer",
|
||||
"k1": "Client-Einstellungen zurücksetzen",
|
||||
"l1": "Melde dich an für mehr:",
|
||||
"ls3": "Anmelden", //m
|
||||
"lu4": "Benutzername", //m
|
||||
"lp4": "Passwort", //m
|
||||
"lo3": "“{0}” überall abmelden", //m
|
||||
"lo2": "Dies beendet die Sitzung in allen Browsern", //m
|
||||
"m1": "Willkommen zurück,",
|
||||
"n1": "404 Nicht gefunden ┐( ´ -`)┌",
|
||||
"o1": 'or maybe you don\'t have access -- try a password or <a href="' + SR + '/?h">go home</a>',
|
||||
@ -151,6 +213,11 @@ var Ls = {
|
||||
"j1": "k304 katkaisee yhteytesi jokaisella HTTP 304:llä, mikä voi estää joitain bugisia välityspalvelimia jumittumasta/lopettamasta sivujen lataamista, <em>mutta</em> se myös vähentää suorituskykyä",
|
||||
"k1": "nollaa asetukset",
|
||||
"l1": "kirjaudu sisään:",
|
||||
"ls3": "kirjaudu sisään", //m
|
||||
"lu4": "käyttäjätunnus", //m
|
||||
"lp4": "salasana", //m
|
||||
"lo3": "kirjaa “{0}” ulos kaikkialta", //m
|
||||
"lo2": "tämä lopettaa istunnon kaikissa selaimissa", //m
|
||||
"m1": "tervetuloa takaisin,",
|
||||
"n1": "404: ei löytynyt mitään ┐( ´ -`)┌",
|
||||
"o1": 'tai ehkä sinulla ei vain ole käyttöoikeuksia? kokeile salasanaa tai <a href="' + SR + '/?h">mene kotiin</a>',
|
||||
@ -177,6 +244,98 @@ var Ls = {
|
||||
"af1": "näytä viimeaikaiset lataukset",
|
||||
"ag1": "näytä tunnetut IdP-käyttäjät",
|
||||
},
|
||||
"fra": {
|
||||
"a1": "rafraîchir",
|
||||
"b1": "salut étranger <small>(vous n'êtes pas connecté.)</small>",
|
||||
"c1": "déconnexion",
|
||||
"d1": "vidange de la pile",
|
||||
"d2": "affiche l'état de tous les threads actifs",
|
||||
"e1": "recharger la configuration",
|
||||
"e2": "recharger le fichier de configuration (comptes/volumes/indicateurs de volume),$Net rescanner tous les volumes e2ds$N$Nnote : n'importe quel changement aux paramètres globaux$Nnécessite un redémarrage complet pour prendre effet",
|
||||
"f1": "vous pouvez naviguer :",
|
||||
"g1": "vous pouvez télécharger sur :",
|
||||
"cc1": "autres choses :",
|
||||
"h1": "désactiver k304",
|
||||
"i1": "activer k304",
|
||||
"j1": "activer k304 va déconnecter votre client sur chaque HTTP 304, ce qui peut éviter à certains proxies défectueux de rester bloqués (les pages ne se chargent soudainement plus), <em>mais</em> cela ralentira également les choses en général",
|
||||
"k1": "réinitialiser les paramètres du client",
|
||||
"l1": "connectez-vous pour en savoir plus :",
|
||||
"ls3": "se connecter", //m
|
||||
"lu4": "nom d'utilisateur", //m
|
||||
"lp4": "mot de passe", //m
|
||||
"lo3": "déconnecter “{0}” partout", //m
|
||||
"lo2": "cela mettra fin à la session sur tous les navigateurs", //m
|
||||
"m1": "heureux de vous revoir,",
|
||||
"n1": "404 introuvable ┐( ´ -`)┌",
|
||||
"o1": 'ou peut-être que vous n\'y avez pas accès -- essayer un mot de passe ou <a href="' + SR + '/?h">aller à la page d\'accueil</a>',
|
||||
"p1": "403 interdit ~┻━┻",
|
||||
"q1": 'utiliser un mot de passe ou <a href="' + SR + '/?h">aller à la page d\'accueil</a>',
|
||||
"r1": "aller à la page d\'accueil",
|
||||
".s1": "rescanner",
|
||||
"t1": "action",
|
||||
"u2": "temps écoulé depuis la dernière écriture sur le serveur$N(téléchargement/renommage/...)$N$N17j = 17 jours$N1h23 = 1 heure 23 minutes$N4m56 = 4 minutes 56 secondes",
|
||||
"v1": "connecter",
|
||||
"v2": "utilisez ce serveur en tant que disque dur local",
|
||||
"w1": "passer à https",
|
||||
"x1": "changer mot de passe",
|
||||
"y1": "modifier les partages",
|
||||
"z1": "déverrouiller ce partage :",
|
||||
"ta1": "entrez d'abord votre nouveau mot de passe",
|
||||
"ta2": "répétez pour confirmer le nouveau mot de passe :",
|
||||
"ta3": "une faute de frappe a été détectée ; veuillez réessayer.",
|
||||
"aa1": "fichiers entrants :",
|
||||
"ab1": "désactiver no304",
|
||||
"ac1": "activer no304",
|
||||
"ad1": "l'activation de no304 désactivera toute mise en cache ; essayez ceci si k304 n'était pas suffisant. Cela va générer un trafic réseau considérable !",
|
||||
"ae1": "téléchargements actifs :",
|
||||
"af1": "afficher les derniers téléchargements",
|
||||
},
|
||||
"grc": {
|
||||
"a1": "ανανέωση",
|
||||
"b1": "γεια σου ξένε! <small>(δεν είσαι συνδεδεμένος)</small>",
|
||||
"c1": "αποσύνδεση",
|
||||
"d1": "λίστα διεργασιών",
|
||||
"d2": "εμφανίζει την κατάσταση όλων των ενεργών διεργασιών",
|
||||
"e1": "επαναφόρτωση του cfg",
|
||||
"e2": "φορτώνει ξανά τα αρχεία ρυθμίσεων (λογαριασμοί/τόμοι/volflags),$Nκαι κάνει επανεξέταση όλων των τόμων e2ds$N$Nσημείωση: οποιαδήποτε αλλαγή στις καθολικές ρυθμίσεις$Nαπαιτεί πλήρη επανεκκίνηση για να εφαρμοστεί",
|
||||
"f1": "μπορείς να περιηγηθείς:",
|
||||
"g1": "μπορείς να εκτελέσεις μεταφόρτωση σε:",
|
||||
"cc1": "άλλα πράγματα:",
|
||||
"h1": "απενεργοποίση k304",
|
||||
"i1": "ενεργοποίηση k304",
|
||||
"j1": "η ενεργοποίηση του k304 θα αποσυνδέσει το πρόγραμμα πελάτη σου σε κάθε HTTP 304, κάτι που μπορεί να αποτρέψει κάποια προβληματικά proxies από το να κολλάνε (να μην φορτώνουν ξαφνικά σελίδες), <em>αλλά</em> θα κάνει τα πράγματα, γενικά πιο αργά",
|
||||
"k1": "επαναφορά ρυθμίσεων στο πρόγραμμα πελάτη",
|
||||
"l1": "συνδέσου για περισσότερα:",
|
||||
"ls3": "σύνδεση", //m
|
||||
"lu4": "όνομα χρήστη", //m
|
||||
"lp4": "κωδικός πρόσβασης", //m
|
||||
"lo3": "αποσύνδεση του “{0}” από παντού", //m
|
||||
"lo2": "αυτό θα τερματίσει τη συνεδρία σε όλους τους περιηγητές", //m
|
||||
"m1": "καλώς ήρθες,",
|
||||
"n1": "404 δεν βρέθηκε ┐( ´ -`)┌",
|
||||
"o1": '´η μήπως δεν έχεις πρόσβαση -- δοκίμασε έναν κωδικό <a href="' + SR + '/?h">πήγαινε στην αρχική</a>',
|
||||
"p1": "403 απαγορευμένο ~┻━┻",
|
||||
"q1": 'δοκίμασε έναν κωδικό <a href="' + SR + '/?h">πήγαινε στην αρχική</a>',
|
||||
"r1": "πίσω στην αρχική",
|
||||
".s1": "επανάληψη σάρωσης",
|
||||
"t1": "ενέργεια",
|
||||
"u2": "χρόνος από την τελευταία εγγραφή του διακομιστή$N( μεταφόρτωση / μετονομασία / ... )$N$N17d = 17 days$N1ω23 = 1 ώρα 23 λεπτά$N4λ56 = 4 λεπτά 56 δευτερόλεπτα",
|
||||
"v1": "σύνδεση",
|
||||
"v2": "χρησιμοποίησε αυτόν το διακομιστή σαν τοπικό δίσκο",
|
||||
"w1": "εναλλαγή σε https",
|
||||
"x1": "αλλαγή κωδικού",
|
||||
"y1": "επεξεργασία κοινόχρηστων φακέλων",
|
||||
"z1": "ξεκλείδωμα αυτού του κοινόχρηστου φακέλου:",
|
||||
"ta1": "συμπλήρωσε πρώτα το νέο σου κωδικό",
|
||||
"ta2": "επανέλαβε για να επιβεβαιώσεις το νέο κωδικό:",
|
||||
"ta3": "βρέθηκε τυπογραφικό λάθος· δοκίμασε ξανά",
|
||||
"aa1": "εισερχόμενα αρχεία:",
|
||||
"ab1": "απενεργοποίηση no304",
|
||||
"ac1": "ενεργοποίηση no304",
|
||||
"ad1": "η ενεργοποίηση του no304 θα απενεργοποιήσει όλη την προσωρινή αποθήκευση· δοκίμασέ το αν το k304 δεν ήταν αρκετό. Προσοχή, θα σπαταλήσει τεράστιο όγκο δικτυακής κίνησης!",
|
||||
"ae1": "ενεργές μεταφορτώσεις:",
|
||||
"af1": "προβολή πρόσφατων μεταφορτώσεων",
|
||||
},
|
||||
"ita": {
|
||||
"a1": "aggiorna",
|
||||
"b1": "ciao <small>(non sei connesso)</small>",
|
||||
@ -193,6 +352,11 @@ var Ls = {
|
||||
"j1": "k304 interrompe la connessione per ogni HTTP 304. Questo aiuta contro alcuni proxy difettosi che possono bloccarsi o smettere improvvisamente di caricare pagine, ma riduce notevolmente le prestazioni",
|
||||
"k1": "resetta impostazioni",
|
||||
"l1": "accedi:",
|
||||
"ls3": "accedi", //m
|
||||
"lu4": "nome utente", //m
|
||||
"lp4": "password", //m
|
||||
"lo3": "disconnetti “{0}” ovunque", //m
|
||||
"lo2": "questo terminerà la sessione su tutti i browser", //m
|
||||
"m1": "bentornato,",
|
||||
"n1": "404: file non trovato ┐( ´ -`)┌",
|
||||
"o1": "oppure forse non hai accesso? prova una password o <a href=\"SR/?h\">torna alla home</a>",
|
||||
@ -219,6 +383,53 @@ var Ls = {
|
||||
"af1": "mostra i file caricati di recente",
|
||||
"ag1": "mostra utenti IdP conosciuti"
|
||||
},
|
||||
"kor": {
|
||||
"a1": "새로고침",
|
||||
"b1": "어이 친구! 처음 보는 얼굴인데? <small>(로그인되어 있지 않습니다)</small>",
|
||||
"c1": "로그아웃",
|
||||
"d1": "스택 덤프하기",
|
||||
"d2": "모든 활성 스레드의 상태를 표시합니다",
|
||||
"e1": "설정 다시 불러오기",
|
||||
"e2": "설정 파일(계정/볼륨/볼륨 플래그)을 다시 불러오고,$N모든 e2ds 볼륨을 다시 스캔합니다$N$N참고: 전역 설정에 대한 변경 사항은$N적용하려면 전체 재시작이 필요합니다",
|
||||
"f1": "탐색 가능한 곳:",
|
||||
"g1": "업로드 가능한 곳:",
|
||||
"cc1": "기타 항목:",
|
||||
"h1": "k304 비활성화",
|
||||
"i1": "k304 활성화",
|
||||
"j1": "k304를 활성화하면 모든 HTTP 304 응답 시 클라이언트 연결이 끊어집니다. 이는 일부 프록시가 멈추는 현상(갑자기 페이지가 로드되지 않음)을 방지할 수 있지만, <em>대신 전반적인 속도는 느려집니다.</em>",
|
||||
"k1": "클라이언트 설정 초기화",
|
||||
"l1": "로그인하기:",
|
||||
"ls3": "로그인", //m
|
||||
"lu4": "사용자 이름", //m
|
||||
"lp4": "비밀번호", //m
|
||||
"lo3": "{0}을(를) 모든 곳에서 로그아웃", //m
|
||||
"lo2": "이 작업은 모든 브라우저에서 세션을 종료합니다", //m
|
||||
"m1": "또 오셨네요,",
|
||||
"n1": "404 찾을 수 없음 ┐( ´ -`)┌",
|
||||
"o1": "또는 접근 권한이 없을 수 있습니다. 비밀번호를 입력하거나 <a href=\"' + SR + '/?h\">홈으로 이동</a>하세요",
|
||||
"p1": "403 접근 금지 ~┻━┻",
|
||||
"q1": "비밀번호를 입력하거나 <a href=\"' + SR + '/?h\">홈으로 이동</a>하세요",
|
||||
"r1": "홈으로 이동",
|
||||
".s1": "다시 스캔",
|
||||
"t1": "작업",
|
||||
"u2": "서버에 마지막으로 쓰기 작업을 한 후 경과된 시간$N(업로드 / 이름 변경 / 등등...)$N$N17d = 17일$N1h23 = 1시간 23분$N4m56 = 4분 56초",
|
||||
"v1": "연결",
|
||||
"v2": "이 서버를 로컬 하드디스크처럼 사용하기",
|
||||
"w1": "HTTPS로 전환",
|
||||
"x1": "비밀번호 변경",
|
||||
"y1": "공유 설정",
|
||||
"z1": "이 공유 잠금해제:",
|
||||
"ta1": "새 비밀번호를 먼저 입력하세요",
|
||||
"ta2": "새 비밀번호 확인을 위해 다시 입력하세요:",
|
||||
"ta3": "오타가 있습니다. 다시 시도해주세요",
|
||||
"aa1": "수신 중인 파일:",
|
||||
"ab1": "no304 비활성화",
|
||||
"ac1": "no304 활성화",
|
||||
"ad1": "no304를 활성화하면 모든 캐싱이 비활성화됩니다. k304로 충분하지 않은 경우 시도해보세요. 네트워크 트래픽이 대량으로 낭비됩니다!",
|
||||
"ae1": "활성 다운로드:",
|
||||
"af1": "최근 업로드 보기",
|
||||
"ag1": "IdP 캐시 보기"
|
||||
},
|
||||
"nld": {
|
||||
"a1": "Update",
|
||||
"b1": "Hallo, hoe gaat het met jou? <small>(Je bent niet ingelogd)</small>",
|
||||
@ -235,6 +446,11 @@ var Ls = {
|
||||
"j1": "k304 verbreekt de verbinding voor elke HTTP 304. Dit helpt tegen bepaalde proxy servers die kunnen vastlopen/plotseling stoppen met het laden van pagina's, maar het vermindert ook de prestaties aanzienlijk",
|
||||
"k1": "Instellingen resetten",
|
||||
"l1": "Inloggen:",
|
||||
"ls3": "inloggen", //m
|
||||
"lu4": "gebruikersnaam", //m
|
||||
"lp4": "wachtwoord", //m
|
||||
"lo3": "“{0}” overal afmelden", //m
|
||||
"lo2": "dit zal de sessie in alle browsers beëindigen", //m
|
||||
"m1": "Welkom terug,",
|
||||
"n1": "404: bestand bestaat niet ┐( ´ -`)┌",
|
||||
"o1": 'of misschien heb je geen toegang? probeer een wachtwoord of <a href="' + SR + '/?h">ga naar startscherm</a>',
|
||||
@ -261,6 +477,100 @@ var Ls = {
|
||||
"af1": "Recent geüploade bestanden weergeven",
|
||||
"ag1": "Bekende IdP-gebruikers weergeven",
|
||||
},
|
||||
"nno": {
|
||||
"a1": "oppdatér",
|
||||
"b1": "heisann <small>(du er ikkje logga inn)</small>",
|
||||
"c1": "logg ut",
|
||||
"d1": "tilstand",
|
||||
"d2": "vis tilstanden åt alle trådar",
|
||||
"e1": "last innst.",
|
||||
"e2": "les inn konfigurasjonsfiler på nytt$N(kontoer, volum, volumbrytarar)$Nog kartlegg alle e2ds-volum$N$Nmerk: endringer i globale parametrar$Nkrev ein full restart for å gjelde",
|
||||
"f1": "du kan sjå på:",
|
||||
"g1": "du kan laste opp åt:",
|
||||
"cc1": "brytarar og slikt:",
|
||||
"h1": "skru av k304",
|
||||
"i1": "skru på k304",
|
||||
"j1": "k304 bryt tilkoplinga for kvar HTTP 304. Dette hjelp mot visse mellomtjenarar som kan sette seg fast / plutselig sluttar å laste sider, men det sett óg ytinga ned betydelig",
|
||||
"k1": "nullstill innstillinger",
|
||||
"l1": "logg inn:",
|
||||
"ls3": "logg inn",
|
||||
"lu4": "brukarnamn",
|
||||
"lp4": "passord",
|
||||
"lo3": "logg ut “{0}” overalt",
|
||||
"lo2": "avslutt økta på alle nettlesarar",
|
||||
"m1": "velkomen attende,",
|
||||
"n1": "404: filen finnast ikkje ┐( ´ -`)┌",
|
||||
"o1": 'eller kanskje du ikkje har høve? prøv eit passord eller <a href="' + SR + '/?h">gå heim</a>',
|
||||
"p1": "403: tilgang nektet ~┻━┻",
|
||||
"q1": 'prøv eit passord eller <a href="' + SR + '/?h">gå heim</a>',
|
||||
"r1": "gå heim",
|
||||
".s1": "kartlegg",
|
||||
"t1": "handling",
|
||||
"u2": "tid sidan nokon sist skreiv åt serveren$N( opplastning / namnendring / ... )$N$N17d = 17 dagar$N1h23 = 1 time 23 minutt$N4m56 = 4 minutt 56 sekund",
|
||||
"v1": "kople åt",
|
||||
"v2": "bruk denne serveren som ein lokal harddisk",
|
||||
"w1": "bytt åt https",
|
||||
"x1": "bytt passord",
|
||||
"y1": "dine delinger",
|
||||
"z1": "lås opp område:",
|
||||
"ta1": "du må skrive eit nytt passord først",
|
||||
"ta2": "gjenta for å stadfeste nytt passord:",
|
||||
"ta3": "fant ein skrivefeil; vennligst prøv igjen",
|
||||
"aa1": "innkommande:",
|
||||
"ab1": "skru av no304",
|
||||
"ac1": "skru på no304",
|
||||
"ad1": "no304 stoppar all bruk av cache. Hvis ikkje k304 var nok, prøv denne. Vil mangedoble dataforbruk!",
|
||||
"ae1": "utgående:",
|
||||
"af1": "vis nylig opplasta filer",
|
||||
"ag1": "vis kjente IdP-brukarar",
|
||||
},
|
||||
"pol": {
|
||||
"a1": "odśwież",
|
||||
"b1": "witaj, nieznajomy <small>(nie jesteś zalogowany)</small>",
|
||||
"c1": "wyloguj się",
|
||||
"d1": "zrzut stosu",
|
||||
"d2": "pokazuje status wszystkich aktywnych wątków",
|
||||
"e1": "przeładuj konfigurację",
|
||||
"e2": "przeładuj pliki konfiguracyjne (konta/wolumeny/flagi wolumenów),$Ni przeskanuje wszystkie wolumeny e2ds$N$Nnotka: zmiany konfiguracji globalnej$Nwymagają pełnego uruchomienia ponownie serwera, aby zaczęły obowiązywać",
|
||||
"f1": "możesz przeglądać:",
|
||||
"g1": "możesz przesyłać do:",
|
||||
"cc1": "inne:",
|
||||
"h1": "wyłącz k304",
|
||||
"i1": "włącz k304",
|
||||
"j1": "włączenie k304 będzie odłączało klienta przy każdorazowym otrzymaniu kodu HTTP 304, co może zapobiec wieszaniu się wadliwych proxy, <em>ale</em> spowolni ogólne działanie",
|
||||
"k1": "zresetuj ustawienia klienta",
|
||||
"l1": "zaloguj się po więcej:",
|
||||
"ls3": "zaloguj się", //m
|
||||
"lu4": "nazwa użytkownika", //m
|
||||
"lp4": "hasło", //m
|
||||
"lo3": "wyloguj “{0}” wszędzie", //m
|
||||
"lo2": "spowoduje to zakończenie sesji we wszystkich przeglądarkach", //m
|
||||
"m1": "Witaj,",
|
||||
"n1": "404 nie znaleziono ┐( ´ -`)┌",
|
||||
"o1": 'lub możesz nie mieć dostępu -- spróbuj wprowadzić hasło lub <a href="' + SR + '/?h">przejdź do strony głównej</a>',
|
||||
"p1": "403 odmowa dostępu ~┻━┻",
|
||||
"q1": 'użyj hasła lub <a href="' + SR + '/?h">przejdź do strony głównej</a>',
|
||||
"r1": "idź do strony głównej",
|
||||
".s1": "przeskanuj ponownie",
|
||||
"t1": "akcje",
|
||||
"u2": "czas od ostatniej interakcji z serwerem$N( przesyłania / zmiany nazwy / ... )$N$N17d = 17 dni$N1h23 = 1 godzina 23 minuty$N4m56 = 4 minuty 56 sekund",
|
||||
"v1": "połącz",
|
||||
"v2": "używaj tego serwera jako dysku lokalnego",
|
||||
"w1": "przejdź na HTTPS",
|
||||
"x1": "zmień hasło",
|
||||
"y1": "edytuj udostępnione",
|
||||
"z1": "odblokuj udostępnienie:",
|
||||
"ta1": "najpierw wprowadź nowe hasło",
|
||||
"ta2": "powtórz hasło dla potwierdzenia:",
|
||||
"ta3": "znaleziono literówkę, spróbuj ponownie",
|
||||
"aa1": "pliki przychodzące:",
|
||||
"ab1": "wyłącz no304",
|
||||
"ac1": "włącz no304",
|
||||
"ad1": "włączenie no304 wyłączy przechowywanie jakiejkolwiek pamięci podręcznej. Zmarnuje to olbrzymią ilość ruchu sieciowego!",
|
||||
"ae1": "trwające pobierania:",
|
||||
"af1": "pokaż ostatnio przesłane pliki",
|
||||
"ag1": "pokaż znanych użytkowników IdP",
|
||||
},
|
||||
"spa": {
|
||||
"a1": "actualizar",
|
||||
"b1": "hola <small>(no has iniciado sesión)</small>",
|
||||
@ -277,6 +587,11 @@ var Ls = {
|
||||
"j1": "activar k304 desconectará tu cliente en cada HTTP 304, lo que puede evitar que algunos proxies con errores se atasquen (dejando de cargar páginas de repente), <em>pero</em> también ralentizará las cosas en general",
|
||||
"k1": "restablecer config. de cliente",
|
||||
"l1": "inicia sesión para más:",
|
||||
"ls3": "iniciar sesión", //m
|
||||
"lu4": "nombre de usuario", //m
|
||||
"lp4": "contraseña", //m
|
||||
"lo3": "cerrar sesión de “{0}” en todas partes", //m
|
||||
"lo2": "esto finalizará la sesión en todos los navegadores", //m
|
||||
"m1": "bienvenido de nuevo,",
|
||||
"n1": "404 no encontrado ┐( ´ -`)┌",
|
||||
"o1": '¿o quizás no tienes acceso? -- prueba con una contraseña o <a href=\"' + SR + '/?h\">vuelve al inicio</a>',
|
||||
@ -303,6 +618,100 @@ var Ls = {
|
||||
"af1": "mostrar subidas recientes",
|
||||
"ag1": "mostrar usuarios IdP conocidos"
|
||||
},
|
||||
"swe": {
|
||||
"a1": "uppdatera",
|
||||
"b1": "tjena främling <small>(du är inte inloggad)</small>",
|
||||
"c1": "logga ut",
|
||||
"d1": "dumpa stacken",
|
||||
"d2": "visar tillståndet på alla aktiva trådar",
|
||||
"e1": "ladda om konfig.",
|
||||
"e2": "ladda om konfigurationsfiler (konton/volymer/volflaggor),$Noch skanna om alla e2ds-volymer$N$Nobs.: ändrade globala inställningar$Nkräver en fullständig omstart",
|
||||
"f1": "du kan bläddra:",
|
||||
"g1": "du kan ladda upp till:",
|
||||
"cc1": "annat:",
|
||||
"h1": "avaktivera k304",
|
||||
"i1": "aktivera k304",
|
||||
"j1": "med k304 aktiverad kommer klienten att koppla bort sig vid varje HTTP 304-fel, vilket kan hindra vissa buggiga proxyservrar från att fastna (sidor slutar ladda), <em>men</em> saker kommer också att bli långsammare i allmänhet",
|
||||
"k1": "återställ klientinställningar",
|
||||
"l1": "logga in för att se mer:",
|
||||
"ls3": "logga in", //m
|
||||
"lu4": "användarnamn", //m
|
||||
"lp4": "lösenord", //m
|
||||
"lo3": "logga ut “{0}” överallt", //m
|
||||
"lo2": "avsluta sessionen i alla webbläsare", //m
|
||||
"m1": "välkommen tillbaka,",
|
||||
"n1": "404 hittades inte ┐( ´ -`)┌",
|
||||
"o1": 'eller så har du kanske inte tillgång -- prova ett lösenord eller <a href="' + SR + '/?h">åk hem</a>',
|
||||
"p1": "403 nekat ~┻━┻",
|
||||
"q1": 'använd ett lösenord eller <a href="' + SR + '/?h">åk hem</a>',
|
||||
"r1": "åk hem",
|
||||
".s1": "skanna om",
|
||||
"t1": "åtgärd",
|
||||
"u2": "tid sedan senaste serverskrivning$N( uppladdning / namnbyte / ... )$N$N17d = 17 dagar$N1h23 = 1 timme 23 minuter$N4m56 = 4 minuter 56 sekunder",
|
||||
"v1": "koppla upp",
|
||||
"v2": "använd denna server som en lokal disk",
|
||||
"w1": "byt till https",
|
||||
"x1": "byt lösenord",
|
||||
"y1": "redigera utdelningar",
|
||||
"z1": "lås upp denna utdelning:",
|
||||
"ta1": "fyll i ditt nya lösenord",
|
||||
"ta2": "upprepa det nya lösenordet:",
|
||||
"ta3": "det blev fel; vänligen försök igen",
|
||||
"aa1": "inkommande filer:",
|
||||
"ab1": "avaktivera no304",
|
||||
"ac1": "aktivera no304",
|
||||
"ad1": "detta stänger av all cachning; prova detta om k304 inte räckte till. Detta kommer att slösa enorma mängder nätverkstrafik!",
|
||||
"ae1": "aktiva nedladdningar:",
|
||||
"af1": "visa senaste uppladdningar",
|
||||
"ag1": "visa idp-cache"
|
||||
},
|
||||
"ukr": {
|
||||
"a1": "оновити",
|
||||
"b1": "привітик, незнайомцю <small>(ви не авторизовані)</small>",
|
||||
"c1": "вийти",
|
||||
"d1": "трасування стека",
|
||||
"d2": "показує стан усіх активних потоків",
|
||||
"e1": "перезавантажити конфіг",
|
||||
"e2": "перезавантажити файли конфігурації (облікові записи/томи/прапорці),$Nта пересканувати всі томи e2ds$N$Nувага: будь-які зміни глобальних налаштувань$Nвимагають повного перезапуску",
|
||||
"f1": "ви можете бачити:",
|
||||
"g1": "ви можете завантажувати файли в:",
|
||||
"cc1": "всяка всячина:",
|
||||
"h1": "вимкнути k304",
|
||||
"i1": "увімкнути k304",
|
||||
"j1": "увімкнення k304 буде відключати ваш клієнт при кожному HTTP 304, що може запобігти зависанню деяких глючних проксі (раптово перестають завантажувати сторінки), <em>але</em> це також зробить усе повільнішим загалом",
|
||||
"k1": "скинути налаштування клієнта",
|
||||
"l1": "авторизуйтесь для інших опцій:",
|
||||
"ls3": "увійти", //m
|
||||
"lu4": "ім'я користувача", //m
|
||||
"lp4": "пароль", //m
|
||||
"lo3": "вийти з облікового запису “{0}” всюди", //m
|
||||
"lo2": "це завершить сеанс у всіх браузерах", //m
|
||||
"m1": "з поверненням,",
|
||||
"n1": "404 не знайдено ┐( ´ -`)┌",
|
||||
"o1": 'або у вас немає доступу -- спробуйте авторизуватися або <a href="' + SR + '/?h">повернутися на головну</a>',
|
||||
"p1": "403 доступ заборонений ~┻━┻",
|
||||
"q1": 'авторизуйтесь або <a href="' + SR + '/?h">поверніться на головну</a>',
|
||||
"r1": "повернутися на головну",
|
||||
".s1": "пересканувати",
|
||||
"t1": "дія",
|
||||
"u2": "час з останнього запису сервера$N( завантаження / перейменування / ... )$N$N17d = 17 днів$N1h23 = 1 година 23 хвилини$N4m56 = 4 хвилини 56 секунд",
|
||||
"v1": "підключити",
|
||||
"v2": "використовувати цей сервер як локальний HDD",
|
||||
"w1": "перейти на https",
|
||||
"x1": "змінити пароль",
|
||||
"y1": "керування доступом",
|
||||
"z1": "розблокувати:",
|
||||
"ta1": "спочатку заповніть ваш новий пароль",
|
||||
"ta2": "повторіть для підтвердження нового пароля:",
|
||||
"ta3": "описка; спробуйте знову",
|
||||
"aa1": "вхідні файли:",
|
||||
"ab1": "вимкнути no304",
|
||||
"ac1": "увімкнути no304",
|
||||
"ad1": "увімкнення no304 вимкне все кешування; спробуйте це, якщо k304 було недостатньо. Це витратить величезну кількість мережевого трафіку!",
|
||||
"ae1": "активні завантаження:",
|
||||
"af1": "показати нещодавні завантаження",
|
||||
"ag1": "показати відомих IdP-користувачів"
|
||||
},
|
||||
"rus": {
|
||||
"a1": "обновить",
|
||||
"b1": "приветик, незнакомец <small>(вы не авторизованы)</small>",
|
||||
@ -319,6 +728,11 @@ var Ls = {
|
||||
"j1": "включённый k304 будет отключать вас при получении HTTP 304, что может помочь при работе с некоторыми глючными прокси (перестают загружаться страницы), <em>но</em> это также сделает работу клиента медленнее",
|
||||
"k1": "сбросить локальные настройки",
|
||||
"l1": "авторизуйтесь для других опций:",
|
||||
"ls3": "войти", //m
|
||||
"lu4": "имя пользователя", //m
|
||||
"lp4": "пароль", //m
|
||||
"lo3": "выйти из “{0}” везде", //m
|
||||
"lo2": "это завершит сеанс во всех браузерах", //m
|
||||
"m1": "с возвращением,",
|
||||
"n1": "404 не найдено ┐( ´ -`)┌",
|
||||
"o1": 'или у вас нет доступа -- попробуйте авторизоваться или <a href="' + SR + '/?h">вернуться на главную</a>',
|
||||
@ -363,7 +777,14 @@ for (var k in (d || {})) {
|
||||
o[a].innerHTML = d[k];
|
||||
else if (f == 2)
|
||||
o[a].setAttribute("tt", d[k]);
|
||||
else if (f == 3)
|
||||
o[a].setAttribute("value", d[k]);
|
||||
else if (f == 4)
|
||||
o[a].setAttribute("placeholder", " " + d[k]);
|
||||
}
|
||||
var o1 = ebi('lo'), o2 = ebi('un');
|
||||
if (o1 && o2 && d.lo3)
|
||||
o1.setAttribute("value", d.lo3.format(o2.textContent));
|
||||
|
||||
try {
|
||||
if (is_idp) {
|
||||
@ -375,8 +796,8 @@ try {
|
||||
catch (ex) { }
|
||||
|
||||
tt.init();
|
||||
var o = QS('input[name="cppwd"]');
|
||||
if (!ebi('c') && o.offsetTop + o.offsetHeight < window.innerHeight)
|
||||
var o = QS('input[name="uname"]') || QS('input[name="cppwd"]');
|
||||
if (!MOBILE && !ebi('c') && o.offsetTop + o.offsetHeight < window.innerHeight)
|
||||
o.focus();
|
||||
|
||||
o = ebi('u');
|
||||
@ -385,6 +806,9 @@ if (o && /[0-9]+$/.exec(o.innerHTML))
|
||||
|
||||
ebi('uhash').value = '' + location.hash;
|
||||
|
||||
if (/\&re=/.test('' + location))
|
||||
ebi('a').className = 'af g';
|
||||
|
||||
(function() {
|
||||
if (!ebi('x'))
|
||||
return;
|
||||
|
@ -37,6 +37,7 @@
|
||||
{% if accs %}<code><b id="pw0">{{ pw }}</b></code>=password, {% endif %}<code><b>mp</b></code>=mountpoint
|
||||
</span>
|
||||
{% if accs %}<a href="#" id="setpw">use real password</a>{% endif %}
|
||||
<a href="#" id="qr">show qr</a>
|
||||
</p>
|
||||
|
||||
|
||||
|
@ -49,12 +49,14 @@ function setos(os) {
|
||||
setos(WINDOWS ? 'win' : LINUX ? 'lin' : MACOS ? 'mac' : 'idk');
|
||||
|
||||
|
||||
var pw = '';
|
||||
function setpw(e) {
|
||||
ev(e);
|
||||
modal.prompt('password:', '', function (v) {
|
||||
if (!v)
|
||||
return;
|
||||
|
||||
pw = v;
|
||||
var pw0 = ebi('pw0').innerHTML,
|
||||
oa = QSA('b');
|
||||
|
||||
@ -67,3 +69,12 @@ function setpw(e) {
|
||||
}
|
||||
if (ebi('setpw'))
|
||||
ebi('setpw').onclick = setpw;
|
||||
|
||||
|
||||
ebi('qr').onclick = function () {
|
||||
var url = ('' + location).split('?')[0];
|
||||
if (pw)
|
||||
url += '?pw=' + pw;
|
||||
var txt = esc(url) + '<img class="b64" width="100" height="100" src="' + addq(url, 'qr') + '" />';
|
||||
modal.alert(txt);
|
||||
};
|
||||
|
@ -964,6 +964,7 @@ function up2k_init(subtle) {
|
||||
"t": 0
|
||||
},
|
||||
"car": 0,
|
||||
"nre": 0,
|
||||
"slow_io": null,
|
||||
"oserr": false,
|
||||
"modn": 0,
|
||||
@ -1572,7 +1573,7 @@ function up2k_init(subtle) {
|
||||
|
||||
function linklist() {
|
||||
var ret = [],
|
||||
base = document.location.origin.replace(/\/$/, '');
|
||||
base = location.origin.replace(/\/$/, '');
|
||||
|
||||
for (var a = 0; a < st.files.length; a++) {
|
||||
var t = st.files[a],
|
||||
@ -1595,7 +1596,7 @@ function up2k_init(subtle) {
|
||||
ev(e);
|
||||
var txt = linklist();
|
||||
cliptxt(txt + '\n', function () {
|
||||
toast.inf(5, un_clip.format(txt.split('\n').length));
|
||||
toast.inf(5, L.un_clip.format(txt.split('\n').length));
|
||||
});
|
||||
};
|
||||
|
||||
@ -1783,8 +1784,7 @@ function up2k_init(subtle) {
|
||||
}
|
||||
|
||||
var tasker = (function () {
|
||||
var running = false,
|
||||
was_busy = false;
|
||||
var running = false;
|
||||
|
||||
var defer = function () {
|
||||
running = false;
|
||||
@ -1801,7 +1801,17 @@ function up2k_init(subtle) {
|
||||
while (true) {
|
||||
var now = Date.now(),
|
||||
blocktime = now - r.tact,
|
||||
is_busy = st.car < st.files.length;
|
||||
was_busy = st.is_busy,
|
||||
is_busy = !!( // gzip take the wheel
|
||||
st.car < st.files.length ||
|
||||
st.busy.hash.length ||
|
||||
st.todo.hash.length ||
|
||||
st.busy.handshake.length ||
|
||||
st.todo.handshake.length ||
|
||||
st.busy.upload.length ||
|
||||
st.todo.upload.length ||
|
||||
st.busy.head.length ||
|
||||
st.todo.head.length);
|
||||
|
||||
if (blocktime > 2500)
|
||||
console.log('main thread blocked for ' + blocktime);
|
||||
@ -1809,7 +1819,16 @@ function up2k_init(subtle) {
|
||||
r.tact = now;
|
||||
|
||||
if (was_busy && !is_busy) {
|
||||
for (var a = 0; a < st.files.length; a++) {
|
||||
var nre = 0, nf = 0;
|
||||
for (var a = 0; a < st.files.length; a++)
|
||||
if (st.files[a].want_recheck)
|
||||
nre++;
|
||||
console.log('nre', nre, 'st', st.nre);
|
||||
if (st.nre != nre) {
|
||||
st.nre = nre;
|
||||
nf = st.files.length;
|
||||
}
|
||||
for (var a = 0; a < nf; a++) {
|
||||
var t = st.files[a];
|
||||
if (t.want_recheck) {
|
||||
t.rechecks++;
|
||||
@ -1817,7 +1836,7 @@ function up2k_init(subtle) {
|
||||
push_t(st.todo.handshake, t);
|
||||
}
|
||||
}
|
||||
is_busy = st.todo.handshake.length;
|
||||
is_busy = !!st.todo.handshake.length;
|
||||
try {
|
||||
if (!is_busy && !uc.fsearch && !msel.getsel().length && (!mp.au || mp.au.paused))
|
||||
treectl.goto();
|
||||
@ -1826,7 +1845,7 @@ function up2k_init(subtle) {
|
||||
}
|
||||
|
||||
if (was_busy != is_busy) {
|
||||
st.is_busy = was_busy = is_busy;
|
||||
st.is_busy = is_busy;
|
||||
|
||||
window[(is_busy ? "add" : "remove") +
|
||||
"EventListener"]("beforeunload", warn_uploader_busy);
|
||||
@ -1947,7 +1966,7 @@ function up2k_init(subtle) {
|
||||
|
||||
for (var a = 0; a < st.files.length; a++) {
|
||||
var t = st.files[a];
|
||||
if (t.want_recheck && !t.rechecks)
|
||||
if (t.want_recheck && t.rechecks < 999)
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2693,8 +2712,9 @@ function up2k_init(subtle) {
|
||||
if (ofs !== -1) {
|
||||
err = err.slice(0, ofs + 1) + linksplit(err.slice(ofs + 2).trimEnd()).join(' / ');
|
||||
}
|
||||
if (!t.rechecks && (err_pend || err_srcb)) {
|
||||
if (!t.rechecks)
|
||||
t.rechecks = 0;
|
||||
if (t.rechecks < 999 && (err_pend || err_srcb)) {
|
||||
t.want_recheck = true;
|
||||
if (st.busy.upload.length || st.busy.handshake.length || st.bytes.uploaded) {
|
||||
err = L.u_dupdefer;
|
||||
@ -2811,7 +2831,7 @@ function up2k_init(subtle) {
|
||||
if (!t.t_uploading)
|
||||
t.t_uploading = Date.now();
|
||||
|
||||
pvis.seth(t.n, 1, "🚀 send");
|
||||
pvis.seth(t.n, 1, "🚀 " + L.ul_send);
|
||||
|
||||
var chunksize = get_chunksize(t.size),
|
||||
car = pcar * chunksize,
|
||||
|
@ -120,7 +120,7 @@ function esc(txt) {
|
||||
function basenames(txt) {
|
||||
return (txt + '').replace(/https?:\/\/[^ \/]+\//g, '/').replace(/js\?_=[a-zA-Z]{4}/g, 'js');
|
||||
}
|
||||
if ((document.location + '').indexOf(',rej,') + 1)
|
||||
if ((location + '').indexOf(',rej,') + 1)
|
||||
window.onunhandledrejection = function (e) {
|
||||
var err = e.reason;
|
||||
try {
|
||||
@ -741,7 +741,7 @@ function assert_vp(path) {
|
||||
if (path.indexOf('//') + 1)
|
||||
throw 'nonlocal1: ' + path;
|
||||
|
||||
var o = window.location.origin;
|
||||
var o = location.origin;
|
||||
if (have_URL && (new URL(path, o)).origin != o)
|
||||
throw 'nonlocal2: ' + path;
|
||||
}
|
||||
@ -893,7 +893,7 @@ function uricom_adec(arr, li) {
|
||||
|
||||
|
||||
function get_evpath() {
|
||||
var ret = document.location.pathname;
|
||||
var ret = location.pathname;
|
||||
|
||||
if (ret.indexOf('/') !== 0)
|
||||
ret = '/' + ret;
|
||||
@ -1020,9 +1020,13 @@ function lhumantime(v) {
|
||||
if (!L || tp.length < 2 || tp[1].indexOf('$') + 1)
|
||||
return t;
|
||||
|
||||
var ret = '';
|
||||
for (var a = 0; a < tp.length; a += 2)
|
||||
ret += tp[a] + ' ' + L['ht_' + tp[a + 1] + (tp[a]==1?1:2)] + L.ht_and;
|
||||
var u, n, ret = '';
|
||||
for (var a = 0; a < tp.length; a += 2) {
|
||||
n = tp[a];
|
||||
u = L.ht_h5 ? (n==1 ? 1 : (n>1&&n<5) ? 2 : 5) :
|
||||
(n==1 ? 1 : 2);
|
||||
ret += tp[a] + ' ' + L['ht_' + tp[a + 1] + u] + L.ht_and;
|
||||
}
|
||||
|
||||
return ret.slice(0, -L.ht_and.length);
|
||||
}
|
||||
@ -1249,10 +1253,10 @@ function hist_replace(url) {
|
||||
|
||||
function sethash(hv) {
|
||||
if (window.history && history.replaceState) {
|
||||
hist_replace(document.location.pathname + document.location.search + '#' + hv);
|
||||
hist_replace(location.pathname + location.search + '#' + hv);
|
||||
}
|
||||
else {
|
||||
document.location.hash = hv;
|
||||
location.hash = hv;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,85 @@
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2025-0807-2213 `v1.19.0` usernames
|
||||
|
||||
## 🧪 new features
|
||||
|
||||
* #511 login with username and password (not just password) can now optionally be enabled with `--usernames` 346515cc
|
||||
* if you have enabled password hashing (`ah-alg: argon2` or similar) then you will need to hash your passwords again after enabling usernames, hashing them as `username:password:`
|
||||
* #468 add Greek translation (thx @chamdim!) 50f46187 392abd06
|
||||
* #471 add Czech translation (thx @kubakubakuba!) c9556583
|
||||
* #515 support systemd socket acivation (thx @mati1210!) 9b9d2a92
|
||||
* #523 add QR-code to the connectpage bcc3b156
|
||||
* #513 optional EOL-conversion for texteditor 8b31ed88
|
||||
* controlpanel refresh-button now toggles automatic refresh 7ae84dea
|
||||
|
||||
## 🩹 bugfixes
|
||||
|
||||
* fix stuck uploads when the up2k database (`e2d`) is not enabled 4a043568
|
||||
* if more than 60'000 files were uploaded and there were several dupes of some files, they could get stuck and never upload
|
||||
* upload performance is improved remarkably by enabling `e2d` so such huge uploads non-e2d had not been tested in a long time
|
||||
* #467 #470 fix ui-crash when exporting links of all uploaded files to clipboard (thx @geekalaa!) 0df1901f
|
||||
* #487 fix ui-crash when the location url-part is `//` 0f55a1ae
|
||||
* fix viewing `.MD` files (8a0746c6)
|
||||
|
||||
## 🔧 other changes
|
||||
|
||||
* when a reverse-proxy is detected, force explicit configuration of `--rproxy` to obtain correct client IP 3f8cb7e8
|
||||
* a bit inconvenient, but helps prevent potentially-dangerous misconfiguration
|
||||
* the necessary configuration changes are explained in the serverlog (you can't miss it)
|
||||
* thanks to @person4268 for pointing out that there was room for improvements!
|
||||
* failed login attempts now only log a sha512 hash of the provided password
|
||||
* to see login-attempts with incorrect passwords as plaintext like before, `log-badpwd: 1`
|
||||
* #502 add systemd user services and templated services (thx @icxes!) 34d98e99
|
||||
* #475 improve helptext for multivalue global-options c2ac57a2
|
||||
* #475 add [chungus.conf](https://github.com/9001/copyparty/blob/hovudstraum/docs/chungus.conf), massive extensive nonsensical demo config b664ebb0
|
||||
* try to detect proxies with incorrect caching behavior 9e980bb5
|
||||
* recent-uploads now support ie9 a57f7cc2
|
||||
* languages and themes are now dropdowns a9ee4f24
|
||||
* copyparty.exe: upgrade python to 3.13.6 a98360f2
|
||||
* introduce [copyparty-en.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-en.py), english-only edition of copyparty-sfx.py to save space 33497e6b
|
||||
|
||||
## 🗿 known issues
|
||||
|
||||
* the `copyparty.pyz` in this release is english-only, and does not include the translations -- they got lost in transit while adjusting the buildscripts to make `copyparty-en.py`
|
||||
|
||||
|
||||
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2025-0804-0013 `v1.18.10` idp speedboost
|
||||
|
||||
## 🧪 new features
|
||||
|
||||
* #426 add Dutch translation (thx @DeStilleGast!) 3798e19a
|
||||
* #458 add Italian translation (thx @AOTREVAI!) a38e6e65
|
||||
* #456 transcode to flac/wav (thx @missaustraliana!) b469db3c b2d48c64 0d09fb68
|
||||
* #439 config-file can be provided through `PRTY_CONFIG` (thx @icxes!) 971360e9
|
||||
* #459 videos can become folder thumbnails 16bbcce5
|
||||
* add `--idp-cookie`, session-tickets for IdP auth (performance boost) f9502c3d
|
||||
* useful when the IdP-server becomes a bottleneck
|
||||
|
||||
## 🩹 bugfixes
|
||||
|
||||
* #412 fix PUT-uploads into volumes with `nosub` volflag 47fa4a92
|
||||
* #435 ignore spurious exceptions from browser extensions 39e55824
|
||||
* #449 IPv6 QR-Code didn't include port 66a5bf36
|
||||
* #295 do not force `d2d` in blank vfs (introduced in v1.18.3) 848315c0
|
||||
|
||||
## 🔧 other changes
|
||||
|
||||
* #440 improved finnish translation (thx @icxes!) a68d5b03
|
||||
* point to the `-nc` option in the "at max connections" warning 153d240d
|
||||
* the play-button now indicates "play-as-audio" for video-files 40d56bb3
|
||||
* docs:
|
||||
* #411 improve password-hashing instructions (thx @chinponya!) c69c7c8a
|
||||
* #429 improve `--cert` helptext (thx @kzshantonu!) 7e3825f8
|
||||
* #413 copyparty is Wii Internet Channel compatible! (thx @techflashYT!) 50f16293
|
||||
* #461 how to use groups without IdP e85a7107
|
||||
* mention that WebDAV and OpenGraph are incompatible by default (and how to fix that) 0bc1b8f7
|
||||
* #345 short explanation about the sfx in quickstart ae5eefc5
|
||||
* #398 pypi-package now has extra-group `all` 6eaf8af1
|
||||
|
||||
|
||||
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2025-0801-2056 `v1.18.9` fix Denial-of-Service
|
||||
|
||||
|
2141
docs/chungus.conf
Normal file
2141
docs/chungus.conf
Normal file
File diff suppressed because it is too large
Load Diff
@ -354,7 +354,7 @@ pip install mutagen # audio metadata
|
||||
pip install pyftpdlib # ftp server
|
||||
pip install partftpy # tftp server
|
||||
pip install impacket # smb server -- disable Windows Defender if you REALLY need this on windows
|
||||
pip install Pillow pyheif-pillow-opener # thumbnails
|
||||
pip install Pillow pillow-heif # thumbnails
|
||||
pip install pyvips # faster thumbnails
|
||||
pip install psutil # better cleanup of stuck metadata parsers on windows
|
||||
pip install black==21.12b0 click==8.0.2 bandit pylint flake8 isort mypy # vscode tooling
|
||||
|
66
scripts/make-rpm.sh
Executable file
66
scripts/make-rpm.sh
Executable file
@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
#--localbuild to build webdeps and tar locally; otherwise just download prebuilt
|
||||
#--pm change packagemanager; otherwise default to dnf
|
||||
|
||||
while [ ! -z "$1" ]; do
|
||||
case $1 in
|
||||
local-build) local_build=1 ; ;;
|
||||
pm) shift;packagemanager="$1"; ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
[ -e copyparty/__main__.py ] || cd ..
|
||||
[ -e copyparty/__main__.py ] ||
|
||||
{
|
||||
echo "run me from within the project root folder"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
packagemanager=${packagemanager:-dnf}
|
||||
ver=$(awk '/^VERSION/{gsub(/[^0-9]/," ");printf "%d.%d.%d\n",$1,$2,$3}' copyparty/__version__.py)
|
||||
releasedir="dist/temp_copyparty_$ver"
|
||||
sourcepkg="copyparty-$ver.tar.gz"
|
||||
|
||||
#make temporary directory to build rpm in
|
||||
mkdir -p $releasedir/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
|
||||
trap "rm -rf $releasedir" EXIT
|
||||
|
||||
# make/get tarball
|
||||
if [ $local_build ]; then
|
||||
if [ ! -f "copyparty/web/deps/mini-fa.woff" ]; then
|
||||
sudo $packagemanager update
|
||||
sudo $packagemanager install podman-docker docker
|
||||
make -C deps-docker
|
||||
fi
|
||||
if [ ! -f "dist/$sourcepkg" ]; then
|
||||
./$cppdir/scripts/make-sfx.sh gz fast # pulls some build-deps + good smoketest
|
||||
./$cppdir/scripts/make-tgz-release.sh "$ver"
|
||||
fi
|
||||
else
|
||||
if [ ! -f "dist/$sourcepkg" ]; then
|
||||
curl -OL https://github.com/9001/copyparty/releases/download/v$ver/$sourcepkg --output-dir dist
|
||||
fi
|
||||
fi
|
||||
|
||||
cp dist/$sourcepkg "$releasedir/SOURCES/$sourcepkg"
|
||||
|
||||
cp "contrib/package/rpm/copyparty.spec" "$releasedir/SPECS/"
|
||||
sed -i "s/\$pkgver/$ver/g" "$releasedir/SPECS/copyparty.spec"
|
||||
sed -i "s/\$pkgrel/1/g" "$releasedir/SPECS/copyparty.spec"
|
||||
|
||||
sudo $packagemanager update
|
||||
sudo $packagemanager install \
|
||||
rpmdevtools python-devel pyproject-rpm-macros \
|
||||
python-wheel python-setuptools python-jinja2 \
|
||||
make pigz
|
||||
cd "$releasedir/"
|
||||
rpmbuild --define "_topdir `pwd`" -bb SPECS/copyparty.spec
|
||||
cd -
|
||||
|
||||
rpm="copyparty-$ver-1.noarch.rpm"
|
||||
mv "$releasedir/RPMS/noarch/$rpm" dist/$rpm
|
@ -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 |
|
||||
@ -218,6 +217,7 @@ necho() {
|
||||
tar -zxf $f
|
||||
mv pyftpdlib-*/pyftpdlib .
|
||||
rm -rf pyftpdlib-* pyftpdlib/test
|
||||
patch -p1 <../scripts/patches/pyftpdlib-win313.patch
|
||||
for f in pyftpdlib/_async{hat,ore}.py; do
|
||||
[ -e "$f" ] || continue;
|
||||
iawk 'NR<4||NR>27||!/^#/;NR==4{print"# license: https://opensource.org/licenses/ISC\n"}' $f
|
||||
@ -395,7 +395,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 +554,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 +583,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
|
||||
|
41
scripts/patches/pyftpdlib-win313.patch
Normal file
41
scripts/patches/pyftpdlib-win313.patch
Normal file
@ -0,0 +1,41 @@
|
||||
Date: Tue, 22 Oct 2024 12:47:30 +0200
|
||||
Subject: Workaround for isabs() on Windows + Python 3.13 (#652)
|
||||
|
||||
Starting from Python 3.13, `os.path.isabs("/foo")` on Windows return `False`
|
||||
|
||||
diff --git a/pyftpdlib/filesystems.py b/pyftpdlib/filesystems.py
|
||||
index 9b9326bf..320ffe40 100644
|
||||
--- a/pyftpdlib/filesystems.py
|
||||
+++ b/pyftpdlib/filesystems.py
|
||||
@@ -132,6 +132,16 @@ def cwd(self, path):
|
||||
|
||||
# --- Pathname / conversion utilities
|
||||
|
||||
+ @staticmethod
|
||||
+ def _isabs(path, _windows=os.name == "nt"):
|
||||
+ # Windows + Python 3.13: isabs() changed so that a path
|
||||
+ # starting with "/" is no longer considered absolute.
|
||||
+ # https://github.com/python/cpython/issues/44626
|
||||
+ # https://github.com/python/cpython/pull/113829/
|
||||
+ if _windows and path.startswith("/"):
|
||||
+ return True
|
||||
+ return os.path.isabs(path)
|
||||
+
|
||||
def ftpnorm(self, ftppath):
|
||||
"""Normalize a "virtual" ftp pathname (typically the raw string
|
||||
coming from client) depending on the current working directory.
|
||||
@@ -146,3 +156,3 @@
|
||||
assert isinstance(ftppath, unicode), ftppath
|
||||
- if os.path.isabs(ftppath):
|
||||
+ if self._isabs(ftppath):
|
||||
p = os.path.normpath(ftppath)
|
||||
@@ -162,3 +172,3 @@
|
||||
# This is for extra protection, maybe not really necessary.
|
||||
- if not os.path.isabs(p):
|
||||
+ if not self._isabs(p):
|
||||
p = u("/")
|
||||
@@ -201,3 +211,3 @@
|
||||
assert isinstance(fspath, unicode), fspath
|
||||
- if os.path.isabs(fspath):
|
||||
+ if self._isabs(fspath):
|
||||
p = os.path.normpath(fspath)
|
@ -29,15 +29,11 @@ update_mpr_pkgbuild() {
|
||||
|
||||
sha=$(sha256sum "$self/../dist/copyparty-$ver.tar.gz" | awk '{print$1}')
|
||||
|
||||
# awk -v ver=$ver -v sha=$sha '
|
||||
# /^pkgver=/{sub(/[0-9\.]+/,ver)};
|
||||
# /^sha256sums=/{sub(/[0-9a-f]{64}/,sha)};
|
||||
# 1' PKGBUILD >a
|
||||
# mv a PKGBUILD
|
||||
# TODO: check if this still works. if so, remove the following 2 lines:
|
||||
|
||||
sed -s -i "s/pkgver=\"\"/pkgver=\"$ver\"/" PKGBUILD
|
||||
sed -s -i "s/sha256sums=(\".*\")/sha256sums=(\"$sha\")/" PKGBUILD
|
||||
awk -v ver=$ver -v sha=$sha '
|
||||
/^pkgver=/{sub(/[0-9\.]+/,ver)};
|
||||
/^sha256sums=/{sub(/[0-9a-f]{64}/,sha)};
|
||||
1' PKGBUILD >a
|
||||
mv a PKGBUILD
|
||||
|
||||
rm -rf x
|
||||
}
|
||||
|
@ -30,5 +30,5 @@ a726fb46cce24f781fc8b55a3e6dea0a884ebc3b2b400ea74aa02333699f4955a5dc1e2ec5927ac7
|
||||
3e39ea6e16b502d99a2e6544579095d0f7c6097761cd85135d5e929b9dec1b32e80669a846f94ee8c2cca9be2f5fe728625d09453988864c04e16bb8445c3f91 pillow-11.3.0-cp313-cp313-win_amd64.whl
|
||||
59fbbcae044f4ee73d203ac74b553b27bfad3e6b2f3fb290fd3f8774753c6b545176b6b3399c240b092d131d152290ce732750accd962dc1e48e930be85f5e53 pyinstaller-6.14.1-py3-none-win_amd64.whl
|
||||
fc6f3e144c5f5b662412de07cb8bf0c2eb3b3be21d19ec448aef3c4244d779b9ab8027fd67a4871e6e13823b248ea0f5a7a9241a53aef30f3b51a6d3cb5bdb3f pyinstaller_hooks_contrib-2025.5-py3-none-any.whl
|
||||
2c7a52e223b8186c21009d3fa5ed6a856d8eb4ef3b98f5d24c378c6a1afbfa1378bd7a51d6addc500e263d7989efb544c862bf920055e740f137c702dfd9d18b python-3.13.5-amd64.exe
|
||||
3c37ea72ab062f65116a5faaaccbaa960a971797c78dbe6a504f41e562b018e7f28924647b5577fdb4fedfa61ffe0f1153842a8585f93b0332ed4d97905f6609 python-3.13.6-amd64.exe
|
||||
2a0420f7faaa33d2132b82895a8282688030e939db0225ad8abb95a47bdb87b45318f10985fc3cee271a9121441c1526caa363d7f2e4a4b18b1a674068766e87 setuptools-80.9.0-py3-none-any.whl
|
||||
|
@ -40,7 +40,7 @@ fns=(
|
||||
pillow-11.3.0-cp313-cp313-win_amd64.whl
|
||||
pyinstaller-6.14.1-py3-none-win_amd64.whl
|
||||
pyinstaller_hooks_contrib-2025.5-py3-none-any.whl
|
||||
python-3.13.5-amd64.exe
|
||||
python-3.13.6-amd64.exe
|
||||
setuptools-80.9.0-py3-none-any.whl
|
||||
)
|
||||
[ $w7 ] && fns+=(
|
||||
|
@ -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,10 @@ 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
|
||||
}
|
||||
./make-pyz.sh
|
||||
|
||||
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
|
||||
|
@ -81,6 +81,7 @@ var tl_cpanel = {
|
||||
"ad1": "enabling no304 will disable all caching; try this if k304 wasn't enough. This will waste a huge amount of network traffic!",
|
||||
"ae1": "active downloads:",
|
||||
"af1": "show recent uploads",
|
||||
"ag1": "view idp cache",
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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_u2abrt 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"
|
||||
@ -158,13 +158,13 @@ class Cfg(Namespace):
|
||||
ex = "hash_mt hsortn qdel safe_dedup srch_time tail_fd tail_rate u2abort u2j u2sz"
|
||||
ka.update(**{k: 1 for k in ex.split()})
|
||||
|
||||
ex = "au_vol dl_list mtab_age reg_cap s_thead s_tbody tail_tmax tail_who th_convt ups_who zip_who"
|
||||
ex = "ac_convt 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 qr_pin 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"
|
||||
@ -185,9 +185,12 @@ class Cfg(Namespace):
|
||||
E=E,
|
||||
bup_ck="sha512",
|
||||
chmod_d="755",
|
||||
cookie_cmax=8192,
|
||||
cookie_nmax=50,
|
||||
dbd="wal",
|
||||
dk_salt="b" * 16,
|
||||
fk_salt="a" * 16,
|
||||
grp_all="acct",
|
||||
idp_gsep=re.compile("[|:;+,]"),
|
||||
iobuf=256 * 1024,
|
||||
lang="eng",
|
||||
|
Loading…
Reference in New Issue
Block a user