nix: format all nix files with nixfmt

This commit is contained in:
Tom van Dijk 2025-07-28 12:42:47 +02:00
parent e278316615
commit 4895579c72
No known key found for this signature in database
GPG key ID: 7A984C8207ADBA51
3 changed files with 274 additions and 234 deletions

View file

@ -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,22 +132,27 @@ in {
}; };
accounts = mkOption { accounts = mkOption {
type = types.attrsOf (types.submodule ({...}: { type = types.attrsOf (
options = { types.submodule (
passwordFile = mkOption { { ... }:
type = types.str; {
description = '' options = {
Runtime file path to a file containing the user password. passwordFile = mkOption {
Must be readable by the copyparty user. type = types.str;
''; description = ''
example = "/run/keys/copyparty/ed"; Runtime file path to a file containing the user password.
}; Must be readable by the copyparty user.
}; '';
})); 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,74 +161,81 @@ in {
}; };
volumes = mkOption { volumes = mkOption {
type = types.attrsOf (types.submodule ({...}: { type = types.attrsOf (
options = { types.submodule (
path = mkOption { { ... }:
type = types.path; {
description = '' options = {
Path of a directory to share. path = mkOption {
''; type = types.path;
}; description = ''
access = mkOption { Path of a directory to share.
type = types.attrs; '';
description = ''
Attribute list of permissions and the users to apply them to.
The key must be a string containing any combination of allowed permission:
"r" (read): list folder contents, download files
"w" (write): upload files; need "r" to see the uploads
"m" (move): move files and folders; need "w" at destination
"d" (delete): permanently delete files and folders
"g" (get): download files, but cannot see folder contents
"G" (upget): "get", but can see filekeys of their own uploads
"h" (html): "get", but folders return their index.html
"a" (admin): can see uploader IPs, config-reload
For example: "rwmd"
The value must be one of:
an account name, defined in `accounts`
a list of account names
"*", which means "any account"
'';
example = literalExpression ''
{
# wG = write-upget = see your own uploads only
wG = "*";
# read-write-modify-delete for users "ed" and "k"
rwmd = ["ed" "k"];
}; };
''; access = mkOption {
}; type = types.attrs;
flags = mkOption { description = ''
type = types.attrs; Attribute list of permissions and the users to apply them to.
description = ''
Attribute list of volume flags to apply. The key must be a string containing any combination of allowed permission:
See `${getExe cfg.package} --help-flags` for more details. "r" (read): list folder contents, download files
''; "w" (write): upload files; need "r" to see the uploads
example = literalExpression '' "m" (move): move files and folders; need "w" at destination
{ "d" (delete): permanently delete files and folders
# "fk" enables filekeys (necessary for upget permission) (4 chars long) "g" (get): download files, but cannot see folder contents
fk = 4; "G" (upget): "get", but can see filekeys of their own uploads
# scan for new files every 60sec "h" (html): "get", but folders return their index.html
scan = 60; "a" (admin): can see uploader IPs, config-reload
# volflag "e2d" enables the uploads database
e2d = true; For example: "rwmd"
# "d2t" disables multimedia parsers (in case the uploads are malicious)
d2t = true; The value must be one of:
# skips hashing file contents if path matches *.iso an account name, defined in `accounts`
nohash = "\.iso$"; a list of account names
"*", which means "any account"
'';
example = literalExpression ''
{
# wG = write-upget = see your own uploads only
wG = "*";
# read-write-modify-delete for users "ed" and "k"
rwmd = ["ed" "k"];
};
'';
}; };
''; flags = mkOption {
default = {}; type = types.attrs;
}; description = ''
}; Attribute list of volume flags to apply.
})); See `${getExe cfg.package} --help-flags` for more details.
'';
example = literalExpression ''
{
# "fk" enables filekeys (necessary for upget permission) (4 chars long)
fk = 4;
# scan for new files every 60sec
scan = 60;
# volflag "e2d" enables the uploads database
e2d = true;
# "d2t" disables multimedia parsers (in case the uploads are malicious)
d2t = true;
# skips hashing file contents if path matches *.iso
nohash = "\.iso$";
};
'';
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,92 +254,89 @@ in {
}; };
}; };
config = mkIf cfg.enable (let config = mkIf cfg.enable (
command = "${getExe cfg.package} -c ${runtimeConfigPath}"; let
in { command = "${getExe cfg.package} -c ${runtimeConfigPath}";
systemd.services.copyparty = { in
description = "http file sharing hub"; {
wantedBy = ["multi-user.target"]; systemd.services.copyparty = {
description = "http file sharing hub";
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}";
set -euo pipefail in
install -m 600 ${configFile} ${runtimeConfigPath} ''
${concatStringsSep "\n" set -euo pipefail
(mapAttrsToList replaceSecretCommand cfg.accounts)} install -m 600 ${configFile} ${runtimeConfigPath}
''; ${concatStringsSep "\n" (mapAttrsToList replaceSecretCommand cfg.accounts)}
'';
serviceConfig = { serviceConfig = {
Type = "simple"; Type = "simple";
ExecStart = command; ExecStart = command;
# 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/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 [ ])
( ++ [ externalStateDir ]
if cfg.settings ? hist ++ (mapAttrsToList (k: v: v.path) cfg.volumes);
then [cfg.settings.hist] # ProtectSystem = "strict";
else [] # Note that unlike what 'ro' implies,
) # this actually makes it impossible to read anything in the root FS,
++ [externalStateDir] # except for things explicitly mounted via `RuntimeDirectory`, `StateDirectory`, `CacheDirectory`, and `BindReadOnlyPaths`.
++ (mapAttrsToList (k: v: v.path) cfg.volumes); # This is because TemporaryFileSystem creates a *new* *empty* filesystem for the process, so only bindmounts are visible.
# ProtectSystem = "strict"; TemporaryFileSystem = "/:ro";
# Note that unlike what 'ro' implies, PrivateTmp = true;
# this actually makes it impossible to read anything in the root FS, PrivateDevices = true;
# except for things explicitly mounted via `RuntimeDirectory`, `StateDirectory`, `CacheDirectory`, and `BindReadOnlyPaths`. ProtectKernelTunables = true;
# This is because TemporaryFileSystem creates a *new* *empty* filesystem for the process, so only bindmounts are visible. ProtectControlGroups = true;
TemporaryFileSystem = "/:ro"; RestrictSUIDSGID = true;
PrivateTmp = true; PrivateMounts = true;
PrivateDevices = true; ProtectKernelModules = true;
ProtectKernelTunables = true; ProtectKernelLogs = true;
ProtectControlGroups = true; ProtectHostname = true;
RestrictSUIDSGID = true; ProtectClock = true;
PrivateMounts = true; ProtectProc = "invisible";
ProtectKernelModules = true; ProcSubset = "pid";
ProtectKernelLogs = true; RestrictNamespaces = true;
ProtectHostname = true; RemoveIPC = true;
ProtectClock = true; UMask = "0077";
ProtectProc = "invisible"; LimitNOFILE = cfg.openFilesLimit;
ProcSubset = "pid"; NoNewPrivileges = true;
RestrictNamespaces = true; LockPersonality = true;
RemoveIPC = true; RestrictRealtime = true;
UMask = "0077"; MemoryDenyWriteExecute = true;
LimitNOFILE = cfg.openFilesLimit; };
NoNewPrivileges = true;
LockPersonality = true;
RestrictRealtime = true;
MemoryDenyWriteExecute = true;
}; };
};
# ensure volumes exist: # ensure volumes exist:
systemd.tmpfiles.settings."copyparty" = ( systemd.tmpfiles.settings."copyparty" = (
lib.attrsets.mapAttrs' ( lib.attrsets.mapAttrs' (
name: value: name: value:
lib.attrsets.nameValuePair (value.path) { lib.attrsets.nameValuePair (value.path) {
d = { d = {
#: in front of things means it wont change it if the directory already exists. #: in front of things means it wont change it if the directory already exists.
@ -332,32 +345,30 @@ 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";
home = externalStateDir; home = externalStateDir;
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 same environment variables as the systemd service
set -a # automatically export variables ${lib.pipe config.systemd.services.copyparty.environment [
# set same environment variables as the systemd service (lib.filterAttrs (n: v: v != null && n != "PATH"))
${lib.pipe config.systemd.services.copyparty.environment [ (lib.mapAttrs (_: v: "${v}"))
(lib.filterAttrs (n: v: v != null && n != "PATH")) (lib.toShellVars)
(lib.mapAttrs (_: v: "${v}")) ]}
(lib.toShellVars) PATH=${config.systemd.services.copyparty.environment.PATH}:$PATH
]}
PATH=${config.systemd.services.copyparty.environment.PATH}:$PATH
exec ${command} --ah-cli exec ${command} --ah-cli
'') '')
]; ];
}); }
);
} }

View file

@ -1,49 +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,
pyftpdlib,
pyopenssl,
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 # extra packages to add to the PATH
extraPackages ? [ ], extraPackages ? [ ],
# function that accepts a python packageset and returns a list of packages to # 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 # be added to the python venv. useful for scripts and such that require
# additional dependencies # additional dependencies
extraPythonPackages ? (_p: [ ]), 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
@ -56,8 +74,9 @@ let
++ lib.optional withHashedPasswords argon2-cffi ++ lib.optional withHashedPasswords argon2-cffi
++ lib.optional withZeroMQ pyzmq ++ lib.optional withZeroMQ pyzmq
++ (extraPythonPackages ps) ++ (extraPythonPackages ps)
); );
in stdenv.mkDerivation { in
stdenv.mkDerivation {
pname = "copyparty"; pname = "copyparty";
version = pinData.version; version = pinData.version;
src = fetchurl { src = fetchurl {
@ -70,7 +89,9 @@ in stdenv.mkDerivation {
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 ] ++ extraPackages ++ lib.optional withMediaProcessing ffmpeg)}:$PATH' \ --set PATH '${
lib.makeBinPath ([ util-linux ] ++ extraPackages ++ lib.optional withMediaProcessing ffmpeg)
}:$PATH' \
--add-flags "$out/share/copyparty-sfx.py" --add-flags "$out/share/copyparty-sfx.py"
''; '';
meta.mainProgram = "copyparty"; meta.mainProgram = "copyparty";

View file

@ -4,16 +4,22 @@
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 = self: super: {
copyparty = copyparty = self.python3.pkgs.callPackage ./contrib/package/nix/copyparty {
self.python3.pkgs.callPackage ./contrib/package/nix/copyparty { ffmpeg = self.ffmpeg-full;
ffmpeg = self.ffmpeg-full; };
};
}; };
} // flake-utils.lib.eachDefaultSystem (system: }
// flake-utils.lib.eachDefaultSystem (
system:
let let
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system; inherit system;
@ -22,10 +28,12 @@
}; };
overlays = [ self.overlays.default ]; overlays = [ self.overlays.default ];
}; };
in { in
{
packages = { packages = {
inherit (pkgs) copyparty; inherit (pkgs) copyparty;
default = self.packages.${system}.copyparty; default = self.packages.${system}.copyparty;
}; };
}); }
);
} }