mirror of
https://github.com/oddlama/nixos-extra-modules.git
synced 2025-10-10 22:00:39 +02:00
feat: add hetzner storage box intergation for restic
This commit is contained in:
parent
4744a2844c
commit
a776d7c476
4 changed files with 158 additions and 0 deletions
|
@ -18,6 +18,7 @@ EFI/BIOS boot config | Module | [Link](./modules/boot.nix) | - | - | Allows you
|
||||||
Nginx recommended options | Module | [Link](./modules/nginx.nix) | - | agenix | Sets many recommended settings for nginx with a single switch plus some opinionated defaults. Also adds a switch for setting recommended security headers on each location.
|
Nginx recommended options | Module | [Link](./modules/nginx.nix) | - | agenix | Sets many recommended settings for nginx with a single switch plus some opinionated defaults. Also adds a switch for setting recommended security headers on each location.
|
||||||
Node options | Module | [Link](./modules/node.nix) | - | - | A module that stores meta information about your nodes (hosts). Required for some other modules that operate across nodes.
|
Node options | Module | [Link](./modules/node.nix) | - | - | A module that stores meta information about your nodes (hosts). Required for some other modules that operate across nodes.
|
||||||
Guests (MicroVMs & Containers) | Module | [Link](./modules/guests) | zfs, node options | - | This module implements a common interface to use guest systems with microvms or nixos-containers.
|
Guests (MicroVMs & Containers) | Module | [Link](./modules/guests) | zfs, node options | - | This module implements a common interface to use guest systems with microvms or nixos-containers.
|
||||||
|
Restic hetzner storage box setup | Module | [Link](./modules/restic.nix) | - | - | This module exposes new options for restic backups that allow a simple setup of hetzner storage boxes. There's [an app](./apps/setup-hetzner-storage-boxes.nix) that you should expose on your flake to automate remote setup.
|
||||||
|
|
||||||
#### Home Manager Modules
|
#### Home Manager Modules
|
||||||
|
|
||||||
|
|
100
apps/setup-hetzner-storage-boxes.nix
Normal file
100
apps/setup-hetzner-storage-boxes.nix
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
nixosConfigurations,
|
||||||
|
decryptIdentity,
|
||||||
|
}: let
|
||||||
|
inherit
|
||||||
|
(pkgs.lib)
|
||||||
|
attrValues
|
||||||
|
concatLines
|
||||||
|
concatStringsSep
|
||||||
|
escapeShellArg
|
||||||
|
filterAttrs
|
||||||
|
flatten
|
||||||
|
flip
|
||||||
|
getExe
|
||||||
|
groupBy
|
||||||
|
head
|
||||||
|
length
|
||||||
|
mapAttrs
|
||||||
|
mapAttrsToList
|
||||||
|
optional
|
||||||
|
throwIf
|
||||||
|
unique
|
||||||
|
;
|
||||||
|
|
||||||
|
allBoxDefinitions = flatten (
|
||||||
|
flip map (attrValues nixosConfigurations) (
|
||||||
|
hostCfg:
|
||||||
|
flip map (attrValues hostCfg.config.services.restic.backups) (
|
||||||
|
backupCfg:
|
||||||
|
optional backupCfg.hetznerStorageBox.enable backupCfg.hetznerStorageBox
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
subUserFor = box: "${box.mainUser}-sub${toString box.subUid}";
|
||||||
|
boxesBySubuser = groupBy subUserFor allBoxDefinitions;
|
||||||
|
|
||||||
|
# We need to know the main storage box user to create subusers
|
||||||
|
boxSubuserToMainUser =
|
||||||
|
flip mapAttrs boxesBySubuser (_: boxes:
|
||||||
|
head (unique (flip map boxes (box: box.mainUser))));
|
||||||
|
|
||||||
|
boxSubuserToPrivateKeys =
|
||||||
|
flip mapAttrs boxesBySubuser (_: boxes:
|
||||||
|
unique (flip map boxes (box: box.sshPrivateKeyFile)));
|
||||||
|
|
||||||
|
# Any subuid that has more than one path in use
|
||||||
|
boxSubuserToPaths =
|
||||||
|
flip mapAttrs boxesBySubuser (_: boxes:
|
||||||
|
unique (flip map boxes (box: box.path)));
|
||||||
|
|
||||||
|
duplicates = filterAttrs (_: boxes: length boxes > 1) boxSubuserToPaths;
|
||||||
|
|
||||||
|
# Only one path must remain per subuser.
|
||||||
|
boxSubuserToPath = throwIf (duplicates != {}) ''
|
||||||
|
At least one storage box subuser has multiple paths assigned to it:
|
||||||
|
${concatStringsSep "\n" (mapAttrsToList (n: v: "${n}: ${toString v}") duplicates)}
|
||||||
|
'' (mapAttrs (_: head) boxSubuserToPaths);
|
||||||
|
|
||||||
|
appendPubkey = privateKey: ''
|
||||||
|
PATH="$PATH:${pkgs.age-plugin-yubikey}/bin" ${pkgs.rage}/bin/rage -d -i ${decryptIdentity} ${escapeShellArg privateKey} \
|
||||||
|
| (exec 3<&0; ssh-keygen -f /proc/self/fd/3 -y) \
|
||||||
|
>> "$TMPFILE"
|
||||||
|
'';
|
||||||
|
|
||||||
|
setupSubuser = subuser: privateKeys: let
|
||||||
|
mainUser = boxSubuserToMainUser.${subuser};
|
||||||
|
path = boxSubuserToPath.${subuser};
|
||||||
|
in ''
|
||||||
|
echo "${mainUser} (for ${subuser}): Removing old ${path}/.ssh if it exists"
|
||||||
|
# Remove any .ssh folder if it exists
|
||||||
|
${pkgs.openssh}/bin/ssh -p 23 "${mainUser}@${mainUser}.your-storagebox.de" -- rm -r ./${path}/.ssh &>/dev/null || true
|
||||||
|
echo "${mainUser} (for ${subuser}): Creating ${path}/.ssh"
|
||||||
|
# Create subuser directory and .ssh
|
||||||
|
${pkgs.openssh}/bin/ssh -p 23 "${mainUser}@${mainUser}.your-storagebox.de" -- mkdir -p ./${path}/.ssh
|
||||||
|
|
||||||
|
# Derive and upload all authorized keys
|
||||||
|
TMPFILE=$(mktemp)
|
||||||
|
${concatLines (map appendPubkey privateKeys)}
|
||||||
|
echo "${mainUser} (for ${subuser}): Uploading $(wc -l < "$TMPFILE") authorized_keys"
|
||||||
|
${pkgs.openssh}/bin/scp -P 23 "$TMPFILE" "${mainUser}@${mainUser}.your-storagebox.de":./${path}/.ssh/authorized_keys
|
||||||
|
rm "$TMPFILE"
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
type = "app";
|
||||||
|
program = getExe (pkgs.writeShellApplication {
|
||||||
|
name = "setup-hetzner-storage-boxes";
|
||||||
|
text = ''
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
${concatLines (mapAttrsToList setupSubuser boxSubuserToPrivateKeys)}
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "[33mPlease visit https://robot.hetzner.com/storage and make sure"
|
||||||
|
echo "that the following subusers are setup correctly:[m"
|
||||||
|
${concatLines (mapAttrsToList (u: p: "echo '[33m ${u}: ${p}[m'") boxSubuserToPath)}
|
||||||
|
'';
|
||||||
|
});
|
||||||
|
}
|
|
@ -7,6 +7,7 @@
|
||||||
./interface-naming.nix
|
./interface-naming.nix
|
||||||
./nginx.nix
|
./nginx.nix
|
||||||
./node.nix
|
./node.nix
|
||||||
|
./restic.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
nixpkgs.overlays = [
|
nixpkgs.overlays = [
|
||||||
|
|
56
modules/restic.nix
Normal file
56
modules/restic.nix
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
{lib, ...}: let
|
||||||
|
inherit
|
||||||
|
(lib)
|
||||||
|
mkEnableOption
|
||||||
|
mkIf
|
||||||
|
mkOption
|
||||||
|
types
|
||||||
|
;
|
||||||
|
in {
|
||||||
|
options.services.restic.backups = mkOption {
|
||||||
|
type = types.attrsOf (types.submodule ({config, ...}: {
|
||||||
|
options.hetznerStorageBox = {
|
||||||
|
enable = mkEnableOption "Automatically configure this backup to use the given hetzner storage box. Will use SFTP via SSH.";
|
||||||
|
|
||||||
|
mainUser = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = ''
|
||||||
|
The main user. While not technically required for restic, we still use it to
|
||||||
|
derive the subuser name and it is required for the automatic setup script
|
||||||
|
that creates the users.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
subUid = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
description = "The id of the subuser that was allocated on the hetzner server for this backup.";
|
||||||
|
};
|
||||||
|
|
||||||
|
path = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = ''
|
||||||
|
The remote path to backup into. While not technically required for restic
|
||||||
|
(since the subuser is chrooted on the remote), it is required for the
|
||||||
|
automatic setup script that creates the users.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
sshPrivateKeyFile = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
description = "The path to the ssh private key to use for uploading backups. Don't use a path from the nix store!";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = let
|
||||||
|
subuser = "${config.hetznerStorageBox.mainUser}-sub${toString config.hetznerStorageBox.subUid}";
|
||||||
|
url = "${subuser}@${subuser}.your-storagebox.de";
|
||||||
|
in
|
||||||
|
mkIf config.hetznerStorageBox.enable {
|
||||||
|
repository = "sftp://${url}:23/";
|
||||||
|
extraOptions = [
|
||||||
|
"sftp.command='ssh -s sftp -p 23 -i ${config.hetznerStorageBox.sshPrivateKeyFile} ${url}'"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue