-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsetup-hetzner-storage-boxes.nix
108 lines (96 loc) · 3.58 KB
/
setup-hetzner-storage-boxes.nix
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
{
pkgs,
nixosConfigurations,
decryptIdentity,
}: let
inherit
(pkgs.lib)
attrValues
concatLines
concatStringsSep
escapeShellArg
filterAttrs
flatten
flip
forEach
getExe
groupBy
head
length
mapAttrs
mapAttrsToList
optional
throwIf
unique
;
allBoxDefinitions = flatten (
forEach (attrValues nixosConfigurations) (
hostCfg:
forEach (attrValues hostCfg.config.services.restic.backups) (
backupCfg:
optional backupCfg.hetznerStorageBox.enable (
backupCfg.hetznerStorageBox
// {sshPrivateKeyFile = hostCfg.config.age.secrets.${backupCfg.hetznerStorageBox.sshAgeSecret}.rekeyFile;}
)
)
)
);
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 (forEach boxes (box: box.mainUser))));
boxSubuserToPrivateKeys =
flip mapAttrs boxesBySubuser (_: boxes:
unique (forEach boxes (box: box.sshPrivateKeyFile)));
# Any subuid that has more than one path in use
boxSubuserToPaths =
flip mapAttrs boxesBySubuser (_: boxes:
unique (forEach 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);
authorizeResticCommand = privateKey: ''
(
echo -n 'command="rclone serve restic --stdio --append-only ./repo" '
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
# Create repo directory
${pkgs.openssh}/bin/ssh -p 23 "${mainUser}@${mainUser}.your-storagebox.de" -- mkdir -p ./${path}/repo
# Derive and upload all authorized keys
TMPFILE=$(mktemp)
${concatLines (map authorizeResticCommand 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)}
'';
});
}