mirror of
https://github.com/oddlama/nix-config.git
synced 2025-10-11 07:10:39 +02:00
feat: add paperless and radicale backups to hetzner
This commit is contained in:
parent
f9e1247b8a
commit
78ecdd2780
12 changed files with 128 additions and 67 deletions
|
@ -1,10 +1,12 @@
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
|
lib,
|
||||||
nodes,
|
nodes,
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
sentinelCfg = nodes.sentinel.config;
|
sentinelCfg = nodes.sentinel.config;
|
||||||
paperlessDomain = "paperless.${sentinelCfg.repo.secrets.local.personalDomain}";
|
paperlessDomain = "paperless.${sentinelCfg.repo.secrets.local.personalDomain}";
|
||||||
|
paperlessBackupDir = "/var/cache/paperless-backup";
|
||||||
in {
|
in {
|
||||||
microvm.mem = 1024 * 6;
|
microvm.mem = 1024 * 6;
|
||||||
microvm.vcpu = 8;
|
microvm.vcpu = 8;
|
||||||
|
@ -92,4 +94,29 @@ in {
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.paperless.serviceConfig.RestartSec = "600"; # Retry every 10 minutes
|
systemd.services.paperless.serviceConfig.RestartSec = "600"; # Retry every 10 minutes
|
||||||
|
|
||||||
|
systemd.tmpfiles.settings."10-paperless".${paperlessBackupDir}.d = {
|
||||||
|
inherit (config.services.paperless) user;
|
||||||
|
mode = "0700";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.paperless-backup = let
|
||||||
|
cfg = config.systemd.services.paperless-consumer;
|
||||||
|
in {
|
||||||
|
description = "Paperless documents backup";
|
||||||
|
serviceConfig = lib.recursiveUpdate cfg.serviceConfig {
|
||||||
|
ExecStart = "${config.services.paperless.package}/bin/paperless-ngx document_exporter -na -nt -f -d ${paperlessBackupDir}";
|
||||||
|
ReadWritePaths = cfg.serviceConfig.ReadWritePaths ++ [paperlessBackupDir];
|
||||||
|
Restart = "no";
|
||||||
|
Type = "oneshot";
|
||||||
|
};
|
||||||
|
inherit (cfg) environment;
|
||||||
|
requiredBy = ["restic-backups-storage-box-dusk.service"];
|
||||||
|
};
|
||||||
|
|
||||||
|
backups.storageBoxes.dusk = {
|
||||||
|
subuser = "paperless";
|
||||||
|
user = "paperless";
|
||||||
|
paths = [paperlessBackupDir];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -246,7 +246,7 @@ in {
|
||||||
"/shares/users/${user}-paperless".d = {
|
"/shares/users/${user}-paperless".d = {
|
||||||
user = "paperless";
|
user = "paperless";
|
||||||
group = "paperless";
|
group = "paperless";
|
||||||
mode = "0750";
|
mode = "0550";
|
||||||
};
|
};
|
||||||
"/paperless/consume/${user}".d = {
|
"/paperless/consume/${user}".d = {
|
||||||
user = "paperless";
|
user = "paperless";
|
||||||
|
@ -347,37 +347,9 @@ in {
|
||||||
}
|
}
|
||||||
// lib.mapAttrs (_: cfg: {gid = cfg.id;}) (smbUsers // smbGroups);
|
// lib.mapAttrs (_: cfg: {gid = cfg.id;}) (smbUsers // smbGroups);
|
||||||
|
|
||||||
# Backups
|
backups.storageBoxes.dusk = {
|
||||||
# ========================================================================
|
subuser = "samba";
|
||||||
|
|
||||||
age.secrets.restic-encryption-password.generator.script = "alnum";
|
|
||||||
age.secrets.restic-ssh-privkey.generator.script = "ssh-ed25519";
|
|
||||||
|
|
||||||
services.restic.backups.main = {
|
|
||||||
hetznerStorageBox = let
|
|
||||||
box = config.repo.secrets.global.hetzner.storageboxes.dusk;
|
|
||||||
in {
|
|
||||||
enable = true;
|
|
||||||
inherit (box) mainUser;
|
|
||||||
inherit (box.users.samba) subUid path;
|
|
||||||
sshAgeSecret = "restic-ssh-privkey";
|
|
||||||
};
|
|
||||||
|
|
||||||
# We need to backup stuff from other users, so run as root.
|
|
||||||
user = "root";
|
user = "root";
|
||||||
timerConfig = {
|
|
||||||
OnCalendar = "06:15";
|
|
||||||
RandomizedDelaySec = "3h";
|
|
||||||
Persistent = true;
|
|
||||||
};
|
|
||||||
initialize = true;
|
|
||||||
passwordFile = config.age.secrets.restic-encryption-password.path;
|
|
||||||
paths = ["/bunker"];
|
paths = ["/bunker"];
|
||||||
pruneOpts = [
|
|
||||||
"--keep-daily 14"
|
|
||||||
"--keep-weekly 7"
|
|
||||||
"--keep-monthly 12"
|
|
||||||
"--keep-yearly 75"
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,4 +82,10 @@ in {
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.radicale.serviceConfig.RestartSec = "600"; # Retry every 10 minutes
|
systemd.services.radicale.serviceConfig.RestartSec = "600"; # Retry every 10 minutes
|
||||||
|
|
||||||
|
backups.storageBoxes.dusk = {
|
||||||
|
subuser = "radicale";
|
||||||
|
user = "radicale";
|
||||||
|
paths = ["/var/lib/radicale"];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,6 @@ in {
|
||||||
smtpSecurity = "force_tls";
|
smtpSecurity = "force_tls";
|
||||||
smtpPort = 465;
|
smtpPort = 465;
|
||||||
};
|
};
|
||||||
#backupDir = "/data/backup";
|
|
||||||
environmentFile = config.age.secrets.vaultwarden-env.path;
|
environmentFile = config.age.secrets.vaultwarden-env.path;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -85,36 +84,9 @@ in {
|
||||||
RestartSec = "600"; # Retry every 10 minutes
|
RestartSec = "600"; # Retry every 10 minutes
|
||||||
};
|
};
|
||||||
|
|
||||||
# Backups
|
backups.storageBoxes.dusk = {
|
||||||
# ========================================================================
|
subuser = "vaultwarden";
|
||||||
|
|
||||||
age.secrets.restic-encryption-password.generator.script = "alnum";
|
|
||||||
age.secrets.restic-ssh-privkey.generator.script = "ssh-ed25519";
|
|
||||||
|
|
||||||
services.restic.backups.main = {
|
|
||||||
hetznerStorageBox = let
|
|
||||||
box = config.repo.secrets.global.hetzner.storageboxes.dusk;
|
|
||||||
in {
|
|
||||||
enable = true;
|
|
||||||
inherit (box) mainUser;
|
|
||||||
inherit (box.users.vaultwarden) subUid path;
|
|
||||||
sshAgeSecret = "restic-ssh-privkey";
|
|
||||||
};
|
|
||||||
|
|
||||||
user = "vaultwarden";
|
user = "vaultwarden";
|
||||||
timerConfig = {
|
|
||||||
OnCalendar = "06:15";
|
|
||||||
RandomizedDelaySec = "3h";
|
|
||||||
Persistent = true;
|
|
||||||
};
|
|
||||||
initialize = true;
|
|
||||||
passwordFile = config.age.secrets.restic-encryption-password.path;
|
|
||||||
paths = [config.services.vaultwarden.backupDir];
|
paths = [config.services.vaultwarden.backupDir];
|
||||||
pruneOpts = [
|
|
||||||
"--keep-daily 14"
|
|
||||||
"--keep-weekly 7"
|
|
||||||
"--keep-monthly 12"
|
|
||||||
"--keep-yearly 75"
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,12 +104,6 @@ in {
|
||||||
masquerade = true;
|
masquerade = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Rule needed to allow local-vms wireguard traffic
|
|
||||||
lan-to-local = {
|
|
||||||
from = ["lan"];
|
|
||||||
to = ["local"];
|
|
||||||
};
|
|
||||||
|
|
||||||
outbound = {
|
outbound = {
|
||||||
from = ["lan"];
|
from = ["lan"];
|
||||||
to = ["lan" "untrusted"];
|
to = ["lan" "untrusted"];
|
||||||
|
|
79
modules/backups.nix
Normal file
79
modules/backups.nix
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
inherit
|
||||||
|
(lib)
|
||||||
|
attrValues
|
||||||
|
flip
|
||||||
|
mkIf
|
||||||
|
mkMerge
|
||||||
|
mkOption
|
||||||
|
types
|
||||||
|
;
|
||||||
|
in {
|
||||||
|
options.backups.storageBoxes = mkOption {
|
||||||
|
description = "Backups to Hetzner Storage Boxes using restic";
|
||||||
|
default = {};
|
||||||
|
type = types.attrsOf (types.submodule (submod: {
|
||||||
|
options = {
|
||||||
|
name = mkOption {
|
||||||
|
description = "The name of the storage box to backup to. The box must be defined in the global secrets. Defaults to the attribute name.";
|
||||||
|
default = submod.config._module.args.name;
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
|
||||||
|
subuser = mkOption {
|
||||||
|
description = "The name of the storage box subuser as defined in the global secrets, mapping this user to a subuser id.";
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
|
||||||
|
user = mkOption {
|
||||||
|
description = "The user as which restic should run.";
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
|
||||||
|
paths = mkOption {
|
||||||
|
description = "The paths to backup.";
|
||||||
|
type = types.listOf types.path;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf (config.backups.storageBoxes != {}) {
|
||||||
|
age.secrets.restic-encryption-password.generator.script = "alnum";
|
||||||
|
age.secrets.restic-ssh-privkey.generator.script = "ssh-ed25519";
|
||||||
|
|
||||||
|
services.restic.backups = mkMerge (flip map (attrValues config.backups.storageBoxes)
|
||||||
|
(boxCfg: {
|
||||||
|
"storage-box-${boxCfg.name}" = {
|
||||||
|
hetznerStorageBox = let
|
||||||
|
box = config.repo.secrets.global.hetzner.storageboxes.${boxCfg.name};
|
||||||
|
in {
|
||||||
|
enable = true;
|
||||||
|
inherit (box) mainUser;
|
||||||
|
inherit (box.users.${boxCfg.subuser}) subUid path;
|
||||||
|
sshAgeSecret = "restic-ssh-privkey";
|
||||||
|
};
|
||||||
|
|
||||||
|
# We need to backup stuff from other users, so run as root.
|
||||||
|
inherit (boxCfg) user paths;
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = "06:15";
|
||||||
|
RandomizedDelaySec = "3h";
|
||||||
|
Persistent = true;
|
||||||
|
};
|
||||||
|
initialize = true;
|
||||||
|
passwordFile = config.age.secrets.restic-encryption-password.path;
|
||||||
|
pruneOpts = [
|
||||||
|
"--keep-daily 14"
|
||||||
|
"--keep-weekly 7"
|
||||||
|
"--keep-monthly 12"
|
||||||
|
"--keep-yearly 75"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
}
|
|
@ -27,6 +27,7 @@
|
||||||
./config/users.nix
|
./config/users.nix
|
||||||
|
|
||||||
./acme-wildcard.nix
|
./acme-wildcard.nix
|
||||||
|
./backups.nix
|
||||||
./deterministic-ids.nix
|
./deterministic-ids.nix
|
||||||
./distributed-config.nix
|
./distributed-config.nix
|
||||||
./kanidm.nix
|
./kanidm.nix
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
age-encryption.org/v1
|
||||||
|
-> X25519 yVph6Vasomk5FHVnvYCP9EIWNTDUV9VLjT3tLWO6V2U
|
||||||
|
jiNBCi4nwRT5BraQtwlF1NLEC8ed6kEvg1D6XKT808g
|
||||||
|
-> piv-p256 xqSe8Q Aj4vwj2hYJUA4EhprNegsyBlBERNaC4JOQSYFDTA4JPL
|
||||||
|
EEqBpXe1tPZfngPeuZ4I/cUkFhoG5Z4P2bjUQ8xIRoM
|
||||||
|
-> n-grease g)hu +9qNhz-|
|
||||||
|
oXZBv+XwPtRbS7tlKyekxGJAhn1Qje/y2nAjjFg+8Ap+oxwpdMwJ4iFG2yxsWdew
|
||||||
|
XcdgOj/61A
|
||||||
|
--- ZSNOvD2AbvfGqKJ+bGh4n6EZOuiIs0KsB+CDGjmojJY
|
||||||
|
œ(™ŽôÄI .MþülB‹'ùбð¶B½ˆ<±`¿ºE¯iÐ[¾&%†„Kø‘@"?ÿ±§§ˆAà¼ÁëXn"£!ñ üTØÅ
|
BIN
secrets/generated/sire-paperless/restic-ssh-privkey.age
Normal file
BIN
secrets/generated/sire-paperless/restic-ssh-privkey.age
Normal file
Binary file not shown.
BIN
secrets/generated/ward-radicale/restic-encryption-password.age
Normal file
BIN
secrets/generated/ward-radicale/restic-encryption-password.age
Normal file
Binary file not shown.
BIN
secrets/generated/ward-radicale/restic-ssh-privkey.age
Normal file
BIN
secrets/generated/ward-radicale/restic-ssh-privkey.age
Normal file
Binary file not shown.
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue