mirror of
https://github.com/9001/copyparty.git
synced 2026-06-19 04:32:26 -06:00
Merge remote-tracking branch 'upstream/HEAD' into hovudstraum
This commit is contained in:
commit
bde9be9974
12
README.md
12
README.md
|
|
@ -89,6 +89,7 @@ built in Norway 🇳🇴 with contributions from [not-norway](https://github.com
|
|||
* [other flags](#other-flags)
|
||||
* [descript.ion](#description) - add a description to each file in a folder
|
||||
* [dothidden](#dothidden) - cosmetically hide specific files in a folder
|
||||
* [thumbnail pregen](#thumbnail-pregen) - if you want to pre-generate everything on startup
|
||||
* [database location](#database-location) - in-volume (`.hist/up2k.db`, default) or somewhere else
|
||||
* [metadata from audio files](#metadata-from-audio-files) - set `-e2t` to index tags on upload
|
||||
* [metadata from xattrs](#metadata-from-xattrs) - unix extended file attributes
|
||||
|
|
@ -1360,6 +1361,7 @@ serverlog is sent to stdout by default (but logging to a file is also possible)
|
|||
* [-q](https://copyparty.eu/cli/#g-q) disables logging to stdout, and may improve performance a little bit
|
||||
* combine it with `-lo logfolder/cpp-%Y-%m-%d.txt` to log to a file instead
|
||||
* the `%Y-%m-%d` makes it create a new logfile every day, with the date as filename
|
||||
* global-option [--rlo](https://copyparty.eu/cli/#rlo-help-page) decides what happens if the filename is taken
|
||||
* `-lo whatever.txt` can be used without `-q` to log to both at the same time
|
||||
* by default, the logfile will have colors if the terminal does (usually the case)
|
||||
* use the [textfile-viewer](https://github.com/user-attachments/assets/8a828947-2fae-4df9-bd2a-3de46f42d478) or `less -R` in a terminal to see colors correctly
|
||||
|
|
@ -3127,9 +3129,10 @@ when generating hashes using `--ah-cli` for docker or systemd services, make sur
|
|||
|
||||
## https
|
||||
|
||||
both HTTP and HTTPS are accepted by default, but letting a [reverse proxy](#reverse-proxy) handle the https/tls/ssl would be better (probably more secure by default)
|
||||
both HTTP and HTTPS are accepted by default, but please ignore copyparty's built-in https/tls support and instead use a [reverse proxy](#reverse-proxy) to handle https/tls/ssl
|
||||
|
||||
copyparty doesn't speak HTTP/2 or QUIC, so using a reverse proxy would solve that as well -- but note that HTTP/1 is usually faster than both HTTP/2 and HTTP/3
|
||||
* reverseproxies do a better job following [best practices](https://cipherlist.eu/) meaning they are more secure, and probably also have higher performance
|
||||
* also, copyparty doesn't speak HTTP/2 or QUIC, so using a reverse proxy would solve that as well -- but note that HTTP/1 is usually faster than both HTTP/2 and HTTP/3
|
||||
|
||||
if [cfssl](https://github.com/cloudflare/cfssl/releases/latest) is installed, copyparty will automatically create a CA and server-cert on startup
|
||||
* the certs are written to `--crt-dir` for distribution, see `--help` for the other `--crt` options
|
||||
|
|
@ -3141,6 +3144,11 @@ to install cfssl on windows:
|
|||
* rename them to `cfssl.exe`, `cfssljson.exe`, `cfssl-certinfo.exe`
|
||||
* put them in PATH, for example inside `c:\windows\system32`
|
||||
|
||||
if you really wanna give copyparty an existing TLS certificate then do one of the following:
|
||||
* `--no-crt --cert server.pem` where `server.pem` is a concatenation of key + cert + chain (in that order), or...
|
||||
* `--no-crt --cert server.crt --certkey server.key` where `server.key` is the key, and `server.crt` is a concatenation of cert + chain (in that order)
|
||||
* file-extensions don't matter, but all files are expected to be [PEM-style](https://github.com/9001/copyparty/blob/hovudstraum/copyparty/res/insecure.pem)
|
||||
|
||||
|
||||
# recovering from crashes
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@ these are `--xiu` hooks; unlike `xbu` and `xau` (which get executed on every sin
|
|||
* this hook uses the `I` flag which makes it 140x faster, but if the plugin has a bug it may crash copyparty
|
||||
|
||||
|
||||
# more upload stuff
|
||||
* combine [reloc-by-wark-xbu.py](reloc-by-wark-xbu.py) and [reloc-by-wark-xau.py](reloc-by-wark-xau.py) to rename uploads to the checksum of the file contents
|
||||
|
||||
|
||||
# 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)
|
||||
|
|
|
|||
92
bin/hooks/reloc-by-wark-xau.py
Normal file
92
bin/hooks/reloc-by-wark-xau.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
_ = r"""
|
||||
rename incoming uploads according to the "wark" (the file identifier)
|
||||
which is basically but not exactly a sha512 hash of the file contents
|
||||
|
||||
NOTE: this does NOT work with up2k uploads (dragdrop into browser);
|
||||
combine this hook with reloc-by-wark-xbu.py to fix that
|
||||
|
||||
example usage as global config:
|
||||
-e2d --xau I,c,bin/hooks/reloc-by-wark-xau.py
|
||||
|
||||
parameters explained,
|
||||
e2d = enable up2k database (mandatory for xau hooks)
|
||||
xau = execute before upload
|
||||
I = import this hook for performance; do not fork / subprocess
|
||||
c = "check"; reject upload if this hook crashes due to a bug
|
||||
|
||||
example usage as a volflag (per-volume config):
|
||||
-v srv/inc:inc:r:rw,ed:c,e2d,xau=I,c,bin/hooks/reloc-by-wark-xau.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 explained above)
|
||||
|
||||
example usage as a volflag in a copyparty config file:
|
||||
[/inc]
|
||||
srv/inc
|
||||
accs:
|
||||
r: *
|
||||
rw: ed
|
||||
flags:
|
||||
e2d, xau: I,c,bin/hooks/reloc-by-wark-xau.py
|
||||
"""
|
||||
|
||||
|
||||
def main(inf):
|
||||
if inf.get("wark"):
|
||||
# this is an up2k upload; nothing to be done, just bail
|
||||
return {}
|
||||
|
||||
abspath = inf["ap"] # filesystem path to the uploaded file
|
||||
|
||||
# we don't have the wark yet so need to calculate it;
|
||||
# generating a regular sha512 would of course be much easier,
|
||||
# but then filenames would be different depending on how the
|
||||
# file was uploaded (laaame) so let's do it the hard way
|
||||
|
||||
# use the standard up2k-salt which nobody ever changes:
|
||||
salt = "hunter2"
|
||||
|
||||
# to generate the wark we'll need some functions from copyparty;
|
||||
# follow the trail to the copyparty module and grab them from there:
|
||||
|
||||
import inspect
|
||||
|
||||
libpath = inspect.getfile(inf["log"])
|
||||
libpath = os.path.dirname(os.path.dirname(libpath))
|
||||
sys.path.insert(0, libpath)
|
||||
|
||||
from copyparty.up2k import up2k_hashlist_from_file, up2k_wark_from_hashlist
|
||||
|
||||
chunklist, st = up2k_hashlist_from_file(abspath)
|
||||
wark = up2k_wark_from_hashlist(salt, st.st_size, chunklist)
|
||||
|
||||
# okay nice
|
||||
# the rest of the code below is just copied from reloc-by-wark-xbu.py
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
# grab the original filename from the vpath...
|
||||
vdir, fn = os.path.split(inf["vp"])
|
||||
|
||||
# ...to retain the original file extension, if any
|
||||
try:
|
||||
fn, ext = fn.rsplit(".", 1)
|
||||
except:
|
||||
ext = ""
|
||||
|
||||
# use the first 16 characters; 12 bytes of entropy,
|
||||
# roughly one collision for every 26 million files
|
||||
fn = wark[:16]
|
||||
|
||||
if ext:
|
||||
ext = ext.lower()
|
||||
fn += "." + ext
|
||||
|
||||
return {"reloc": {"fn": fn}}
|
||||
69
bin/hooks/reloc-by-wark-xbu.py
Normal file
69
bin/hooks/reloc-by-wark-xbu.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
|
||||
|
||||
_ = r"""
|
||||
rename incoming uploads according to the "wark" (the file identifier)
|
||||
which is basically but not exactly a sha512 hash of the file contents
|
||||
|
||||
NOTE: this only works for up2k uploads (dragdrop into browser);
|
||||
combine this with reloc-by-wark-xau.py to cover the other protocols
|
||||
|
||||
example usage as global config:
|
||||
-e2d --xbu I,c,bin/hooks/reloc-by-wark-xbu.py
|
||||
|
||||
parameters explained,
|
||||
e2d = enable up2k database (mandatory for xbu hooks)
|
||||
xbu = execute before upload
|
||||
I = import this hook for performance; do not fork / subprocess
|
||||
c = "check"; reject upload if this hook crashes due to a bug
|
||||
|
||||
example usage as a volflag (per-volume config):
|
||||
-v srv/inc:inc:r:rw,ed:c,e2d,xbu=I,c,bin/hooks/reloc-by-wark-xbu.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 explained above)
|
||||
|
||||
example usage as a volflag in a copyparty config file:
|
||||
[/inc]
|
||||
srv/inc
|
||||
accs:
|
||||
r: *
|
||||
rw: ed
|
||||
flags:
|
||||
e2d, xbu: I,c,bin/hooks/reloc-by-wark-xbu.py
|
||||
"""
|
||||
|
||||
|
||||
def main(inf):
|
||||
wark = inf.get("wark")
|
||||
if not wark:
|
||||
# not an up2k upload, so we don't have the hash;
|
||||
|
||||
# option 1: let upload proceed with original filename
|
||||
return {}
|
||||
|
||||
# option 2: reject the upload
|
||||
return {"rejectmsg": "only up2k uploads are allowed in this volume"}
|
||||
|
||||
# grab the original filename from the vpath...
|
||||
vdir, fn = os.path.split(inf["vp"])
|
||||
|
||||
# ...to retain the original file extension, if any
|
||||
try:
|
||||
fn, ext = fn.rsplit(".", 1)
|
||||
except:
|
||||
ext = ""
|
||||
|
||||
# use the first 16 characters; 12 bytes of entropy,
|
||||
# roughly one collision for every 26 million files
|
||||
fn = wark[:16]
|
||||
|
||||
if ext:
|
||||
ext = ext.lower()
|
||||
fn += "." + ext
|
||||
|
||||
return {"reloc": {"fn": fn}}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
S_VERSION = "2.19"
|
||||
S_BUILD_DT = "2026-01-18"
|
||||
S_VERSION = "2.20"
|
||||
S_BUILD_DT = "2026-04-22"
|
||||
|
||||
"""
|
||||
u2c.py: upload to copyparty
|
||||
|
|
@ -1184,7 +1184,7 @@ class Ctl(object):
|
|||
handshake(self.ar, file, False)
|
||||
|
||||
def cleanup_vt100(self):
|
||||
if VT100:
|
||||
if VT100 and not self.ar.ns:
|
||||
ss.scroll_region(None)
|
||||
else:
|
||||
eprint("\033]9;4;0\033\\")
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
# NOTE: You generally shouldn't use this PKGBUILD on Arch, as it is mainly for testing purposes. Install copyparty using pacman instead.
|
||||
|
||||
pkgname=copyparty
|
||||
pkgver="1.20.13"
|
||||
pkgver="1.20.14"
|
||||
pkgrel=1
|
||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, SFTP, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
||||
arch=("any")
|
||||
|
|
@ -24,7 +24,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=("b2af9250f7ef97a5df26df412ee082c6d2be0f0cd31d579b4fbb6aa2f3e5c271")
|
||||
sha256sums=("8783dc8390be17673d306f424e7a28dd9f9b4fce005e35734d30c1b296707c12")
|
||||
|
||||
build() {
|
||||
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
|
||||
pkgname=copyparty
|
||||
pkgver=1.20.13
|
||||
pkgver=1.20.14
|
||||
pkgrel=1
|
||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, SFTP, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
||||
arch=("any")
|
||||
|
|
@ -21,7 +21,7 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag
|
|||
)
|
||||
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||
backup=("/etc/${pkgname}.d/init" )
|
||||
sha256sums=("b2af9250f7ef97a5df26df412ee082c6d2be0f0cd31d579b4fbb6aa2f3e5c271")
|
||||
sha256sums=("8783dc8390be17673d306f424e7a28dd9f9b4fce005e35734d30c1b296707c12")
|
||||
|
||||
build() {
|
||||
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"url": "https://github.com/9001/copyparty/releases/download/v1.20.13/copyparty-1.20.13.tar.gz",
|
||||
"version": "1.20.13",
|
||||
"hash": "sha256-sq+SUPfvl6XfJt9BLuCCxtK+DwzTHVebT7tqovPlwnE="
|
||||
"url": "https://github.com/9001/copyparty/releases/download/v1.20.14/copyparty-1.20.14.tar.gz",
|
||||
"version": "1.20.14",
|
||||
"hash": "sha256-h4Pcg5C+F2c9MG9CTnoo3Z+bT84AXjVzTTDBspZwfBI="
|
||||
}
|
||||
|
|
@ -70,7 +70,10 @@ from .util import (
|
|||
expand_osenv_noop,
|
||||
expand_osenv_s,
|
||||
has_resource,
|
||||
list_ips,
|
||||
list_nics,
|
||||
load_resource,
|
||||
lprint,
|
||||
min_ex,
|
||||
pybin,
|
||||
read_utf8,
|
||||
|
|
@ -98,7 +101,6 @@ except:
|
|||
HAVE_SSL = False
|
||||
|
||||
u = unicode
|
||||
printed: list[str] = []
|
||||
zsid = uuid.uuid4().urn[4:]
|
||||
|
||||
CFG_DEF = [os.environ.get("PRTY_CONFIG", "")]
|
||||
|
|
@ -174,16 +176,6 @@ class BasicDodge11874(
|
|||
super(BasicDodge11874, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def lprint(*a: Any, **ka: Any) -> None:
|
||||
eol = ka.pop("end", "\n")
|
||||
txt: str = " ".join(unicode(x) for x in a) + eol
|
||||
printed.append(txt)
|
||||
if not VT100:
|
||||
txt = RE_ANSI.sub("", txt)
|
||||
|
||||
print(txt, end="", **ka)
|
||||
|
||||
|
||||
def warn(msg: str) -> None:
|
||||
lprint("\033[1mwarning:\033[0;33m {}\033[0m\n".format(msg))
|
||||
|
||||
|
|
@ -644,6 +636,9 @@ def get_sects():
|
|||
\033[32m-i fd:\033[33m3\033[0m uses the socket passed to copyparty on file descriptor 3
|
||||
|
||||
\033[33m-p\033[0m (tcp ports) is ignored for unix-sockets and FDs
|
||||
|
||||
\033[33m--list-nics\033[0m shows all network adapters (also offline ones);
|
||||
\033[33m--list-ips\033[0m shows all LAN IPs
|
||||
"""
|
||||
),
|
||||
],
|
||||
|
|
@ -969,6 +964,7 @@ def get_sects():
|
|||
values for --urlform:
|
||||
\033[36mstash\033[35m dumps the data to file and returns length + checksum
|
||||
\033[36msave,get\033[35m dumps to file and returns the page like a GET
|
||||
\033[36mget\033[35m ignores the message and returns the page like a GET
|
||||
\033[36mprint \033[35m prints the data to log and returns an error
|
||||
\033[36mprint,xm \033[35m prints the data to log and returns --xm output
|
||||
\033[36mprint,get\033[35m prints the data to log and returns GET\033[0m
|
||||
|
|
@ -1029,6 +1025,23 @@ def get_sects():
|
|||
"""
|
||||
),
|
||||
],
|
||||
[
|
||||
"rlo",
|
||||
"logrotate format",
|
||||
dedent(
|
||||
"""
|
||||
a logrotate-counter is added if the logfile filename is taken;
|
||||
by default at the end, unless \033[32m%R\033[0m is somewhere in the \033[36m-lo\033[0m pattern,
|
||||
for example: -lo /var/log/cpp/%Y-%m-%d%R.txt
|
||||
|
||||
\033[36m--rlo\033[0m configures the logrotate format; examples:
|
||||
.1 = when necessary, append a dot followed by a single digit
|
||||
.1! = counter is always added, even when not necessary
|
||||
-3 = a hyphen followed by three-digit counter
|
||||
(blank) = disable counter; overwrite existing logfile
|
||||
"""
|
||||
),
|
||||
],
|
||||
[
|
||||
"ls",
|
||||
"volume inspection",
|
||||
|
|
@ -1216,7 +1229,7 @@ def add_general(ap, nc, srvname):
|
|||
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("--site", metavar="URL", type=u, default="", help="public URL to assume when creating links; example: [\033[32mhttps://example.com/\033[0m]")
|
||||
ap2.add_argument("--env-expand", metavar="N", type=int, default=-1, help="syntax to expect for environment-variables to expand in config-files; [\033[32m0\033[0m]=disable, [\033[32m1\033[0m]=$VAR (old syntax (scary)), [\033[32m2\033[0m]=${VAR} (new syntax (recommended))")
|
||||
ap2.add_argument("--env-expand", metavar="N", type=int, default=-1, help="expand environment-variables in config-files? [\033[32m0\033[0m]=no, [\033[32m1\033[0m]=$VAR (old scary syntax), [\033[32m2\033[0m]=${VAR} (new recommended syntax); default is new-syntax with panic if old-syntax is seen")
|
||||
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)")
|
||||
|
|
@ -1350,13 +1363,16 @@ def add_network(ap):
|
|||
ap2.add_argument("--s-wr-slp", metavar="SEC", type=float, default=0.0, help="debug: socket write delay in seconds")
|
||||
ap2.add_argument("--rsp-slp", metavar="SEC", type=float, default=0.0, help="debug: response delay in seconds")
|
||||
ap2.add_argument("--rsp-jtr", metavar="SEC", type=float, default=0.0, help="debug: response delay, random duration 0..\033[33mSEC\033[0m")
|
||||
ap2.add_argument("--list-nics", action="store_true", help="debug: list detected network adapters")
|
||||
ap2.add_argument("--list-ips", action="store_true", help="debug: list detected LAN IPs")
|
||||
|
||||
|
||||
def add_tls(ap, cert_path):
|
||||
ap2 = ap.add_argument_group("SSL/TLS options")
|
||||
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls -- force plaintext")
|
||||
ap2.add_argument("--https-only", action="store_true", help="disable plaintext -- force tls")
|
||||
ap2.add_argument("--cert", metavar="PATH", type=u, default=cert_path, help="path to file containing a concatenation of TLS key and certificate chain")
|
||||
ap2.add_argument("--cert", metavar="PATH", type=u, default=cert_path, help="path to file containing a concatenation of TLS key and certificate chain (if \033[33m--certkey\033[0m is not set), or just the certificate chain (if \033[33m--certkey\033[0m is set)")
|
||||
ap2.add_argument("--certkey", metavar="PATH", type=u, default="", help="path to file containing just the certificate key; if this is set, then \033[33m--cert\033[0m should only contain the certificate chain")
|
||||
ap2.add_argument("--ssl-ver", metavar="LIST", type=u, default="", help="set allowed ssl/tls versions; [\033[32mhelp\033[0m] shows available versions; default is what your python version considers safe")
|
||||
ap2.add_argument("--ciphers", metavar="LIST", type=u, default="", help="set allowed ssl/tls ciphers; [\033[32mhelp\033[0m] shows available ciphers")
|
||||
ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
|
||||
|
|
@ -1588,10 +1604,10 @@ def add_stats(ap):
|
|||
|
||||
def add_yolo(ap):
|
||||
ap2 = ap.add_argument_group("yolo options")
|
||||
ap2.add_argument("--allow-csrf", action="store_true", help="disable csrf protections; let other domains/sites impersonate you through cross-site requests")
|
||||
ap2.add_argument("--allow-csrf", action="store_true", help="disable csrf protections; let other domains/sites impersonate you through cross-site requests; \033[1;31mDANGEROUS\033[0m / LAN-only")
|
||||
ap2.add_argument("--cookie-lax", action="store_true", help="allow cookies from other domains (if you follow a link from another website into your server, you will arrive logged-in); this reduces protection against CSRF")
|
||||
ap2.add_argument("--no-fnugg", action="store_true", help="disable the smoketest for caching-related issues in the web-UI")
|
||||
ap2.add_argument("--getmod", action="store_true", help="permit ?move=[...] and ?delete as GET")
|
||||
ap2.add_argument("--getmod", action="store_true", help="permit ?move=[...] and ?delete as GET -- \033[1;31mDANGEROUS\033[0m, removes csrf protection")
|
||||
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")
|
||||
|
||||
|
|
@ -1689,6 +1705,7 @@ def add_logging(ap):
|
|||
ap2.add_argument("-q", action="store_true", help="quiet; disable most STDOUT messages")
|
||||
ap2.add_argument("-lo", metavar="PATH", type=u, default="", help="logfile; use .txt for plaintext or .xz for compressed. Example: \033[32mcpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz\033[0m (NB: some errors may appear on STDOUT only)")
|
||||
ap2.add_argument("--flo", metavar="N", type=int, default=1, help="log format for \033[33m-lo\033[0m; [\033[32m1\033[0m]=classic/colors, [\033[32m2\033[0m]=no-color")
|
||||
ap2.add_argument("--rlo", metavar="TXT", type=u, default=".1", help="logrotate counter format; see \033[33m--help-rlo\033[0m")
|
||||
ap2.add_argument("--no-ansi", action="store_true", default=not VT100, help="disable colors; same as environment-variable NO_COLOR")
|
||||
ap2.add_argument("--ansi", action="store_true", help="force colors; overrides environment-variable NO_COLOR")
|
||||
ap2.add_argument("--no-logflush", action="store_true", help="don't flush the logfile after each write; tiny bit faster")
|
||||
|
|
@ -1888,7 +1905,7 @@ def add_ui(ap, retry: int):
|
|||
ap2.add_argument("--grid", action="store_true", default="true", help="show grid/thumbnails by default (volflag=grid)")
|
||||
ap2.add_argument("--gsel", action="store_true", default="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="4", help="default filesize format; one of these: 0, 1, 2, 2c, 3, 3c, 4, 4c, 5, 5c, fuzzy (see UI)")
|
||||
ap2.add_argument("--ui-filesz", metavar="FMT", type=u, default="4", help="default filesize format; one of these: 0, 1, 2, 2c, 3, 3c, 4, 4c, 5, 5c, 6, 6c, 7, 7c, fuzzy (see UI)")
|
||||
ap2.add_argument("--gauto", metavar="PERCENT", type=int, default=0, help="switch to gridview if more than \033[33mPERCENT\033[0m of files are pics/vids; 0=disabled")
|
||||
ap2.add_argument("--rcm", metavar="TXT", default="yy", help="rightclick-menu; two yes/no options: 1st y/n is enable-custom-menu, 2nd y/n is enable-double")
|
||||
ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language, for example \033[32meng\033[0m / \033[32mnor\033[0m / ...")
|
||||
|
|
@ -2114,6 +2131,14 @@ def main(argv: Optional[list[str]] = None) -> None:
|
|||
print("\n".join("%8s %s" % (k, v) for k, v in sorted(MIMES.items())))
|
||||
sys.exit(0)
|
||||
|
||||
if "--list-ips" in argv:
|
||||
print("\n".join(str(x) for x in sorted(list_ips())))
|
||||
sys.exit(0)
|
||||
|
||||
if "--list-nics" in argv:
|
||||
print("\n".join(str(x) for x in sorted(list_nics(True).items())))
|
||||
sys.exit(0)
|
||||
|
||||
if EXE:
|
||||
print("pybin: {}\n".format(pybin), end="")
|
||||
|
||||
|
|
@ -2301,7 +2326,7 @@ def main(argv: Optional[list[str]] = None) -> None:
|
|||
|
||||
# signal.signal(signal.SIGINT, sighandler)
|
||||
|
||||
SvcHub(al, dal, argv, "".join(printed)).run()
|
||||
SvcHub(al, dal, argv).run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# coding: utf-8
|
||||
|
||||
VERSION = (1, 20, 13)
|
||||
VERSION = (1, 20, 14)
|
||||
CODENAME = "sftp is fine too"
|
||||
BUILD_DT = (2026, 3, 23)
|
||||
BUILD_DT = (2026, 4, 24)
|
||||
|
||||
S_VERSION = ".".join(map(str, VERSION))
|
||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||
|
|
|
|||
|
|
@ -51,9 +51,12 @@ def ensure_cert(log: "RootLogger", args) -> None:
|
|||
with open(args.cert, "wb") as f:
|
||||
f.write(cert_insec)
|
||||
|
||||
if args.certkey and not os.path.isfile(args.certkey):
|
||||
raise Exception("certificate-key file does not exist: " + args.certkey)
|
||||
|
||||
with open(args.cert, "rb") as f:
|
||||
buf = f.read()
|
||||
o1 = buf.find(b" PRIVATE KEY-")
|
||||
o1 = buf.find(b" PRIVATE KEY-") if not args.certkey else 0
|
||||
o2 = buf.find(b" CERTIFICATE-")
|
||||
m = "unsupported certificate format: "
|
||||
if o1 < 0:
|
||||
|
|
@ -252,7 +255,7 @@ def gencert(log: "RootLogger", args, netdevs: dict[str, Netdev]):
|
|||
if args.http_only:
|
||||
return
|
||||
|
||||
if args.no_crt or not HAVE_CFSSL:
|
||||
if args.no_crt or args.certkey or not HAVE_CFSSL:
|
||||
ensure_cert(log, args)
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -616,6 +616,8 @@ class Ftpd(object):
|
|||
print(t.format(pybin))
|
||||
sys.exit(1)
|
||||
|
||||
if self.args.certkey:
|
||||
h1.keyfile = self.args.certkey
|
||||
h1.certfile = self.args.cert
|
||||
h1.tls_control_required = True
|
||||
h1.tls_data_required = True
|
||||
|
|
|
|||
|
|
@ -4897,15 +4897,8 @@ class HttpCli(object):
|
|||
dl_id,
|
||||
)
|
||||
sent = (eof - ofs) - remains
|
||||
ofs = eof - remains
|
||||
f.seek(ofs)
|
||||
|
||||
try:
|
||||
st2 = os.stat(open_args[0])
|
||||
if st.st_ino == st2.st_ino:
|
||||
st = st2 # for filesize
|
||||
except:
|
||||
pass
|
||||
f.seek(eof - remains)
|
||||
ofs = f.tell()
|
||||
|
||||
gone = 0
|
||||
unsent = False
|
||||
|
|
@ -4925,6 +4918,7 @@ class HttpCli(object):
|
|||
t_fd = t_ka = now
|
||||
self.s.sendall(buf)
|
||||
sent += len(buf)
|
||||
ofs += len(buf)
|
||||
unsent = False
|
||||
dls[dl_id] = (time.time(), sent)
|
||||
continue
|
||||
|
|
@ -4935,14 +4929,9 @@ class HttpCli(object):
|
|||
self.s.send(b"\x00")
|
||||
if t_fd < now - sec_fd:
|
||||
try:
|
||||
st2 = os.stat(open_args[0])
|
||||
szd = st2.st_size - st.st_size
|
||||
if (
|
||||
st2.st_ino != st.st_ino
|
||||
or st2.st_size < sent
|
||||
or szd < 0
|
||||
or unsent
|
||||
):
|
||||
st2 = os.stat(abspath)
|
||||
szd = st2.st_size - ofs
|
||||
if st2.st_ino != st.st_ino or szd < 0 or unsent:
|
||||
assert f # !rm
|
||||
# open new file before closing previous to avoid toctous (open may fail; cannot null f before)
|
||||
f2 = open_nolock(*open_args)
|
||||
|
|
@ -4950,15 +4939,15 @@ class HttpCli(object):
|
|||
f = f2
|
||||
f.seek(0, os.SEEK_END)
|
||||
eof = f.tell()
|
||||
if eof < sent:
|
||||
ofs = sent = 0 # shrunk; send from start
|
||||
if eof < ofs:
|
||||
ofs = 0 # shrunk; send from start
|
||||
zb = b"\n\n*** file size decreased -- rewinding to the start of the file ***\n\n"
|
||||
self.s.sendall(zb)
|
||||
if ofs0 < 0 and eof > -ofs0:
|
||||
ofs = eof + ofs0
|
||||
else:
|
||||
ofs = sent # just new fd? resume from same ofs
|
||||
# else: probably just new fd; resume from same ofs
|
||||
f.seek(ofs)
|
||||
ofs = f.tell()
|
||||
self.log("reopened at byte %d: %r" % (ofs, abspath), 6)
|
||||
unsent = False
|
||||
gone = 0
|
||||
|
|
@ -5846,6 +5835,7 @@ class HttpCli(object):
|
|||
excl, target = (target.split("/", 1) + [""])[:2]
|
||||
sub = self.gen_tree("/".join([top, excl]).strip("/"), target, dk)
|
||||
ret["k" + quotep(excl)] = sub
|
||||
dk = ""
|
||||
|
||||
vfs = self.asrv.vfs
|
||||
dk_sz = False
|
||||
|
|
@ -5885,16 +5875,16 @@ class HttpCli(object):
|
|||
else:
|
||||
dirs = exclude_dotfiles(dirs)
|
||||
|
||||
dirs = [quotep(x) for x in dirs if x != excl]
|
||||
|
||||
if dk_sz and fsroot:
|
||||
kdirs = []
|
||||
fsroot_ = os.path.join(fsroot, "")
|
||||
for dn in dirs:
|
||||
for dn in [x for x in dirs if x != excl]:
|
||||
ap = fsroot_ + dn
|
||||
zs = self.gen_fk(2, self.args.dk_salt, ap, 0, 0)[:dk_sz]
|
||||
kdirs.append(dn + "?k=" + zs)
|
||||
kdirs.append(quotep(dn) + "?k=" + zs)
|
||||
dirs = kdirs
|
||||
else:
|
||||
dirs = [quotep(x) for x in dirs if x != excl]
|
||||
|
||||
if vfs_virt:
|
||||
for x in vfs_virt:
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ class HttpConn(object):
|
|||
try:
|
||||
assert ssl # type: ignore # !rm
|
||||
ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||
ctx.load_cert_chain(self.args.cert)
|
||||
ctx.load_cert_chain(self.args.cert, self.args.certkey)
|
||||
if self.args.ssl_ver:
|
||||
ctx.options &= ~self.args.ssl_flags_en
|
||||
ctx.options |= self.args.ssl_flags_de
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ from .util import (
|
|||
HAVE_PSUTIL,
|
||||
HAVE_SQLITE3,
|
||||
HAVE_ZMQ,
|
||||
LOG,
|
||||
RE_ANSI,
|
||||
URL_BUG,
|
||||
UTC,
|
||||
|
|
@ -74,6 +75,7 @@ from .util import (
|
|||
load_ipr,
|
||||
load_ipu,
|
||||
lock_file,
|
||||
lprinted,
|
||||
min_ex,
|
||||
mp,
|
||||
odfusion,
|
||||
|
|
@ -126,7 +128,6 @@ class SvcHub(object):
|
|||
args: argparse.Namespace,
|
||||
dargs: argparse.Namespace,
|
||||
argv: list[str],
|
||||
printed: str,
|
||||
) -> None:
|
||||
self.args = args
|
||||
self.dargs = dargs
|
||||
|
|
@ -208,8 +209,24 @@ class SvcHub(object):
|
|||
else:
|
||||
self.log = self._log_enabled
|
||||
|
||||
self.lo1 = self.lo2 = ""
|
||||
if args.lo:
|
||||
self._setup_logfile(printed)
|
||||
if "%" in args.lo and "%R" not in args.lo:
|
||||
args.lo += "%R"
|
||||
if not args.rlo:
|
||||
args.lo = args.lo.replace("%R", "")
|
||||
try:
|
||||
self.lo1, self.lo2 = args.lo.split("%R")
|
||||
except:
|
||||
self.lo1 = args.lo
|
||||
try:
|
||||
self.rot_fmt = "%%s%s%%0%sd%s" % (args.rlo[:1], args.rlo[1:2], self.lo2)
|
||||
except:
|
||||
self.rot_fmt = "%s.%d"
|
||||
self._setup_logfile()
|
||||
|
||||
LOG[0] = self.log
|
||||
lprinted[:] = []
|
||||
|
||||
lg = logging.getLogger()
|
||||
lh = HLog(self.log)
|
||||
|
|
@ -1229,6 +1246,9 @@ class SvcHub(object):
|
|||
for x in [x.split(" ") for x in al.sftp_key or []]
|
||||
}
|
||||
|
||||
if not al.certkey:
|
||||
al.certkey = None
|
||||
|
||||
mte = ODict.fromkeys(DEF_MTE.split(","), True)
|
||||
al.mte = odfusion(mte, al.mte)
|
||||
|
||||
|
|
@ -1353,7 +1373,7 @@ class SvcHub(object):
|
|||
|
||||
def _logname(self) -> str:
|
||||
dt = datetime.now(self.tz)
|
||||
fn = str(self.args.lo)
|
||||
fn = str(self.lo1)
|
||||
for fs in "YmdHMS":
|
||||
fs = "%" + fs
|
||||
if fs in fn:
|
||||
|
|
@ -1361,16 +1381,18 @@ class SvcHub(object):
|
|||
|
||||
return fn
|
||||
|
||||
def _setup_logfile(self, printed: str) -> None:
|
||||
base_fn = fn = sel_fn = self._logname()
|
||||
do_xz = fn.lower().endswith(".xz")
|
||||
if fn != self.args.lo:
|
||||
ctr = 0
|
||||
def _setup_logfile(self) -> None:
|
||||
base_fn = fn = self._logname()
|
||||
sel_fn = fn + self.lo2
|
||||
do_xz = sel_fn.lower().endswith(".xz")
|
||||
if "%R" in self.args.lo:
|
||||
# yup this is a race; if started sufficiently concurrently, two
|
||||
# copyparties can grab the same logfile (considered and ignored)
|
||||
while os.path.exists(sel_fn):
|
||||
ctr += 1
|
||||
sel_fn = "{}.{}".format(fn, ctr)
|
||||
for n in range(9999):
|
||||
if n or "!" in self.args.rlo:
|
||||
sel_fn = self.rot_fmt % (fn, n)
|
||||
if not os.path.exists(sel_fn):
|
||||
break
|
||||
|
||||
fn = sel_fn
|
||||
try:
|
||||
|
|
@ -1401,7 +1423,7 @@ class SvcHub(object):
|
|||
argv = ['"{}"'.format(x) for x in argv]
|
||||
|
||||
msg = "[+] opened logfile [{}]\n".format(fn)
|
||||
printed += msg
|
||||
printed = "".join(lprinted) + msg
|
||||
t = "t0: {:.3f}\nargv: {}\n\n{}"
|
||||
lh.write(t.format(self.E.t0, " ".join(argv), printed))
|
||||
self.logf = lh
|
||||
|
|
@ -1673,7 +1695,7 @@ class SvcHub(object):
|
|||
def _set_next_day(self, dt: datetime) -> None:
|
||||
if self.cday and self.logf and self.logf_base_fn != self._logname():
|
||||
self.logf.close()
|
||||
self._setup_logfile("")
|
||||
self._setup_logfile()
|
||||
|
||||
self.cday = dt.day
|
||||
self.cmon = dt.month
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from .util import (
|
|||
atomic_move,
|
||||
chkcmd,
|
||||
get_adapters,
|
||||
list_nics,
|
||||
min_ex,
|
||||
sunpack,
|
||||
termsize,
|
||||
|
|
@ -461,23 +462,7 @@ class TcpSrv(object):
|
|||
def detect_interfaces(self, listen_ips: list[str]) -> dict[str, Netdev]:
|
||||
listen_ips = [x for x in listen_ips if not x.startswith(("unix:", "fd:"))]
|
||||
|
||||
nics = get_adapters(True)
|
||||
eps: dict[str, Netdev] = {}
|
||||
for nic in nics:
|
||||
for nip in nic.ips:
|
||||
ipa = nip.ip[0] if ":" in str(nip.ip) else nip.ip
|
||||
sip = "{}/{}".format(ipa, nip.network_prefix)
|
||||
nd = Netdev(sip, nic.index or 0, nic.nice_name, "")
|
||||
eps[sip] = nd
|
||||
try:
|
||||
idx = socket.if_nametoindex(nd.name)
|
||||
if idx and idx != nd.idx:
|
||||
t = "netdev idx mismatch; ifaddr={} cpython={}"
|
||||
self.log("tcpsrv", t.format(nd.idx, idx), 3)
|
||||
nd.idx = idx
|
||||
except:
|
||||
pass
|
||||
|
||||
eps = list_nics()
|
||||
netlist = str(sorted(eps.items()))
|
||||
if netlist == self.netlist and self.netdevs:
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -789,6 +789,10 @@ class ThumbSrv(object):
|
|||
if not ret:
|
||||
return
|
||||
|
||||
if "vc" not in ret and "ac" in ret:
|
||||
# audio in a video trenchcoat
|
||||
return self.conv_spec(abspath, tpath, fmt, vn)
|
||||
|
||||
ext = abspath.rsplit(".")[-1].lower()
|
||||
if ext in ["h264", "h265"] or ext in self.fmt_ffi:
|
||||
seek: list[bytes] = []
|
||||
|
|
|
|||
|
|
@ -3357,7 +3357,7 @@ class Up2k(object):
|
|||
job["size"],
|
||||
job["addr"],
|
||||
job["at"],
|
||||
None,
|
||||
[dwark],
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or hr.get("rc") != 0:
|
||||
|
|
@ -4069,7 +4069,7 @@ class Up2k(object):
|
|||
sz,
|
||||
ip,
|
||||
at or time.time(),
|
||||
None,
|
||||
[dwark],
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or hr.get("rc") != 0:
|
||||
|
|
@ -5251,7 +5251,7 @@ class Up2k(object):
|
|||
job["size"],
|
||||
job["addr"],
|
||||
job["t0"],
|
||||
None,
|
||||
[job["dwrk"]],
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or hr.get("rc") != 0:
|
||||
|
|
@ -5708,6 +5708,31 @@ def up2k_chunksize(filesize: int) -> int:
|
|||
stepsize *= mul
|
||||
|
||||
|
||||
def up2k_hashlist_from_file(path: str) -> tuple[list[str], os.stat_result]:
|
||||
"""not used by copyparty itself, only by some hooks"""
|
||||
st = bos.stat(path)
|
||||
fsz = st.st_size
|
||||
csz = up2k_chunksize(fsz)
|
||||
ret = []
|
||||
with open(fsenc(path), "rb", 256 * 1024) as f:
|
||||
while fsz > 0:
|
||||
hashobj = hashlib.sha512()
|
||||
rem = min(csz, fsz)
|
||||
fsz -= rem
|
||||
while rem > 0:
|
||||
buf = f.read(min(rem, 64 * 1024))
|
||||
if not buf:
|
||||
raise Exception("EOF at " + str(f.tell()))
|
||||
|
||||
hashobj.update(buf)
|
||||
rem -= len(buf)
|
||||
|
||||
digest = hashobj.digest()[:33]
|
||||
ret.append(ub64enc(digest).decode("ascii"))
|
||||
|
||||
return ret, st
|
||||
|
||||
|
||||
def up2k_wark_from_hashlist(salt: str, filesize: int, hashes: list[str]) -> str:
|
||||
"""server-reproducible file identifier, independent of name or location"""
|
||||
values = [salt, str(filesize)] + hashes
|
||||
|
|
|
|||
|
|
@ -62,6 +62,20 @@ def noop(*a, **ka):
|
|||
pass
|
||||
|
||||
|
||||
def lprint(*a: "Any", **ka: "Any") -> None:
|
||||
eol = ka.pop("end", "\n")
|
||||
txt = " ".join(unicode(x) for x in a) + eol
|
||||
lprinted.append(txt)
|
||||
if not VT100 and "\033" in txt:
|
||||
txt = RE_ANSI.sub("", txt)
|
||||
|
||||
print(txt, end="", **ka)
|
||||
|
||||
|
||||
lprinted: list[str] = []
|
||||
LOG: list["Callable[..., None]"] = [lprint]
|
||||
|
||||
|
||||
try:
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
|
@ -788,7 +802,7 @@ def read_utf8(log: Optional["NamedLogger"], ap: Union[str, bytes], strict: bool)
|
|||
if log:
|
||||
log(t, 3)
|
||||
else:
|
||||
print(t)
|
||||
LOG[0]("#", t)
|
||||
return buf.decode("utf-8", "replace")
|
||||
|
||||
t = "ERROR: The file [%s] is not using the UTF-8 character encoding, and cannot be loaded. The first unreadable character was byte %r at offset %d. Please convert this file to UTF-8 by opening the file in your text-editor and saving it as UTF-8."
|
||||
|
|
@ -796,7 +810,7 @@ def read_utf8(log: Optional["NamedLogger"], ap: Union[str, bytes], strict: bool)
|
|||
if log:
|
||||
log(t, 3)
|
||||
else:
|
||||
print(t)
|
||||
LOG[0]("#", t)
|
||||
raise NotUTF8(t)
|
||||
|
||||
|
||||
|
|
@ -1573,12 +1587,18 @@ def _expand_osenv_c(txt) -> str:
|
|||
ret = zsl[0]
|
||||
for v in zsl[1:]:
|
||||
if "}" not in v:
|
||||
raise Exception("missing '}' after %r in config-value %r" % (v, txt))
|
||||
t = "missing '}' after %r in config-value %r" % (v, txt)
|
||||
LOG[0]("ERROR:", t)
|
||||
raise Exception(t)
|
||||
a, b = v.split("}", 1)
|
||||
try:
|
||||
ret += os.environ[a] + b
|
||||
continue
|
||||
except:
|
||||
raise Exception("env-var %r not defined; config-value %r" % (a, txt))
|
||||
pass
|
||||
t = "env-var %r not defined; config-value %r" % (a, txt)
|
||||
LOG[0]("ERROR:", t)
|
||||
raise Exception(t)
|
||||
return ret
|
||||
|
||||
|
||||
|
|
@ -1595,8 +1615,24 @@ def expand_osenv_cs(txt) -> str:
|
|||
b = expand_osenv_s(txt)
|
||||
if a == b:
|
||||
return a
|
||||
t = "config-value %r is using the old syntax for environment-variables; choose one of the following options:\noption 1: update the config-value to the new syntax, ${VAR} instead of $VAR or %%VAR%%\noption 2: tell copyparty to allow the old syntax with global-option --env-expand 1 (risky)\noption 3: tell copyparty to only use the new syntax (and not expand this variable) with global-option --env-expand 2\noption 4: disable all environment-variable expansions with PRTY_NO_ENVEXPAND=1 or global-option --env-expand 0"
|
||||
raise Exception(t % (txt,))
|
||||
|
||||
t = "config-value %r is using old syntax for environment-variables; choose one of the following:\noption 1: update the config-value to the new syntax; ${VAR} instead of $VAR or %%VAR%%\noption 2: allow and expand old-syntax with global-option --env-expand 1 (risky)\noption 3: ignore/disable expansion of old-syntax with global-option --env-expand 2\noption 4: disable all env-var expansions by setting env-var PRTY_NO_ENVEXPAND=1"
|
||||
t = t % (txt,)
|
||||
LOG[0]("WARNING:", t)
|
||||
|
||||
try:
|
||||
_, _ = txt.split("$")
|
||||
zs = r"\$(LOGS_DIRECTORY|XDG_[A-Z]+_HOME|XDG_[A-Z]+_DIR)\b"
|
||||
txt = re.sub(zs, r"${\1}", txt)
|
||||
|
||||
a = expand_osenv_c(txt)
|
||||
b = expand_osenv_s(txt)
|
||||
if a == b:
|
||||
return a
|
||||
except:
|
||||
pass
|
||||
|
||||
raise Exception(t)
|
||||
|
||||
|
||||
def rice_tid() -> str:
|
||||
|
|
@ -3140,6 +3176,31 @@ def list_ips() -> list[str]:
|
|||
return list(ret)
|
||||
|
||||
|
||||
def list_nics(alll: bool = False) -> dict[str, Netdev]:
|
||||
nics = get_adapters(alll)
|
||||
eps: dict[str, Netdev] = {}
|
||||
for nic in nics:
|
||||
name = nic.nice_name
|
||||
try:
|
||||
idx = socket.if_nametoindex(name)
|
||||
if idx and idx != nic.index:
|
||||
LOG[0]("#", "nic-idx mismatch; ifaddr=%r libc=%r" % (nic.index, idx), 3)
|
||||
except:
|
||||
idx = nic.index
|
||||
|
||||
for nip in nic.ips:
|
||||
ipa = nip.ip[0] if ":" in str(nip.ip) else nip.ip
|
||||
sip = "%s/%s" % (ipa, nip.network_prefix)
|
||||
nd = Netdev(sip, idx or 0, name, "")
|
||||
eps[sip] = nd
|
||||
|
||||
if alll and not nic.ips:
|
||||
zs = "no-ip-%s" % (idx,)
|
||||
eps[zs] = Netdev(zs, idx or 0, name, "")
|
||||
|
||||
return eps
|
||||
|
||||
|
||||
def build_netmap(csv: str, defer_mutex: bool = False):
|
||||
csv = csv.lower().strip()
|
||||
|
||||
|
|
@ -4097,8 +4158,11 @@ def _runhook(
|
|||
"src": src,
|
||||
}
|
||||
if txt:
|
||||
ja["txt"] = txt[0]
|
||||
ja["body"] = txt[1]
|
||||
if src in ("xm", "xban"):
|
||||
ja["txt"] = txt[0]
|
||||
ja["body"] = txt[1]
|
||||
else:
|
||||
ja["wark"] = txt[0] # acshually the dwark but less confusing
|
||||
if imp:
|
||||
ja["log"] = log
|
||||
mod = loadpy(acmd[0], False)
|
||||
|
|
@ -4211,7 +4275,7 @@ def runhook(
|
|||
else:
|
||||
ret[k] = v
|
||||
except Exception as ex:
|
||||
(log or print)("hook: %r, %s" % (ex, ex))
|
||||
(log or print)("hook failed; %s:\n%s" % (ex, min_ex()))
|
||||
if ",c," in "," + cmd:
|
||||
return {"rc": 1}
|
||||
break
|
||||
|
|
|
|||
|
|
@ -1227,16 +1227,20 @@ ebi('op_cfg').innerHTML = (
|
|||
'<div>\n' +
|
||||
' <h3 id="h_filesize">🔢 ' + L.cl_hfsz + '</h3>\n' +
|
||||
' <div><select id="fszfmt">\n' +
|
||||
' <option value="0">0 ┃ 1234567</option>\n' +
|
||||
' <option value="1">1 ┃ 1 234 567</option>\n' +
|
||||
' <option value="2">2- ┃ 1.18 M</option>\n' +
|
||||
' <option value="2c">2c ┃ 1.18 M</option>\n' +
|
||||
' <option value="3">3- ┃ 1.2 M</option>\n' +
|
||||
' <option value="3c">3c ┃ 1.2 M</option>\n' +
|
||||
' <option value="4">4- ┃ 1.18 MB</option>\n' +
|
||||
' <option value="4c">4c ┃ 1.18 MB</option>\n' +
|
||||
' <option value="5">5- ┃ 1.2 MB</option>\n' +
|
||||
' <option value="5c">5c ┃ 1.2 MB</option>\n' +
|
||||
' <option value="0">0 ┃ 2345678</option>\n' +
|
||||
' <option value="1">1 ┃ 2 345 678</option>\n' +
|
||||
' <option value="2">2- ┃ 2.24 M /1024</option>\n' +
|
||||
' <option value="2c">2c ┃ 2.24 M /1024</option>\n' +
|
||||
' <option value="3">3- ┃ 2.2 M /1024</option>\n' +
|
||||
' <option value="3c">3c ┃ 2.2 M /1024</option>\n' +
|
||||
' <option value="4">4- ┃ 2.24 Mi /1024</option>\n' +
|
||||
' <option value="4c">4c ┃ 2.24 Mi /1024</option>\n' +
|
||||
' <option value="5">5- ┃ 2.2 Mi /1024</option>\n' +
|
||||
' <option value="5c">5c ┃ 2.2 Mi /1024</option>\n' +
|
||||
' <option value="6">6- ┃ 2.35 MB /1000</option>\n' +
|
||||
' <option value="6c">6c ┃ 2.35 MB /1000</option>\n' +
|
||||
' <option value="7">7- ┃ 2.3 MB /1000</option>\n' +
|
||||
' <option value="7c">7c ┃ 2.3 MB /1000</option>\n' +
|
||||
' <option value="fuzzy">fuzzy</option>\n' +
|
||||
' </select></div>\n' +
|
||||
'</div>\n' +
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ function U2pvis(act, btns, uc, st) {
|
|||
nb = fo.bt * (++fo.nh / fo.cb.length),
|
||||
p = r.perc(nb, 0, fobj.size, fobj.t_hashing);
|
||||
|
||||
fo.hp = f2f(p[0], 2) + '%, ' + p[1] + ', ' + f2f(p[2], 2) + ' MB/s';
|
||||
fo.hp = f2f(p[0], 2) + '%, ' + p[1] + ', ' + f2f(p[2], 2) + ' MiB/s';
|
||||
if (!r.is_act(fo.in))
|
||||
return;
|
||||
|
||||
|
|
@ -302,7 +302,7 @@ function U2pvis(act, btns, uc, st) {
|
|||
return;
|
||||
|
||||
var p = r.perc(fo.bd, fo.bd0, fo.bt, fobj.t_uploading);
|
||||
fo.hp = f2f(p[0], 2) + '%, ' + p[1] + ', ' + f2f(p[2], 2) + ' MB/s';
|
||||
fo.hp = f2f(p[0], 2) + '%, ' + p[1] + ', ' + f2f(p[2], 2) + ' MiB/s';
|
||||
|
||||
if (!r.is_act(fo.in))
|
||||
return;
|
||||
|
|
@ -1664,7 +1664,7 @@ function up2k_init(subtle) {
|
|||
}
|
||||
|
||||
if (!nhash) {
|
||||
var h = L.u_etadone.format(humansize(st.bytes.hashed), pvis.ctr.ok + pvis.ctr.ng);
|
||||
var h = L.u_etadone.format(humansize(st.bytes.hashed, 2), pvis.ctr.ok + pvis.ctr.ng);
|
||||
if (st.eta.h !== h) {
|
||||
st.eta.h = ebi('u2etah').innerHTML = h;
|
||||
console.log('{0} hash, {1} up, {2} busy'.format(
|
||||
|
|
@ -1675,7 +1675,7 @@ function up2k_init(subtle) {
|
|||
}
|
||||
|
||||
if (!nsend && !nhash) {
|
||||
var h = L.u_etadone.format(humansize(st.bytes.uploaded), pvis.ctr.ok + pvis.ctr.ng);
|
||||
var h = L.u_etadone.format(humansize(st.bytes.uploaded, 2), pvis.ctr.ok + pvis.ctr.ng);
|
||||
|
||||
if (st.eta.u !== h)
|
||||
st.eta.u = ebi('u2etau').innerHTML = h;
|
||||
|
|
@ -1740,7 +1740,7 @@ function up2k_init(subtle) {
|
|||
|
||||
donut.eta = eta;
|
||||
st.eta[eid] = '{0}, {1}/s, {2}'.format(
|
||||
humansize(rem), humansize(bps, 1), humantime(eta));
|
||||
humansize(rem, 2), humansize(bps, 1), humantime(eta));
|
||||
|
||||
if (!etaskip)
|
||||
ebi(hid).innerHTML = st.eta[eid];
|
||||
|
|
@ -2721,7 +2721,7 @@ function up2k_init(subtle) {
|
|||
var spd1 = (t.size / ((t.t_hashed - t.t_hashing) / 1000.)) / (1024 * 1024.),
|
||||
spd2 = (t.size / ((t.t_uploaded - t.t_uploading) / 1000.)) / (1024 * 1024.);
|
||||
|
||||
pvis.seth(t.n, 2, 'hash {0}, up {1} MB/s'.format(
|
||||
pvis.seth(t.n, 2, 'hash {0}, up {1} MiB/s'.format(
|
||||
f2f(spd1, 2), !isNum(spd2) ? '--' : f2f(spd2, 2)));
|
||||
|
||||
pvis.move(t.n, 'ok');
|
||||
|
|
|
|||
|
|
@ -979,17 +979,24 @@ function f2f(val, nd) {
|
|||
}
|
||||
|
||||
|
||||
var HSZ_U = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
function humansize(b, terse) {
|
||||
var HSZ_U = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];
|
||||
var HSZ_U2 = ['B', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi'];
|
||||
var HSZ_UD = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
function humansize(b, tersity) {
|
||||
var i = 0;
|
||||
while (b >= 1000 && i < 5) { b /= 1024; i += 1; }
|
||||
return (f2f(b, b >= 100 ? 0 : b >= 10 ? 1 : 2) +
|
||||
' ' + (terse ? HSZ_U[i].charAt(0) : HSZ_U[i]));
|
||||
' ' + (tersity ? HSZ_U[i].slice(0, tersity) : HSZ_U[i]));
|
||||
}
|
||||
function humansize_su(b) {
|
||||
var i = 0;
|
||||
while (b >= 1000 && i < 5) { b /= 1024; i += 1; }
|
||||
return [b, HSZ_U[i]];
|
||||
return [b, HSZ_U2[i]];
|
||||
}
|
||||
function humansize_sud(b) {
|
||||
var i = 0;
|
||||
while (b >= 1000 && i < 5) { b /= 1000; i += 1; }
|
||||
return [b, HSZ_UD[i]];
|
||||
}
|
||||
function humansize_0(b) {
|
||||
return '' + b;
|
||||
|
|
@ -1013,6 +1020,14 @@ 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_6g(b) {
|
||||
var z = humansize_sud(b), u = z[1]; b = z[0];
|
||||
return [parseFloat(b.toFixed(b >= 100 ? 0 : b >= 10 ? 1 : 2)) + ' ' + u, u.charAt(0)];
|
||||
}
|
||||
function humansize_7g(b) {
|
||||
var z = humansize_sud(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];
|
||||
}
|
||||
|
|
@ -1025,6 +1040,12 @@ function humansize_4(b) {
|
|||
function humansize_5(b) {
|
||||
return humansize_5g(b)[0];
|
||||
}
|
||||
function humansize_6(b) {
|
||||
return humansize_6g(b)[0];
|
||||
}
|
||||
function humansize_7(b) {
|
||||
return humansize_7g(b)[0];
|
||||
}
|
||||
function humansize_2c(b) {
|
||||
var v = humansize_2g(b);
|
||||
return '<span class="fsz_' + v[1].charAt(0) + '">' + v[0] + '</span>';
|
||||
|
|
@ -1041,6 +1062,14 @@ function humansize_5c(b) {
|
|||
var v = humansize_5g(b);
|
||||
return '<span class="fsz_' + v[1].charAt(0) + '">' + v[0] + '</span>';
|
||||
}
|
||||
function humansize_6c(b) {
|
||||
var v = humansize_6g(b);
|
||||
return '<span class="fsz_' + v[1].charAt(0) + '">' + v[0] + '</span>';
|
||||
}
|
||||
function humansize_7c(b) {
|
||||
var v = humansize_7g(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";
|
||||
|
|
@ -1063,7 +1092,7 @@ function humansize_fuzzy(b) {
|
|||
if (b <= 50050000000) return "BD-DL";
|
||||
return "LTO";
|
||||
}
|
||||
var humansize_fmts = ['0', '1', '2', '2c', '3', '3c', '4', '4c', '5', '5c', 'fuzzy'];
|
||||
var humansize_fmts = ['0', '1', '2', '2c', '3', '3c', '4', '4c', '5', '5c', '6', '6c', '7', '7c', 'fuzzy'];
|
||||
window.filesizefun = (function () {
|
||||
var v = sread('fszfmt', humansize_fmts);
|
||||
return window['humansize_' + (v || window.dfszf)] || humansize_1;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,74 @@
|
|||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2026-0323-0328 `v1.20.13` dothidden
|
||||
|
||||
## 🧪 new features
|
||||
|
||||
* #1351 add [.hidden](https://github.com/9001/copyparty/#dothidden) support (thx @NecRaul!) beb634dc 134e378e
|
||||
* cosmetic filter to exclude specific files from directory listings by adding their filenames to a textfile named `.hidden` similar to many linux desktop file managers
|
||||
* the files are still easily available from various APIs; this is **not** a security feature, just a way to keep things neat and tidy
|
||||
* #1381 thumbnail pregeneration 7d6b037d
|
||||
* usually/generally not a good idea; [readme explains it](https://github.com/9001/copyparty/#thumbnail-pregen)
|
||||
* shares: now possible to grant the `.` permission to see dotfiles 66f9c950
|
||||
|
||||
## 🩹 bugfixes
|
||||
|
||||
* #1372 #1333 no thumbnails if the server OS was too old to have JXL support and the webbrowser was asking for JXL 1afe48b8
|
||||
* #1363 new-version alert would only appear if the visitor had the Admin permission in the webroot specifically; now `A` in any volume is sufficient 6eb4f0ad
|
||||
* 66f1ef63 should have blocked mkdir too and now it does (thx @restriction!) ac60a1da
|
||||
* setting the `nohtml` or `noscript` volflags on the webroot would break the web-UI eb028c92
|
||||
* shares: the [-ed](https://copyparty.eu/cli/#g-ed) global-option did not make dotfiles visible in shares 66f9c950
|
||||
* the `dots` volflag still doesn't, but that one is intentional
|
||||
|
||||
## 🔧 other changes
|
||||
|
||||
* tried to stop libvips from gobbling up ram while creating jxl thumbnails; didn't really work abdbd69a
|
||||
* jxl support in libvips is now default-disabled unless the libc is musl and the allocator is mallocng, which means alpine linux
|
||||
* in other words, libvips is still fully enabled in the `iv` and `dj` docker images if you do not enable mimalloc
|
||||
* all other deployments will now have slightly slower jxl thumbnail generation by using ffmpeg instead (it's fine really)
|
||||
* new global-option [--th-vips-jxl](https://copyparty.eu/cli/#g-th-vips-jxl) lets you force-enable it if you dare
|
||||
* volflags `nohtml` and `noscript` now available as global-options `--no-html` and `--no-script` 5f3b76c8
|
||||
* and the `-ss` paranoia option now also enables `--no-html --no-readme --no-logues`
|
||||
* [--flo 2](https://copyparty.eu/cli/#g-flo) now removes colors from logfiles even if [-q](https://copyparty.eu/cli/#g-q) is not set 8c6d8a3c
|
||||
* update dompurify to 3.3.3 6a9e6da8
|
||||
* docs:
|
||||
* #1360 versus.md: more readable headers (thx @eugenesvk!) e71e1900
|
||||
* #1367 mention [--shr-who](https://copyparty.eu/cli/#g-shr-who) in the readme (thx @TWhiteShadow!) 4688410f
|
||||
|
||||
## 🌠 fun facts
|
||||
|
||||
* it is easter soon edc20175
|
||||
|
||||
|
||||
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2026-0311-0042 `v1.20.12` fix shares in ftp/sftp
|
||||
|
||||
## ⚠️ ATTN: this release fixes an ftp/sftp issue with shares
|
||||
|
||||
* [GHSA-67rw-2x62-mqqm](https://github.com/9001/copyparty/security/advisories/GHSA-67rw-2x62-mqqm): when a share is created for just one or more files inside a folder, it was possible to use FTP or SFTP to access the other files inside that folder by guessing the filenames
|
||||
* so ignore this issue if you did not enable [ftp](https://copyparty.eu/cli/#g-ftp) or [sftp](https://copyparty.eu/cli/#g-sftp) in the server config
|
||||
* 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](https://copyparty.eu/cli/#g-shr) global-option
|
||||
* password-protected shares were not affected through SFTP, only FTP
|
||||
|
||||
this release also fixes [GHSA-rcp6-88mm-9vgf](https://github.com/9001/copyparty/security/advisories/GHSA-rcp6-88mm-9vgf) but that one is nothing to worry about
|
||||
|
||||
## 🧪 new features
|
||||
|
||||
* features? in this econonmy?? ain't nobody got time for that
|
||||
|
||||
## 🩹 bugfixes
|
||||
|
||||
* 66f1ef63547a8c5f45dc2472801d2a973ff997cc [GHSA-67rw-2x62-mqqm](https://github.com/9001/copyparty/security/advisories/GHSA-67rw-2x62-mqqm) (shares)
|
||||
* 9f9d30f42c89d1d5fc79ae745f136a9d5f857192 [GHSA-rcp6-88mm-9vgf](https://github.com/9001/copyparty/security/advisories/GHSA-rcp6-88mm-9vgf) (the other thing)
|
||||
|
||||
## 🌠 fun facts
|
||||
|
||||
* the [first cve](https://github.com/9001/copyparty/security/advisories/GHSA-pxfv-7rr3-2qjg) is still by far the worst, none of the others even close... so at least that's nice
|
||||
* if you saw the cve notification and got all worked up, here's some [comfy music to relax and upgrade copyparty to](https://www.youtube.com/watch?v=A4zlH2mzMHw&list=PLRKwPvvniAjauumQljdrWAImRQGF3mCRU&index=1)
|
||||
|
||||
|
||||
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2026-0308-2106 `v1.20.11` what? nohtml is evolving!
|
||||
|
||||
|
|
|
|||
|
|
@ -264,6 +264,7 @@ sz=3321225472; csz=16777216;
|
|||
sz=4394967296; csz=25165824;
|
||||
sz=6509559808; csz=33554432;
|
||||
sz=138438953472; csz=50331648;
|
||||
sz=85932900352; csz=$((1024*1024*4)); # flippy bd
|
||||
f=csz-$csz; truncate -s $sz $f; sz=$((sz/16)); step=$((csz/16)); ofs=0; while [ $ofs -lt $sz ]; do dd if=/dev/urandom of=$f bs=16 count=2 seek=$ofs conv=notrunc iflag=fullblock; [ $ofs = 0 ] && ofs=$((ofs+step-1)) || ofs=$((ofs+step)); done
|
||||
|
||||
# py2 on osx
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ FROM alpine:3.23
|
|||
WORKDIR /z
|
||||
ENV ver_hashwasm=4.12.0 \
|
||||
ver_marked=4.3.0 \
|
||||
ver_dompf=3.4.0 \
|
||||
ver_dompf=3.4.1 \
|
||||
ver_mde=2.18.0 \
|
||||
ver_codemirror=5.65.18 \
|
||||
ver_fontawesome=5.13.0 \
|
||||
|
|
|
|||
|
|
@ -21,6 +21,6 @@ echo zlib=$zlib ff=$ff
|
|||
|
||||
[ "$1" ] && exit
|
||||
|
||||
[ $zlib ] && { make zlib; cp -pv 1 2 ../cver/; }
|
||||
[ $ff ] && { make ff; cp -pv 3 ../cver/; }
|
||||
[ $zlib ] && { make -C.. zlib; cp -pv 1 2 ../cver/; }
|
||||
[ $ff ] && { make -C.. ff; cp -pv 3 ../cver/; }
|
||||
rm -rf cver2
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ cat $f | awk '
|
|||
sub(/\[/,"");
|
||||
sub(/\]\([^)]+\)/,"");
|
||||
bab=$0;
|
||||
gsub(/ /,"-",bab);
|
||||
gsub(/[ :]+/,"-",bab);
|
||||
gsub(/\./,"",bab);
|
||||
h=sprintf("%" ((lv-1)*4+1) "s [%s](#%s)", "*",$0,bab);
|
||||
next
|
||||
|
|
|
|||
Loading…
Reference in a new issue