clan.core.state: wrap all commands in shell scripts
Some checks failed
buildbot/nix-eval Build done.
checks / checks-impure (pull_request) Successful in 1m54s

Otherwise we cannot execute them via ssh and also have nix store
dependencies.
This commit is contained in:
Jörg Thalheim 2024-06-19 11:42:14 +02:00
parent 7fb6c35de8
commit 5a4d6117a1
6 changed files with 93 additions and 28 deletions

View File

@ -72,12 +72,6 @@
environment.systemPackages = [ environment.systemPackages = [
self.packages.${pkgs.system}.clan-cli self.packages.${pkgs.system}.clan-cli
(pkgs.writeShellScriptBin "pre-restore-command" ''
touch /var/test-service/pre-restore-command
'')
(pkgs.writeShellScriptBin "post-restore-command" ''
touch /var/test-service/post-restore-command
'')
]; ];
environment.etc.install-closure.source = "${closureInfo}/store-paths"; environment.etc.install-closure.source = "${closureInfo}/store-paths";
nix.settings = { nix.settings = {
@ -90,11 +84,15 @@
clanCore.state.test-backups.folders = [ "/var/test-backups" ]; clanCore.state.test-backups.folders = [ "/var/test-backups" ];
clanCore.state.test-service = { clanCore.state.test-service = {
preBackupCommand = '' preBackupScript = ''
touch /var/test-service/pre-backup-command touch /var/test-service/pre-backup-command
''; '';
preRestoreCommand = "pre-restore-command"; preRestoreScript = ''
postRestoreCommand = "post-restore-command"; touch /var/test-service/pre-restore-command
'';
postRestoreScript = ''
touch /var/test-service/post-restore-command
'';
folders = [ "/var/test-service" ]; folders = [ "/var/test-service" ];
}; };
clan.borgbackup.destinations.test-backup.repo = "borg@machine:."; clan.borgbackup.destinations.test-backup.repo = "borg@machine:.";

View File

@ -50,7 +50,7 @@
machine.succeed(""" machine.succeed("""
set -x set -x
${nodes.machine.clanCore.state.postgresql-test.postRestoreCommand} ${nodes.machine.clanCore.state.test.postRestoreCommand}
""") """)
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -l >&2") machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -l >&2")
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c '\dt' >&2") machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c '\dt' >&2")

View File

