feat(copyparty): add read-only root volume

This commit is contained in:
2026-09-14 22:50:45 -04:00
parent 6159d87d10
commit a73b59aa0a
4 changed files with 24 additions and 2 deletions

View File

@@ -46,6 +46,10 @@
- `sops.secrets.<name>.owner` must name a user known on the decrypting host. - `sops.secrets.<name>.owner` must name a user known on the decrypting host.
Do not set the owner to a guest-only service user unless the host also declares that user. Do not set the owner to a guest-only service user unless the host also declares that user.
Use a host-visible owner plus a shared group mode when the guest service can read the decrypted secret file through its group. Use a host-visible owner plus a shared group mode when the guest service can read the decrypted secret file through its group.
- A read-only guest bind mount cannot contain nested writable bind mounts unless their mount-point
directories already exist inside the read-only source.
For aggregate Copyparty volumes such as `/srv` with nested `/srv/inbox`, keep the guest bind mount
writable and enforce read-only behavior through Copyparty access plus service `ReadWritePaths`.
- Pikachu deploys from the monorepo checkout copied to `~/dev`. - Pikachu deploys from the monorepo checkout copied to `~/dev`.
Do not use the old `~/wrk/dotfiles` path for remote rebuild commands. Do not use the old `~/wrk/dotfiles` path for remote rebuild commands.
- Do not hand-author `hardware-configuration.nix` for a host that has not been generated on that machine. - Do not hand-author `hardware-configuration.nix` for a host that has not been generated on that machine.

View File

@@ -24,6 +24,12 @@ let
description = "Copyparty access map from permission string to account names."; description = "Copyparty access map from permission string to account names.";
}; };
readOnly = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Serve this Copyparty volume read-only.";
};
flags = lib.mkOption { flags = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
default = [ ]; default = [ ];
@@ -94,7 +100,7 @@ in
inherit (cfg.reverseProxy) trustedSources; inherit (cfg.reverseProxy) trustedSources;
}; };
volumes = lib.mapAttrs (_urlPath: volume: { volumes = lib.mapAttrs (_urlPath: volume: {
inherit (volume) path access flags; inherit (volume) path access flags readOnly;
}) cfg.volumes; }) cfg.volumes;
}; };

View File

@@ -158,6 +158,12 @@
trustedSources = [ "${registry.address.${config.networking.hostName}."reverse-proxy"}/32" ]; trustedSources = [ "${registry.address.${config.networking.hostName}."reverse-proxy"}/32" ];
}; };
volumes = { volumes = {
"/" = {
hostPath = "/pikachu/data/copyparty/root";
path = "/srv";
access.r = [ "alexion" ];
readOnly = true;
};
"/inbox" = { "/inbox" = {
hostPath = "/pikachu/__inbox"; hostPath = "/pikachu/__inbox";
path = "/srv/inbox"; path = "/srv/inbox";

View File

@@ -23,6 +23,12 @@ let
description = "Copyparty access map from permission string to account names."; description = "Copyparty access map from permission string to account names.";
}; };
readOnly = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Serve this volume read-only.";
};
flags = lib.mkOption { flags = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
default = [ ]; default = [ ];
@@ -196,7 +202,7 @@ in
PrivateTmp = true; PrivateTmp = true;
ProtectSystem = "strict"; ProtectSystem = "strict";
ProtectHome = true; ProtectHome = true;
ReadWritePaths = [ cfg.stateDir ] ++ map (volume: volume.path) (lib.attrValues cfg.volumes); ReadWritePaths = [ cfg.stateDir ] ++ map (volume: volume.path) (lib.filter (volume: !volume.readOnly) (lib.attrValues cfg.volumes));
}; };
}; };