nixos/rustic: nixfmt
This commit is contained in:
@@ -1,46 +1,70 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
tomlFormat = pkgs.formats.toml {};
|
||||
tomlFormat = pkgs.formats.toml { };
|
||||
|
||||
cfg = config.services.rustic;
|
||||
configFiles = lib.mapAttrs (k: v: tomlFormat.generate "rustic-${k}.toml" v) cfg.profiles;
|
||||
|
||||
commonOpts = v:
|
||||
if v ? files then v.files
|
||||
else if v ? command then v.command
|
||||
else if v ? postgres then v.postgres
|
||||
else throw "unexpected: backup has none of the authorized types";
|
||||
commonOpts =
|
||||
v:
|
||||
if v ? files then
|
||||
v.files
|
||||
else if v ? command then
|
||||
v.command
|
||||
else if v ? postgres then
|
||||
v.postgres
|
||||
else
|
||||
throw "unexpected: backup has none of the authorized types";
|
||||
|
||||
backupType = v:
|
||||
if v ? files then "files"
|
||||
else if v ? command then "command"
|
||||
else if v ? postgres then "postgres"
|
||||
else throw "unexpected: backup has none of the authorized types";
|
||||
backupType =
|
||||
v:
|
||||
if v ? files then
|
||||
"files"
|
||||
else if v ? command then
|
||||
"command"
|
||||
else if v ? postgres then
|
||||
"postgres"
|
||||
else
|
||||
throw "unexpected: backup has none of the authorized types";
|
||||
|
||||
backupConfigToCmd = k: v: let
|
||||
o = commonOpts v;
|
||||
profileArgs = lib.concatMapStrings (s: " -P \"${s}\"") o.useProfiles;
|
||||
extraArgs = lib.concatMapStrings (s: " \"${s}\"") o.extraArgs;
|
||||
genericArgs = profileArgs + extraArgs;
|
||||
backupConfigToCmd =
|
||||
k: v:
|
||||
let
|
||||
o = commonOpts v;
|
||||
profileArgs = lib.concatMapStrings (s: " -P \"${s}\"") o.useProfiles;
|
||||
extraArgs = lib.concatMapStrings (s: " \"${s}\"") o.extraArgs;
|
||||
genericArgs = profileArgs + extraArgs;
|
||||
|
||||
sourcesArgs = lib.concatMapStrings (s: " ${s}") v.files.sources;
|
||||
asPathArg = lib.optionalString (v.files.asPath != null) " --as-path \"${v.files.asPath}\"";
|
||||
ifFilesArgs = lib.optionalString (v ? files) (sourcesArgs + asPathArg);
|
||||
sourcesArgs = lib.concatMapStrings (s: " ${s}") v.files.sources;
|
||||
asPathArg = lib.optionalString (v.files.asPath != null) " --as-path \"${v.files.asPath}\"";
|
||||
ifFilesArgs = lib.optionalString (v ? files) (sourcesArgs + asPathArg);
|
||||
|
||||
commandArg = " --stdin-command \"${v.command.command}\" -"; # stdin-command requires adding `-` as a source
|
||||
stdinFilenameArg = lib.optionalString (v.command.filename != null) " --stdin-filename \"${v.command.filename}\"";
|
||||
ifCommandArgs = lib.optionalString (v ? command) (commandArg + stdinFilenameArg);
|
||||
commandArg = " --stdin-command \"${v.command.command}\" -"; # stdin-command requires adding `-` as a source
|
||||
stdinFilenameArg = lib.optionalString (
|
||||
v.command.filename != null
|
||||
) " --stdin-filename \"${v.command.filename}\"";
|
||||
ifCommandArgs = lib.optionalString (v ? command) (commandArg + stdinFilenameArg);
|
||||
|
||||
pipeJsonIntoCmd =
|
||||
if o.pipeJsonInto ? nothing then ""
|
||||
else if o.pipeJsonInto ? command then " --json | ${o.pipeJsonInto.command} \"$1\""
|
||||
else if o.pipeJsonInto ? prometheus then " --json | ${prometheusWriteScript o.pipeJsonInto.prometheus.nodeExporterCollectorFolder} \"$1\""
|
||||
else throw "unexpected: pipeJsonInto has none of the authorized types";
|
||||
in
|
||||
if v ? postgres
|
||||
then
|
||||
let systemctl = "${config.systemd.package}/bin/systemctl"; in
|
||||
pipeJsonIntoCmd =
|
||||
if o.pipeJsonInto ? nothing then
|
||||
""
|
||||
else if o.pipeJsonInto ? command then
|
||||
" --json | ${o.pipeJsonInto.command} \"$1\""
|
||||
else if o.pipeJsonInto ? prometheus then
|
||||
" --json | ${prometheusWriteScript o.pipeJsonInto.prometheus.nodeExporterCollectorFolder} \"$1\""
|
||||
else
|
||||
throw "unexpected: pipeJsonInto has none of the authorized types";
|
||||
in
|
||||
if v ? postgres then
|
||||
let
|
||||
systemctl = "${config.systemd.package}/bin/systemctl";
|
||||
in
|
||||
pkgs.writeScript "rustic-postgres-starter-${k}" ''
|
||||
#!${pkgs.bash}/bin/bash
|
||||
set -euo pipefail
|
||||
@@ -98,24 +122,29 @@ let
|
||||
rustic_backup_${m}{id=\"{{id}}\"} {{${m}}}
|
||||
'') prometheusAllMetrics;
|
||||
|
||||
prometheusWriteScript = f: pkgs.writeScript "rustic-write-to-prometheus" ''
|
||||
#!${pkgs.bash}/bin/bash
|
||||
set -euo pipefail
|
||||
prometheusWriteScript =
|
||||
f:
|
||||
pkgs.writeScript "rustic-write-to-prometheus" ''
|
||||
#!${pkgs.bash}/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
${pkgs.jq}/bin/jq -r --arg template "${prometheusJqTemplate}" '
|
||||
. as $json
|
||||
| $template
|
||||
| gsub("{{id}}"; "'"$1"'")
|
||||
${lib.concatMapStrings
|
||||
(m: ''| gsub("{{${m}}}"; $json.summary.${m} | tostring)'')
|
||||
prometheusNumericMetrics}
|
||||
${lib.concatMapStrings
|
||||
# jq's `fromdate` builtins do not support sub-second accuracy (as of early 2025)
|
||||
(m: ''| gsub("{{${m}}}"; $json.summary.${m} | sub(".[0-9]{9}Z"; "Z") | fromdateiso8601 | tostring)'')
|
||||
prometheusTimeMetrics}
|
||||
' > "${f}/rustic-backup-$1.prom.next"
|
||||
mv "${f}/rustic-backup-$1.prom.next" "${f}/rustic-backup-$1.prom"
|
||||
'';
|
||||
${pkgs.jq}/bin/jq -r --arg template "${prometheusJqTemplate}" '
|
||||
. as $json
|
||||
| $template
|
||||
| gsub("{{id}}"; "'"$1"'")
|
||||
${lib.concatMapStrings (
|
||||
m: ''| gsub("{{${m}}}"; $json.summary.${m} | tostring)''
|
||||
) prometheusNumericMetrics}
|
||||
${lib.concatMapStrings
|
||||
# jq's `fromdate` builtins do not support sub-second accuracy (as of early 2025)
|
||||
(
|
||||
m: ''| gsub("{{${m}}}"; $json.summary.${m} | sub(".[0-9]{9}Z"; "Z") | fromdateiso8601 | tostring)''
|
||||
)
|
||||
prometheusTimeMetrics
|
||||
}
|
||||
' > "${f}/rustic-backup-$1.prom.next"
|
||||
mv "${f}/rustic-backup-$1.prom.next" "${f}/rustic-backup-$1.prom"
|
||||
'';
|
||||
|
||||
commonOptions = {
|
||||
startAt = lib.mkOption {
|
||||
@@ -129,13 +158,13 @@ let
|
||||
|
||||
useProfiles = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
default = [ ];
|
||||
description = "Config profiles to use";
|
||||
};
|
||||
|
||||
extraArgs = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
default = [ ];
|
||||
description = "Extra command-line arguments to pass to the `rustic` call.";
|
||||
};
|
||||
};
|
||||
@@ -143,7 +172,9 @@ let
|
||||
commonBackupOptions = commonOptions // {
|
||||
pipeJsonInto = lib.mkOption {
|
||||
description = "Command that will be called on the JSON result of `rustic backup`.";
|
||||
default = { nothing = {}; };
|
||||
default = {
|
||||
nothing = { };
|
||||
};
|
||||
type = lib.types.attrTag {
|
||||
nothing = lib.mkOption {
|
||||
description = "Do nothing with the result.";
|
||||
@@ -181,7 +212,7 @@ in
|
||||
{
|
||||
options.services.rustic = {
|
||||
enable = lib.mkEnableOption "rustic";
|
||||
package = lib.mkPackageOption pkgs "rustic" {};
|
||||
package = lib.mkPackageOption pkgs "rustic" { };
|
||||
|
||||
checkProfiles = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
@@ -197,7 +228,7 @@ in
|
||||
|
||||
profiles = lib.mkOption {
|
||||
type = lib.types.attrsOf tomlFormat.type;
|
||||
default = {};
|
||||
default = { };
|
||||
description = ''
|
||||
Configuration files for rustic, see
|
||||
<https://github.com/rustic-rs/rustic/blob/main/config/README.md>
|
||||
@@ -215,7 +246,7 @@ in
|
||||
};
|
||||
|
||||
backups = lib.mkOption {
|
||||
default = {};
|
||||
default = { };
|
||||
description = ''
|
||||
Backup configurations.
|
||||
|
||||
@@ -224,76 +255,78 @@ in
|
||||
running at different intervals, and backing up different folders or
|
||||
databases.
|
||||
'';
|
||||
type = lib.types.attrsOf (lib.types.attrTag {
|
||||
files = lib.mkOption {
|
||||
description = "Backup files and directories.";
|
||||
type = lib.types.submodule {
|
||||
options = commonBackupOptions // {
|
||||
sources = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.path;
|
||||
default = [];
|
||||
description = ''
|
||||
List of sources to backup.
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.attrTag {
|
||||
files = lib.mkOption {
|
||||
description = "Backup files and directories.";
|
||||
type = lib.types.submodule {
|
||||
options = commonBackupOptions // {
|
||||
sources = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.path;
|
||||
default = [ ];
|
||||
description = ''
|
||||
List of sources to backup.
|
||||
|
||||
Defaults to the ones defined in the relevant configuration files.
|
||||
'';
|
||||
};
|
||||
Defaults to the ones defined in the relevant configuration files.
|
||||
'';
|
||||
};
|
||||
|
||||
asPath = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Manually set the backup path in snapshot";
|
||||
asPath = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Manually set the backup path in snapshot";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
command = lib.mkOption {
|
||||
description = "Backup the output of a command.";
|
||||
type = lib.types.submodule {
|
||||
options = commonBackupOptions // {
|
||||
command = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Command of which to backup the output";
|
||||
};
|
||||
command = lib.mkOption {
|
||||
description = "Backup the output of a command.";
|
||||
type = lib.types.submodule {
|
||||
options = commonBackupOptions // {
|
||||
command = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Command of which to backup the output";
|
||||
};
|
||||
|
||||
filename = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Filename to use in the backup.
|
||||
filename = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Filename to use in the backup.
|
||||
|
||||
Defaults to the profile-provided filename, or `stdin`.
|
||||
'';
|
||||
Defaults to the profile-provided filename, or `stdin`.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
postgres = lib.mkOption {
|
||||
description = ''
|
||||
Backup all postgresql databases.
|
||||
postgres = lib.mkOption {
|
||||
description = ''
|
||||
Backup all postgresql databases.
|
||||
|
||||
Use a custom `command` using `pg_dump` if you want to backup a single
|
||||
database.
|
||||
Use a custom `command` using `pg_dump` if you want to backup a single
|
||||
database.
|
||||
|
||||
This backs up globals and individual databases in independent files.
|
||||
'';
|
||||
type = lib.types.submodule {
|
||||
options = commonBackupOptions // {
|
||||
prefix = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/postgres";
|
||||
description = "Path prefix for the dumps.";
|
||||
This backs up globals and individual databases in independent files.
|
||||
'';
|
||||
type = lib.types.submodule {
|
||||
options = commonBackupOptions // {
|
||||
prefix = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/postgres";
|
||||
description = "Path prefix for the dumps.";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
checks = lib.mkOption {
|
||||
default = {};
|
||||
default = { };
|
||||
description = ''
|
||||
Check configurations.
|
||||
|
||||
@@ -311,14 +344,16 @@ in
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
system.checks = lib.optional cfg.checkProfiles (pkgs.runCommand "rustic-profiles-check" {} ''
|
||||
${lib.concatMapStrings (p: ''
|
||||
ln -s ${p} ./rustic-config-for-checking.toml
|
||||
${cfg.package}/bin/rustic show-config -P rustic-config-for-checking > /dev/null
|
||||
rm ./rustic-config-for-checking.toml
|
||||
'') (lib.attrValues configFiles)}
|
||||
touch $out
|
||||
'');
|
||||
system.checks = lib.optional cfg.checkProfiles (
|
||||
pkgs.runCommand "rustic-profiles-check" { } ''
|
||||
${lib.concatMapStrings (p: ''
|
||||
ln -s ${p} ./rustic-config-for-checking.toml
|
||||
${cfg.package}/bin/rustic show-config -P rustic-config-for-checking > /dev/null
|
||||
rm ./rustic-config-for-checking.toml
|
||||
'') (lib.attrValues configFiles)}
|
||||
touch $out
|
||||
''
|
||||
);
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
@@ -327,65 +362,83 @@ in
|
||||
value.source = v;
|
||||
}) configFiles;
|
||||
|
||||
systemd.services = lib.concatMapAttrs (k: v: {
|
||||
"rustic-backup-${k}" = {
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "root";
|
||||
ExecStart = "${backupConfigToCmd k v} ${backupType v}-${k}";
|
||||
};
|
||||
startAt = (commonOpts v).startAt;
|
||||
};
|
||||
} // lib.optionalAttrs (v ? postgres) {
|
||||
"rustic-postgres-globals-${k}" = {
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "root";
|
||||
ExecStart = "${backupConfigToCmd k {
|
||||
command = v.postgres // {
|
||||
filename = "${v.postgres.prefix}/globals.sql";
|
||||
command = "${pkgs.sudo}/bin/sudo -u postgres ${config.services.postgresql.package}/bin/pg_dumpall --globals-only";
|
||||
systemd.services =
|
||||
lib.concatMapAttrs (
|
||||
k: v:
|
||||
{
|
||||
"rustic-backup-${k}" = {
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "root";
|
||||
ExecStart = "${backupConfigToCmd k v} ${backupType v}-${k}";
|
||||
};
|
||||
}} postgres-globals-${k}";
|
||||
};
|
||||
};
|
||||
startAt = (commonOpts v).startAt;
|
||||
};
|
||||
}
|
||||
// lib.optionalAttrs (v ? postgres) {
|
||||
"rustic-postgres-globals-${k}" = {
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "root";
|
||||
ExecStart = "${
|
||||
backupConfigToCmd k {
|
||||
command = v.postgres // {
|
||||
filename = "${v.postgres.prefix}/globals.sql";
|
||||
command = "${pkgs.sudo}/bin/sudo -u postgres ${config.services.postgresql.package}/bin/pg_dumpall --globals-only";
|
||||
};
|
||||
}
|
||||
} postgres-globals-${k}";
|
||||
};
|
||||
};
|
||||
|
||||
"rustic-postgres-db-${k}@" = {
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "root";
|
||||
ExecStart = "${backupConfigToCmd k {
|
||||
command = v.postgres // {
|
||||
filename = "$3";
|
||||
command = "${pkgs.sudo}/bin/sudo -u postgres ${config.services.postgresql.package}/bin/pg_dump $2";
|
||||
"rustic-postgres-db-${k}@" = {
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "root";
|
||||
ExecStart = "${
|
||||
backupConfigToCmd k {
|
||||
command = v.postgres // {
|
||||
filename = "$3";
|
||||
command = "${pkgs.sudo}/bin/sudo -u postgres ${config.services.postgresql.package}/bin/pg_dump $2";
|
||||
};
|
||||
}
|
||||
} 'postgres-db-${k}-%i' '%i' '${v.postgres.prefix}/db/%i.sql'";
|
||||
};
|
||||
}} 'postgres-db-${k}-%i' '%i' '${v.postgres.prefix}/db/%i.sql'";
|
||||
};
|
||||
};
|
||||
}) cfg.backups // lib.concatMapAttrs (k: v: let
|
||||
profiles = lib.concatMapStrings (p: " -P \"${p}\"") v.useProfiles;
|
||||
extraArgs = lib.concatMapStrings (a: " \"${a}\"") v.extraArgs;
|
||||
in {
|
||||
"rustic-check-${k}" = {
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "root";
|
||||
ExecStart = "${cfg.package}/bin/rustic check${profiles}${extraArgs}";
|
||||
};
|
||||
startAt = v.startAt;
|
||||
};
|
||||
}) cfg.checks // lib.optionalAttrs cfg.prune.enable (let
|
||||
profiles = lib.concatMapStrings (p: " -P \"${p}\"") cfg.prune.useProfiles;
|
||||
extraArgs = lib.concatMapStrings (a: " \"${a}\"") cfg.prune.extraArgs;
|
||||
in {
|
||||
"rustic-prune" = {
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "root";
|
||||
ExecStart = "${cfg.package}/bin/rustic forget --prune${profiles}${extraArgs}";
|
||||
};
|
||||
startAt = cfg.prune.startAt;
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
) cfg.backups
|
||||
// lib.concatMapAttrs (
|
||||
k: v:
|
||||
let
|
||||
profiles = lib.concatMapStrings (p: " -P \"${p}\"") v.useProfiles;
|
||||
extraArgs = lib.concatMapStrings (a: " \"${a}\"") v.extraArgs;
|
||||
in
|
||||
{
|
||||
"rustic-check-${k}" = {
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "root";
|
||||
ExecStart = "${cfg.package}/bin/rustic check${profiles}${extraArgs}";
|
||||
};
|
||||
startAt = v.startAt;
|
||||
};
|
||||
}
|
||||
) cfg.checks
|
||||
// lib.optionalAttrs cfg.prune.enable (
|
||||
let
|
||||
profiles = lib.concatMapStrings (p: " -P \"${p}\"") cfg.prune.useProfiles;
|
||||
extraArgs = lib.concatMapStrings (a: " \"${a}\"") cfg.prune.extraArgs;
|
||||
in
|
||||
{
|
||||
"rustic-prune" = {
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "root";
|
||||
ExecStart = "${cfg.package}/bin/rustic forget --prune${profiles}${extraArgs}";
|
||||
};
|
||||
startAt = cfg.prune.startAt;
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user