flake/helpers/services.nix
2025-11-08 21:36:09 +00:00

187 lines
5.7 KiB
Nix

# Helper module to configure multiple services with onion and clearnet access, as well as optional backups.
# Each service can specify its own URL, onion settings, data directory, and backup options.
{
config,
lib,
pkgs,
...
}: let
cfg = config.distrust.services;
backup_cfg = config.distrust.backups;
in {
options = {
distrust = {
services = lib.mkOption {
description = "Services configuration map";
type = lib.types.attrsOf (
lib.types.submodule {
options = {
url = lib.mkOption {
description = "Clearnet URL";
type = lib.types.str;
};
dataDir = lib.mkOption {
description = "Path to stateful storage for service";
type = lib.types.nullOr lib.types.path;
};
onion = lib.mkOption {
description = "Onion service settings";
type = lib.types.submodule {
options = {
url = lib.mkOption {
description = ".onion URL";
type = lib.types.str;
};
secretKey = lib.mkOption {
description = "Path to onion secret key file";
type = lib.types.path;
};
};
};
default = {};
};
backup = lib.mkOption {
description = "Backup configuration";
type = lib.types.submodule {
options = {
enable = lib.mkOption {
description = "Enable backups for this service";
type = lib.types.bool;
default = false;
};
paths = lib.mkOption {
description = "Paths to back up";
type = lib.types.listOf lib.types.str;
default = [];
};
database = lib.mkOption {
description = "Database to back up";
type = lib.types.nullOr lib.types.str;
default = null;
};
};
};
default = {};
};
virtualHostConfig = lib.mkOption {
description = "Caddy virtual host config";
type = lib.types.str;
};
};
}
);
default = {};
};
backups = lib.mkOption {
description = "Global backup configuration";
type = lib.types.submodule {
options = {
borgRepository = lib.mkOption {
description = "Borg backup repository URL";
type = lib.types.str;
};
borgSSHKey = lib.mkOption {
description = "Path to SSH key for Borg backup repository";
type = lib.types.path;
};
borgPassCommand = lib.mkOption {
description = "Command to retrieve Borg repository passphrase";
type = lib.types.str;
};
};
};
};
};
};
config = {
services = {
tor.relay.onionServices =
builtins.foldl'
(acc: key:
acc
// {
"${key}" = {
map = [80];
inherit (cfg.${key}.onion) secretKey;
};
})
{}
(builtins.attrNames cfg);
caddy = {
enable = true;
virtualHosts = builtins.foldl' (acc: key: let
site = cfg.${key};
vhostKey = "${site.url} ${site.onion.url}";
extraCfg = ''
${site.virtualHostConfig or ""}
header Onion-Location ${site.onion.url}
'';
in
acc
// {
"${vhostKey}" = {
extraConfig = extraCfg;
};
}) {} (builtins.attrNames cfg);
};
borgbackup.jobs =
builtins.foldl'
(acc: key: let
site = cfg.${key};
dump = site.backup.database;
paths = builtins.concatLists [
site.backup.paths
(
if dump != null
then ["/var/backup/${key}.sql"]
else []
)
];
preHook = lib.mkIf (dump != null) ''
mkdir -p /var/backup
${pkgs.sudo}/bin/sudo -u postgres pg_dump ${dump} > /var/backup/postgres/${key}.sql
'';
postHook = lib.mkIf (dump != null) ''
rm -f /var/backup/postgres/${key}.sql
'';
in
if site.backup.enable
then
acc
// {
"${key}" = {
repo = backup_cfg.borgRepository + "/./${key}";
environment = {
BORG_RSH = "ssh -i ${backup_cfg.borgSSHKey} -o 'StrictHostKeyChecking=no'";
};
inherit paths;
encryption = {
mode = "keyfile";
passCommand = backup_cfg.borgPassCommand;
};
compression = "auto,lzma";
startAt = "daily";
prune.keep = {
daily = 7;
weekly = 4;
monthly = -1;
};
inherit preHook;
inherit postHook;
};
}
else acc) {} (builtins.attrNames cfg);
};
systemd.tmpfiles.settings = {
"99-borgdatabasebackups"."/var/backup/postgres".d = {
user = "root";
group = "root";
mode = "0755";
};
};
};
}