@ -13,14 +13,14 @@ let
state: state:
lib.optionalString (state.preBackupCommand != null) '' lib.optionalString (state.preBackupCommand != null) ''
echo "Running pre-backup command for ${state.name}" echo "Running pre-backup command for ${state.name}"
if ! ( ${state.preBackupCommand} ) then if ! /run/current-system/sw/bin/${state.preBackupCommand}; then
preCommandErrors["${state.name}"]=1 preCommandErrors["${state.name}"]=1
fi fi
'' ''
) (lib.attrValues config.clanCore.state)} ) (lib.attrValues config.clanCore.state)}
if [[ ''${#preCommandErrors[@]} -gt 0 ]]; then if [[ ''${#preCommandErrors[@]} -gt 0 ]]; then
echo "PreBackupCommand failed for the following services:" echo "pre-backup commands failed for the following services:"
for state in "''${!preCommandErrors[@]}"; do for state in "''${!preCommandErrors[@]}"; do
echo " $state" echo " $state"
done done

View File

@ -125,7 +125,6 @@ in
} }
${lib.concatMapStringsSep "\n" (target: '' ${lib.concatMapStringsSep "\n" (target: ''
${mountHook target} ${mountHook target}
set -x
echo "Creating backup '${target.name}'" echo "Creating backup '${target.name}'"
${lib.optionalString (target.preBackupHook != null) '' ${lib.optionalString (target.preBackupHook != null) ''
@ -139,7 +138,7 @@ in
state: state:
lib.optionalString (state.preBackupCommand != null) '' lib.optionalString (state.preBackupCommand != null) ''
echo "Running pre-backup command for ${state.name}" echo "Running pre-backup command for ${state.name}"
if ! ( ${state.preBackupCommand} ) then if ! /run/current-system/sw/bin/${state.preBackupCommand}; then
preCommandErrors["${state.name}"]=1 preCommandErrors["${state.name}"]=1
fi fi
'' ''

View File

@ -14,7 +14,7 @@ let
in in
{ {
folders = [ folder ]; folders = [ folder ];
preBackupCommand = '' preBackupScript = ''
export PATH=${ export PATH=${
lib.makeBinPath [ lib.makeBinPath [
config.services.postgresql.package config.services.postgresql.package
@ -32,7 +32,7 @@ let
runuser -u postgres -- pg_dump ${compression} --dbname=${db.name} -Fc -c > "${current}.tmp" runuser -u postgres -- pg_dump ${compression} --dbname=${db.name} -Fc -c > "${current}.tmp"
mv "${current}.tmp" ${current} mv "${current}.tmp" ${current}
''; '';
postRestoreCommand = '' postRestoreScript = ''
export PATH=${ export PATH=${
lib.makeBinPath [ lib.makeBinPath [
config.services.postgresql.package config.services.postgresql.package
@ -166,7 +166,7 @@ in
''; '';
clanCore.state = lib.mapAttrs' ( clanCore.state = lib.mapAttrs' (
_: db: lib.nameValuePair "${db.service}" (createDatatbaseState db) _: db: lib.nameValuePair db.service (createDatatbaseState db)
) config.clan.postgresql.databases; ) config.clan.postgresql.databases;
}; };
} }

View File

@ -1,18 +1,20 @@
{ lib, ... }:
{ {
# defaults lib,
config.clanCore.state.HOME.folders = [ "/home" ]; pkgs,
config,
...
}:
{
# interface # interface
options.clanCore.state = lib.mkOption { options.clanCore.state = lib.mkOption {
default = { }; default = { };
type = lib.types.attrsOf ( type = lib.types.attrsOf (
lib.types.submodule ( lib.types.submodule (
{ name, ... }: { name, config, ... }:
{ {
options = { options = {
name = lib.mkOption { name = lib.mkOption {
type = lib.types.str; type = lib.types.strMatching "^[a-zA-Z0-9_-]+$";
default = name; default = name;
description = '' description = ''
Name of the state Name of the state
@ -24,8 +26,9 @@
Folder where state resides in Folder where state resides in
''; '';
}; };
preBackupCommand = lib.mkOption {
type = lib.types.nullOr lib.types.str; preBackupScript = lib.mkOption {
type = lib.types.nullOr lib.types.lines;
default = null; default = null;
description = '' description = ''
script to run before backing up the state dir script to run before backing up the state dir
@ -34,6 +37,15 @@
''; '';
}; };
preBackupCommand = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = if config.preBackupScript == null then null else "pre-backup-${name}";
readOnly = true;
description = ''
Use this command in backup providers. It contains the content of preBackupScript.
'';
};
# TODO: implement this # TODO: implement this
#stopOnRestore = lib.mkOption { #stopOnRestore = lib.mkOption {
# type = lib.types.listOf lib.types.str; # type = lib.types.listOf lib.types.str;
@ -45,8 +57,8 @@
# ''; # '';
#}; #};
preRestoreCommand = lib.mkOption { preRestoreScript = lib.mkOption {
type = lib.types.nullOr lib.types.str; type = lib.types.nullOr lib.types.lines;
default = null; default = null;
description = '' description = ''
script to run before restoring the state dir from a backup script to run before restoring the state dir from a backup
@ -55,8 +67,18 @@
''; '';
}; };
postRestoreCommand = lib.mkOption { preRestoreCommand = lib.mkOption {
type = lib.types.nullOr lib.types.str; type = lib.types.nullOr lib.types.str;
default = if config.preRestoreScript == null then null else "pre-restore-${name}";
readOnly = true;
description = ''
This command can be called to restore the state dir from a backup.
It contains the content of preRestoreScript.
'';
};
postRestoreScript = lib.mkOption {
type = lib.types.nullOr lib.types.lines;
default = null; default = null;
description = '' description = ''
script to restore the service after the state dir was restored from a backup script to restore the service after the state dir was restored from a backup
@ -64,9 +86,55 @@
Utilize this to start services which were previously stopped Utilize this to start services which were previously stopped
''; '';
}; };
postRestoreCommand = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = if config.postRestoreScript == null then null else "post-restore-${name}";
readOnly = true;
description = ''
This command is called after a restore of the state dir from a backup.
It contains the content of postRestoreScript.
'';
};
}; };
} }
) )
); );
}; };
# defaults
config.clanCore.state.HOME.folders = [ "/home" ];
config.environment.systemPackages = lib.optional (config.clanCore.state != { }) (
pkgs.runCommand "state-commands" { } ''
${builtins.concatStringsSep "\n" (
builtins.map (state: ''
writeShellScript() {
local name=$1
local content=$2
echo "!${pkgs.runtimeShell}" > $out/bin/$name
echo "set -eu -o pipefail" >> $out/bin/$name
echo "$content" > $out/bin/$name
}
mkdir -p $out/bin/
${
lib.optionalString (state.preBackupCommand != null) ''
writeShellScript ${lib.escapeShellArg state.preBackupCommand} ${lib.escapeShellArg state.preBackupScript}
''
}
${
lib.optionalString (state.preRestoreCommand != null) ''
writeShellScript ${lib.escapeShellArg state.preRestoreCommand} ${lib.escapeShellArg state.preRestoreScript}
''
}
${
lib.optionalString (state.postRestoreCommand != null) ''
writeShellScript ${lib.escapeShellArg state.postRestoreCommand} ${lib.escapeShellArg state.postRestoreScript}
''
}
find $out/bin/ -type f -print0 | xargs --no-run-if-empty -0 chmod 755
'') (builtins.attrValues config.clanCore.state)
)}
''
);
} }