mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
rewrite the nix module config with nix options
This commit is contained in:
parent
6e615dcd03
commit
397bc92fbc
72
README.md
72
README.md
|
@ -1193,32 +1193,62 @@ for this setup, you will need a [flake-enabled](https://nixos.wiki/wiki/Flakes)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
copyparty on NixOS is configured via `services.copyparty.config`, for example:
|
copyparty on NixOS is configured via `services.copyparty` options, for example:
|
||||||
```nix
|
```nix
|
||||||
services.copyparty = {
|
services.copyparty = {
|
||||||
enable = true;
|
enable = true;
|
||||||
config = ''
|
# directly maps to values in the [global] section of the copyparty config.
|
||||||
[global]
|
# see `copyparty --help` for available options
|
||||||
i: 0.0.0.0
|
settings = {
|
||||||
no-reload
|
i = "0.0.0.0";
|
||||||
|
# use lists to set multiple values
|
||||||
|
p = [ 3210 3211 ];
|
||||||
|
# use booleans to set binary flags
|
||||||
|
no-reload = true;
|
||||||
|
# using 'false' will do nothing and omit the value when generating a config
|
||||||
|
ignored-flag = false;
|
||||||
|
};
|
||||||
|
|
||||||
# create users
|
# create users
|
||||||
[accounts]
|
accounts = {
|
||||||
# username: password
|
# specify the account name as the key
|
||||||
ed: 123
|
ed = {
|
||||||
|
# provide the path to a file containing the password, keeping it out of /nix/store
|
||||||
|
# must be readable by the copyparty service user
|
||||||
|
passwordFile = "/run/keys/copyparty/ed_password";
|
||||||
|
};
|
||||||
|
# or do both in one go
|
||||||
|
k.passwordFile = "/run/keys/copyparty/k_password";
|
||||||
|
};
|
||||||
|
|
||||||
# create a volume
|
# create a volume
|
||||||
[/] # create a volume at "/" (the webroot), which will
|
volumes = {
|
||||||
/srv/copyparty # share the contents of "/srv/copyparty"
|
# create a volume at "/" (the webroot), which will
|
||||||
accs:
|
"/" = {
|
||||||
r: * # everyone gets read-access, but
|
# share the contents of "/srv/copyparty"
|
||||||
rw: ed # the user "ed" gets read-write
|
path = "/srv/copyparty";
|
||||||
'';
|
# see `copyparty --help-accounts` for available options
|
||||||
# the service runs in an isolated environment by default
|
access = {
|
||||||
# any directory you reference in the volume configuration
|
# everyone gets read-access, but
|
||||||
# needs to be added here, in order to make it discoverable
|
r = "*";
|
||||||
# with the exception of /var/lib/copyparty, which is always available
|
# users "ed" and "k" get read-write
|
||||||
readWritePaths = [ "/srv/copyparty" ];
|
rw = [ "ed" "k" ];
|
||||||
|
};
|
||||||
|
# see `copyparty --help-flags` for available options
|
||||||
|
flags = {
|
||||||
|
# "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$";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
# you may increase the open file limit for the process
|
# you may increase the open file limit for the process
|
||||||
openFilesLimit = 8192;
|
openFilesLimit = 8192;
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,11 +3,57 @@
|
||||||
with lib;
|
with lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
|
mkKeyValue = key: value:
|
||||||
|
if value == true then
|
||||||
|
# sets with a true boolean value are coerced to just the key name
|
||||||
|
key
|
||||||
|
else if value == false then
|
||||||
|
# or omitted completely when false
|
||||||
|
""
|
||||||
|
else
|
||||||
|
(generators.mkKeyValueDefault { inherit mkValueString; } ": " key value);
|
||||||
|
|
||||||
|
mkAttrsString = value: (generators.toKeyValue { inherit mkKeyValue; } value);
|
||||||
|
|
||||||
|
mkValueString = value:
|
||||||
|
if isList value then
|
||||||
|
(concatStringsSep ", " (map mkValueString value))
|
||||||
|
else if isAttrs value then
|
||||||
|
"\n" + (mkAttrsString value)
|
||||||
|
else
|
||||||
|
(generators.mkValueStringDefault { } value);
|
||||||
|
|
||||||
|
mkSectionName = value: "[" + (escape [ "[" "]" ] value) + "]";
|
||||||
|
|
||||||
|
mkSection = name: attrs: ''
|
||||||
|
${mkSectionName name}
|
||||||
|
${mkAttrsString attrs}
|
||||||
|
'';
|
||||||
|
|
||||||
|
mkVolume = name: attrs: ''
|
||||||
|
${mkSectionName name}
|
||||||
|
${attrs.path}
|
||||||
|
${mkAttrsString {
|
||||||
|
accs = attrs.access;
|
||||||
|
flags = attrs.flags;
|
||||||
|
}}
|
||||||
|
'';
|
||||||
|
|
||||||
|
passwordPlaceholder = name: "{{password-${name}}}";
|
||||||
|
|
||||||
|
accountsWithPlaceholders = mapAttrs (name: attrs: passwordPlaceholder name);
|
||||||
|
|
||||||
|
configStr = ''
|
||||||
|
${mkSection "global" cfg.settings}
|
||||||
|
${mkSection "accounts" (accountsWithPlaceholders cfg.accounts)}
|
||||||
|
${concatStringsSep "\n" (mapAttrsToList mkVolume cfg.volumes)}
|
||||||
|
'';
|
||||||
|
|
||||||
name = "copyparty";
|
name = "copyparty";
|
||||||
cfg = config.services.copyparty;
|
cfg = config.services.copyparty;
|
||||||
configFile = pkgs.writeText "copyparty.conf" cfg.config;
|
configFile = pkgs.writeText "${name}.conf" configStr;
|
||||||
bin = "${cfg.package}/bin/${name}";
|
runtimeConfigPath = "/run/${name}/${name}.conf";
|
||||||
home = "/var/lib/copyparty";
|
home = "/var/lib/${name}";
|
||||||
defaultShareDir = "${home}/data";
|
defaultShareDir = "${home}/data";
|
||||||
in {
|
in {
|
||||||
options.services.copyparty = {
|
options.services.copyparty = {
|
||||||
|
@ -22,49 +68,136 @@ in {
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
readWritePaths = mkOption {
|
|
||||||
default = [ ];
|
|
||||||
type = types.listOf types.str;
|
|
||||||
description = "Paths permitted for read/write.";
|
|
||||||
};
|
|
||||||
|
|
||||||
openFilesLimit = mkOption {
|
openFilesLimit = mkOption {
|
||||||
default = 4096;
|
default = 4096;
|
||||||
type = types.either types.int types.str;
|
type = types.either types.int types.str;
|
||||||
description = "Number of files to allow copyparty to open.";
|
description = "Number of files to allow copyparty to open.";
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkOption {
|
settings = mkOption {
|
||||||
type = types.lines;
|
type = types.attrs;
|
||||||
description =
|
description = ''
|
||||||
"Configuration file. See https://github.com/9001/copyparty#server-config for reference";
|
Global settings to apply.
|
||||||
default = ''
|
Directly maps to values in the [global] section of the copyparty config.
|
||||||
[global]
|
See `${getExe cfg.package} --help` for more details.
|
||||||
i: 127.0.0.1
|
|
||||||
no-reload
|
|
||||||
|
|
||||||
# create a volume:
|
|
||||||
[/] # create a volume at "/" (the webroot), which will
|
|
||||||
${defaultShareDir} # share the contents of "${defaultShareDir}"
|
|
||||||
accs:
|
|
||||||
r: * # everyone gets read-access, but
|
|
||||||
'';
|
'';
|
||||||
example = ''
|
default = {
|
||||||
[global]
|
i = "127.0.0.1";
|
||||||
i: 0.0.0.0
|
no-reload = true;
|
||||||
no-reload
|
};
|
||||||
|
example = literalExpression ''
|
||||||
|
{
|
||||||
|
i = "0.0.0.0";
|
||||||
|
no-reload = true;
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
# create users:
|
accounts = mkOption {
|
||||||
[accounts]
|
type = types.attrsOf (types.submodule ({ ... }: {
|
||||||
# username: password
|
options = {
|
||||||
ed: 123
|
passwordFile = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = ''
|
||||||
|
Runtime file path to a file containing the user password.
|
||||||
|
Must be readable by the copyparty user.
|
||||||
|
'';
|
||||||
|
example = "/run/keys/copyparty/ed";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
description = ''
|
||||||
|
A set of copyparty accounts to create.
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
example = literalExpression ''
|
||||||
|
{
|
||||||
|
ed.passwordFile = "/run/keys/copyparty/ed";
|
||||||
|
};
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
# create a volume:
|
volumes = mkOption {
|
||||||
[/] # create a volume at "/" (the webroot), which will
|
type = types.attrsOf (types.submodule ({ ... }: {
|
||||||
${defaultShareDir} # share the contents of "${defaultShareDir}"
|
options = {
|
||||||
accs:
|
path = mkOption {
|
||||||
r: * # everyone gets read-access, but
|
type = types.str;
|
||||||
rw: ed # the user "ed" gets read-write
|
description = ''
|
||||||
|
Path of a directory to share.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
access = mkOption {
|
||||||
|
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
|
||||||
|
|
||||||
|
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"];
|
||||||
|
};
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
flags = mkOption {
|
||||||
|
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";
|
||||||
|
default = {
|
||||||
|
"/" = {
|
||||||
|
path = defaultShareDir;
|
||||||
|
access = { r = "*"; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
example = literalExpression ''
|
||||||
|
{
|
||||||
|
"/" = {
|
||||||
|
path = ${defaultShareDir};
|
||||||
|
access = {
|
||||||
|
# wG = write-upget = see your own uploads only
|
||||||
|
wG = "*";
|
||||||
|
# read-write-modify-delete for users "ed" and "k"
|
||||||
|
rwmd = ["ed" "k"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -79,20 +212,29 @@ in {
|
||||||
XDG_CONFIG_HOME = "${home}/.config";
|
XDG_CONFIG_HOME = "${home}/.config";
|
||||||
};
|
};
|
||||||
|
|
||||||
preStart = ''
|
preStart = let
|
||||||
mkdir -p "$XDG_CONFIG_HOME"
|
replaceSecretCommand = name: attrs:
|
||||||
mkdir -p "${defaultShareDir}"
|
"${getExe pkgs.replace-secret} '${
|
||||||
|
passwordPlaceholder name
|
||||||
|
}' '${attrs.passwordFile}' ${runtimeConfigPath}";
|
||||||
|
in ''
|
||||||
|
set -euo pipefail
|
||||||
|
install -m 600 ${configFile} ${runtimeConfigPath}
|
||||||
|
${concatStringsSep "\n"
|
||||||
|
(mapAttrsToList replaceSecretCommand cfg.accounts)}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "simple";
|
Type = "simple";
|
||||||
ExecStart = "${bin} -c ${configFile}";
|
ExecStart = "${getExe cfg.package} -c ${runtimeConfigPath}";
|
||||||
|
|
||||||
# Hardening options
|
# Hardening options
|
||||||
User = "copyparty";
|
User = "copyparty";
|
||||||
Group = "copyparty";
|
Group = "copyparty";
|
||||||
StateDirectory = "copyparty";
|
RuntimeDirectory = name;
|
||||||
StateDirectoryMode = "0755";
|
RuntimeDirectoryMode = "0700";
|
||||||
|
StateDirectory = [ name "${name}/data" "${name}/.config" ];
|
||||||
|
StateDirectoryMode = "0700";
|
||||||
WorkingDirectory = home;
|
WorkingDirectory = home;
|
||||||
TemporaryFileSystem = "/:ro";
|
TemporaryFileSystem = "/:ro";
|
||||||
BindReadOnlyPaths = [
|
BindReadOnlyPaths = [
|
||||||
|
@ -101,8 +243,8 @@ in {
|
||||||
"-/etc/nsswitch.conf"
|
"-/etc/nsswitch.conf"
|
||||||
"-/etc/hosts"
|
"-/etc/hosts"
|
||||||
"-/etc/localtime"
|
"-/etc/localtime"
|
||||||
];
|
] ++ (mapAttrsToList (k: v: "-${v.passwordFile}") cfg.accounts);
|
||||||
BindPaths = [ home ] ++ cfg.readWritePaths;
|
BindPaths = [ home ] ++ (mapAttrsToList (k: v: v.path) cfg.volumes);
|
||||||
# Would re-mount paths ignored by temporary root
|
# Would re-mount paths ignored by temporary root
|
||||||
#ProtectSystem = "strict";
|
#ProtectSystem = "strict";
|
||||||
ProtectHome = true;
|
ProtectHome = true;
|
||||||
|
@ -125,7 +267,6 @@ in {
|
||||||
NoNewPrivileges = true;
|
NoNewPrivileges = true;
|
||||||
LockPersonality = true;
|
LockPersonality = true;
|
||||||
RestrictRealtime = true;
|
RestrictRealtime = true;
|
||||||
RestrictAddressFamilies = "AF_INET AF_INET6";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue