mirror of
https://github.com/9001/copyparty.git
synced 2025-08-18 01:22:13 -06:00
Merge branch '9001:hovudstraum' into hovudstraum
This commit is contained in:
commit
5a271b6061
13
README.md
13
README.md
|
@ -12,7 +12,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
||||||
|
|
||||||
📷 **screenshots:** [browser](#the-browser) // [upload](#uploading) // [unpost](#unpost) // [thumbnails](#thumbnails) // [search](#searching) // [fsearch](#file-search) // [zip-DL](#zip-downloads) // [md-viewer](#markdown-viewer)
|
📷 **screenshots:** [browser](#the-browser) // [upload](#uploading) // [unpost](#unpost) // [thumbnails](#thumbnails) // [search](#searching) // [fsearch](#file-search) // [zip-DL](#zip-downloads) // [md-viewer](#markdown-viewer)
|
||||||
|
|
||||||
🎬 **videos:** [upload](https://a.ocv.me/pub/demo/pics-vids/up2k.webm) // [cli-upload](https://a.ocv.me/pub/demo/pics-vids/u2cli.webm) // [race-the-beam](https://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm)
|
🎬 **videos:** [upload](https://a.ocv.me/pub/demo/pics-vids/up2k.webm) // [cli-upload](https://a.ocv.me/pub/demo/pics-vids/u2cli.webm) // [race-the-beam](https://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm) // 👉 **[feature-showcase](https://a.ocv.me/pub/demo/showcase-hq.webm)** ([youtube](https://www.youtube.com/watch?v=15_-hgsX2V0))
|
||||||
|
|
||||||
made in Norway 🇳🇴
|
made in Norway 🇳🇴
|
||||||
|
|
||||||
|
@ -1439,12 +1439,17 @@ if you enable deduplication with `--dedup` then it'll create a symlink instead o
|
||||||
**warning:** when enabling dedup, you should also:
|
**warning:** when enabling dedup, you should also:
|
||||||
* enable indexing with `-e2dsa` or volflag `e2dsa` (see [file indexing](#file-indexing) section below); strongly recommended
|
* enable indexing with `-e2dsa` or volflag `e2dsa` (see [file indexing](#file-indexing) section below); strongly recommended
|
||||||
* ...and/or `--hardlink-only` to use hardlink-based deduplication instead of symlinks; see explanation below
|
* ...and/or `--hardlink-only` to use hardlink-based deduplication instead of symlinks; see explanation below
|
||||||
|
* ...and/or `--reflink` to use CoW/reflink-based dedup (much safer than hardlink, but OS/FS-dependent)
|
||||||
|
|
||||||
it will not be safe to rename/delete files if you only enable dedup and none of the above; if you enable indexing then it is not *necessary* to also do hardlinks (but you may still want to)
|
it will not be safe to rename/delete files if you only enable dedup and none of the above; if you enable indexing then it is not *necessary* to also do hardlinks (but you may still want to)
|
||||||
|
|
||||||
by default, deduplication is done based on symlinks (symbolic links); these are tiny files which are pointers to the nearest full copy of the file
|
by default, deduplication is done based on symlinks (symbolic links); these are tiny files which are pointers to the nearest full copy of the file
|
||||||
|
|
||||||
you can choose to use hardlinks instead of softlinks, globally with `--hardlink-only` or volflag `hardlinkonly`;
|
you can choose to use hardlinks instead of softlinks, globally with `--hardlink-only` or volflag `hardlinkonly`, and you can choose to use reflinks with `--reflink` or volflag `reflink`
|
||||||
|
|
||||||
|
advantages of using reflinks (CoW, copy-on-write):
|
||||||
|
* entirely safe (when your filesystem supports it correctly); either file can be edited or deleted without affecting other copies
|
||||||
|
* only linux 5.3 or newer, only python 3.14 or newer, only some filesystems (btrfs probably ok, maybe xfs too, but zfs had bugs)
|
||||||
|
|
||||||
advantages of using hardlinks:
|
advantages of using hardlinks:
|
||||||
* hardlinks are more compatible with other software; they behave entirely like regular files
|
* hardlinks are more compatible with other software; they behave entirely like regular files
|
||||||
|
@ -2022,7 +2027,7 @@ some reverse proxies (such as [Caddy](https://caddyserver.com/)) can automatical
|
||||||
* **warning:** nginx-QUIC (HTTP/3) is still experimental and can make uploads much slower, so HTTP/1.1 is recommended for now
|
* **warning:** nginx-QUIC (HTTP/3) is still experimental and can make uploads much slower, so HTTP/1.1 is recommended for now
|
||||||
* depending on server/client, HTTP/1.1 can also be 5x faster than HTTP/2
|
* depending on server/client, HTTP/1.1 can also be 5x faster than HTTP/2
|
||||||
|
|
||||||
for improved security (and a 10% performance boost) consider listening on a unix-socket with `-i unix:770:www:/tmp/party.sock` (permission `770` means only members of group `www` can access it)
|
for improved security (and a 10% performance boost) consider listening on a unix-socket with `-i unix:770:www:/dev/shm/party.sock` (permission `770` means only members of group `www` can access it)
|
||||||
|
|
||||||
example webserver / reverse-proxy configs:
|
example webserver / reverse-proxy configs:
|
||||||
|
|
||||||
|
@ -2243,7 +2248,7 @@ NOTE: there used to be an aur package; this evaporated when copyparty was adopte
|
||||||
|
|
||||||
## fedora package
|
## fedora package
|
||||||
|
|
||||||
does not exist yet; using the [copr-pypi](https://copr.fedorainfracloud.org/coprs/g/copr/PyPI/) builds is **NOT recommended** because updates can be delayed by [several months](https://github.com/fedora-copr/copr/issues/3056)
|
does not exist yet; there are rumours that it is being packaged! keep an eye on this space...
|
||||||
|
|
||||||
|
|
||||||
## nix package
|
## nix package
|
||||||
|
|
|
@ -85,13 +85,13 @@ server {
|
||||||
proxy_buffer_size 16k;
|
proxy_buffer_size 16k;
|
||||||
proxy_busy_buffers_size 24k;
|
proxy_busy_buffers_size 24k;
|
||||||
|
|
||||||
|
proxy_set_header Connection "Keep-Alive";
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
# NOTE: with cloudflare you want this instead:
|
|
||||||
#proxy_set_header X-Forwarded-For $http_cf_connecting_ip;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
proxy_set_header Connection "Keep-Alive";
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
# NOTE: with cloudflare you want this X-Forwarded-For instead:
|
||||||
|
#proxy_set_header X-Forwarded-For $http_cf_connecting_ip;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,28 +4,31 @@
|
||||||
lib,
|
lib,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
with lib; let
|
with lib;
|
||||||
mkKeyValue = key: value:
|
let
|
||||||
if value == true
|
mkKeyValue =
|
||||||
then
|
key: value:
|
||||||
|
if value == true then
|
||||||
# sets with a true boolean value are coerced to just the key name
|
# sets with a true boolean value are coerced to just the key name
|
||||||
key
|
key
|
||||||
else if value == false
|
else if value == false then
|
||||||
then
|
|
||||||
# or omitted completely when false
|
# or omitted completely when false
|
||||||
""
|
""
|
||||||
else (generators.mkKeyValueDefault {inherit mkValueString;} ": " key value);
|
else
|
||||||
|
(generators.mkKeyValueDefault { inherit mkValueString; } ": " key value);
|
||||||
|
|
||||||
mkAttrsString = value: (generators.toKeyValue {inherit mkKeyValue;} value);
|
mkAttrsString = value: (generators.toKeyValue { inherit mkKeyValue; } value);
|
||||||
|
|
||||||
mkValueString = value:
|
mkValueString =
|
||||||
if isList value
|
value:
|
||||||
then (concatStringsSep ", " (map mkValueString value))
|
if isList value then
|
||||||
else if isAttrs value
|
(concatStringsSep ", " (map mkValueString value))
|
||||||
then "\n" + (mkAttrsString value)
|
else if isAttrs value then
|
||||||
else (generators.mkValueStringDefault {} value);
|
"\n" + (mkAttrsString value)
|
||||||
|
else
|
||||||
|
(generators.mkValueStringDefault { } value);
|
||||||
|
|
||||||
mkSectionName = value: "[" + (escape ["[" "]"] value) + "]";
|
mkSectionName = value: "[" + (escape [ "[" "]" ] value) + "]";
|
||||||
|
|
||||||
mkSection = name: attrs: ''
|
mkSection = name: attrs: ''
|
||||||
${mkSectionName name}
|
${mkSectionName name}
|
||||||
|
@ -57,7 +60,8 @@ with lib; let
|
||||||
externalCacheDir = "/var/cache/copyparty";
|
externalCacheDir = "/var/cache/copyparty";
|
||||||
externalStateDir = "/var/lib/copyparty";
|
externalStateDir = "/var/lib/copyparty";
|
||||||
defaultShareDir = "${externalStateDir}/data";
|
defaultShareDir = "${externalStateDir}/data";
|
||||||
in {
|
in
|
||||||
|
{
|
||||||
options.services.copyparty = {
|
options.services.copyparty = {
|
||||||
enable = mkEnableOption "web-based file manager";
|
enable = mkEnableOption "web-based file manager";
|
||||||
|
|
||||||
|
@ -128,7 +132,10 @@ in {
|
||||||
};
|
};
|
||||||
|
|
||||||
accounts = mkOption {
|
accounts = mkOption {
|
||||||
type = types.attrsOf (types.submodule ({...}: {
|
type = types.attrsOf (
|
||||||
|
types.submodule (
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
options = {
|
options = {
|
||||||
passwordFile = mkOption {
|
passwordFile = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
|
@ -139,11 +146,13 @@ in {
|
||||||
example = "/run/keys/copyparty/ed";
|
example = "/run/keys/copyparty/ed";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}));
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
description = ''
|
description = ''
|
||||||
A set of copyparty accounts to create.
|
A set of copyparty accounts to create.
|
||||||
'';
|
'';
|
||||||
default = {};
|
default = { };
|
||||||
example = literalExpression ''
|
example = literalExpression ''
|
||||||
{
|
{
|
||||||
ed.passwordFile = "/run/keys/copyparty/ed";
|
ed.passwordFile = "/run/keys/copyparty/ed";
|
||||||
|
@ -152,7 +161,10 @@ in {
|
||||||
};
|
};
|
||||||
|
|
||||||
volumes = mkOption {
|
volumes = mkOption {
|
||||||
type = types.attrsOf (types.submodule ({...}: {
|
type = types.attrsOf (
|
||||||
|
types.submodule (
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
options = {
|
options = {
|
||||||
path = mkOption {
|
path = mkOption {
|
||||||
type = types.path;
|
type = types.path;
|
||||||
|
@ -211,15 +223,19 @@ in {
|
||||||
nohash = "\.iso$";
|
nohash = "\.iso$";
|
||||||
};
|
};
|
||||||
'';
|
'';
|
||||||
default = {};
|
default = { };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}));
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
description = "A set of copyparty volumes to create";
|
description = "A set of copyparty volumes to create";
|
||||||
default = {
|
default = {
|
||||||
"/" = {
|
"/" = {
|
||||||
path = defaultShareDir;
|
path = defaultShareDir;
|
||||||
access = {r = "*";};
|
access = {
|
||||||
|
r = "*";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
example = literalExpression ''
|
example = literalExpression ''
|
||||||
|
@ -238,27 +254,30 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable (let
|
config = mkIf cfg.enable (
|
||||||
|
let
|
||||||
command = "${getExe cfg.package} -c ${runtimeConfigPath}";
|
command = "${getExe cfg.package} -c ${runtimeConfigPath}";
|
||||||
in {
|
in
|
||||||
|
{
|
||||||
systemd.services.copyparty = {
|
systemd.services.copyparty = {
|
||||||
description = "http file sharing hub";
|
description = "http file sharing hub";
|
||||||
wantedBy = ["multi-user.target"];
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
|
||||||
environment = {
|
environment = {
|
||||||
PYTHONUNBUFFERED = "true";
|
PYTHONUNBUFFERED = "true";
|
||||||
XDG_CONFIG_HOME = externalStateDir;
|
XDG_CONFIG_HOME = externalStateDir;
|
||||||
};
|
};
|
||||||
|
|
||||||
preStart = let
|
preStart =
|
||||||
replaceSecretCommand = name: attrs: "${getExe pkgs.replace-secret} '${
|
let
|
||||||
passwordPlaceholder name
|
replaceSecretCommand =
|
||||||
}' '${attrs.passwordFile}' ${runtimeConfigPath}";
|
name: attrs:
|
||||||
in ''
|
"${getExe pkgs.replace-secret} '${passwordPlaceholder name}' '${attrs.passwordFile}' ${runtimeConfigPath}";
|
||||||
|
in
|
||||||
|
''
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
install -m 600 ${configFile} ${runtimeConfigPath}
|
install -m 600 ${configFile} ${runtimeConfigPath}
|
||||||
${concatStringsSep "\n"
|
${concatStringsSep "\n" (mapAttrsToList replaceSecretCommand cfg.accounts)}
|
||||||
(mapAttrsToList replaceSecretCommand cfg.accounts)}
|
|
||||||
'';
|
'';
|
||||||
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
|
@ -267,29 +286,24 @@ in {
|
||||||
# Hardening options
|
# Hardening options
|
||||||
User = cfg.user;
|
User = cfg.user;
|
||||||
Group = cfg.group;
|
Group = cfg.group;
|
||||||
RuntimeDirectory = ["copyparty"];
|
RuntimeDirectory = [ "copyparty" ];
|
||||||
RuntimeDirectoryMode = "0700";
|
RuntimeDirectoryMode = "0700";
|
||||||
StateDirectory = ["copyparty"];
|
StateDirectory = [ "copyparty" ];
|
||||||
StateDirectoryMode = "0700";
|
StateDirectoryMode = "0700";
|
||||||
CacheDirectory = lib.mkIf (cfg.settings ? hist) ["copyparty"];
|
CacheDirectory = lib.mkIf (cfg.settings ? hist) [ "copyparty" ];
|
||||||
CacheDirectoryMode = lib.mkIf (cfg.settings ? hist) "0700";
|
CacheDirectoryMode = lib.mkIf (cfg.settings ? hist) "0700";
|
||||||
WorkingDirectory = externalStateDir;
|
WorkingDirectory = externalStateDir;
|
||||||
BindReadOnlyPaths =
|
BindReadOnlyPaths = [
|
||||||
[
|
|
||||||
"/nix/store"
|
"/nix/store"
|
||||||
"-/etc/resolv.conf"
|
"-/etc/resolv.conf"
|
||||||
"-/etc/nsswitch.conf"
|
"-/etc/nsswitch.conf"
|
||||||
|
"-/etc/group"
|
||||||
"-/etc/hosts"
|
"-/etc/hosts"
|
||||||
"-/etc/localtime"
|
"-/etc/localtime"
|
||||||
]
|
] ++ (mapAttrsToList (k: v: "-${v.passwordFile}") cfg.accounts);
|
||||||
++ (mapAttrsToList (k: v: "-${v.passwordFile}") cfg.accounts);
|
|
||||||
BindPaths =
|
BindPaths =
|
||||||
(
|
(if cfg.settings ? hist then [ cfg.settings.hist ] else [ ])
|
||||||
if cfg.settings ? hist
|
++ [ externalStateDir ]
|
||||||
then [cfg.settings.hist]
|
|
||||||
else []
|
|
||||||
)
|
|
||||||
++ [externalStateDir]
|
|
||||||
++ (mapAttrsToList (k: v: v.path) cfg.volumes);
|
++ (mapAttrsToList (k: v: v.path) cfg.volumes);
|
||||||
# ProtectSystem = "strict";
|
# ProtectSystem = "strict";
|
||||||
# Note that unlike what 'ro' implies,
|
# Note that unlike what 'ro' implies,
|
||||||
|
@ -332,11 +346,10 @@ in {
|
||||||
mode = ":755";
|
mode = ":755";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
)
|
) cfg.volumes
|
||||||
cfg.volumes
|
|
||||||
);
|
);
|
||||||
|
|
||||||
users.groups.copyparty = lib.mkIf (cfg.user == "copyparty" && cfg.group == "copyparty") {};
|
users.groups.copyparty = lib.mkIf (cfg.user == "copyparty" && cfg.group == "copyparty") { };
|
||||||
users.users.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";
|
description = "Service user for copyparty";
|
||||||
group = "copyparty";
|
group = "copyparty";
|
||||||
|
@ -344,9 +357,7 @@ in {
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
};
|
};
|
||||||
environment.systemPackages = lib.mkIf cfg.mkHashWrapper [
|
environment.systemPackages = lib.mkIf cfg.mkHashWrapper [
|
||||||
(pkgs.writeShellScriptBin
|
(pkgs.writeShellScriptBin "copyparty-hash" ''
|
||||||
"copyparty-hash"
|
|
||||||
''
|
|
||||||
set -a # automatically export variables
|
set -a # automatically export variables
|
||||||
# set same environment variables as the systemd service
|
# set same environment variables as the systemd service
|
||||||
${lib.pipe config.systemd.services.copyparty.environment [
|
${lib.pipe config.systemd.services.copyparty.environment [
|
||||||
|
@ -359,5 +370,6 @@ in {
|
||||||
exec ${command} --ah-cli
|
exec ${command} --ah-cli
|
||||||
'')
|
'')
|
||||||
];
|
];
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Maintainer: icxes <dev.null@need.moe>
|
# Maintainer: icxes <dev.null@need.moe>
|
||||||
pkgname=copyparty
|
pkgname=copyparty
|
||||||
pkgver="1.18.4"
|
pkgver="1.18.6"
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
||||||
arch=("any")
|
arch=("any")
|
||||||
|
@ -22,7 +22,7 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag
|
||||||
)
|
)
|
||||||
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||||
backup=("etc/${pkgname}.d/init" )
|
backup=("etc/${pkgname}.d/init" )
|
||||||
sha256sums=("ce903db06d2f1889fd600e8f47e0514d4cce66d8d06f726a8db2ab56ba3c164f")
|
sha256sums=("80762d91ac88815e73d0ca2806c6391dcf8ccd521bc402cc312349f3bc8e8b28")
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||||
|
|
|
@ -1,41 +1,67 @@
|
||||||
{ lib, stdenv, makeWrapper, fetchurl, util-linux, python, jinja2, impacket, pyftpdlib, pyopenssl, argon2-cffi, pillow, pyvips, pyzmq, ffmpeg, mutagen,
|
{
|
||||||
|
lib,
|
||||||
|
stdenv,
|
||||||
|
makeWrapper,
|
||||||
|
fetchurl,
|
||||||
|
util-linux,
|
||||||
|
python,
|
||||||
|
jinja2,
|
||||||
|
impacket,
|
||||||
|
pyopenssl,
|
||||||
|
cfssl,
|
||||||
|
argon2-cffi,
|
||||||
|
pillow,
|
||||||
|
pyvips,
|
||||||
|
pyzmq,
|
||||||
|
ffmpeg,
|
||||||
|
mutagen,
|
||||||
|
|
||||||
# use argon2id-hashed passwords in config files (sha2 is always available)
|
# use argon2id-hashed passwords in config files (sha2 is always available)
|
||||||
withHashedPasswords ? true,
|
withHashedPasswords ? true,
|
||||||
|
|
||||||
# generate TLS certificates on startup (pointless when reverse-proxied)
|
# generate TLS certificates on startup (pointless when reverse-proxied)
|
||||||
withCertgen ? false,
|
withCertgen ? false,
|
||||||
|
|
||||||
# create thumbnails with Pillow; faster than FFmpeg / MediaProcessing
|
# create thumbnails with Pillow; faster than FFmpeg / MediaProcessing
|
||||||
withThumbnails ? true,
|
withThumbnails ? true,
|
||||||
|
|
||||||
# create thumbnails with PyVIPS; even faster, uses more memory
|
# create thumbnails with PyVIPS; even faster, uses more memory
|
||||||
# -- can be combined with Pillow to support more filetypes
|
# -- can be combined with Pillow to support more filetypes
|
||||||
withFastThumbnails ? false,
|
withFastThumbnails ? false,
|
||||||
|
|
||||||
# enable FFmpeg; thumbnails for most filetypes (also video and audio), extract audio metadata, transcode audio to opus
|
# enable FFmpeg; thumbnails for most filetypes (also video and audio), extract audio metadata, transcode audio to opus
|
||||||
# -- possibly dangerous if you allow anonymous uploads, since FFmpeg has a huge attack surface
|
# -- possibly dangerous if you allow anonymous uploads, since FFmpeg has a huge attack surface
|
||||||
# -- can be combined with Thumbnails and/or FastThumbnails, since FFmpeg is slower than both
|
# -- can be combined with Thumbnails and/or FastThumbnails, since FFmpeg is slower than both
|
||||||
withMediaProcessing ? true,
|
withMediaProcessing ? true,
|
||||||
|
|
||||||
# if MediaProcessing is not enabled, you probably want this instead (less accurate, but much safer and faster)
|
# if MediaProcessing is not enabled, you probably want this instead (less accurate, but much safer and faster)
|
||||||
withBasicAudioMetadata ? false,
|
withBasicAudioMetadata ? false,
|
||||||
|
|
||||||
# send ZeroMQ messages from event-hooks
|
# send ZeroMQ messages from event-hooks
|
||||||
withZeroMQ ? true,
|
withZeroMQ ? true,
|
||||||
|
|
||||||
# enable FTPS support in the FTP server
|
# enable FTPS support in the FTP server
|
||||||
withFTPS ? false,
|
withFTPS ? false,
|
||||||
|
|
||||||
# samba/cifs server; dangerous and buggy, enable if you really need it
|
# samba/cifs server; dangerous and buggy, enable if you really need it
|
||||||
withSMB ? false,
|
withSMB ? false,
|
||||||
|
|
||||||
|
# extra packages to add to the PATH
|
||||||
|
extraPackages ? [ ],
|
||||||
|
|
||||||
|
# function that accepts a python packageset and returns a list of packages to
|
||||||
|
# be added to the python venv. useful for scripts and such that require
|
||||||
|
# additional dependencies
|
||||||
|
extraPythonPackages ? (_p: [ ]),
|
||||||
|
|
||||||
}:
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
pinData = lib.importJSON ./pin.json;
|
pinData = lib.importJSON ./pin.json;
|
||||||
pyEnv = python.withPackages (ps:
|
pyEnv = python.withPackages (
|
||||||
with ps; [
|
ps:
|
||||||
|
with ps;
|
||||||
|
[
|
||||||
jinja2
|
jinja2
|
||||||
]
|
]
|
||||||
++ lib.optional withSMB impacket
|
++ lib.optional withSMB impacket
|
||||||
|
@ -47,22 +73,36 @@ let
|
||||||
++ lib.optional withBasicAudioMetadata mutagen
|
++ lib.optional withBasicAudioMetadata mutagen
|
||||||
++ lib.optional withHashedPasswords argon2-cffi
|
++ lib.optional withHashedPasswords argon2-cffi
|
||||||
++ lib.optional withZeroMQ pyzmq
|
++ lib.optional withZeroMQ pyzmq
|
||||||
|
++ (extraPythonPackages ps)
|
||||||
);
|
);
|
||||||
in stdenv.mkDerivation {
|
|
||||||
|
runtimeDeps = ([ util-linux ] ++ extraPackages ++ lib.optional withMediaProcessing ffmpeg);
|
||||||
|
in
|
||||||
|
stdenv.mkDerivation {
|
||||||
pname = "copyparty";
|
pname = "copyparty";
|
||||||
version = pinData.version;
|
inherit (pinData) version;
|
||||||
src = fetchurl {
|
src = fetchurl {
|
||||||
url = pinData.url;
|
inherit (pinData) url hash;
|
||||||
hash = pinData.hash;
|
|
||||||
};
|
};
|
||||||
buildInputs = [ makeWrapper ];
|
nativeBuildInputs = [ makeWrapper ];
|
||||||
dontUnpack = true;
|
dontUnpack = true;
|
||||||
dontBuild = true;
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
install -Dm755 $src $out/share/copyparty-sfx.py
|
install -Dm755 $src $out/share/copyparty-sfx.py
|
||||||
makeWrapper ${pyEnv.interpreter} $out/bin/copyparty \
|
makeWrapper ${pyEnv.interpreter} $out/bin/copyparty \
|
||||||
--set PATH '${lib.makeBinPath ([ util-linux ] ++ lib.optional withMediaProcessing ffmpeg)}:$PATH' \
|
--prefix PATH : ${lib.makeBinPath runtimeDeps} \
|
||||||
--add-flags "$out/share/copyparty-sfx.py"
|
--add-flag $out/share/copyparty-sfx.py
|
||||||
'';
|
'';
|
||||||
meta.mainProgram = "copyparty";
|
meta = {
|
||||||
|
description = "Turn almost any device into a file server";
|
||||||
|
longDescription = ''
|
||||||
|
Portable file server with accelerated resumable uploads, dedup, WebDAV,
|
||||||
|
FTP, TFTP, zeroconf, media indexer, thumbnails++ all in one file, no deps
|
||||||
|
'';
|
||||||
|
homepage = "https://github.com/9001/copyparty";
|
||||||
|
changelog = "https://github.com/9001/copyparty/releases/tag/v${pinData.version}";
|
||||||
|
license = lib.licenses.mit;
|
||||||
|
inherit (python.meta) platforms;
|
||||||
|
mainProgram = "copyparty";
|
||||||
|
sourceProvenance = [ lib.sourceTypes.binaryBytecode ];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"url": "https://github.com/9001/copyparty/releases/download/v1.18.4/copyparty-sfx.py",
|
"url": "https://github.com/9001/copyparty/releases/download/v1.18.6/copyparty-sfx.py",
|
||||||
"version": "1.18.4",
|
"version": "1.18.6",
|
||||||
"hash": "sha256-rTX2yQne7i91vXJaUolLZYSjCNKVdQlvgv1x+N2UsDE="
|
"hash": "sha256-No89mzKHHZZH19ws9dqfvQO0pnZw7jKDMGhNa4LOFlY="
|
||||||
}
|
}
|
26
contrib/package/nix/partyfuse/default.nix
Normal file
26
contrib/package/nix/partyfuse/default.nix
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
stdenvNoCC,
|
||||||
|
copyparty,
|
||||||
|
python3,
|
||||||
|
makeBinaryWrapper,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
python = python3.withPackages (p: [ p.fusepy ]);
|
||||||
|
in
|
||||||
|
stdenvNoCC.mkDerivation {
|
||||||
|
pname = "partyfuse";
|
||||||
|
inherit (copyparty) version meta;
|
||||||
|
src = ../../../..;
|
||||||
|
|
||||||
|
nativeBuildInputs = [ makeBinaryWrapper ];
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
install -Dm444 bin/partyfuse.py -t $out/share/copyparty
|
||||||
|
makeWrapper ${python.interpreter} $out/bin/partyfuse \
|
||||||
|
--add-flag $out/share/copyparty/partyfuse.py
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
}
|
24
contrib/package/nix/u2c/default.nix
Normal file
24
contrib/package/nix/u2c/default.nix
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
stdenvNoCC,
|
||||||
|
copyparty,
|
||||||
|
python312,
|
||||||
|
makeBinaryWrapper,
|
||||||
|
}:
|
||||||
|
stdenvNoCC.mkDerivation {
|
||||||
|
pname = "u2c";
|
||||||
|
inherit (copyparty) version meta;
|
||||||
|
src = ../../../..;
|
||||||
|
|
||||||
|
nativeBuildInputs = [ makeBinaryWrapper ];
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
install -Dm444 bin/u2c.py -t $out/share/copyparty
|
||||||
|
mkdir $out/bin
|
||||||
|
makeWrapper ${python312.interpreter} $out/bin/u2c \
|
||||||
|
--add-flag $out/share/copyparty/u2c.py
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
}
|
|
@ -547,14 +547,15 @@ def get_sects():
|
||||||
when running behind a reverse-proxy, it's recommended to
|
when running behind a reverse-proxy, it's recommended to
|
||||||
use unix-sockets for improved performance and security;
|
use unix-sockets for improved performance and security;
|
||||||
|
|
||||||
\033[32m-i unix:770:www:\033[33m/tmp/a.sock\033[0m listens on \033[33m/tmp/a.sock\033[0m with
|
\033[32m-i unix:770:www:\033[33m/dev/shm/party.sock\033[0m listens on
|
||||||
permissions \033[33m0770\033[0m; only accessible to members of the \033[33mwww\033[0m
|
\033[33m/dev/shm/party.sock\033[0m with permissions \033[33m0770\033[0m;
|
||||||
group. This is the best approach. Alternatively,
|
only accessible to members of the \033[33mwww\033[0m group.
|
||||||
|
This is the best approach. Alternatively,
|
||||||
|
|
||||||
\033[32m-i unix:777:\033[33m/tmp/a.sock\033[0m sets perms \033[33m0777\033[0m so anyone can
|
\033[32m-i unix:777:\033[33m/dev/shm/party.sock\033[0m sets perms \033[33m0777\033[0m so anyone
|
||||||
access it; bad unless it's inside a restricted folder
|
can access it; bad unless it's inside a restricted folder
|
||||||
|
|
||||||
\033[32m-i unix:\033[33m/tmp/a.sock\033[0m keeps umask-defined permissions
|
\033[32m-i unix:\033[33m/dev/shm/party.sock\033[0m keeps umask-defined permission
|
||||||
(usually \033[33m0600\033[0m) and the same user/group as copyparty
|
(usually \033[33m0600\033[0m) and the same user/group as copyparty
|
||||||
|
|
||||||
\033[33m-p\033[0m (tcp ports) is ignored for unix sockets
|
\033[33m-p\033[0m (tcp ports) is ignored for unix sockets
|
||||||
|
@ -872,31 +873,31 @@ def get_sects():
|
||||||
|
|
||||||
similarly, \033[33m--chmod-d\033[0m and \033[33mchmod_d\033[0m sets the directory/folder perm
|
similarly, \033[33m--chmod-d\033[0m and \033[33mchmod_d\033[0m sets the directory/folder perm
|
||||||
|
|
||||||
the value is a three-digit octal number such as 755, 750, 644, etc.
|
the value is a three-digit octal number such as \033[32m755\033[0m, \033[32m750\033[0m, \033[32m644\033[0m, etc.
|
||||||
|
|
||||||
first digit = "User"; permission for the unix-user
|
first digit = "User"; permission for the unix-user
|
||||||
second digit = "Group"; permission for the unix-group
|
second digit = "Group"; permission for the unix-group
|
||||||
third digit = "Other"; permission for all other users/groups
|
third digit = "Other"; permission for all other users/groups
|
||||||
|
|
||||||
for files:
|
for files:
|
||||||
0 = --- = no access
|
\033[32m0\033[0m = \033[35m---\033[0m = no access
|
||||||
1 = --x = can execute the file as a program
|
\033[32m1\033[0m = \033[35m--x\033[0m = can execute the file as a program
|
||||||
2 = -w- = can write
|
\033[32m2\033[0m = \033[35m-w-\033[0m = can write
|
||||||
3 = -wx = can write and execute
|
\033[32m3\033[0m = \033[35m-wx\033[0m = can write and execute
|
||||||
4 = r-- = can read
|
\033[32m4\033[0m = \033[35mr--\033[0m = can read
|
||||||
5 = r-x = can read and execute
|
\033[32m5\033[0m = \033[35mr-x\033[0m = can read and execute
|
||||||
6 = rw- = can read and write
|
\033[32m6\033[0m = \033[35mrw-\033[0m = can read and write
|
||||||
7 = rwx = can read, write, execute
|
\033[32m7\033[0m = \033[35mrwx\033[0m = can read, write, execute
|
||||||
|
|
||||||
for directories/folders:
|
for directories/folders:
|
||||||
0 = --- = no access
|
\033[32m0\033[0m = \033[35m---\033[0m = no access
|
||||||
1 = --x = can read files in folder but not list contents
|
\033[32m1\033[0m = \033[35m--x\033[0m = can read files in folder but not list contents
|
||||||
2 = -w- = n/a
|
\033[32m2\033[0m = \033[35m-w-\033[0m = n/a
|
||||||
3 = -wx = can create files but not list
|
\033[32m3\033[0m = \033[35m-wx\033[0m = can create files but not list
|
||||||
4 = r-- = can list, but not read/write
|
\033[32m4\033[0m = \033[35mr--\033[0m = can list, but not read/write
|
||||||
5 = r-x = can list and read files
|
\033[32m5\033[0m = \033[35mr-x\033[0m = can list and read files
|
||||||
6 = rw- = n/a
|
\033[32m6\033[0m = \033[35mrw-\033[0m = n/a
|
||||||
7 = rwx = can read, write, list
|
\033[32m7\033[0m = \033[35mrwx\033[0m = can read, write, list
|
||||||
"""
|
"""
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1056,6 +1057,7 @@ def add_upload(ap):
|
||||||
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("--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)")
|
ap2.add_argument("--hardlink", action="store_true", help="enable hardlink-based dedup; will fallback on symlinks when that is impossible (across filesystems) (volflag=hardlink)")
|
||||||
ap2.add_argument("--hardlink-only", action="store_true", help="do not fallback to symlinks when a hardlink cannot be made (volflag=hardlinkonly)")
|
ap2.add_argument("--hardlink-only", action="store_true", help="do not fallback to symlinks when a hardlink cannot be made (volflag=hardlinkonly)")
|
||||||
|
ap2.add_argument("--reflink", action="store_true", help="enable reflink-based dedup; will fallback on full copies when that is impossible (non-CoW filesystem) (volflag=reflink)")
|
||||||
ap2.add_argument("--no-dupe", action="store_true", help="reject duplicate files during upload; only matches within the same volume (volflag=nodupe)")
|
ap2.add_argument("--no-dupe", action="store_true", help="reject duplicate files during upload; only matches within the same volume (volflag=nodupe)")
|
||||||
ap2.add_argument("--no-clone", action="store_true", help="do not use existing data on disk to satisfy dupe uploads; reduces server HDD reads in exchange for much more network load (volflag=noclone)")
|
ap2.add_argument("--no-clone", action="store_true", help="do not use existing data on disk to satisfy dupe uploads; reduces server HDD reads in exchange for much more network load (volflag=noclone)")
|
||||||
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("--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")
|
||||||
|
@ -1288,6 +1290,7 @@ def add_stats(ap):
|
||||||
def add_yolo(ap):
|
def add_yolo(ap):
|
||||||
ap2 = ap.add_argument_group('yolo options')
|
ap2 = ap.add_argument_group('yolo options')
|
||||||
ap2.add_argument("--allow-csrf", action="store_true", help="disable csrf protections; let other domains/sites impersonate you through cross-site requests")
|
ap2.add_argument("--allow-csrf", action="store_true", help="disable csrf protections; let other domains/sites impersonate you through cross-site requests")
|
||||||
|
ap2.add_argument("--cookie-lax", action="store_true", help="allow cookies from other domains (if you follow a link from another website into your server, you will arrive logged-in); this reduces protection against CSRF")
|
||||||
ap2.add_argument("--getmod", action="store_true", help="permit ?move=[...] and ?delete as GET")
|
ap2.add_argument("--getmod", action="store_true", help="permit ?move=[...] and ?delete as GET")
|
||||||
ap2.add_argument("--wo-up-readme", action="store_true", help="allow users with write-only access to upload logues and readmes without adding the _wo_ filename prefix (volflag=wo_up_readme)")
|
ap2.add_argument("--wo-up-readme", action="store_true", help="allow users with write-only access to upload logues and readmes without adding the _wo_ filename prefix (volflag=wo_up_readme)")
|
||||||
|
|
||||||
|
@ -1335,6 +1338,7 @@ def add_safety(ap):
|
||||||
ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything (volflag=norobots)")
|
ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything (volflag=norobots)")
|
||||||
ap2.add_argument("--logout", metavar="H", type=float, default=8086.0, help="logout clients after \033[33mH\033[0m hours of inactivity; [\033[32m0.0028\033[0m]=10sec, [\033[32m0.1\033[0m]=6min, [\033[32m24\033[0m]=day, [\033[32m168\033[0m]=week, [\033[32m720\033[0m]=month, [\033[32m8760\033[0m]=year)")
|
ap2.add_argument("--logout", metavar="H", type=float, default=8086.0, help="logout clients after \033[33mH\033[0m hours of inactivity; [\033[32m0.0028\033[0m]=10sec, [\033[32m0.1\033[0m]=6min, [\033[32m24\033[0m]=day, [\033[32m168\033[0m]=week, [\033[32m720\033[0m]=month, [\033[32m8760\033[0m]=year)")
|
||||||
ap2.add_argument("--ban-pw", metavar="N,W,B", type=u, default="9,60,1440", help="more than \033[33mN\033[0m wrong passwords in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; disable with [\033[32mno\033[0m]")
|
ap2.add_argument("--ban-pw", metavar="N,W,B", type=u, default="9,60,1440", help="more than \033[33mN\033[0m wrong passwords in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; disable with [\033[32mno\033[0m]")
|
||||||
|
ap2.add_argument("--ban-pwc", metavar="N,W,B", type=u, default="5,60,1440", help="more than \033[33mN\033[0m password-changes in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; disable with [\033[32mno\033[0m]")
|
||||||
ap2.add_argument("--ban-404", metavar="N,W,B", type=u, default="50,60,1440", help="hitting more than \033[33mN\033[0m 404's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; only affects users who cannot see directory listings because their access is either g/G/h")
|
ap2.add_argument("--ban-404", metavar="N,W,B", type=u, default="50,60,1440", help="hitting more than \033[33mN\033[0m 404's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; only affects users who cannot see directory listings because their access is either g/G/h")
|
||||||
ap2.add_argument("--ban-403", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m 403's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; [\033[32m1440\033[0m]=day, [\033[32m10080\033[0m]=week, [\033[32m43200\033[0m]=month")
|
ap2.add_argument("--ban-403", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m 403's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; [\033[32m1440\033[0m]=day, [\033[32m10080\033[0m]=week, [\033[32m43200\033[0m]=month")
|
||||||
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-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 ++)")
|
||||||
|
@ -1565,7 +1569,7 @@ def add_ui(ap, retry):
|
||||||
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("--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("--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("--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-np\033[0m")
|
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)")
|
||||||
ap2.add_argument("--k304", metavar="NUM", type=int, default=0, help="configure the option to enable/disable k304 on the controlpanel (workaround for buggy reverse-proxies); [\033[32m0\033[0m] = hidden and default-off, [\033[32m1\033[0m] = visible and default-off, [\033[32m2\033[0m] = visible and default-on")
|
ap2.add_argument("--k304", metavar="NUM", type=int, default=0, help="configure the option to enable/disable k304 on the controlpanel (workaround for buggy reverse-proxies); [\033[32m0\033[0m] = hidden and default-off, [\033[32m1\033[0m] = visible and default-off, [\033[32m2\033[0m] = visible and default-on")
|
||||||
ap2.add_argument("--no304", metavar="NUM", type=int, default=0, help="configure the option to enable/disable no304 on the controlpanel (workaround for buggy caching in browsers); [\033[32m0\033[0m] = hidden and default-off, [\033[32m1\033[0m] = visible and default-off, [\033[32m2\033[0m] = visible and default-on")
|
ap2.add_argument("--no304", metavar="NUM", type=int, default=0, help="configure the option to enable/disable no304 on the controlpanel (workaround for buggy caching in browsers); [\033[32m0\033[0m] = hidden and default-off, [\033[32m1\033[0m] = visible and default-off, [\033[32m2\033[0m] = visible and default-on")
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (1, 18, 4)
|
VERSION = (1, 18, 6)
|
||||||
CODENAME = "logtail"
|
CODENAME = "logtail"
|
||||||
BUILD_DT = (2025, 7, 25)
|
BUILD_DT = (2025, 7, 28)
|
||||||
|
|
||||||
S_VERSION = ".".join(map(str, VERSION))
|
S_VERSION = ".".join(map(str, VERSION))
|
||||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||||
|
|
|
@ -2124,6 +2124,7 @@ class AuthSrv(object):
|
||||||
all_mte = {}
|
all_mte = {}
|
||||||
errors = False
|
errors = False
|
||||||
free_umask = False
|
free_umask = False
|
||||||
|
have_reflink = False
|
||||||
for vol in vfs.all_nodes.values():
|
for vol in vfs.all_nodes.values():
|
||||||
if (self.args.e2ds and vol.axs.uwrite) or self.args.e2dsa:
|
if (self.args.e2ds and vol.axs.uwrite) or self.args.e2dsa:
|
||||||
vol.flags["e2ds"] = True
|
vol.flags["e2ds"] = True
|
||||||
|
@ -2207,6 +2208,9 @@ class AuthSrv(object):
|
||||||
if "unlistcr" in vol.flags or "unlistcw" in vol.flags:
|
if "unlistcr" in vol.flags or "unlistcw" in vol.flags:
|
||||||
self.args.have_unlistc = True
|
self.args.have_unlistc = True
|
||||||
|
|
||||||
|
if "reflink" in vol.flags:
|
||||||
|
have_reflink = True
|
||||||
|
|
||||||
zs = str(vol.flags.get("tcolor", "")).lstrip("#")
|
zs = str(vol.flags.get("tcolor", "")).lstrip("#")
|
||||||
if len(zs) == 3: # fc5 => ffcc55
|
if len(zs) == 3: # fc5 => ffcc55
|
||||||
vol.flags["tcolor"] = "".join([x * 2 for x in zs])
|
vol.flags["tcolor"] = "".join([x * 2 for x in zs])
|
||||||
|
@ -2571,6 +2575,13 @@ class AuthSrv(object):
|
||||||
t = "WARNING! The following IdP volumes are mounted below another volume where other users can read and/or write files. This is a SECURITY HAZARD!! When copyparty is restarted, it will not know about these IdP volumes yet. These volumes will then be accessible by an unexpected set of permissions UNTIL one of the users associated with their volume sends a request to the server. RECOMMENDATION: You should create a restricted volume where nobody can read/write files, and make sure that all IdP volumes are configured to appear somewhere below that volume."
|
t = "WARNING! The following IdP volumes are mounted below another volume where other users can read and/or write files. This is a SECURITY HAZARD!! When copyparty is restarted, it will not know about these IdP volumes yet. These volumes will then be accessible by an unexpected set of permissions UNTIL one of the users associated with their volume sends a request to the server. RECOMMENDATION: You should create a restricted volume where nobody can read/write files, and make sure that all IdP volumes are configured to appear somewhere below that volume."
|
||||||
self.log(t + "".join(self.idp_err), 1)
|
self.log(t + "".join(self.idp_err), 1)
|
||||||
|
|
||||||
|
if have_reflink:
|
||||||
|
t = "WARNING: Reflink-based dedup was requested, but %s. This will not work; files will be full copies instead."
|
||||||
|
if sys.version_info < (3, 14):
|
||||||
|
self.log(t % "your python version is not new enough", 1)
|
||||||
|
if not sys.platform.startswith("linux"):
|
||||||
|
self.log(t % "your OS is not Linux", 1)
|
||||||
|
|
||||||
self.vfs = vfs
|
self.vfs = vfs
|
||||||
self.acct = acct
|
self.acct = acct
|
||||||
self.defpw = defpw
|
self.defpw = defpw
|
||||||
|
|
|
@ -52,6 +52,7 @@ def vf_bmap() -> dict[str, str]:
|
||||||
"og_no_head",
|
"og_no_head",
|
||||||
"og_s_title",
|
"og_s_title",
|
||||||
"rand",
|
"rand",
|
||||||
|
"reflink",
|
||||||
"rmagic",
|
"rmagic",
|
||||||
"rss",
|
"rss",
|
||||||
"wo_up_readme",
|
"wo_up_readme",
|
||||||
|
@ -168,6 +169,7 @@ flagcats = {
|
||||||
"dedup": "enable symlink-based file deduplication",
|
"dedup": "enable symlink-based file deduplication",
|
||||||
"hardlink": "enable hardlink-based file deduplication,\nwith fallback on symlinks when that is impossible",
|
"hardlink": "enable hardlink-based file deduplication,\nwith fallback on symlinks when that is impossible",
|
||||||
"hardlinkonly": "dedup with hardlink only, never symlink;\nmake a full copy if hardlink is impossible",
|
"hardlinkonly": "dedup with hardlink only, never symlink;\nmake a full copy if hardlink is impossible",
|
||||||
|
"reflink": "enable reflink-based file deduplication,\nwith fallback on full copy when that is impossible",
|
||||||
"safededup": "verify on-disk data before using it for dedup",
|
"safededup": "verify on-disk data before using it for dedup",
|
||||||
"noclone": "take dupe data from clients, even if available on HDD",
|
"noclone": "take dupe data from clients, even if available on HDD",
|
||||||
"nodupe": "rejects existing files (instead of linking/cloning them)",
|
"nodupe": "rejects existing files (instead of linking/cloning them)",
|
||||||
|
|
|
@ -1575,6 +1575,18 @@ class HttpCli(object):
|
||||||
self.log("inaccessible: %r" % ("/" + self.vpath,))
|
self.log("inaccessible: %r" % ("/" + self.vpath,))
|
||||||
raise Pebkac(401, "authenticate")
|
raise Pebkac(401, "authenticate")
|
||||||
|
|
||||||
|
if "quota-available-bytes" in props and not self.args.nid:
|
||||||
|
bfree, btot, _ = get_df(vn.realpath, False)
|
||||||
|
if btot:
|
||||||
|
df = {
|
||||||
|
"quota-available-bytes": str(bfree),
|
||||||
|
"quota-used-bytes": str(btot - bfree),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
df = {}
|
||||||
|
else:
|
||||||
|
df = {}
|
||||||
|
|
||||||
fgen = itertools.chain([topdir], fgen)
|
fgen = itertools.chain([topdir], fgen)
|
||||||
vtop = vjoin(self.args.R, vjoin(vn.vpath, rem))
|
vtop = vjoin(self.args.R, vjoin(vn.vpath, rem))
|
||||||
|
|
||||||
|
@ -1617,6 +1629,9 @@ class HttpCli(object):
|
||||||
ap = os.path.join(tap, x["vp"])
|
ap = os.path.join(tap, x["vp"])
|
||||||
pvs["getcontenttype"] = html_escape(guess_mime(rp, ap))
|
pvs["getcontenttype"] = html_escape(guess_mime(rp, ap))
|
||||||
pvs["getcontentlength"] = str(st.st_size)
|
pvs["getcontentlength"] = str(st.st_size)
|
||||||
|
elif df:
|
||||||
|
pvs.update(df)
|
||||||
|
df = {}
|
||||||
|
|
||||||
for k, v in pvs.items():
|
for k, v in pvs.items():
|
||||||
if k not in props:
|
if k not in props:
|
||||||
|
@ -2912,6 +2927,7 @@ class HttpCli(object):
|
||||||
|
|
||||||
ok, msg = self.asrv.chpw(self.conn.hsrv.broker, self.uname, pwd)
|
ok, msg = self.asrv.chpw(self.conn.hsrv.broker, self.uname, pwd)
|
||||||
if ok:
|
if ok:
|
||||||
|
self.cbonk(self.conn.hsrv.gpwc, pwd, "pw", "too many password changes")
|
||||||
ok, msg = self.get_pwd_cookie(pwd)
|
ok, msg = self.get_pwd_cookie(pwd)
|
||||||
if ok:
|
if ok:
|
||||||
msg = "new password OK"
|
msg = "new password OK"
|
||||||
|
@ -2995,12 +3011,20 @@ class HttpCli(object):
|
||||||
# reset both plaintext and tls
|
# reset both plaintext and tls
|
||||||
# (only affects active tls cookies when tls)
|
# (only affects active tls cookies when tls)
|
||||||
for k in ("cppwd", "cppws") if self.is_https else ("cppwd",):
|
for k in ("cppwd", "cppws") if self.is_https else ("cppwd",):
|
||||||
ck = gencookie(k, pwd, self.args.R, False)
|
ck = gencookie(k, pwd, self.args.R, self.args.cookie_lax, False)
|
||||||
self.out_headerlist.append(("Set-Cookie", ck))
|
self.out_headerlist.append(("Set-Cookie", ck))
|
||||||
self.out_headers.pop("Set-Cookie", None) # drop keepalive
|
self.out_headers.pop("Set-Cookie", None) # drop keepalive
|
||||||
else:
|
else:
|
||||||
k = "cppws" if self.is_https else "cppwd"
|
k = "cppws" if self.is_https else "cppwd"
|
||||||
ck = gencookie(k, pwd, self.args.R, self.is_https, dur, "; HttpOnly")
|
ck = gencookie(
|
||||||
|
k,
|
||||||
|
pwd,
|
||||||
|
self.args.R,
|
||||||
|
self.args.cookie_lax,
|
||||||
|
self.is_https,
|
||||||
|
dur,
|
||||||
|
"; HttpOnly",
|
||||||
|
)
|
||||||
self.out_headers["Set-Cookie"] = ck
|
self.out_headers["Set-Cookie"] = ck
|
||||||
|
|
||||||
return dur > 0, msg
|
return dur > 0, msg
|
||||||
|
@ -3017,6 +3041,9 @@ class HttpCli(object):
|
||||||
self.gctx = vpath
|
self.gctx = vpath
|
||||||
vpath = undot(vpath)
|
vpath = undot(vpath)
|
||||||
vfs, rem = self.asrv.vfs.get(vpath, self.uname, False, True)
|
vfs, rem = self.asrv.vfs.get(vpath, self.uname, False, True)
|
||||||
|
if "nosub" in vfs.flags:
|
||||||
|
raise Pebkac(403, "mkdir is forbidden below this folder")
|
||||||
|
|
||||||
rem = sanitize_vpath(rem, "/")
|
rem = sanitize_vpath(rem, "/")
|
||||||
fn = vfs.canonical(rem)
|
fn = vfs.canonical(rem)
|
||||||
|
|
||||||
|
@ -4865,13 +4892,21 @@ class HttpCli(object):
|
||||||
def tx_svcs(self) -> bool:
|
def tx_svcs(self) -> bool:
|
||||||
aname = re.sub("[^0-9a-zA-Z]+", "", self.args.vname) or "a"
|
aname = re.sub("[^0-9a-zA-Z]+", "", self.args.vname) or "a"
|
||||||
ep = self.host
|
ep = self.host
|
||||||
host = ep.split(":")[0]
|
sep = "]:" if "]" in ep else ":"
|
||||||
hport = ep[ep.find(":") :] if ":" in ep else ""
|
if sep in ep:
|
||||||
rip = (
|
host, hport = ep.rsplit(":", 1)
|
||||||
host
|
hport = ":" + hport
|
||||||
if self.args.rclone_mdns or not self.args.zm
|
else:
|
||||||
else self.conn.hsrv.nm.map(self.ip) or host
|
host = ep
|
||||||
)
|
hport = ""
|
||||||
|
|
||||||
|
if host.endswith(".local") and self.args.zm and not self.args.rclone_mdns:
|
||||||
|
rip = self.conn.hsrv.nm.map(self.ip) or host
|
||||||
|
if ":" in rip and "[" not in rip:
|
||||||
|
rip = "[%s]" % (rip,)
|
||||||
|
else:
|
||||||
|
rip = host
|
||||||
|
|
||||||
# safer than html_escape/quotep since this avoids both XSS and shell-stuff
|
# safer than html_escape/quotep since this avoids both XSS and shell-stuff
|
||||||
pw = re.sub(r"[<>&$?`\"']", "_", self.pw or "hunter2")
|
pw = re.sub(r"[<>&$?`\"']", "_", self.pw or "hunter2")
|
||||||
vp = re.sub(r"[<>&$?`\"']", "_", self.uparam["hc"] or "").lstrip("/")
|
vp = re.sub(r"[<>&$?`\"']", "_", self.uparam["hc"] or "").lstrip("/")
|
||||||
|
@ -5041,7 +5076,7 @@ class HttpCli(object):
|
||||||
def setck(self) -> bool:
|
def setck(self) -> bool:
|
||||||
k, v = self.uparam["setck"].split("=", 1)
|
k, v = self.uparam["setck"].split("=", 1)
|
||||||
t = 0 if v in ("", "x") else 86400 * 299
|
t = 0 if v in ("", "x") else 86400 * 299
|
||||||
ck = gencookie(k, v, self.args.R, False, t)
|
ck = gencookie(k, v, self.args.R, self.args.cookie_lax, False, t)
|
||||||
self.out_headerlist.append(("Set-Cookie", ck))
|
self.out_headerlist.append(("Set-Cookie", ck))
|
||||||
if "cc" in self.ouparam:
|
if "cc" in self.ouparam:
|
||||||
self.redirect("", "?h#cc")
|
self.redirect("", "?h#cc")
|
||||||
|
@ -5053,7 +5088,7 @@ class HttpCli(object):
|
||||||
for k in ALL_COOKIES:
|
for k in ALL_COOKIES:
|
||||||
if k not in self.cookies:
|
if k not in self.cookies:
|
||||||
continue
|
continue
|
||||||
cookie = gencookie(k, "x", self.args.R, False)
|
cookie = gencookie(k, "x", self.args.R, self.args.cookie_lax, False)
|
||||||
self.out_headerlist.append(("Set-Cookie", cookie))
|
self.out_headerlist.append(("Set-Cookie", cookie))
|
||||||
|
|
||||||
self.redirect("", "?h#cc")
|
self.redirect("", "?h#cc")
|
||||||
|
@ -6137,13 +6172,13 @@ class HttpCli(object):
|
||||||
self.log("#wow #whoa")
|
self.log("#wow #whoa")
|
||||||
|
|
||||||
if not self.args.nid:
|
if not self.args.nid:
|
||||||
free, total, _ = get_df(abspath, False)
|
free, total, zs = get_df(abspath, False)
|
||||||
if total is not None:
|
if total:
|
||||||
h1 = humansize(free or 0)
|
h1 = humansize(free or 0)
|
||||||
h2 = humansize(total)
|
h2 = humansize(total)
|
||||||
srv_info.append("{} free of {}".format(h1, h2))
|
srv_info.append("{} free of {}".format(h1, h2))
|
||||||
elif free is not None:
|
elif zs:
|
||||||
srv_info.append(humansize(free, True) + " free")
|
self.log("diskfree(%r): %s" % (abspath, zs), 3)
|
||||||
|
|
||||||
srv_infot = "</span> // <span>".join(srv_info)
|
srv_infot = "</span> // <span>".join(srv_info)
|
||||||
|
|
||||||
|
|
|
@ -123,6 +123,7 @@ class HttpSrv(object):
|
||||||
self.nm = NetMap([], [])
|
self.nm = NetMap([], [])
|
||||||
self.ssdp: Optional["SSDPr"] = None
|
self.ssdp: Optional["SSDPr"] = None
|
||||||
self.gpwd = Garda(self.args.ban_pw)
|
self.gpwd = Garda(self.args.ban_pw)
|
||||||
|
self.gpwc = Garda(self.args.ban_pwc)
|
||||||
self.g404 = Garda(self.args.ban_404)
|
self.g404 = Garda(self.args.ban_404)
|
||||||
self.g403 = Garda(self.args.ban_403)
|
self.g403 = Garda(self.args.ban_403)
|
||||||
self.g422 = Garda(self.args.ban_422, False)
|
self.g422 = Garda(self.args.ban_422, False)
|
||||||
|
|
|
@ -166,12 +166,13 @@ def au_unpk(
|
||||||
znil = [x for x in znil if "cover" in x[0]] or znil
|
znil = [x for x in znil if "cover" in x[0]] or znil
|
||||||
znil = [x for x in znil if CBZ_01.search(x[0])] or znil
|
znil = [x for x in znil if CBZ_01.search(x[0])] or znil
|
||||||
t = "cbz: %d files, %d hits" % (nf, len(znil))
|
t = "cbz: %d files, %d hits" % (nf, len(znil))
|
||||||
|
using = sorted(znil)[0][1].filename
|
||||||
if znil:
|
if znil:
|
||||||
t += ", using " + znil[0][1].filename
|
t += ", using " + using
|
||||||
log(t)
|
log(t)
|
||||||
if not znil:
|
if not znil:
|
||||||
raise Exception("no images inside cbz")
|
raise Exception("no images inside cbz")
|
||||||
fi = zf.open(znil[0][1])
|
fi = zf.open(using)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise Exception("unknown compression %s" % (pk,))
|
raise Exception("unknown compression %s" % (pk,))
|
||||||
|
|
|
@ -168,6 +168,7 @@ class SvcHub(object):
|
||||||
# for non-http clients (ftp, tftp)
|
# for non-http clients (ftp, tftp)
|
||||||
self.bans: dict[str, int] = {}
|
self.bans: dict[str, int] = {}
|
||||||
self.gpwd = Garda(self.args.ban_pw)
|
self.gpwd = Garda(self.args.ban_pw)
|
||||||
|
self.gpwc = Garda(self.args.ban_pwc)
|
||||||
self.g404 = Garda(self.args.ban_404)
|
self.g404 = Garda(self.args.ban_404)
|
||||||
self.g403 = Garda(self.args.ban_403)
|
self.g403 = Garda(self.args.ban_403)
|
||||||
self.g422 = Garda(self.args.ban_422, False)
|
self.g422 = Garda(self.args.ban_422, False)
|
||||||
|
|
|
@ -3476,6 +3476,8 @@ class Up2k(object):
|
||||||
|
|
||||||
linked = False
|
linked = False
|
||||||
try:
|
try:
|
||||||
|
if "reflink" in flags:
|
||||||
|
raise Exception("reflink")
|
||||||
if not is_mv and not flags.get("dedup"):
|
if not is_mv and not flags.get("dedup"):
|
||||||
raise Exception("dedup is disabled in config")
|
raise Exception("dedup is disabled in config")
|
||||||
|
|
||||||
|
@ -3532,6 +3534,7 @@ class Up2k(object):
|
||||||
|
|
||||||
linked = True
|
linked = True
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
if str(ex) != "reflink":
|
||||||
self.log("cannot link; creating copy: " + repr(ex))
|
self.log("cannot link; creating copy: " + repr(ex))
|
||||||
if bos.path.isfile(src):
|
if bos.path.isfile(src):
|
||||||
csrc = src
|
csrc = src
|
||||||
|
|
|
@ -2037,15 +2037,25 @@ def formatdate(ts: Optional[float] = None) -> str:
|
||||||
return RFC2822 % (WKDAYS[wd], d, MONTHS[mo - 1], y, h, mi, s)
|
return RFC2822 % (WKDAYS[wd], d, MONTHS[mo - 1], y, h, mi, s)
|
||||||
|
|
||||||
|
|
||||||
def gencookie(k: str, v: str, r: str, tls: bool, dur: int = 0, txt: str = "") -> str:
|
def gencookie(
|
||||||
|
k: str, v: str, r: str, lax: bool, tls: bool, dur: int = 0, txt: str = ""
|
||||||
|
) -> str:
|
||||||
v = v.replace("%", "%25").replace(";", "%3B")
|
v = v.replace("%", "%25").replace(";", "%3B")
|
||||||
if dur:
|
if dur:
|
||||||
exp = formatdate(time.time() + dur)
|
exp = formatdate(time.time() + dur)
|
||||||
else:
|
else:
|
||||||
exp = "Fri, 15 Aug 1997 01:00:00 GMT"
|
exp = "Fri, 15 Aug 1997 01:00:00 GMT"
|
||||||
|
|
||||||
t = "%s=%s; Path=/%s; Expires=%s%s%s; SameSite=Lax"
|
t = "%s=%s; Path=/%s; Expires=%s%s%s; SameSite=%s"
|
||||||
return t % (k, v, r, exp, "; Secure" if tls else "", txt)
|
return t % (
|
||||||
|
k,
|
||||||
|
v,
|
||||||
|
r,
|
||||||
|
exp,
|
||||||
|
"; Secure" if tls else "",
|
||||||
|
txt,
|
||||||
|
"Lax" if lax else "Strict",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def humansize(sz: float, terse: bool = False) -> str:
|
def humansize(sz: float, terse: bool = False) -> str:
|
||||||
|
@ -2652,7 +2662,7 @@ def wunlink(log: "NamedLogger", abspath: str, flags: dict[str, Any]) -> bool:
|
||||||
return _fs_mvrm(log, abspath, "", False, flags)
|
return _fs_mvrm(log, abspath, "", False, flags)
|
||||||
|
|
||||||
|
|
||||||
def get_df(abspath: str, prune: bool) -> tuple[Optional[int], Optional[int], str]:
|
def get_df(abspath: str, prune: bool) -> tuple[int, int, str]:
|
||||||
try:
|
try:
|
||||||
ap = fsenc(abspath)
|
ap = fsenc(abspath)
|
||||||
while prune and not os.path.isdir(ap) and BOS_SEP in ap:
|
while prune and not os.path.isdir(ap) and BOS_SEP in ap:
|
||||||
|
@ -2663,17 +2673,22 @@ def get_df(abspath: str, prune: bool) -> tuple[Optional[int], Optional[int], str
|
||||||
assert ctypes # type: ignore # !rm
|
assert ctypes # type: ignore # !rm
|
||||||
abspath = fsdec(ap)
|
abspath = fsdec(ap)
|
||||||
bfree = ctypes.c_ulonglong(0)
|
bfree = ctypes.c_ulonglong(0)
|
||||||
|
btotal = ctypes.c_ulonglong(0)
|
||||||
|
bavail = ctypes.c_ulonglong(0)
|
||||||
ctypes.windll.kernel32.GetDiskFreeSpaceExW( # type: ignore
|
ctypes.windll.kernel32.GetDiskFreeSpaceExW( # type: ignore
|
||||||
ctypes.c_wchar_p(abspath), None, None, ctypes.pointer(bfree)
|
ctypes.c_wchar_p(abspath),
|
||||||
|
ctypes.pointer(bavail),
|
||||||
|
ctypes.pointer(btotal),
|
||||||
|
ctypes.pointer(bfree),
|
||||||
)
|
)
|
||||||
return (bfree.value, None, "")
|
return (bavail.value, btotal.value, "")
|
||||||
else:
|
else:
|
||||||
sv = os.statvfs(ap)
|
sv = os.statvfs(ap)
|
||||||
free = sv.f_frsize * sv.f_bfree
|
free = sv.f_frsize * sv.f_bavail
|
||||||
total = sv.f_frsize * sv.f_blocks
|
total = sv.f_frsize * sv.f_blocks
|
||||||
return (free, total, "")
|
return (free, total, "")
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
return (None, None, repr(ex))
|
return (0, 0, repr(ex))
|
||||||
|
|
||||||
|
|
||||||
if not ANYWIN and not MACOS:
|
if not ANYWIN and not MACOS:
|
||||||
|
|
|
@ -278,6 +278,7 @@ var Ls = {
|
||||||
"ml_drc": "dynamic range compressor",
|
"ml_drc": "dynamic range compressor",
|
||||||
|
|
||||||
"mt_loop": "loop/repeat one song\">🔁",
|
"mt_loop": "loop/repeat one song\">🔁",
|
||||||
|
"mt_one": "stop after one song\">1️⃣",
|
||||||
"mt_shuf": "shuffle the songs in each folder\">🔀",
|
"mt_shuf": "shuffle the songs in each folder\">🔀",
|
||||||
"mt_aplay": "autoplay if there is a song-ID in the link you clicked to access the server$N$Ndisabling this will also stop the page URL from being updated with song-IDs when playing music, to prevent autoplay if these settings are lost but the URL remains\">a▶",
|
"mt_aplay": "autoplay if there is a song-ID in the link you clicked to access the server$N$Ndisabling this will also stop the page URL from being updated with song-IDs when playing music, to prevent autoplay if these settings are lost but the URL remains\">a▶",
|
||||||
"mt_preload": "start loading the next song near the end for gapless playback\">preload",
|
"mt_preload": "start loading the next song near the end for gapless playback\">preload",
|
||||||
|
@ -295,6 +296,7 @@ var Ls = {
|
||||||
"mt_uncache": "clear cache (try this if your browser cached$Na broken copy of a song so it refuses to play)\">uncache",
|
"mt_uncache": "clear cache (try this if your browser cached$Na broken copy of a song so it refuses to play)\">uncache",
|
||||||
"mt_mloop": "loop the open folder\">🔁 loop",
|
"mt_mloop": "loop the open folder\">🔁 loop",
|
||||||
"mt_mnext": "load the next folder and continue\">📂 next",
|
"mt_mnext": "load the next folder and continue\">📂 next",
|
||||||
|
"mt_mstop": "stop playback\">⏸ stop",
|
||||||
"mt_cflac": "convert flac / wav to opus\">flac",
|
"mt_cflac": "convert flac / wav to opus\">flac",
|
||||||
"mt_caac": "convert aac / m4a to opus\">aac",
|
"mt_caac": "convert aac / m4a to opus\">aac",
|
||||||
"mt_coth": "convert all others (not mp3) to opus\">oth",
|
"mt_coth": "convert all others (not mp3) to opus\">oth",
|
||||||
|
@ -527,20 +529,20 @@ var Ls = {
|
||||||
"un_fclr": "clear filter",
|
"un_fclr": "clear filter",
|
||||||
"un_derr": 'unpost-delete failed:\n',
|
"un_derr": 'unpost-delete failed:\n',
|
||||||
"un_f5": 'something broke, please try a refresh or hit F5',
|
"un_f5": 'something broke, please try a refresh or hit F5',
|
||||||
"un_uf5": "Sorry, aber du musst die Seite neuladen (z.B. in dem du F5 oder STRG-R drückst) bevor zu diesen Upload abbrechen kannst",
|
"un_uf5": "sorry but you have to refresh the page (for example by pressing F5 or CTRL-R) before this upload can be aborted",
|
||||||
"un_nou": '<b>Warnung:</b> Der Server ist grade zu beschäftigt, um unvollständige Uploads anzuzeigen; Drücke den "Neu laden"-Link in ein paar Sekunden',
|
"un_nou": '<b>warning:</b> server too busy to show unfinished uploads; click the "refresh" link in a bit',
|
||||||
"un_noc": '<b>Warnung:</b> unpost von vollständig hochgeladenen Dateien ist über die Serverkonfiguration gesperrt',
|
"un_noc": '<b>warning:</b> unpost of fully uploaded files is not enabled/permitted in server config',
|
||||||
"un_max": "Zeige die ersten 2000 Dateien (benutze Filter, um die gewünschten Dateien zu finden)",
|
"un_max": "showing first 2000 files (use the filter)",
|
||||||
"un_avail": "{0} zuletzt hochgeladene Dateien können gelöscht werden<br />{1} Unvollständige können abgebrochen werden",
|
"un_avail": "{0} recent uploads can be deleted<br />{1} unfinished ones can be aborted",
|
||||||
"un_m2": "Sortiert nach Upload-Zeitpunkt; neuste zuerst:",
|
"un_m2": "sorted by upload time; most recent first:",
|
||||||
"un_no1": "sike! no uploads are sufficiently recent",
|
"un_no1": "sike! no uploads are sufficiently recent",
|
||||||
"un_no2": "sike! no uploads matching that filter are sufficiently recent",
|
"un_no2": "sike! no uploads matching that filter are sufficiently recent",
|
||||||
"un_next": "Lösche die nächsten {0} Dateien",
|
"un_next": "delete the next {0} files below",
|
||||||
"un_abrt": "Abbrechen",
|
"un_abrt": "abort",
|
||||||
"un_del": "Löschen",
|
"un_del": "delete",
|
||||||
"un_m3": "Deine letzten Uploads werden geladen ...",
|
"un_m3": "loading your recent uploads...",
|
||||||
"un_busy": "Lösche {0} Dateien ...",
|
"un_busy": "deleting {0} files...",
|
||||||
"un_clip": "{0} Links in die Zwischenablage kopiert",
|
"un_clip": "{0} links copied to clipboard",
|
||||||
|
|
||||||
"u_https1": "you should",
|
"u_https1": "you should",
|
||||||
"u_https2": "switch to https",
|
"u_https2": "switch to https",
|
||||||
|
@ -903,6 +905,7 @@ var Ls = {
|
||||||
"ml_drc": "compressor (volum-utjevning)",
|
"ml_drc": "compressor (volum-utjevning)",
|
||||||
|
|
||||||
"mt_loop": "spill den samme sangen om og om igjen\">🔁",
|
"mt_loop": "spill den samme sangen om og om igjen\">🔁",
|
||||||
|
"mt_one": "spill kun én sang\">1️⃣",
|
||||||
"mt_shuf": "sangene i hver mappe$Nspilles i tilfeldig rekkefølge\">🔀",
|
"mt_shuf": "sangene i hver mappe$Nspilles i tilfeldig rekkefølge\">🔀",
|
||||||
"mt_aplay": "forsøk å starte avspilling hvis linken du klikket på for å åpne nettsiden inneholder en sang-ID$N$Nhvis denne deaktiveres så vil heller ikke nettside-URLen bli oppdatert med sang-ID'er når musikk spilles, i tilfelle innstillingene skulle gå tapt og nettsiden lastes på ny\">a▶",
|
"mt_aplay": "forsøk å starte avspilling hvis linken du klikket på for å åpne nettsiden inneholder en sang-ID$N$Nhvis denne deaktiveres så vil heller ikke nettside-URLen bli oppdatert med sang-ID'er når musikk spilles, i tilfelle innstillingene skulle gå tapt og nettsiden lastes på ny\">a▶",
|
||||||
"mt_preload": "hent ned litt av neste sang i forkant,$Nslik at pausen i overgangen blir mindre\">forles",
|
"mt_preload": "hent ned litt av neste sang i forkant,$Nslik at pausen i overgangen blir mindre\">forles",
|
||||||
|
@ -920,6 +923,7 @@ var Ls = {
|
||||||
"mt_uncache": "prøv denne hvis en sang ikke spiller riktig\">oppfrisk",
|
"mt_uncache": "prøv denne hvis en sang ikke spiller riktig\">oppfrisk",
|
||||||
"mt_mloop": "repeter hele mappen\">🔁 gjenta",
|
"mt_mloop": "repeter hele mappen\">🔁 gjenta",
|
||||||
"mt_mnext": "hopp til neste mappe og fortsett\">📂 neste",
|
"mt_mnext": "hopp til neste mappe og fortsett\">📂 neste",
|
||||||
|
"mt_mstop": "stopp avspilling\">⏸ stopp",
|
||||||
"mt_cflac": "konverter flac / wav-filer til opus\">flac",
|
"mt_cflac": "konverter flac / wav-filer til opus\">flac",
|
||||||
"mt_caac": "konverter aac / m4a-filer til to opus\">aac",
|
"mt_caac": "konverter aac / m4a-filer til to opus\">aac",
|
||||||
"mt_coth": "konverter alt annet (men ikke mp3) til opus\">andre",
|
"mt_coth": "konverter alt annet (men ikke mp3) til opus\">andre",
|
||||||
|
@ -1528,6 +1532,7 @@ var Ls = {
|
||||||
"ml_drc": "动态范围压缩器",
|
"ml_drc": "动态范围压缩器",
|
||||||
|
|
||||||
"mt_loop": "循环播放当前的歌曲\">🔁", //m
|
"mt_loop": "循环播放当前的歌曲\">🔁", //m
|
||||||
|
"mt_one": "只播放一首歌后停止\">1️⃣", //m
|
||||||
"mt_shuf": "在每个文件夹中随机播放歌曲\">🔀",
|
"mt_shuf": "在每个文件夹中随机播放歌曲\">🔀",
|
||||||
"mt_aplay": "如果链接中有歌曲 ID,则自动播放,禁用此选项将停止在播放音乐时更新页面 URL 中的歌曲 ID,以防止在设置丢失但 URL 保留时自动播放\">自动播放▶",
|
"mt_aplay": "如果链接中有歌曲 ID,则自动播放,禁用此选项将停止在播放音乐时更新页面 URL 中的歌曲 ID,以防止在设置丢失但 URL 保留时自动播放\">自动播放▶",
|
||||||
"mt_preload": "在歌曲快结束时开始加载下一首歌,以实现无缝播放\">预加载",
|
"mt_preload": "在歌曲快结束时开始加载下一首歌,以实现无缝播放\">预加载",
|
||||||
|
@ -1545,6 +1550,7 @@ var Ls = {
|
||||||
"mt_uncache": "清除缓存 $N(如果你的浏览器缓存了一个损坏的歌曲副本而拒绝播放,请尝试此操作)\">uncache",
|
"mt_uncache": "清除缓存 $N(如果你的浏览器缓存了一个损坏的歌曲副本而拒绝播放,请尝试此操作)\">uncache",
|
||||||
"mt_mloop": "循环打开的文件夹\">🔁 循环",
|
"mt_mloop": "循环打开的文件夹\">🔁 循环",
|
||||||
"mt_mnext": "加载下一个文件夹并继续\">📂 下一首",
|
"mt_mnext": "加载下一个文件夹并继续\">📂 下一首",
|
||||||
|
"mt_mstop": "停止播放\">⏸ 停止", //m
|
||||||
"mt_cflac": "将 flac / wav 转换为 opus\">flac",
|
"mt_cflac": "将 flac / wav 转换为 opus\">flac",
|
||||||
"mt_caac": "将 aac / m4a 转换为 opus\">aac",
|
"mt_caac": "将 aac / m4a 转换为 opus\">aac",
|
||||||
"mt_coth": "将所有其他(不是 mp3)转换为 opus\">oth",
|
"mt_coth": "将所有其他(不是 mp3)转换为 opus\">oth",
|
||||||
|
@ -2992,6 +2998,7 @@ var mpl = (function () {
|
||||||
ebi('op_player').innerHTML = (
|
ebi('op_player').innerHTML = (
|
||||||
'<div><h3>' + L.cl_opts + '</h3><div>' +
|
'<div><h3>' + L.cl_opts + '</h3><div>' +
|
||||||
'<a href="#" class="tgl btn" id="au_loop" tt="' + L.mt_loop + '</a>' +
|
'<a href="#" class="tgl btn" id="au_loop" tt="' + L.mt_loop + '</a>' +
|
||||||
|
'<a href="#" class="tgl btn" id="au_one" tt="' + L.mt_one + '</a>' +
|
||||||
'<a href="#" class="tgl btn" id="au_shuf" tt="' + L.mt_shuf + '</a>' +
|
'<a href="#" class="tgl btn" id="au_shuf" tt="' + L.mt_shuf + '</a>' +
|
||||||
'<a href="#" class="tgl btn" id="au_aplay" tt="' + L.mt_aplay + '</a>' +
|
'<a href="#" class="tgl btn" id="au_aplay" tt="' + L.mt_aplay + '</a>' +
|
||||||
'<a href="#" class="tgl btn" id="au_preload" tt="' + L.mt_preload + '</a>' +
|
'<a href="#" class="tgl btn" id="au_preload" tt="' + L.mt_preload + '</a>' +
|
||||||
|
@ -3015,6 +3022,7 @@ var mpl = (function () {
|
||||||
'<div><h3>' + L.ml_pmode + '</h3><div id="pb_mode">' +
|
'<div><h3>' + L.ml_pmode + '</h3><div id="pb_mode">' +
|
||||||
'<a href="#" class="tgl btn" m="loop" tt="' + L.mt_mloop + '</a>' +
|
'<a href="#" class="tgl btn" m="loop" tt="' + L.mt_mloop + '</a>' +
|
||||||
'<a href="#" class="tgl btn" m="next" tt="' + L.mt_mnext + '</a>' +
|
'<a href="#" class="tgl btn" m="next" tt="' + L.mt_mnext + '</a>' +
|
||||||
|
'<a href="#" class="tgl btn" m="stop" tt="' + L.mt_mstop + '</a>' +
|
||||||
'</div></div>' +
|
'</div></div>' +
|
||||||
|
|
||||||
(have_acode ? (
|
(have_acode ? (
|
||||||
|
@ -3040,11 +3048,15 @@ var mpl = (function () {
|
||||||
'');
|
'');
|
||||||
|
|
||||||
var r = {
|
var r = {
|
||||||
"pb_mode": (sread('pb_mode', ['loop', 'next']) || 'next').split('-')[0],
|
"pb_mode": (sread('pb_mode', ['loop', 'next', 'stop']) || 'next').split('-')[0],
|
||||||
"os_ctl": bcfg_get('au_os_ctl', have_mctl) && have_mctl,
|
"os_ctl": bcfg_get('au_os_ctl', have_mctl) && have_mctl,
|
||||||
'traversals': 0,
|
'traversals': 0,
|
||||||
'm3ut': '#EXTM3U\n',
|
'm3ut': '#EXTM3U\n',
|
||||||
};
|
};
|
||||||
|
bcfg_bind(r, 'one', 'au_one', false, function (v) {
|
||||||
|
if (mp.au)
|
||||||
|
mp.au.loop = !v && r.loop;
|
||||||
|
});
|
||||||
bcfg_bind(r, 'loop', 'au_loop', false, function (v) {
|
bcfg_bind(r, 'loop', 'au_loop', false, function (v) {
|
||||||
if (mp.au)
|
if (mp.au)
|
||||||
mp.au.loop = v;
|
mp.au.loop = v;
|
||||||
|
@ -4294,7 +4306,7 @@ var mpui = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// preload next song
|
// preload next song
|
||||||
if (mpl.preload && preloaded != mp.au.rsrc) {
|
if (!mpl.one && mpl.preload && preloaded != mp.au.rsrc) {
|
||||||
var len = mp.au.duration,
|
var len = mp.au.duration,
|
||||||
rem = pos > 1 ? len - pos : 999,
|
rem = pos > 1 ? len - pos : 999,
|
||||||
full = null;
|
full = null;
|
||||||
|
@ -4313,7 +4325,12 @@ var mpui = (function () {
|
||||||
var oi = mp.order.indexOf(mp.au.tid) + 1,
|
var oi = mp.order.indexOf(mp.au.tid) + 1,
|
||||||
evp = get_evpath();
|
evp = get_evpath();
|
||||||
|
|
||||||
if (mpl.pb_mode == 'loop' || mp.au.evp != evp || ebi('unsearch'))
|
if (oi >= mp.order.length && (
|
||||||
|
mpl.one ||
|
||||||
|
mpl.pb_mode != 'next' ||
|
||||||
|
mp.au.evp != evp ||
|
||||||
|
ebi('unsearch'))
|
||||||
|
)
|
||||||
oi = 0;
|
oi = 0;
|
||||||
|
|
||||||
if (oi >= mp.order.length) {
|
if (oi >= mp.order.length) {
|
||||||
|
@ -4799,6 +4816,9 @@ function play(tid, is_ev, seek) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tn >= mp.order.length) {
|
if (tn >= mp.order.length) {
|
||||||
|
if (mpl.pb_mode == 'stop')
|
||||||
|
return;
|
||||||
|
|
||||||
if (mpl.pb_mode == 'loop' || ebi('unsearch')) {
|
if (mpl.pb_mode == 'loop' || ebi('unsearch')) {
|
||||||
tn = 0;
|
tn = 0;
|
||||||
}
|
}
|
||||||
|
@ -4883,7 +4903,7 @@ function play(tid, is_ev, seek) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mp.nopause();
|
mp.nopause();
|
||||||
mp.au.loop = mpl.loop;
|
mp.au.loop = mpl.loop && !mpl.one;
|
||||||
if (mpl.aplay || is_ev !== -1)
|
if (mpl.aplay || is_ev !== -1)
|
||||||
mp.au.play();
|
mp.au.play();
|
||||||
|
|
||||||
|
@ -4929,6 +4949,8 @@ function scroll2playing() {
|
||||||
|
|
||||||
|
|
||||||
function evau_end(e) {
|
function evau_end(e) {
|
||||||
|
if (mpl.one)
|
||||||
|
return;
|
||||||
if (!mpl.loop)
|
if (!mpl.loop)
|
||||||
return next_song(e);
|
return next_song(e);
|
||||||
ev(e);
|
ev(e);
|
||||||
|
@ -6650,6 +6672,7 @@ var showfile = (function () {
|
||||||
m = /[?&](k=[^&#]+)/.exec(url);
|
m = /[?&](k=[^&#]+)/.exec(url);
|
||||||
|
|
||||||
url = url.split('?')[0] + (m ? '?' + m[1] : '');
|
url = url.split('?')[0] + (m ? '?' + m[1] : '');
|
||||||
|
assert_vp(url);
|
||||||
if (r.taildoc)
|
if (r.taildoc)
|
||||||
return r.tail(url, no_push);
|
return r.tail(url, no_push);
|
||||||
|
|
||||||
|
@ -8068,7 +8091,7 @@ var search_ui = (function () {
|
||||||
nodes = ['<tr><td>-</td><td><div>' + links + '</div>', sz];
|
nodes = ['<tr><td>-</td><td><div>' + links + '</div>', sz];
|
||||||
|
|
||||||
for (var b = 0; b < tagord.length; b++) {
|
for (var b = 0; b < tagord.length; b++) {
|
||||||
var k = tagord[b],
|
var k = esc(tagord[b]),
|
||||||
v = r.tags[k] || "";
|
v = r.tags[k] || "";
|
||||||
|
|
||||||
if (k == ".dur") {
|
if (k == ".dur") {
|
||||||
|
@ -8077,7 +8100,7 @@ var search_ui = (function () {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes.push(v);
|
nodes.push(esc('' + v));
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes = nodes.concat([ext, unix2iso(ts)]);
|
nodes = nodes.concat([ext, unix2iso(ts)]);
|
||||||
|
@ -8144,6 +8167,7 @@ function ev_load_m3u(e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function load_m3u(url) {
|
function load_m3u(url) {
|
||||||
|
assert_vp(url);
|
||||||
var xhr = new XHR();
|
var xhr = new XHR();
|
||||||
xhr.open('GET', url, true);
|
xhr.open('GET', url, true);
|
||||||
xhr.onload = render_m3u;
|
xhr.onload = render_m3u;
|
||||||
|
@ -8548,6 +8572,17 @@ var treectl = (function () {
|
||||||
return toast.err(30, "bad <code>?tree</code> reply;\nexpected json, got this:\n\n" + esc(this.responseText + ''));
|
return toast.err(30, "bad <code>?tree</code> reply;\nexpected json, got this:\n\n" + esc(this.responseText + ''));
|
||||||
}
|
}
|
||||||
r.rendertree(res, this.ts, this.top, this.dst, this.rst);
|
r.rendertree(res, this.ts, this.top, this.dst, this.rst);
|
||||||
|
|
||||||
|
if (r.lsc && r.lsc.unlist)
|
||||||
|
r.prunetree(r.lsc);
|
||||||
|
};
|
||||||
|
|
||||||
|
r.prunetree = function (res) {
|
||||||
|
var ptn = new RegExp(res.unlist);
|
||||||
|
var els = QSA('#treeul li>a+a');
|
||||||
|
for (var a = els.length - 1; a >= 0; a--)
|
||||||
|
if (ptn.exec(els[a].textContent) && !els[a].className)
|
||||||
|
els[a].closest('ul').removeChild(els[a].closest('li'));
|
||||||
};
|
};
|
||||||
|
|
||||||
r.rendertree = function (res, ts, top0, dst, rst) {
|
r.rendertree = function (res, ts, top0, dst, rst) {
|
||||||
|
@ -8835,6 +8870,8 @@ var treectl = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
r.rendertree({ "a": dirs }, this.ts, ".", get_evpath() + (dk ? '?k=' + dk : ''));
|
r.rendertree({ "a": dirs }, this.ts, ".", get_evpath() + (dk ? '?k=' + dk : ''));
|
||||||
|
if (res.unlist)
|
||||||
|
r.prunetree(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
r.gentab(this.top, res);
|
r.gentab(this.top, res);
|
||||||
|
@ -8895,6 +8932,7 @@ var treectl = (function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
r.gentab = function (top, res) {
|
r.gentab = function (top, res) {
|
||||||
|
showfile.untail();
|
||||||
var nodes = res.dirs.concat(res.files),
|
var nodes = res.dirs.concat(res.files),
|
||||||
html = mk_files_header(res.taglist),
|
html = mk_files_header(res.taglist),
|
||||||
sel = msel.hist[top],
|
sel = msel.hist[top],
|
||||||
|
@ -8915,7 +8953,7 @@ var treectl = (function () {
|
||||||
if (res.unlist) {
|
if (res.unlist) {
|
||||||
var ptn = new RegExp(res.unlist);
|
var ptn = new RegExp(res.unlist);
|
||||||
for (var a = nodes.length - 1; a >= 0; a--)
|
for (var a = nodes.length - 1; a >= 0; a--)
|
||||||
if (ptn.exec(nodes[a].href.split('?')[0]))
|
if (ptn.exec(uricom_dec(nodes[a].href.split('?')[0])))
|
||||||
nodes.splice(a, 1);
|
nodes.splice(a, 1);
|
||||||
}
|
}
|
||||||
nodes = sortfiles(nodes);
|
nodes = sortfiles(nodes);
|
||||||
|
@ -8965,7 +9003,7 @@ var treectl = (function () {
|
||||||
top + tn.href + '" id="' + id + '">' + hname + '</a>', tn.sz];
|
top + tn.href + '" id="' + id + '">' + hname + '</a>', tn.sz];
|
||||||
|
|
||||||
for (var b = 0; b < res.taglist.length; b++) {
|
for (var b = 0; b < res.taglist.length; b++) {
|
||||||
var k = res.taglist[b],
|
var k = esc(res.taglist[b]),
|
||||||
v = (tn.tags || {})[k] || "",
|
v = (tn.tags || {})[k] || "",
|
||||||
sv = null;
|
sv = null;
|
||||||
|
|
||||||
|
@ -8974,7 +9012,7 @@ var treectl = (function () {
|
||||||
else if (k == ".up_at")
|
else if (k == ".up_at")
|
||||||
sv = v ? unix2iso(v) : "";
|
sv = v ? unix2iso(v) : "";
|
||||||
else {
|
else {
|
||||||
ln.push(v);
|
ln.push(esc('' + v));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ln[ln.length - 1] += '</td><td sortv="' + v + '">' + sv;
|
ln[ln.length - 1] += '</td><td sortv="' + v + '">' + sv;
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
<span class="os lin mac">
|
<span class="os lin mac">
|
||||||
{% if accs %}<code><b id="pw0">{{ pw }}</b></code>=password, {% endif %}<code><b>mp</b></code>=mountpoint
|
{% if accs %}<code><b id="pw0">{{ pw }}</b></code>=password, {% endif %}<code><b>mp</b></code>=mountpoint
|
||||||
</span>
|
</span>
|
||||||
<a href="#" id="setpw">use real password</a>
|
{% if accs %}<a href="#" id="setpw">use real password</a>{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ function setos(os) {
|
||||||
setos(WINDOWS ? 'win' : LINUX ? 'lin' : MACOS ? 'mac' : 'idk');
|
setos(WINDOWS ? 'win' : LINUX ? 'lin' : MACOS ? 'mac' : 'idk');
|
||||||
|
|
||||||
|
|
||||||
ebi('setpw').onclick = function (e) {
|
function setpw(e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
modal.prompt('password:', '', function (v) {
|
modal.prompt('password:', '', function (v) {
|
||||||
if (!v)
|
if (!v)
|
||||||
|
@ -65,3 +65,5 @@ ebi('setpw').onclick = function (e) {
|
||||||
add_dls();
|
add_dls();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (ebi('setpw'))
|
||||||
|
ebi('setpw').onclick = setpw;
|
||||||
|
|
|
@ -383,8 +383,10 @@ if (!String.prototype.format)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var have_URL = false;
|
||||||
try {
|
try {
|
||||||
new URL('/a/', 'https://a.com/');
|
new URL('/a/', 'https://a.com/');
|
||||||
|
have_URL = true;
|
||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
console.log('ie11 shim URL()');
|
console.log('ie11 shim URL()');
|
||||||
|
@ -732,6 +734,16 @@ function makeSortable(table, cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function assert_vp(path) {
|
||||||
|
if (path.indexOf('//') + 1)
|
||||||
|
throw 'nonlocal1: ' + path;
|
||||||
|
|
||||||
|
var o = window.location.origin;
|
||||||
|
if (have_URL && (new URL(path, o)).origin != o)
|
||||||
|
throw 'nonlocal2: ' + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function linksplit(rp, id) {
|
function linksplit(rp, id) {
|
||||||
var ret = [],
|
var ret = [],
|
||||||
apath = '/',
|
apath = '/',
|
||||||
|
|
|
@ -1,3 +1,57 @@
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2025-0727-2305 `v1.18.5` SECURITY: fix XSS in media tags
|
||||||
|
|
||||||
|
## ⚠️ ATTN: this release fixes an XSS vulnerability
|
||||||
|
|
||||||
|
[GHSA-9q4r-x2hj-jmvr](https://github.com/9001/copyparty/security/advisories/GHSA-9q4r-x2hj-jmvr), exploitable in two different ways, could let an attacker execute arbitrary javascript on other users:
|
||||||
|
* either: tricking someone into clicking a malicious URL to load and execute javascript
|
||||||
|
* or: uploading a malicious audio file to the server, affecting any successive visitors
|
||||||
|
|
||||||
|
so, with new and curious eyes on the project, we are starting off with a bang. Huge thanks to @altperfect for finding and reporting this earlier today.
|
||||||
|
|
||||||
|
## recent important news
|
||||||
|
|
||||||
|
* [v1.18.5 (2025-07-28)](https://github.com/9001/copyparty/releases/tag/v1.18.5) fixed XSS in display of media tags
|
||||||
|
* [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
|
||||||
|
|
||||||
|
* #214 option to stop playback after one song, and/or at end of folder 6bb27e60
|
||||||
|
|
||||||
|
## 🩹 bugfixes
|
||||||
|
|
||||||
|
* GHSA-9q4r-x2hj-jmvr 895880ae
|
||||||
|
* block external m3u files 2228f81f
|
||||||
|
* #202 the connect-page could show IP-address when it should have used hostnames/domains b0dec83a
|
||||||
|
* scrolling locked after tailing a file and closing it creatively d197e754
|
||||||
|
|
||||||
|
## 🔧 other changes
|
||||||
|
|
||||||
|
* #189 the `SameSite` cookie parameter now defaults to `Strict`, increasing CSRF protection ca6d0b8d
|
||||||
|
* new option `--cookie-lax` reverts to previous value `Lax`
|
||||||
|
* docker: add FTPS support b4199847
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2025-0725-1841 `v1.18.4` Landmarks
|
||||||
|
|
||||||
|
## 🧪 new features
|
||||||
|
|
||||||
|
* #182 [Landmarks](https://github.com/9001/copyparty#database-location) edba7fff
|
||||||
|
* detects that a storage backend is glitching out and disengage the up2k-database as a precaution
|
||||||
|
* #183 quickdelete 21a96bcf
|
||||||
|
* new togglebutton `qdel` in the UI which reduces the number of deletion confirmations by one
|
||||||
|
* global-option `--qdel=0` which can bring it all the way to zero (good luck)
|
||||||
|
|
||||||
|
## 🩹 bugfixes
|
||||||
|
|
||||||
|
* fix unpost in recently created shares 2d322dd4
|
||||||
|
* fix filekeys on windows df6d4df4
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
# 2025-0721-2307 `v1.18.3` drop the umask
|
# 2025-0721-2307 `v1.18.3` drop the umask
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
version: '3'
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
copyparty:
|
copyparty:
|
||||||
|
@ -11,9 +10,12 @@ services:
|
||||||
- ./:/cfg:z
|
- ./:/cfg:z
|
||||||
- /path/to/your/fileshare/top/folder:/w:z
|
- /path/to/your/fileshare/top/folder:/w:z
|
||||||
|
|
||||||
# enabling mimalloc by replacing "NOPE" with "2" will make some stuff twice as fast, but everything will use twice as much ram:
|
|
||||||
environment:
|
environment:
|
||||||
LD_PRELOAD: /usr/lib/libmimalloc-secure.so.NOPE
|
LD_PRELOAD: /usr/lib/libmimalloc-secure.so.NOPE
|
||||||
|
# enable mimalloc by replacing "NOPE" with "2" for a nice speed-boost (will use twice as much ram)
|
||||||
|
|
||||||
|
PYTHONUNBUFFERED: 1
|
||||||
|
# ensures log-messages are not delayed (but can reduce speed a tiny bit)
|
||||||
|
|
||||||
stop_grace_period: 15s # thumbnailer is allowed to continue finishing up for 10s after the shutdown signal
|
stop_grace_period: 15s # thumbnailer is allowed to continue finishing up for 10s after the shutdown signal
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
|
|
@ -27,6 +27,9 @@ services:
|
||||||
LD_PRELOAD: /usr/lib/libmimalloc-secure.so.NOPE
|
LD_PRELOAD: /usr/lib/libmimalloc-secure.so.NOPE
|
||||||
# enable mimalloc by replacing "NOPE" with "2" for a nice speed-boost (will use twice as much ram)
|
# enable mimalloc by replacing "NOPE" with "2" for a nice speed-boost (will use twice as much ram)
|
||||||
|
|
||||||
|
PYTHONUNBUFFERED: 1
|
||||||
|
# ensures log-messages are not delayed (but can reduce speed a tiny bit)
|
||||||
|
|
||||||
authelia:
|
authelia:
|
||||||
image: authelia/authelia:v4.38.0-beta3 # the config files in the authelia folder use the new syntax
|
image: authelia/authelia:v4.38.0-beta3 # the config files in the authelia folder use the new syntax
|
||||||
container_name: idp_authelia
|
container_name: idp_authelia
|
||||||
|
|
|
@ -27,6 +27,9 @@ services:
|
||||||
LD_PRELOAD: /usr/lib/libmimalloc-secure.so.NOPE
|
LD_PRELOAD: /usr/lib/libmimalloc-secure.so.NOPE
|
||||||
# enable mimalloc by replacing "NOPE" with "2" for a nice speed-boost (will use twice as much ram)
|
# enable mimalloc by replacing "NOPE" with "2" for a nice speed-boost (will use twice as much ram)
|
||||||
|
|
||||||
|
PYTHONUNBUFFERED: 1
|
||||||
|
# ensures log-messages are not delayed (but can reduce speed a tiny bit)
|
||||||
|
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik:v2.11
|
image: traefik:v2.11
|
||||||
container_name: traefik
|
container_name: traefik
|
||||||
|
|
53
flake.nix
53
flake.nix
|
@ -4,16 +4,30 @@
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils }:
|
outputs =
|
||||||
|
{
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
flake-utils,
|
||||||
|
}:
|
||||||
{
|
{
|
||||||
nixosModules.default = ./contrib/nixos/modules/copyparty.nix;
|
nixosModules.default = ./contrib/nixos/modules/copyparty.nix;
|
||||||
overlays.default = self: super: {
|
overlays.default = final: prev: rec {
|
||||||
copyparty =
|
copyparty = final.python3.pkgs.callPackage ./contrib/package/nix/copyparty {
|
||||||
self.python3.pkgs.callPackage ./contrib/package/nix/copyparty {
|
ffmpeg = final.ffmpeg-full;
|
||||||
ffmpeg = self.ffmpeg-full;
|
};
|
||||||
|
|
||||||
|
partyfuse = prev.callPackage ./contrib/package/nix/partyfuse {
|
||||||
|
inherit copyparty;
|
||||||
|
};
|
||||||
|
|
||||||
|
u2c = prev.callPackage ./contrib/package/nix/u2c {
|
||||||
|
inherit copyparty;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
} // flake-utils.lib.eachDefaultSystem (system:
|
}
|
||||||
|
// flake-utils.lib.eachDefaultSystem (
|
||||||
|
system:
|
||||||
let
|
let
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
|
@ -22,10 +36,31 @@
|
||||||
};
|
};
|
||||||
overlays = [ self.overlays.default ];
|
overlays = [ self.overlays.default ];
|
||||||
};
|
};
|
||||||
in {
|
in
|
||||||
|
{
|
||||||
|
# check that copyparty builds with all optionals turned on
|
||||||
|
checks.copyparty-full = self.packages.${system}.copyparty.override {
|
||||||
|
withHashedPasswords = true;
|
||||||
|
withCertgen = true;
|
||||||
|
withThumbnails = true;
|
||||||
|
withFastThumbnails = true;
|
||||||
|
withMediaProcessing = true;
|
||||||
|
withBasicAudioMetadata = true;
|
||||||
|
withZeroMQ = true;
|
||||||
|
withFTPS = true;
|
||||||
|
withSMB = true;
|
||||||
|
};
|
||||||
|
|
||||||
packages = {
|
packages = {
|
||||||
inherit (pkgs) copyparty;
|
inherit (pkgs)
|
||||||
|
copyparty
|
||||||
|
partyfuse
|
||||||
|
u2c
|
||||||
|
;
|
||||||
default = self.packages.${system}.copyparty;
|
default = self.packages.${system}.copyparty;
|
||||||
};
|
};
|
||||||
});
|
|
||||||
|
formatter = pkgs.nixfmt-tree;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -364,6 +364,7 @@ var tl_browser = {
|
||||||
"ml_drc": "dynamic range compressor",
|
"ml_drc": "dynamic range compressor",
|
||||||
|
|
||||||
"mt_loop": "loop/repeat one song\">🔁",
|
"mt_loop": "loop/repeat one song\">🔁",
|
||||||
|
"mt_one": "stop after one song\">1️⃣",
|
||||||
"mt_shuf": "shuffle the songs in each folder\">🔀",
|
"mt_shuf": "shuffle the songs in each folder\">🔀",
|
||||||
"mt_aplay": "autoplay if there is a song-ID in the link you clicked to access the server$N$Ndisabling this will also stop the page URL from being updated with song-IDs when playing music, to prevent autoplay if these settings are lost but the URL remains\">a▶",
|
"mt_aplay": "autoplay if there is a song-ID in the link you clicked to access the server$N$Ndisabling this will also stop the page URL from being updated with song-IDs when playing music, to prevent autoplay if these settings are lost but the URL remains\">a▶",
|
||||||
"mt_preload": "start loading the next song near the end for gapless playback\">preload",
|
"mt_preload": "start loading the next song near the end for gapless playback\">preload",
|
||||||
|
@ -381,6 +382,7 @@ var tl_browser = {
|
||||||
"mt_uncache": "clear cache (try this if your browser cached$Na broken copy of a song so it refuses to play)\">uncache",
|
"mt_uncache": "clear cache (try this if your browser cached$Na broken copy of a song so it refuses to play)\">uncache",
|
||||||
"mt_mloop": "loop the open folder\">🔁 loop",
|
"mt_mloop": "loop the open folder\">🔁 loop",
|
||||||
"mt_mnext": "load the next folder and continue\">📂 next",
|
"mt_mnext": "load the next folder and continue\">📂 next",
|
||||||
|
"mt_mstop": "stop playback\">⏸ stop",
|
||||||
"mt_cflac": "convert flac / wav to opus\">flac",
|
"mt_cflac": "convert flac / wav to opus\">flac",
|
||||||
"mt_caac": "convert aac / m4a to opus\">aac",
|
"mt_caac": "convert aac / m4a to opus\">aac",
|
||||||
"mt_coth": "convert all others (not mp3) to opus\">oth",
|
"mt_coth": "convert all others (not mp3) to opus\">oth",
|
||||||
|
|
|
@ -143,7 +143,7 @@ class Cfg(Namespace):
|
||||||
def __init__(self, a=None, v=None, c=None, **ka0):
|
def __init__(self, a=None, v=None, c=None, **ka0):
|
||||||
ka = {}
|
ka = {}
|
||||||
|
|
||||||
ex = "chpw 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 magic nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tail no_tarcmp no_thumb no_vthumb no_zip nrand nsort nw og og_no_head og_s_title ohead q rand re_dirsz rmagic rss smb srch_dbg srch_excl stats uqe vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs"
|
ex = "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 magic nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tail no_tarcmp no_thumb no_vthumb no_zip nrand nsort nw og og_no_head og_s_title ohead q rand re_dirsz reflink rmagic rss smb srch_dbg srch_excl stats uqe vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs"
|
||||||
ka.update(**{k: False for k in ex.split()})
|
ka.update(**{k: False for k in ex.split()})
|
||||||
|
|
||||||
ex = "dav_inf dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump re_dhash see_dots plain_ip"
|
ex = "dav_inf dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump re_dhash see_dots plain_ip"
|
||||||
|
@ -164,7 +164,7 @@ class Cfg(Namespace):
|
||||||
ex = "ah_alg bname chmod_f chpw_db doctitle df exit favico idp_h_usr ipa html_head lg_sba lg_sbf log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i shr tcolor textfiles unlist vname xff_src zipmaxt R RS SR"
|
ex = "ah_alg bname chmod_f chpw_db doctitle df exit favico idp_h_usr ipa html_head lg_sba lg_sbf log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i shr tcolor textfiles unlist vname xff_src zipmaxt R RS SR"
|
||||||
ka.update(**{k: "" for k in ex.split()})
|
ka.update(**{k: "" for k in ex.split()})
|
||||||
|
|
||||||
ex = "ban_403 ban_404 ban_422 ban_pw ban_url spinner"
|
ex = "ban_403 ban_404 ban_422 ban_pw ban_pwc ban_url spinner"
|
||||||
ka.update(**{k: "no" for k in ex.split()})
|
ka.update(**{k: "no" for k in ex.split()})
|
||||||
|
|
||||||
ex = "ext_th grp on403 on404 xac xad xar xau xban xbc xbd xbr xbu xiu xm"
|
ex = "ext_th grp on403 on404 xac xad xar xau xban xbc xbd xbr xbu xiu xm"
|
||||||
|
|
Loading…
Reference in a new issue