Compare commits

...

172 commits

Author SHA1 Message Date
ed dbd8f837e8 hooks: add wget-i.py (import-safe) (#904) 2025-10-08 22:55:19 +00:00
ed 20ac117c32 update pkgs to 1.19.16 2025-10-05 23:08:25 +00:00
ed cd3feaac86 v1.19.16 2025-10-05 22:59:38 +00:00
ed f8e19815e1 mention ?v suffix for media links (#895) 2025-10-05 21:58:27 +00:00
ed 76e9f23a6d batch-rename: initial counter values 2025-10-05 21:51:41 +00:00
ed 4542ad3c01 hook-flag to send input on stdin 2025-10-05 20:35:03 +00:00
ed 50276c0cfa show mediatags in shares 2025-10-05 20:06:17 +00:00
ed c5f1229685 hide new-share btn in shares 2025-10-05 18:36:10 +00:00
ed 73ec2d296f rss: option to not embed pw in feed 2025-10-05 18:09:05 +00:00
ed a499648291 recommend libvips for heic/heif 2025-10-05 17:24:55 +00:00
ed 4bb5baf1b8 dangit 2025-10-05 00:44:07 +00:00
ed efd19af7ca new hook: granular ramdisk detection 2025-10-05 00:13:34 +00:00
ed aace711eb9 unvendorable surrogateescape (#887);
stolen/surrogateescape.py can be deleted;
this file is only necessary for python2 compatibility
2025-10-04 22:10:34 +00:00
ed 39bd4e5b58 unvendorable dnslib (#887);
stolen/dnslib/ can be deleted and replaced with system lib

NOTE: unvendoring dnslib will make it impossible to communicate with
  devices which have a particular avahi bug; see 6c1cf68bca
2025-10-04 22:09:40 +00:00
ed 08ebb0b4c9 unvendorable qrcodegen (#887);
move copyparty-original code to qrkode.py

stolen/qrcodegen.py can be deleted and replaced with system lib

this is safe and has minimal affect on functionality;
performance will be a tiny bit slower without the vendored copy
2025-10-04 21:36:41 +00:00
ed 656f0a6c39 unvendorable ifaddr (#887);
stolen/ifaddr/ can be deleted and replaced with system lib;
this is safe and will not affect any functionality
2025-10-04 21:33:01 +00:00
ed 805a7054e9 add missing licenses (#887);
* added missing license entry for fusepy
* added missing license entry for DOMPurify
* aligned license names with SPDX identifiers
2025-10-04 21:29:08 +00:00
ed 01709691f2 git-mv ofl.r13 2025-10-04 21:22:28 +00:00
ed 41ed559faa hooks: import-flag 2025-10-04 13:32:26 +00:00
ed a0f8f794a8 mention termux fix 2025-10-04 12:03:37 +00:00
ed fbe5fa582e helptext fix 2025-10-04 12:00:42 +00:00
ed 2248705e1a --ui-filesz can have trailing dash 2025-10-04 11:59:53 +00:00
ed eb173be4f1 folder-thumbs: cbz, epub 2025-10-04 11:57:43 +00:00
ed d05a88d2ee add rename counters; closes #854 2025-10-04 11:53:59 +00:00
ed 09e6f29e5e indent 2025-10-04 11:42:43 +00:00
ed 2ce32e4fb6 apply vol-favicon on nav; closes #882 2025-10-04 11:16:16 +00:00
ed 9b7f933b78 optimize --name-url (#884) 2025-10-04 10:45:17 +00:00
Lulu 38cc809822
Add --name-url option (#884)
Turns the server name into a hyperlink to a spefified URL

Can link back to homepage with `--name-url=/`, controlpanel with
`name-url="/?h"`, or external sites with `name-url="https://foo.bar/"`
2025-10-04 10:10:48 +00:00
ed e9b6e645d3 fix buildscript perm 2025-10-04 09:41:05 +00:00
ed 0f9a239078 allow favicon.png/gif (samsung-android) 2025-10-04 09:39:13 +00:00
ed 0453b7ac53 xhrchk: generic error only as fallback 2025-10-04 09:38:34 +00:00
Chloe Surett 1bcdf8c9e3
Add Blu-ray discs to fuzzy file size type (#878)
Signed-off-by: Chloe Surett <chloe@surett.me>
Signed-off-by: ed <s@ocv.me>
Co-authored-by: ed <s@ocv.me>
2025-10-04 09:35:58 +00:00
ed 4177c1d9ed epub: handle missing covers; closes #860 2025-10-04 09:30:43 +00:00
ed 171ca985c8 bbox: flex conditionally 2025-10-04 09:23:42 +00:00
AppleTheGolden dacc64dd2e
baguettebox: RTL support (#881) 2025-10-04 09:12:02 +00:00
Kaleb Debre 31f1b535b2
nixos: unix-user/group to run as (#886)
Co-authored-by: Kaleb Debre <kaleb.debre@web.de>
2025-10-03 17:13:28 +02:00
Daniel Lovegrove 7fc379abc8
Add setup example for running with Podman under systemd (#460)
* Create copyparty.container file
* Add and document rootful configuration
* Add non-root config, clean up README

Signed-off-by: Daniel Lovegrove <d.lovegrove11@gmail.com>
2025-10-03 05:45:44 +02:00
ed a8f53d5ef0 shrink docker-min from 45 to 33 MiB 2025-09-30 23:13:46 +00:00
ed 3f59710294 allow chpw with idp; closes #872 2025-09-30 21:26:37 +00:00
ed 24e01221c5 update pkgs to 1.19.15 2025-09-29 23:19:00 +00:00
ed daba1ab7bd v1.19.15 2025-09-29 23:10:18 +00:00
ed 1bca86c6e1 thx pyright 2025-09-29 23:07:03 +00:00
ed fc2754cba5 option to delete .PARTIAL on expiration 2025-09-29 22:16:57 +00:00
ed 470b504843 raster favicons; closes #383, #473 2025-09-29 21:50:13 +00:00
ed 435db14798 pregen html_head when static 2025-09-29 21:47:53 +00:00
ed d08e872062 formatting 2025-09-28 22:23:02 +00:00
ed f91a653bde zed pls 2025-09-28 22:22:27 +00:00
ed 7d86f39a23 uds-only mdns fix; closes #864 2025-09-28 22:17:30 +00:00
ed 456addf26f show warks in folder-listings 2025-09-28 21:37:46 +00:00
ed 4e38e4087e make warks (checksums) searchable 2025-09-28 21:36:45 +00:00
suza f0ecb08347
fix(opengraph): video embeds (#870) 2025-09-28 20:13:37 +00:00
ed 1193f9ba6c stop binary garbage from hitting logs 2025-09-28 19:29:18 +00:00
ed 234eddec90 filesize formats; closes #184 2025-09-28 01:14:00 +00:00
ed e3baf932f3 reflinks are non-e2d safe 2025-09-27 19:29:36 +00:00
ed eb5d767b01 MTHash: fully preserve exception info 2025-09-27 19:28:41 +00:00
ed ec7418734d uds-only http/https; closes #855 2025-09-27 19:12:06 +00:00
ed a3d9506783 mdns: customize http/https ports (#855) 2025-09-27 19:11:15 +00:00
ed 57650a218f use reflinks (not hardlinks) in -ss; closes #858 2025-09-27 18:44:14 +00:00
ed 983865d96c cbz thumbs without ffmpeg; closes #859 2025-09-27 18:39:18 +00:00
ed 6f6b70ad04 fix misleading comments 2025-09-27 00:22:28 +00:00
ed e187df28f2 fix markdown-expand example 2025-09-26 23:50:19 +00:00
ed df0fa9d1b7 xbu/xau with custom message 2025-09-26 23:49:32 +00:00
ed 397ed5653b overwrite on upload with header "replace" 2025-09-26 21:35:00 +00:00
ed 9f46e4dbd7 koie layout 2025-09-26 21:17:07 +00:00
ed 6912e86747 loud warning on EIO (HDD/FS trouble) (#851) 2025-09-25 23:18:09 +00:00
ed 80ca78516e create idp-db when necessary (#849) 2025-09-24 21:32:15 +00:00
ed a493cd6530 update pkgs to 1.19.14 2025-09-23 23:06:19 +00:00
ed c72b62ad86 v1.19.14; closes #847 2025-09-23 22:47:07 +00:00
ed fdcd92bac8 update pkgs to 1.19.13 2025-09-23 21:43:35 +00:00
ed b00dac997a v1.19.13 2025-09-23 21:20:13 +00:00
ed 9d066414c6 friendly http-5XX errors 2025-09-23 20:42:06 +00:00
ed 5e4ff90b1c config-loader: support utf8-bom 2025-09-23 20:00:45 +00:00
ed deb8a4a86e readme: ./scripts/toc.sh 2025-09-23 19:40:40 +00:00
ed 733e85c040 cosmetic: fix uptoast on page load 2025-09-23 19:38:24 +00:00
ed 892a452446 tl cleanup 2025-09-23 19:35:34 +00:00
ed 38df223b8f reindent: try/catch subchunk logic;
ensures chunks get unlocked on throw

reindent of fc8298c468
2025-09-23 19:35:21 +00:00
ed b136a5b042 fast_confirm_chunks: release all on error;
possibly fixes an issue someone has been runnning into:

an upload could get stuck on "that chunk is already being written to"
when the server was overloaded enough that connections kept dropping
2025-09-23 19:11:41 +00:00
ed 377eddcd06 tl cleanup 2025-09-22 21:15:26 +00:00
NandeMD 549fe33f51
add Turkish translation (#786)
Signed-off-by: NandeMD <76772692+NandeMD@users.noreply.github.com>
2025-09-22 21:13:18 +00:00
ed c214a93caa opdf: fix merge issues 2025-09-22 20:37:33 +00:00
ed 0941fd4ec1 ensure ?ls not from tx_ls_vols (unmapped root)
fixes regression in 8f6194fe
2025-09-22 19:58:41 +00:00
AppleTheGolden 6dbd9901b2
OPDS Support (#779)
* add OPDS support
* add `?opds` to devnotes.md
* send content-disposition for opds downloads
2025-09-22 19:34:34 +00:00
Taylor e9ca36fa88 versus.md: add links with tooltips to the comparison matrices 2025-09-22 18:12:16 +00:00
augustanational a053a663b4
versus.md: sftpgo (#839)
Signed-off-by: augustanational <milanistavietnam1911@gmail.com>
2025-09-22 16:24:14 +02:00
ed 1923a25879 update pkgs to 1.19.12 2025-09-21 22:21:44 +00:00
ed 4cce799012 v1.19.12 2025-09-21 22:11:56 +00:00
ed 1460fe97ac rotf timezone option; closes #802 2025-09-21 21:21:41 +00:00
ed ca872c4055 hide unpost tab in read-only vols; closes #836 2025-09-21 20:45:56 +00:00
ed 3ddb4c042a tl cleanup 2025-09-21 19:59:29 +00:00
/dev/urandom 15d3c2fbff
Esperanto translation (#787) 2025-09-21 19:53:33 +00:00
ed 6a24432019 windows: fix sharing entire drives (closes #837);
broke in 8b66874b
2025-09-21 19:44:35 +00:00
ed 8f6194fe77 /?ls on unmapped root 2025-09-21 19:35:28 +00:00
25huizengek1 260da2f45c fix: rootless podman/docker not building correctly on NixOS 2025-09-21 00:36:32 +00:00
ed a9e02ce753 update pkgs to 1.19.11 2025-09-20 10:17:59 +00:00
ed 70c088aeca v1.19.11 2025-09-20 10:11:03 +00:00
ed 280815f158 ftp: fix unmapped root; closes #827 2025-09-20 10:05:29 +00:00
ed e1ea9852c6 update pkgs to 1.19.10 2025-09-19 22:52:29 +00:00
ed 2ee9c80d3b v1.19.10 2025-09-19 22:44:54 +00:00
ed 4b2ff3a196 epub-thumbs errorhandling 2025-09-19 22:19:01 +00:00
ed 538a205ce4 fix up2k fstab after 59a01221 2025-09-19 21:50:21 +00:00
ed 6559152882 add free-threading suffix to version dump 2025-09-19 21:27:13 +00:00
ed 669b10754d copyparty32.exe: english-only 2025-09-19 21:21:44 +00:00
ed 478f1c764e logging: limit/disable fs-indexing progress 2025-09-19 21:05:42 +00:00
ed a043d7cfb6 explain daw better 2025-09-19 20:35:01 +00:00
Nora Struck ee5f31908f Add groups to nix config 2025-09-19 20:15:30 +00:00
ed 35326a6fb8 iOS: fix a-shell docs; closes #806 2025-09-19 20:13:10 +00:00
ed 59a0122179 prevent upload into ramdisk;
tries to detect misconfigured docker environments, e.g. /w/foo is mapped
to a disk but /w/ itself isn't
2025-09-19 19:46:14 +00:00
ed 5996a58b20 fix medialinks sans ls0 (closes #809);
on a browser's very first visit, the first page load would not hydrate
correctly, initializing msel without file-IDs, causing medialinks
(#gf-0f6f5c0d) to throw an error
2025-09-16 22:28:41 +00:00
ed fd331a545d nice typo 2025-09-15 01:05:17 +00:00
ed e7ef31ee33 update pkgs to 1.19.9 2025-09-15 00:44:51 +00:00
ed 49ce67e9cd v1.19.9 2025-09-15 00:19:24 +00:00
ed 8b66874b85 be case-sensitive on windows/macos (closes #781);
on Windows and Macos, most filesystems are case-insensitive,
which can lead to dangerous situations

one example is when another program (not copyparty or its UI) wants to
rename a file from `Foo` to `foo`; the program will probably start by
checking if `foo` exists and then delete it, however this would match
`Foo` and confuse the program into deleting the wrong file

fix this by adding a VERY EXPENSIVE detector to prevent this,
by listing the parent folder and checking if the case matches

this check will auto-enable when a case-insensitive FS is detected on
startup, but option `casechk` (global or volflag) can override this
2025-09-14 23:39:46 +00:00
ed 3a2381ff2d webdav: fix depth:0 in rootless vfs;
also safeguards against potential issues with invalid
paths if the api is used incorrectly from a plugin
2025-09-14 23:27:33 +00:00
ed 83bd197438 warn on invalid idp-volume mapping 2025-09-14 23:20:45 +00:00
ed c03b332ec0 linter fixes 2025-09-14 23:18:34 +00:00
ed 17b4f905a7 misc buildscript / docs:
* make-sfx.sh: fix missing licenses on very first build
* docker/make.sh: add warning on missing deps
* pyinstaller: cleanup notes
* add notes:
  * building on archlinux
  * buliding python 2.7
* support for Zed IDE
2025-09-14 23:15:18 +00:00
ed 8f587627e1 download-as-zip: better names for selections;
previously, the first selected file became the name of the zip

now, the name will be "sel-foldername", similar to when the whole
folder is downloaded, but with a prefix to indicate it's a subset
2025-09-13 21:10:10 +00:00
ed 14b7e5143f md-editor: fix extra http roundtrip;
the check for a leading newline was not specific enough,
accidentally matching the opening line of a json document,
triggering the xhr safeguard and wasting a roundtrip
2025-09-13 21:09:59 +00:00
ed 3e97a77784 cosmetic fixes after edafa1586 2025-09-13 21:04:16 +00:00
ed 3f45492725 fix --help on non-utf8 consoles 2025-09-13 20:59:00 +00:00
ed 9c9e4057e0 media-tags: unmap "conductor" (was album-artist) 2025-09-13 20:55:08 +00:00
ed 3d09bec1bb fix ?v to opt-out from index.html 2025-09-13 20:53:12 +00:00
ed d7887f3d55 qr-code can optionally ignore -q;
new options --qr-stdout and --qr-stderr will always print the qr-code
into the console, even if copyparty is running in quiet mode (-q)

closes https://codeberg.org/9001/copyparty/issues/1
2025-09-13 20:14:50 +00:00
ed ecd18adc3c optional case-insensitive unicode search (#789);
made it default-disabled because it's a bit expensive
2025-09-13 19:48:08 +00:00
Kyler Clay e2aa8fc1a4
case-insensitive search for non-ascii filenames/paths (#789)
the previous case-insensitive searching of filenames and paths
would only apply to ascii letters; extend this to all characters
2025-09-13 19:44:26 +00:00
Beethoven dfd9e007ee
debian: Fix launcher path in systemd service (#798)
assume launcher is in /usr/local/bin/ rather than /usr/bin
which is the case as of recently

Signed-off-by: Beethoven <44652883+Beethoven-n@users.noreply.github.com>
2025-09-13 18:45:53 +00:00
David Sullivan 5c1a43c711
fix hotkey typo (P->O) in lightbox (#788)
helptext indicated that P was the hotkey for seeking

Signed-off-by: David Sullivan <311316+tkroo@users.noreply.github.com>
2025-09-11 00:31:58 +02:00
daimond113 52438bcc0d update polish pluralization 2025-09-08 23:18:18 +02:00
ed e09f3c9e2c shutil: ignore errors from copystat in copy2;
ntfs on linux can be picky about cloning mtime onto a new file;
generally we don't care if that fails, however, we also want the
speedup that CopyFile2 can offer, so cannot use copyfile directly

this avoids the following issue:

up2k:3537 <_symlink>: shutil.copy2(fsenc(csrc), fsenc(dst))
shutil:437 <copy2>: copystat(src, dst, follow_symlinks=follow_sym[...]
shutil:376 <copystat>: lookup("utime")(dst, ns=(st.st_atime_ns, s[...]
[PermissionError] [Errno 1] Operation not permitted, '/windows/videos'
2025-09-08 20:21:12 +00:00
ed 25749b4b5f accept empty files through bup; closes #775 2025-09-08 10:08:34 +02:00
ed 75b0b312a4 update pkgs to 1.19.8 2025-09-07 23:16:36 +00:00
ed c47c708433 v1.19.8 2025-09-07 23:00:05 +00:00
ed e0a92ba72d fence fileshares to just those files
when a share is created for just a single file, it was possible to
guess other filenames in the source folder and access those files
2025-09-07 22:48:31 +00:00
ed 98386f28f0 simplify og_ua logic;
idk what this was *supposed* to do but what it *did* was prevent
loading the full image even when the request had a good referrer
(this broke viewing images in firefox at least)
2025-09-07 20:54:37 +00:00
ed 422f8f624e fix volflag og_ua 2025-09-07 20:42:23 +00:00
ed edafa1586a volflag to block sharing of a volume 2025-09-07 17:20:51 +00:00
ed e270fe60ed fix uds perms with rm-sck 2025-09-07 09:02:43 +00:00
ed ab56238249 docker: fix image annotations;
docker buildx imagetools inspect copyparty/ac:beta@sha256:[...] --raw
would show the annotations from the base alpine image instead of ours

thx to @EmilyxFox for figuring this out!
2025-09-06 23:44:48 +00:00
ed 3bdef75e88 connectpage: usernames 2025-09-06 22:17:48 +00:00
ed 67ba5b0252 partyfuse: suggest fuse2 2025-09-06 22:12:07 +00:00
ed 06d2654b3f partyfuse: readdir from cache;
dircache only applied to `getattr` and not `readdir` itself
2025-09-06 21:31:09 +00:00
ed 1cdb388090 partyfuse: usernames 2025-09-06 21:00:21 +00:00
ed f7e08ed007 defer next-song hotkey while changing folders 2025-09-05 23:19:20 +00:00
ed b049631169 ftp: CWD is optional (#539) 2025-09-05 22:36:16 +00:00
ed aaeec11f81 bail from aborted batch operations; closes #748
f.shift() in rename_cb would return null since the queue was dumped
2025-09-05 21:43:33 +00:00
ed 96b109b0d6 decrement folder-sz on delete; closes #759, #393 2025-09-05 21:03:30 +00:00
ed 74821a38ad speed 2025-09-05 20:38:04 +00:00
ed 19a4c45389 rbac disk-info and --ver (closes #726);
options --du-who and --ver-who specifies who can see the disk-info
(disk-usage, disk-free) and server-version based on user permissions
2025-09-05 19:48:38 +00:00
ed 09f22993be idp login/logout routes (#761) 2025-09-05 18:44:30 +00:00
ed c2be664e96 cleanup jinja whitespace 2025-09-05 18:12:53 +00:00
ed 7a4ee4dbc8 apply ipr during login too (#397) 2025-09-05 16:20:00 +00:00
ed bd6d1f961d konmai intensifies
thx SG
2025-09-04 23:48:22 +00:00
ed eeb7738b53 clamp utime to filesystem limits (#539) 2025-09-04 23:31:05 +00:00
ed e6755aa8a1 restrict runtime-state in $TMP; closes #747
the preferred locations (XDG_CONFIG_HOME and ~/.config)
are trusted and will behave as before, because they are
only writable by the current unix-user

but when an emergency fallback location ($TMPDIR or /tmp) is used
because none of the preferred locations are writable, then this
will now force-disable sessions-db, idp-db, chpw, and shares

this security safeguard can be overridden with --unsafe-state

will now also create the config folder with chmod 700 (rwx------)
2025-09-03 21:55:07 +00:00
ed 230a146209 ignore dotfiles in config-folders; closes #727
macos adds garbage files named ._something.conf
into config folders, crashing the config parser
2025-09-03 19:57:28 +00:00
ed c71128fd72 ignore cppws on plaintext;
cppws, if set from https context, cannot be cleared by plaintext

this could lead to confusing login/logout behavior
2025-09-03 19:50:54 +00:00
ed b59b915962 ie11 fixes 2025-09-03 19:48:47 +00:00
ed f0caf88185 add konmai quality
blame msw for this :p
2025-09-03 19:45:19 +00:00
ed bfcb6eac41 fix chrome reverting video pause toggles
pausing a video with spacebar while video is focused would first
get handled by the js hotkey, and then chrome would ignore our
hint that bubbling should cease and undo it anyways
2025-09-03 19:37:24 +00:00
ed e798a9a53a fix hotkeys on dvorak (closes #298, closes #733);
apparently the convention is that hotkeys should follow the letters
according to the layout, and not remain in the qwerty position

this breaks apart the cluster of media controls (uiojkl),
but that's the intended and expected behavior so it should be fine
2025-09-03 19:33:48 +00:00
xvrqt 09e3018bf9
nix-module: Add globalExtraConfig option (#751)
Added an option, 'services.copyparty.globalExtraConfig', with default
value and description to the NixOS Module. The option type is 'str' and
the default value is the empty string.

This string is appened verbatim to the [global] section of the config.
This allows the use of settings which rely on repeated values to be
correctly used. For example, the: 'ipu: 255.255.255.1/32=user' key which
allows automatic sign in for users of a CIDR subnet. Because attribute
sets in Nix must have unique keys, it is not possible to set more than
one CIDR subnet/user pair.

Signed-off-by: xvrqt <git@xvrqt.com>
2025-09-03 10:03:59 +02:00
ed 87539800e8 FTPS: add curl example (#734) 2025-08-31 19:06:56 +02:00
Christian Kastner 0469b5a29e bubbleparty.sh: process substitution requires bash
POSIX shell does not yet support  `<(process substitution)`.

Signed-off-by: Christian Kastner <ckk@kvr.at>
2025-08-31 18:44:39 +02:00
ed 3e90abbf6f update pkgs to 1.19.7 2025-08-28 20:24:59 +00:00
ed 26a29797a6 v1.19.7 2025-08-28 20:14:50 +00:00
ed 14555d5832 add chdir option 2025-08-28 20:14:25 +00:00
ed d1f75229b5 docker: ensure /state writable 2025-08-28 19:45:47 +00:00
ed 01cf20a029 docker: change $HOME to /state 2025-08-28 19:41:42 +00:00
EmilyxFox 6f0871173e
Set org.opencontainers.image.source label correctly in all dockerfiles (#717) 2025-08-28 21:07:42 +02:00
ed 914686ec7c fix navigation by holding I/K 2025-08-28 18:46:11 +02:00
ed 0d96786e68 fix using empty dir as state storage;
also supports 4111 (d--x--x--x) XDG_CONFIG_HOME
2025-08-28 18:15:37 +02:00
ed 4c3792de07 update pkgs to 1.19.6 2025-08-27 21:03:02 +00:00
93 changed files with 5188 additions and 1043 deletions

52
.vscode/launch.json vendored
View file

@ -3,7 +3,7 @@
"configurations": [
{
"name": "Run copyparty",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "copyparty",
"console": "integratedTerminal",
@ -11,30 +11,46 @@
"justMyCode": false,
"env": {
"PYDEVD_DISABLE_FILE_VALIDATION": "1",
"PYTHONWARNINGS": "always", //error
"PYTHONWARNINGS": "always" //error
},
"args": [
//"-nw",
"-ed",
"-emp",
//"-nw", // no-write; for testing uploads without writing to disk
//"-q", // quiet; speedboost when console output is not needed
// # increase debugger performance:
//"no-htp",
//"hash-mt=0",
//"mtag-mt=1",
//"th-mt=1",
// # listen for FTP and TFTP
"--ftp=3921",
"--ftp-pr=12000-12099",
"--tftp=3969",
// # listen on all IPv6, all IPv4, and unix-socket
"-i::,unix:777:a.sock",
// # misc
"--dedup",
"-e2dsa",
"-e2ts",
"-mtp=.bpm=f,bin/mtag/audio-bpm.py",
"--rss",
"--shr=/shr",
"--stats",
"-z",
// # users + volumes
"-aed:wark",
"-vsrv::r:rw,ed:c,dupe",
"-vdist:dist:r"
"-vdist:dist:r",
"-vsrv::r:rw,ed",
"-vsrv/junk:junk:r:A,ed",
"--ver"
]
},
{
"name": "No debug",
"preLaunchTask": "no_dbg",
"type": "python",
//"request": "attach", "port": 42069
// fork: nc -l 42069 </dev/null
},
{
"name": "Run active unit test",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "unittest",
"console": "integratedTerminal",
@ -51,6 +67,6 @@
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": false
},
}
]
}
}

View file

@ -50,6 +50,7 @@ made in Norway 🇳🇴
* [shares](#shares) - share a file or folder by creating a temporary link
* [batch rename](#batch-rename) - select some files and press `F2` to bring up the rename UI
* [rss feeds](#rss-feeds) - monitor a folder with your RSS reader
* [opds feeds](#opds-feeds) - browse and download files from your e-book reader
* [recent uploads](#recent-uploads) - list all recent uploads
* [media player](#media-player) - plays almost every audio format there is
* [playlists](#playlists) - create and play [m3u8](https://en.wikipedia.org/wiki/M3U) playlists
@ -137,6 +138,7 @@ made in Norway 🇳🇴
* [dependencies](#dependencies) - mandatory deps
* [optional dependencies](#optional-dependencies) - install these to enable bonus features
* [dependency chickenbits](#dependency-chickenbits) - prevent loading an optional dependency
* [dependency unvendoring](#dependency-unvendoring) - force use of system modules
* [optional gpl stuff](#optional-gpl-stuff)
* [sfx](#sfx) - the self-contained "binary" (recommended!)
* [copyparty.exe](#copypartyexe) - download [copyparty.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty.exe) (win8+) or [copyparty32.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty32.exe) (win7+)
@ -212,6 +214,7 @@ you may also want these, especially on servers:
* [contrib/systemd/copyparty.service](contrib/systemd/copyparty.service) to run copyparty as a systemd service (see guide inside)
* [contrib/systemd/prisonparty.service](contrib/systemd/prisonparty.service) to run it in a chroot (for extra security)
* [contrib/podman-systemd/](contrib/podman-systemd/) to run copyparty in a Podman container as a systemd service (see guide inside)
* [contrib/openrc/copyparty](contrib/openrc/copyparty) to run copyparty on Alpine / Gentoo
* [contrib/rc/copyparty](contrib/rc/copyparty) to run copyparty on FreeBSD
* [nixos module](#nixos-module) to run copyparty on NixOS hosts
@ -728,6 +731,10 @@ to show `/icons/exe.png` and `/icons/elf.gif` as the thumbnail for all `.exe` an
* the supported image formats are [jpg, png, gif, webp, ico](https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/Image_types)
* be careful with svg; chrome will crash if you have too many unique svg files showing on the same page (the limit is 250 or so) -- showing the same handful of svg files thousands of times is ok however
note:
* heif/heifs/heic/heics images usually require the `libvips` [optional dependency](#optional-dependencies) (available in the `iv` docker image, `withFastThumbnails` in nixos)
* technical trivia: FFmpeg has basic support for tiled heic as of v7.0; need `-show_stream_groups` for correct resolution
config file example:
```yaml
@ -868,6 +875,8 @@ the files will be hashed on the client-side, and each hash is sent to the server
files go into `[ok]` if they exist (and you get a link to where it is), otherwise they land in `[ng]`
* the main reason filesearch is combined with the uploader is cause the code was too spaghetti to separate it out somewhere else, this is no longer the case but now i've warmed up to the idea too much
if you have a "wark" (file-identifier/checksum) then you can also search for that in the [🔎] tab by putting `w = kFpDiztbZc8Z1Lzi` in the `raw` field
### unpost
@ -994,6 +1003,8 @@ available functions:
* `$lpad(text, length, pad_char)`
* `$rpad(text, length, pad_char)`
two counters are available; `.n.s` is the nth file in the selection, and `.n.d` the nth file in the folder, for example rename-output `file(.n.d).(ext)` gives `file5.bin`, and `beach-$lpad((.n.s),3,0).(ext)` is `beach-017.jpg` and the initial value of each counter can be set in the textboxes underneath the preset dropdown
so,
say you have a file named [`meganeko - Eclipse - 07 Sirius A.mp3`](https://www.youtube.com/watch?v=-dtb0vDPruI) (absolutely fantastic album btw) and the tags are: `Album:Eclipse`, `Artist:meganeko`, `Title:Sirius A`, `tn:7`
@ -1029,6 +1040,8 @@ url parameters:
* `pw=hunter2` for password auth
* if you enabled `--usernames` then do `pw=username:password` instead
* `nopw` disables embedding the password (if provided) into item-URLs in the feed
* `nopw=a` disables mentioning the password anywhere at all in the feed; may break some readers
* `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)
@ -1040,6 +1053,28 @@ url parameters:
* uppercase = reverse-sort; `M` = oldest file first
## opds feeds
browse and download files from your e-book reader
enabled with the `opds` volflag or `--opds` global option
add `?opds` to the end of the url you would like to browse, then input that in your opds client.
for example: `https://copyparty.example/books/?opds`.
to log in with a password, enter it into either of the username or password fields in your client.
- if you've enabled `--usernames`, then you need to enter both username and password .
note: some clients (e.g. Moon+ Reader) will not send the password when downloading cover images, which will
cause your ip to be banned by copyparty. to work around this, you can grant the [`g` permission](#accounts-and-volumes)
to unauthenticated requests and enable [filekeys](#filekeys) to prevent guessing filenames. for example:
`-vbooks:books:r,ed:g:c,fk,opds`
by default, not all file types will be listed in opds feeds. to change this, add the extension to
`--opds-exts` (volflag: `opds_exts`), or empty the list to list everything
## recent uploads
list all recent uploads by clicking "show recent uploads" in the controlpanel
@ -1073,6 +1108,7 @@ some highlights:
* shows the audio waveform in the seekbar
* not perfectly gapless but can get really close (see settings + eq below); good enough to enjoy gapless albums as intended
* videos can be played as audio, without wasting bandwidth on the video
* adding `?v` to the end of an audio/video/image link will make it open in the mediaplayer
click the `play` link next to an audio file, or copy the link target to [share it](https://a.ocv.me/pub/demo/music/Ubiktune%20-%20SOUNDSHOCK%202%20-%20FM%20FUNK%20TERRROR!!/#af-1fbfba61&t=18) (optionally with a timestamp to start playing from, like that example does)
@ -1328,6 +1364,8 @@ some recommended FTP / FTPS clients; `wark` = example password:
* https://rclone.org/ does FTPS with `tls=false explicit_tls=true`
* `lftp -u k,wark -p 3921 127.0.0.1 -e ls`
* `lftp -u k,wark -p 3990 127.0.0.1 -e 'set ssl:verify-certificate no; ls'`
* `curl ftp://127.0.0.1:3921/` (plaintext ftp)
* `curl --ssl-reqd ftp://127.0.0.1:3990/` (encrypted ftps)
config file example, which restricts FTP to only use ports 3921 and 12000-12099 so all of those ports must be opened in your firewall:
@ -1351,7 +1389,7 @@ general usage:
on macos, connect from finder:
* [Go] -> [Connect to Server...] -> http://192.168.123.1:3923/
in order to grant full write-access to webdav clients, the volflag `daw` must be set and the account must also have delete-access (otherwise the client won't be allowed to replace the contents of existing files, which is how webdav works)
to upload or edit files with WebDAV clients, enable the `daw` volflag (because most WebDAV clients expect this) and give your account the delete-permission. This avoids getting several copies of the same file on the server. HOWEVER: This will also make all PUT-uploads overwrite existing files if the user has delete-access, so use with caution.
> note: if you have enabled [IdP authentication](#identity-providers) then that may cause issues for some/most webdav clients; see [the webdav section in the IdP docs](https://github.com/9001/copyparty/blob/hovudstraum/docs/idp.md#connecting-webdav-clients)
@ -1552,6 +1590,7 @@ the same arguments can be set as volflags, in addition to `d2d`, `d2ds`, `d2t`,
note:
* upload-times can be displayed in the file listing by enabling the `.up_at` metadata key, either globally with `-e2d -mte +.up_at` or per-volume with volflags `e2d,mte=+.up_at` (will have a ~17% performance impact on directory listings)
* and file checksums can be shown with global-option `-e2d -mte +w` or volflag `e2d,mte=+w` (always active for users with permission `a`)
* `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those, unless there is a new copyparty version with new parsers and the release note says otherwise
config file example (these options are recommended btw):
@ -1635,6 +1674,7 @@ set upload rules using volflags, some examples:
* `:c,nosub` disallow uploading into subdirectories; goes well with `rotn` and `rotf`:
* `:c,rotn=1000,2` moves uploads into subfolders, up to 1000 files in each folder before making a new one, two levels deep (must be at least 1)
* `:c,rotf=%Y/%m/%d/%H` enforces files to be uploaded into a structure of subfolders according to that date format
* `:c,rotf_tz=Europe/Oslo` sets the timezone (default is UTC unless global-option `rotf-tz` is changed)
* if someone uploads to `/foo/bar` the path would be rewritten to `/foo/bar/2021/08/06/23` for example
* but the actual value is not verified, just the structure, so the uploader can choose any values which conform to the format string
* just to avoid additional complexity in up2k which is enough of a mess already
@ -1940,6 +1980,10 @@ you can disable the built-in password-based login system, and instead replace it
* the regular config-defined users will be used as a fallback for requests which don't include a valid (trusted) IdP username header
* `--auth-ord` configured auth precedence, for example to allow overriding the IdP with a copyparty password
* the login/logout links/buttons can be replaced with links to your IdP with `--idp-login` and `--idp-logout` , for example `--idp-login /idp/login/?redir={dst}` will expand `{dst}` to the page the user was on when clicking Login
* if your IdP-server is slow, consider `--idp-cookie` and let requests with the cookie `cppws` bypass the IdP; experimental sessions-based feature added for a party
some popular identity providers are [Authelia](https://www.authelia.com/) (config-file based) and [authentik](https://goauthentik.io/) (GUI-based, more complex)
@ -2316,6 +2360,7 @@ buggy feature? rip it out by setting any of the following environment variables
| `PRTY_NO_SQLITE` | disable all database-related functionality (file indexing, metadata indexing, most file deduplication logic) |
| `PRTY_NO_TLS` | disable native HTTPS support; if you still want to accept HTTPS connections then TLS must now be terminated by a reverse-proxy |
| `PRTY_NO_TPOKE` | disable systemd-tmpfilesd avoider |
| `PRTY_UNSAFE_STATE` | allow storing secrets into emergency-fallback locations |
example: `PRTY_NO_IFADDR=1 python3 copyparty-sfx.py`
@ -2431,6 +2476,10 @@ copyparty on NixOS is configured via `services.copyparty` options, for example:
```nix
services.copyparty = {
enable = true;
# the user to run the service as
user = "copyparty";
# the group to run the service as
group = "copyparty";
# directly maps to values in the [global] section of the copyparty config.
# see `copyparty --help` for available options
settings = {
@ -2455,6 +2504,12 @@ services.copyparty = {
k.passwordFile = "/run/keys/copyparty/k_password";
};
# create a group
groups = {
# users "ed" and "k" are part of the group g1
g1 = [ "ed" "k" ];
};
# create a volume
volumes = {
# create a volume at "/" (the webroot), which will
@ -2698,6 +2753,10 @@ below are some tweaks roughly ordered by usefulness:
* using [pypy](https://www.pypy.org/) instead of [cpython](https://www.python.org/) *can* be 70% faster for some workloads, but slower for many others
* and pypy can sometimes crash on startup with `-j0` (TODO make issue)
* if you are running the copyparty server **on Windows or Macos:**
* `--casechk=n` makes it much faster, but also awakens [the usual surprises](https://github.com/9001/copyparty/issues/781) you expect from a case-insensitive filesystem
* this is the same as `casechk: n` in a config-file
## client-side
@ -2940,6 +2999,20 @@ example: `PRTY_NO_PIL=1 python3 copyparty-sfx.py`
* python2.7 on windows: `PRTY_NO_FFMPEG` + `PRTY_NO_FFPROBE` saves startup time
### dependency unvendoring
force use of system modules instead of the vendored versions:
| env-var | what it does |
| -------------------- | ------------ |
| `PRTY_SYS_ALL` | all of the below |
| `PRTY_SYS_DNSLIB` | replace [stolen/dnslib](./copyparty/stolen/dnslib) with [upstream](https://pypi.org/project/dnslib/) |
| `PRTY_SYS_IFADDR` | replace [stolen/ifaddr](./copyparty/stolen/ifaddr) with [upstream](https://pypi.org/project/ifaddr/) |
| `PRTY_SYS_QRCG` | replace [stolen/qrcodegen.py](./copyparty/stolen/qrcodegen.py) with [upstream](https://github.com/nayuki/QR-Code-generator/blob/master/python/qrcodegen.py) |
to debug, run copyparty with `PRTY_MODSPEC=1` to see where it's getting each module from
## optional gpl stuff
some bundled tools have copyleft dependencies, see [./bin/#mtag](bin/#mtag)
@ -3002,6 +3075,8 @@ if you want thumbnails (photos+videos) and you're okay with spending another 132
* or if you want to use `vips` for photo-thumbs instead, `pkg install libvips && python -m pip install --user -U wheel && python -m pip install --user -U pyvips && (cd /data/data/com.termux/files/usr/lib/; ln -s libgobject-2.0.so{,.0}; ln -s libvips.so{,.42})`
if you are suddenly unable to access storage (permission issues), try forcequitting termux, revoke all of its permissions in android settings, and run the command `termux-setup-storage`
# install on iOS
@ -3012,7 +3087,7 @@ first install one of the following:
and then copypaste the following command into `a-Shell`:
```sh
curl https://github.com/9001/copyparty/raw/refs/heads/hovudstraum/contrib/setup-ashell.sh | sh
curl -L https://github.com/9001/copyparty/raw/refs/heads/hovudstraum/contrib/setup-ashell.sh | sh
```
what this does:

View file

@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
# usage: ./bubbleparty.sh ./copyparty-sfx.py ....
bwrap \
--unshare-all \

View file

@ -28,10 +28,19 @@ these are `--xiu` hooks; unlike `xbu` and `xau` (which get executed on every sin
* [reject-extension.py](reject-extension.py) rejects uploads if they match a list of file extensions
* [reloc-by-ext.py](reloc-by-ext.py) redirects an upload to another destination based on the file extension
* good example of the `reloc` [hook effect](https://github.com/9001/copyparty/blob/hovudstraum/docs/devnotes.md#hook-effects)
* [reject-and-explain.py](reject-and-explain.py) shows a custom error-message when it rejects an upload
* [reject-ramdisk.py](reject-ramdisk.py) rejects the upload if the destination is a ramdisk
* this hook uses the `I` flag which makes it 140x faster, but if the plugin has a bug it may crash copyparty
# on message
* [wget.py](wget.py) lets you download files by POSTing URLs to copyparty
* [wget-i.py](wget-i.py) is an import-safe modification of this hook (starts 140x faster, but higher chance of bugs)
* [qbittorrent-magnet.py](qbittorrent-magnet.py) starts downloading a torrent if you post a magnet url
* [usb-eject.py](usb-eject.py) adds web-UI buttons to safe-remove usb flashdrives shared through copyparty
* [msg-log.py](msg-log.py) is a guestbook; logs messages to a doc in the same folder
# general concept demos
* [import-me.py](import-me.py) shows how the `I` flag makes the hook 140x faster (but you need to be Very Careful when writing the plugin)
* [wget-i.py](wget-i.py) is an import-safe modification of [wget.py](wget.py)

55
bin/hooks/import-me.py Normal file
View file

@ -0,0 +1,55 @@
#!/usr/bin/env python3
from typing import Any
_ = r"""
the fastest hook in the west
(runs directly inside copyparty, not as a subprocess)
example usage as global config:
--xbu I,bin/hooks/import-me.py
example usage as a volflag (per-volume config):
-v srv/inc:inc:r:rw,ed:c,xbu=I,bin/hooks/import-me.py
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(share filesystem-path srv/inc as volume /inc,
readable by everyone, read-write for user 'ed',
running this plugin on all uploads with the params listed below)
example usage as a volflag in a copyparty config file:
[/inc]
srv/inc
accs:
r: *
rw: ed
flags:
xbu: I,bin/hooks/import-me.py
parameters explained,
I = import; do not fork / subprocess
IMPORTANT NOTE:
because this hook is running inside copyparty, you need to
be EXCEPTIONALLY CAREFUL to avoid side-effects, for example
DO NOT os.chdir() or anything like that, and also make sure
that the name of this file is unique (cannot be the same as
an existing python module/library)
"""
def main(ka: dict[str, Any]) -> dict[str, Any]:
# "ka" is a dictionary with info from copyparty...
# but because we are running inside copyparty, we don't need such courtesies;
import inspect
cf = inspect.currentframe().f_back.f_back.f_back
t = "hello from hook; I am able to peek into copyparty's memory like so:\n function name: %s\n variables:\n %s\n"
t2 = "\n ".join([("%r: %r" % (k, v))[:99] for k, v in cf.f_locals.items()][:9])
logger = ka["log"]
logger(t % (cf.f_code, t2))
# must return a dictionary with:
# "rc": the retcode; 0 is ok
return {"rc": 0}

View file

@ -0,0 +1,60 @@
#!/usr/bin/env python3
import json
import os
import re
import sys
_ = r"""
reject file upload (with a nice explanation why)
example usage as global config:
--xbu j,c1,bin/hooks/reject-and-explain.py
example usage as a volflag (per-volume config):
-v srv/inc:inc:r:rw,ed:c,xbu=j,c1,bin/hooks/reject-and-explain.py
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(share filesystem-path srv/inc as volume /inc,
readable by everyone, read-write for user 'ed',
running this plugin on all uploads with the params listed below)
example usage as a volflag in a copyparty config file:
[/inc]
srv/inc
accs:
r: *
rw: ed
flags:
xbu: j,c1,bin/hooks/reject-and-explain.py
parameters explained,
xbu = execute-before-upload (can also be xau, execute-after-upload)
j = this hook needs upload information as json (not just the filename)
c1 = this hook returns json on stdout, so tell copyparty to read that
"""
def main():
inf = json.loads(sys.argv[1])
vdir, fn = os.path.split(inf["vp"])
print("inf[vp] = %r" % (inf["vp"],), file=sys.stderr)
# the following is what decides if we'll accept the upload or reject it:
# we check if the upload-folder url matches the following regex-pattern:
ok = re.search(r"(^|/)day[0-9]+$", vdir, re.IGNORECASE)
if ok:
# allow the upload
print("{}")
return
# the upload was rejected; display the following errortext:
errmsg = "Files can only be uploaded into a folder named 'DayN' where N is a number, for example 'Day573'. This file was REJECTED: "
errmsg += inf["vp"] # if you want to mention the file's url
print(json.dumps({"rejectmsg": errmsg}))
if __name__ == "__main__":
main()

View file

@ -0,0 +1,72 @@
#!/usr/bin/env python3
import os
import threading
from argparse import Namespace
from jinja2.nodes import Name
from copyparty.fsutil import Fstab
from typing import Any, Optional
_ = r"""
reject an upload if the target folder is on a ramdisk; useful when you
have a volume where some folders inside are ramdisks but others aren't
example usage as global config:
--xbu I,bin/hooks/reject-ramdisk.py
example usage as a volflag (per-volume config):
-v srv/inc:inc:r:rw,ed:c,xbu=I,bin/hooks/reject-ramdisk.py
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(share filesystem-path srv/inc as volume /inc,
readable by everyone, read-write for user 'ed',
running this plugin on all uploads with the params listed below)
example usage as a volflag in a copyparty config file:
[/inc]
srv/inc
accs:
r: *
rw: ed
flags:
xbu: I,bin/hooks/reject-ramdisk.py
parameters explained,
I = import; do not fork / subprocess
IMPORTANT NOTE:
because this hook is imported inside copyparty, you need to
be EXCEPTIONALLY CAREFUL to avoid side-effects, for example
DO NOT os.chdir() or anything like that, and also make sure
that the name of this file is unique (cannot be the same as
an existing python module/library)
"""
mutex = threading.Lock()
fstab: Optional[Fstab] = None
def main(ka: dict[str, Any]) -> dict[str, Any]:
global fstab
with mutex:
log = ka["log"] # this is a copyparty NamedLogger function
if not fstab:
log("<HOOK:RAMDISK> creating fstab", 6)
args = Namespace()
args.mtab_age = 1 # cache the filesystem info for 1 sec
fstab = Fstab(log, args, False)
ap = ka["ap"] # abspath the upload is going to
fs, mp = fstab.get(ap) # figure out what the filesystem is
ramdisk = fs in ("tmpfs", "overlay") # looks like a ramdisk?
# log("<HOOK:RAMDISK> fs=%r" % (fs,))
if ramdisk:
t = "Upload REJECTED because destination is a ramdisk"
return {"rc": 1, "rejectmsg": t}
return {"rc": 0}

97
bin/hooks/wget-i.py Executable file
View file

@ -0,0 +1,97 @@
#!/usr/bin/env python3
import os
import threading
import subprocess as sp
_ = r"""
use copyparty as a file downloader by POSTing URLs as
application/x-www-form-urlencoded (for example using the
📟 message-to-server-log in the web-ui)
this hook is a modified copy of wget.py, modified to
make it import-safe so it can be run with the 'I' flag,
which speeds up the startup time of the hook by 140x
example usage as global config:
--xm aw,I,bin/hooks/wget-i.py
parameters explained,
xm = execute on message-to-server-log
aw = only users with write-access can use this
I = import; do not fork / subprocess
example usage as a volflag (per-volume config):
-v srv/inc:inc:r:rw,ed:c,xm=aw,I,bin/hooks/wget.py
^^^^^^^^^^^^^^^^^^^^^^^^^^^
(share filesystem-path srv/inc as volume /inc,
readable by everyone, read-write for user 'ed',
running this plugin on all messages with the params explained above)
example usage as a volflag in a copyparty config file:
[/inc]
srv/inc
accs:
r: *
rw: ed
flags:
xm: aw,I,bin/hooks/wget.py
the volflag examples only kicks in if you send the message
while you're in the /inc folder (or any folder below there)
IMPORTANT NOTE:
because this hook uses the 'I' flag to run inside copyparty,
many other flags will not work (f,j,c3,t3600 as seen in the
original wget.py), and furthermore + more importantly we
need to be EXCEPTIONALLY CAREFUL to avoid side-effects, so
the os.chdir has been replaced with cwd=dirpath for example
"""
def do_stuff(inf):
"""
worker function which is executed in another thread to
avoid blocking copyparty while the download is running,
since we cannot use the 'f,t3600' hook-flags with 'I'
"""
# first things first; grab the logger-function which copyparty is letting us borrow
log = inf["log"]
url = inf["txt"]
if "://" not in url:
url = "https://" + url
proto = url.split("://")[0].lower()
if proto not in ("http", "https", "ftp", "ftps"):
raise Exception("bad proto {}".format(proto))
dirpath = inf["ap"]
name = url.split("?")[0].split("/")[-1]
msg = "-- DOWNLOADING " + name
log(msg)
tfn = os.path.join(dirpath, msg)
open(tfn, "wb").close()
cmd = ["wget", "--trust-server-names", "-nv", "--", url]
try:
# two things to note here:
# - cannot use the `c3` hook-flag with `I` so mute output with stdout=sp.DEVNULL instead;
# - MUST NOT use os.chdir with 'I' so use cwd=dirpath instead
sp.check_call(cmd, cwd=dirpath, stdout=sp.DEVNULL)
except:
t = "-- FAILED TO DOWNLOAD " + name
log(t, 3) # 3=yellow=warning
open(os.path.join(dirpath, t), "wb").close()
raise # have copyparty scream about the details in the log
os.unlink(tfn)
def main(inf):
threading.Thread(target=do_stuff, args=(inf,), daemon=True).start()

View file

@ -6,8 +6,8 @@ __copyright__ = 2019
__license__ = "MIT"
__url__ = "https://github.com/9001/copyparty/"
S_VERSION = "2.0"
S_BUILD_DT = "2024-10-01"
S_VERSION = "2.1"
S_BUILD_DT = "2025-09-06"
"""
mount a copyparty server (local or remote) as a filesystem
@ -99,7 +99,7 @@ except:
elif MACOS:
libfuse = "install https://osxfuse.github.io/"
else:
libfuse = "apt install libfuse3-3\n modprobe fuse"
libfuse = "apt install libfuse2\n modprobe fuse"
m = """\033[33m
could not import fuse; these may help:
@ -359,7 +359,7 @@ class Gateway(object):
def sendreq(self, meth, path, headers, **kwargs):
tid = get_tid()
if self.password:
headers["Cookie"] = "=".join(["cppwd", self.password])
headers["PW"] = self.password
try:
c = self.getconn(tid)
@ -902,9 +902,7 @@ class CPPF(Operations):
return ret
def _readdir(self, path, fh=None):
path = path.strip("/")
dbg("readdir %r [%s]", path, fh)
dbg("dircache miss")
ret = self.gw.listdir(path)
if not self.n_dircache:
return ret
@ -914,11 +912,17 @@ class CPPF(Operations):
self.dircache.append(cn)
self.clean_dircache()
# import pprint; pprint.pprint(ret)
return ret
def readdir(self, path, fh=None):
return [".", ".."] + list(self._readdir(path, fh))
dbg("readdir %r [%s]", path, fh)
path = path.strip("/")
cn = self.get_cached_dir(path)
if cn:
ret = cn.data
else:
ret = self._readdir(path, fh)
return [".", ".."] + list(ret)
def read(self, path, length, offset, fh=None):
req_max = 1024 * 1024 * 8
@ -993,7 +997,6 @@ class CPPF(Operations):
if cn:
dents = cn.data
else:
dbg("cache miss")
dents = self._readdir(dirpath)
try:
@ -1141,10 +1144,15 @@ def main():
if WINDOWS:
examples.append("http://192.168.1.69:3923/music/ M:")
epi = "example:" + ex_pre + ex_pre.join(examples)
epi += """\n
NOTE: if server has --usernames enabled, then password is "username:password"
"""
ap = argparse.ArgumentParser(
formatter_class=TheArgparseFormatter,
description="mount a copyparty server as a local filesystem -- " + ver,
epilog="example:" + ex_pre + ex_pre.join(examples),
epilog=epi,
)
# fmt: off
ap.add_argument("base_url", type=str, help="remote copyparty URL to mount")

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python3
from __future__ import print_function, unicode_literals
S_VERSION = "2.12"
S_BUILD_DT = "2025-08-26"
S_VERSION = "2.13"
S_BUILD_DT = "2025-09-05"
"""
u2c.py: upload to copyparty
@ -590,9 +590,10 @@ def undns(url):
def _scd(err, top):
"""non-recursive listing of directory contents, along with stat() info"""
top_ = os.path.join(top, b"")
with os.scandir(top) as dh:
for fh in dh:
abspath = os.path.join(top, fh.name)
abspath = top_ + fh.name
try:
yield [abspath, fh.stat()]
except Exception as ex:
@ -601,8 +602,9 @@ def _scd(err, top):
def _lsd(err, top):
"""non-recursive listing of directory contents, along with stat() info"""
top_ = os.path.join(top, b"")
for name in os.listdir(top):
abspath = os.path.join(top, name)
abspath = top_ + name
try:
yield [abspath, os.stat(abspath)]
except Exception as ex:

View file

@ -7,6 +7,12 @@
* works on windows, linux and macos
* assumes `copyparty-sfx.py` was renamed to `copyparty.py` in the same folder as `copyparty.bat`
### [`setup-ashell.sh`](setup-ashell.sh)
* run copyparty on an iPhone/iPad using [a-Shell](https://holzschu.github.io/a-Shell_iOS/)
* not very useful due to limitations in iOS:
* not able to share all of your phone's storage
* cannot run in the background
### [`index.html`](index.html)
* drop-in redirect from an httpd to copyparty
* assumes the webserver and copyparty is running on the same server/IP

View file

@ -50,7 +50,9 @@ let
configStr = ''
${mkSection "global" cfg.settings}
${cfg.globalExtraConfig}
${mkSection "accounts" (accountsWithPlaceholders cfg.accounts)}
${mkSection "groups" cfg.groups}
${concatStringsSep "\n" (mapAttrsToList mkVolume cfg.volumes)}
'';
@ -131,6 +133,12 @@ in
'';
};
globalExtraConfig = mkOption {
type = types.str;
default = "";
description = "Appended to the end of the [global] section verbatim. This is useful for flags which are used in a repeating manner (e.g. ipu: 255.255.255.1=user) which can't be repeated in the settings = {} attribute set.";
};
accounts = mkOption {
type = types.attrsOf (
types.submodule (
@ -160,6 +168,19 @@ in
'';
};
groups = mkOption {
type = types.attrsOf (types.listOf types.str);
description = ''
A set of copyparty groups to create and the users that should be part of each group.
'';
default = { };
example = literalExpression ''
{
group_name = [ "user1" "user2" ];
};
'';
};
volumes = mkOption {
type = types.attrsOf (
types.submodule (
@ -349,12 +370,16 @@ in
) cfg.volumes
);
users.groups.copyparty = lib.mkIf (cfg.user == "copyparty" && cfg.group == "copyparty") { };
users.users.copyparty = lib.mkIf (cfg.user == "copyparty" && cfg.group == "copyparty") {
description = "Service user for copyparty";
group = "copyparty";
home = externalStateDir;
isSystemUser = true;
users.groups = lib.mkIf (cfg.group == "copyparty") {
copyparty = { };
};
users.users = lib.mkIf (cfg.user == "copyparty") {
copyparty = {
description = "Service user for copyparty";
group = cfg.group;
home = externalStateDir;
isSystemUser = true;
};
};
environment.systemPackages = lib.mkIf cfg.mkHashWrapper [
(pkgs.writeShellScriptBin "copyparty-hash" ''

View file

@ -3,7 +3,7 @@
# NOTE: You generally shouldn't use this PKGBUILD on Arch, as it is mainly for testing purposes. Install copyparty using pacman instead.
pkgname=copyparty
pkgver="1.19.5"
pkgver="1.19.16"
pkgrel=1
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
arch=("any")
@ -23,7 +23,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}/copyparty.conf" )
sha256sums=("366e0e73d0e322e1dba9315baa328a6cd488f272cb431b9187da326361c2d7da")
sha256sums=("d8cc10d3623eaccc8acaebdd3b0102a1ebf878a03ffe6e737d132c66f799d682")
build() {
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"

View file

@ -2,7 +2,7 @@
pkgname=copyparty
pkgver=1.19.5
pkgver=1.19.16
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=("366e0e73d0e322e1dba9315baa328a6cd488f272cb431b9187da326361c2d7da")
sha256sums=("d8cc10d3623eaccc8acaebdd3b0102a1ebf878a03ffe6e737d132c66f799d682")
build() {
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"

View file

@ -26,7 +26,7 @@ Environment=XDG_CONFIG_HOME=/home/cpp/.config
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
ExecStart=/usr/bin/python3 /usr/local/bin/copyparty -c /etc/copyparty.d/init
[Install]
WantedBy=multi-user.target

View file

@ -1,5 +1,5 @@
{
"url": "https://github.com/9001/copyparty/releases/download/v1.19.5/copyparty-1.19.5.tar.gz",
"version": "1.19.5",
"hash": "sha256-Nm4Oc9DjIuHbqTFbqjKKbNSI8nLLQxuRh9oyY2HC19o="
"url": "https://github.com/9001/copyparty/releases/download/v1.19.16/copyparty-1.19.16.tar.gz",
"version": "1.19.16",
"hash": "sha256-2MwQ02I+rMyKyuvdOwECoev4eKA//m5zfRMsZveZ1oI="
}

View file

@ -101,10 +101,10 @@
function our_hotkey_handler(e) {
// bail if either ALT, CTRL, or SHIFT is pressed
if (e.altKey || e.shiftKey || e.isComposing || ctrl(e))
if (anymod(e))
return main_hotkey_handler(e); // let copyparty handle this keystroke
var key_name = (e.code || e.key) + '',
var keycode = (e.key || e.code) + '',
ae = document.activeElement,
aet = ae && ae != document.body ? ae.nodeName.toLowerCase() : '';
@ -114,7 +114,7 @@
if (aet && !/^(a|button|tr|td|div|pre)$/.test(aet))
return main_hotkey_handler(e); // let copyparty handle this keystroke
if (key_name == 'KeyW') {
if (keycode == 'w' || keycode == 'KeyW') {
// okay, this one's for us... do the thing
action_to_perform();
return ev(e);

View file

@ -0,0 +1,173 @@
# copyparty with Podman and Systemd
Use this configuration if you want to run copyparty in a Podman container, with the reliability of running the container under a systemd service.
Documentation for `.container` files can be found in the [Container unit](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html#container-units-container) docs. Systemd does not understand `.container` files natively, so Podman converts these to `.service` files with a [systemd-generator](https://www.freedesktop.org/software/systemd/man/latest/systemd.generator.html). This process is transparent, but sometimes needs to be debugged in case your `.container` file is malformed. There are instructions to debug the systemd generator in the Troubleshooting section below.
To run copyparty in this way, you must already have podman installed. To install Podman, see: https://podman.io/docs/installation
There is a sample configuration file in the same directory as this file (`copyparty.conf`).
## Run the container as root
Running the container as the root user is easy to set up, but less secure. There are instructions in the next section to run the container as a rootless user if you'd rather run the container like that.
First, change this line in the `copyparty.container` file to reflect the directory you want to share. By default, it shares `/mnt/` but you'll probably want to change that.
```
# Change /mnt to something you want to share
Volume=/mnt:/w:z
```
Note that you can select the owner and group of this volume by changing the `uid:` and `gid:` of the volume in `copyparty.conf`, but for simplicity let's assume you want it to be owned by `root:root`.
To install and start copyparty with Podman and systemd as the root user, run the following:
```shell
sudo mkdir -pv /etc/systemd/container/ /etc/copyparty/
sudo cp -v copyparty.container /etc/systemd/containers/
sudo cp -v copyparty.conf /etc/copyparty/
sudo systemctl daemon-reload
sudo systemctl start copyparty
```
Note: You can't "enable" this kind of Podman service. The `[Install]` section of the `.container` file effectively handles enabling the service so that it starts when the server reboots.
You can see the status of the service with:
```shell
sudo systemctl status -a copyparty
```
You can see (and follow) the logs with either of these commands:
```shell
sudo podman logs -f copyparty
# -a is required or else you'll get output like: copyparty[549025]: [649B blob data]
sudo journalctl -a -f -u copyparty
```
## Run the container as a non-root user
This configuration is more secure, but is more involved and requires ensuring files have proper permissions. You will need a root user account to do some of this setup.
First, you need a user to run the container as. In this example we'll create a "podman" user with UID=1001 and GID=1001.
```shell
sudo groupadd -g 1001 podman
sudo useradd -u 1001 -m podman
sudo usermod -aG podman podman
sudo loginctl enable-linger podman
# Set a strong password for this user
sudo -u podman passwd
```
The `enable-linger` command allows the podman user to run systemd user services that persist even when the user is not logged in. You could use a user that already exists in the system to run this service as, just make sure to run `loginctl enable-linger USERNAME` for that user.
Next, change these lines in the `copyparty.container` file to reflect the config directory and the directory you want to share. By default, the config shares `/home/podman/copyparty/sharing/` but you'll probably want to change this:
```
# Change to reflect your non-root user's home directory
Volume=/home/podman/copyparty/config:/cfg:z
# Change to the directory you want to share
Volume=/home/podman/copyparty/sharing:/w:z
```
Make sure the podman user has read/write access to both of these directories.
Next, **log in to the server as the podman user**.
To install and start copyparty as the non-root podman user, run the following:
```shell
mkdir -pv /home/podman/.config/containers/systemd/ /home/podman/copyparty/config
cp -v copyparty.container /home/podman/.config/containers/systemd/copyparty.container
cp -v copyparty.conf /home/podman/copyparty/config
systemctl --user daemon-reload
systemctl --user start copyparty
```
**Important note: Never use `sudo` with `systemctl --user`!**
You can check the status of the user service with:
```shell
systemctl --user status -a copyparty
```
You can see (and follow) the logs with:
```shell
podman logs -f copyparty
journalctl --user -a -f -u copyparty
```
## Troubleshooting
If the container fails to start, and you've modified the `.container` service, it's likely that your `.container` file failed to be translated into a `.service` file. You can debug the podman service generator with this command:
```shell
sudo /usr/lib/systemd/system-generators/podman-system-generator --dryrun
```
## Allowing Traffic from Outside your Server
To allow traffic on port 3923 of your server, you should run:
```shell
sudo firewall-cmd --permanent --add-port=3923/tcp
sudo firewall-cmd --reload
```
Otherwise, you won't be able to access the copyparty server from anywhere other than the server itself.
## Updating copyparty
To update the version of copyparty used in the container, you can:
```shell
# If root:
sudo podman pull docker.io/copyparty/ac:latest
sudo systemctl restart copyparty
# If non-root:
podman pull docker.io/copyparty/ac:latest
systemctl --user restart copyparty
```
Or, you can change the pinned version of the image in the `[Container]` section of the `.container` file and run:
```shell
# If root:
sudo systemctl daemon-reload
sudo systemctl restart copyparty
# If non-root:
systemctl --user daemon-reload
systemctl --user restart copyparty
```
Podman will pull the image you've specified when restarting. If you have it set to `:latest`, Podman does not know to re-pull the container.
### Enabling auto-update
Alternatively, you can enable auto-updates by un-commenting this line:
```
# AutoUpdate=registry
```
You will also need to enable the [podman auto-updater service](https://docs.podman.io/en/latest/markdown/podman-auto-update.1.html) with:
```shell
# If root:
sudo systemctl enable podman-auto-update.timer podman-auto-update.service
# If non-root:
systemctl --user enable podman-auto-update.timer podman-auto-update.service
```
This works best if you always want the latest version of copyparty. The auto-updater runs once every 24 hours.

View file

@ -0,0 +1,36 @@
[global]
e2dsa # enable file indexing and filesystem scanning
e2ts # and enable multimedia indexing
ansi # and colors in log messages
# uncomment the line starting with q, lo: to log to a file instead of stdout/journalctl;
# $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
/w # share the contents of the "/w" folder
accs:
rw: * # everyone gets read-write access, but
rwmda: ed # the user "ed" gets read-write-move-delete-admin
# uid: 1000 # If you're running as root, you can change the owner of this volume here
# gid: 1000 # If you're running as root, you can change the group of this volume here

View file

@ -0,0 +1,55 @@
[Container]
# It's recommended to replace :latest with a specific version
# for example: docker.io/copyparty/ac:1.19.15
Image=docker.io/copyparty/ac:latest
ContainerName=copyparty
# Uncomment to enable auto-updates
# AutoUpdate=registry
# Environment variables
# enable mimalloc by replacing "NOPE" with "2" for a nice speed-boost (will use twice as much ram)
Environment=LD_PRELOAD=/usr/lib/libmimalloc-secure.so.NOPE
# ensures log-messages are not delayed (but can reduce speed a tiny bit)
Environment=PYTHONUNBUFFERED=1
# Ports
PublishPort=3923:3923
# Volumes (PLEASE LOOK!)
# Rootful setup:
# Leave as-is
# Non-root setup:
# Change /etc/copyparty to /home/<USER>/copyparty/config
Volume=/etc/copyparty:/cfg:z
# Rootful setup:
# Change /mnt to the directory you want to share
# Non-root setup:
# Change /mnt to something owned by your user, e.g., /home/<USER>/copyparty/sharing:/w:z
Volume=/mnt:/w:z
# Give the container time to stop in case the thumbnailer is still running.
# It's allowed to continue finishing up for 10s after the shutdown signal, give it a 5s buffer
StopTimeout=15
# hide it from logs with "/._" so it matches the default --lf-url filter
HealthCmd="wget --spider -q 127.0.0.1:3923/?reset=/._"
HealthInterval=1m
HealthTimeout=2s
HealthRetries=5
HealthStartPeriod=15s
[Unit]
After=default.target
[Install]
# Start by default on boot
WantedBy=default.target
[Service]
# Give the container time to start in case it needs to pull the image
TimeoutStartSec=600

View file

@ -6,7 +6,7 @@
# https://apps.apple.com/us/app/a-shell/id1473805438
#
# step 2: copypaste the following command into a-Shell:
# curl https://github.com/9001/copyparty/raw/refs/heads/hovudstraum/contrib/setup-ashell.sh
# curl -L https://github.com/9001/copyparty/raw/refs/heads/hovudstraum/contrib/setup-ashell.sh
#
# step 3: launch copyparty with this command: cpp
#

View file

@ -88,6 +88,7 @@ web/mde.html
web/mde.js
web/msg.css
web/msg.html
web/opds.xml
web/rups.css
web/rups.html
web/rups.js
@ -111,7 +112,9 @@ class EnvParams(object):
def __init__(self) -> None:
self.t0 = time.time()
self.mod = ""
self.mod_ = ""
self.cfg = ""
self.scfg = True
E = EnvParams()

View file

@ -36,6 +36,7 @@ from .__init__ import (
)
from .__version__ import CODENAME, S_BUILD_DT, S_VERSION
from .authsrv import expand_config_file, split_cfg_ln, upgrade_cfg_fmt
from .bos import bos
from .cfg import flagcats, onedash
from .svchub import SvcHub
from .util import (
@ -186,7 +187,7 @@ def init_E(EE: EnvParams) -> None:
E = EE # pylint: disable=redefined-outer-name
def get_unixdir() -> str:
def get_unixdir() -> tuple[str, bool]:
paths: list[tuple[Callable[..., Any], str]] = [
(os.environ.get, "XDG_CONFIG_HOME"),
(os.path.expanduser, "~/.config"),
@ -197,6 +198,8 @@ def init_E(EE: EnvParams) -> None:
]
errs = []
for npath, (pf, pa) in enumerate(paths):
priv = npath < 2 # private/trusted location
ram = npath > 1 # "nonvolatile"; not semantically same as `not priv`
p = ""
try:
p = pf(pa)
@ -204,17 +207,23 @@ def init_E(EE: EnvParams) -> None:
continue
p = os.path.normpath(p)
if os.path.isdir(p) and os.listdir(p):
mkdir = False
else:
mkdir = True
os.mkdir(p)
mkdir = not os.path.isdir(p)
if mkdir:
os.mkdir(p, 0o700)
p = os.path.join(p, "copyparty")
if not os.path.isdir(p):
os.mkdir(p)
if not priv and os.path.isdir(p):
uid = os.geteuid()
if os.stat(p).st_uid != uid:
p += ".%s" % (uid,)
if os.path.isdir(p) and os.stat(p).st_uid != uid:
raise Exception("filesystem has broken unix permissions")
try:
os.listdir(p)
except:
os.mkdir(p, 0o700)
if npath > 1:
if ram:
t = "Using %s/copyparty [%s] for config; filekeys/dirkeys will change on every restart. Consider setting XDG_CONFIG_HOME or giving the unix-user a ~/.config/"
errs.append(t % (pa, p))
elif mkdir:
@ -226,17 +235,19 @@ def init_E(EE: EnvParams) -> None:
if errs:
warn(". ".join(errs))
return p # type: ignore
return p, priv
except Exception as ex:
if p and npath < 2:
if p:
t = "Unable to store config in %s [%s] due to %r"
errs.append(t % (pa, p, ex))
raise Exception("could not find a writable path for config")
t = "could not find a writable path for runtime state:\n> %s"
raise Exception(t % ("\n> ".join(errs)))
E.mod = os.path.dirname(os.path.realpath(__file__))
if E.mod.endswith("__init__"):
E.mod = os.path.dirname(E.mod)
E.mod_ = os.path.join(E.mod, "")
try:
p = os.environ.get("XDG_CONFIG_HOME")
@ -247,7 +258,7 @@ def init_E(EE: EnvParams) -> None:
p = os.path.abspath(os.path.realpath(p))
p = os.path.join(p, "copyparty")
if not os.path.isdir(p):
os.mkdir(p)
os.mkdir(p, 0o700)
os.listdir(p)
except:
p = ""
@ -260,11 +271,11 @@ def init_E(EE: EnvParams) -> None:
elif sys.platform == "darwin":
E.cfg = os.path.expanduser("~/Library/Preferences/copyparty")
else:
E.cfg = get_unixdir()
E.cfg, E.scfg = get_unixdir()
E.cfg = E.cfg.replace("\\", "/")
try:
os.makedirs(E.cfg)
bos.makedirs(E.cfg, bos.MKD_700)
except:
if not os.path.isdir(E.cfg):
raise
@ -802,9 +813,11 @@ def get_sects():
\033[36mf\033[35m forks the process, doesn't wait for completion
\033[36mc\033[35m checks return code, blocks the action if non-zero
\033[36mj\033[35m provides json with info as 1st arg instead of filepath
\033[36ms\033[35m provides input data on stdin (instead of 1st arg)
\033[36mwN\033[35m waits N sec after command has been started before continuing
\033[36mtN\033[35m sets an N sec timeout before the command is abandoned
\033[36miN\033[35m xiu only: volume must be idle for N sec (default = 5)
\033[36mI\033[35m import and run as module, not as subprocess
\033[36mar\033[35m only run hook if user has read-access
\033[36marw\033[35m only run hook if user has read-write-access
@ -834,6 +847,9 @@ def get_sects():
the \033[33m--\033[35m stops notify-send from reading the message as args
and the alert will be "hey" followed by the messagetext
\033[36m--xm s,,tee,-a,log.txt\033[35m appends each msg to log.txt;
\033[36m--xm s,j,,tee,-a,log.txt\033[35m writes it as json instead
\033[36m--xau zmq:pub:tcp://*:5556\033[35m announces uploads on zeromq;
\033[36m--xau t3,zmq:push:tcp://*:5557\033[35m also works, and you can
\033[36m--xau t3,j,zmq:req:tcp://localhost:5555\033[35m too for example
@ -843,7 +859,8 @@ def get_sects():
as soon as the volume has been idle for iN seconds (5 by default)
\033[36mxiu\033[0m is also unique in that it will pass the metadata to the
executed program on STDIN instead of as argv arguments, and
executed program on STDIN instead of as argv arguments (so
just like the \033[36ms\033[0m option does for the other hook types), and
it also includes the wark (file-id/hash) as a json property
\033[36mxban\033[0m can be used to overrule / cancel a user ban event;
@ -854,6 +871,12 @@ def get_sects():
on new uploads, but with certain limitations. See
bin/hooks/reloc* and docs/devnotes.md#hook-effects
the \033[36mI\033[0m option will override most other options, because
it entirely hands over control to the hook, which is
then able to tamper with copyparty's internal memory
and wreck havoc if it wants to -- but this is worh it
because it makes the hook 140x faster
except for \033[36mxm\033[0m, only one hook / one action can run at a time,
so it's recommended to use the \033[36mf\033[0m flag unless you really need
to wait for the hook to finish before continuing (without \033[36mf\033[0m
@ -887,6 +910,11 @@ def get_sects():
middleware and not by clients! and, as an extra precaution,
send a header named '\033[36mfinalmasterspark\033[0m' (a secret keyword)
and then \033[36m--idp-h-key finalmasterspark\033[0m to require that
the login/logout links/buttons can be replaced with links
going to your IdP's UI; \033[36m--idp-login /login/?redir={dst}\033[0m
will expand \033[36m{dst}\033[0m to the URL of the current page, so
the IdP can redirect the user back to where they were
"""
),
],
@ -940,7 +968,7 @@ def get_sects():
\033[36m{{vf.thsize}} \033[35mthumbnail size
\033[36m{{srv.itime}} \033[35mserver time in seconds
\033[36m{{srv.htime}} \033[35mserver time as YY-mm-dd, HH:MM:SS (UTC)
\033[36m{{hdr.cf_ipcountry}} \033[35mthe "CF-IPCountry" client header (probably blank)
\033[36m{{hdr.cf-ipcountry}} \033[35mthe "CF-IPCountry" client header (probably blank)
\033[0m
so the following types of placeholders can be added to the lists:
* any client header can be accessed through {{hdr.*}}
@ -1134,10 +1162,13 @@ def add_general(ap, nc, srvname):
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("--chdir", metavar="PATH", type=u, help="change working-directory to \033[33mPATH\033[0m before mapping volumes")
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("--name-url", metavar="TXT", type=u, help="URL for server name hyperlink (displayed topleft in browser)")
ap2.add_argument("--name-html", type=u, help=argparse.SUPPRESS)
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)")
@ -1160,11 +1191,14 @@ def add_qr(ap, tty):
ap2.add_argument("--qr-every", metavar="SEC", type=float, default=0, help="print the qr-code every \033[33mSEC\033[0m (try this with/without --qr-pin in case of issues)")
ap2.add_argument("--qr-winch", metavar="SEC", type=float, default=0, help="when --qr-pin is enabled, check for terminal size change every \033[33mSEC\033[0m")
ap2.add_argument("--qr-file", metavar="TXT", type=u, action="append", help="\033[34mREPEATABLE:\033[0m write qr-code to file.\n └─To create txt or svg, \033[33mTXT\033[0m is Filepath:Zoom:Pad, for example [\033[32mqr.txt:1:2\033[0m]\n └─To create png or gif, \033[33mTXT\033[0m is Filepath:Zoom:Pad:Foreground:Background, for example [\033[32mqr.png:8:2:333333:ffcc55\033[0m], or [\033[32mqr.png:8:2::ffcc55\033[0m] for transparent")
ap2.add_argument("--qr-stdout", action="store_true", help="always display the QR-code on STDOUT in the terminal, even if \033[33m-q\033[0m")
ap2.add_argument("--qr-stderr", action="store_true", help="always display the QR-code on STDERR in the terminal, even if \033[33m-q\033[0m")
def add_fs(ap):
ap2 = ap.add_argument_group("filesystem options")
rm_re_def = "15/0.1" if ANYWIN else "0/0"
ap2.add_argument("--casechk", metavar="N", type=u, default="auto", help="detect and prevent CI (case-insensitive) behavior if the underlying filesystem is CI? [\033[32my\033[0m] = detect and prevent, [\033[32mn\033[0m] = ignore and allow, [\033[32mauto\033[0m] = \033[32my\033[0m if CI fs detected. NOTE: \033[32my\033[0m is very slow but necessary for correct WebDAV behavior on Windows/Macos (volflag=casechk)")
ap2.add_argument("--rm-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be deleted because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=rm_retry)")
ap2.add_argument("--mv-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be renamed because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=mv_retry)")
ap2.add_argument("--iobuf", metavar="BYTES", type=int, default=256*1024, help="file I/O buffer-size; if your volumes are on a network drive, try increasing to \033[32m524288\033[0m or even \033[32m4194304\033[0m (and let me know if that improves your performance)")
@ -1176,6 +1210,7 @@ def add_share(ap):
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-who", metavar="TXT", type=u, default="auth", help="who can create a share? [\033[32mno\033[0m]=nobody, [\033[32ma\033[0m]=admin-permission, [\033[32mauth\033[0m]=authenticated (volflag=shr_who)")
ap2.add_argument("--shr-adm", metavar="U,U", type=u, default="", help="comma-separated list of users allowed to view/delete any share")
ap2.add_argument("--shr-rt", metavar="MIN", type=int, default=1440, help="shares can be revived by their owner if they expired less than MIN minutes ago; [\033[32m60\033[0m]=hour, [\033[32m1440\033[0m]=day, [\033[32m10080\033[0m]=week")
ap2.add_argument("--shr-v", action="store_true", help="debug")
@ -1199,6 +1234,7 @@ def add_upload(ap):
ap2.add_argument("--chmod-d", metavar="UGO", type=u, default="755", help="unix file permissions to use when creating directories; see --help-chmod. Examples: [\033[32m755\033[0m] = owner-RW + all-R, [\033[32m777\033[0m] = full-yolo (volflag=chmod_d)")
ap2.add_argument("--uid", metavar="N", type=int, default=-1, help="unix user-id to chown new files/folders to; default = -1 = do-not-change (volflag=uid)")
ap2.add_argument("--gid", metavar="N", type=int, default=-1, help="unix group-id to chown new files/folders to; default = -1 = do-not-change (volflag=gid)")
ap2.add_argument("--wram", action="store_true", help="allow uploading even if a volume is inside a ramdisk, meaning that all data will be lost on the next server reboot (volflag=wram)")
ap2.add_argument("--dedup", action="store_true", help="enable symlink-based upload deduplication (volflag=dedup)")
ap2.add_argument("--safe-dedup", metavar="N", type=int, default=50, help="how careful to be when deduplicating files; [\033[32m1\033[0m] = just verify the filesize, [\033[32m50\033[0m] = verify file contents have not been altered (volflag=safededup)")
ap2.add_argument("--hardlink", action="store_true", help="enable hardlink-based dedup; will fallback on symlinks when that is impossible (across filesystems) (volflag=hardlink)")
@ -1209,7 +1245,9 @@ def add_upload(ap):
ap2.add_argument("--no-snap", action="store_true", help="disable snapshots -- forget unfinished uploads on shutdown; don't create .hist/up2k.snap files -- abandoned/interrupted uploads must be cleaned up manually")
ap2.add_argument("--snap-wri", metavar="SEC", type=int, default=300, help="write upload state to ./hist/up2k.snap every \033[33mSEC\033[0m seconds; allows resuming incomplete uploads after a server crash")
ap2.add_argument("--snap-drop", metavar="MIN", type=float, default=1440.0, help="forget unfinished uploads after \033[33mMIN\033[0m minutes; impossible to resume them after that (360=6h, 1440=24h)")
ap2.add_argument("--rm-partial", action="store_true", help="delete the .PARTIAL file when an unfinished upload expires after \033[33m--snap-drop\033[0m (volflag=rm_partial)")
ap2.add_argument("--u2ts", metavar="TXT", type=u, default="c", help="how to timestamp uploaded files; [\033[32mc\033[0m]=client-last-modified, [\033[32mu\033[0m]=upload-time, [\033[32mfc\033[0m]=force-c, [\033[32mfu\033[0m]=force-u (volflag=u2ts)")
ap2.add_argument("--rotf-tz", metavar="TXT", type=u, default="UTC", help="default timezone for the rotf upload rule; examples: [\033[32mEurope/Oslo\033[0m], [\033[32mAmerica/Toronto\033[0m], [\033[32mAntarctica/South_Pole\033[0m] (volflag=rotf_tz)")
ap2.add_argument("--rand", action="store_true", help="force randomized filenames, \033[33m--nrand\033[0m chars long (volflag=rand)")
ap2.add_argument("--nrand", metavar="NUM", type=int, default=9, help="randomized filenames length (volflag=nrand)")
ap2.add_argument("--magic", action="store_true", help="enable filetype detection on nameless uploads (volflag=magic)")
@ -1234,6 +1272,7 @@ def add_network(ap):
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]")
ap2.add_argument("--http-no-tcp", action="store_true", help="do not listen on TCP/IP for http/https; only listen on unix-domain-sockets")
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")
elif not MACOS:
@ -1292,6 +1331,9 @@ def add_auth(ap):
ap2.add_argument("--idp-store", metavar="N", type=int, default=1, help="how to use \033[33m--idp-db\033[0m; [\033[32m0\033[0m] = entirely disable, [\033[32m1\033[0m] = write-only (effectively disabled), [\033[32m2\033[0m] = remember users, [\033[32m3\033[0m] = remember users and groups.\nNOTE: Will remember and restore the IdP-volumes of all users for all eternity if set to 2 or 3, even when user is deleted from your IdP")
ap2.add_argument("--idp-adm", metavar="U,U", type=u, default="", help="comma-separated list of users allowed to use /?idp (the cache management UI)")
ap2.add_argument("--idp-cookie", metavar="S", type=int, default=0, help="generate a session-token for IdP users which is written to cookie \033[33mcppws\033[0m (or \033[33mcppwd\033[0m if plaintext), to reduce the load on the IdP server, lifetime \033[33mS\033[0m seconds.\n └─note: The expiration time is a client hint only; the actual lifetime of the session-token is infinite (until next restart with \033[33m--ses-db\033[0m wiped)")
ap2.add_argument("--idp-login", metavar="L", type=u, default="", help="replace all login-buttons with a link to URL \033[33mL\033[0m (unless \033[32mpw\033[0m is in \033[33m--auth-ord\033[0m then both will be shown); [\033[32m{dst}\033[0m] expands to url of current page")
ap2.add_argument("--idp-login-t", metavar="T", type=u, default="Login with SSO", help="the label/text for the idp-login button")
ap2.add_argument("--idp-logout", metavar="L", type=u, default="", help="replace all logout-buttons with a link to URL \033[33mL\033[0m")
ap2.add_argument("--auth-ord", metavar="TXT", type=u, default="idp,ipu", help="controls auth precedence; examples: [\033[32mpw,idp,ipu\033[0m], [\033[32mipu,pw,idp\033[0m], see --help-auth-ord")
ap2.add_argument("--no-bauth", action="store_true", help="disable basic-authentication support; do not accept passwords from the 'Authenticate' header at all. NOTE: This breaks support for the android app")
ap2.add_argument("--bauth-last", action="store_true", help="keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins")
@ -1306,7 +1348,7 @@ def add_auth(ap):
ap2.add_argument("--ao-idp-before-pw", type=u, default="", help=argparse.SUPPRESS)
ap2.add_argument("--ao-h-before-hm", type=u, default="", help=argparse.SUPPRESS)
ap2.add_argument("--ao-ipu-wins", type=u, default="", help=argparse.SUPPRESS)
ap2.add_argument("--ao-has-pw", type=u, default="", help=argparse.SUPPRESS)
ap2.add_argument("--ao-have-pw", type=u, default="", help=argparse.SUPPRESS)
def add_chpw(ap):
@ -1338,6 +1380,8 @@ def add_zc_mdns(ap):
ap2.add_argument("--zm6", action="store_true", help="IPv6 only")
ap2.add_argument("--zmv", action="store_true", help="verbose mdns")
ap2.add_argument("--zmvv", action="store_true", help="verboser mdns")
ap2.add_argument("--zm-http", metavar="PORT", type=int, default=-1, help="port to announce for http/webdav; [\033[32m-1\033[0m] = auto, [\033[32m0\033[0m] = disabled, [\033[32m4649\033[0m] = port 4649")
ap2.add_argument("--zm-https", metavar="PORT", type=int, default=-1, help="port to announce for https/webdavs; [\033[32m-1\033[0m] = auto, [\033[32m0\033[0m] = disabled, [\033[32m4649\033[0m] = port 4649")
ap2.add_argument("--zm-no-pe", action="store_true", help="mute parser errors (invalid incoming MDNS packets)")
ap2.add_argument("--zm-nwa-1", action="store_true", help="disable workaround for avahi-bug #379 (corruption in Avahi's mDNS reflection feature)")
ap2.add_argument("--zms", metavar="dhf", type=u, default="", help="list of services to announce -- d=webdav h=http f=ftp s=smb -- lowercase=plaintext uppercase=TLS -- default: all enabled services except http/https (\033[32mDdfs\033[0m if \033[33m--ftp\033[0m and \033[33m--smb\033[0m is set, \033[32mDd\033[0m otherwise)")
@ -1411,6 +1455,10 @@ def add_smb(ap):
ap2.add_argument("--smbvv", action="store_true", help="verboser")
ap2.add_argument("--smbvvv", action="store_true", help="verbosest")
def add_opds(ap):
ap2 = ap.add_argument_group("OPDS options")
ap2.add_argument("--opds", action="store_true", help="enable opds -- allows e-book readers to browse and download files (volflag=opds)")
ap2.add_argument("--opds-exts", metavar="T,T", type=u, default="epub,cbz,pdf", help="file formats to list in OPDS feeds; leave empty to show everything (volflag=opds_exts)")
def add_handlers(ap):
ap2 = ap.add_argument_group("handlers (see --help-handlers)")
@ -1452,6 +1500,7 @@ def add_yolo(ap):
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)")
ap2.add_argument("--unsafe-state", action="store_true", help="when one of the emergency fallback locations are used for runtime state ($TMPDIR, /tmp), certain features will be force-disabled for security reasons by default. This option overrides that safeguard and allows unsafe storage of secrets")
def add_optouts(ap):
@ -1465,7 +1514,7 @@ def add_optouts(ap):
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")
ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI. This is the same as --du-who no")
ap2.add_argument("-nb", action="store_true", help="no powered-by-copyparty branding in UI")
ap2.add_argument("--zipmaxn", metavar="N", type=u, default="0", help="reject download-as-zip if more than \033[33mN\033[0m files in total; optionally takes a unit suffix: [\033[32m256\033[0m], [\033[32m9K\033[0m], [\033[32m4G\033[0m] (volflag=zipmaxn)")
ap2.add_argument("--zipmaxs", metavar="SZ", type=u, default="0", help="reject download-as-zip if total download size exceeds \033[33mSZ\033[0m bytes; optionally takes a unit suffix: [\033[32m256M\033[0m], [\033[32m4G\033[0m], [\033[32m2T\033[0m] (volflag=zipmaxs)")
@ -1484,7 +1533,7 @@ def add_optouts(ap):
def add_safety(ap):
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 requires login, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --hardlink --dav-auth --vague-403 -nih")
ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav requires login, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --reflink --dav-auth --vague-403 -nih")
ap2.add_argument("-sss", action="store_true", help="further increase safety: Enable logging to disk, scan for dangerous symlinks.\n └─Alias of\033[32m -ss --no-dav --no-logues --no-readme -lo=cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz --ls=**,*,ln,p,r")
ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, default="", help="do a sanity/safety check of all volumes on startup; arguments \033[33mUSER\033[0m,\033[33mVOL\033[0m,\033[33mFLAGS\033[0m (see \033[33m--help-ls\033[0m); example [\033[32m**,*,ln,p,r\033[0m]")
ap2.add_argument("--xvol", action="store_true", help="never follow symlinks leaving the volume root, unless the link is into another volume where the user has similar access (volflag=xvol)")
@ -1504,7 +1553,7 @@ def add_safety(ap):
ap2.add_argument("--ban-422", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m 422's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes (invalid requests, attempted exploits ++)")
ap2.add_argument("--ban-url", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m sus URL's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; applies only to permissions g/G/h (decent replacement for \033[33m--ban-404\033[0m if that can't be used)")
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("--nonsus-urls", metavar="R", type=u, default=r"^(favicon\..{3}|robots\.txt)$|^apple-touch-icon|^\.well-known", help="harmless URLs ignored from 403/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")
@ -1552,6 +1601,9 @@ def add_logging(ap):
ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="print request \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all")
ap2.add_argument("--ohead", metavar="HEADER", type=u, action='append', help="print response \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all")
ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|[?&]th=[wjp]|/\.(_|ql_|DS_Store$|localized$)", help="dont log URLs matching regex \033[33mRE\033[0m")
ap2.add_argument("--scan-st-r", metavar="SEC", type=float, default=0.1, help="fs-indexing: wait \033[33mSEC\033[0m between each status-message")
ap2.add_argument("--scan-pr-r", metavar="SEC", type=float, default=10, help="fs-indexing: wait \033[33mSEC\033[0m between each 'progress:' message")
ap2.add_argument("--scan-pr-s", metavar="MiB", type=float, default=1, help="fs-indexing: say 'file: <name>' when a file larger than \033[33mMiB\033[0m is about to be hashed")
def add_admin(ap):
@ -1659,6 +1711,7 @@ def add_db_general(ap, hcores):
ap2.add_argument("--hash-mt", metavar="CORES", type=int, default=hcores, help="num cpu cores to use for file hashing; set 0 or 1 for single-core hashing")
ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="rescan filesystem for changes every \033[33mSEC\033[0m seconds; 0=off (volflag=scan)")
ap2.add_argument("--db-act", metavar="SEC", type=float, default=10.0, help="defer any scheduled volume reindexing until \033[33mSEC\033[0m seconds after last db write (uploads, renames, ...)")
ap2.add_argument("--srch-icase", action="store_true", help="case-insensitive search for all unicode characters (the default is icase for just ascii). NOTE: will make searches much slower (around 4x), and NOTE: only applies to filenames/paths, not tags")
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=45, help="search deadline -- terminate searches running for more than \033[33mSEC\033[0m seconds")
ap2.add_argument("--srch-hits", metavar="N", type=int, default=7999, help="max search results to allow clients to fetch; 125 results will be shown initially")
ap2.add_argument("--srch-excl", metavar="PTN", type=u, default="", help="regex: exclude files from search results if the file-URL matches \033[33mPTN\033[0m (case-sensitive). Example: [\033[32mpassword|logs/[0-9]\033[0m] any URL containing 'password' or 'logs/DIGIT' (volflag=srch_excl)")
@ -1713,12 +1766,13 @@ def add_og(ap):
ap2.add_argument("--uqe", action="store_true", help="query-string parceling; translate a request for \033[33m/foo/.uqe/BASE64\033[0m into \033[33m/foo?TEXT\033[0m, or \033[33m/foo/?TEXT\033[0m if the first character in \033[33mTEXT\033[0m is a slash. Automatically enabled for \033[33m--og\033[0m")
def add_ui(ap, retry):
def add_ui(ap, retry: int):
THEMES = 10
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("--ui-filesz", metavar="FMT", type=u, default="1", help="default filesize format; one of these: 0, 1, 2, 2c, 3, 3c, 4, 4c, 5, 5c, fuzzy (see UI)")
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..%d)" % (THEMES - 1,))
ap2.add_argument("--themes", metavar="NUM", type=int, default=THEMES, help="number of themes installed")
@ -1730,6 +1784,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("--ufavico", metavar="TXT", type=u, default="", help="URL to .ico/png/gif/svg file; \033[33m--favico\033[0m takes precedence unless disabled (volflag=ufavico)")
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]")
@ -1737,13 +1792,18 @@ def add_ui(ap, retry):
ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include in the filebrowser html")
ap2.add_argument("--js-other", metavar="L", type=u, default="", help="URL to additional JS to include in all other pages")
ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages (except for basic-browser); can be @PATH to send the contents of a file at PATH, and/or begin with %% to render as jinja2 template (volflag=html_head)")
ap2.add_argument("--html-head-s", metavar="T", type=u, default="", help="text to append to the <head> of all HTML pages (except for basic-browser); similar to (and can be combined with) --html-head but only accepts static text (volflag=html_head_s)")
ap2.add_argument("--ih", action="store_true", help="if a folder contains index.html, show that instead of the directory listing by default (can be changed in the client settings UI, or add ?v to URL for override)")
ap2.add_argument("--textfiles", metavar="CSV", type=u, default="txt,nfo,diz,cue,readme", help="file extensions to present as plaintext")
ap2.add_argument("--txt-max", metavar="KiB", type=int, default=64, help="max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS)")
ap2.add_argument("--doctitle", metavar="TXT", type=u, default="copyparty @ --name", help="title / service-name to show in html documents")
ap2.add_argument("--bname", metavar="TXT", type=u, default="--name", help="server name (displayed in filebrowser document title)")
ap2.add_argument("--pb-url", metavar="URL", type=u, default=URL_PRJ, help="powered-by link; disable with \033[33m-nb\033[0m")
ap2.add_argument("--ver", action="store_true", help="show version on the control panel (incompatible with \033[33m-nb\033[0m)")
ap2.add_argument("--ver", action="store_true", help="show version on the control panel (incompatible with \033[33m-nb\033[0m). This is the same as --ver-who all")
ap2.add_argument("--ver-who", metavar="TXT", type=u, default="no", help="only show version for: [\033[32ma\033[0m]=admin-permission-anywhere, [\033[32mauth\033[0m]=authenticated, [\033[32mall\033[0m]=anyone")
ap2.add_argument("--du-who", metavar="TXT", type=u, default="all", help="only show disk usage for: [\033[32mno\033[0m]=nobody, [\033[32ma\033[0m]=admin-permission, [\033[32mrw\033[0m]=read-write, [\033[32mw\033[0m]=write, [\033[32mauth\033[0m]=authenticated, [\033[32mall\033[0m]=anyone (volflag=du_who)")
ap2.add_argument("--ver-iwho", type=int, default=0, help=argparse.SUPPRESS)
ap2.add_argument("--du-iwho", type=int, default=0, help=argparse.SUPPRESS)
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")
@ -1784,7 +1844,7 @@ def add_debug(ap):
def run_argparse(
argv: list[str], formatter: Any, retry: bool, nc: int, verbose=True
argv: list[str], formatter: Any, retry: int, nc: int, verbose=True
) -> argparse.Namespace:
ap = argparse.ArgumentParser(
formatter_class=formatter,
@ -1830,6 +1890,7 @@ def run_argparse(
add_webdav(ap)
add_tftp(ap)
add_smb(ap)
add_opds(ap)
add_safety(ap)
add_salt(ap, fk_salt, dk_salt, ah_salt)
add_optouts(ap)
@ -1851,18 +1912,21 @@ def run_argparse(
for k, h, _ in sects:
ap2.add_argument("--help-" + k, action="store_true", help=h)
try:
if not retry:
raise Exception()
if retry:
a = ["ascii", "replace"]
for x in ap._actions:
if not x.help:
continue
try:
x.default = x.default.encode(*a).decode(*a)
except:
pass
a = ["ascii", "replace"]
x.help = x.help.encode(*a).decode(*a) + "\033[0m"
except:
pass
try:
if x.help and x.help is not argparse.SUPPRESS:
x.help = x.help.replace("└─", "`-").encode(*a).decode(*a)
if retry > 2:
x.help = RE_ANSI.sub("", x.help)
except:
pass
ret = ap.parse_args(args=argv[1:])
for k, h, t in sects:
@ -1972,7 +2036,7 @@ def main(argv: Optional[list[str]] = None) -> None:
except:
nc = 486 # mdns/ssdp restart headroom; select() maxfd is 512 on windows
retry = False
retry = 0
for fmtr in [RiceFormatter, RiceFormatter, Dodge11874, BasicDodge11874]:
try:
al = run_argparse(argv, fmtr, retry, nc)
@ -1981,8 +2045,9 @@ def main(argv: Optional[list[str]] = None) -> None:
except SystemExit:
raise
except:
retry = True
lprint("\n[ {} ]:\n{}\n".format(fmtr, min_ex()))
retry += 1
t = "WARNING: due to limitations in your terminal and/or OS, the helptext cannot be displayed correctly. Will show a simplified version due to the following error:\n[ %s ]:\n%s\n"
lprint(t % (fmtr, min_ex()))
try:
assert al # type: ignore
@ -1991,6 +2056,9 @@ def main(argv: Optional[list[str]] = None) -> None:
except:
sys.exit(1)
if al.chdir:
os.chdir(al.chdir)
if al.ansi:
al.no_ansi = False
elif not al.no_ansi:

View file

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

View file

@ -13,7 +13,7 @@ import threading
import time
from datetime import datetime
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, WINDOWS, E
from .__init__ import ANYWIN, MACOS, PY2, TYPE_CHECKING, WINDOWS, E
from .bos import bos
from .cfg import flagdescs, permdescs, vf_bmap, vf_cmap, vf_vmap
from .pwhash import PWHash
@ -21,8 +21,10 @@ from .util import (
DEF_MTE,
DEF_MTH,
EXTS,
FAVICON_MIMES,
HAVE_SQLITE3,
IMPLICATIONS,
META_NOBOTS,
MIMES,
SQLITE_VER,
UNPLICATIONS,
@ -99,6 +101,8 @@ SBADCFG = " ({})".format(BAD_CFG)
PTN_U_GRP = re.compile(r"\$\{u(%[+-][^}]+)\}")
PTN_G_GRP = re.compile(r"\$\{g(%[+-][^}]+)\}")
PTN_U_ANY = re.compile(r"(\${[u][}%])")
PTN_G_ANY = re.compile(r"(\${[g][}%])")
PTN_SIGIL = re.compile(r"(\${[ug][}%])")
@ -167,14 +171,19 @@ class Lim(object):
self.rotn = 0 # rot num files
self.rotl = 0 # rot depth
self.rotf = "" # rot datefmt
self.rotf_tz = UTC # rot timezone
self.rot_re = re.compile("") # rotf check
def log(self, msg: str, c: Union[int, str] = 0) -> None:
if self.log_func:
self.log_func("up-lim", msg, c)
def set_rotf(self, fmt: str) -> None:
def set_rotf(self, fmt: str, tz: str) -> None:
self.rotf = fmt
if tz != "UTC":
from zoneinfo import ZoneInfo
self.rotf_tz = ZoneInfo(tz)
r = re.escape(fmt).replace("%Y", "[0-9]{4}").replace("%j", "[0-9]{3}")
r = re.sub("%[mdHMSWU]", "[0-9]{2}", r)
self.rot_re = re.compile("(^|/)" + r + "$")
@ -278,7 +287,7 @@ class Lim(object):
if self.rot_re.search(path.replace("\\", "/")):
return path, ""
suf = datetime.now(UTC).strftime(self.rotf)
suf = datetime.now(self.rotf_tz).strftime(self.rotf)
if path:
path += "/"
@ -424,10 +433,14 @@ class VFS(object):
self.all_nodes[vpath] = self
self.all_aps = [(rp, [self])]
self.all_vps = [(vp, self)]
self.canonical = self._canonical
self.dcanonical = self._dcanonical
else:
self.histpath = self.dbpath = ""
self.all_aps = []
self.all_vps = []
self.canonical = self._canonical_null
self.dcanonical = self._dcanonical_null
self.get_dbv = self._get_dbv
self.ls = self._ls
@ -624,7 +637,39 @@ class VFS(object):
vrem = vjoin(self.vpath[len(dbv.vpath) :].lstrip("/"), vrem)
return dbv, vrem
def canonical(self, rem: str, resolve: bool = True) -> str:
def casechk(self, rem: str, do_stat: bool) -> bool:
ap = self.canonical(rem, False)
if do_stat and not bos.path.exists(ap):
return True # doesn't exist at all; good to go
dp, fn = os.path.split(ap)
if not fn:
return True # filesystem root
try:
fns = os.listdir(dp)
except:
return True # maybe chmod 111; assume ok
if fn in fns:
return True
hit = "<?>"
lfn = fn.lower()
for zs in fns:
if lfn == zs.lower():
hit = zs
break
if not hit:
return True # NFC/NFD or something, can't be helped either way
if self.log:
t = "returning 404 due to underlying case-insensitive filesystem:\n http-req: %r\n local-fs: %r"
self.log("vfs", t % (fn, hit))
return False
def _canonical_null(self, rem: str, resolve: bool = True) -> str:
return ""
def _dcanonical_null(self, rem: str) -> str:
return ""
def _canonical(self, rem: str, resolve: bool = True) -> str:
"""returns the canonical path (fully-resolved absolute fs path)"""
ap = self.realpath
if rem:
@ -632,7 +677,7 @@ class VFS(object):
return absreal(ap) if resolve else ap
def dcanonical(self, rem: str) -> str:
def _dcanonical(self, rem: str) -> str:
"""resolves until the final component (filename)"""
ap = self.realpath
if rem:
@ -641,6 +686,44 @@ class VFS(object):
ad, fn = os.path.split(ap)
return os.path.join(absreal(ad), fn)
def _canonical_shr(self, rem: str, resolve: bool = True) -> str:
"""returns the canonical path (fully-resolved absolute fs path)"""
ap = self.realpath
if rem:
ap += "/" + rem
rap = absreal(ap)
if self.shr_files:
assert self.shr_src # !rm
vn, rem = self.shr_src
chk = absreal(os.path.join(vn.realpath, rem))
if chk != rap:
# not the dir itself; assert file allowed
ad, fn = os.path.split(rap)
if chk != ad or fn not in self.shr_files:
return "\n\n"
return rap if resolve else ap
def _dcanonical_shr(self, rem: str) -> str:
"""resolves until the final component (filename)"""
ap = self.realpath
if rem:
ap += "/" + rem
ad, fn = os.path.split(ap)
ad = absreal(ad)
if self.shr_files:
assert self.shr_src # !rm
vn, rem = self.shr_src
chk = absreal(os.path.join(vn.realpath, rem))
if chk != absreal(ap):
# not the dir itself; assert file allowed
if ad != chk or fn not in self.shr_files:
return "\n\n"
return os.path.join(ad, fn)
def _ls_nope(
self, *a, **ka
) -> tuple[str, list[tuple[str, os.stat_result]], dict[str, "VFS"]]:
@ -673,8 +756,12 @@ class VFS(object):
"""return user-readable [fsdir,real,virt] items at vpath"""
virt_vis = {} # nodes readable by user
abspath = self.canonical(rem)
real = list(statdir(self.log, scandir, lstat, abspath, throw))
real.sort()
if abspath:
real = list(statdir(self.log, scandir, lstat, abspath, throw))
real.sort()
else:
real = []
if not rem:
# no vfs nodes in the list of real inodes
real = [x for x in real if x[0] not in self.nodes]
@ -976,6 +1063,14 @@ class AuthSrv(object):
self.indent = ""
self.is_lxc = args.c == ["/z/initcfg"]
self._vf0b = {
"tcolor": self.args.tcolor,
"du_iwho": self.args.du_iwho,
"shr_who": self.args.shr_who if self.args.shr else "no",
}
self._vf0 = self._vf0b.copy()
self._vf0["d2d"] = True
# fwd-decl
self.vfs = VFS(log_func, "", "", "", AXS(), {})
self.acct: dict[str, str] = {} # uname->pw
@ -1014,7 +1109,10 @@ class AuthSrv(object):
yield prev, True
def vf0(self):
return {"d2d": True, "tcolor": self.args.tcolor}
return self._vf0.copy()
def vf0b(self):
return self._vf0b.copy()
def idp_checkin(
self, broker: Optional["BrokerCli"], uname: str, gname: str
@ -1080,6 +1178,16 @@ class AuthSrv(object):
src0 = src # abspath
dst0 = dst # vpath
zsl = []
for ptn, sigil in ((PTN_U_ANY, "${u}"), (PTN_G_ANY, "${g}")):
if bool(ptn.search(src)) != bool(ptn.search(dst)):
zsl.append(sigil)
if zsl:
t = "ERROR: if %s is mentioned in a volume definition, it must be included in both the filesystem-path [%s] and the volume-url [/%s]"
t = "\n".join([t % (x, src, dst) for x in zsl])
self.log(t, 1)
raise Exception(t)
un_gn = [(un, gn) for un, gns in un_gns.items() for gn in gns]
if not un_gn:
# ensure volume creation if there's no users
@ -1172,8 +1280,8 @@ class AuthSrv(object):
self.log(t, c=3)
raise Exception(BAD_CFG)
if not bos.path.isdir(src):
self.log("warning: filesystem-path does not exist: {}".format(src), 3)
if not bos.path.exists(src):
self.log("warning: filesystem-path did not exist: %r" % (src,), 3)
mount[dst] = (src, dst0)
daxs[dst] = AXS()
@ -1711,8 +1819,7 @@ class AuthSrv(object):
self.log("\n{0}\n{1}{0}".format(t, "\n".join(slns)))
raise
self.args.have_idp_hdrs = bool(self.args.idp_h_usr or self.args.idp_hm_usr)
self.args.have_ipu_or_ipr = bool(self.args.ipu or self.args.ipr)
derive_args(self.args)
self.setup_auth_ord()
self.setup_pwhash(acct)
@ -1751,12 +1858,15 @@ class AuthSrv(object):
files = os.listdir(E.cfg)
except:
files = []
hits = [x for x in files if x.lower().endswith(".conf")]
hits = [
x
for x in files
if x.lower().endswith(".conf") and not x.startswith(".")
]
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)
vfs = VFS(self.log_func, absreal("."), "", "", axs, self.vf0b())
if not axs.uread:
self.badcfg1 = True
elif "" not in mount:
@ -1792,7 +1902,7 @@ class AuthSrv(object):
vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
vol.root = vfs
zs = "neversymlink"
zs = "neversymlink du_iwho"
k_ign = set(zs.split())
for vol in vfs.all_vols.values():
unknown_flags = set()
@ -1944,6 +2054,8 @@ class AuthSrv(object):
promote = []
demote = []
for vol in vfs.all_vols.values():
if not vol.realpath:
continue
hid = self.hid_cache.get(vol.realpath)
if not hid:
zb = hashlib.sha512(afsenc(vol.realpath)).digest()
@ -1982,6 +2094,8 @@ class AuthSrv(object):
vol.histpath = absreal(vol.histpath)
for vol in vfs.all_vols.values():
if not vol.realpath:
continue
hid = self.hid_cache[vol.realpath]
vflag = vol.flags.get("dbpath")
if vflag == "-":
@ -2110,7 +2224,7 @@ class AuthSrv(object):
zs = vol.flags.get("rotf")
if zs:
use = True
lim.set_rotf(zs)
lim.set_rotf(zs, vol.flags.get("rotf_tz") or "UTC")
zs = vol.flags.get("maxn")
if zs:
@ -2290,6 +2404,11 @@ class AuthSrv(object):
vol.lim.uid = vol.flags["uid"]
vol.lim.gid = vol.flags["gid"]
vol.flags["du_iwho"] = n_du_who(vol.flags["du_who"])
if not enshare:
vol.flags["shr_who"] = self.args.shr_who = "no"
if vol.flags.get("og"):
self.args.uqe = True
@ -2376,6 +2495,45 @@ class AuthSrv(object):
t = "WARNING: volume [/%s]: invalid value specified for ext-th: %s"
self.log(t % (vol.vpath, etv), 3)
zs = str(vol.flags.get("html_head") or "")
if zs and zs[:1] in "%@":
vol.flags["html_head_d"] = zs
head_s = str(vol.flags.get("html_head_s") or "")
else:
zs2 = str(vol.flags.get("html_head_s") or "")
if zs2 and zs:
head_s = "%s\n%s\n" % (zs2.strip(), zs.strip())
else:
head_s = zs2 or zs
if head_s and not head_s.endswith("\n"):
head_s += "\n"
if "norobots" in vol.flags:
head_s += META_NOBOTS
ico_url = vol.flags.get("ufavico")
if ico_url:
ico_h = ""
ico_ext = ico_url.split("?")[0].split(".")[-1].lower()
if ico_ext in FAVICON_MIMES:
zs = '<link rel="icon" type="%s" href="%s">\n'
ico_h = zs % (FAVICON_MIMES[ico_ext], ico_url)
elif ico_ext == "ico":
zs = '<link rel="shortcut icon" href="%s">\n'
ico_h = zs % (ico_url,)
if ico_h:
vol.flags["ufavico_h"] = ico_h
head_s += ico_h
if head_s:
vol.flags["html_head_s"] = head_s
else:
vol.flags.pop("html_head_s", None)
if not vol.flags.get("html_head_d"):
vol.flags.pop("html_head_d", None)
vol.check_landmarks()
# d2d drops all database features for a volume
@ -2465,6 +2623,47 @@ class AuthSrv(object):
self.log(t.format(vol.vpath, mtp), 1)
errors = True
for vol in vfs.all_nodes.values():
if not vol.realpath or os.path.isfile(vol.realpath):
continue
ccs = vol.flags["casechk"][:1].lower()
if ccs in ("y", "n"):
if ccs == "y":
vol.flags["bcasechk"] = True
continue
try:
bos.makedirs(vol.realpath, vf=vol.flags)
files = os.listdir(vol.realpath)
for fn in files:
fn2 = fn.lower()
if fn == fn2:
fn2 = fn.upper()
if fn == fn2 or fn2 in files:
continue
is_ci = os.path.exists(os.path.join(vol.realpath, fn2))
ccs = "y" if is_ci else "n"
break
if ccs not in ("y", "n"):
ap = os.path.join(vol.realpath, "casechk")
open(ap, "wb").close()
ccs = "y" if os.path.exists(ap[:-1] + "K") else "n"
os.unlink(ap)
except Exception as ex:
if ANYWIN:
zs = "Windows"
ccs = "y"
elif MACOS:
zs = "Macos"
ccs = "y"
else:
zs = "Linux"
ccs = "n"
t = "unable to determine if filesystem at %r is case-insensitive due to %r; assuming casechk=%s due to %s"
self.log(t % (vol.realpath, ex, ccs, zs), 3)
vol.flags["casechk"] = ccs
if ccs == "y":
vol.flags["bcasechk"] = True
tags = self.args.mtp or []
tags = [x.split("=")[0] for x in tags]
tags = [y for x in tags for y in x.split(",")]
@ -2568,7 +2767,11 @@ class AuthSrv(object):
if "dedup" in zv.flags:
have_dedup = True
if "e2d" not in zv.flags and "hardlink" not in zv.flags:
if (
"e2d" not in zv.flags
and "hardlink" not in zv.flags
and "reflink" not in zv.flags
):
unsafe_dedup.append("/" + zv.vpath)
t += "\n"
@ -2732,13 +2935,22 @@ class AuthSrv(object):
shn.shr_files = set(fns)
shn.ls = shn._ls_shr
shn.canonical = shn._canonical_shr
shn.dcanonical = shn._dcanonical_shr
else:
shn.ls = shn._ls
shn.canonical = shn._canonical
shn.dcanonical = shn._dcanonical
shn.shr_owner = s_un
shn.shr_src = (s_vfs, s_rem)
shn.realpath = s_vfs.canonical(s_rem)
o_vn, _ = shn._get_share_src("")
shn.flags = o_vn.flags.copy()
shn.dbpath = o_vn.dbpath
shn.histpath = o_vn.histpath
# root.all_aps doesn't include any shares, so make a copy where the
# share appears in all abspaths it can provide (for example for chk_ap)
ap = shn.realpath
@ -2796,19 +3008,24 @@ class AuthSrv(object):
"dcrop": vf["crop"],
"dth3x": vf["th3x"],
"u2ts": vf["u2ts"],
"shr_who": vf["shr_who"],
"frand": bool(vf.get("rand")),
"lifetime": vf.get("lifetime") or 0,
"unlist": vf.get("unlist") or "",
"sb_lg": "" if "no_sb_lg" in vf else (vf.get("lg_sbf") or "y"),
}
if "ufavico_h" in vf:
vn.js_ls["ufavico"] = vf["ufavico_h"]
js_htm = {
"SPINNER": self.args.spinner,
"s_name": self.args.bname,
"idp_login": self.args.idp_login,
"have_up2k_idx": "e2d" in vf,
"have_acode": not self.args.no_acode,
"have_c2flac": self.args.allow_flac,
"have_c2wav": self.args.allow_wav,
"have_shr": self.args.shr,
"shr_who": vf["shr_who"],
"have_zip": not self.args.no_zip,
"have_mv": not self.args.no_mv,
"have_del": not self.args.no_del,
@ -2834,6 +3051,7 @@ class AuthSrv(object):
"dvol": self.args.au_vol,
"idxh": int(self.args.ih),
"dutc": not self.args.localtime,
"dfszf": self.args.ui_filesz.strip("-"),
"themes": self.args.themes,
"turbolvl": self.args.turbo,
"nosubtle": self.args.nosubtle,
@ -2875,7 +3093,7 @@ class AuthSrv(object):
self.args.ao_idp_before_pw = min(h, hm) < pw
self.args.ao_h_before_hm = h < hm
self.args.ao_ipu_wins = ipu == 0
self.args.ao_have_pw = pw < 99
self.args.ao_have_pw = pw < 99 or not self.args.have_idp_hdrs
def load_idp_db(self, quiet=False) -> None:
# mutex me
@ -3469,6 +3687,35 @@ class AuthSrv(object):
self.log("generated config:\n\n" + "\n".join(ret))
def derive_args(args: argparse.Namespace) -> None:
args.have_idp_hdrs = bool(args.idp_h_usr or args.idp_hm_usr)
args.have_ipu_or_ipr = bool(args.ipu or args.ipr)
def n_du_who(s: str) -> int:
if s == "all":
return 9
if s == "auth":
return 7
if s == "w":
return 5
if s == "rw":
return 4
if s == "a":
return 3
return 0
def n_ver_who(s: str) -> int:
if s == "all":
return 9
if s == "auth":
return 6
if s == "a":
return 3
return 0
def split_cfg_ln(ln: str) -> dict[str, Any]:
# "a, b, c: 3" => {a:true, b:true, c:3}
ret = {}
@ -3501,7 +3748,9 @@ def expand_config_file(
if os.path.isdir(fp):
names = list(sorted(os.listdir(fp)))
cnames = [x for x in names if x.lower().endswith(".conf")]
cnames = [
x for x in names if x.lower().endswith(".conf") and not x.startswith(".")
]
if not cnames:
t = "warning: tried to read config-files from folder '%s' but it does not contain any "
if names:

View file

@ -2,18 +2,22 @@
from __future__ import print_function, unicode_literals
import os
import time
from ..util import SYMTIME, fsdec, fsenc
from . import path as path
if True: # pylint: disable=using-constant-test
from typing import Any, Optional
from typing import Any, Optional, Union
from ..util import NamedLogger
MKD_755 = {"chmod_d": 0o755}
MKD_700 = {"chmod_d": 0o700}
UTIME_CLAMPS = ((max, -2147483647), (max, 1), (min, 4294967294), (min, 2147483646))
_ = (path, MKD_755, MKD_700)
__all__ = ["path", "MKD_755", "MKD_700"]
_ = (path, MKD_755, MKD_700, UTIME_CLAMPS)
__all__ = ["path", "MKD_755", "MKD_700", "UTIME_CLAMPS"]
# grep -hRiE '(^|[^a-zA-Z_\.-])os\.' . | gsed -r 's/ /\n/g;s/\(/(\n/g' | grep -hRiE '(^|[^a-zA-Z_\.-])os\.' | sort | uniq -c
# printf 'os\.(%s)' "$(grep ^def bos/__init__.py | gsed -r 's/^def //;s/\(.*//' | tr '\n' '|' | gsed -r 's/.$//')"
@ -99,6 +103,40 @@ def utime(
return os.utime(fsenc(p), times)
def utime_c(
log: Union["NamedLogger", Any], p: str, ts: int, follow_symlinks: bool = True, throw: bool = False
) -> Optional[int]:
clamp = 0
ov = ts
bp = fsenc(p)
now = int(time.time())
while True:
try:
if SYMTIME:
os.utime(bp, (now, ts), follow_symlinks=follow_symlinks)
else:
os.utime(bp, (now, ts))
if clamp:
t = "filesystem rejected utime(%r); clamped %s to %s"
log(t % (p, ov, ts))
return ts
except Exception as ex:
pv = ts
while clamp < len(UTIME_CLAMPS):
fun, cv = UTIME_CLAMPS[clamp]
ts = fun(ts, cv)
clamp += 1
if ts != pv:
break
if clamp >= len(UTIME_CLAMPS):
if throw:
raise
else:
t = "could not utime(%r) to %s; %s, %r"
log(t % (p, ov, ex, ex))
return None
if hasattr(os, "lstat"):
def lstat(p: str) -> os.stat_result:

View file

@ -12,6 +12,7 @@ import queue
from .__init__ import ANYWIN
from .authsrv import AuthSrv
from .broker_util import BrokerCli, ExceptionalQueue, NotExQueue
from .fsutil import ramdisk_chk
from .httpsrv import HttpSrv
from .util import FAKE_MP, Daemon, HMaccas
@ -56,6 +57,7 @@ class MpWorker(BrokerCli):
# starting to look like a good idea
self.asrv = AuthSrv(args, None, False)
ramdisk_chk(self.asrv)
# instantiate all services here (TODO: inheritance?)
self.iphash = HMaccas(os.path.join(self.args.E.cfg, "iphash"), 8)
@ -99,6 +101,7 @@ class MpWorker(BrokerCli):
if dest == "reload":
self.logw("mpw.asrv reloading")
self.asrv.reload()
ramdisk_chk(self.asrv)
self.logw("mpw.asrv reloaded")
continue

View file

@ -52,11 +52,14 @@ def vf_bmap() -> dict[str, str]:
"og",
"og_no_head",
"og_s_title",
"opds",
"rand",
"reflink",
"rm_partial",
"rmagic",
"rss",
"wo_up_readme",
"wram",
"xdev",
"xlink",
"xvol",
@ -81,12 +84,16 @@ def vf_vmap() -> dict[str, str]:
}
for k in (
"bup_ck",
"casechk",
"chmod_d",
"chmod_f",
"dbd",
"du_who",
"ufavico",
"forget_ip",
"hsortn",
"html_head",
"html_head_s",
"lg_sbf",
"md_sbf",
"lg_sba",
@ -103,10 +110,12 @@ def vf_vmap() -> dict[str, str]:
"og_title_i",
"og_tpl",
"og_ua",
"opds_exts",
"put_ck",
"put_name",
"mv_retry",
"rm_retry",
"shr_who",
"sort",
"tail_fd",
"tail_rate",
@ -184,8 +193,10 @@ flagcats = {
"chmod_f=644": "unix-permission for new files",
"uid=573": "change owner of new files/folders to unix-user 573",
"gid=999": "change owner of new files/folders to unix-group 999",
"wram": "allow uploading into ramdisks",
"sparse": "force use of sparse files, mainly for s3-backed storage",
"nosparse": "deny use of sparse files, mainly for slow storage",
"rm_partial": "delete unfinished uploads from HDD when they timeout",
"daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
"nosub": "forces all uploads into the top folder of the vfs",
"magic": "enables filetype detection for nameless uploads",
@ -214,6 +225,7 @@ flagcats = {
"upload rotation\n(moves all uploads into the specified folder structure)": {
"rotn=100,3": "3 levels of subfolders with 100 entries in each",
"rotf=%Y-%m/%d-%H": "date-formatted organizing",
"rotf_tz=Europe/Oslo": "timezone (default=UTC)",
"lifetime=3600": "uploads are deleted after 1 hour",
},
"database, general": {
@ -242,6 +254,7 @@ flagcats = {
"no_db_ip": "never store uploader-IP in the db; disables unpost",
"fat32": "avoid excessive reindexing on android sdcardfs",
"dbd=[acid|swal|wal|yolo]": "database speed-durability tradeoff",
"casechk=auto": "actively prevent case-insensitive filesystem? y/n",
"xlink": "cross-volume dupe detection / linking (dangerous)",
"xdev": "do not descend into other filesystems",
"xvol": "do not follow symlinks leaving the volume root",
@ -292,10 +305,13 @@ flagcats = {
"sort": "default sort order",
"nsort": "natural-sort of leading digits in filenames",
"hsortn": "number of sort-rules to add to media URLs",
"ufavico=URL": "per-volume favicon (.ico/png/gif/svg)",
"unlist": "dont list files matching REGEX",
"html_head=TXT": "includes TXT in the <head>, or @PATH for file at PATH",
"html_head_s=TXT": "additional static text in the html <head>",
"tcolor=#fc0": "theme color (a hint for webbrowsers, discord, etc.)",
"nodirsz": "don't show total folder size",
"du_who=all": "show disk-usage info to everyone",
"robots": "allows indexing by search engines (default)",
"norobots": "kindly asks search engines to leave",
"unlistcr": "don't list read-access in controlpanel",
@ -324,6 +340,10 @@ flagcats = {
"og_no_head": "you want to add tags manually with og_tpl",
"og_ua": "if defined: only send OG html if useragent matches this regex",
},
"opds": {
"opds": "enable OPDS",
"opds_exts": "file formats to list in OPDS feeds; leave empty to show everything",
},
"textfiles": {
"md_no_br": "newline only on double-newline or two tailing spaces",
"md_hist": "where to put markdown backups; s=subfolder, v=volHist, n=nope",
@ -348,6 +368,7 @@ flagcats = {
"dky": 'allow seeing files (not folders) inside a specific folder\nwith "g" perm, and does not require a valid dirkey to do so',
"rss": "allow '?rss' URL suffix (experimental)",
"rmagic": "expensive analysis for mimetype accuracy",
"shr_who=auth": "who can create shares? no/auth/a",
"unp_who=2": "unpost only if same... 1=ip+name, 2=ip, 3=name",
"ups_who=2": "restrict viewing the list of recent uploads",
"zip_who=2": "restrict access to download-as-zip/tar",

View file

@ -7,7 +7,7 @@ import re
import time
from .__init__ import ANYWIN, MACOS
from .authsrv import AXS, VFS
from .authsrv import AXS, VFS, AuthSrv
from .bos import bos
from .util import chkcmd, min_ex, undot
@ -18,22 +18,25 @@ if True: # pylint: disable=using-constant-test
class Fstab(object):
def __init__(self, log: "RootLogger", args: argparse.Namespace):
def __init__(self, log: "RootLogger", args: argparse.Namespace, verbose: bool):
self.log_func = log
self.verbose = verbose
self.warned = False
self.trusted = False
self.tab: Optional[VFS] = None
self.oldtab: Optional[VFS] = None
self.srctab = "a"
self.cache: dict[str, str] = {}
self.cache: dict[str, tuple[str, str]] = {}
self.age = 0.0
self.maxage = args.mtab_age
def log(self, msg: str, c: Union[int, str] = 0) -> None:
if not c or self.verbose:
return
self.log_func("fstab", msg, c)
def get(self, path: str) -> str:
def get(self, path: str) -> tuple[str, str]:
now = time.time()
if now - self.age > self.maxage or len(self.cache) > 9000:
self.age = now
@ -41,6 +44,7 @@ class Fstab(object):
self.tab = None
self.cache = {}
mp = ""
fs = "ext4"
msg = "failed to determine filesystem at %r; assuming %s\n%s"
@ -50,7 +54,7 @@ class Fstab(object):
path = self._winpath(path)
except:
self.log(msg % (path, fs, min_ex()), 3)
return fs
return fs, ""
path = undot(path)
try:
@ -59,14 +63,14 @@ class Fstab(object):
pass
try:
fs = self.get_w32(path) if ANYWIN else self.get_unix(path)
fs, mp = self.get_w32(path) if ANYWIN else self.get_unix(path)
except:
self.log(msg % (path, fs, min_ex()), 3)
fs = fs.lower()
self.cache[path] = fs
self.log("found %s at %r" % (fs, path))
return fs
self.cache[path] = (fs, mp)
self.log("found %s at %r, %r" % (fs, mp, path))
return fs, mp
def _winpath(self, path: str) -> str:
# try to combine volume-label + st_dev (vsn)
@ -81,34 +85,49 @@ class Fstab(object):
self.tab = VFS(self.log_func, "idk", "/", "/", AXS(), {})
self.trusted = False
def build_tab(self) -> None:
self.log("inspecting mtab for changes")
def _from_sp_mount(self) -> dict[str, str]:
sptn = r"^.*? on (.*) type ([^ ]+) \(.*"
if MACOS:
sptn = r"^.*? on (.*) \(([^ ]+), .*"
ptn = re.compile(sptn)
so, _ = chkcmd(["mount"])
tab1: list[tuple[str, str]] = []
atab = []
dtab: dict[str, str] = {}
for ln in so.split("\n"):
m = ptn.match(ln)
if not m:
continue
zs1, zs2 = m.groups()
tab1.append((str(zs1), str(zs2)))
atab.append(ln)
dtab[str(zs1)] = str(zs2)
return dtab
def _from_proc(self) -> dict[str, str]:
ret: dict[str, str] = {}
with open("/proc/self/mounts", "rb", 262144) as f:
src = f.read(262144).decode("utf-8", "replace").split("\n")
for zsl in [x.split(" ") for x in src]:
if len(zsl) < 3:
continue
zs = zsl[1]
zs = zs.replace("\\011", "\t").replace("\\040", " ").replace("\\134", "\\")
ret[zs] = zsl[2]
return ret
def build_tab(self) -> None:
self.log("inspecting mtab for changes")
dtab = self._from_sp_mount() if MACOS else self._from_proc()
# keep empirically-correct values if mounttab unchanged
srctab = "\n".join(sorted(atab))
srctab = str(sorted(dtab.items()))
if srctab == self.srctab:
self.tab = self.oldtab
return
self.log("mtab has changed; reevaluating support for sparse files")
tab1 = list(dtab.items())
tab1.sort(key=lambda x: (len(x[0]), x[0]))
path1, fs1 = tab1[0]
tab = VFS(self.log_func, fs1, path1, path1, AXS(), {})
@ -146,7 +165,7 @@ class Fstab(object):
vn.realpath = ptn.sub(nval, vn.realpath)
visit.extend(list(vn.nodes.values()))
def get_unix(self, path: str) -> str:
def get_unix(self, path: str) -> tuple[str, str]:
if not self.tab:
try:
self.build_tab()
@ -155,20 +174,44 @@ class Fstab(object):
# prisonparty or other restrictive environment
if not self.warned:
self.warned = True
self.log("failed to build tab:\n{}".format(min_ex()), 3)
t = "failed to associate fs-mounts with the VFS (this is fine):\n%s"
self.log(t % (min_ex(),), 6)
self.build_fallback()
assert self.tab # !rm
ret = self.tab._find(path)[0]
if self.trusted or path == ret.vpath:
return ret.realpath.split("/")[0]
return ret.realpath.split("/")[0], ret.vpath
else:
return "idk"
return "idk", ""
def get_w32(self, path: str) -> str:
def get_w32(self, path: str) -> tuple[str, str]:
if not self.tab:
self.build_fallback()
assert self.tab # !rm
ret = self.tab._find(path)[0]
return ret.realpath
return ret.realpath, ""
def ramdisk_chk(asrv: AuthSrv) -> None:
# should have been in authsrv but that's a circular import
mods = []
ramfs = ("tmpfs", "overlay")
log = asrv.log_func or print
fstab = Fstab(log, asrv.args, False)
for vn in asrv.vfs.all_nodes.values():
if not vn.axs.uwrite or "wram" in vn.flags:
continue
ap = vn.realpath
if not ap or os.path.isfile(ap):
continue
fs, mp = fstab.get(ap)
mp = "/" + mp.strip("/")
if fs == "tmpfs" or (mp == "/" and fs in ramfs):
mods.append((vn.vpath, ap, fs, mp))
vn.axs.uwrite.clear()
if mods:
t = "WARNING: write-access was removed from the following volumes because they are not mapped to an actual HDD for storage! All uploaded data would live in RAM only, and all uploaded files would be LOST on next reboot. To allow uploading and ignore this hazard, enable the 'wram' option (global/volflag). List of affected volumes:"
t2 = ["\n volume=[/%s], abspath=%r, type=%s, root=%r" % x for x in mods]
log("vfs", t + "".join(t2) + "\n", 1)

View file

@ -152,10 +152,6 @@ class FtpFs(AbstractedFS):
self.cwd = "/" # pyftpdlib convention of leading slash
self.root = "/var/lib/empty"
self.can_read = self.can_write = self.can_move = False
self.can_delete = self.can_get = self.can_upget = False
self.can_admin = self.can_dot = False
self.listdirinfo = self.listdir
self.chdir(".")
@ -206,6 +202,9 @@ class FtpFs(AbstractedFS):
if r and not cr or w and not cw or m and not cm or d and not cd:
raise FSE(t.format(vpath), 1)
if "bcasechk" in vfs.flags and not vfs.casechk(rem, True):
raise FSE("No such file or directory", 1)
return os.path.join(vfs.realpath, rem), vfs, rem
except Pebkac as ex:
raise FSE(str(ex))
@ -218,7 +217,7 @@ class FtpFs(AbstractedFS):
m: bool = False,
d: bool = False,
) -> tuple[str, VFS, str]:
return self.v2a(os.path.join(self.cwd, vpath), r, w, m, d)
return self.v2a(join(self.cwd, vpath), r, w, m, d)
def ftp2fs(self, ftppath: str) -> str:
# return self.v2a(ftppath)
@ -280,6 +279,10 @@ class FtpFs(AbstractedFS):
def chdir(self, path: str) -> None:
nwd = join(self.cwd, path)
vfs, rem = self.hub.asrv.vfs.get(nwd, self.uname, False, False)
if not vfs.realpath:
self.cwd = nwd
return
ap = vfs.canonical(rem)
try:
st = bos.stat(ap)
@ -289,24 +292,11 @@ 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
avfs = vfs.chk_ap(ap, st)
if not avfs:
raise FSE("Permission denied", 1)
self.cwd = nwd
(
self.can_read,
self.can_write,
self.can_move,
self.can_delete,
self.can_get,
self.can_upget,
self.can_admin,
self.can_dot,
) = avfs.can_access("", self.h.uname)
def mkdir(self, path: str) -> None:
ap, vfs, _ = self.rv2a(path, w=True)
@ -329,7 +319,7 @@ class FtpFs(AbstractedFS):
vfs_ls = [x[0] for x in vfs_ls1]
vfs_ls.extend(vfs_virt.keys())
if not self.can_dot:
if self.uname not in vfs.axs.udot:
vfs_ls = exclude_dotfiles(vfs_ls)
vfs_ls.sort()
@ -377,9 +367,6 @@ class FtpFs(AbstractedFS):
raise FSE(str(ex))
def rename(self, src: str, dst: str) -> None:
if not self.can_move:
raise FSE("Not allowed for user " + self.h.uname)
if self.args.no_mv:
raise FSE("The rename/move feature is disabled in server config")
@ -409,12 +396,8 @@ class FtpFs(AbstractedFS):
return st
def utime(self, path: str, timeval: float) -> None:
try:
ap = self.rv2a(path, w=True)[0]
return bos.utime(ap, (int(time.time()), int(timeval)))
except Exception as ex:
logging.error("ftp.utime: %s, %r", ex, ex)
raise
ap = self.rv2a(path, w=True)[0]
bos.utime_c(logging.warning, ap, int(timeval), False)
def lstat(self, path: str) -> os.stat_result:
ap = self.rv2a(path)[0]
@ -510,24 +493,30 @@ class FtpHandler(FTPHandler):
return
self.vfs_map[ap] = vp
xbu = vfs.flags.get("xbu")
if xbu and not runhook(
None,
None,
self.hub.up2k,
"xbu.ftpd",
xbu,
ap,
vp,
"",
self.uname,
self.hub.asrv.vfs.get_perms(vp, self.uname),
0,
0,
self.cli_ip,
time.time(),
"",
):
raise FSE("Upload blocked by xbu server config")
if xbu:
hr = runhook(
None,
None,
self.hub.up2k,
"xbu.ftpd",
xbu,
ap,
vp,
"",
self.uname,
self.hub.asrv.vfs.get_perms(vp, self.uname),
0,
0,
self.cli_ip,
time.time(),
"",
)
t = hr.get("rejectmsg") or ""
if t or not hr:
if not t:
t = "Upload blocked by xbu server config: %r" % (vp,)
self.respond("550 %s" % (t,), logging.info)
return
# print("ftp_STOR: {} {} => {}".format(vp, mode, ap))
ret = FTPHandler.ftp_STOR(self, file, mode)

File diff suppressed because it is too large Load diff

View file

@ -187,6 +187,7 @@ class HttpSrv(object):
"svcs",
]
self.j2 = {x: env.get_template(x + ".html") for x in jn}
self.j2["opds"] = env.get_template("opds.xml")
self.prism = has_resource(self.E, "web/deps/prism.js.gz")
if self.args.ipu:
@ -571,7 +572,7 @@ class HttpSrv(object):
v = self.E.t0
try:
with os.scandir(os.path.join(self.E.mod, "web")) as dh:
with os.scandir(self.E.mod_ + "web") as dh:
for fh in dh:
inf = fh.stat()
v = max(v, inf.st_mtime)

View file

@ -2,6 +2,7 @@
from __future__ import print_function, unicode_literals
import errno
import os
import random
import select
import socket
@ -12,29 +13,64 @@ from ipaddress import IPv4Network, IPv6Network
from .__init__ import TYPE_CHECKING
from .__init__ import unicode as U
from .multicast import MC_Sck, MCast
from .stolen.dnslib import AAAA
from .stolen.dnslib import CLASS as DC
from .stolen.dnslib import (
NSEC,
PTR,
QTYPE,
RR,
SRV,
TXT,
A,
DNSHeader,
DNSQuestion,
DNSRecord,
set_avahi_379,
)
from .util import IP6_LL, CachedSet, Daemon, Netdev, list_ips, min_ex
try:
if os.getenv("PRTY_SYS_ALL") or os.getenv("PRTY_SYS_DNSLIB"):
raise ImportError()
from .stolen.dnslib import (
AAAA,
)
from .stolen.dnslib import CLASS as DC
from .stolen.dnslib import (
NSEC,
PTR,
QTYPE,
RR,
SRV,
TXT,
A,
DNSHeader,
DNSQuestion,
DNSRecord,
set_avahi_379,
)
DNS_VND = True
except ImportError:
DNS_VND = False
from dnslib import (
AAAA,
)
from dnslib import CLASS as DC
from dnslib import (
NSEC,
PTR,
QTYPE,
RR,
SRV,
TXT,
A,
Bimap,
DNSHeader,
DNSQuestion,
DNSRecord,
)
DC.forward[0x8001] = "F_IN"
DC.reverse["F_IN"] = 0x8001
if TYPE_CHECKING:
from .svchub import SvcHub
if True: # pylint: disable=using-constant-test
from typing import Any, Optional, Union
if os.getenv("PRTY_MODSPEC"):
from inspect import getsourcefile
print("PRTY_MODSPEC: dnslib:", getsourcefile(A))
MDNS4 = "224.0.0.251"
MDNS6 = "ff02::fb"
@ -73,7 +109,7 @@ class MDNS(MCast):
self.ngen = ngen
self.ttl = 300
if not self.args.zm_nwa_1:
if not self.args.zm_nwa_1 and DNS_VND:
set_avahi_379()
zs = self.args.zm_fqdn or (self.args.name + ".local")
@ -100,9 +136,14 @@ class MDNS(MCast):
self.log_func(self.logsrc, msg, c)
def build_svcs(self) -> tuple[dict[str, dict[str, Any]], set[str]]:
ar = self.args
zms = self.args.zms
http = {"port": 80 if 80 in self.args.p else self.args.p[0]}
https = {"port": 443 if 443 in self.args.p else self.args.p[0]}
zi = ar.zm_http
http = {"port": zi if zi != -1 else 80 if 80 in ar.p else ar.p[0]}
zi = ar.zm_https
https = {"port": zi if zi != -1 else 443 if 443 in ar.p else ar.p[0]}
webdav = http.copy()
webdavs = https.copy()
webdav["u"] = webdavs["u"] = "u" # KDE requires username
@ -127,16 +168,16 @@ class MDNS(MCast):
svcs: dict[str, dict[str, Any]] = {}
if "d" in zms:
if "d" in zms and http["port"]:
svcs["_webdav._tcp.local."] = webdav
if "D" in zms:
if "D" in zms and https["port"]:
svcs["_webdavs._tcp.local."] = webdavs
if "h" in zms:
if "h" in zms and http["port"]:
svcs["_http._tcp.local."] = http
if "H" in zms:
if "H" in zms and https["port"]:
svcs["_https._tcp.local."] = https
if "f" in zms.lower():

View file

@ -178,6 +178,7 @@ def au_unpk(
elif pk == "epub":
fi = get_cover_from_epub(log, abspath)
assert fi # !rm
else:
raise Exception("unknown compression %s" % (pk,))
@ -199,9 +200,10 @@ def au_unpk(
except Exception as ex:
if ret:
t = "failed to decompress audio file %r: %r"
t = "failed to decompress file %r: %r"
log(t % (abspath, ex))
wunlink(log, ret, vn.flags if vn else VF_CAREFUL)
return ""
return abspath
@ -421,10 +423,17 @@ def get_cover_from_epub(log: "NamedLogger", abspath: str) -> Optional[IO[bytes]]
# This might be an EPUB2 file, try the legacy way of specifying covers
coverimage_path = _get_cover_from_epub2(log, package_root, package_ns)
if not coverimage_path:
raise Exception("no cover inside epub")
# 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)
try:
return z.open(adjusted_cover_path)
except KeyError:
t = "epub: cover specified in package document, but doesn't exist: %s"
log(t % (adjusted_cover_path,))
def _get_cover_from_epub2(
@ -432,9 +441,8 @@ def _get_cover_from_epub2(
) -> 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"
)
xn = package_root.find("./metadata/meta[@name='cover']", package_ns)
cover_id = xn.get("content") if xn is not None else None
if not cover_id:
return None
@ -510,7 +518,6 @@ class MTag(object):
"album-artist",
"tpe2",
"aart",
"conductor",
"organization",
"band",
],
@ -644,6 +651,9 @@ class MTag(object):
return self._get(abspath)
ap = au_unpk(self.log, self.args.au_unpk, abspath)
if not ap:
return {}
ret = self._get(ap)
if ap != abspath:
wunlink(self.log, ap, VF_CAREFUL)
@ -749,6 +759,9 @@ class MTag(object):
ap = abspath
ret: dict[str, Any] = {}
if not ap:
return ret
for tagname, parser in sorted(parsers.items(), key=lambda x: (x[1].pri, x[0])):
try:
cmd = [parser.bin, ap]

View file

@ -96,7 +96,10 @@ class MCast(object):
def create_servers(self) -> list[str]:
bound: list[str] = []
netdevs = self.hub.tcpsrv.netdevs
ips = [x[0] for x in self.hub.tcpsrv.bound]
blist = self.hub.tcpsrv.bound
if self.args.http_no_tcp:
blist = self.hub.tcpsrv.seen_eps
ips = [x[0] for x in blist]
if "::" in ips:
ips = [x for x in ips if x != "::"] + list(

112
copyparty/qrkode.py Normal file
View file

@ -0,0 +1,112 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
import os
try:
if os.getenv("PRTY_SYS_ALL") or os.getenv("PRTY_SYS_QRCG"):
raise ImportError()
from .stolen.qrcodegen import QrCode
qrgen = QrCode.encode_binary
VENDORED = True
except ImportError:
VENDORED = False
from qrcodegen import QrCode
if os.getenv("PRTY_MODSPEC"):
from inspect import getsourcefile
print("PRTY_MODSPEC: qrcode:", getsourcefile(QrCode))
if True: # pylint: disable=using-constant-test
import typing
from typing import Any, Optional, Sequence, Union
if not VENDORED:
def _qrgen(data: Union[bytes, Sequence[int]]) -> "QrCode":
ret = None
V = QrCode.Ecc
for e in [V.HIGH, V.QUARTILE, V.MEDIUM, V.LOW]:
qr = QrCode.encode_binary(data, e)
qr.size = qr._size
qr.modules = qr._modules
if not ret or ret.size > qr.size:
ret = qr
return ret
qrgen = _qrgen
def qr2txt(qr: QrCode, zoom: int = 1, pad: int = 4) -> str:
tab = qr.modules
sz = qr.size
if sz % 2 and zoom == 1:
tab.append([False] * sz)
tab = [[False] * sz] * pad + tab + [[False] * sz] * pad
tab = [[False] * pad + x + [False] * pad for x in tab]
rows: list[str] = []
if zoom == 1:
for y in range(0, len(tab), 2):
row = ""
for x in range(len(tab[y])):
v = 2 if tab[y][x] else 0
v += 1 if tab[y + 1][x] else 0
row += " ▄▀█"[v]
rows.append(row)
else:
for tr in tab:
row = ""
for zb in tr:
row += ""[int(zb)] * 2
rows.append(row)
return "\n".join(rows)
def qr2png(
qr: QrCode,
zoom: int,
pad: int,
bg: Optional[tuple[int, int, int]],
fg: Optional[tuple[int, int, int]],
ap: str,
) -> None:
from PIL import Image
tab = qr.modules
sz = qr.size
psz = sz + pad * 2
if bg:
img = Image.new("RGB", (psz, psz), bg)
else:
img = Image.new("RGBA", (psz, psz), (0, 0, 0, 0))
fg = (fg[0], fg[1], fg[2], 255)
for y in range(sz):
for x in range(sz):
if tab[y][x]:
img.putpixel((x + pad, y + pad), fg)
if zoom != 1:
img = img.resize((sz * zoom, sz * zoom), Image.Resampling.NEAREST)
img.save(ap)
def qr2svg(qr: QrCode, border: int) -> str:
parts: list[str] = []
for y in range(qr.size):
sy = border + y
for x in range(qr.size):
if qr.modules[y][x]:
parts.append("M%d,%dh1v1h-1z" % (border + x, sy))
t = """\
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 {0} {0}" stroke="none">
<rect width="100%" height="100%" fill="#F7F7F7"/>
<path d="{1}" fill="#111111"/>
</svg>
"""
return t.format(qr.size + border * 2, " ".join(parts))

View file

@ -246,24 +246,29 @@ class SMB(object):
ap = absreal(ap)
xbu = vfs.flags.get("xbu")
if xbu and not runhook(
self.nlog,
None,
self.hub.up2k,
"xbu.smb",
xbu,
ap,
vpath,
"",
"",
"",
0,
0,
"1.7.6.2",
time.time(),
"",
):
yeet("blocked by xbu server config: %r" % (vpath,))
if xbu:
hr = runhook(
self.nlog,
None,
self.hub.up2k,
"xbu.smb",
xbu,
ap,
vpath,
"",
"",
"",
0,
0,
"1.7.6.2",
time.time(),
"",
)
t = hr.get("rejectmsg") or ""
if t or not hr:
if not t:
t = "blocked by xbu server config: %r" % (vpath,)
yeet(t)
ret = bos.open(ap, flags, *a, mode=chmod, **ka)
if wr:
@ -373,7 +378,7 @@ class SMB(object):
t = "blocked utime (no-write-acc %s): /%s @%s"
yeet(t % (vfs.axs.uwrite, vpath, uname))
return bos.utime(ap, times)
bos.utime_c(info, ap, int(times[1]), False)
def _p_exists(self, vpath: str) -> bool:
# ap = "?"

View file

@ -4,7 +4,7 @@
# https://github.com/nayuki/QR-Code-generator/blob/daa3114/python/qrcodegen.py
# the original ^ is extremely well commented so refer to that for explanations
# hacks: binary-only, auto-ecc, render, py2-compat
# hacks: binary-only, auto-ecc, py2-compat
from __future__ import print_function, unicode_literals
@ -173,52 +173,6 @@ class QrCode(object):
self._apply_mask(msk) # Apply the final choice of mask
self._draw_format_bits(msk) # Overwrite old format bits
def render(self, zoom=1, pad=4) -> str:
tab = self.modules
sz = self.size
if sz % 2 and zoom == 1:
tab.append([False] * sz)
tab = [[False] * sz] * pad + tab + [[False] * sz] * pad
tab = [[False] * pad + x + [False] * pad for x in tab]
rows: list[str] = []
if zoom == 1:
for y in range(0, len(tab), 2):
row = ""
for x in range(len(tab[y])):
v = 2 if tab[y][x] else 0
v += 1 if tab[y + 1][x] else 0
row += " ▄▀█"[v]
rows.append(row)
else:
for tr in tab:
row = ""
for zb in tr:
row += ""[int(zb)] * 2
rows.append(row)
return "\n".join(rows)
def to_png(self, zoom, pad, bg, fg, ap) -> None:
from PIL import Image
tab = self.modules
sz = self.size
psz = sz + pad * 2
if bg:
img = Image.new("RGB", (psz, psz), bg)
else:
img = Image.new("RGBA", (psz, psz), (0, 0, 0, 0))
fg = (fg[0], fg[1], fg[2], 255)
for y in range(sz):
for x in range(sz):
if tab[y][x]:
img.putpixel((x + pad, y + pad), fg)
if zoom != 1:
img = img.resize((sz * zoom, sz * zoom), Image.Resampling.NEAREST)
img.save(ap)
def _draw_function_patterns(self) -> None:
# Draw horizontal and vertical timing patterns
for i in range(self.size):
@ -613,20 +567,3 @@ def _get_bit(x: int, i: int) -> bool:
class DataTooLongError(ValueError):
pass
def qr2svg(qr: QrCode, border: int) -> str:
parts: list[str] = []
for y in range(qr.size):
sy = border + y
for x in range(qr.size):
if qr.modules[y][x]:
parts.append("M%d,%dh1v1h-1z" % (border + x, sy))
t = """\
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 {0} {0}" stroke="none">
<rect width="100%" height="100%" fill="#F7F7F7"/>
<path d="{1}" fill="#111111"/>
</svg>
"""
return t.format(qr.size + border * 2, " ".join(parts))

View file

@ -27,9 +27,10 @@ if True: # pylint: disable=using-constant-test
from typing import Any, Optional, Union
from .__init__ import ANYWIN, EXE, MACOS, PY2, TYPE_CHECKING, E, EnvParams, unicode
from .authsrv import BAD_CFG, AuthSrv
from .authsrv import BAD_CFG, AuthSrv, derive_args, n_du_who, n_ver_who
from .bos import bos
from .cert import ensure_cert
from .fsutil import ramdisk_chk
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, HAVE_MUTAGEN
from .pwhash import HAVE_ARGON2
from .tcpsrv import TcpSrv
@ -66,6 +67,7 @@ from .util import (
build_netmap,
expat_ver,
gzip,
html_escape,
load_ipr,
load_ipu,
lock_file,
@ -156,7 +158,7 @@ class SvcHub(object):
args.unpost = 0
args.no_del = True
args.no_mv = True
args.hardlink = True
args.reflink = True
args.dav_auth = True
args.vague_403 = True
args.nih = True
@ -246,8 +248,8 @@ class SvcHub(object):
t = "WARNING: --th-ram-max is very small (%.2f GiB); will not be able to %s"
self.log("root", t % (args.th_ram_max, zs), 3)
if args.chpw and args.have_idp_hdrs:
t = "ERROR: user-changeable passwords is incompatible with IdP/identity-providers; you must disable either --chpw or --idp-h-usr"
if args.chpw and args.have_idp_hdrs and "pw" not in args.auth_ord.split(","):
t = "ERROR: user-changeable passwords is not compatible with your current configuration. Choose one of these options to fix it:\n option1: disable --chpw\n option2: remove all use of IdP features; --idp-*\n option3: change --auth-ord to something like pw,idp,ipu"
self.log("root", t, 1)
raise Exception(t)
@ -289,6 +291,14 @@ class SvcHub(object):
ch = "abcdefghijklmnopqrstuvwx"[int(args.theme / 2)]
args.theme = "{0}{1} {0} {1}".format(ch, bri)
if args.nid:
args.du_who = "no"
args.du_iwho = n_du_who(args.du_who)
if args.ver and args.ver_who == "no":
args.ver_who = "all"
args.ver_iwho = n_ver_who(args.ver_who)
if args.nih:
args.vname = ""
args.doctitle = args.doctitle.replace(" @ --name", "")
@ -302,6 +312,7 @@ class SvcHub(object):
# initiate all services to manage
self.asrv = AuthSrv(self.args, self.log, dargs=self.dargs)
ramdisk_chk(self.asrv)
if args.cgen:
self.asrv.cgen()
@ -381,7 +392,10 @@ class SvcHub(object):
t = "invalid mp3 transcoding quality [%s] specified; only supports [0] to disable, a CBR value such as [192k], or a CQ/CRF value such as [v2]"
raise Exception(t % (args.q_mp3,))
else:
args.au_unpk = {}
zss = set(args.th_r_ffa.split(",") + args.th_r_ffv.split(","))
args.au_unpk = {
k: v for k, v in args.au_unpk.items() if v.split(".")[0] not in zss
}
args.th_poke = min(args.th_poke, args.th_maxage, args.ac_maxage)
@ -844,6 +858,10 @@ class SvcHub(object):
if w8:
time.sleep(w8)
self.log("qr-code", qr)
if self.args.qr_stdout:
self.pr(self.tcpsrv.qr)
if self.args.qr_stderr:
self.pr(self.tcpsrv.qr, file=sys.stderr)
w8 = self.args.qr_every
msg = "%s\033[%dA" % (qr, len(qr.split("\n")))
while w8:
@ -877,8 +895,13 @@ class SvcHub(object):
self.sticky_qr()
if self.args.qr_wait or self.args.qr_every or self.args.qr_winch:
Daemon(self._qr_thr, "qr")
elif not self.args.qr_pin:
self.log("qr-code", self.tcpsrv.qr)
else:
if not self.args.qr_pin:
self.log("qr-code", self.tcpsrv.qr)
if self.args.qr_stdout:
self.pr(self.tcpsrv.qr)
if self.args.qr_stderr:
self.pr(self.tcpsrv.qr, file=sys.stderr)
else:
self.log("root", "workers OK\n")
@ -976,6 +999,24 @@ class SvcHub(object):
t = "WARNING:\nDisabling WebDAV support because dxml selftest failed. Please report this bug;\n%s\n...and include the following information in the bug-report:\n%s | expat %s\n"
self.log("root", t % (URL_BUG, VERSIONS, expat_ver()), 1)
if not E.scfg and not al.unsafe_state and not os.getenv("PRTY_UNSAFE_STATE"):
t = "because runtime config is currently being stored in an untrusted emergency-fallback location. Please fix your environment so either XDG_CONFIG_HOME or ~/.config can be used instead, or disable this safeguard with --unsafe-state or env-var PRTY_UNSAFE_STATE=1."
if not al.no_ses:
al.no_ses = True
t2 = "A consequence of this misconfiguration is that passwords will now be sent in the HTTP-header of every request!"
self.log("root", "WARNING:\nWill disable sessions %s %s" % (t, t2), 1)
if al.idp_store == 1:
al.idp_store = 0
self.log("root", "WARNING:\nDisabling --idp-store %s" % (t,), 3)
if al.idp_store:
t2 = "ERROR: Cannot enable --idp-store %s" % (t,)
self.log("root", t2, 1)
raise Exception(t2)
if al.shr:
t2 = "ERROR: Cannot enable shares %s" % (t,)
self.log("root", t2, 1)
raise Exception(t2)
def _process_config(self) -> bool:
al = self.args
@ -1131,6 +1172,13 @@ class SvcHub(object):
if len(al.tcolor) == 3: # fc5 => ffcc55
al.tcolor = "".join([x * 2 for x in al.tcolor])
if self.args.name_url:
zs = html_escape(self.args.name_url, True, True)
zs = '<a href="%s">%s</a>' % (zs, self.args.name)
else:
zs = self.args.name
self.args.name_html = zs
zs = al.u2sz
zsl = [x.strip() for x in zs.split(",")]
if len(zsl) not in (1, 3):
@ -1149,6 +1197,7 @@ class SvcHub(object):
zi2 = zi
al.u2sz = ",".join(zsl)
derive_args(al)
return True
def _ipa2re(self, txt) -> Optional[re.Pattern]:
@ -1324,6 +1373,7 @@ class SvcHub(object):
with self.reload_mutex:
self.log("root", "reloading config")
self.asrv.reload(9 if up2k else 4)
ramdisk_chk(self.asrv)
if up2k:
self.up2k.reload(rescan_all_vols)
t += "; volumes are now reinitializing"

View file

@ -9,7 +9,7 @@ import time
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, unicode
from .cert import gencert
from .stolen.qrcodegen import QrCode, qr2svg
from .qrkode import QrCode, qr2png, qr2svg, qr2txt, qrgen
from .util import (
E_ACCESS,
E_ADDR_IN_USE,
@ -21,6 +21,7 @@ from .util import (
VF_CAREFUL,
Netdev,
atomic_move,
get_adapters,
min_ex,
sunpack,
termsize,
@ -59,6 +60,7 @@ class TcpSrv(object):
self.stopping = False
self.srv: list[socket.socket] = []
self.bound: list[tuple[str, int]] = []
self.seen_eps: list[tuple[str, int]] = [] # also skipped by uds-only
self.netdevs: dict[str, Netdev] = {}
self.netlist = ""
self.nsrv = 0
@ -299,12 +301,19 @@ class TcpSrv(object):
try:
if tcp:
if self.args.http_no_tcp:
self.seen_eps.append((ip, port))
return
srv.bind((ip, port))
else:
if ANYWIN or self.args.rm_sck:
if os.path.exists(ip):
os.unlink(ip)
srv.bind(ip)
if uds_gid != -1:
os.chown(ip, -1, uds_gid)
if uds_perm != -1:
os.chmod(ip, uds_perm)
else:
tf = "%s.%d" % (ip, os.getpid())
if os.path.exists(tf):
@ -405,6 +414,7 @@ class TcpSrv(object):
self.srv = srvs
self.bound = bound
self.seen_eps = list(set(self.seen_eps + bound))
self.nsrv = len(srvs)
self._distribute_netdevs()
@ -447,8 +457,6 @@ class TcpSrv(object):
self._distribute_netdevs()
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 not x.startswith(("unix:", "fd:"))]
nics = get_adapters(True)
@ -621,7 +629,7 @@ class TcpSrv(object):
pad = self.args.qrp
zoom = self.args.qrz
qrc = QrCode.encode_binary(btxt)
qrc = qrgen(btxt)
for zs in self.args.qr_file or []:
self._qr2file(qrc, zs)
@ -634,7 +642,7 @@ class TcpSrv(object):
except:
zoom = 1
qr = qrc.render(zoom, pad)
qr = qr2txt(qrc, zoom, pad)
if self.args.no_ansi:
return "{}\n{}".format(txt, qr)
@ -675,12 +683,12 @@ class TcpSrv(object):
if zoom not in (1, 2):
raise Exception("invalid zoom for qr.txt; must be 1 or 2")
with open(ap, "wb") as f:
f.write(qrc.render(zoom, pad).encode("utf-8"))
f.write(qr2txt(qrc, zoom, pad).encode("utf-8"))
elif ap.endswith(".svg"):
with open(ap, "wb") as f:
f.write(qr2svg(qrc, pad).encode("utf-8"))
else:
qrc.to_png(zoom, pad, self._h2i(bg), self._h2i(fg), ap)
qr2png(qrc, zoom, pad, self._h2i(bg), self._h2i(fg), ap)
def _h2i(self, hs):
try:

View file

@ -363,24 +363,29 @@ class Tftpd(object):
yeet("blocked write; folder not world-deletable: /%s" % (vpath,))
xbu = vfs.flags.get("xbu")
if xbu and not runhook(
self.nlog,
None,
self.hub.up2k,
"xbu.tftpd",
xbu,
ap,
vpath,
"",
"",
"",
0,
0,
"8.3.8.7",
time.time(),
"",
):
yeet("blocked by xbu server config: %r" % (vpath,))
if xbu:
hr = runhook(
self.nlog,
None,
self.hub.up2k,
"xbu.tftpd",
xbu,
ap,
vpath,
"",
"",
"",
0,
0,
"8.3.8.7",
time.time(),
"",
)
t = hr.get("rejectmsg") or ""
if t or not hr:
if not t:
t = "upload blocked by xbu server config: %r" % (vpath,)
yeet(t)
if not self.args.tftp_nols and bos.path.isdir(ap):
return self._ls(vpath, "", 0, True)

View file

@ -381,7 +381,7 @@ class ThumbSrv(object):
else:
ap_unpk = abspath
if not bos.path.exists(tpath):
if ap_unpk and not bos.path.exists(tpath):
tex = tpath.rsplit(".", 1)[-1]
want_mp3 = tex == "mp3"
want_opus = tex in ("opus", "owa", "caf")
@ -424,12 +424,14 @@ class ThumbSrv(object):
except:
pass
conv_ok = False
for fun in funs:
try:
if not png_ok and tpath.endswith(".png"):
raise Exception("png only allowed for waveforms")
fun(ap_unpk, ttpath, fmt, vn)
conv_ok = True
break
except Exception as ex:
msg = "%s could not create thumbnail of %r\n%s"
@ -451,16 +453,20 @@ class ThumbSrv(object):
except:
pass
if abspath != ap_unpk:
if abspath != ap_unpk and ap_unpk:
wunlink(self.log, ap_unpk, vn.flags)
try:
atomic_move(self.log, ttpath, tpath, vn.flags)
except Exception as ex:
if not os.path.exists(tpath):
if conv_ok and not os.path.exists(tpath):
t = "failed to move [%s] to [%s]: %r"
self.log(t % (ttpath, tpath, ex), 3)
pass
elif not conv_ok:
try:
open(tpath, "ab").close()
except:
pass
untemp = []
with self.mutex:
@ -682,7 +688,7 @@ class ThumbSrv(object):
return
c: Union[str, int] = "90"
t = "FFmpeg failed (probably a corrupt video file):\n"
t = "FFmpeg failed (probably a corrupt file):\n"
if (
(not self.args.th_ff_jpg or time.time() - int(self.args.th_ff_jpg) < 60)
and cmd[-1].lower().endswith(b".webp")

View file

@ -53,6 +53,11 @@ class U2idx(object):
self.log("your python does not have sqlite3; searching will be disabled")
return
if self.args.srch_icase:
self._open_db = self._open_db_icase
else:
self._open_db = self._open_db_std
assert sqlite3 # type: ignore # !rm
self.active_id = ""
@ -69,6 +74,16 @@ class U2idx(object):
def log(self, msg: str, c: Union[int, str] = 0) -> None:
self.log_func("u2idx", msg, c)
def _open_db_std(self, *args, **kwargs):
assert sqlite3 # type: ignore # !rm
kwargs["check_same_thread"] = False
return sqlite3.connect(*args, **kwargs)
def _open_db_icase(self, *args, **kwargs):
db = self._open_db_std(*args, **kwargs)
db.create_function("casefold", 1, lambda x: x.casefold() if x else x)
return db
def shutdown(self) -> None:
if not HAVE_SQLITE3:
return
@ -148,8 +163,7 @@ class U2idx(object):
uri = ""
try:
uri = "{}?mode=ro&nolock=1".format(Path(db_path).as_uri())
db = sqlite3.connect(uri, timeout=2, uri=True, check_same_thread=False)
cur = db.cursor()
cur = self._open_db(uri, timeout=2, uri=True).cursor()
cur.execute('pragma table_info("up")').fetchone()
self.log("ro: %r" % (db_path,))
except:
@ -160,7 +174,7 @@ class U2idx(object):
if not cur:
# on windows, this steals the write-lock from up2k.deferred_init --
# seen on win 10.0.17763.2686, py 3.10.4, sqlite 3.37.2
cur = sqlite3.connect(db_path, timeout=2, check_same_thread=False).cursor()
cur = self._open_db(db_path, timeout=2).cursor()
self.log("opened %r" % (db_path,))
self.cur[ptop] = cur
@ -173,6 +187,8 @@ class U2idx(object):
if not HAVE_SQLITE3:
return [], [], False
icase = self.args.srch_icase
q = ""
v: Union[str, int] = ""
va: list[Union[str, int]] = []
@ -180,6 +196,7 @@ class U2idx(object):
is_key = True
is_size = False
is_date = False
is_wark = False
field_end = "" # closing parenthesis or whatever
kw_key = ["(", ")", "and ", "or ", "not "]
kw_val = ["==", "=", "!=", ">", ">=", "<", "<=", "like "]
@ -198,6 +215,8 @@ class U2idx(object):
is_key = kw in kw_key
uq = uq[len(kw) :]
ok = True
if is_wark:
kw = "= "
q += kw
break
@ -232,9 +251,17 @@ class U2idx(object):
elif v == "path":
v = "trim(?||up.rd,'/')"
va.append("\nrd")
if icase:
v = "casefold(%s)" % (v,)
elif v == "name":
v = "up.fn"
if icase:
v = "casefold(%s)" % (v,)
elif v == "w":
v = "substr(up.w,1,16)"
is_wark = True
elif v == "tags" or ptn_mt.match(v):
have_mt = True
@ -247,7 +274,7 @@ class U2idx(object):
v = "exists(select 1 from mt where mt.w = mtw and " + vq
else:
raise Pebkac(400, "invalid key [{}]".format(v))
raise Pebkac(400, "invalid key %r" % (v,))
q += v + " "
continue
@ -276,6 +303,14 @@ class U2idx(object):
is_size = False
v = int(float(v) * 1024 * 1024)
elif is_wark:
is_wark = False
v = v.strip("*")
if len(v) > 16:
v = v[:16]
if len(v) < 16:
raise Pebkac(400, "w/filehash must be 16+ chars")
else:
if v.startswith("*"):
head = "'%'||"
@ -285,6 +320,12 @@ class U2idx(object):
tail = "||'%'"
v = v[:-1]
if icase and "casefold(" in q:
try:
v = unicode(v).casefold()
except:
v = unicode(v).lower()
q += " {}?{} ".format(head, tail)
va.append(v)
is_key = True
@ -319,7 +360,7 @@ class U2idx(object):
uname: str,
vols: list[VFS],
uq: str,
uv: list[Union[str, int]],
uv: Union[list[str], list[Union[str, int]]],
have_mt: bool,
sort: bool,
lim: int,

View file

@ -18,13 +18,15 @@ from copy import deepcopy
from queue import Queue
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, WINDOWS, E
from .__init__ import ANYWIN, MACOS, PY2, TYPE_CHECKING, WINDOWS, E
from .authsrv import LEELOO_DALLAS, SEESLOG, VFS, AuthSrv
from .bos import bos
from .cfg import vf_bmap, vf_cmap, vf_vmap
from .fsutil import Fstab
from .mtag import MParser, MTag
from .util import (
E_FS_CRIT,
E_FS_MEH,
HAVE_SQLITE3,
SYMTIME,
VF_CAREFUL,
@ -60,6 +62,7 @@ from .util import (
sfsenc,
spack,
statdir,
trystat_shutil_copy2,
ub64enc,
unhumanize,
vjoin,
@ -91,7 +94,7 @@ ICV_EXTS = set(zsg.split(","))
zsg = "3gp,asf,av1,avc,avi,flv,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,vob,webm,wmv"
VCV_EXTS = set(zsg.split(","))
zsg = "aif,aiff,alac,ape,flac,m4a,mp3,oga,ogg,opus,tak,tta,wav,wma,wv"
zsg = "aif,aiff,alac,ape,flac,m4a,mp3,oga,ogg,opus,tak,tta,wav,wma,wv,cbz,epub"
ACV_EXTS = set(zsg.split(","))
zsg = "nohash noidx xdev xvol"
@ -212,7 +215,7 @@ class Up2k(object):
t = "could not initialize sqlite3, will use in-memory registry only"
self.log(t, 3)
self.fstab = Fstab(self.log_func, self.args)
self.fstab = Fstab(self.log_func, self.args, True)
self.gen_fk = self._gen_fk if self.args.log_fk else gen_filekey
if self.args.hash_mt < 2:
@ -414,10 +417,11 @@ class Up2k(object):
ret: list[tuple[int, str, int, int, int]] = []
userset = set([(uname or "\n"), "*"])
e_d = {}
n = 1000
try:
for ptop, tab2 in self.registry.items():
cfg = self.flags.get(ptop, {}).get("u2abort", 1)
cfg = self.flags.get(ptop, e_d).get("u2abort", 1)
if not cfg:
continue
addr = (ip or "\n") if cfg in (1, 2) else ""
@ -1138,7 +1142,7 @@ class Up2k(object):
ft = "\033[0;32m{}{:.0}"
ff = "\033[0;35m{}{:.0}"
fv = "\033[0;36m{}:\033[90m{}"
zs = "ext_th_d html_head put_name2 mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot zipmax zipmaxn_v zipmaxs_v"
zs = "bcasechk du_iwho ext_th_d html_head html_head_d html_head_s put_name2 mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot zipmax zipmaxn_v zipmaxs_v"
fx = set(zs.split())
fd = vf_bmap()
fd.update(vf_cmap())
@ -1493,9 +1497,11 @@ class Up2k(object):
files: list[tuple[int, int, str]] = []
fat32 = True
cv = vcv = acv = ""
e_d = {}
th_cvd = self.args.th_coversd
th_cvds = self.args.th_coversd_set
scan_pr_s = self.args.scan_pr_s
assert self.pp and self.mem_cur # !rm
self.pp.msg = "a%d %s" % (self.pp.n, cdir)
@ -1693,7 +1699,7 @@ class Up2k(object):
t = "reindex %r => %r mtime(%s/%s) size(%s/%s)"
self.log(t % (top, rp, dts, lmod, dsz, sz))
self.db_rm(db.c, rd, fn, 0)
self.db_rm(db.c, e_d, rd, fn, 0)
tfa += 1
db.n += 1
in_db = []
@ -1708,7 +1714,7 @@ class Up2k(object):
if nohash or not sz:
wark = up2k_wark_from_metadata(self.salt, sz, lmod, rd, fn)
else:
if sz > 1024 * 1024:
if sz > 1024 * 1024 * scan_pr_s:
self.log("file: %r" % (abspath,))
try:
@ -1716,7 +1722,7 @@ class Up2k(object):
abspath, "a{}, ".format(self.pp.n)
)
except Exception as ex:
self.log("hash: %r @ %r" % (ex, abspath))
self._ex_hash(ex, abspath)
continue
if not hashes:
@ -1730,7 +1736,7 @@ class Up2k(object):
un = ""
# skip upload hooks by not providing vflags
self.db_add(db.c, {}, rd, fn, lmod, sz, "", "", wark, wark, "", un, ip, at)
self.db_add(db.c, e_d, rd, fn, lmod, sz, "", "", wark, wark, "", un, ip, at)
db.n += 1
db.nf += 1
tfa += 1
@ -1790,7 +1796,7 @@ class Up2k(object):
rm_files = [x for x in hits if x not in seen_files]
n_rm = len(rm_files)
for fn in rm_files:
self.db_rm(db.c, rd, fn, 0)
self.db_rm(db.c, e_d, rd, fn, 0)
if n_rm:
self.log("forgot {} deleted files".format(n_rm))
@ -1977,7 +1983,7 @@ class Up2k(object):
try:
hashes, _ = self._hashlist_from_file(abspath, pf)
except Exception as ex:
self.log("hash: %r @ %r" % (ex, abspath))
self._ex_hash(ex, abspath)
continue
if not hashes:
@ -2766,7 +2772,7 @@ class Up2k(object):
cur.close()
db.close()
shutil.copy2(fsenc(db_path), fsenc(bak))
trystat_shutil_copy2(self.log, fsenc(db_path), fsenc(bak))
return self._orz(db_path)
def _read_ver(self, cur: "sqlite3.Cursor") -> Optional[int]:
@ -3003,7 +3009,7 @@ class Up2k(object):
# check if filesystem supports sparse files;
# refuse out-of-order / multithreaded uploading if sprs False
sprs = self.fstab.get(pdir) != "ng"
sprs = self.fstab.get(pdir)[0] != "ng"
if True:
jcur = self.cur.get(ptop)
@ -3141,7 +3147,7 @@ class Up2k(object):
for cur, dp_dir, dp_fn in lost:
t = "forgetting desynced db entry: %r"
self.log(t % ("/" + vjoin(vjoin(vfs.vpath, dp_dir), dp_fn)))
self.db_rm(cur, dp_dir, dp_fn, cj["size"])
self.db_rm(cur, vfs.flags, dp_dir, dp_fn, cj["size"])
if c2 and c2 != cur:
c2.connection.commit()
@ -3294,8 +3300,11 @@ class Up2k(object):
job["at"],
"",
)
if not hr:
t = "upload blocked by xbu server config: %r" % (dst,)
t = hr.get("rejectmsg") or ""
if t or not hr:
if not t:
t = "upload blocked by xbu server config: %r"
t = t % (vp,)
self.log(t, 1)
raise Pebkac(403, t)
if hr.get("reloc"):
@ -3435,10 +3444,9 @@ class Up2k(object):
cur.connection.commit()
ap = djoin(job["ptop"], job["prel"], job["name"])
times = (int(time.time()), int(cj["lmod"]))
bos.utime(ap, times, False)
mt = bos.utime_c(self.log, ap, int(cj["lmod"]), False, True)
self.log("touched %r from %d to %d" % (ap, job["lmod"], cj["lmod"]))
self.log("touched %r from %d to %d" % (ap, job["lmod"], mt))
except Exception as ex:
self.log("umod failed, %r" % (ex,), 3)
@ -3472,7 +3480,7 @@ class Up2k(object):
vrel = vjoin(job["prel"], fname)
xlink = bool(vf.get("xlink"))
cur, wark, _, _, _, _, _ = self._find_from_vpath(ptop, vrel)
self._forget_file(ptop, vrel, cur, wark, True, st.st_size, xlink)
self._forget_file(ptop, vrel, vf, cur, wark, True, st.st_size, xlink)
except Exception as ex:
self.log("skipping replace-relink: %r" % (ex,))
finally:
@ -3590,11 +3598,10 @@ class Up2k(object):
t = "BUG: no valid sources to link from! orig(%r) fsrc(%r) link(%r)"
self.log(t, 1)
raise Exception(t % (src, fsrc, dst))
shutil.copy2(fsenc(csrc), fsenc(dst))
trystat_shutil_copy2(self.log, fsenc(csrc), fsenc(dst))
if lmod and (not linked or SYMTIME):
times = (int(time.time()), int(lmod))
bos.utime(dst, times, False)
bos.utime_c(self.log, dst, int(lmod), False)
def handle_chunks(
self, ptop: str, wark: str, chashes: list[str]
@ -3677,14 +3684,15 @@ class Up2k(object):
t = t.format(job["name"], nchunks[0][0], coffsets[0][0], cur_sz)
raise Pebkac(400, t)
job["busy"][chash] = 1
for chash in chashes:
job["busy"][chash] = 1
job["poke"] = time.time()
return chashes, chunksize, coffsets, path, job["lmod"], job["size"], job["sprs"]
def fast_confirm_chunks(
self, ptop: str, wark: str, chashes: list[str]
self, ptop: str, wark: str, chashes: list[str], locked: list[str]
) -> tuple[int, str]:
if not self.mutex.acquire(False):
return -1, ""
@ -3692,7 +3700,7 @@ class Up2k(object):
self.mutex.release()
return -1, ""
try:
return self._confirm_chunks(ptop, wark, chashes, chashes)
return self._confirm_chunks(ptop, wark, chashes, locked, False)
finally:
self.reg_mutex.release()
self.mutex.release()
@ -3701,10 +3709,10 @@ class Up2k(object):
self, ptop: str, wark: str, written: list[str], locked: list[str]
) -> tuple[int, str]:
with self.mutex, self.reg_mutex:
return self._confirm_chunks(ptop, wark, written, locked)
return self._confirm_chunks(ptop, wark, written, locked, True)
def _confirm_chunks(
self, ptop: str, wark: str, written: list[str], locked: list[str]
self, ptop: str, wark: str, written: list[str], locked: list[str], final: bool
) -> tuple[int, str]:
if True:
self.db_act = self.vol_act[ptop] = time.time()
@ -3716,14 +3724,16 @@ class Up2k(object):
except Exception as ex:
return -2, "confirm_chunk, wark(%r)" % (ex,) # type: ignore
for chash in locked:
for chash in locked if final else written:
job["busy"].pop(chash, None)
try:
for chash in written:
job["need"].remove(chash)
except Exception as ex:
# dead tcp connections can get here by timeout (OK)
for zs in locked:
if job["busy"].pop(zs, None):
self.log("panic-unlock wark(%s) chunk(%s)" % (wark, zs), 1)
return -2, "confirm_chunk, chash(%s) %r" % (chash, ex) # type: ignore
ret = len(job["need"])
@ -3767,10 +3777,8 @@ class Up2k(object):
times = (int(time.time()), int(job["lmod"]))
t = "no more chunks, setting times %s (%d) on %r"
self.log(t % (times, bos.path.getsize(dst), dst))
try:
bos.utime(dst, times)
except:
self.log("failed to utime (%r, %s)" % (dst, times))
bos.utime_c(self.log, dst, times[1], False)
# the above logmsg (and associated logic) is retained due to unforget.py
zs = "prel name lmod size ptop vtop wark dwrk host user addr"
z2 = [job[x] for x in zs.split()]
@ -3889,7 +3897,9 @@ class Up2k(object):
return True
def db_rm(self, db: "sqlite3.Cursor", rd: str, fn: str, sz: int) -> None:
def db_rm(
self, db: "sqlite3.Cursor", vflags: dict[str, Any], rd: str, fn: str, sz: int
) -> None:
sql = "delete from up where rd = ? and fn = ?"
try:
r = db.execute(sql, (rd, fn))
@ -3897,9 +3907,22 @@ class Up2k(object):
assert self.mem_cur # !rm
r = db.execute(sql, s3enc(self.mem_cur, rd, fn))
if r.rowcount:
self.volsize[db] -= sz
self.volnfiles[db] -= 1
if not r.rowcount:
return
self.volsize[db] -= sz
self.volnfiles[db] -= 1
if "nodirsz" not in vflags:
try:
q = "update ds set nf=nf-1, sz=sz-? where rd=?"
while True:
db.execute(q, (sz, rd))
if not rd:
break
rd = rd.rsplit("/", 1)[0] if "/" in rd else ""
except:
pass
def db_add(
self,
@ -3920,7 +3943,7 @@ class Up2k(object):
skip_xau: bool = False,
) -> None:
"""mutex(main) me"""
self.db_rm(db, rd, fn, sz)
self.db_rm(db, vflags, rd, fn, sz)
if not ip:
db_ip = ""
@ -3961,8 +3984,11 @@ class Up2k(object):
at or time.time(),
"",
)
if not hr:
t = "upload blocked by xau server config"
t = hr.get("rejectmsg") or ""
if t or not hr:
if not t:
t = "upload blocked by xau server config: %r"
t = t % (djoin(vtop, rd, fn),)
self.log(t, 1)
wunlink(self.log, dst, vflags)
self.registry[ptop].pop(wark, None)
@ -4132,6 +4158,9 @@ class Up2k(object):
except:
raise Pebkac(400, "file not found on disk (already deleted?)")
if "bcasechk" in vn.flags and not vn.casechk(rem, False):
raise Pebkac(400, "file does not exist case-sensitively")
scandir = not self.args.no_scandir
if is_dir:
# note: deletion inside shares would require a rewrite here;
@ -4203,7 +4232,7 @@ class Up2k(object):
xlink = bool(dbv.flags.get("xlink"))
cur, wark, _, _, _, _, _ = self._find_from_vpath(ptop, volpath)
self._forget_file(
ptop, volpath, cur, wark, True, st.st_size, xlink
ptop, volpath, dbv.flags, cur, wark, True, st.st_size, xlink
)
finally:
if cur:
@ -4256,6 +4285,9 @@ class Up2k(object):
self.db_act = self.vol_act[svn_dbv.realpath] = time.time()
st = bos.stat(sabs)
if "bcasechk" in svn.flags and not svn.casechk(srem, False):
raise Pebkac(400, "file does not exist case-sensitively")
if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
with self.mutex:
try:
@ -4414,7 +4446,7 @@ class Up2k(object):
b1, b2 = fsenc(sabs), fsenc(dabs)
is_link = os.path.islink(b1) # due to _relink
try:
shutil.copy2(b1, b2)
trystat_shutil_copy2(self.log, b1, b2)
except:
try:
wunlink(self.log, dabs, dvn.flags)
@ -4474,6 +4506,9 @@ class Up2k(object):
raise Pebkac(400, "mv: cannot move a mountpoint")
st = bos.lstat(sabs)
if "bcasechk" in svn.flags and not svn.casechk(srem, False):
raise Pebkac(400, "file does not exist case-sensitively")
if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
with self.mutex:
try:
@ -4661,7 +4696,14 @@ class Up2k(object):
with self.reg_mutex:
has_dupes = self._forget_file(
svn.realpath, srem, c1, w, is_xvol, fsize_ or fsize, xlink
svn.realpath,
srem,
svn.flags,
c1,
w,
is_xvol,
fsize_ or fsize,
xlink,
)
if not is_xvol:
@ -4713,7 +4755,7 @@ class Up2k(object):
b1, b2 = fsenc(sabs), fsenc(dabs)
is_link = os.path.islink(b1) # due to _relink
try:
shutil.copy2(b1, b2)
trystat_shutil_copy2(self.log, b1, b2)
except:
try:
wunlink(self.log, dabs, dvn.flags)
@ -4806,6 +4848,7 @@ class Up2k(object):
self,
ptop: str,
vrem: str,
vflags: dict[str, Any],
cur: Optional["sqlite3.Cursor"],
wark: Optional[str],
drop_tags: bool,
@ -4830,7 +4873,7 @@ class Up2k(object):
q = "delete from mt where w=?"
cur.execute(q, (wark[:16],))
self.db_rm(cur, srd, sfn, sz)
self.db_rm(cur, vflags, srd, sfn, sz)
reg = self.registry.get(ptop)
if reg:
@ -4919,7 +4962,10 @@ class Up2k(object):
mt = bos.path.getmtime(slabs, False)
flags = self.flags.get(ptop) or {}
atomic_move(self.log, sabs, slabs, flags)
bos.utime(slabs, (int(time.time()), int(mt)), False)
try:
bos.utime(slabs, (int(time.time()), int(mt)), False)
except:
self.log("relink: failed to utime(%r, %s)" % (slabs, mt), 3)
self._symlink(slabs, sabs, flags, False, is_mv=True)
full[slabs] = (ptop, rem)
sabs = slabs
@ -4992,7 +5038,7 @@ class Up2k(object):
for k in cj["hash"]:
if not self.r_hash.match(k):
raise Pebkac(
400, "at least one hash is not according to spec: {}".format(k)
400, "at least one hash is not according to spec: %r" % (k,)
)
# try to use client-provided timestamp, don't care if it fails somehow
@ -5049,6 +5095,16 @@ class Up2k(object):
return ret, st
def _ex_hash(self, ex: Exception, ap: str) -> None:
eno = getattr(ex, "errno", 0)
if eno in E_FS_MEH:
return self.log("hashing failed; %r @ %r" % (ex, ap))
if eno not in E_FS_CRIT:
return self.log("hashing failed; %r @ %r\n%s" % (ex, ap, min_ex()), 3)
t = "hashing failed; %r @ %r\n%s\nWARNING: This MAY indicate a serious issue with your harddisk or filesystem! Please investigate %sOS-logs\n"
t2 = "" if ANYWIN or MACOS else "dmesg and "
return self.log(t % (ex, ap, min_ex(), t2), 1)
def _new_upload(self, job: dict[str, Any], vfs: VFS, depth: int) -> dict[str, str]:
pdir = djoin(job["ptop"], job["prel"])
if not job["size"]:
@ -5082,8 +5138,10 @@ class Up2k(object):
job["t0"],
"",
)
if not hr:
t = "upload blocked by xbu server config: %r" % (vp_chk,)
t = hr.get("rejectmsg") or ""
if t or not hr:
if not t:
t = "upload blocked by xbu server config: %r" % (vp_chk,)
self.log(t, 1)
raise Pebkac(403, t)
if hr.get("reloc"):
@ -5145,7 +5203,7 @@ class Up2k(object):
sprs = False
if not ANYWIN and sprs and sz > 1024 * 1024:
fs = self.fstab.get(pdir)
fs, mnt = self.fstab.get(pdir)
if fs == "ok":
pass
elif "nosparse" in vf:
@ -5231,17 +5289,21 @@ class Up2k(object):
self.log("\n".join([t] + vis))
for job in rm:
del reg[job["wark"]]
rsv_cleared = False
try:
# remove the filename reservation
path = djoin(job["ptop"], job["prel"], job["name"])
if bos.path.getsize(path) == 0:
bos.unlink(path)
rsv_cleared = True
except:
pass
try:
if len(job["hash"]) == len(job["need"]):
# PARTIAL is empty, delete that too
if len(job["hash"]) == len(job["need"]) or (
rsv_cleared and "rm_partial" in self.flags[job["ptop"]]
):
# PARTIAL is empty (hash==need) or --rm-partial, so delete that too
path = djoin(job["ptop"], job["prel"], job["tnam"])
bos.unlink(path)
except:

View file

@ -55,7 +55,6 @@ from .__init__ import (
unicode,
)
from .__version__ import S_BUILD_DT, S_VERSION
from .stolen import surrogateescape
try:
from datetime import datetime, timezone
@ -81,6 +80,9 @@ except:
if PY2:
range = xrange # type: ignore
from .stolen import surrogateescape
surrogateescape.register_surrogateescape()
if sys.version_info >= (3, 7) or (
@ -111,6 +113,8 @@ E_ADDR_NOT_AVAIL = _ens("EADDRNOTAVAIL WSAEADDRNOTAVAIL")
E_ADDR_IN_USE = _ens("EADDRINUSE WSAEADDRINUSE")
E_ACCESS = _ens("EACCES WSAEACCES")
E_UNREACH = _ens("EHOSTUNREACH WSAEHOSTUNREACH ENETUNREACH WSAENETUNREACH")
E_FS_MEH = _ens("EPERM EACCES ENOENT ENOTCAPABLE")
E_FS_CRIT = _ens("EIO EFAULT EUCLEAN ENOTBLK")
IP6ALL = "0:0:0:0:0:0:0:0"
IP6_LL = ("fe8", "fe9", "fea", "feb")
@ -134,6 +138,25 @@ try:
except:
pass
try:
if os.environ.get("PRTY_NO_IFADDR"):
raise Exception()
try:
if os.getenv("PRTY_SYS_ALL") or os.getenv("PRTY_SYS_IFADDR"):
raise ImportError()
from .stolen.ifaddr import get_adapters
except ImportError:
from ifaddr import get_adapters
HAVE_IFADDR = True
except:
HAVE_IFADDR = False
def get_adapters(include_unconfigured=False):
return []
try:
if os.environ.get("PRTY_NO_SQLITE"):
raise Exception()
@ -171,6 +194,11 @@ try:
except:
pass
if os.getenv("PRTY_MODSPEC"):
from inspect import getsourcefile
print("PRTY_MODSPEC: ifaddr:", getsourcefile(get_adapters))
if True: # pylint: disable=using-constant-test
import types
from collections.abc import Callable, Iterable
@ -267,7 +295,6 @@ RE_MEMAVAIL = re.compile("^MemAvailable:.* kB")
BOS_SEP = ("%s" % (os.sep,)).encode("ascii")
surrogateescape.register_surrogateescape()
if WINDOWS and PY2:
FS_ENCODING = "utf-8"
else:
@ -376,6 +403,13 @@ DAV_ALLPROP_L = [
DAV_ALLPROPS = set(DAV_ALLPROP_L)
FAVICON_MIMES = {
"gif": "image/gif",
"png": "image/png",
"svg": "image/svg+xml",
}
MIMES = {
"opus": "audio/ogg; codecs=opus",
"owa": "audio/webm; codecs=opus",
@ -430,7 +464,7 @@ EXTS["vnd.mozilla.apng"] = "png"
MAGIC_MAP = {"jpeg": "jpg"}
DEF_EXP = "self.ip self.ua self.uname self.host cfg.name cfg.logout vf.scan vf.thsize hdr.cf_ipcountry srv.itime srv.htime"
DEF_EXP = "self.ip self.ua self.uname self.host cfg.name cfg.logout vf.scan vf.thsize hdr.cf-ipcountry srv.itime srv.htime"
DEF_MTE = ".files,circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash,vhash"
@ -546,6 +580,8 @@ def py_desc() -> str:
ofs = py_ver.find(".final.")
if ofs > 0:
py_ver = py_ver[:ofs]
if "free-threading" in sys.version:
py_ver += "t"
host_os = platform.system()
compiler = platform.python_compiler().split("http")[0]
@ -656,6 +692,9 @@ def read_utf8(log: Optional["NamedLogger"], ap: Union[str, bytes], strict: bool)
with open(ap, "rb") as f:
buf = f.read()
if buf.startswith(b"\xef\xbb\xbf"):
buf = buf[3:]
try:
return buf.decode("utf-8", "strict")
except UnicodeDecodeError as ex:
@ -1121,16 +1160,18 @@ class ProgressPrinter(threading.Thread):
sigblock()
tp = 0
msg = None
no_stdout = self.args.q
slp_pr = self.args.scan_pr_r
slp_ps = min(slp_pr, self.args.scan_st_r)
no_stdout = self.args.q or slp_pr == slp_ps
fmt = " {}\033[K\r" if VT100 else " {} $\r"
while not self.end:
time.sleep(0.1)
time.sleep(slp_ps)
if msg == self.msg or self.end:
continue
msg = self.msg
now = time.time()
if msg and now - tp > 10:
if msg and now - tp >= slp_pr:
tp = now
self.log("progress: %r" % (msg,), 6)
@ -1188,21 +1229,21 @@ class MTHash(object):
for nch in range(nchunks):
self.work_q.put(nch)
ex = ""
ex: Optional[Exception] = None
for nch in range(nchunks):
qe = self.done_q.get()
try:
nch, dig, ofs, csz = qe
chunks[nch] = (dig, ofs, csz)
except:
ex = ex or str(qe)
ex = ex or qe # type: ignore
if pp:
mb = (fsz - nch * chunksz) // (1024 * 1024)
pp.msg = prefix + str(mb) + suffix
if ex:
raise Exception(ex)
raise ex
ret = []
for n in range(nchunks):
@ -1219,7 +1260,7 @@ class MTHash(object):
try:
v = self.hash_at(ofs)
except Exception as ex:
v = str(ex) # type: ignore
v = ex # type: ignore
self.done_q.put(v)
@ -1577,10 +1618,12 @@ def vol_san(vols: list["VFS"], txt: bytes) -> bytes:
bvp = vol.vpath.encode("utf-8")
bvph = b"$hist(/" + bvp + b")"
txt = txt.replace(bap, bvp)
txt = txt.replace(bhp, bvph)
txt = txt.replace(bap.replace(b"\\", b"\\\\"), bvp)
txt = txt.replace(bhp.replace(b"\\", b"\\\\"), bvph)
if bap:
txt = txt.replace(bap, bvp)
txt = txt.replace(bap.replace(b"\\", b"\\\\"), bvp)
if bhp:
txt = txt.replace(bhp, bvph)
txt = txt.replace(bhp.replace(b"\\", b"\\\\"), bvph)
if vol.histpath != vol.dbpath:
bdp = vol.dbpath.encode("utf-8")
@ -1759,12 +1802,12 @@ class MultipartParser(object):
continue
if m.group(1).lower() != "form-data":
raise Pebkac(400, "not form-data: {}".format(ln))
raise Pebkac(400, "not form-data: %r" % (ln,))
try:
field = self.re_cdisp_field.match(ln).group(1) # type: ignore
except:
raise Pebkac(400, "missing field name: {}".format(ln))
raise Pebkac(400, "missing field name: %r" % (ln,))
try:
fn = self.re_cdisp_file.match(ln).group(1) # type: ignore
@ -1936,7 +1979,7 @@ def get_boundary(headers: dict[str, str]) -> str:
ct = headers["content-type"]
m = re.match(ptn, ct, re.IGNORECASE)
if not m:
raise Pebkac(400, "invalid content-type for a multipart post: {}".format(ct))
raise Pebkac(400, "invalid content-type for a multipart post: %r" % (ct,))
return m.group(2)
@ -2108,6 +2151,7 @@ def humansize(sz: float, terse: bool = False) -> str:
sz /= 1024.0
assert unit # type: ignore # !rm
if terse:
return "%s%s" % (str(sz)[:4].rstrip("."), unit[:1])
else:
@ -2260,14 +2304,14 @@ def odfusion(
ret = base.copy()
if oth.startswith("+"):
for k in words1:
ret[k] = True
ret[k] = True # type: ignore
elif oth[:1] in ("-", "/"):
for k in words1:
ret.pop(k, None)
ret.pop(k, None) # type: ignore
else:
ret = ODict.fromkeys(words0, True)
return ret
return ret # type: ignore
def ipnorm(ip: str) -> str:
@ -2639,6 +2683,24 @@ def set_fperms(f: Union[typing.BinaryIO, typing.IO[Any]], vf: dict[str, Any]) ->
os.fchown(fno, vf["uid"], vf["gid"])
def trystat_shutil_copy2(log: "NamedLogger", src: bytes, dst: bytes) -> bytes:
try:
return shutil.copy2(src, dst)
except:
# ignore failed mtime on linux+ntfs; for example:
# shutil.py:437 <copy2>: copystat(src, dst, follow_symlinks=follow_symlinks)
# shutil.py:376 <copystat>: lookup("utime")(dst, ns=(st.st_atime_ns, st.st_mtime_ns),
# [PermissionError] [Errno 1] Operation not permitted, '/windows/_videos'
_, _, tb = sys.exc_info()
for _, _, fun, _ in traceback.extract_tb(tb):
if fun == "copystat":
if log:
t = "warning: failed to retain some file attributes (timestamp and/or permissions) during copy from %r to %r:\n%s"
log(t % (src, dst, min_ex()), 3)
return dst # close enough
raise
def _fs_mvrm(
log: "NamedLogger", src: str, dst: str, atomic: bool, flags: dict[str, Any]
) -> bool:
@ -2677,7 +2739,7 @@ def _fs_mvrm(
t = "something appeared at dst; aborting rename %r ==> %r"
log(t % (src, dst), 1)
return False
osfun(*args)
osfun(*args) # type: ignore
if attempt:
now = time.time()
t = "%sd in %.2f sec, attempt %d: %r"
@ -2727,7 +2789,7 @@ def atomic_move(log: "NamedLogger", src: str, dst: str, flags: dict[str, Any]) -
os.unlink(bdst)
except:
pass
shutil.move(bsrc, bdst)
shutil.move(bsrc, bdst) # type: ignore
def wunlink(log: "NamedLogger", abspath: str, flags: dict[str, Any]) -> bool:
@ -2770,6 +2832,8 @@ def get_df(abspath: str, prune: bool) -> tuple[int, int, str]:
if not ANYWIN and not MACOS:
def siocoutq(sck: socket.socket) -> int:
assert fcntl # type: ignore # !rm
assert termios # type: ignore # !rm
# SIOCOUTQ^sockios.h == TIOCOUTQ^ioctl.h
try:
zb = fcntl.ioctl(sck.fileno(), termios.TIOCOUTQ, b"AAAA")
@ -2889,8 +2953,6 @@ def read_socket_chunked(
def list_ips() -> list[str]:
from .stolen.ifaddr import get_adapters
ret: set[str] = set()
for nic in get_adapters():
for ipo in nic.ips:
@ -3133,7 +3195,7 @@ def sendfile_kern(
try:
req = min(0x2000000, upper - ofs) # 32 MiB
if use_poll:
poll.poll(10000)
poll.poll(10000) # type: ignore
else:
select.select([], [out_fd], [], 10)
n = os.sendfile(out_fd, in_fd, ofs, req)
@ -3189,8 +3251,9 @@ def statdir(
else:
src = "listdir"
fun: Any = os.lstat if lstat else os.stat
btop_ = os.path.join(btop, b"")
for name in os.listdir(btop):
abspath = os.path.join(btop, name)
abspath = btop_ + name
try:
yield (fsdec(name), fun(abspath))
except Exception as ex:
@ -3229,7 +3292,9 @@ def rmdirs(
stats = statdir(logger, scandir, lstat, top, False)
dirs = [x[0] for x in stats if stat.S_ISDIR(x[1].st_mode)]
dirs = [os.path.join(top, x) for x in dirs]
if dirs:
top_ = os.path.join(top, "")
dirs = [top_ + x for x in dirs]
ok = []
ng = []
for d in reversed(dirs):
@ -3420,7 +3485,9 @@ NICEB = NICES.encode("utf-8")
def runcmd(
argv: Union[list[bytes], list[str]], timeout: Optional[float] = None, **ka: Any
argv: Union[list[bytes], list[str], list["LiteralString"]],
timeout: Optional[float] = None,
**ka: Any
) -> tuple[int, str, str]:
isbytes = isinstance(argv[0], (bytes, bytearray))
oom = ka.pop("oom", 0) # 0..1000
@ -3439,19 +3506,19 @@ def runcmd(
if ANYWIN:
if isbytes:
if argv[0] in CMD_EXEB:
argv[0] += b".exe"
argv[0] += b".exe" # type: ignore
else:
if argv[0] in CMD_EXES:
argv[0] += ".exe"
argv[0] += ".exe" # type: ignore
if ka.pop("nice", None):
if WINDOWS:
ka["creationflags"] = 0x4000
elif NICEB:
if isbytes:
argv = [NICEB] + argv
argv = [NICEB] + argv # type: ignore
else:
argv = [NICES] + argv
argv = [NICES] + argv # type: ignore
p = sp.Popen(argv, stdout=cout, stderr=cerr, **ka)
@ -3463,10 +3530,10 @@ def runcmd(
pass
if not timeout or PY2:
bout, berr = p.communicate(sin)
bout, berr = p.communicate(sin) # type: ignore
else:
try:
bout, berr = p.communicate(sin, timeout=timeout)
bout, berr = p.communicate(sin, timeout=timeout) # type: ignore
except sp.TimeoutExpired:
if kill == "n":
return -18, "", "" # SIGCONT; leave it be
@ -3476,7 +3543,7 @@ def runcmd(
killtree(p.pid)
try:
bout, berr = p.communicate(timeout=1)
bout, berr = p.communicate(timeout=1) # type: ignore
except:
bout = b""
berr = b""
@ -3562,11 +3629,13 @@ def retchk(
def _parsehook(
log: Optional["NamedLogger"], cmd: str
) -> tuple[str, bool, bool, bool, float, dict[str, Any], list[str]]:
) -> tuple[str, bool, bool, bool, bool, bool, float, dict[str, Any], list[str]]:
areq = ""
chk = False
fork = False
jtxt = False
imp = False
sin = False
wait = 0.0
tout = 0.0
kill = "t"
@ -3580,6 +3649,10 @@ def _parsehook(
fork = True
elif arg == "j":
jtxt = True
elif arg == "I":
imp = True
elif arg == "s":
sin = True
elif arg.startswith("w"):
wait = float(arg[1:])
elif arg.startswith("t"):
@ -3624,7 +3697,7 @@ def _parsehook(
argv[0] = os.path.expandvars(os.path.expanduser(argv[0]))
return areq, chk, fork, jtxt, wait, sp_ka, argv
return areq, chk, imp, fork, sin, jtxt, wait, sp_ka, argv
def runihook(
@ -3634,7 +3707,7 @@ def runihook(
vol: "VFS",
ups: list[tuple[str, int, int, str, str, str, int, str]],
) -> bool:
_, chk, fork, jtxt, wait, sp_ka, acmd = _parsehook(log, cmd)
_, chk, _, fork, _, jtxt, wait, sp_ka, acmd = _parsehook(log, cmd)
bcmd = [sfsenc(x) for x in acmd]
if acmd[0].endswith(".py"):
bcmd = [sfsenc(pybin)] + bcmd
@ -3813,7 +3886,7 @@ def _runhook(
txt: str,
) -> dict[str, Any]:
ret = {"rc": 0}
areq, chk, fork, jtxt, wait, sp_ka, acmd = _parsehook(log, cmd)
areq, chk, imp, fork, sin, jtxt, wait, sp_ka, acmd = _parsehook(log, cmd)
if areq:
for ch in areq:
if ch not in perms:
@ -3821,7 +3894,7 @@ def _runhook(
if log:
log(t % (uname, cmd, areq, perms))
return ret # fallthrough to next hook
if jtxt:
if imp or jtxt:
ja = {
"ap": ap,
"vp": vp,
@ -3835,6 +3908,10 @@ def _runhook(
"src": src,
"txt": txt,
}
if imp:
ja["log"] = log
mod = loadpy(acmd[0], False)
return mod.main(ja)
arg = json.dumps(ja)
else:
arg = txt or ap
@ -3845,7 +3922,11 @@ def _runhook(
raise Exception("zmq says %d" % (zi,))
return {"rc": 0, "stdout": zs}
acmd += [arg]
if sin:
sp_ka["sin"] = (arg + "\n").encode("utf-8", "replace")
else:
acmd += [arg]
if acmd[0].endswith(".py"):
acmd = [pybin] + acmd
@ -3899,7 +3980,7 @@ def runhook(
txt: str,
) -> dict[str, Any]:
assert broker or up2k # !rm
args = (broker or up2k).args
args = (broker or up2k).args # type: ignore
verbose = args.hook_v
vp = vp.replace("\\", "/")
ret = {"rc": 0}
@ -3917,6 +3998,7 @@ def runhook(
if broker:
broker.say("up2k.hook_fx", k, v, vp)
else:
assert up2k # !rm
up2k.fx_backlog.append((k, v, vp))
elif k == "reloc" and v:
# idk, just take the last one ig
@ -3929,7 +4011,7 @@ def runhook(
else:
ret[k] = v
except Exception as ex:
(log or print)("hook: {}".format(ex))
(log or print)("hook: %r, %s" % (ex, ex))
if ",c," in "," + cmd:
return {}
break
@ -4076,6 +4158,8 @@ def termsize() -> tuple[int, int]:
env = os.environ
def ioctl_GWINSZ(fd: int) -> Optional[tuple[int, int]]:
assert fcntl # type: ignore # !rm
assert termios # type: ignore # !rm
try:
cr = sunpack(b"hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, b"AAAA"))
return cr[::-1]
@ -4205,7 +4289,7 @@ def _pkg_resource_exists(pkg: str, name: str) -> bool:
def stat_resource(E: EnvParams, name: str):
path = os.path.join(E.mod, name)
path = E.mod_ + name
if os.path.exists(path):
return os.stat(fsenc(path))
return None
@ -4252,7 +4336,7 @@ def _has_resource(name: str):
def has_resource(E: EnvParams, name: str):
return _has_resource(name) or os.path.exists(os.path.join(E.mod, name))
return _has_resource(name) or os.path.exists(E.mod_ + name)
def load_resource(E: EnvParams, name: str, mode="rb") -> IO[bytes]:
@ -4277,7 +4361,7 @@ def load_resource(E: EnvParams, name: str, mode="rb") -> IO[bytes]:
stream = codecs.getreader(enc)(stream)
return stream
ap = os.path.join(E.mod, name)
ap = E.mod_ + name
if PY2:
return codecs.open(ap, "r", encoding=enc) # type: ignore

View file

@ -22,8 +22,9 @@ window.baguetteBox = (function () {
afterHide: null,
duringHide: null,
onChange: null,
readDirRtl: false,
},
overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnFull, btnVmode, btnClose,
overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnFull, btnVmode, btnReadDir, btnClose,
currentGallery = [],
currentIndex = 0,
isOverlayVisible = false,
@ -44,6 +45,7 @@ window.baguetteBox = (function () {
loopA = null,
loopB = null,
url_ts = null,
un_pp = 0,
resume_mp = false;
var onFSC = function (e) {
@ -72,10 +74,10 @@ window.baguetteBox = (function () {
var touchEvent = e.touches[0] || e.changedTouches[0];
if (touchEvent.pageX - touch.startX > 40) {
touchFlag = true;
showPreviousImage();
showLeftImage();
} else if (touchEvent.pageX - touch.startX < -40) {
touchFlag = true;
showNextImage();
showRightImage();
} else if (touch.startY - touchEvent.pageY > 100) {
hideOverlay();
}
@ -113,9 +115,9 @@ window.baguetteBox = (function () {
scrollTimer = Date.now();
if (d > 0)
showNextImage();
showNextImageIgnoreReadDir();
else
showPreviousImage();
showPreviousImageIgnoreReadDir();
};
var trapFocusInsideOverlay = function (e) {
@ -211,6 +213,7 @@ window.baguetteBox = (function () {
'<div id="bbox-btns">' +
'<button id="bbox-help" type="button">?</button>' +
'<button id="bbox-anim" type="button" tt="a">-</button>' +
'<button id="bbox-readdir" type="button" tt="a">ltr</button>' +
'<button id="bbox-rotl" type="button">↶</button>' +
'<button id="bbox-rotr" type="button">↷</button>' +
'<button id="bbox-tsel" type="button">sel</button>' +
@ -228,6 +231,7 @@ window.baguetteBox = (function () {
btnNext = ebi('bbox-next');
btnHelp = ebi('bbox-help');
btnAnim = ebi('bbox-anim');
btnReadDir = ebi('bbox-readdir');
btnRotL = ebi('bbox-rotl');
btnRotR = ebi('bbox-rotr');
btnSel = ebi('bbox-tsel');
@ -252,7 +256,7 @@ window.baguetteBox = (function () {
['S', 'toggle file selection'],
['space, P, K', 'video: play / pause'],
['U', 'video: seek 10sec back'],
['P', 'video: seek 10sec ahead'],
['O', 'video: seek 10sec ahead'],
['0..9', 'video: seek 0%..90%'],
['M', 'video: toggle mute'],
['V', 'video: toggle loop'],
@ -278,58 +282,68 @@ window.baguetteBox = (function () {
if (modal.busy)
return;
if (e.key == '?')
return halp();
if (anymod(e, true))
return;
var k = (e.code || e.key) + '', v = vid(), pos = -1;
var k = (e.key || e.code) + '', v = vid();
if (k == "BracketLeft")
if (k.startsWith('Key'))
k = k.slice(3);
else if (k.startsWith('Digit'))
k = k.slice(5);
var kl = k.toLowerCase();
if (k == '?')
return halp();
if (k == "[" || k == "BracketLeft")
setloop(1);
else if (k == "BracketRight")
else if (k == "]" || k == "BracketRight")
setloop(2);
else if (e.shiftKey && k != "KeyR" && k != "R")
else if (e.shiftKey && kl != "r")
return;
else if (k == "ArrowLeft" || k == "KeyJ" || k == "Left" || k == "j")
showPreviousImage();
else if (k == "ArrowRight" || k == "KeyL" || k == "Right" || k == "l")
showNextImage();
else if (k == "ArrowLeft" || k == "Left" || kl == "j")
showLeftImage();
else if (k == "ArrowRight" || k == "Right" || kl == "l")
showRightImage();
else if (k == "Escape" || k == "Esc")
hideOverlay();
else if (k == "Home")
showFirstImage(e);
else if (k == "End")
showLastImage(e);
else if (k == "Space" || k == "KeyP" || k == "KeyK")
else if (k == "Space" || k == "Spacebar" || kl == " " || kl == "p" || kl == "k")
playpause();
else if (k == "KeyU" || k == "KeyO")
relseek(k == "KeyU" ? -10 : 10);
else if (k.indexOf('Digit') === 0 && v)
v.currentTime = v.duration * parseInt(k.slice(-1)) * 0.1;
else if (k == "KeyM" && v) {
else if (kl == "u" || kl == "o")
relseek(kl == "u" ? -10 : 10);
else if (v && /^[0-9]$/.test(k))
v.currentTime = v.duration * parseInt(k) * 0.1;
else if (kl == "m" && v) {
v.muted = vmute = !vmute;
mp_ctl();
}
else if (k == "KeyV" && v) {
else if (kl == "v" && v) {
vloop = !vloop;
vnext = vnext && !vloop;
setVmode();
}
else if (k == "KeyC" && v) {
else if (kl == "c" && v) {
vnext = !vnext;
vloop = vloop && !vnext;
setVmode();
}
else if (k == "KeyF")
else if (kl == "f")
tglfull();
else if (k == "KeyS" || k == "s")
else if (kl == "s")
tglsel();
else if (k == "KeyR" || k == "r" || k == "R")
else if (kl == "r")
rotn(e.shiftKey ? -1 : 1);
else if (k == "KeyY")
else if (kl == "y")
dlpic();
else
return;
return ev(e);
}
function anim() {
@ -342,6 +356,21 @@ window.baguetteBox = (function () {
tt.show.call(this);
}
function toggleReadDir() {
var o = options,
next = options.readDirRtl ? "ltr" : "rtl";
swrite('greaddir', next);
slider.className = "no-transition";
options = {};
setOptions(o);
updateOffset(true);
window.getComputedStyle(slider).opacity; // force a restyle
slider.className = "";
if (tt.en)
tt.show.call(this);
}
function setVmode() {
var v = vid();
ebi('bbox-vmode').style.display = v ? '' : 'none';
@ -450,10 +479,12 @@ window.baguetteBox = (function () {
if (anymod(e))
return;
var k = e.code + '';
var k = (e.key || e.code) + '';
if (k == "Space")
ev(e);
if (k == "Space" || k == "Spacebar" || k == " ") {
un_pp = Date.now();
return ev(e);
}
}
var passiveSupp = false;
@ -479,12 +510,13 @@ window.baguetteBox = (function () {
bind(document, 'fullscreenchange', onFSC);
bind(overlay, 'click', overlayClickHandler);
bind(overlay, 'wheel', overlayWheelHandler);
bind(btnPrev, 'click', showPreviousImage);
bind(btnNext, 'click', showNextImage);
bind(btnPrev, 'click', showLeftImage);
bind(btnNext, 'click', showRightImage);
bind(btnClose, 'click', hideOverlay);
bind(btnVmode, 'click', tglVmode);
bind(btnHelp, 'click', halp);
bind(btnAnim, 'click', anim);
bind(btnReadDir, 'click', toggleReadDir);
bind(btnRotL, 'click', rotl);
bind(btnRotR, 'click', rotr);
bind(btnSel, 'click', tglsel);
@ -502,12 +534,13 @@ window.baguetteBox = (function () {
unbind(document, 'fullscreenchange', onFSC);
unbind(overlay, 'click', overlayClickHandler);
unbind(overlay, 'wheel', overlayWheelHandler);
unbind(btnPrev, 'click', showPreviousImage);
unbind(btnNext, 'click', showNextImage);
unbind(btnPrev, 'click', showLeftImage);
unbind(btnNext, 'click', showRightImage);
unbind(btnClose, 'click', hideOverlay);
unbind(btnVmode, 'click', tglVmode);
unbind(btnHelp, 'click', halp);
unbind(btnAnim, 'click', anim);
unbind(btnReadDir, 'click', toggleReadDir);
unbind(btnRotL, 'click', rotl);
unbind(btnRotR, 'click', rotr);
unbind(btnSel, 'click', tglsel);
@ -558,6 +591,23 @@ window.baguetteBox = (function () {
btnAnim.textContent = ['⇄', '⮺', '⚡'][anims.indexOf(an)];
btnAnim.setAttribute('tt', 'animation: ' + an);
options.readDirRtl = sread('greaddir') === "rtl";
var msg;
if (options.readDirRtl) {
btnReadDir.innerText = "rtl";
msg = "browse from right to left";
slider.style.display = "flex";
slider.style.flexDirection = "row-reverse";
} else {
btnReadDir.innerText = "ltr";
msg = "browse from left to right";
slider.style.flexDirection = "";
slider.style.display = "block";
}
btnReadDir.setAttribute("tt", msg);
btnReadDir.setAttribute("aria-label", msg);
slider.style.transition = (options.animation === 'fadeIn' ? 'opacity .3s ease' :
options.animation === 'slideIn' ? '' : 'none');
@ -778,8 +828,7 @@ window.baguetteBox = (function () {
image.setAttribute('playsinline', '1');
// ios ignores poster
image.onended = vidEnd;
image.onplay = function () { show_buttons(1); };
image.onpause = function () { show_buttons(); };
image.onplay = image.onpause = ppHandler;
}
image.alt = thumbnailElement ? thumbnailElement.alt || '' : '';
if (options.titleTag && imageCaption)
@ -794,12 +843,33 @@ window.baguetteBox = (function () {
callback();
}
function showNextImage(e) {
function ppHandler() {
var now = Date.now();
if (now - un_pp < 50) {
un_pp = 0;
return playpause(); // browser undid space hotkey
}
show_buttons(this.paused ? 1 : 0);
}
function showRightImage(e) {
ev(e);
var dir = options.readDirRtl ? -1 : 1;
return show(currentIndex + dir);
}
function showLeftImage(e) {
ev(e);
var dir = options.readDirRtl ? 1 : -1;
return show(currentIndex + dir);
}
function showNextImageIgnoreReadDir(e) {
ev(e);
return show(currentIndex + 1);
}
function showPreviousImage(e) {
function showPreviousImageIgnoreReadDir(e) {
ev(e);
return show(currentIndex - 1);
}
@ -827,10 +897,10 @@ window.baguetteBox = (function () {
}
if (index < 0)
return bounceAnimation('left');
return bounceAnimation(options.readDirRtl ? 'right' : 'left');
if (index >= imagesElements.length)
return bounceAnimation('right');
return bounceAnimation(options.readDirRtl ? 'left' : 'right');
try {
vid().pause();
@ -984,7 +1054,7 @@ window.baguetteBox = (function () {
function vidEnd() {
if (this == vid() && vnext)
showNextImage();
showNextImageIgnoreReadDir();
}
function setloop(side) {
@ -1045,11 +1115,12 @@ window.baguetteBox = (function () {
return false;
}
function updateOffset() {
var offset = -currentIndex * 100 + '%',
function updateOffset(noTransition) {
var dir = options.readDirRtl ? 1 : -1,
offset = dir * currentIndex * 100 + '%',
xform = slider.style.perspective !== undefined;
if (options.animation === 'fadeIn') {
if (options.animation === 'fadeIn' && !noTransition) {
slider.style.opacity = 0;
setTimeout(function () {
xform ?
@ -1095,10 +1166,10 @@ window.baguetteBox = (function () {
fx = x / (rc.right - rc.left);
if (fx < 0.3)
return showPreviousImage();
return showLeftImage();
if (fx > 0.7)
return showNextImage();
return showRightImage();
show_buttons('t');
@ -1154,8 +1225,8 @@ window.baguetteBox = (function () {
return {
run: run,
show: show,
showNext: showNextImage,
showPrevious: showPreviousImage,
showNext: showRightImage,
showPrevious: showLeftImage,
relseek: relseek,
urltime: urltime,
playpause: playpause,

View file

@ -84,6 +84,13 @@
--sort-1: #fb0;
--sort-2: #d09;
--sz-b: #aaa;
--sz-k: #4ff;
--sz-m: var(--tab-alt);
--sz-g: var(--a);
--sz-t: var(--sz-g);
--sz-p: var(--sz-t);
--srv-1: #aaa;
--srv-2: #a73;
--srv-3: #f4c;
@ -187,6 +194,9 @@ html.y {
--sort-1: #059;
--sort-2: #f5d;
--sz-b: #777;
--sz-k: #380;
--srv-1: #555;
--srv-2: #c83;
--srv-3: #c0a;
@ -344,6 +354,9 @@ html.cz {
--btn-1-bb: .2em solid #e90;
--btn-1-bs: 0 .1em .8em rgba(255,205,0,0.9);
--sz-b: #ddd;
--sz-k: #c9f;
--srv-3: #fff;
--u2-tab-b1: var(--bg-d3);
@ -740,6 +753,15 @@ html.y #files tr.fade a {
#files tbody tr td:last-child {
white-space: nowrap;
}
#files span.fsz_B { color: var(--sz-b); }
#files span.fsz_K { color: var(--sz-k); }
#files span.fsz_M { color: var(--sz-m); }
#files span.fsz_G { color: var(--sz-g); }
#files span.fsz_T { color: var(--sz-t); }
#files span.fsz_P { color: var(--sz-p); }
html.y #files span.fsz_G,
html.y #files span.fsz_T,
html.y #files span.fsz_P { font-weight: bold }
#files thead th[style] {
width: auto !important;
}
@ -865,6 +887,9 @@ html.y #path a:hover {
#srv_info2 span {
color: var(--srv-1);
}
#srv_info2 a {
padding: 0;
}
#srv_info2 {
display: none;
}
@ -882,6 +907,9 @@ html.y #path a:hover {
#flogout {
display: inline;
}
html.dz #flogout {
margin-left: 1em;
}
#goh+span {
color: var(--bg-u5);
padding-left: .5em;
@ -1152,6 +1180,9 @@ html.y #widget.open {
border: 1px solid var(--bg-u5);
border-width: 0 .1em 0 0;
}
#wzip1 {
margin-right: .2em;
}
#wfm.act+#wzip1+#wzip,
#wfm.act+#wzip1+#wzip+#wnp {
margin-left: .2em;
@ -1555,11 +1586,13 @@ html {
#treepar {
z-index: 1;
position: fixed;
background: #fff;
background: var(--tree-bg);
left: -.96em;
width: calc(.3em + var(--nav-sz) - var(--sbw));
border-bottom: 1px solid var(--bg-u5);
overflow: hidden;
border-right: .5em solid #999\9;
}
#treepar.off {
display: none;
@ -1906,6 +1939,8 @@ html.y #tree.nowrap .ntree a+a:hover {
#rui td input[type="text"] {
width: 100%;
}
#rui #rn_n_d,
#rui #rn_n_s,
#shui td.exs input[type="text"] {
width: 3em;
}
@ -1995,6 +2030,7 @@ html.y #doc .line-highlight {
}
#seldoc.sel {
color: var(--fg2-max);
background: #f0f;
background: var(--g-sel-b1);
}
#pvol,
@ -2014,6 +2050,7 @@ a.btn,
user-select: none;
}
#hkhelp {
background: #fff;
background: var(--bg);
}
#hkhelp table {
@ -2108,6 +2145,7 @@ html.noscroll .sbar::-webkit-scrollbar {
width: 100%;
height: 100%;
text-align: center;
flex: none;
}
.full-image figure {
display: inline;
@ -2302,6 +2340,9 @@ html.y #bbox-overlay figcaption a {
0%, 100% {transform: scale(0)}
50% {transform: scale(1)}
}
.no-transition {
transition: none !important;
}
@ -3489,7 +3530,6 @@ html.ez {
html.e {
text-shadow: none;
}
html.e #files,
html.e #u2conf input[type="checkbox"]:hover + label,
html.e .tgl.btn.on:hover,
@ -3589,6 +3629,10 @@ html.e #srv_info {
display: flex;
align-items: center;
}
html.e #acc_info span.warn,
html.e #acc_info a {
color: var(--white);
}
html.e #flogout:before {
padding-left: 0.2em;
padding-right: 0.4em;

View file

@ -134,7 +134,6 @@
CGV = {{ cgv|tojson }},
TS = "{{ ts }}",
dtheme = "{{ dtheme }}",
srvinf = "{{ srv_info }}",
lang = "{{ lang }}",
dfavico = "{{ favico }}",
have_tags_idx = {{ have_tags_idx }},

File diff suppressed because it is too large Load diff

View file

@ -23,17 +23,17 @@
<th>user</th>
<th>groups</th>
</tr></thead><tbody>
{% for un, gn in rows %}
{%- for un, gn in rows %}
<tr>
<td><a href="{{ r }}/?idp=rm={{ un|e }}">forget</a></td>
<td>{{ un|e }}</td>
<td>{{ gn|e }}</td>
</tr>
{% endfor %}
{%- endfor %}
</tbody></table>
{% if not rows %}
{%- if not rows %}
(there are no IdP users in the cache)
{% endif %}
{%- endif %}
</div>
<a href="#" id="repl">π</a>
<script>

View file

@ -217,7 +217,7 @@ function convert_markdown(md_text, dest_dom) {
catch (ex) {
if (IE) {
dest_dom.innerHTML = 'IE cannot into markdown ;_;';
return;
return false;
}
if (ext)
@ -344,6 +344,8 @@ function convert_markdown(md_text, dest_dom) {
}
catch (ex) { }
}, 1);
return true;
}

View file

@ -1,6 +1,10 @@
"use strict";
var sloc0 = '' + location,
dbg_kbd = /[?&]dbgkbd\b/.exec(sloc0);
// server state
var server_md = dom_src.value;
@ -936,23 +940,30 @@ var set_lno = (function () {
// hotkeys / toolbar
(function () {
var keydown = function (ev) {
if (!ev && window.event) {
ev = window.event;
var keydown = function (e) {
if (!e && window.event) {
e = window.event;
if (dev_fbw == 1) {
toast.warn(10, 'hello from fallback code ;_;\ncheck console trace');
console.error('using window.event');
}
}
var kc = ev.code || ev.keyCode || ev.which,
var k = (e.key || e.code) + '',
editing = document.activeElement == dom_src;
//console.log(ev.key, ev.code, ev.keyCode, ev.which);
if (ctrl(ev) && (ev.code == "KeyS" || kc == 83)) {
if (k.startsWith('Key'))
k = k.slice(3);
var kl = k.toLowerCase();
if (dbg_kbd)
console.log('KBD', k, kl, e.key, e.code, e.keyCode, e.which);
if (ctrl(e) && kl == "s") {
save();
return false;
}
if (ev.code == "Escape" || kc == 27) {
if (k == "Escape" || k == "Esc") {
var d = ebi('helpclose');
if (d)
d.click();
@ -960,46 +971,44 @@ var set_lno = (function () {
if (editing)
set_lno();
if (ctrl(ev)) {
if (ev.code == "KeyE") {
if (ctrl(e)) {
if (kl == "e") {
dom_nsbs.click();
return false;
}
if (!editing)
return true;
if (ev.code == "KeyH" || kc == 72) {
md_header(ev.shiftKey);
if (kl == "h") {
md_header(e.shiftKey);
return false;
}
if (ev.code == "KeyZ" || kc == 90) {
if (ev.shiftKey)
if (kl == "z") {
if (e.shiftKey)
action_stack.redo();
else
action_stack.undo();
return false;
}
if (ev.code == "KeyY" || kc == 89) {
if (kl == "y") {
action_stack.redo();
return false;
}
if (ev.code == "KeyK") {
if (kl == "k") {
fmt_table();
return false;
}
if (ev.code == "KeyU") {
if (kl == "u") {
iter_uni();
return false;
}
var up = ev.code == "ArrowUp" || kc == 38;
var dn = ev.code == "ArrowDown" || kc == 40;
if (up || dn) {
md_p_jump(dn);
if (k == "ArrowUp" || k == "ArrowDown") {
md_p_jump(k == "ArrowDown");
return false;
}
if (ev.code == "KeyX" || ev.code == "KeyC") {
md_cut(ev.code == "KeyX");
if (kl == "x" || kl == "c") {
md_cut(kl == "x");
return true; //sic
}
}
@ -1007,18 +1016,18 @@ var set_lno = (function () {
if (!editing)
return true;
if (ev.code == "Tab" || kc == 9) {
md_indent(ev.shiftKey);
if (k == "Tab") {
md_indent(e.shiftKey);
return false;
}
if (ev.code == "Home" || kc == 36) {
md_home(ev.shiftKey);
if (k == "Home") {
md_home(e.shiftKey);
return false;
}
if (!ev.shiftKey && ((ev.code + '').endsWith("Enter") || kc == 13)) {
if (!e.shiftKey && k.endsWith("Enter")) {
return md_newline();
}
if (!ev.shiftKey && kc == 8) {
if (!e.shiftKey && k == "Backspace") {
return md_backspace();
}
}
@ -1042,7 +1051,9 @@ ebi('help').onclick = function (e) {
var dom = ebi('helpbox');
var dtxt = dom.getElementsByTagName('textarea');
if (dtxt.length > 0) {
convert_markdown(dtxt[0].value, dom);
var txt = dtxt[0].value;
if (!convert_markdown(txt, dom))
dom.innerText = txt.split('## markdown')[0];
dom.innerHTML = '<a href="#" id="helpclose">close</a>' + dom.innerHTML;
}

31
copyparty/web/opds.xml Normal file
View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
{%- for d in dirs %}
<entry>
<title>{{ d.name }}</title>
<link rel="subsection"
href="{{ d.href | e }}"
type="application/atom+xml;profile=opds-catalog"/>
<updated>{{ d.iso8601 }}</updated>
</entry>
{%- endfor %}
{%- for f in files %}
<entry>
<title>{{ f.name }}</title>
<updated>{{ f.iso8601 }}</updated>
<link rel="http://opds-spec.org/acquisition"
href="{{ f.href | e }}"
type="{{ f.mime }}"/>
{%- if f.jpeg_thumb_href != None %}
<link rel="http://opds-spec.org/image/thumbnail"
href="{{ f.jpeg_thumb_href | e }}"
type="image/jpeg"/>
{%- endif %}
{%- if f.jpeg_thumb_href_hires != None %}
<link rel="http://opds-spec.org/image"
href="{{ f.jpeg_thumb_href_hires | e }}"
type="image/jpeg"/>
{%- endif %}
</entry>
{%- endfor %}
</feed>

View file

@ -36,7 +36,7 @@
<th>hrs</th>
<th>add time</th>
</tr></thead><tbody>
{% for k, pw, vp, pr, st, un, t0, t1 in rows %}
{%- for k, pw, vp, pr, st, un, t0, t1 in rows %}
<tr>
<td>
<a href="{{ r }}{{ shr }}{{ k }}?qr">qr</a>
@ -54,11 +54,11 @@
<td>{{ "inf" if not t1 else "dead" if t1 < now else ((t1 - now) / 3600) | round(1) }}</td>
<td></td>
</tr>
{% endfor %}
{%- endfor %}
</tbody></table>
{% if not rows %}
{%- if not rows %}
(you don't have any active shares btw)
{% endif %}
{%- endif %}
</div>
<a href="#" id="repl">π</a>
<script>

View file

@ -38,6 +38,7 @@ a {
td a {
margin: 0;
}
#wb,
#w {
color: #fff;
background: #940;
@ -117,6 +118,26 @@ table {
.btns>a:first-child {
margin-left: 0;
}
.agr br {
display: none;
}
#lo,
.agr a,
.agr form {
margin: 0 .5em 0 0;
line-height: 4em;
}
.agr form,
.agr input {
display: inline;
padding: 0;
margin: 0;
}
#lo,
.agr input {
line-height: 1em;
font-weight: normal;
}
#msg {
margin: 3em 0;
}

View file

@ -18,12 +18,16 @@
<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 &nbsp; <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 id="un">{{ this.uname|e }}</strong></p>
{%- endif %}
{%- if this.uname == '*' %}
<p id="b">howdy stranger &nbsp; <small>(you're not logged in)</small></p>
{%- else %}
{%- if this.args.idp_logout %}
<a id="c" href="{{ this.args.idp_logout }}" class="logout">logout</a>
{%- else %}
<a id="c" href="{{ r }}/?pw=x" class="logout">logout</a>
{%- endif %}
<p><span id="m">welcome back,</span> <strong id="un">{{ this.uname|e }}</strong></p>
{%- endif %}
{%- endif %}
{%- if msg %}
@ -37,9 +41,9 @@
<table class="vols">
<thead><tr><th>%</th><th>speed</th><th>eta</th><th>idle</th><th>dir</th><th>file</th></tr></thead>
<tbody>
{% for u in ups %}
{%- for u in ups %}
<tr><td>{{ u[0] }}</td><td>{{ u[1] }}</td><td>{{ u[2] }}</td><td>{{ u[3] }}</td><td><a href="{{ u[4] }}">{{ u[5]|e }}</a></td><td>{{ u[6]|e }}</td></tr>
{% endfor %}
{%- endfor %}
</tbody>
</table>
{%- endif %}
@ -49,9 +53,9 @@
<table class="vols">
<thead><tr><th>%</th><th>sent</th><th>speed</th><th>eta</th><th>idle</th><th></th><th>dir</th><th>file</th></tr></thead>
<tbody>
{% for u in dls %}
{%- for u in dls %}
<tr><td>{{ u[0] }}</td><td>{{ u[1] }}</td><td>{{ u[2] }}</td><td>{{ u[3] }}</td><td>{{ u[4] }}</td><td>{{ u[5] }}</td><td><a href="{{ u[6] }}">{{ u[7]|e }}</a></td><td>{{ u[8] }}</td></tr>
{% endfor %}
{%- endfor %}
</tbody>
</table>
{%- endif %}
@ -70,11 +74,11 @@
<table class="vols">
<thead><tr><th>vol</th><th id="t">action</th><th>status</th></tr></thead>
<tbody>
{% for mp in avol %}
{%- for mp in avol %}
{%- if mp in vstate and vstate[mp] %}
<tr><td><a href="{{ r }}{{ mp }}{{ url_suf }}">{{ mp }}</a></td><td><a class="s" href="{{ r }}{{ mp }}?scan">rescan</a></td><td>{{ vstate[mp] }}</td></tr>
{%- endif %}
{% endfor %}
{%- endfor %}
</tbody>
</table>
</td></tr></table>
@ -87,18 +91,18 @@
{%- if rvol %}
<h1 id="f">you can browse:</h1>
<ul>
{% for mp in rvol %}
{%- for mp in rvol %}
<li><a href="{{ r }}{{ mp }}{{ url_suf }}">{{ mp }}</a></li>
{% endfor %}
{%- endfor %}
</ul>
{%- endif %}
{%- if wvol %}
<h1 id="g">you can upload to:</h1>
<ul>
{% for mp in wvol %}
{%- for mp in wvol %}
<li><a href="{{ r }}{{ mp }}{{ url_suf }}">{{ mp }}</a></li>
{% endfor %}
{%- endfor %}
</ul>
{%- endif %}
@ -110,71 +114,85 @@
<input type="password" id="lp" name="cppwd" placeholder=" password" />
<input type="hidden" name="uhash" id="uhash" value="x" />
<input type="submit" id="ls" value="Unlock" />
{% if ahttps %}
{%- if ahttps %}
<a id="w" href="{{ ahttps }}">switch to https</a>
{% endif %}
{%- endif %}
</form>
</div>
{%- else %}
<h1 id="l">login for more:</h1>
<div>
{%- if this.args.idp_login %}
<ul><li>
<a href="{{ this.args.idp_login | replace("{dst}",r+"/"+qvpath) }}">{{ this.args.idp_login_t }}</a>
{%- if this.args.ao_have_pw %}or alternatively:{%- endif %}
</li></ul>
{%- endif %}
{%- if this.args.ao_have_pw %}
<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 %}
{%- 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 %}
{%- else %}
<input type="password" id="lp" name="cppwd" placeholder=" password" />
{% endif %}
{%- endif %}
<input type="hidden" name="uhash" id="uhash" value="x" />
<input type="submit" id="ls" value="login" />
{% if chpw %}
{%- if chpw %}
<a id="x" href="#">change password</a>
{% endif %}
{% if ahttps %}
{%- endif %}
{%- if ahttps %}
<a id="w" href="{{ ahttps }}">switch to https</a>
{% endif %}
{%- endif %}
</form>
{%- endif %}
</div>
{%- endif %}
<h1 id="cc">other stuff:</h1>
<ul>
{%- if this.uname in this.args.idp_adm_set %}
<li><a id="ag" href="{{ r }}/?idp">view idp cache</a></li>
{% endif %}
<div class="agr">
{%- if ahttps %}
<a id="wb" href="{{ ahttps }}">switch to https</a><br />
{%- endif %}
<a id="af" href="{{ r }}/?ru">show recent uploads</a><br />
{%- if this.uname != '*' and this.args.shr %}
<li><a id="y" href="{{ r }}/?shares">edit shares</a></li>
{% endif %}
<a id="y" href="{{ r }}/?shares">edit shares</a><br />
{%- endif %}
{% if k304 or k304vis %}
{% if k304 %}
{%- if this.uname in this.args.idp_adm_set %}
<a id="ag" href="{{ r }}/?idp">view idp cache</a><br />
{%- endif %}
<a id="k" href="{{ r }}/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a><br />
{%- if this.uname != '*' and not in_shr %}
<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>
{%- endif %}
</div>
<ul>
{%- if k304 or k304vis %}
{%- if k304 %}
<li><a id="h" href="{{ r }}/?cc&setck=k304=n">disable k304</a> (currently enabled)
{%- else %}
<li><a id="i" href="{{ r }}/?cc&setck=k304=y" class="r">enable k304</a> (currently disabled)
{% endif %}
{%- endif %}
<blockquote id="j">enabling k304 will disconnect your client on every HTTP 304, which can prevent some buggy proxies from getting stuck (suddenly not loading pages), <em>but</em> it will also make things slower in general</blockquote></li>
{% endif %}
{%- endif %}
{% if no304 or no304vis %}
{% if no304 %}
{%- if no304 or no304vis %}
{%- if no304 %}
<li><a id="ab" href="{{ r }}/?cc&setck=no304=n">disable no304</a> (currently enabled)
{%- else %}
<li><a id="ac" href="{{ r }}/?cc&setck=no304=y" class="r">enable no304</a> (currently disabled)
{% endif %}
{%- endif %}
<blockquote id="ad">enabling no304 will disable all caching; try this if k304 wasn't enough. This will waste a huge amount of network traffic!</blockquote></li>
{% endif %}
<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 %}
{%- endif %}
</ul>
</div>

View file

@ -197,6 +197,53 @@ var Ls = {
"ae1": "Aktive Downloads:",
"af1": "Zeige neue Uploads",
},
"epo": {
"a1": "reŝargi",
"b1": "sal, nekonatulo &nbsp; <small>(vi ne estas ensalutita)</small>",
"c1": "elsaluti",
"d1": "montru stakon", // TLNote: "d2" is the tooltip for this button
"d2": "montras la staton de ĉiuj aktivaj fadenoj",
"e1": "reŝargi CFGon",
"e2": "reŝargas la agordo-dosierojn (kontoj/portiloj/portilo-flagoj),$Nkaj reskanas ĉiuj portiloj de ed2s$N$Nnoto: ĉiuj ŝanĝoj de ĝeneralaj agordoj postulas$Npostulas tutan restartigon por efektiviĝi",
"f1": "vi povas vidi:",
"g1": "vi povas alŝuti al:",
"cc1": "aliaĵoj:",
"h1": "malŝalti k304-on", // TLNote: "j1" explains what k304 is
"i1": "ŝalti k304-on",
"j1": "k304 malkonektas vian klienton je ĉiu HTTP-eraro 304; tio povas eviti paraliziĝon dum uzado de difektitaj retperantoj (paĝoj subite ne ŝargiĝas), <em>sed</em> ĝi ankaŭ plimalrapidigas ĉion",
"k1": "rekomenci agordojn de kliento",
"l1": "ensaluti por pli da opcioj:",
"ls3": "ensaluti", //m
"lu4": "uzantnomo", //m
"lp4": "pasvorto", //m
"lo3": "ensaluti kiel “{0}” ĉie", //m
"lo2": "ĉi tiu finigos seancon en ĉiuj retumiloj", //m
"m1": "bonvenon denove,", // TLNote: "welcome back, USERNAME"
"n1": "404 ne trovita &nbsp;┐( ´ -`)┌",
"o1": 'aŭ eble vi ne havas rajton -- provu uzi pasvorton aŭ <a href="' + SR + '/?h">iri hejmen</a>',
"p1": "403 ne permesita &nbsp;~┻━┻",
"q1": 'uzu pasvorton aŭ <a href="' + SR + '/?h">iru hejmen</a>',
"r1": "hejmen",
".s1": "reskani",
"t1": "ago", // TLNote: this is the header above the "rescan" buttons
"u2": "tempo post lasta skribo (alŝuto / alinomado / ...) je servilo$N( upload / rename / ... )$N$N17d = 17 tagoj$N1h23 = 1 horo 23 minutoj$N4m56 = 4 minutoj 56 sekundoj",
"v1": "konekti",
"v2": "uzi ĉi tiun servilon kiel loka disko",
"w1": "uzi HTTPS-protokolon",
"x1": "ŝanĝi pasvorton",
"y1": "redakti komunaĵojn", // TLNote: shows the list of folders that the user has decided to share
"z1": "malŝlosi ĉi tiun komunaĵon:", // TLNote: the password prompt to see a hidden share
"ta1": "entajpu novan pasvorton unue",
"ta2": "retajpu por konfirmi:",
"ta3": "tajpo-eraro; bonvolu provu denove",
"aa1": "aktivaj alŝutoj:",
"ab1": "malŝalti no304-on",
"ac1": "ŝalti no304-on",
"ad1": "no304 malŝaltas ĉiun kaŝmemoradon; provu ĉi tion, se k304 ne riparis la difektojn. Ĉi tiu agordo malŝparas multon da datumtrafiko!",
"ae1": "aktivaj elŝutoj:",
"af1": "montri lastajn alŝutojn",
"ag1": "montri kaŝmemoron de idp",
},
"fin": {
"a1": "päivitä",
"b1": "hei sie muukalainen &nbsp; <small>(et ole kirjautunut sisään)</small>",
@ -806,6 +853,47 @@ var Ls = {
"af1": "показать недавние загрузки",
"ag1": "показать известных IdP-пользователей",
},
"tur": {
"a1": "yenile",
"b1": "N'aber aga &nbsp; <small>(giriş yapmamışsın)</small>",
"c1": ıkış yap",
"d1": "yığını yolla",
"d2": "tüm aktif iş parçacıklarının durumunu gösterir",
"e1": "cfg'yi yenile",
"e2": "yapılandırma dosyalarını yenile (hesaplar/hacimler/hacim bayrakları),$Nve tüm e2ds hacimlerini yeniden tarayın$N$Nnot: global ayarlardaki herhangi bir değişiklik$Netkili hale gelmesi için tam bir yeniden başlatma gerektirir",
"f1": "göz atabilirsiniz:",
"g1": "yükleyebilirsiniz:",
"cc1": "diğer şeyler:",
"h1": "k304'ü devre dışı bırak",
"i1": "k304'ü etkinleştir",
"j1": "k304'ü etkinleştirmek, her HTTP 304'te istemcinizin bağlantısını keser, bu da bazı hatalı proxy'lerin takılmasını önleyebilir (sayfaların birdenbire yüklenmesinin durması gibi); <em>ama</em> bu, aynı zamanda genel olarak işleyişi yavaşlatır",
"k1": "istemci ayarlarını sıfırla",
"l1": "daha fazlası için giriş yap:",
"m1": "hoş geldin,",
"n1": "404 bulunamadı &nbsp;┐( ´ -`)┌",
"o1": 'ya da erişim iznin yok -- bir şifre dene veya <a href="' + SR + '/?h">ana sayfaya dön</a>',
"p1": "403 yasaklandı &nbsp;~┻━┻",
"q1": 'bir şifre kullan veya <a href="' + SR + '/?h">ana sayfaya dön</a>',
"r1": "ana sayfaya dön",
".s1": "yeniden tara",
"t1": "işlem",
"u2": "son sunucu yazma zamanı$N( yükleme / yeniden adlandırma / ... )$N$N17d = 17 gün$N1h23 = 1 saat 23 dakika$N4m56 = 4 dakika 56 saniye",
"v1": "bağlan",
"v2": "bu sunucuyu yerel HDD olarak kullan",
"w1": "https'ye geç",
"x1": "şifreyi değiştir",
"y1": "paylaşılanları düzenle",
"z1": "gizli paylaşımın kilidini aç:",
"ta1": "ilk önce yeni şifreyi doldur",
"ta2": "yeni şifreyi onaylamak için tekrar girin:",
"ta3": "bir yazım hatası bulundu; lütfen tekrar deneyin",
"aa1": "gelen dosyalar:",
"ab1": "no304'ü devre dışı bırak",
"ac1": "no304'ü etkinleştir",
"ad1": "no304'ü etkinleştirmek, tüm önbelleği devre dışı bırakır; bunu k304 yeterli olmadıysa deneyin. Bu, büyük miktarda ağ trafiği israf edecektir!",
"ae1": "aktif indirmeler:",
"af1": "son yüklemeleri göster",
},
};
if (window.langmod)
@ -814,6 +902,8 @@ if (window.langmod)
var d = Ls[sread("cpp_lang", Object.keys(Ls)) || lang] ||
Ls.eng || Ls.nor || Ls.chi;
d.wb = d.w;
for (var k in (d || {})) {
var f = k.slice(-1),
i = k.slice(0, -1),
@ -844,14 +934,16 @@ catch (ex) { }
tt.init();
var o = QS('input[name="uname"]') || QS('input[name="cppwd"]');
if (!MOBILE && !ebi('c') && o.offsetTop + o.offsetHeight < window.innerHeight)
if (o && !MOBILE && !ebi('c') && o.offsetTop + o.offsetHeight < window.innerHeight)
o.focus();
o = ebi('u');
if (o && /[0-9]+$/.exec(o.innerHTML))
o.innerHTML = shumantime(o.innerHTML);
ebi('uhash').value = '' + location.hash;
o = ebi('uhash')
if (o)
o.value = '' + location.hash;
if (/\&re=/.test('' + location))
ebi('a').className = 'af g';

View file

@ -31,10 +31,10 @@
<br />
<span class="os win lin mac">placeholders:</span>
<span class="os win">
{% if accs %}<code><b id="pw0">{{ pw }}</b></code>=password, {% endif %}<code><b>W:</b></code>=mountpoint
{% if accs %}{% if un %}<code><b id="un0">{{ un }}</b></code>=username, <code><b id="up0">{{ unpw }}</b></code>=username:password, {% endif %}<code><b id="pw0">{{ pw }}</b></code>=password, {% endif %}<code><b>W:</b></code>=mountpoint
</span>
<span class="os lin mac">
{% if accs %}<code><b id="pw0">{{ pw }}</b></code>=password, {% endif %}<code><b>mp</b></code>=mountpoint
{% if accs %}{% if un %}<code><b id="un0">{{ un }}</b></code>=username, <code><b id="up0">{{ unpw }}</b></code>=username:password, {% endif %}<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>
@ -44,7 +44,7 @@
{% if args.have_idp_hdrs %}
<p style="line-height:2em"><b>WARNING:</b> this server is using IdP-based authentication, so this stuff may not work as advertised. Depending on server config, these commands can probably only be used to access areas which don't require authentication, unless you auth using any non-IdP accounts defined in the copyparty config. Please see <a href="https://github.com/9001/copyparty/blob/hovudstraum/docs/idp.md#connecting-webdav-clients">the IdP docs</a></p>
{% endif %}
{%- endif %}
@ -54,50 +54,64 @@
<div class="os win">
<p>if you can, install <a href="https://winfsp.dev/rel/">winfsp</a>+<a href="https://downloads.rclone.org/rclone-current-windows-amd64.zip">rclone</a> and then paste this in cmd:</p>
<pre>
rclone config create {{ aname }}-dav webdav url=http{{ s }}://{{ rip }}{{ hport }} vendor=owncloud pacer_min_sleep=0.01ms{% if accs %} user=k pass=<b>{{ pw }}</b>{% endif %}
rclone config create {{ aname }}-dav webdav url=http{{ s }}://{{ rip }}{{ hport }} vendor=owncloud pacer_min_sleep=0.01ms{% if accs %} user={{ b_un }} pass=<b>{{ pw }}</b>{% endif %}
rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-dav:{{ rvp }} <b>W:</b>
</pre>
<ul>
{% if s %}
{%- if s %}
<li>running <code>rclone mount</code> on LAN (or just dont have valid certificates)? add <code>--no-check-certificate</code></li>
{% endif %}
{%- endif %}
<li>old version of rclone? replace all <code>=</code> with <code>&nbsp;</code> (space)</li>
</ul>
<p>if you want to use the native WebDAV client in windows instead (slow and buggy), first run <a href="{{ r }}/.cpr/a/webdav-cfg.bat">webdav-cfg.bat</a> to remove the 47 MiB filesize limit (also fixes latency and password login), then connect:</p>
<pre>
{%- if un %}
net use <b>w:</b> http{{ s }}://{{ ep }}/{{ rvp }}{% if accs %} <b>{{ pw }}</b> /user:{{ b_un }}{% endif %}
{%- else %}
net use <b>w:</b> http{{ s }}://{{ ep }}/{{ rvp }}{% if accs %} k /user:<b>{{ pw }}</b>{% endif %}
{%- endif %}
</pre>
</div>
<div class="os lin">
<p>rclone (v1.63 or later) is recommended:</p>
<pre>
rclone config create {{ aname }}-dav webdav url=http{{ s }}://{{ rip }}{{ hport }} vendor=owncloud pacer_min_sleep=0.01ms{% if accs %} user=k pass=<b>{{ pw }}</b>{% endif %}
rclone config create {{ aname }}-dav webdav url=http{{ s }}://{{ rip }}{{ hport }} vendor=owncloud pacer_min_sleep=0.01ms{% if accs %} user={{ b_un }} pass=<b>{{ pw }}</b>{% endif %}
rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-dav:{{ rvp }} <b>mp</b>
</pre>
<ul>
{% if s %}
{%- if s %}
<li>running <code>rclone mount</code> on LAN (or just dont have valid certificates)? add <code>--no-check-certificate</code></li>
{% endif %}
{%- endif %}
<li>running <code>rclone mount</code> as root? add <code>--allow-other</code></li>
<li>old version of rclone? replace all <code>=</code> with <code>&nbsp;</code> (space)</li>
</ul>
<p>alternatively use davfs2 (requires root, is slower, forgets lastmodified-timestamp on upload):</p>
<pre>
yum install davfs2
{%- if un %}
{% if accs %}printf '%s\n' {{ b_un }} <b>{{ pw }}</b> | {% endif %}mount -t davfs -ouid=1000 http{{ s }}://{{ ep }}/{{ rvp }} <b>mp</b>
{%- else %}
{% if accs %}printf '%s\n' <b>{{ pw }}</b> k | {% endif %}mount -t davfs -ouid=1000 http{{ s }}://{{ ep }}/{{ rvp }} <b>mp</b>
{%- endif %}
</pre>
{%- if accs %}
<p>make davfs2 automount on boot:</p>
<pre>
{%- if un %}
printf '%s\n' "http{{ s }}://{{ ep }}/{{ rvp }} {{ b_un }} <b>{{ pw }}</b>" >> /etc/davfs2/secrets
{%- else %}
printf '%s\n' "http{{ s }}://{{ ep }}/{{ rvp }} <b>{{ pw }}</b> k" >> /etc/davfs2/secrets
{%- endif %}
printf '%s\n' "http{{ s }}://{{ ep }}/{{ rvp }} <b>mp</b> davfs rw,user,uid=1000,noauto 0 0" >> /etc/fstab
</pre>
{%- endif %}
<p>or the emergency alternative (gnome/gui-only):</p>
<!-- gnome-bug: ignores vp -->
<pre>
{%- if accs %}
echo <b>{{ pw }}</b> | gio mount dav{{ s }}://k@{{ ep }}/{{ rvp }}
echo <b>{{ pw }}</b> | gio mount dav{{ s }}://{{ b_un }}@{{ ep }}/{{ rvp }}
{%- else %}
gio mount -a dav{{ s }}://{{ ep }}/{{ rvp }}
{%- endif %}
@ -107,18 +121,18 @@
<div class="os mac">
<pre>
osascript -e ' mount volume "http{{ s }}://k:<b>{{ pw }}</b>@{{ ep }}/{{ rvp }}" '
osascript -e ' mount volume "http{{ s }}://{{ b_un }}:<b>{{ pw }}</b>@{{ ep }}/{{ rvp }}" '
</pre>
<p>or you can open up a Finder, press command-K and paste this instead:</p>
<pre>
http{{ s }}://k:<b>{{ pw }}</b>@{{ ep }}/{{ rvp }}
http{{ s }}://{{ b_un }}:<b>{{ pw }}</b>@{{ ep }}/{{ rvp }}
</pre>
{% if s %}
{%- if s %}
<p><em>replace <code>https</code> with <code>http</code> if it doesn't work</em></p>
{% endif %}
{%- endif %}
</div>
{% endif %}
{%- endif %}
@ -127,51 +141,71 @@
<div class="os win">
<p>if you can, install <a href="https://winfsp.dev/rel/">winfsp</a>+<a href="https://downloads.rclone.org/rclone-current-windows-amd64.zip">rclone</a> and then paste this in cmd:</p>
{% if args.ftp %}
{%- if args.ftp %}
<p>connect with plaintext FTP:</p>
<pre>
{%- if un %}
rclone config create {{ aname }}-ftp ftp host={{ rip }} port={{ args.ftp }} user={% if accs %}{{ b_un }} pass=<b>{{ pw }}</b>{% else %}anonymous pass=k{% endif %} tls=false
{%- else %}
rclone config create {{ aname }}-ftp ftp host={{ rip }} port={{ args.ftp }} pass=k user={% if accs %}<b>{{ pw }}</b>{% else %}anonymous{% endif %} tls=false
{%- endif %}
rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-ftp:{{ rvp }} <b>W:</b>
</pre>
{% endif %}
{% if args.ftps %}
{%- endif %}
{%- if args.ftps %}
<p>connect with TLS-encrypted FTPS:</p>
<pre>
{%- if un %}
rclone config create {{ aname }}-ftps ftp host={{ rip }} port={{ args.ftps }} user={% if accs %}{{ b_un }} pass=<b>{{ pw }}</b>{% else %}anonymous pass=k{% endif %} tls=false explicit_tls=true
{%- else %}
rclone config create {{ aname }}-ftps ftp host={{ rip }} port={{ args.ftps }} pass=k user={% if accs %}<b>{{ pw }}</b>{% else %}anonymous{% endif %} tls=false explicit_tls=true
{%- endif %}
rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-ftps:{{ rvp }} <b>W:</b>
</pre>
{% endif %}
{%- endif %}
<ul>
{% if args.ftps %}
{%- if args.ftps %}
<li>running on LAN (or just dont have valid certificates)? add <code>no_check_certificate=true</code> to the config command</li>
{% endif %}
{%- endif %}
<li>old version of rclone? replace all <code>=</code> with <code>&nbsp;</code> (space)</li>
</ul>
<p>if you want to use the native FTP client in windows instead (please dont), press <code>win+R</code> and run this command:</p>
<pre>
{%- if un %}
explorer {{ "ftp" if args.ftp else "ftps" }}://{% if accs %}{{ b_un }}:<b>{{ pw }}</b>@{% endif %}{{ host }}:{{ args.ftp or args.ftps }}/{{ rvp }}
{%- else %}
explorer {{ "ftp" if args.ftp else "ftps" }}://{% if accs %}<b>{{ pw }}</b>:k@{% endif %}{{ host }}:{{ args.ftp or args.ftps }}/{{ rvp }}
{%- endif %}
</pre>
</div>
<div class="os lin">
{% if args.ftp %}
{%- if args.ftp %}
<p>connect with plaintext FTP:</p>
<pre>
{%- if un %}
rclone config create {{ aname }}-ftp ftp host={{ rip }} port={{ args.ftp }} user={% if accs %}{{ b_un }} pass=<b>{{ pw }}</b>{% else %}anonymous pass=k{% endif %} tls=false
{%- else %}
rclone config create {{ aname }}-ftp ftp host={{ rip }} port={{ args.ftp }} pass=k user={% if accs %}<b>{{ pw }}</b>{% else %}anonymous{% endif %} tls=false
{%- endif %}
rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-ftp:{{ rvp }} <b>mp</b>
</pre>
{% endif %}
{% if args.ftps %}
{%- endif %}
{%- if args.ftps %}
<p>connect with TLS-encrypted FTPS:</p>
<pre>
{%- if un %}
rclone config create {{ aname }}-ftps ftp host={{ rip }} port={{ args.ftps }} user={% if accs %}{{ b_un }} pass=<b>{{ pw }}</b>{% else %}anonymous pass=k{% endif %} tls=false explicit_tls=true
{%- else %}
rclone config create {{ aname }}-ftps ftp host={{ rip }} port={{ args.ftps }} pass=k user={% if accs %}<b>{{ pw }}</b>{% else %}anonymous{% endif %} tls=false explicit_tls=true
{%- endif %}
rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-ftps:{{ rvp }} <b>mp</b>
</pre>
{% endif %}
{%- endif %}
<ul>
{% if args.ftps %}
{%- if args.ftps %}
<li>running on LAN (or just dont have valid certificates)? add <code>no_check_certificate=true</code> to the config command</li>
{% endif %}
{%- endif %}
<li>running <code>rclone mount</code> as root? add <code>--allow-other</code></li>
<li>old version of rclone? replace all <code>=</code> with <code>&nbsp;</code> (space)</li>
</ul>
@ -179,7 +213,7 @@
<!-- gnome-bug: ignores vp -->
<pre>
{%- if accs %}
echo <b>{{ pw }}</b> | gio mount ftp{{ "" if args.ftp else "s" }}://k@{{ host }}:{{ args.ftp or args.ftps }}/{{ rvp }}
echo <b>{{ pw }}</b> | gio mount ftp{{ "" if args.ftp else "s" }}://{{ b_un }}@{{ host }}:{{ args.ftp or args.ftps }}/{{ rvp }}
{%- else %}
gio mount -a ftp{{ "" if args.ftp else "s" }}://{{ host }}:{{ args.ftp or args.ftps }}/{{ rvp }}
{%- endif %}
@ -189,10 +223,10 @@
<div class="os mac">
<p>note: FTP is read-only on macos; please use WebDAV instead</p>
<pre>
open {{ "ftp" if args.ftp else "ftps" }}://{% if accs %}k:<b>{{ pw }}</b>@{% else %}anonymous:@{% endif %}{{ host }}:{{ args.ftp or args.ftps }}/{{ rvp }}
open {{ "ftp" if args.ftp else "ftps" }}://{% if accs %}{{ b_un }}:<b>{{ pw }}</b>@{% else %}anonymous:@{% endif %}{{ host }}:{{ args.ftp or args.ftps }}/{{ rvp }}
</pre>
</div>
{% endif %}
{%- endif %}
@ -204,11 +238,11 @@
<span class="os lin">doesn't need root</span>
</p>
<pre>
partyfuse.py{% if accs %} -a <b>{{ pw }}</b>{% endif %} http{{ s }}://{{ ep }}/{{ rvp }} <b><span class="os win">W:</span><span class="os lin mac">mp</span></b>
partyfuse.py{% if accs %} -a <b>{{ unpw }}</b>{% endif %} http{{ s }}://{{ ep }}/{{ rvp }} <b><span class="os win">W:</span><span class="os lin mac">mp</span></b>
</pre>
{% if s %}
{%- if s %}
<ul><li>if you are on LAN (or just dont have valid certificates), add <code>-td</code></li></ul>
{% endif %}
{%- endif %}
<p>
you can use <a href="{{ r }}/.cpr/a/u2c.py">u2c.py</a> to upload (sometimes faster than web-browsers)
</p>
@ -217,6 +251,10 @@
{% if args.smb %}
<h1>SMB / CIFS</h1>
{%- if un %}
<h2>not available on this server because <code>--usernames</code> is enabled in the server config</h2>
{%- else %}
<div class="os win">
<pre>
net use <b>w:</b> \\{{ host }}\a{% if accs %} k /user:<b>{{ pw }}</b>{% endif %}
@ -234,7 +272,8 @@
<pre class="os mac">
open 'smb://<b>{{ pw }}</b>:k@{{ host }}/a'
</pre>
{% endif %}
{%- endif %}
{%- endif %}
@ -247,7 +286,7 @@
{ "Version": "15.0.0", "Name": "copyparty",
"RequestURL": "http{{ s }}://{{ ep }}/{{ rvp }}",
"Headers": {
{% if accs %}"pw": "<b>{{ pw }}</b>", {% endif %}"accept": "url"
{% if accs %}"pw": "<b>{{ unpw }}</b>", {% endif %}"accept": "url"
},
"DestinationType": "ImageUploader, TextUploader, FileUploader",
"Body": "MultipartFormData", "URL": "{response}",
@ -260,7 +299,7 @@
{ "Name": "copyparty",
"RequestURL": "http{{ s }}://{{ ep }}/{{ rvp }}",
"Headers": {
{% if accs %}"pw": "<b>{{ pw }}</b>", {% endif %}"accept": "url"
{% if accs %}"pw": "<b>{{ unpw }}</b>", {% endif %}"accept": "url"
},
"DestinationType": "ImageUploader, TextUploader, FileUploader",
"FileFormName": "f" }
@ -278,7 +317,9 @@
{ "Name": "copyparty",
"RequestURL": "http{{ s }}://{{ ep }}/{{ rvp }}",
"Headers": {
{% if accs %}"pw": "<b>{{ pw }}</b>",{% endif %}
{%- if accs %}
"pw": "<b>{{ unpw }}</b>",
{%- endif %}
"accept": "json"
},
"ResponseURL": "{{ '{{fileurl}}' }}",
@ -295,7 +336,7 @@
<pre class="dl" name="flameshot.sh">
#!/bin/bash
pw="<b>{{ pw }}</b>"
pw="<b>{{ unpw }}</b>"
url="http{{ s }}://{{ ep }}/{{ rvp }}"
filename="$(date +%Y-%m%d-%H%M%S).png"
flameshot gui -s -r | curl -sT- "$url$filename?want=url&pw=$pw" | xsel -ib

View file

@ -49,21 +49,47 @@ function setos(os) {
setos(WINDOWS ? 'win' : LINUX ? 'lin' : MACOS ? 'mac' : 'idk');
var pw = '';
var un, un0, pw, pw0, unpw, up0;
function setpw(e) {
ev(e);
if (!ebi('un0'))
return askpw();
modal.prompt('username:', '', function (v) {
if (!v)
return;
un = v;
un0 = ebi('un0').innerHTML;
var oa = QSA('b');
for (var a = 0; a < oa.length; a++)
if (oa[a].innerHTML == un0)
oa[a].textContent = un;
askpw();
});
}
function askpw() {
modal.prompt('password:', '', function (v) {
if (!v)
return;
pw = v;
var pw0 = ebi('pw0').innerHTML,
oa = QSA('b');
pw0 = ebi('pw0').innerHTML;
var oa = QSA('b');
for (var a = 0; a < oa.length; a++)
if (oa[a].innerHTML == pw0)
oa[a].textContent = v;
oa[a].textContent = pw;
if (un) {
unpw = un ? (un+':'+pw) : pw;
up0 = ebi('up0').innerHTML;
for (var a = 0; a < oa.length; a++)
if (oa[a].innerHTML == up0)
oa[a].textContent = unpw;
}
add_dls();
});
}

View file

@ -732,7 +732,7 @@ function Donut(uc, st) {
tstrober = setInterval(strobe, 300);
if (uc.upsfx && actx && actx.state != 'suspended')
sfx();
sfx_nice();
// firefox may forget that filedrops are user-gestures so it can skip this:
if (uc.upnag && Notification && Notification.permission == 'granted')
@ -745,8 +745,10 @@ function Donut(uc, st) {
if (!txt)
clearInterval(tstrober);
}
}
function sfx() {
function sfx_nice() {
if (true) {
var osc = actx.createOscillator(),
gain = actx.createGain(),
gg = gain.gain,
@ -1801,7 +1803,7 @@ function up2k_init(subtle) {
while (true) {
var now = Date.now(),
blocktime = now - r.tact,
was_busy = st.is_busy,
was_busy = !!st.is_busy,
is_busy = !!( // gzip take the wheel
st.car < st.files.length ||
st.busy.hash.length ||
@ -3037,10 +3039,12 @@ function up2k_init(subtle) {
if (anymod(e))
return;
if (e.code == 'ArrowUp')
var k = e.key || e.code;
if (k == 'ArrowUp')
bumpthread(1);
if (e.code == 'ArrowDown')
if (k == 'ArrowDown')
bumpthread(-1);
}
@ -3103,7 +3107,8 @@ function up2k_init(subtle) {
ebi('u2szg').addEventListener('blur', read_u2sz);
ebi('u2szg').onkeydown = function (e) {
if (anymod(e)) return;
var n = e.code == 'ArrowUp' ? 1 : e.code == 'ArrowDown' ? -1 : 0;
var k = e.key || e.code,
n = k == 'ArrowUp' ? 1 : k == 'ArrowDown' ? -1 : 0;
if (!n) return;
this.value = parseInt(this.value) + n;
read_u2sz();
@ -3180,7 +3185,8 @@ function up2k_init(subtle) {
function kd_life(e) {
var el = e.target,
d = e.code == 'ArrowUp' ? 1 : e.code == 'ArrowDown' ? -1 : 0;
k = e.key || e.code,
d = k == 'ArrowUp' ? 1 : k == 'ArrowDown' ? -1 : 0;
if (anymod(e) || !d)
return;
@ -3421,6 +3427,8 @@ if (QS('#op_up2k.act'))
goto_up2k();
apply_perms({ "perms": perms, "frand": frand, "u2ts": u2ts });
if (ls0)
fileman.render();
(function () {

View file

@ -960,15 +960,95 @@ function f2f(val, nd) {
}
var HSZ_U = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
function humansize(b, terse) {
var i = 0, u = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
while (b >= 1000 && i < u.length - 1) {
b /= 1024;
i += 1;
}
var i = 0;
while (b >= 1000 && i < 5) { b /= 1024; i += 1; }
return (f2f(b, b >= 100 ? 0 : b >= 10 ? 1 : 2) +
' ' + (terse ? u[i].charAt(0) : u[i]));
' ' + (terse ? HSZ_U[i].charAt(0) : HSZ_U[i]));
}
function humansize_su(b) {
var i = 0;
while (b >= 1000 && i < 5) { b /= 1024; i += 1; }
return [b, HSZ_U[i]];
}
function humansize_0(b) {
return '' + b;
}
function humansize_1(b) {
return ('' + b).replace(/\B(?=(\d{3})+(?!\d))/g, " ");
}
function humansize_2g(b) {
var z = humansize_su(b), u = z[1].charAt(0); b = z[0];
return [f2f(b, b >= 100 ? 0 : b >= 10 ? 1 : 2) + ' ' + u, u];
}
function humansize_3g(b) {
var z = humansize_su(b), u = z[1].charAt(0); b = z[0];
return [f2f(b, b >= 10 ? 0 : 1) + ' ' + u, u];
}
function humansize_4g(b) {
var z = humansize_su(b), u = z[1]; b = z[0];
return [parseFloat(b.toFixed(b >= 100 ? 0 : b >= 10 ? 1 : 2)) + ' ' + u, u.charAt(0)];
}
function humansize_5g(b) {
var z = humansize_su(b), u = z[1]; b = z[0];
return [parseFloat(b.toFixed(b >= 10 ? 0 : 1)) + ' ' + u, u.charAt(0)];
}
function humansize_2(b) {
return humansize_2g(b)[0];
}
function humansize_3(b) {
return humansize_3g(b)[0];
}
function humansize_4(b) {
return humansize_4g(b)[0];
}
function humansize_5(b) {
return humansize_5g(b)[0];
}
function humansize_2c(b) {
var v = humansize_2g(b);
return '<span class="fsz_' + v[1].charAt(0) + '">' + v[0] + '</span>';
}
function humansize_3c(b) {
var v = humansize_3g(b);
return '<span class="fsz_' + v[1].charAt(0) + '">' + v[0] + '</span>';
}
function humansize_4c(b) {
var v = humansize_4g(b);
return '<span class="fsz_' + v[1].charAt(0) + '">' + v[0] + '</span>';
}
function humansize_5c(b) {
var v = humansize_5g(b);
return '<span class="fsz_' + v[1].charAt(0) + '">' + v[0] + '</span>';
}
function humansize_fuzzy(b) {
if (b <= 0) return "yes";
if (b <= 80) return "hullkort";
if (b <= 368640) return "5¼ DD";
if (b <= 1474560) return "save icon";
if (b <= 2880000) return "3½ Extended";
if (b <= 13107200) return "C90 Tape";
if (b <= 21000000) return "Floptical";
if (b <= 33554432) return "MPMan F10";
if (b <= 50000000) return "creditcardCD";
if (b <= 100663296) return "Zipdisk";
if (b <= 170000000) return "MD";
if (b <= 220200960) return "8cm CD";
if (b <= 737280000) return "CD-R";
if (b <= 900000000) return "UMD";
if (b <= 1300000000) return "GD-ROM";
if (b <= 4700000000) return "DVD";
if (b <= 9400000000) return "DVD-DL";
if (b <= 25025000000) return "BluRei";
if (b <= 50050000000) return "BD-DL";
return "LTO";
}
var humansize_fmts = ['0', '1', '2', '2c', '3', '3c', '4', '4c', '5', '5c', 'fuzzy'];
window.filesizefun = (function () {
var v = sread('fszfmt', humansize_fmts);
return window['humansize_' + (v || window.dfszf)] || humansize_1;
})();
function humantime(v) {
@ -1824,12 +1904,12 @@ var modal = (function () {
};
var onkey = function (e) {
var k = (e.code || e.key) + '',
var k = (e.key || e.code) + '',
eok = ebi('modal-ok'),
eng = ebi('modal-ng'),
ae = document.activeElement;
if (k == 'Space' && ae && (ae === eok || ae === eng))
if ((k == 'Space' || k == 'Spacebar' || k == ' ') && ae && (ae === eok || ae === eng))
k = 'Enter';
if (k.endsWith('Enter')) {
@ -2181,6 +2261,19 @@ function bchrome() {
}
bchrome();
var XC_CMSG = {
502: "bad gateway (server offline)",
503: "server offline",
504: "gateway timeout (server busy)",
529: "gateway timeout (server busy)",
520: "unknown error from server",
521: "server offline",
523: "server offline",
522: "proxy timeout (server busy)",
524: "proxy timeout (server busy)",
598: "proxy timeout (server busy)",
599: "proxy timeout (server busy)",
};
var cf_cha_t = 0;
function xhrchk(xhr, prefix, e404, lvl, tag) {
if (xhr.status < 400 && xhr.status >= 200)
@ -2223,5 +2316,8 @@ function xhrchk(xhr, prefix, e404, lvl, tag) {
document.body.appendChild(fr);
}
if (XC_CMSG[xhr.status] && (errtxt.indexOf('<html') + 1))
errtxt = XC_CMSG[xhr.status];
return fun(0, prefix + xhr.status + ": " + errtxt, tag);
}

View file

@ -1,3 +1,284 @@
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0929-2310 `v1.19.15` merry christmas
## 🧪 new features
* #184 add various human-readable formats for filesizes 234eddec
* search for files by their identifier ("wark"/checksum) 4e38e408
* and those are displayed in file-listings now too 456addf2
* PUT-upload with header `Replace` will overwrite any existing files 397ed565
* xbu/xau hooks can reject uploads with a custom message df0fa9d1
* #855 mDNS options to change the announced http/https port a3d95067
* #473 #383 custom favicons per-volume (.ico/png/gif/svg) 470b5048
* doesn't seem to work in internet explorer... ah whatever, go next
## 🩹 bugfixes
* #849 create IdP-db for `--idp-store` when necessary 80ca7851
* #859 cbz-thumbnailing had an accidental dependency on FFmpeg 983865d9
* docs: misleading markdown-expansion example e187df28
## 🔧 other changes
* #851 show a huge warning when copyparty accidentally detects a failing HDD and/or filesystem-corruption during indexing 6912e867 eb5d767b
* #870 improved discord video embeds (thx @tsuza!) f0ecb083
* #858 prefer reflinks (not hardlinks) in the `-ss` security option 57650a21
* improved controlpanel action-buttons layout 9f46e4db
## 🌠 fun facts
* includes (a tiny bit of) code written at [koie ramen](https://a.ocv.me/pub/g/2025/09/PXL_20250925_151716836.jpg)
* [according to Biltema](https://a.ocv.me/pub/g/2025/09/PXL_20250927_160446367~2.jpg), september is an excellent time to start decorating for xmas
<img src="https://a.ocv.me/pub/stuff/padoru.gif" alt="padoru" /> <img src="https://a.ocv.me/pub/stuff/padoru.gif" alt="padoru" /> <img src="https://a.ocv.me/pub/stuff/padoru.gif" alt="padoru" />
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0923-2247 `v1.19.14` Voile, the Magic Library
## 🧪 new features
* #779 add [OPDS](https://opds.io/) support (thx @Scotsguy!) 6dbd9901
* copyparty can now serve books for [KOReader](https://koreader.rocks/)
* [the mandatory soundtrack](https://www.youtube.com/watch?v=F8Aex6tzH-s)
* #786 add Turkish translation (thx @NandeMD!) 549fe33f
* #808 support reading config-files in UTF8-BOM 5e4ff90b
* make more http-errors return a friendly errortext rather than the scary wall of html 9d066414
## 🩹 bugfixes
* #842 could not navpane into webroot if webroot is unmapped 0941fd4e
* upload-resume becomes funky when the OS/network is overloaded to the point where it starts dropping connections left and right -- the issue was reported on discord and I don't have a good way to reproduce it, but these changes may help and/or fix it:
* b136a5b0 panic and drop chunk reservations if client or connection glitches out
* 38df223b also drop reservations if subchunk logic hits an edgecase
## 🔧 other changes
* [versus.md](https://github.com/9001/copyparty/blob/hovudstraum/docs/versus.md) tweaks:
* #840 tooltips in the table headers (thx @guano!) e9ca36fa
* #839 sftpgo updates (thx @augustanational!) a053a663
## 🌠 fun facts
* this release is identical to v1.19.13 except [the pypi package isn't messed up](https://github.com/9001/copyparty/issues/847) 👉😎👉
* as if the 13 wasn't foreshadowing enough
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0921-2211 `v1.19.12` conlangparty
## 🧪 new features
* #787 add Esperanto translation (thx @slashdevslashurandom!) 15d3c2fb
* #802 timezone can be specified for the rotf upload rule (thx @Lehmustus!) 1460fe97
## 🩹 bugfixes
* #837 sharing an entire HDD on Windows ([v1.19.9](https://github.com/9001/copyparty/releases/tag/v1.19.9) regression) 6a244320
* sharing your whole [【Dドライブ】](https://www.youtube.com/watch?v=BFfYrxm2t58) is once again possible
* TLNote: `Dドライブ` means "D:\ drive"
* if you can't upgrade, a workaround is global-option `casechk: n`
* `/?ls` on an unmapped root didn't give a sensible response; now it should be okay except it won't have a `cfg` field 8f6194fe
## 🔧 other changes
* #836 hide the unpost tab in folders where user has no write-access ca872c40
* #835 fix webdep buildscript to avoid an edgecase on some platforms (thx @25huizengek1!) 260da2f4
## 🌠 fun facts
* the esperanto translation was the final straw; `copyparty-sfx.py` is now 1 MiB large
* `copyparty-en.py` is still a comfy 759 KiB
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0920-1011 `v1.19.11` ftp fix
## 🩹 bugfixes
* #827 ftp on servers with unmapped root broke in v1.19.9 280815f1
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0919-2244 `v1.19.10` ramdisk kinshi
## 🧪 new features
* prevent uploading into ramdisks by default 59a01221 538a205c
* safeguard against misconfigured docker containers, where certain parts of the vfs has not been mapped to actual storage, for example `/w/music` is but `/w/` itself isn't
* can be disabled with `wram` (global-option and/or volflag), mainly for ephemeral servers
* #799 nixos: groups can be specified (thx @AnyTimeTraveler!) ee5f3190
* the logspam from the filesystem indexer can be reduced/disabled 478f1c76
* new options `scan-st-r`, `scan-pr-r`, `scan-pr-s`
## 🩹 bugfixes
* #809 medialinks (`#af-badf00d`) would fail on the very first pageload from a new browser 5996a58b
* #806 instructions for running on iOS was bad (thx @GhelloZ!) 35326a6f
## 🔧 other changes
* copyparty32.exe is now english-only, to save space 669b1075
* version info on startup indicates free-threading or not 65591528
* docs: explain the `daw` option better a043d7cf
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0915-0019 `v1.19.9` case-sensitivity, give or take
## 🧪 new features
* #781 case-sensitive behavior is now simulated on Windows/Macos/Fat32/NTFS 8b66874b
* avoids some of the scary issues associated with case-insensitive filesystems
* unfortunately this is expensive and may be **noticeably slower in large folders;** disable the safeguard with `casechk: n` if you know you don't need it
* #789 case-insensitive search for unicode filenames/paths (thx @km-clay!) e2aa8fc1 ecd18adc
* default-disabled because it is somewhat expensive; enable with global-option `srch-icase`
* [CB-1](https://codeberg.org/9001/copyparty/issues/1) add `--qr-stdout` and `--qr-stderr` to show qr-code even with `-q` d7887f3d
## 🩹 bugfixes
* #775 the basic-uploader didn't accept empty files 25749b4b
* opt-out from index.html with `?v` did not work as documented 3d09bec1
* Windows: dedup could get rejected by the filesystem if the origin file had a timestamp from the cambrian era e09f3c9e
* webdav would incorrectly return an error for Depth:0 on an unmapped root 3a2381ff
* markdown-editor would waste another http roundtrip on certain documents 14b7e514
* `--help` didn't render if terminal was non-UTF8 3f454927
## 🔧 other changes
* #788 fixed a hotkey typo in the imageviewer (thx @tkroo!) 5c1a43c7
* #778 improved polish translation (thx @daimond113!) 52438bcc
* #798 debian: fixed an issue in the systemd script (thx @Beethoven-n, and congrats on commit number 4000!) dfd9e007
* media-tag `conductor` is no longer mapped to `circle` (album-artist) 9c9e4057
* "download-selection-as-zip" now produces a better filename, `sel-FOLDERNAME.zip` instead of `FIRSTFILE.zip` 8f587627
* detect and warn if IdP volumes are misconfigured in a particular way 83bd1974
## 🌠 fun facts
* the themesong of this release is [KO3 - Give it up?](https://www.youtube.com/watch?v=8w_na7HAppU) because that's what the car mechanic got to enjoy when i forgot to unplug the flashdrive before handing in the shitbox for service
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0907-2300 `v1.19.8` SECURITY: fix single-file shares
## ⚠️ ATTN: this release fixes [CVE-2025-58753](https://github.com/9001/copyparty/security/advisories/GHSA-pxvw-4w88-6x95), an issue with shares
* when a share is created for just one or more files inside a folder, it was possible to access the other files inside that folder by guessing the filenames
* it was not possible to descend into subdirectories in this manner; only the sibling files were accessible
* NOTE: this does NOT affect filekeys; this is specifically regarding the `shr` global-option
## recent important news
* [v1.19.8 (2025-09-07)](https://github.com/9001/copyparty/releases/tag/v1.19.8) fixed [CVE-2025-58753](https://github.com/9001/copyparty/security/advisories/GHSA-pxvw-4w88-6x95) (a missing permission-check inside single-file shares)
* [v1.15.0 (2024-09-08)](https://github.com/9001/copyparty/releases/tag/v1.15.0) changed upload deduplication to be default-disabled
* [v1.14.3 (2024-08-30)](https://github.com/9001/copyparty/releases/tag/v1.14.3) fixed a bug that was introduced in v1.13.8 (2024-08-13); this bug could lead to **data loss** -- see the v1.14.3 release-notes for details
## 🧪 new features
* #761 IdP: option to replace the login/logout links and buttons with redirects into an IdP UI 09f22993
* #726 disk-usage and server-version can be selectively hidden according to user permissions 19a4c453
* option `--shr-who` / volflag `shr_who` decides who is able to create a share of that volume edafa158
* #751 nixos: add globalExtraConfig to specify repeatable config parameters (thx @xvrqt!) 09e3018b
* some very small speedups (mainly u2c and ancient python versions) 74821a38
* #759 #393 total folder size now decreases when files inside are deleted 96b109b0
* would previously require a reindex to get back on track
## 🩹 bugfixes
* fix [GHSA-pxvw-4w88-6x95](https://github.com/9001/copyparty/security/advisories/GHSA-pxvw-4w88-6x95) by fencing fileshares to just the shared files e0a92ba7
* #397 prevent hinting at valid passwords, even if they cannot be used to authenticate with 7a4ee4db
* #747 disable some features if `/tmp` must be used for runtime config e6755aa8
* the config-folder will now also be created with chmod 700 (accessible by owner only)
* #733 #298 fix hotkeys on non-qwerty keyboard layouts (dvorak etc.) e798a9a5
* #539 ftp-server: support clients which never does a CWD b0496311
* ignore the plaintext session-cookie on https; fixes some confusing behavior when switching from https to http c71128fd
* `og-ua` would prevent clients matching the pattern from accessing fullsize files
* `og-ua` was only possible to set globally; the `og_ua` volflag was ignored 422f8f62
* uds / unix-domain-sockets got wrong permissions when `rm-sck` was used e270fe60
* #727 macos: support running from config-files 230a1462
* #539 avoid issues if someone uploads a file with a last-modified timestamp from year -9999999999999 eeb7738b
* using the spacebar to pause a video was jank on chrome bfcb6eac
* block the next-song hotkey while a folder is loading f7e08ed0
* #748 fix rare js-panic when an action is aborted aaeec11f
* #738 bubbleparty: use /bin/bash (thx @ckastner!) 0469b5a2
## 🔧 other changes
* partyfuse: nice speedup by caching `readdir` too 06d2654b
* partyfuse: explain usage with usernames 1cdb3880
* connect-page: better examples when usernames enabled 3bdef75e
* docker: fix image annotations ab562382
## 🌠 fun facts
* konami's biggest legacy lives on f0caf881 bd6d1f96
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0828-2014 `v1.19.7` chdir
## 🧪 new features
* new option `chdir` to change the PWD (process working-directory) before volumes are mapped 14555d58
## 🩹 bugfixes
* fix using empty folders as statefile storage ([v1.19.6](https://github.com/9001/copyparty/releases/tag/v1.19.6) made this a bit too strict) 0d96786e
* holding I/K to scroll through folders quickly now works better 914686ec
## 🔧 other changes
* #717 docker: fix the image repo metadata (thx @EmilyxFox!) 6f087117
* docker: change `$HOME` to `/state` 01cf20a0 d1f75229
* and use the new `chdir` option to preserve old config-file semantics 14555d58
* helps avoid statefiles accidentally landing in `/w` as a consequence of misconfiguration
## 🌠 fun facts
* this release was made at [RevSpace NL](https://a.ocv.me/pub/g/nerd-stuff/PXL_20250828_202820075.jpg?cache)
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0827-2038 `v1.19.6` auth-precedence
## 🧪 new features
* #673 add Portuguese translation (thx anonymous!) 4b8c2215
* ...and enable the Polish translation (whoops) 8f235be6
* #689 add option to control authentication priority/precedence 543b7ea9
* url-parameter `?dl` forces file download instead of displaying in-browser 48d6224e
* #533 more ways to make the QR-code always-visible in the console 2848941e
* #695 option to log invalid xml from clients 28b93d79
* #552 configurable markdown newline behavior 0491123b
* and tweak the styling of monospace in links 68503444
## 🩹 bugfixes
* #628 FTP-server now accepts connections from IPv6 link-local addresses 978801d0
* incorrect assumption that all IPv6 link-local addresses start with `fe80` d39c74c1
* ftp: fix file rename d40f061a
* u2c: couldn't upload files located at the very top of the unix file hierarchy 599e82f2
* #699 markdown-editor: fix panic if the table-formatter is executed on something that isn't a table 4c042b3c
## 🔧 other changes
* #696 a volume can be one single file, not just folders aa1c9213
* #442 strongly prefer XDG_CONFIG_HOME as config location 35472557
* #691 album-art collected from audio-files can now become folder thumbnails 0b50fde3
* allow spaces in more of the comma-separated options d30240b4
* docs:
* mention config requirements for [syncing folders](https://github.com/9001/copyparty/#folder-sync) with u2c 6cd0a396 59f142cd
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-0821-2319 `v1.19.5` it runs on iOS

View file

@ -15,6 +15,7 @@
* [general](#general)
* [event hooks](#event-hooks) - on writing your own [hooks](../README.md#event-hooks)
* [hook effects](#hook-effects) - hooks can cause intentional side-effects
* [hook import](#hook-import) - the `I` flag runs the hook inside copyparty
* [assumptions](#assumptions)
* [mdns](#mdns)
* [sfx repack](#sfx-repack) - reduce the size of an sfx by removing features
@ -165,6 +166,7 @@ authenticate using header `Cookie: cppwd=foo` or url param `&pw=foo`
| GET | `?ls&dots` | list files/folders at URL as JSON, including dotfiles |
| GET | `?ls=t` | list files/folders at URL as plaintext |
| GET | `?ls=v` | list files/folders at URL, terminal-formatted |
| GET | `?opds` | list files/folders at URL as opds feed, for e-readers |
| GET | `?lt` | in listings, use symlink timestamps rather than targets |
| GET | `?b` | list files/folders at URL as simplified HTML |
| GET | `?tree=.` | list one level of subdirectories inside URL |
@ -245,6 +247,7 @@ upload modifiers:
| `Accept: json` | `want=json` | return upload info as json; same as `?j` |
| `Rand: 4` | `rand=4` | generate random filename with 4 characters |
| `Life: 30` | `life=30` | delete file after 30 seconds |
| `Replace: 1` | `replace` | overwrite file if exists |
| `CK: no` | `ck` | disable serverside checksum (maybe faster) |
| `CK: md5` | `ck=md5` | return md5 checksum instead of sha512 |
| `CK: sha1` | `ck=sha1` | return sha1 checksum |
@ -253,7 +256,9 @@ upload modifiers:
| `CK: b2s` | `ck=b2s` | return blake2s checksum |
* `life` only has an effect if the volume has a lifetime, and the volume lifetime must be greater than the file's
* `replace` upload-modifier:
* the header `replace: 1` works for both PUT and multipart-post
* the url-param `replace` only works for multipart-post
* server behavior of `msg` can be reconfigured with `--urlform`
## admin
@ -306,6 +311,14 @@ a subset of effect types are available for a subset of hook types,
to trigger indexing of files `/foo/1.txt` and `/foo/bar/2.txt`, a hook can `print(json.dumps({"idx":{"vp":["/foo/1.txt","/foo/bar/2.txt"]}}))` (and replace "idx" with "del" to delete instead)
* note: paths starting with `/` are absolute URLs, but you can also do `../3.txt` relative to the destination folder of each uploaded file
## hook import
the `I` flag runs the hook inside copyparty, which can be very useful and dangerous:
* around 140x faster because it doesn't need to launch a new subprocess
* the hook can intentionally (or accidentally) mess with copyparty's internals
* very easy to crash things if not careful
# assumptions
@ -357,10 +370,18 @@ pip install partftpy # tftp server
pip install impacket # smb server -- disable Windows Defender if you REALLY need this on windows
pip install Pillow pillow-heif # thumbnails
pip install pyvips # faster thumbnails
pip install psutil # better cleanup of stuck metadata parsers on windows
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
```
* on archlinux you can do this:
* `sudo pacman -Sy --needed python-{pip,isort,jinja,argon2-cffi,pyzmq,mutagen,pyftpdlib,pillow}`
* then, as user: `python3 -m pip install --user --break-system-packages -U strip_hints black==21.12b0 click==8.0.2`
* for building docker images: `sudo pacman -Sy --needed qemu-user-static{,-binfmt} podman{,-docker} jq`
* and if you want to run the python 2.7 tests:
* `git clone https://github.com/pyenv/pyenv .pyenv ; cd .pyenv/bin ; env PYTHON_CONFIGURE_OPTS='--enable-optimizations' PYTHON_CFLAGS='-march=native -mtune=native -std=c17' ./pyenv install 2.7.18 -v ; ln -s $HOME/.pyenv/versions/2.7.18/bin/python2 $HOME/bin/`
## just the sfx

View file

@ -6,15 +6,15 @@ L: MIT
https://github.com/pallets/jinja/
C: 2007 Pallets
L: BSD 3-Clause
L: BSD-3-Clause
https://github.com/pallets/markupsafe/
C: 2010 Pallets
L: BSD 3-Clause
L: BSD-3-Clause
https://github.com/paulc/dnslib/
C: 2010-2017 Paul Chakravarti
L: BSD 2-Clause
L: BSD-2-Clause
https://github.com/pydron/ifaddr/
C: 2014 Stefan C. Mueller
@ -36,6 +36,10 @@ https://github.com/ahupp/python-magic/
C: 2001-2014 Adam Hupp
L: MIT
https://github.com/fusepy/fusepy
C: 2012 Giorgos Verigakis
L: ISC
--- client-side --- software ---
https://github.com/Daninet/hash-wasm/
@ -50,6 +54,10 @@ https://github.com/feimosi/baguetteBox.js/
C: 2017 Marek Grzybek
L: MIT
https://github.com/cure53/DOMPurify/
C: 2025 Cure53 / Mario Heiderich
L: Apache-2.0 or MPL-2.0
https://github.com/markedjs/marked/
C: 2018+, MarkedJS
C: 2011-2018, Christopher Jeffrey (https://github.com/chjj/)
@ -68,8 +76,8 @@ L: MIT
https://github.com/adobe-fonts/source-code-pro/
C: 2010-2019 Adobe
L: SIL OFL 1.1
L: OFL-1.1
https://github.com/FortAwesome/Font-Awesome/
C: 2022 Fonticons, Inc.
L: SIL OFL 1.1
L: OFL-1.1

View file

@ -64,6 +64,8 @@ if you give it the value `@ASDF` it will try to open a file named ASDF and send
if the value starts with `%` it will assume a jinja2 template and expand it; the template has access to the `HttpCli` object through a property named `this` as well as everything in `j2a` and the stuff added by `self.j2s`; see [browser.html](https://github.com/9001/copyparty/blob/hovudstraum/copyparty/web/browser.html) for inspiration or look under the hood in [httpcli.py](https://github.com/9001/copyparty/blob/hovudstraum/copyparty/httpcli.py)
there is also `--html-head-s` and volflag `html_head_s` to add a plain static bit of text, possibly in addition to `--html-head`
# translations

View file

@ -85,19 +85,34 @@ currently up to date with [awesome-selfhosted](https://github.com/awesome-selfho
the table headers in the matrixes below are the different softwares, with a quick review of each software in the next section
the softwares,
* `a` = [copyparty](https://github.com/9001/copyparty)
* `b` = [hfs2](https://github.com/rejetto/hfs2/) 🔥
* `c` = [hfs3](https://rejetto.com/hfs/)
* `d` = [nextcloud](https://github.com/nextcloud/server)
* `e` = [seafile](https://github.com/haiwen/seafile)
* `f` = [rclone](https://github.com/rclone/rclone), specifically `rclone serve webdav .`
* `g` = [dufs](https://github.com/sigoden/dufs)
* `h` = [chibisafe](https://github.com/chibisafe/chibisafe)
* `i` = [kodbox](https://github.com/kalcaddle/kodbox)
* `j` = [filebrowser](https://github.com/filebrowser/filebrowser)
* `k` = [filegator](https://github.com/filegator/filegator)
* `l` = [sftpgo](https://github.com/drakkan/sftpgo)
* `m` = [arozos](https://github.com/tobychui/arozos)
[a]: https://github.com/9001/copyparty "copyparty"
[b]: https://github.com/rejetto/hfs2/ "hfs2"
[c]: https://rejetto.com/hfs/ "hfs3"
[d]: https://github.com/nextcloud/server "nextcloud"
[e]: https://github.com/haiwen/seafile "seafile"
[f]: https://github.com/rclone/rclone "rclone"
[g]: https://github.com/sigoden/dufs "dufs"
[h]: https://github.com/chibisafe/chibisafe "chibisafe"
[i]: https://github.com/kalcaddle/kodbox "kodbox"
[j]: https://github.com/filebrowser/filebrowser "filebrowser"
[k]: https://github.com/filegator/filegator "filegator"
[l]: https://github.com/drakkan/sftpgo "sftpgo"
[m]: https://github.com/tobychui/arozos "arozos"
* `a` = [copyparty][a]
* `b` = [hfs2][b] 🔥
* `c` = [hfs3][c]
* `d` = [nextcloud][d]
* `e` = [seafile][e]
* `f` = [rclone][f], specifically `rclone serve webdav .`
* `g` = [dufs][g]
* `h` = [chibisafe][h]
* `i` = [kodbox][i]
* `j` = [filebrowser][j]
* `k` = [filegator][k]
* `l` = [sftpgo][l]
* `m` = [arozos][m]
some softwares not in the matrixes,
* [updog](#updog)
@ -119,13 +134,13 @@ symbol legend,
## general
| feature / software | a | b | c | d | e | f | g | h | i | j | k | l | m |
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
| intuitive UX | | | █ | █ | █ | | █ | █ | █ | █ | █ | █ | █ |
| config GUI | | █ | █ | █ | █ | | | █ | █ | █ | | █ | █ |
| good documentation | | | █ | █ | █ | █ | █ | | | █ | █ | | |
| runs on iOS | | | | | | | | | | | | | |
| runs on Android | █ | | █ | | | █ | | | | | | | |
| runs on Android | █ | | █ | | | █ | | | | | | | |
| runs on WinXP | █ | █ | | | | █ | | | | | | | |
| runs on Windows | █ | █ | █ | █ | █ | █ | █ | | █ | █ | █ | █ | |
| runs on Linux | █ | | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ |
@ -146,7 +161,9 @@ symbol legend,
* `b`/hfs2 runs on linux through wine
* `f`/rclone must be started with the command `rclone serve webdav .` or similar
* `h`/chibisafe has undocumented windows support
* `i`/sftpgo must be launched with a command
* `l`/sftpgo:
* Must be launched with a command
* On Termux, just run `pkg in sftpgo`
* `m`/arozos has partial windows support
@ -154,7 +171,7 @@ symbol legend,
*the thing that copyparty is actually kinda good at*
| feature / software | a | b | c | d | e | f | g | h | i | j | k | l | m |
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
| download folder as zip | █ | █ | █ | █ | | | █ | | █ | █ | | █ | |
| download folder as tar | █ | | | | | | | | | | | | |
@ -218,7 +235,7 @@ symbol legend,
## protocols and client support
| feature / software | a | b | c | d | e | f | g | h | i | j | k | l | m |
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
| serve https | █ | | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ |
| serve webdav | █ | | | █ | █ | █ | █ | | █ | | | █ | █ |
@ -249,7 +266,7 @@ symbol legend,
## server configuration
| feature / software | a | b | c | d | e | f | g | h | i | j | k | l | m |
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
| config from cmd args | █ | | █ | | | █ | █ | | | █ | | | |
| config files | █ | █ | █ | | | █ | | █ | | █ | • | | |
@ -262,7 +279,7 @@ symbol legend,
* `folder-rproxy` = reverse-proxying without dedicating an entire (sub)domain, using a subfolder instead
* `l`/sftpgo:
* config: users must be added through gui / api calls
* config: user can be added by cmd command in [Portable mode](https://docs.sftpgo.com/2.6/cli/#portable-mode); if not in Portable mode users must be added through gui / api calls
* `m`/arozos:
* configuration is primarily through GUI
* reverse-proxy is not guaranteed to see the correct client IP
@ -270,7 +287,7 @@ symbol legend,
## server capabilities
| feature / software | a | b | c | d | e | f | g | h | i | j | k | l | m |
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
| accounts | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ |
| per-account chroot | | | | | | | | | | | | █ | |
@ -280,7 +297,7 @@ symbol legend,
| per-volume permissions | █ | █ | █ | █ | █ | █ | █ | | █ | █ | | █ | █ |
| per-folder permissions | | | █ | █ | █ | | █ | | █ | █ | | █ | █ |
| per-file permissions | | | █ | █ | █ | | █ | | █ | | | | █ |
| per-file passwords | █ | | | █ | █ | | █ | | █ | | | | █ |
| per-file passwords | █ | | | █ | █ | | █ | | █ | | | | █ |
| unmap subfolders | █ | | █ | | | | █ | | | █ | | • | |
| index.html blocks list | | | | | | | █ | | | • | | | |
| write-only folders | █ | | █ | | █ | | | | | | █ | █ | |
@ -337,23 +354,23 @@ symbol legend,
## client features
| feature / software | a | b | c | d | e | f | g | h | i | j | k | l | m |
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
| ---------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
| single-page app | █ | | █ | █ | █ | | | █ | █ | █ | █ | | █ |
| themes | █ | █ | █ | █ | | | | | █ | | | | |
| directory tree nav | █ | | | | █ | | | | █ | | | | |
| multi-column sorting | █ | | | | | | | | | | | | |
| multi-column sorting | █ | | | | | | | | | | | | |
| thumbnails | █ | | / | | | | | █ | █ | | | | █ |
| ┗ image thumbnails | █ | | / | █ | █ | | | █ | █ | █ | | | █ |
| ┗ video thumbnails | █ | | | █ | █ | | | | █ | | | | █ |
| ┗ audio spectrograms | █ | | | | | | | | | | | | |
| audio player | █ | | | █ | █ | | | | █ | | | | █ |
| audio player | █ | | | █ | █ | | | | █ | | | | █ |
| ┗ gapless playback | █ | | | | | | | | • | | | | |
| ┗ audio equalizer | █ | | | | | | | | | | | | |
| ┗ waveform seekbar | █ | | | | | | | | | | | | |
| ┗ OS integration | █ | | █ | | | | | | | | | | |
| ┗ transcode to lossy | █ | | | | | | | | | | | | |
| video player | █ | | █ | █ | █ | | | | █ | █ | | | █ |
| video player | █ | | █ | █ | █ | | | | █ | █ | | | █ |
| ┗ video transcoding | | | / | | | | | | █ | | | | |
| audio BPM detector | █ | | | | | | | | | | | | |
| audio key detector | █ | | | | | | | | | | | | |
@ -366,16 +383,16 @@ symbol legend,
| find local file | █ | | | | | | | | | | | | |
| undo recent uploads | █ | | | | | | | | | | | | |
| create directories | █ | | █ | █ | █ | | █ | █ | █ | █ | █ | █ | █ |
| image viewer | █ | | █ | █ | █ | | | | █ | █ | █ | | █ |
| image viewer | █ | | █ | █ | █ | | | | █ | █ | █ | | █ |
| markdown viewer | █ | | / | | █ | | | | █ | | | | █ |
| markdown editor | █ | | | | █ | | | | █ | | | | █ |
| markdown editor | █ | | | | █ | | | | █ | | | | █ |
| readme.md in listing | █ | | / | █ | | | | | | | | | |
| rename files | █ | █ | █ | █ | █ | | █ | | █ | █ | █ | █ | █ |
| batch rename | █ | | | | | | | | █ | | | | |
| cut / paste files | █ | █ | █ | █ | █ | | | | █ | | | | █ |
| move files | █ | █ | █ | █ | █ | | █ | | █ | █ | █ | | █ |
| move files | █ | █ | █ | █ | █ | | █ | | █ | █ | █ | | █ |
| delete files | █ | █ | █ | █ | █ | | █ | █ | █ | █ | █ | █ | █ |
| copy files | | | / | | █ | | | | █ | █ | █ | | █ |
| copy files | | | / | | █ | | | | █ | █ | █ | | █ |
* `single-page app` = multitasking; possible to continue navigating while uploading
* `audio player » os-integration` = use the [lockscreen](https://user-images.githubusercontent.com/241032/142711926-0700be6c-3e31-47b3-9928-53722221f722.png) or [media hotkeys](https://user-images.githubusercontent.com/241032/215347492-b4250797-6c90-4e09-9a4c-721edf2fb15c.png) to play/pause, prev/next song
@ -389,11 +406,14 @@ symbol legend,
* audio playback does not continue into next song
* plaintext viewer/editor
* `k`/filegator directory tree is a modal window
* `l`/sftpgo remarks:
* audio/video playback does not continue into next song/video
* plaintext viewer/editor
## integration
| feature / software | a | b | c | d | e | f | g | h | i | j | k | l | m |
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
| OS alert on upload | | | | | | | | | | | | | |
| discord | | | | | | | | | | | | | |
@ -614,10 +634,8 @@ symbol legend,
* ⚠️ across the atlantic, copyparty is 2.5x faster
* 🔵 sftp uploads are resumable
* ⚠️ web UI is very minimal + a bit slow
* ⚠️ no thumbnails / image viewer / audio player
* ⚠️ basic file manager (no cut/paste/move)
* ⚠️ no thumbnails
* ⚠️ no filesystem indexing / search
* ⚠️ doesn't run on phones, tablets
* ⚠️ no zeroconf (mdns/ssdp)
* ⚠️ impractical directory URLs
* ⚠️ AGPL licensed

View file

@ -93,6 +93,7 @@ copyparty = [
"web/*.js",
"web/*.css",
"web/*.html",
"web/*.xml",
"web/a/*.bat",
"web/deps/*.gz",
"web/deps/*.woff*",

View file

@ -3,7 +3,7 @@ WORKDIR /z
ENV ver_asmcrypto=c72492f4a66e17a0e5dd8ad7874de354f3ccdaa5 \
ver_hashwasm=4.12.0 \
ver_marked=4.3.0 \
ver_dompf=3.2.6 \
ver_dompf=3.2.7 \
ver_mde=2.18.0 \
ver_codemirror=5.65.18 \
ver_fontawesome=5.13.0 \
@ -41,25 +41,25 @@ RUN mkdir -p /z/dist/no-pk \
&& (mkdir hash-wasm \
&& cd hash-wasm \
&& unzip ../hash-wasm.zip) \
&& (tar -xf asmcrypto.tgz \
&& (tar --no-same-owner -xf asmcrypto.tgz \
&& cd asmcrypto.js-$ver_asmcrypto \
&& npm install ) \
&& (tar -xf marked.tgz \
&& (tar --no-same-owner -xf marked.tgz \
&& cd marked-$ver_marked \
&& npm install \
&& npm i grunt uglify-js -g ) \
&& (tar -xf codemirror.tgz \
&& (tar --no-same-owner -xf codemirror.tgz \
&& cd codemirror5-$ver_codemirror \
&& npm install ) \
&& (tar -xf mde.tgz \
&& (tar --no-same-owner -xf mde.tgz \
&& cd easy-markdown-editor* \
&& npm install \
&& npm i gulp-cli -g ) \
&& tar -xf dompurify.tgz \
&& tar -xf prism.tgz \
&& tar -xf fusepy.tgz \
&& tar --no-same-owner -xf dompurify.tgz \
&& tar --no-same-owner -xf prism.tgz \
&& tar --no-same-owner -xf fusepy.tgz \
&& unzip fontawesome.zip \
&& tar -xf zopfli.tgz
&& tar --no-same-owner -xf zopfli.tgz
COPY busy-mp3.sh /z/
@ -68,7 +68,7 @@ RUN /z/busy-mp3.sh \
# build fonttools (which needs zopfli)
RUN tar -xf zopfli.tgz \
RUN tar --no-same-owner -xf zopfli.tgz \
&& cd zopfli* \
&& cmake \
-DCMAKE_INSTALL_PREFIX=/usr \

View file

@ -1,7 +1,7 @@
FROM alpine:latest
WORKDIR /z
LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \
org.opencontainers.image.source="https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker" \
org.opencontainers.image.source="https://github.com/9001/copyparty" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.title="copyparty-ac" \
org.opencontainers.image.description="copyparty with Pillow and FFmpeg (image/audio/video thumbnails, audio transcoding, media tags)"
@ -16,6 +16,6 @@ COPY i/dist/copyparty-sfx.py innvikler.sh ./
ADD base ./base
RUN ash innvikler.sh ac
WORKDIR /w
WORKDIR /state
EXPOSE 3923
ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "-c", "/z/initcfg"]
ENTRYPOINT ["python3", "-m", "copyparty", "-c", "/z/initcfg"]

View file

@ -1,7 +1,7 @@
FROM alpine:latest
WORKDIR /z
LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \
org.opencontainers.image.source="https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker" \
org.opencontainers.image.source="https://github.com/9001/copyparty" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.title="copyparty-dj" \
org.opencontainers.image.description="copyparty with all optional dependencies, including musical key / bpm detection"
@ -38,6 +38,6 @@ COPY i/dist/copyparty-sfx.py innvikler.sh ./
ADD base ./base
RUN ash innvikler.sh dj
WORKDIR /w
WORKDIR /state
EXPOSE 3923
ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "-c", "/z/initcfg"]
ENTRYPOINT ["python3", "-m", "copyparty", "-c", "/z/initcfg"]

View file

@ -1,7 +1,7 @@
FROM alpine:latest
WORKDIR /z
LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \
org.opencontainers.image.source="https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker" \
org.opencontainers.image.source="https://github.com/9001/copyparty" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.title="copyparty-im" \
org.opencontainers.image.description="copyparty with Pillow and Mutagen (image thumbnails, media tags)"
@ -15,6 +15,6 @@ COPY i/dist/copyparty-sfx.py innvikler.sh ./
ADD base ./base
RUN ash innvikler.sh im
WORKDIR /w
WORKDIR /state
EXPOSE 3923
ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "-c", "/z/initcfg"]
ENTRYPOINT ["python3", "-m", "copyparty", "-c", "/z/initcfg"]

View file

@ -1,7 +1,7 @@
FROM alpine:latest
WORKDIR /z
LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \
org.opencontainers.image.source="https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker" \
org.opencontainers.image.source="https://github.com/9001/copyparty" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.title="copyparty-iv" \
org.opencontainers.image.description="copyparty with Pillow, FFmpeg, libvips (image/audio/video thumbnails, audio transcoding, media tags)"
@ -28,6 +28,6 @@ COPY i/dist/copyparty-sfx.py innvikler.sh ./
ADD base ./base
RUN ash innvikler.sh iv
WORKDIR /w
WORKDIR /state
EXPOSE 3923
ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "-c", "/z/initcfg"]
ENTRYPOINT ["python3", "-m", "copyparty", "-c", "/z/initcfg"]

View file

@ -1,7 +1,7 @@
FROM alpine:latest
WORKDIR /z
LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \
org.opencontainers.image.source="https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker" \
org.opencontainers.image.source="https://github.com/9001/copyparty" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.title="copyparty-min" \
org.opencontainers.image.description="just copyparty, no thumbnails / media tags / audio transcoding"
@ -13,6 +13,6 @@ RUN apk --no-cache add !pyc \
COPY i/dist/copyparty-sfx.py innvikler.sh ./
RUN ash innvikler.sh min
WORKDIR /w
WORKDIR /state
EXPOSE 3923
ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "--no-thumb", "-c", "/z/initcfg"]
ENTRYPOINT ["python3", "-m", "copyparty", "--no-thumb", "-c", "/z/initcfg"]

View file

@ -1,6 +1,19 @@
#!/bin/ash
set -ex
tmv() {
touch -r "$1" t
mv t "$1"
}
iawk() {
awk "$1" <"$2" >t
tmv "$2"
}
ised() {
sed -r "$1" <"$2" >t
tmv "$2"
}
# use zlib-ng if available
f=/z/base/zlib_ng-0.5.1-cp312-cp312-linux_$(cat /etc/apk/arch).whl
[ "$1" != min ] && [ -e $f ] && {
@ -15,9 +28,15 @@ rm -rf /z/base
rm -rf /var/cache/apk/* /root/.cache
# initial config; common for all flavors
mkdir /cfg /w
chmod 777 /cfg /w
echo % /cfg > initcfg
mkdir /state /cfg /w
chmod 777 /state /cfg /w
cat >initcfg <<'EOF'
[global]
chdir: /w
no-crt
% /cfg
EOF
# unpack sfx and dive in
python3 copyparty-sfx.py --version
@ -32,8 +51,13 @@ rm -rf \
/tmp/pe-* /z/copyparty-sfx.py \
ensurepip pydoc_data turtle.py turtledemo lib2to3
cd /usr/lib/python3.*/site-packages/copyparty/
rm stolen/surrogateescape.py
iawk '/^[^ ]/{s=0}/^if not VENDORED:/{s=1}!s' qrkode.py
iawk '/^[^ ]/{s=0}/^ DNS_VND = False/{s=1;print" raise"}!s' mdns.py
# speedhack
sed -ri 's/os.environ.get\("PRTY_NO_IMPRESO"\)/"1"/' /usr/lib/python3.*/site-packages/copyparty/util.py
ised 's/os.environ.get\("PRTY_NO_IMPRESO"\)/"1"/' util.py
# drop bytecode
find / -xdev -name __pycache__ -print0 | xargs -0 rm -rf
@ -46,6 +70,12 @@ find -name __pycache__ |
grep -E 'ty/web/|/pycpar' |
tr '\n' '\0' | xargs -0 rm -rf
smoketest() {
# two-for-one:
# 1) smoketest copyparty even starts
# 2) build any bytecode we missed
@ -82,5 +112,95 @@ kill $pid; wait $pid
# output from -e2d
rm -rf .hist /cfg/copyparty
}
smoketest
[ "$1" == min ] && {
# shrink amd64 from 45.5 to 33.2 MiB
# libstdc++ is pulled in by libmpdec++ in libmpdec; keep libmpdec.so
cd /usr/lib ; rm -rf \
libmpdec++.so* \
libncurses* \
libpanelw* \
libreadline* \
libstdc++.so* \
--
cd /usr/lib/python3.*/lib-dynload/ ; rm -rf \
*audioop.* \
_asyncio.* \
_ctypes_test.* \
_curses* \
_test* \
_xx* \
ossaudio.* \
readline.* \
xx* \
--
# keep http/client for u2c
cd /usr/lib/python3.*/ ; rm -rf \
site-packages/*.dist-info \
aifc.py \
asyncio \
bdb.py \
cgi.py \
config-3.*/Makefile \
ctypes/macholib \
dbm \
difflib.py \
doctest.py \
email/_header_value_parser.py \
html \
http/cookiejar.* \
http/server.* \
imaplib.py \
importlib/resources \
mailbox.py \
nntplib.py \
pickletools.py \
pydoc.py \
smtplib.py \
statistics.py \
tomllib \
unittest \
urllib/request.* \
venv \
wsgiref \
xml/dom \
xml/sax \
xmlrpc \
--
set +x
find -iname '*.pyc' |
grep -viE 'tftpy' |
while IFS= read -r x; do
y="$(printf '%s\n' "$x" | sed -r 's`/__pycache__/([^/]+)\.cpython-312\.pyc$`/\1.py`')"
[ -e "$y" ] || continue
[ "$y" = "$x" ] && continue
rm "$y"
mv "$x" "${y}c"
done
find -iname __pycache__ -print0 | xargs -0 rm -rf --
rm -rf /a
set -x
smoketest
# printf '%s\n' 'FROM localhost/copyparty-min-amd64' 'COPY a /' 'RUN /bin/ash /a' >Dockerfile
# podman rmi localhost/m2 ; podman build --squash-all -t m2 . && podman images && podman run --rm -it localhost/m2 --exit=idx && podman images
}
# goodbye
exec rm innvikler.sh

View file

@ -18,6 +18,13 @@ ngs=(
dj-{ppc64le,s390x,arm}
)
err=
for x in awk jq podman python3 tar wget ; do
command -v $x >/dev/null && continue
err=1; echo ERROR: missing dependency: $x
done
[ $err ] && exit 1
for v in "$@"; do
[ "$v" = clean ] && clean=1
[ "$v" = hclean ] && hclean=1
@ -56,7 +63,7 @@ filt=
for a in $sarchs; do # arm/v6
podman pull --arch=$a alpine:latest
done
podman images --format "{{.ID}} {{.History}}" |
awk '/library\/alpine/{print$1}' |
while read id; do
@ -102,12 +109,18 @@ filt=
# arm takes forever so make it top priority
[ ${a::3} == arm ] && nice= || nice=-n20
# not sure if this is necessary or if inherit-annotations=false was enough, but won't hurt
readarray -t annot < <(awk <Dockerfile.$i '/org.opencontainers.image/{sub(/[^\.]+/,"");sub(/[" \\]+$/,"");sub(/"/,"");print"--annotation";print"org"$0}')
annot+=( --annotation "org.opencontainers.image.created=$( date -u +%Y-%m-%dT%H:%M:%SZ )" )
# --pull=never does nothing at all btw
(set -x
nice $nice podman build \
--squash \
--pull=never \
--from localhost/alpine-$a \
--inherit-annotations=false \
"${annot[@]}" \
-t copyparty-$i-$a$suf \
-f Dockerfile.$i . ||
(echo $? $i-$a >> err; printf '%096d\n' $(seq 1 42))

View file

@ -16,6 +16,8 @@ lics = [
"MIT License",
"BSD 2-Clause License",
"BSD 3-Clause License",
"ISC License",
"Apache License v2.0",
"SIL Open Font License v1.1",
]

View file

@ -1,39 +1,3 @@
CERNZOYR
Crezvffvba gb hfr, pbcl, zbqvsl, naq/be qvfgevohgr guvf fbsgjner sbe nal checbfr jvgu be jvgubhg srr vf urerol tenagrq, cebivqrq gung gur nobir pbclevtug abgvpr naq guvf crezvffvba abgvpr nccrne va nyy pbcvrf.
Gur tbnyf bs gur Bcra Sbag Yvprafr (BSY) ner gb fgvzhyngr jbeyqjvqr qrirybczrag bs pbyynobengvir sbag cebwrpgf, gb fhccbeg gur sbag perngvba rssbegf bs npnqrzvp naq yvathvfgvp pbzzhavgvrf, naq gb cebivqr n serr naq bcra senzrjbex va juvpu sbagf znl or funerq naq vzcebirq va cnegarefuvc jvgu bguref.
Gur BSY nyybjf gur yvprafrq sbagf gb or hfrq, fghqvrq, zbqvsvrq naq erqvfgevohgrq serryl nf ybat nf gurl ner abg fbyq ol gurzfryirf. Gur sbagf, vapyhqvat nal qrevingvir jbexf, pna or ohaqyrq, rzorqqrq, erqvfgevohgrq naq/be fbyq jvgu nal fbsgjner cebivqrq gung nal erfreirq anzrf ner abg hfrq ol qrevingvir jbexf. Gur sbagf naq qrevingvirf, ubjrire, pnaabg or eryrnfrq haqre nal bgure glcr bs yvprafr. Gur erdhverzrag sbe sbagf gb erznva haqre guvf yvprafr qbrf abg nccyl gb nal qbphzrag perngrq hfvat gur sbagf be gurve qrevingvirf.
QRSVAVGVBAF
"Sbag Fbsgjner" ersref gb gur frg bs svyrf eryrnfrq ol gur Pbclevtug Ubyqre(f) haqre guvf yvprafr naq pyrneyl znexrq nf fhpu. Guvf znl vapyhqr fbhepr svyrf, ohvyq fpevcgf naq qbphzragngvba.
"Erfreirq Sbag Anzr" ersref gb nal anzrf fcrpvsvrq nf fhpu nsgre gur pbclevtug fgngrzrag(f).
"Bevtvany Irefvba" ersref gb gur pbyyrpgvba bs Sbag Fbsgjner pbzcbaragf nf qvfgevohgrq ol gur Pbclevtug Ubyqre(f).
"Zbqvsvrq Irefvba" ersref gb nal qrevingvir znqr ol nqqvat gb, qryrgvat, be fhofgvghgvat - va cneg be va jubyr - nal bs gur pbzcbaragf bs gur Bevtvany Irefvba, ol punatvat sbezngf be ol cbegvat gur Sbag Fbsgjner gb n arj raivebazrag.
"Nhgube" ersref gb nal qrfvtare, ratvarre, cebtenzzre, grpuavpny jevgre be bgure crefba jub pbagevohgrq gb gur Sbag Fbsgjner.
CREZVFFVBA & PBAQVGVBAF
Crezvffvba vf urerol tenagrq, serr bs punetr, gb nal crefba bognvavat n pbcl bs gur Sbag Fbsgjner, gb hfr, fghql, pbcl, zretr, rzorq, zbqvsl, erqvfgevohgr, naq fryy zbqvsvrq naq hazbqvsvrq pbcvrf bs gur Sbag Fbsgjner, fhowrpg gb gur sbyybjvat pbaqvgvbaf:
1) Arvgure gur Sbag Fbsgjner abe nal bs vgf vaqvivqhny pbzcbaragf, va Bevtvany be Zbqvsvrq Irefvbaf, znl or fbyq ol vgfrys.
2) Bevtvany be Zbqvsvrq Irefvbaf bs gur Sbag Fbsgjner znl or ohaqyrq, erqvfgevohgrq naq/be fbyq jvgu nal fbsgjner, cebivqrq gung rnpu pbcl pbagnvaf gur nobir pbclevtug abgvpr naq guvf yvprafr. Gurfr pna or vapyhqrq rvgure nf fgnaq-nybar grkg svyrf, uhzna-ernqnoyr urnqref be va gur nccebcevngr znpuvar-ernqnoyr zrgnqngn svryqf jvguva grkg be ovanel svyrf nf ybat nf gubfr svryqf pna or rnfvyl ivrjrq ol gur hfre.
3) Ab Zbqvsvrq Irefvba bs gur Sbag Fbsgjner znl hfr gur Erfreirq Sbag Anzr(f) hayrff rkcyvpvg jevggra crezvffvba vf tenagrq ol gur pbeerfcbaqvat Pbclevtug Ubyqre. Guvf erfgevpgvba bayl nccyvrf gb gur cevznel sbag anzr nf cerfragrq gb gur hfref.
4) Gur anzr(f) bs gur Pbclevtug Ubyqre(f) be gur Nhgube(f) bs gur Sbag Fbsgjner funyy abg or hfrq gb cebzbgr, raqbefr be nqiregvfr nal Zbqvsvrq Irefvba, rkprcg gb npxabjyrqtr gur pbagevohgvba(f) bs gur Pbclevtug Ubyqre(f) naq gur Nhgube(f) be jvgu gurve rkcyvpvg jevggra crezvffvba.
5) Gur Sbag Fbsgjner, zbqvsvrq be hazbqvsvrq, va cneg be va jubyr, zhfg or qvfgevohgrq ragveryl haqre guvf yvprafr, naq zhfg abg or qvfgevohgrq haqre nal bgure yvprafr. Gur erdhverzrag sbe sbagf gb erznva haqre guvf yvprafr qbrf abg nccyl gb nal qbphzrag perngrq hfvat gur Sbag Fbsgjner.
GREZVANGVBA
Guvf yvprafr orpbzrf ahyy naq ibvq vs nal bs gur nobir pbaqvgvbaf ner abg zrg.
QVFPYNVZRE
GUR SBAG FBSGJNER VF CEBIVQRQ "NF VF", JVGUBHG JNEENAGL BS NAL XVAQ, RKCERFF BE VZCYVRQ, VAPYHQVAT OHG ABG YVZVGRQ GB NAL JNEENAGVRF BS ZREPUNAGNOVYVGL, SVGARFF SBE N CNEGVPHYNE CHECBFR NAQ ABAVASEVATRZRAG BS PBCLEVTUG, CNGRAG, GENQRZNEX, BE BGURE EVTUG. VA AB RIRAG FUNYY GUR PBCLEVTUG UBYQRE OR YVNOYR SBE NAL PYNVZ, QNZNTRF BE BGURE YVNOVYVGL, VAPYHQVAT NAL TRARENY, FCRPVNY, VAQVERPG, VAPVQRAGNY, BE PBAFRDHRAGVNY QNZNTRF, JURGURE VA NA NPGVBA BS PBAGENPG, GBEG BE BGUREJVFR, NEVFVAT SEBZ, BHG BS GUR HFR BE VANOVYVGL GB HFR GUR SBAG FBSGJNER BE SEBZ BGURE QRNYVATF VA GUR SBAG FBSGJNER.
GUR FBSGJNER VF CEBIVQRQ "NF VF" NAQ VFP QVFPYNVZF NYY JNEENAGVRF JVGU ERTNEQ GB GUVF FBSGJNER VAPYHQVAT NYY VZCYVRQ JNEENAGVRF BS ZREPUNAGNOVYVGL NAQ SVGARFF. VA AB RIRAG FUNYY VFP OR YVNOYR SBE NAL FCRPVNY, QVERPG, VAQVERPG, BE PBAFRDHRAGVNY QNZNTRF BE NAL QNZNTRF JUNGFBRIRE ERFHYGVAT SEBZ YBFF BS HFR, QNGN BE CEBSVGF, JURGURE VA NA NPGVBA BS PBAGENPG, ARTYVTRAPR BE BGURE GBEGVBHF NPGVBA, NEVFVAT BHG BS BE VA PBAARPGVBA JVGU GUR HFR BE CRESBEZNAPR BS GUVF FBSGJNER.

53
scripts/lics/5.r13 Normal file
View file

@ -0,0 +1,53 @@
Ncnpur Yvprafr
Irefvba 2.0, Wnahnel 2004
uggc://jjj.ncnpur.bet/yvprafrf/
GREZF NAQ PBAQVGVBAF SBE HFR, ERCEBQHPGVBA, NAQ QVFGEVOHGVBA
1. Qrsvavgvbaf.
"Yvprafr" funyy zrna gur grezf naq pbaqvgvbaf sbe hfr, ercebqhpgvba, naq qvfgevohgvba nf qrsvarq ol Frpgvbaf 1 guebhtu 9 bs guvf qbphzrag.
"Yvprafbe" funyy zrna gur pbclevtug bjare be ragvgl nhgubevmrq ol gur pbclevtug bjare gung vf tenagvat gur Yvprafr.
"Yrtny Ragvgl" funyy zrna gur havba bs gur npgvat ragvgl naq nyy bgure ragvgvrf gung pbageby, ner pbagebyyrq ol, be ner haqre pbzzba pbageby jvgu gung ragvgl. Sbe gur checbfrf bs guvf qrsvavgvba, "pbageby" zrnaf (v) gur cbjre, qverpg be vaqverpg, gb pnhfr gur qverpgvba be znantrzrag bs fhpu ragvgl, jurgure ol pbagenpg be bgurejvfr, be (vv) bjarefuvc bs svsgl creprag (50%) be zber bs gur bhgfgnaqvat funerf, be (vvv) orarsvpvny bjarefuvc bs fhpu ragvgl.
"Lbh" (be "Lbhe") funyy zrna na vaqvivqhny be Yrtny Ragvgl rkrepvfvat crezvffvbaf tenagrq ol guvf Yvprafr.
"Fbhepr" sbez funyy zrna gur cersreerq sbez sbe znxvat zbqvsvpngvbaf, vapyhqvat ohg abg yvzvgrq gb fbsgjner fbhepr pbqr, qbphzragngvba fbhepr, naq pbasvthengvba svyrf.
"Bowrpg" sbez funyy zrna nal sbez erfhygvat sebz zrpunavpny genafsbezngvba be genafyngvba bs n Fbhepr sbez, vapyhqvat ohg abg yvzvgrq gb pbzcvyrq bowrpg pbqr, trarengrq qbphzragngvba, naq pbairefvbaf gb bgure zrqvn glcrf.
"Jbex" funyy zrna gur jbex bs nhgubefuvc, jurgure va Fbhepr be Bowrpg sbez, znqr ninvynoyr haqre gur Yvprafr, nf vaqvpngrq ol n pbclevtug abgvpr gung vf vapyhqrq va be nggnpurq gb gur jbex (na rknzcyr vf cebivqrq va gur Nccraqvk orybj).
"Qrevingvir Jbexf" funyy zrna nal jbex, jurgure va Fbhepr be Bowrpg sbez, gung vf onfrq ba (be qrevirq sebz) gur Jbex naq sbe juvpu gur rqvgbevny erivfvbaf, naabgngvbaf, rynobengvbaf, be bgure zbqvsvpngvbaf ercerfrag, nf n jubyr, na bevtvany jbex bs nhgubefuvc. Sbe gur checbfrf bs guvf Yvprafr, Qrevingvir Jbexf funyy abg vapyhqr jbexf gung erznva frcnenoyr sebz, be zreryl yvax (be ovaq ol anzr) gb gur vagresnprf bs, gur Jbex naq Qrevingvir Jbexf gurerbs.
"Pbagevohgvba" funyy zrna nal jbex bs nhgubefuvc, vapyhqvat gur bevtvany irefvba bs gur Jbex naq nal zbqvsvpngvbaf be nqqvgvbaf gb gung Jbex be Qrevingvir Jbexf gurerbs, gung vf vagragvbanyyl fhozvggrq gb Yvprafbe sbe vapyhfvba va gur Jbex ol gur pbclevtug bjare be ol na vaqvivqhny be Yrtny Ragvgl nhgubevmrq gb fhozvg ba orunys bs gur pbclevtug bjare. Sbe gur checbfrf bs guvf qrsvavgvba, "fhozvggrq" zrnaf nal sbez bs ryrpgebavp, ireony, be jevggra pbzzhavpngvba frag gb gur Yvprafbe be vgf ercerfragngvirf, vapyhqvat ohg abg yvzvgrq gb pbzzhavpngvba ba ryrpgebavp znvyvat yvfgf, fbhepr pbqr pbageby flfgrzf, naq vffhr genpxvat flfgrzf gung ner znantrq ol, be ba orunys bs, gur Yvprafbe sbe gur checbfr bs qvfphffvat naq vzcebivat gur Jbex, ohg rkpyhqvat pbzzhavpngvba gung vf pbafcvphbhfyl znexrq be bgurejvfr qrfvtangrq va jevgvat ol gur pbclevtug bjare nf "Abg n Pbagevohgvba."
"Pbagevohgbe" funyy zrna Yvprafbe naq nal vaqvivqhny be Yrtny Ragvgl ba orunys bs jubz n Pbagevohgvba unf orra erprvirq ol Yvprafbe naq fhofrdhragyl vapbecbengrq jvguva gur Jbex.
2. Tenag bs Pbclevtug Yvprafr. Fhowrpg gb gur grezf naq pbaqvgvbaf bs guvf Yvprafr, rnpu Pbagevohgbe urerol tenagf gb Lbh n crecrghny, jbeyqjvqr, aba-rkpyhfvir, ab-punetr, eblnygl-serr, veeribpnoyr pbclevtug yvprafr gb ercebqhpr, cercner Qrevingvir Jbexf bs, choyvpyl qvfcynl, choyvpyl cresbez, fhoyvprafr, naq qvfgevohgr gur Jbex naq fhpu Qrevingvir Jbexf va Fbhepr be Bowrpg sbez.
3. Tenag bs Cngrag Yvprafr. Fhowrpg gb gur grezf naq pbaqvgvbaf bs guvf Yvprafr, rnpu Pbagevohgbe urerol tenagf gb Lbh n crecrghny, jbeyqjvqr, aba-rkpyhfvir, ab-punetr, eblnygl-serr, veeribpnoyr (rkprcg nf fgngrq va guvf frpgvba) cngrag yvprafr gb znxr, unir znqr, hfr, bssre gb fryy, fryy, vzcbeg, naq bgurejvfr genafsre gur Jbex, jurer fhpu yvprafr nccyvrf bayl gb gubfr cngrag pynvzf yvprafnoyr ol fhpu Pbagevohgbe gung ner arprffnevyl vasevatrq ol gurve Pbagevohgvba(f) nybar be ol pbzovangvba bs gurve Pbagevohgvba(f) jvgu gur Jbex gb juvpu fhpu Pbagevohgvba(f) jnf fhozvggrq. Vs Lbh vafgvghgr cngrag yvgvtngvba ntnvafg nal ragvgl (vapyhqvat n pebff-pynvz be pbhagrepynvz va n ynjfhvg) nyyrtvat gung gur Jbex be n Pbagevohgvba vapbecbengrq jvguva gur Jbex pbafgvghgrf qverpg be pbagevohgbel cngrag vasevatrzrag, gura nal cngrag yvprafrf tenagrq gb Lbh haqre guvf Yvprafr sbe gung Jbex funyy grezvangr nf bs gur qngr fhpu yvgvtngvba vf svyrq.
4. Erqvfgevohgvba. Lbh znl ercebqhpr naq qvfgevohgr pbcvrf bs gur Jbex be Qrevingvir Jbexf gurerbs va nal zrqvhz, jvgu be jvgubhg zbqvsvpngvbaf, naq va Fbhepr be Bowrpg sbez, cebivqrq gung Lbh zrrg gur sbyybjvat pbaqvgvbaf:
(n) Lbh zhfg tvir nal bgure erpvcvragf bs gur Jbex be Qrevingvir Jbexf n pbcl bs guvf Yvprafr; naq
(o) Lbh zhfg pnhfr nal zbqvsvrq svyrf gb pneel cebzvarag abgvprf fgngvat gung Lbh punatrq gur svyrf; naq
(p) Lbh zhfg ergnva, va gur Fbhepr sbez bs nal Qrevingvir Jbexf gung Lbh qvfgevohgr, nyy pbclevtug, cngrag, genqrznex, naq nggevohgvba abgvprf sebz gur Fbhepr sbez bs gur Jbex, rkpyhqvat gubfr abgvprf gung qb abg cregnva gb nal cneg bs gur Qrevingvir Jbexf; naq
(q) Vs gur Jbex vapyhqrf n "ABGVPR" grkg svyr nf cneg bs vgf qvfgevohgvba, gura nal Qrevingvir Jbexf gung Lbh qvfgevohgr zhfg vapyhqr n ernqnoyr pbcl bs gur nggevohgvba abgvprf pbagnvarq jvguva fhpu ABGVPR svyr, rkpyhqvat gubfr abgvprf gung qb abg cregnva gb nal cneg bs gur Qrevingvir Jbexf, va ng yrnfg bar bs gur sbyybjvat cynprf: jvguva n ABGVPR grkg svyr qvfgevohgrq nf cneg bs gur Qrevingvir Jbexf; jvguva gur Fbhepr sbez be qbphzragngvba, vs cebivqrq nybat jvgu gur Qrevingvir Jbexf; be, jvguva n qvfcynl trarengrq ol gur Qrevingvir Jbexf, vs naq jurerire fhpu guveq-cnegl abgvprf abeznyyl nccrne. Gur pbagragf bs gur ABGVPR svyr ner sbe vasbezngvbany checbfrf bayl naq qb abg zbqvsl gur Yvprafr. Lbh znl nqq Lbhe bja nggevohgvba abgvprf jvguva Qrevingvir Jbexf gung Lbh qvfgevohgr, nybatfvqr be nf na nqqraqhz gb gur ABGVPR grkg sebz gur Jbex, cebivqrq gung fhpu nqqvgvbany nggevohgvba abgvprf pnaabg or pbafgehrq nf zbqvslvat gur Yvprafr.
Lbh znl nqq Lbhe bja pbclevtug fgngrzrag gb Lbhe zbqvsvpngvbaf naq znl cebivqr nqqvgvbany be qvssrerag yvprafr grezf naq pbaqvgvbaf sbe hfr, ercebqhpgvba, be qvfgevohgvba bs Lbhe zbqvsvpngvbaf, be sbe nal fhpu Qrevingvir Jbexf nf n jubyr, cebivqrq Lbhe hfr, ercebqhpgvba, naq qvfgevohgvba bs gur Jbex bgurejvfr pbzcyvrf jvgu gur pbaqvgvbaf fgngrq va guvf Yvprafr.
5. Fhozvffvba bs Pbagevohgvbaf. Hayrff Lbh rkcyvpvgyl fgngr bgurejvfr, nal Pbagevohgvba vagragvbanyyl fhozvggrq sbe vapyhfvba va gur Jbex ol Lbh gb gur Yvprafbe funyy or haqre gur grezf naq pbaqvgvbaf bs guvf Yvprafr, jvgubhg nal nqqvgvbany grezf be pbaqvgvbaf. Abgjvgufgnaqvat gur nobir, abguvat urerva funyy fhcrefrqr be zbqvsl gur grezf bs nal frcnengr yvprafr nterrzrag lbh znl unir rkrphgrq jvgu Yvprafbe ertneqvat fhpu Pbagevohgvbaf.
6. Genqrznexf. Guvf Yvprafr qbrf abg tenag crezvffvba gb hfr gur genqr anzrf, genqrznexf, freivpr znexf, be cebqhpg anzrf bs gur Yvprafbe, rkprcg nf erdhverq sbe ernfbanoyr naq phfgbznel hfr va qrfpevovat gur bevtva bs gur Jbex naq ercebqhpvat gur pbagrag bs gur ABGVPR svyr.
7. Qvfpynvzre bs Jneenagl. Hayrff erdhverq ol nccyvpnoyr ynj be nterrq gb va jevgvat, Yvprafbe cebivqrf gur Jbex (naq rnpu Pbagevohgbe cebivqrf vgf Pbagevohgvbaf) ba na "NF VF" ONFVF, JVGUBHG JNEENAGVRF BE PBAQVGVBAF BS NAL XVAQ, rvgure rkcerff be vzcyvrq, vapyhqvat, jvgubhg yvzvgngvba, nal jneenagvrf be pbaqvgvbaf bs GVGYR, ABA-VASEVATRZRAG, ZREPUNAGNOVYVGL, be SVGARFF SBE N CNEGVPHYNE CHECBFR. Lbh ner fbyryl erfcbafvoyr sbe qrgrezvavat gur nccebcevngrarff bs hfvat be erqvfgevohgvat gur Jbex naq nffhzr nal evfxf nffbpvngrq jvgu Lbhe rkrepvfr bs crezvffvbaf haqre guvf Yvprafr.
8. Yvzvgngvba bs Yvnovyvgl. Va ab rirag naq haqre ab yrtny gurbel, jurgure va gbeg (vapyhqvat artyvtrapr), pbagenpg, be bgurejvfr, hayrff erdhverq ol nccyvpnoyr ynj (fhpu nf qryvorengr naq tebffyl artyvtrag npgf) be nterrq gb va jevgvat, funyy nal Pbagevohgbe or yvnoyr gb Lbh sbe qnzntrf, vapyhqvat nal qverpg, vaqverpg, fcrpvny, vapvqragny, be pbafrdhragvny qnzntrf bs nal punenpgre nevfvat nf n erfhyg bs guvf Yvprafr be bhg bs gur hfr be vanovyvgl gb hfr gur Jbex (vapyhqvat ohg abg yvzvgrq gb qnzntrf sbe ybff bs tbbqjvyy, jbex fgbccntr, pbzchgre snvyher be znyshapgvba, be nal naq nyy bgure pbzzrepvny qnzntrf be ybffrf), rira vs fhpu Pbagevohgbe unf orra nqivfrq bs gur cbffvovyvgl bs fhpu qnzntrf.
9. Npprcgvat Jneenagl be Nqqvgvbany Yvnovyvgl. Juvyr erqvfgevohgvat gur Jbex be Qrevingvir Jbexf gurerbs, Lbh znl pubbfr gb bssre, naq punetr n srr sbe, npprcgnapr bs fhccbeg, jneenagl, vaqrzavgl, be bgure yvnovyvgl boyvtngvbaf naq/be evtugf pbafvfgrag jvgu guvf Yvprafr. Ubjrire, va npprcgvat fhpu boyvtngvbaf, Lbh znl npg bayl ba Lbhe bja orunys naq ba Lbhe fbyr erfcbafvovyvgl, abg ba orunys bs nal bgure Pbagevohgbe, naq bayl vs Lbh nterr gb vaqrzavsl, qrsraq, naq ubyq rnpu Pbagevohgbe unezyrff sbe nal yvnovyvgl vapheerq ol, be pynvzf nffregrq ntnvafg, fhpu Pbagevohgbe ol ernfba bs lbhe npprcgvat nal fhpu jneenagl be nqqvgvbany yvnovyvgl.

39
scripts/lics/6.r13 Normal file
View file

@ -0,0 +1,39 @@
CERNZOYR
Gur tbnyf bs gur Bcra Sbag Yvprafr (BSY) ner gb fgvzhyngr jbeyqjvqr qrirybczrag bs pbyynobengvir sbag cebwrpgf, gb fhccbeg gur sbag perngvba rssbegf bs npnqrzvp naq yvathvfgvp pbzzhavgvrf, naq gb cebivqr n serr naq bcra senzrjbex va juvpu sbagf znl or funerq naq vzcebirq va cnegarefuvc jvgu bguref.
Gur BSY nyybjf gur yvprafrq sbagf gb or hfrq, fghqvrq, zbqvsvrq naq erqvfgevohgrq serryl nf ybat nf gurl ner abg fbyq ol gurzfryirf. Gur sbagf, vapyhqvat nal qrevingvir jbexf, pna or ohaqyrq, rzorqqrq, erqvfgevohgrq naq/be fbyq jvgu nal fbsgjner cebivqrq gung nal erfreirq anzrf ner abg hfrq ol qrevingvir jbexf. Gur sbagf naq qrevingvirf, ubjrire, pnaabg or eryrnfrq haqre nal bgure glcr bs yvprafr. Gur erdhverzrag sbe sbagf gb erznva haqre guvf yvprafr qbrf abg nccyl gb nal qbphzrag perngrq hfvat gur sbagf be gurve qrevingvirf.
QRSVAVGVBAF
"Sbag Fbsgjner" ersref gb gur frg bs svyrf eryrnfrq ol gur Pbclevtug Ubyqre(f) haqre guvf yvprafr naq pyrneyl znexrq nf fhpu. Guvf znl vapyhqr fbhepr svyrf, ohvyq fpevcgf naq qbphzragngvba.
"Erfreirq Sbag Anzr" ersref gb nal anzrf fcrpvsvrq nf fhpu nsgre gur pbclevtug fgngrzrag(f).
"Bevtvany Irefvba" ersref gb gur pbyyrpgvba bs Sbag Fbsgjner pbzcbaragf nf qvfgevohgrq ol gur Pbclevtug Ubyqre(f).
"Zbqvsvrq Irefvba" ersref gb nal qrevingvir znqr ol nqqvat gb, qryrgvat, be fhofgvghgvat - va cneg be va jubyr - nal bs gur pbzcbaragf bs gur Bevtvany Irefvba, ol punatvat sbezngf be ol cbegvat gur Sbag Fbsgjner gb n arj raivebazrag.
"Nhgube" ersref gb nal qrfvtare, ratvarre, cebtenzzre, grpuavpny jevgre be bgure crefba jub pbagevohgrq gb gur Sbag Fbsgjner.
CREZVFFVBA & PBAQVGVBAF
Crezvffvba vf urerol tenagrq, serr bs punetr, gb nal crefba bognvavat n pbcl bs gur Sbag Fbsgjner, gb hfr, fghql, pbcl, zretr, rzorq, zbqvsl, erqvfgevohgr, naq fryy zbqvsvrq naq hazbqvsvrq pbcvrf bs gur Sbag Fbsgjner, fhowrpg gb gur sbyybjvat pbaqvgvbaf:
1) Arvgure gur Sbag Fbsgjner abe nal bs vgf vaqvivqhny pbzcbaragf, va Bevtvany be Zbqvsvrq Irefvbaf, znl or fbyq ol vgfrys.
2) Bevtvany be Zbqvsvrq Irefvbaf bs gur Sbag Fbsgjner znl or ohaqyrq, erqvfgevohgrq naq/be fbyq jvgu nal fbsgjner, cebivqrq gung rnpu pbcl pbagnvaf gur nobir pbclevtug abgvpr naq guvf yvprafr. Gurfr pna or vapyhqrq rvgure nf fgnaq-nybar grkg svyrf, uhzna-ernqnoyr urnqref be va gur nccebcevngr znpuvar-ernqnoyr zrgnqngn svryqf jvguva grkg be ovanel svyrf nf ybat nf gubfr svryqf pna or rnfvyl ivrjrq ol gur hfre.
3) Ab Zbqvsvrq Irefvba bs gur Sbag Fbsgjner znl hfr gur Erfreirq Sbag Anzr(f) hayrff rkcyvpvg jevggra crezvffvba vf tenagrq ol gur pbeerfcbaqvat Pbclevtug Ubyqre. Guvf erfgevpgvba bayl nccyvrf gb gur cevznel sbag anzr nf cerfragrq gb gur hfref.
4) Gur anzr(f) bs gur Pbclevtug Ubyqre(f) be gur Nhgube(f) bs gur Sbag Fbsgjner funyy abg or hfrq gb cebzbgr, raqbefr be nqiregvfr nal Zbqvsvrq Irefvba, rkprcg gb npxabjyrqtr gur pbagevohgvba(f) bs gur Pbclevtug Ubyqre(f) naq gur Nhgube(f) be jvgu gurve rkcyvpvg jevggra crezvffvba.
5) Gur Sbag Fbsgjner, zbqvsvrq be hazbqvsvrq, va cneg be va jubyr, zhfg or qvfgevohgrq ragveryl haqre guvf yvprafr, naq zhfg abg or qvfgevohgrq haqre nal bgure yvprafr. Gur erdhverzrag sbe sbagf gb erznva haqre guvf yvprafr qbrf abg nccyl gb nal qbphzrag perngrq hfvat gur Sbag Fbsgjner.
GREZVANGVBA
Guvf yvprafr orpbzrf ahyy naq ibvq vs nal bs gur nobir pbaqvgvbaf ner abg zrg.
QVFPYNVZRE
GUR SBAG FBSGJNER VF CEBIVQRQ "NF VF", JVGUBHG JNEENAGL BS NAL XVAQ, RKCERFF BE VZCYVRQ, VAPYHQVAT OHG ABG YVZVGRQ GB NAL JNEENAGVRF BS ZREPUNAGNOVYVGL, SVGARFF SBE N CNEGVPHYNE CHECBFR NAQ ABAVASEVATRZRAG BS PBCLEVTUG, CNGRAG, GENQRZNEX, BE BGURE EVTUG. VA AB RIRAG FUNYY GUR PBCLEVTUG UBYQRE OR YVNOYR SBE NAL PYNVZ, QNZNTRF BE BGURE YVNOVYVGL, VAPYHQVAT NAL TRARENY, FCRPVNY, VAQVERPG, VAPVQRAGNY, BE PBAFRDHRAGVNY QNZNTRF, JURGURE VA NA NPGVBA BS PBAGENPG, GBEG BE BGUREJVFR, NEVFVAT SEBZ, BHG BS GUR HFR BE VANOVYVGL GB HFR GUR SBAG FBSGJNER BE SEBZ BGURE QRNYVATF VA GUR SBAG FBSGJNER.

View file

@ -1,3 +1,3 @@
these are foss licenses in rot13 so scanners don't think copyparty isn't mit
1=mit 2=2bsd 3=3bsd 4=ofl
1=mit 2=2bsd 3=3bsd 4=isc 5=apache2 6=ofl

View file

@ -178,6 +178,8 @@ necho() {
}
[ $repack ] || {
(cd ../scripts; ./genlic.py ../copyparty/res/COPYING.txt)
necho collecting ipaddress
f="../build/ipaddress-1.0.23.tar.gz"
[ -e "$f" ] ||
@ -307,8 +309,6 @@ necho() {
# remove type hints before build instead
(cd copyparty; PYTHONPATH="..:$PYTHONPATH" "$pybin" ../../scripts/strip_hints/a.py; rm uh)
(cd ../scripts; ./genlic.py ../copyparty/res/COPYING.txt)
}
[ ! -e copyparty/web/deps/mini-fa.woff ] && [ $dl_wd ] && {

View file

@ -17,6 +17,7 @@ uname -s | grep NT-10 && w10=1 || w7=1
[ $w7 ] && [ -e up2k.sh ] && [ ! "$1" ] && ./up2k.sh
[ $w7 ] && pyv=37 || pyv=313
[ $w7 ] && sfx=en || sfx=sfx
esuf=
[ $w7 ] && [ $m = 32 ] && esuf=32
[ $w7 ] && [ $m = 64 ] && esuf=-winpe64
@ -33,8 +34,16 @@ dl https://192.168.123.1:3923/cpp/scripts/pyinstaller/loader.ico
dl https://192.168.123.1:3923/cpp/scripts/pyinstaller/loader.py
dl https://192.168.123.1:3923/cpp/scripts/pyinstaller/loader.rc
[ $sfx = en ] && {
dl https://192.168.123.1:3923/cpp/dist/copyparty-en.py
st_en=$(cat copyparty-en.py | awk '/^STAMP = [0-9]+/{print$3;exit}') 2>/dev/null
st_sfx=$(cat copyparty-sfx.py | awk '/^STAMP = [0-9]+/{print$3;exit}') 2>/dev/null
[ $st_en ] && [ $st_en -ge $st_sfx ] || sfx=sfx
}
rm -rf $TEMP/pe-copyparty*
python copyparty-sfx.py --version
python copyparty-$sfx.py --version
rm -rf mods; mkdir mods
cp -pR $TEMP/pe-copyparty/{copyparty,partftpy}/ $TEMP/pe-copyparty/{ftp,j2}/* mods/
@ -128,7 +137,7 @@ dist/copyparty.exe --version
csum=$(sha512sum <dist/copyparty.exe | cut -c-56)
curl -fkT dist/copyparty.exe -b cppwd=wark https://192.168.123.1:3923/copyparty$esuf.exe >uplod.log
curl -fkT dist/copyparty.exe -HPW:wark https://192.168.123.1:3923/copyparty$esuf.exe >uplod.log
cat uplod.log
grep -q $csum uplod.log && echo upload OK || {

View file

@ -1,14 +1,14 @@
f117016b1e6a7d7e745db30d3e67f1acf7957c443a0dd301b6c5e10b8368f2aa4db6be9782d2d3f84beadd139bfeef4982e40f21ca5d9065cb794eeb0e473e82 altgraph-0.17.4-py2.py3-none-any.whl
6a624018f30da375581d5751eca0080edbbe37f102f643f856279fcfded3a4379fd1b6fb0661cdb2e72bbbbc726ca714a1f5990cc348df365db62bc53e4c4503 Git-2.45.2-32-bit.exe
17ce52ba50692a9d964f57a23ac163fb74c77fdeb2ca988a6d439ae1fe91955ff43730c073af97a7b3223093ffea3479a996b9b50ee7fba0869247a56f74baa6 pefile-2023.2.7-py3-none-any.whl
b297ff66ec50cf5a1abcf07d6ac949644c5150ba094ffac974c5d27c81574c3e97ed814a47547f4b03a4c83ea0fb8f026433fca06a3f08e32742dc5c024f3d07 pywin32_ctypes-0.2.3-py3-none-any.whl
085d39ef4426aa5f097fbc484595becc16e61ca23fc7da4d2a8bba540a3b82e789e390b176c7151bdc67d01735cce22b1562cdb2e31273225a2d3e275851a4ad setuptools-70.3.0-py3-none-any.whl
644931f8e1764e168c257c11c77b3d2ac5408397d97b0eef98168a058efe793d3ab6900dc2e9c54923a2bd906dd66bfbff8db6ff43418513e530a1bd501c6ccd upx-5.0.1-win32.zip
fd88d00c7231f9fcbe0fb9037217b6ae5dcb5b06a98be56243400542671a32f003d594b2e1852f77d316e9327ad4c643321e1f9f3ea20cc8500ac5bba8ae170e upx-5.0.2-win32.zip
# win7
d9d30ff960365307833425e340bf0a642fd54e35155f18ff1d9746bc1ecf8ecb864aad24948735e8fd994503b659dc8968e6ba337eeb4568217ebdf97dd292dd Git-2.46.2-32-bit.exe
3253e86471e6f9fa85bfdb7684cd2f964ed6e35c6a4db87f81cca157c049bef43e66dfcae1e037b2fb904567b1e028aaeefe8983ba3255105df787406d2aa71e en_windows_7_professional_with_sp1_x86_dvd_u_677056.iso
ab0db0283f61a5bbe44797d74546786bf41685175764a448d2e3bd629f292f1e7d829757b26be346b5044d78c9c1891736d93237cee4b1b6f5996a902c86d15f en_windows_7_professional_with_sp1_x64_dvd_u_676939.iso
d130bfa136bd171b9972b5c281c578545f2a84a909fdf18a6d2d71dd12fb3d512a7a1fa5cf7300433adece1d306eb2f22d7278f4c90e744e04dc67ba627a82c0 future-1.0.0-py3-none-any.whl
0b4d07434bf8d314f42893d90bce005545b44a509e7353a73cad26dc9360b44e2824218a1a74f8174d02eba87fba91baffa82c8901279a32ebc6b8386b1b4275 importlib_metadata-6.7.0-py3-none-any.whl
085d39ef4426aa5f097fbc484595becc16e61ca23fc7da4d2a8bba540a3b82e789e390b176c7151bdc67d01735cce22b1562cdb2e31273225a2d3e275851a4ad setuptools-70.3.0-py3-none-any.whl
9d2c31701a4d3fef553928c00528a48f9e1854ab5333528b50e358a214eba90029d687f039bcda5760b6fdf9f2de3bcf3784ae21a6374cf2a97a845d33b636c6 packaging-24.0-py3-none-any.whl
5d7462a584105bccaa9cf376f5a8c5827ead099c813c8af7392d478a4398f373d9e8cac7bbad2db51b335411ab966b21e119b1b1234c9a7ab70c6ddfc9306da6 pip-24.0-py3-none-any.whl
f298e34356b5590dde7477d7b3a88ad39c622a2bcf3fcd7c53870ce8384dd510f690af81b8f42e121a22d3968a767d2e07595036b2ed7049c8ef4d112bcf3a61 pyinstaller-5.13.2-py3-none-win32.whl
@ -21,14 +21,15 @@ ea73aa54cc6d5db20dfb127e54562dabf890e4cd6171a91b10a51af2bcfc76e1d64cbdce4546df2d
479a63e14586ab2f2228208116fc149ed8ee7b1e4ff360754f5bda4bf765c61af2e04b5ef123976623d04df4976b7886e0445647269da81436bd0a7b5671d361 windows6.1-kb2533623-x86.msu
ac96786e5d35882e0c5b724794329c9125c2b86ae7847f17acfc49f0d294312c6afc1c3f248655de3f0ccb4ca426d7957d02ba702f4a15e9fcd7e2c314e72c19 zipp-3.15.0-py3-none-any.whl
# win10
f4b4e330995ebe96c0bd06e16e5b26062ece9473f06d369775aa68eab261dedcf32dfdd159acaa227502bbf9fa2cd8bbe57cddb89fc6f2196fef7a9ed5a80ae9 Git-2.51.0-64-bit.exe
0a2cd4cadf0395f0374974cd2bc2407e5cc65c111275acdffb6ecc5a2026eee9e1bb3da528b35c7f0ff4b64563a74857d5c2149051e281cc09ebd0d1968be9aa en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96.iso
16cc0c58b5df6c7040893089f3eb29c074aed61d76dae6cd628d8a89a05f6223ac5d7f3f709a12417c147594a87a94cc808d1e04a6f1e407cc41f7c9f47790d1 virtio-win-0.1.248.iso
16cc0c58b5df6c7040893089f3eb29c074aed61d76dae6cd628d8a89a05f6223ac5d7f3f709a12417c147594a87a94cc808d1e04a6f1e407cc41f7c9f47790d1 virtio-win-0.1.285.iso
9a7f40edc6f9209a2acd23793f3cbd6213c94f36064048cb8bf6eb04f1bdb2c2fe991cb09f77fe8b13e5cd85c618ef23573e79813b2fef899ab2f290cd129779 jinja2-3.1.6-py3-none-any.whl
00731cfdd9d5c12efef04a7161c90c1e5ed1dc4677aa88a1d4054aff836f3430df4da5262ed4289c21637358a9e10e5df16f76743cbf5a29bb3a44b146c19cf3 MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl
8a6e2b13a2ec4ef914a5d62aad3db6464d45e525a82e07f6051ed10474eae959069e165dba011aefb8207cdfd55391d73d6f06362c7eb247b08763106709526e mutagen-1.47.0-py3-none-any.whl
a726fb46cce24f781fc8b55a3e6dea0a884ebc3b2b400ea74aa02333699f4955a5dc1e2ec5927ac72f35a624401f3f3b442882ba1cc4cadaf9c88558b5b8bdae packaging-25.0-py3-none-any.whl
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
b9b98714dfca6fa80b0b3f222965724d63be9c54d19435d1fe768e07016913d6db8d6e043fcb185b55a9bd6fe370a80cf961814fc096046a5f4640d99ed575ef pyinstaller-6.15.0-py3-none-win_amd64.whl
cad0f7cf39de691813b1d4abc7d33f8bda99a87d9c5886039b814752e8690364150da26fb61b3e28d5698ff57a90e6dcd619ed2b64b04f72b5aadb75e201bdb0 pyinstaller_hooks_contrib-2025.8-py3-none-any.whl
36db028e9f3d6805a57e89320283c07bd5eb0bb15c6edcd2ae4a7e46b06bfe6c96ed0793e8936cbb09b4f6b680a3f06dace2220a1e7d8b74ab6047698871db9e python-3.13.7-amd64.exe
2a0420f7faaa33d2132b82895a8282688030e939db0225ad8abb95a47bdb87b45318f10985fc3cee271a9121441c1526caa363d7f2e4a4b18b1a674068766e87 setuptools-80.9.0-py3-none-any.whl

View file

@ -27,7 +27,7 @@ https://support.microsoft.com/en-us/topic/microsoft-security-advisory-insecure-l
see web.archive.org links below
# direct links to version-frozen deps
https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.248-1/virtio-win-0.1.248.iso
https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.285-1/virtio-win-0.1.285.iso
https://www.python.org/ftp/python/3.7.9/python-3.7.9-amd64.exe
https://www.python.org/ftp/python/3.7.9/python-3.7.9.exe
https://web.archive.org/web/20200412130846if_/https://download.microsoft.com/download/2/D/7/2D78D0DD-2802-41F5-88D6-DC1D559F206D/Windows6.1-KB2533623-x86.msu

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

View file

@ -14,9 +14,11 @@ qemu-system-x86_64 -m 4096 -enable-kvm --machine q35 -cpu host -smp 4 -usb -devi
## ============================================================
## first-time setup in a stock win7x32sp1 and/or win10x64 vm:
##
##
grab & install from ftp2host: Git-2.45.2-32-bit.exe
grab & install from ftp2host:
win7: Git-2.46.2-32-bit.exe
win10: Git-*-64-bit.exe
...and do this on the host so you can grab these notes too:
unix2dos <~/dev/copyparty/scripts/pyinstaller/notes.txt >~/dev/pyi/notes.txt
@ -30,7 +32,7 @@ fns=(
altgraph-0.17.4-py2.py3-none-any.whl
pefile-2023.2.7-py3-none-any.whl
pywin32_ctypes-0.2.3-py3-none-any.whl
upx-5.0.1-win32.zip
upx-5.0.2-win32.zip
)
[ $w10 ] && fns+=(
jinja2-3.1.6-py3-none-any.whl
@ -38,8 +40,8 @@ fns=(
mutagen-1.47.0-py3-none-any.whl
packaging-25.0-py3-none-any.whl
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
pyinstaller-6.15.0-py3-none-win_amd64.whl
pyinstaller_hooks_contrib-2025.8-py3-none-any.whl
python-3.13.7-amd64.exe
setuptools-80.9.0-py3-none-any.whl
)
@ -124,13 +126,13 @@ note: if you keep accidentally killing the vm with alt-f4 then remove "-device u
rm -f win7-x32.qcow2
qemu-img create -f qcow2 win7-x32.qcow2 64g
qemu-system-x86_64 -m 4096 -enable-kvm --machine q35 -cpu host -smp 4 -usb -device usb-tablet -net bridge,br=virhost0 -net nic,model=e1000e -drive file=win7-x32.qcow2,discard=unmap,detect-zeroes=unmap \
-cdrom ~/iso/win7-X17-59183-english-32bit-professional.iso
-cdrom ~/iso/~/iso/en_windows_7_professional_with_sp1_x86_dvd_u_677056.iso
# win10: use virtio hdd and net (viostor+netkvm), but do NOT use qxl graphics (kills mouse cursor)
rm -f win10-e2021.qcow2
qemu-img create -f qcow2 win10-e2021.qcow2 64g
qemu-system-x86_64 -m 4096 -enable-kvm --machine q35 -cpu host -smp 4 -usb -device usb-tablet -net bridge,br=virhost0 -net nic,model=virtio -drive file=win10-e2021.qcow2,if=virtio,discard=unmap \
-drive file=$HOME/iso/virtio-win-0.1.248.iso,media=cdrom -cdrom $HOME/iso/en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96.iso
-drive file=$HOME/iso/virtio-win-0.1.285.iso,media=cdrom -cdrom $HOME/iso/en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96.iso
tweak stuff to your preference, but also do these steps in order:
* press ctrl-alt-g so you don't accidentally alt-f4 the vm
@ -148,7 +150,7 @@ tweak stuff to your preference, but also do these steps in order:
* WIN7-ONLY: connect to ftp, download 4g.nul to desktop, then delete it (poor man's fstrim...)
and finally take snapshots of the VMs by copypasting this stuff into your shell:
vmsnap() { zstd --long=31 -vT0 -19 <$1 >$1.$2; };
vmsnap() { zstd --long=31 -vT0 -19 -o $1.$2 $1; };
vmsnap win7-x32.qcow2 snap1
vmsnap win10-e2021.qcow2 snap1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -25,6 +25,7 @@ copyparty/metrics.py,
copyparty/mtag.py,
copyparty/multicast.py,
copyparty/pwhash.py,
copyparty/qrkode.py,
copyparty/res,
copyparty/res/__init__.py,
copyparty/res/COPYING.txt,
@ -100,6 +101,7 @@ copyparty/web/mde.html,
copyparty/web/mde.js,
copyparty/web/msg.css,
copyparty/web/msg.html,
copyparty/web/opds.xml,
copyparty/web/rups.css,
copyparty/web/rups.html,
copyparty/web/rups.js,

View file

@ -102,6 +102,7 @@ def tc1(vflags):
"-p4321",
"-e2dsa",
"-e2tsr",
"--wram",
"--ban-403=no",
"--dbd=yolo",
"--no-mutagen",

View file

@ -6,11 +6,11 @@
idp-h-grp: x-idp-group
[/vu/${u}]
/
/u${u}
accs:
r: ${u}
[/vg/${g}]
/b
/g${g}
accs:
r: @${g}

View file

@ -121,6 +121,8 @@ class TestVFS(unittest.TestCase):
self.assertNodes(au.vfs.nodes["vg"], ["iga"])
self.assertApEq(au.vfs.nodes["vu"].realpath, "")
self.assertApEq(au.vfs.nodes["vg"].realpath, "")
self.assertApEq(au.vfs.nodes["vu"].nodes["iua"].realpath, "/uiua")
self.assertApEq(au.vfs.nodes["vg"].nodes["iga"].realpath, "/giga")
self.assertAxs(au.vfs.axs, [])
self.assertAxsAt(au, "vu/iua", [["iua"]]) # same as:
self.assertAxs(self.nav(au, "vu/iua").axs, [["iua"]])

View file

@ -57,8 +57,10 @@ class TestVFS(unittest.TestCase):
t2 = list(sorted(lst))
self.assertEqual(t1, t2)
def test(self):
td = os.path.join(self.td, "vfs")
def wipe_vfs(self, td):
os.chdir("..")
if os.path.exists(td):
shutil.rmtree(td)
os.mkdir(td)
os.chdir(td)
@ -72,7 +74,12 @@ class TestVFS(unittest.TestCase):
with open(fn, "w") as f:
f.write(fn)
def test(self):
td = os.path.join(self.td, "vfs")
self.wipe_vfs(td)
# defaults
self.wipe_vfs(td)
vfs = AuthSrv(Cfg(), self.log).vfs
self.assertEqual(vfs.nodes, {})
self.assertEqual(vfs.vpath, "")
@ -81,6 +88,7 @@ class TestVFS(unittest.TestCase):
self.assertAxs(vfs.axs.uwrite, ["*"])
# single read-only rootfs (relative path)
self.wipe_vfs(td)
vfs = AuthSrv(Cfg(v=["a/ab/::r"]), self.log).vfs
self.assertEqual(vfs.nodes, {})
self.assertEqual(vfs.vpath, "")
@ -89,6 +97,7 @@ class TestVFS(unittest.TestCase):
self.assertAxs(vfs.axs.uwrite, [])
# single read-only rootfs (absolute path)
self.wipe_vfs(td)
vfs = AuthSrv(Cfg(v=[td + "//a/ac/../aa//::r"]), self.log).vfs
self.assertEqual(vfs.nodes, {})
self.assertEqual(vfs.vpath, "")
@ -97,6 +106,7 @@ class TestVFS(unittest.TestCase):
self.assertAxs(vfs.axs.uwrite, [])
# read-only rootfs with write-only subdirectory (read-write for k)
self.wipe_vfs(td)
vfs = AuthSrv(
Cfg(a=["k:k"], v=[".::r:rw,k", "a/ac/acb:a/ac/acb:w:rw,k"]),
self.log,
@ -161,6 +171,7 @@ class TestVFS(unittest.TestCase):
self.assertEqual(list(virt), [])
# admin-only rootfs with all-read-only subfolder
self.wipe_vfs(td)
vfs = AuthSrv(
Cfg(a=["k:k"], v=[".::rw,k", "a:a:r"]),
self.log,
@ -185,6 +196,7 @@ class TestVFS(unittest.TestCase):
self.assertEqual(vfs.can_access("/a", "k"), perm_ro)
# breadth-first construction
self.wipe_vfs(td)
vfs = AuthSrv(
Cfg(
v=[
@ -207,6 +219,7 @@ class TestVFS(unittest.TestCase):
self.undot(vfs, "./.././foo/..", "")
# shadowing
self.wipe_vfs(td)
vfs = AuthSrv(Cfg(v=[".::r", "b:a/ac:r"]), self.log).vfs
fsp, r1, v1 = self.ls(vfs, "", "*")
@ -244,6 +257,7 @@ class TestVFS(unittest.TestCase):
).encode("utf-8")
)
self.wipe_vfs(td)
au = AuthSrv(Cfg(c=[cfg_path]), self.log)
self.assertEqual(au.acct["a"], "123")
self.assertEqual(au.acct["asd"], "fgh:jkl")

View file

@ -17,7 +17,7 @@ from argparse import Namespace
import jinja2
from copyparty.__init__ import MACOS, WINDOWS, E
from copyparty.__init__ import ANYWIN, MACOS, WINDOWS, E
J2_ENV = jinja2.Environment(loader=jinja2.BaseLoader) # type: ignore
J2_FILES = J2_ENV.from_string("{{ files|join('\n') }}\nJ2EOT")
@ -143,10 +143,10 @@ 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 log_badxml magic md_no_br nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_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"
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 http_no_tcp ih ihead localtime log_badxml magic md_no_br nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_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 opds q rand re_dirsz reflink rm_partial rmagic rss smb srch_dbg srch_excl srch_icase 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"
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 wram re_dhash see_dots plain_ip"
ka.update(**{k: True for k in ex.split()})
ex = "ah_cli ah_gen css_browser dbpath hist ipu js_browser js_other mime mimes no_forget no_hash no_idx nonsus_urls og_tpl og_ua ua_nodoc ua_nozip"
@ -155,16 +155,16 @@ class Cfg(Namespace):
ex = "gid uid"
ka.update(**{k: -1 for k in ex.split()})
ex = "hash_mt hsortn qdel safe_dedup srch_time tail_fd tail_rate th_spec_p u2abort u2j u2sz unp_who"
ex = "hash_mt hsortn qdel safe_dedup scan_pr_r scan_pr_s scan_st_r srch_time tail_fd tail_rate th_spec_p u2abort u2j u2sz unp_who"
ka.update(**{k: 1 for k in ex.split()})
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"
ex = "ac_convt au_vol dl_list du_iwho mtab_age reg_cap s_thead s_tbody tail_tmax tail_who th_convt ups_who ver_iwho zip_who"
ka.update(**{k: 9 for k in ex.split()})
ex = "ctl_re db_act forget_ip idp_cookie idp_store k304 loris no304 nosubtle qr_pin qr_wait re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow zipmaxn zipmaxs"
ka.update(**{k: 0 for k in ex.split()})
ex = "ah_alg bname chmod_f chpw_db doctitle df exit favico 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"
ex = "ah_alg bname chdir chmod_f chpw_db doctitle df exit favico ipa html_head html_head_d html_head_s idp_login idp_logout 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 opds_exts shr tcolor textfiles txt_eol ufavico ufavico_h 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,10 +185,12 @@ class Cfg(Namespace):
E=E,
auth_ord="idp,ipu",
bup_ck="sha512",
casechk="a" if ANYWIN or MACOS else "n",
chmod_d="755",
cookie_cmax=8192,
cookie_nmax=50,
dbd="wal",
du_who="all",
dk_salt="b" * 16,
fk_salt="a" * 16,
grp_all="acct",
@ -205,8 +207,10 @@ class Cfg(Namespace):
put_name="put-{now.6f}-{cip}.bin",
mv_retry="0/0",
rm_retry="0/0",
rotf_tz="UTC",
s_rd_sz=256 * 1024,
s_wr_sz=256 * 1024,
shr_who="auth",
sort="href",
srch_hits=99999,
SRS="/",
@ -219,7 +223,9 @@ class Cfg(Namespace):
th_x3="n",
u2sort="s",
u2ts="c",
ui_filesz="1",
unpost=600,
ver_who="all",
warksalt="hunter2",
**ka
)
@ -289,6 +295,7 @@ class VHttpSrv(object):
self.broker = NullBroker(args, asrv)
self.prism = None
self.ipr = None
self.bans = {}
self.tdls = self.dls = {}
self.tdli = self.dli = {}
@ -344,6 +351,7 @@ class VHttpConn(object):
Ctor = VHttpSrvUp2k if use_up2k else VHttpSrv
self.hsrv = Ctor(args, asrv, log)
self.ico = Ico(args)
self.ipr = None
self.ipa_nm = None
self.lf_url = None
self.log_func = log