From 5a4d6117a10cf1d7e09889cdb9d4c2a77798d903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Wed, 19 Jun 2024 11:42:14 +0200 Subject: [PATCH] clan.core.state: wrap all commands in shell scripts Otherwise we cannot execute them via ssh and also have nix store dependencies. --- checks/backups/flake-module.nix | 16 +++-- checks/postgresql/default.nix | 2 +- clanModules/borgbackup/default.nix | 4 +- clanModules/localbackup/default.nix | 3 +- clanModules/postgresql/default.nix | 6 +- nixosModules/clanCore/state.nix | 90 +++++++++++++++++++++++++---- 6 files changed, 93 insertions(+), 28 deletions(-) diff --git a/checks/backups/flake-module.nix b/checks/backups/flake-module.nix index 37aa28e3..205d6354 100644 --- a/checks/backups/flake-module.nix +++ b/checks/backups/flake-module.nix @@ -72,12 +72,6 @@ environment.systemPackages = [ 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"; nix.settings = { @@ -90,11 +84,15 @@ clanCore.state.test-backups.folders = [ "/var/test-backups" ]; clanCore.state.test-service = { - preBackupCommand = '' + preBackupScript = '' touch /var/test-service/pre-backup-command ''; - preRestoreCommand = "pre-restore-command"; - postRestoreCommand = "post-restore-command"; + preRestoreScript = '' + touch /var/test-service/pre-restore-command + ''; + postRestoreScript = '' + touch /var/test-service/post-restore-command + ''; folders = [ "/var/test-service" ]; }; clan.borgbackup.destinations.test-backup.repo = "borg@machine:."; diff --git a/checks/postgresql/default.nix b/checks/postgresql/default.nix index 91bf2e52..569b5170 100644 --- a/checks/postgresql/default.nix +++ b/checks/postgresql/default.nix @@ -50,7 +50,7 @@ machine.succeed(""" 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 -d test -c '\dt' >&2") diff --git a/clanModules/borgbackup/default.nix b/clanModules/borgbackup/default.nix index 3d4a0cbb..de671c55 100644 --- a/clanModules/borgbackup/default.nix +++ b/clanModules/borgbackup/default.nix @@ -13,14 +13,14 @@ let state: lib.optionalString (state.preBackupCommand != null) '' 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 fi '' ) (lib.attrValues config.clanCore.state)} 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 echo " $state" done diff --git a/clanModules/localbackup/default.nix b/clanModules/localbackup/default.nix index 917406a8..c80049e0 100644 --- a/clanModules/localbackup/default.nix +++ b/clanModules/localbackup/default.nix @@ -125,7 +125,6 @@ in } ${lib.concatMapStringsSep "\n" (target: '' ${mountHook target} - set -x echo "Creating backup '${target.name}'" ${lib.optionalString (target.preBackupHook != null) '' @@ -139,7 +138,7 @@ in state: lib.optionalString (state.preBackupCommand != null) '' 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 fi '' diff --git a/clanModules/postgresql/default.nix b/clanModules/postgresql/default.nix index b9b6efe7..1d3a0e3f 100644 --- a/clanModules/postgresql/default.nix +++ b/clanModules/postgresql/default.nix @@ -14,7 +14,7 @@ let in { folders = [ folder ]; - preBackupCommand = '' + preBackupScript = '' export PATH=${ lib.makeBinPath [ config.services.postgresql.package @@ -32,7 +32,7 @@ let runuser -u postgres -- pg_dump ${compression} --dbname=${db.name} -Fc -c > "${current}.tmp" mv "${current}.tmp" ${current} ''; - postRestoreCommand = '' + postRestoreScript = '' export PATH=${ lib.makeBinPath [ config.services.postgresql.package @@ -166,7 +166,7 @@ in ''; clanCore.state = lib.mapAttrs' ( - _: db: lib.nameValuePair "${db.service}" (createDatatbaseState db) + _: db: lib.nameValuePair db.service (createDatatbaseState db) ) config.clan.postgresql.databases; }; } diff --git a/nixosModules/clanCore/state.nix b/nixosModules/clanCore/state.nix index 1128fdcc..fc1d9829 100644 --- a/nixosModules/clanCore/state.nix +++ b/nixosModules/clanCore/state.nix @@ -1,18 +1,20 @@ -{ lib, ... }: { - # defaults - config.clanCore.state.HOME.folders = [ "/home" ]; - + lib, + pkgs, + config, + ... +}: +{ # interface options.clanCore.state = lib.mkOption { default = { }; type = lib.types.attrsOf ( lib.types.submodule ( - { name, ... }: + { name, config, ... }: { options = { name = lib.mkOption { - type = lib.types.str; + type = lib.types.strMatching "^[a-zA-Z0-9_-]+$"; default = name; description = '' Name of the state @@ -24,8 +26,9 @@ 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; description = '' 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 #stopOnRestore = lib.mkOption { # type = lib.types.listOf lib.types.str; @@ -45,8 +57,8 @@ # ''; #}; - preRestoreCommand = lib.mkOption { - type = lib.types.nullOr lib.types.str; + preRestoreScript = lib.mkOption { + type = lib.types.nullOr lib.types.lines; default = null; description = '' 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; + 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; description = '' 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 ''; }; + + 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) + )} + '' + ); }