mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
nixos: revamp (#159)
* formatting clean-up with alejandra. * added ability to specify user and group. * added option to have hist data live with volumes. * improved my understanding of what paths copyparty needs to function. * added environment script. * Revert "added environment script." Cant have 2 instances of copyparty running, even if one is just for ah-cli... This reverts commitc60c8d8e0b
. * fixup! added ability to specify user and group. * Reapply "added environment script." This reverts commita54e950ecc
. * Moved back to TemporaryFileSystem for system hardening. I misunderstood bind mounts... * made systemd.tmpfiles rules to ensure the volume directories exist. * changed copyparty-env script to copyparty-hash. * removed seperatehist in favor of default settings attrset. * new update of copyparty removed the need for some options. * minor refactoring. * fixed some descriptions that had not kept up with changes. * fixup! removed seperatehist in favor of default settings attrset.
This commit is contained in:
parent
94352f278b
commit
d1bca1f52f
|
@ -1,27 +1,29 @@
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
with lib;
|
pkgs,
|
||||||
|
lib,
|
||||||
let
|
...
|
||||||
|
}:
|
||||||
|
with lib; let
|
||||||
mkKeyValue = key: value:
|
mkKeyValue = key: value:
|
||||||
if value == true then
|
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 then
|
else if value == false
|
||||||
|
then
|
||||||
# or omitted completely when false
|
# or omitted completely when false
|
||||||
""
|
""
|
||||||
else
|
else (generators.mkKeyValueDefault {inherit mkValueString;} ": " key value);
|
||||||
(generators.mkKeyValueDefault { inherit mkValueString; } ": " key value);
|
|
||||||
|
|
||||||
mkAttrsString = value: (generators.toKeyValue {inherit mkKeyValue;} value);
|
mkAttrsString = value: (generators.toKeyValue {inherit mkKeyValue;} value);
|
||||||
|
|
||||||
mkValueString = value:
|
mkValueString = value:
|
||||||
if isList value then
|
if isList value
|
||||||
(concatStringsSep ", " (map mkValueString value))
|
then (concatStringsSep ", " (map mkValueString value))
|
||||||
else if isAttrs value then
|
else if isAttrs value
|
||||||
"\n" + (mkAttrsString value)
|
then "\n" + (mkAttrsString value)
|
||||||
else
|
else (generators.mkValueStringDefault {} value);
|
||||||
(generators.mkValueStringDefault { } value);
|
|
||||||
|
|
||||||
mkSectionName = value: "[" + (escape ["[" "]"] value) + "]";
|
mkSectionName = value: "[" + (escape ["[" "]"] value) + "]";
|
||||||
|
|
||||||
|
@ -49,12 +51,12 @@ let
|
||||||
${concatStringsSep "\n" (mapAttrsToList mkVolume cfg.volumes)}
|
${concatStringsSep "\n" (mapAttrsToList mkVolume cfg.volumes)}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
name = "copyparty";
|
|
||||||
cfg = config.services.copyparty;
|
cfg = config.services.copyparty;
|
||||||
configFile = pkgs.writeText "${name}.conf" configStr;
|
configFile = pkgs.writeText "copyparty.conf" configStr;
|
||||||
runtimeConfigPath = "/run/${name}/${name}.conf";
|
runtimeConfigPath = "/run/copyparty/copyparty.conf";
|
||||||
home = "/var/lib/${name}";
|
externalCacheDir = "/var/cache/copyparty";
|
||||||
defaultShareDir = "${home}/data";
|
externalStateDir = "/var/lib/copyparty";
|
||||||
|
defaultShareDir = "${externalStateDir}/data";
|
||||||
in {
|
in {
|
||||||
options.services.copyparty = {
|
options.services.copyparty = {
|
||||||
enable = mkEnableOption "web-based file manager";
|
enable = mkEnableOption "web-based file manager";
|
||||||
|
@ -68,6 +70,35 @@ in {
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mkHashWrapper = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Make a shell script wrapper called 'copyparty-hash' with all options set here,
|
||||||
|
that launches the hashing cli.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
user = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "copyparty";
|
||||||
|
description = ''
|
||||||
|
The user that copyparty will run under.
|
||||||
|
|
||||||
|
If changed from default, you are responsible for making sure the user exists.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
group = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "copyparty";
|
||||||
|
description = ''
|
||||||
|
The group that copyparty will run under.
|
||||||
|
|
||||||
|
If changed from default, you are responsible for making sure the user exists.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
openFilesLimit = mkOption {
|
openFilesLimit = mkOption {
|
||||||
default = 4096;
|
default = 4096;
|
||||||
type = types.either types.int types.str;
|
type = types.either types.int types.str;
|
||||||
|
@ -79,16 +110,19 @@ in {
|
||||||
description = ''
|
description = ''
|
||||||
Global settings to apply.
|
Global settings to apply.
|
||||||
Directly maps to values in the [global] section of the copyparty config.
|
Directly maps to values in the [global] section of the copyparty config.
|
||||||
|
Cannot set "c" or "hist", those are set by this module.
|
||||||
See `${getExe cfg.package} --help` for more details.
|
See `${getExe cfg.package} --help` for more details.
|
||||||
'';
|
'';
|
||||||
default = {
|
default = {
|
||||||
i = "127.0.0.1";
|
i = "127.0.0.1";
|
||||||
no-reload = true;
|
no-reload = true;
|
||||||
|
hist = externalCacheDir;
|
||||||
};
|
};
|
||||||
example = literalExpression ''
|
example = literalExpression ''
|
||||||
{
|
{
|
||||||
i = "0.0.0.0";
|
i = "0.0.0.0";
|
||||||
no-reload = true;
|
no-reload = true;
|
||||||
|
hist = ${externalCacheDir};
|
||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
@ -121,7 +155,7 @@ in {
|
||||||
type = types.attrsOf (types.submodule ({...}: {
|
type = types.attrsOf (types.submodule ({...}: {
|
||||||
options = {
|
options = {
|
||||||
path = mkOption {
|
path = mkOption {
|
||||||
type = types.str;
|
type = types.path;
|
||||||
description = ''
|
description = ''
|
||||||
Path of a directory to share.
|
Path of a directory to share.
|
||||||
'';
|
'';
|
||||||
|
@ -204,19 +238,20 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable (let
|
||||||
|
command = "${getExe cfg.package} -c ${runtimeConfigPath}";
|
||||||
|
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 = "${home}/.config";
|
XDG_CONFIG_HOME = externalStateDir;
|
||||||
};
|
};
|
||||||
|
|
||||||
preStart = let
|
preStart = let
|
||||||
replaceSecretCommand = name: attrs:
|
replaceSecretCommand = name: attrs: "${getExe pkgs.replace-secret} '${
|
||||||
"${getExe pkgs.replace-secret} '${
|
|
||||||
passwordPlaceholder name
|
passwordPlaceholder name
|
||||||
}' '${attrs.passwordFile}' ${runtimeConfigPath}";
|
}' '${attrs.passwordFile}' ${runtimeConfigPath}";
|
||||||
in ''
|
in ''
|
||||||
|
@ -228,28 +263,40 @@ in {
|
||||||
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "simple";
|
Type = "simple";
|
||||||
ExecStart = "${getExe cfg.package} -c ${runtimeConfigPath}";
|
ExecStart = command;
|
||||||
|
|
||||||
# Hardening options
|
# Hardening options
|
||||||
User = "copyparty";
|
User = cfg.user;
|
||||||
Group = "copyparty";
|
Group = cfg.group;
|
||||||
RuntimeDirectory = name;
|
RuntimeDirectory = ["copyparty"];
|
||||||
RuntimeDirectoryMode = "0700";
|
RuntimeDirectoryMode = "0700";
|
||||||
StateDirectory = [ name "${name}/data" "${name}/.config" ];
|
StateDirectory = ["copyparty"];
|
||||||
StateDirectoryMode = "0700";
|
StateDirectoryMode = "0700";
|
||||||
WorkingDirectory = home;
|
CacheDirectory = lib.mkIf (cfg.settings ? hist) ["copyparty"];
|
||||||
TemporaryFileSystem = "/:ro";
|
CacheDirectoryMode = lib.mkIf (cfg.settings ? hist) "0700";
|
||||||
BindReadOnlyPaths = [
|
WorkingDirectory = externalStateDir;
|
||||||
|
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);
|
]
|
||||||
BindPaths = [ home ] ++ (mapAttrsToList (k: v: v.path) cfg.volumes);
|
++ (mapAttrsToList (k: v: "-${v.passwordFile}") cfg.accounts);
|
||||||
# Would re-mount paths ignored by temporary root
|
BindPaths =
|
||||||
|
(
|
||||||
|
if cfg.settings ? hist
|
||||||
|
then [cfg.settings.hist]
|
||||||
|
else []
|
||||||
|
)
|
||||||
|
++ [externalStateDir]
|
||||||
|
++ (mapAttrsToList (k: v: v.path) cfg.volumes);
|
||||||
# ProtectSystem = "strict";
|
# ProtectSystem = "strict";
|
||||||
ProtectHome = true;
|
# Note that unlike what 'ro' implies,
|
||||||
|
# this actually makes it impossible to read anything in the root FS,
|
||||||
|
# except for things explicitly mounted via `RuntimeDirectory`, `StateDirectory`, `CacheDirectory`, and `BindReadOnlyPaths`.
|
||||||
|
# This is because TemporaryFileSystem creates a *new* *empty* filesystem for the process, so only bindmounts are visible.
|
||||||
|
TemporaryFileSystem = "/:ro";
|
||||||
PrivateTmp = true;
|
PrivateTmp = true;
|
||||||
PrivateDevices = true;
|
PrivateDevices = true;
|
||||||
ProtectKernelTunables = true;
|
ProtectKernelTunables = true;
|
||||||
|
@ -269,15 +316,48 @@ in {
|
||||||
NoNewPrivileges = true;
|
NoNewPrivileges = true;
|
||||||
LockPersonality = true;
|
LockPersonality = true;
|
||||||
RestrictRealtime = true;
|
RestrictRealtime = true;
|
||||||
|
MemoryDenyWriteExecute = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
users.groups.copyparty = { };
|
# ensure volumes exist:
|
||||||
users.users.copyparty = {
|
systemd.tmpfiles.settings."copyparty" = (
|
||||||
description = "Service user for copyparty";
|
lib.attrsets.mapAttrs' (
|
||||||
group = "copyparty";
|
name: value:
|
||||||
home = home;
|
lib.attrsets.nameValuePair (value.path) {
|
||||||
isSystemUser = true;
|
d = {
|
||||||
};
|
#: in front of things means it wont change it if the directory already exists.
|
||||||
|
group = ":${cfg.group}";
|
||||||
|
user = ":${cfg.user}";
|
||||||
|
mode = ":755";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
cfg.volumes
|
||||||
|
);
|
||||||
|
|
||||||
|
users.groups.copyparty = lib.mkIf (cfg.user == "copyparty" && cfg.group == "copyparty") {};
|
||||||
|
users.users.copyparty = lib.mkIf (cfg.user == "copyparty" && cfg.group == "copyparty") {
|
||||||
|
description = "Service user for copyparty";
|
||||||
|
group = "copyparty";
|
||||||
|
home = externalStateDir;
|
||||||
|
isSystemUser = true;
|
||||||
|
};
|
||||||
|
environment.systemPackages = lib.mkIf cfg.mkHashWrapper [
|
||||||
|
(pkgs.writeShellScriptBin
|
||||||
|
"copyparty-hash"
|
||||||
|
''
|
||||||
|
set -a # automatically export variables
|
||||||
|
# set same environment variables as the systemd service
|
||||||
|
${lib.pipe config.systemd.services.copyparty.environment [
|
||||||
|
(lib.filterAttrs (n: v: v != null && n != "PATH"))
|
||||||
|
(lib.mapAttrs (_: v: "${v}"))
|
||||||
|
(lib.toShellVars)
|
||||||
|
]}
|
||||||
|
PATH=${config.systemd.services.copyparty.environment.PATH}:$PATH
|
||||||
|
|
||||||
|
exec ${command} --ah-cli
|
||||||
|
'')
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue