diff options
| -rw-r--r-- | accounts/root@vidhar.nix | 10 | ||||
| -rw-r--r-- | hosts/vidhar/borg/append.borgbase | 26 | ||||
| -rwxr-xr-x | hosts/vidhar/borg/copy.py | 216 | ||||
| -rw-r--r-- | hosts/vidhar/borg/default.nix | 82 | ||||
| -rw-r--r-- | hosts/vidhar/borg/yggdrasil.borgkey | 26 |
5 files changed, 355 insertions, 5 deletions
diff --git a/accounts/root@vidhar.nix b/accounts/root@vidhar.nix index ad30cffb..0b5fb6ec 100644 --- a/accounts/root@vidhar.nix +++ b/accounts/root@vidhar.nix | |||
| @@ -1,11 +1,17 @@ | |||
| 1 | { userName, ... }: | 1 | { config, userName, ... }: |
| 2 | { | 2 | { |
| 3 | home-manager.users.${userName} = { | 3 | home-manager.users.${userName} = { |
| 4 | programs.ssh.matchBlocks = { | 4 | programs.ssh.matchBlocks = { |
| 5 | "yggdrasil.borgbase" = | 5 | "yggdrasil.borgbase" = |
| 6 | { hostname = "nx69hpl8.repo.borgbase.com"; | 6 | { hostname = "nx69hpl8.repo.borgbase.com"; |
| 7 | user = "nx69hpl8"; | 7 | user = "nx69hpl8"; |
| 8 | identityFile = "~/.ssh/append.borgbase"; | 8 | identityFile = config.sops.secrets."append.borgbase".path; |
| 9 | identitiesOnly = true; | ||
| 10 | serverAliveInterval = 10; | ||
| 11 | serverAliveCountMax = 30; | ||
| 12 | extraOptions = { | ||
| 13 | BatchMode = "yes"; | ||
| 14 | }; | ||
| 9 | }; | 15 | }; |
| 10 | }; | 16 | }; |
| 11 | }; | 17 | }; |
diff --git a/hosts/vidhar/borg/append.borgbase b/hosts/vidhar/borg/append.borgbase new file mode 100644 index 00000000..d7091871 --- /dev/null +++ b/hosts/vidhar/borg/append.borgbase | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | { | ||
| 2 | "data": "ENC[AES256_GCM,data:+TKVqaaf8MYqzLgJ4DFYg6CaGmrSkNlGZiK3PNLusVwZhxAk19Xd/ouFEpI4+Gsecah9kyDZgMRM7x4bXz6RhhR9cEyPbm9lIYQz6OGoqW/xMIui1+DwCIfrtByXZ1SQKLz4052+fUwQvGgdG2/i9twF1SPUUA0NxNKyqjVfZpAVia8aJkl7Zu74UzjUPAqaORYAoZcZRVcUDH1cUQpDMzUza3SsDv9/FbnG/ZIify8sKVXaNsNjmuKQ1WEshcSLNcQQzvQIM0jCwpN8KE9C4mj0ANf3JGehoP39ticvzZl2iOCfBwNuL4feWUepBXuRxtn4nbv8OxmQkdJrqR4VwDzgbP3XzpI5zraLIMH093BIQSLbuNXPOHVDh4QxpWrj5+IZYq9ZjUHw3zCg66AGFZRMKPs11N9RVnxp/LpZRMRSYNhzh12ik7YVqyGt32MkDXrSdGFw96ae1PsTSe5V1rY1yevnznwY6OtXj20cneT/CMGFyj9DkcZ6ClT5KDYP1xPwR2D1A56cBDgvPeNPfhPWhRHoYLU90eHkF16N9zLrSBBhKtoNUUbB9K46hiSWaTwdQVWNR2ZLafR3,iv:6eDgcQuPi9Z5tVbr1BEVzCRu5IZC6hYfZtXfuwIKaZQ=,tag:wdYKzdGkaKOv3jHd6TzA9w==,type:str]", | ||
| 3 | "sops": { | ||
| 4 | "kms": null, | ||
| 5 | "gcp_kms": null, | ||
| 6 | "azure_kv": null, | ||
| 7 | "hc_vault": null, | ||
| 8 | "age": null, | ||
| 9 | "lastmodified": "2022-02-17T10:32:27Z", | ||
| 10 | "mac": "ENC[AES256_GCM,data:7i7zPalH3J/VtNGzHrECMHt02WTGOuT5KL+HQT6b5zLCpYyTTBit/HM4xW4hONxnEaEgPpkUr0cTNvsGrrKE0zyj784xLDASvaBcTinkmNvkBEVsB9ACPG3G5YmexvYC9Z3tVFjfLPdQeXgy5nAm6Q8TMANgp+xIQTnc0IGVjS8=,iv:u3UL8XrR6UKEDOWgMa4nzEYl8vmguZmcANmby2kgxWM=,tag:2k7+DKVTtmI7ldk/ktXzbQ==,type:str]", | ||
| 11 | "pgp": [ | ||
| 12 | { | ||
| 13 | "created_at": "2022-02-17T10:32:26Z", | ||
| 14 | "enc": "-----BEGIN PGP MESSAGE-----\n\nhF4DbYDvGI0HDr0SAQdArVlDVy8NhAS3QNPoul2iE05B1Su1r8fnmFm4k4+ORX4w\nddhRrT+TAkbPM0Zl1nDyazqJKWlq2DXZ8DZ6qEFAS0bYN0x/QiazIqH4NfWWwa0N\n0l4BunqQtbQNkv0qjqBmkhDnlVNainmEPv4ChXrJc1z6HXYdvv4CkfzwdvEfr5yO\ndpHERmg5O5mX29BnM6LHk6CdFtJS1jN3qZy3oa0KEECoZm8Ak2hlBw/PeEGk3eBM\n=SHc3\n-----END PGP MESSAGE-----\n", | ||
| 15 | "fp": "A1C7C95E6CAF0A965CB47277BCF50A89C1B1F362" | ||
| 16 | }, | ||
| 17 | { | ||
| 18 | "created_at": "2022-02-17T10:32:26Z", | ||
| 19 | "enc": "-----BEGIN PGP MESSAGE-----\n\nhF4DXxoViZlp6dISAQdApIVC5MIiWio4yRZ0hIVWntk2Xk7LKHPwJP2pw73SgREw\nCJZDCT91QEMPDwobh8eq4R8vook9fUJ0t+M5TUN5IwjSwSdmwiQ+mpArmyStIi4W\n0l4BiEt5Giar+H8V62bBiN38EbqHF4jN7jfjt6QrU4Nr/kP7DokI26TvXPqOtOOh\nTGOhiTvroHu4zRGMZKqq/IDE9FLA1SXRU3rXmHtVjz2U2Zmnj0Hj9iR+bZmy+TuY\n=ugF2\n-----END PGP MESSAGE-----\n", | ||
| 20 | "fp": "30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51" | ||
| 21 | } | ||
| 22 | ], | ||
| 23 | "unencrypted_suffix": "_unencrypted", | ||
| 24 | "version": "3.7.1" | ||
| 25 | } | ||
| 26 | } \ No newline at end of file | ||
diff --git a/hosts/vidhar/borg/copy.py b/hosts/vidhar/borg/copy.py new file mode 100755 index 00000000..b99e301a --- /dev/null +++ b/hosts/vidhar/borg/copy.py | |||
| @@ -0,0 +1,216 @@ | |||
| 1 | #!@python@/bin/python | ||
| 2 | |||
| 3 | import json | ||
| 4 | import os | ||
| 5 | import subprocess | ||
| 6 | import re | ||
| 7 | import sys | ||
| 8 | from sys import stderr | ||
| 9 | from humanize import naturalsize | ||
| 10 | |||
| 11 | from tempfile import TemporaryDirectory | ||
| 12 | |||
| 13 | from datetime import (datetime, timedelta) | ||
| 14 | from dateutil.tz import (tzlocal, tzutc) | ||
| 15 | import dateutil.parser | ||
| 16 | import argparse | ||
| 17 | |||
| 18 | from tqdm import tqdm | ||
| 19 | |||
| 20 | from xdg import xdg_runtime_dir | ||
| 21 | import pathlib | ||
| 22 | |||
| 23 | import unshare | ||
| 24 | from time import sleep | ||
| 25 | |||
| 26 | from halo import Halo | ||
| 27 | |||
| 28 | |||
| 29 | parser = argparse.ArgumentParser() | ||
| 30 | parser.add_argument('source', metavar='REPO_OR_ARCHIVE') | ||
| 31 | parser.add_argument('target', metavar='REPO_OR_ARCHIVE') | ||
| 32 | args = parser.parse_args() | ||
| 33 | |||
| 34 | def read_repo(path): | ||
| 35 | with Halo(text=f'Listing {path}', spinner='arc') as sp: | ||
| 36 | res = None | ||
| 37 | with subprocess.Popen(['borg', 'list', '--info', '--lock-wait', '120', '--json', path], stdout=subprocess.PIPE) as proc: | ||
| 38 | res = json.load(proc.stdout)['archives'] | ||
| 39 | sp.succeed(f'{len(res)} archives in {path}') | ||
| 40 | return res | ||
| 41 | |||
| 42 | class ToSync: | ||
| 43 | def __iter__(self): | ||
| 44 | return self | ||
| 45 | |||
| 46 | def __next__(self): | ||
| 47 | while True: | ||
| 48 | try: | ||
| 49 | src = read_repo(args.source) | ||
| 50 | dst = read_repo(args.target) | ||
| 51 | for entry in src: | ||
| 52 | if entry['name'] not in {dst_entry['name'] for dst_entry in dst} and not entry['name'].endswith('.checkpoint'): | ||
| 53 | return entry | ||
| 54 | raise StopIteration | ||
| 55 | except (subprocess.CalledProcessError, json.decoder.JSONDecodeError) as err: | ||
| 56 | print(err, file=stderr) | ||
| 57 | continue | ||
| 58 | |||
| 59 | def copy_archive(src_repo_path, dst_repo_path, entry): | ||
| 60 | cache_suffix = None | ||
| 61 | with Halo(text=f'Determine archive parameters', spinner='arc') as sp: | ||
| 62 | match = re.compile('^(.*)-[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.(checkpoint|recreate)(\.[0-9]+)?)?').fullmatch(entry['name']) | ||
| 63 | if match: | ||
| 64 | repo_id = None | ||
| 65 | with subprocess.Popen(['borg', 'info', '--info', '--lock-wait', '120', '--json', src_repo_path], stdout=subprocess.PIPE) as proc: | ||
| 66 | repo_id = json.load(proc.stdout)['repository']['id'] | ||
| 67 | if repo_id: | ||
| 68 | cache_suffix = f'{repo_id}_{match.group(1)}' | ||
| 69 | sp.succeed(f'Will process {entry["name"]} ({dateutil.parser.isoparse(entry["start"])}, cache_suffix={cache_suffix})') | ||
| 70 | with TemporaryDirectory(prefix=f'borg-mount_{entry["name"]}_') as tmpdir: | ||
| 71 | child = os.fork() | ||
| 72 | if child == 0: | ||
| 73 | # print('unshare/chroot', file=stderr) | ||
| 74 | unshare.unshare(unshare.CLONE_NEWNS) | ||
| 75 | subprocess.run(['mount', '--make-rprivate', '/'], check=True) | ||
| 76 | chroot = pathlib.Path(tmpdir) / 'chroot' | ||
| 77 | upper = pathlib.Path(tmpdir) / 'upper' | ||
| 78 | work = pathlib.Path(tmpdir) / 'work' | ||
| 79 | for path in [chroot,upper,work]: | ||
| 80 | path.mkdir() | ||
| 81 | subprocess.run(['mount', '-t', 'overlay', 'overlay', '-o', f'lowerdir=/,upperdir={upper},workdir={work}', chroot], check=True) | ||
| 82 | bindMounts = ['nix', 'run', 'proc', 'dev', 'sys', pathlib.Path(os.path.expanduser('~')).relative_to('/')] | ||
| 83 | if not ":" in src_repo_path: | ||
| 84 | bindMounts.append(pathlib.Path(src_repo_path).relative_to('/')) | ||
| 85 | if 'SSH_AUTH_SOCK' in os.environ: | ||
| 86 | bindMounts.append(pathlib.Path(os.environ['SSH_AUTH_SOCK']).parent.relative_to('/')) | ||
| 87 | for bindMount in bindMounts: | ||
| 88 | (chroot / bindMount).mkdir(parents=True,exist_ok=True) | ||
| 89 | subprocess.run(['mount', '--bind', pathlib.Path('/') / bindMount, chroot / bindMount], check=True) | ||
| 90 | os.chroot(chroot) | ||
| 91 | os.chdir('/') | ||
| 92 | dir = pathlib.Path('/borg') | ||
| 93 | dir.mkdir(parents=True,exist_ok=True) | ||
| 94 | with Halo(text=f'Determine size', spinner='arc') as sp: | ||
| 95 | total_size = None | ||
| 96 | total_files = None | ||
| 97 | with subprocess.Popen(['borg', 'info', '--info', '--json', '--lock-wait', '120', f'{src_repo_path}::{entry["name"]}'], stdout=subprocess.PIPE, text=True) as proc: | ||
| 98 | stats = json.load(proc.stdout)['archives'][0]['stats'] | ||
| 99 | total_size = stats['original_size'] | ||
| 100 | total_files = stats['nfiles'] | ||
| 101 | sp.succeed(f'{total_files} files, {naturalsize(total_size, binary=True)}') | ||
| 102 | # print(f'Mounting to {dir}', file=stderr) | ||
| 103 | with subprocess.Popen(['borg', 'mount', '--foreground', '--progress', '--lock-wait', '120', f'{src_repo_path}::{entry["name"]}', dir]) as mount_proc: | ||
| 104 | with Halo(text='Waiting for mount', spinner='arc') as sp: | ||
| 105 | wait_start = datetime.now() | ||
| 106 | while True: | ||
| 107 | ret = subprocess.run(['mountpoint', '-q', dir]) | ||
| 108 | if ret.returncode == 0: | ||
| 109 | break | ||
| 110 | elif datetime.now() - wait_start > timedelta(minutes=10): | ||
| 111 | ret.check_returncode() | ||
| 112 | sleep(0.1) | ||
| 113 | sp.succeed('Mounted') | ||
| 114 | while True: | ||
| 115 | try: | ||
| 116 | with tqdm(total=total_size, unit_scale=True, unit_divisor=1024, unit='B', smoothing=0.01, disable=None, dynamic_ncols=True, maxinterval=0.5, miniters=1) as progress: | ||
| 117 | seen = 0 | ||
| 118 | env = os.environ.copy() | ||
| 119 | create_args = ['borg', | ||
| 120 | 'create', | ||
| 121 | '--lock-wait=120', | ||
| 122 | '--one-file-system', | ||
| 123 | '--compression=auto,zstd,10', | ||
| 124 | '--chunker-params=10,23,16,4095', | ||
| 125 | '--files-cache=ctime,size', | ||
| 126 | '--show-rc', | ||
| 127 | # '--remote-ratelimit=20480', | ||
| 128 | '--log-json', | ||
| 129 | '--progress', | ||
| 130 | '--list', | ||
| 131 | '--filter=AMEi-x?', | ||
| 132 | '--stats' | ||
| 133 | ] | ||
| 134 | archive_time = datetime.strptime(entry["time"], "%Y-%m-%dT%H:%M:%S.%f").replace(tzinfo=tzlocal()).astimezone(tzutc()) | ||
| 135 | create_args += [f'--timestamp={archive_time.strftime("%Y-%m-%dT%H:%M:%S")}'] | ||
| 136 | if cache_suffix: | ||
| 137 | env['BORG_FILES_CACHE_SUFFIX'] = cache_suffix | ||
| 138 | else: | ||
| 139 | create_args += ['--files-cache=disabled'] | ||
| 140 | create_args += [f'{dst_repo_path}::{entry["name"]}', '.'] | ||
| 141 | with subprocess.Popen(create_args, cwd=dir, stdin=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True, env=env) as proc: | ||
| 142 | last_list = None | ||
| 143 | last_list_time = None | ||
| 144 | for line in proc.stderr: | ||
| 145 | try: | ||
| 146 | json_line = json.loads(line) | ||
| 147 | except json.decoder.JSONDecodeError: | ||
| 148 | tqdm.write(line) | ||
| 149 | continue | ||
| 150 | |||
| 151 | t = '' | ||
| 152 | if 'time' in json_line: | ||
| 153 | ts = datetime.fromtimestamp(json_line['time']).replace(tzinfo=tzlocal()) | ||
| 154 | t = f'{ts.isoformat(timespec="minutes")} ' | ||
| 155 | if json_line['type'] == 'archive_progress': | ||
| 156 | if last_list_time is None or ((datetime.now() - last_list_time) // timedelta(seconds=3)) % 2 == 1: | ||
| 157 | if 'path' in json_line and json_line['path']: | ||
| 158 | progress.set_description(f'… {json_line["path"]}', refresh=False) | ||
| 159 | else: | ||
| 160 | progress.set_description(None, refresh=False) | ||
| 161 | elif last_list is not None: | ||
| 162 | progress.set_description(last_list, refresh=False) | ||
| 163 | progress.set_postfix(compressed=naturalsize(json_line['compressed_size'], binary=True), deduplicated=naturalsize(json_line['deduplicated_size'], binary=True), nfiles=f'{json_line["nfiles"]}/{total_files}', refresh=False) | ||
| 164 | progress.update(json_line["original_size"] - seen) | ||
| 165 | seen = json_line["original_size"] | ||
| 166 | elif json_line['type'] == 'file_status': | ||
| 167 | # tqdm.write(t + f'{json_line["status"]} {json_line["path"]}') | ||
| 168 | last_list = f'{json_line["status"]} {json_line["path"]}' | ||
| 169 | last_list_time = datetime.now() | ||
| 170 | progress.set_description(last_list, refresh=False) | ||
| 171 | elif (json_line['type'] == 'log_message' or json_line['type'] == 'progress_message' or json_line['type'] == 'progress_percent') and ('message' in json_line or 'msgid' in json_line): | ||
| 172 | if 'message' in json_line: | ||
| 173 | tqdm.write(t + json_line['message']) | ||
| 174 | elif 'msgid' in json_line: | ||
| 175 | tqdm.write(t + json_line['msgid']) | ||
| 176 | else: | ||
| 177 | tqdm.write(t + line) | ||
| 178 | progress.set_description(None) | ||
| 179 | if proc.wait() != 0: | ||
| 180 | continue | ||
| 181 | except subprocess.CalledProcessError as err: | ||
| 182 | print(err, file=stderr) | ||
| 183 | continue | ||
| 184 | else: | ||
| 185 | break | ||
| 186 | mount_proc.terminate() | ||
| 187 | os._exit(0) | ||
| 188 | else: | ||
| 189 | while True: | ||
| 190 | waitpid, waitret = os.wait() | ||
| 191 | if waitret != 0: | ||
| 192 | sys.exit(waitret) | ||
| 193 | if waitpid == child: | ||
| 194 | break | ||
| 195 | |||
| 196 | def main(): | ||
| 197 | if "::" in args.source: | ||
| 198 | (src_repo_path, _, src_archive) = args.source.partition("::") | ||
| 199 | entry = None | ||
| 200 | for candidate_entry in read_repo(src_repo_path): | ||
| 201 | if entry['name'] != src_archive: | ||
| 202 | continue | ||
| 203 | entry = candidate_entry | ||
| 204 | break | ||
| 205 | |||
| 206 | if entry is None: | ||
| 207 | print("Did not find archive", file=stderr) | ||
| 208 | os.exit(1) | ||
| 209 | |||
| 210 | copy_archive(src_repo_path, args.target, entry) | ||
| 211 | else: | ||
| 212 | for entry in ToSync(): | ||
| 213 | copy_archive(args.source, args.target, entry) | ||
| 214 | |||
| 215 | if __name__ == "__main__": | ||
| 216 | sys.exit(main()) | ||
diff --git a/hosts/vidhar/borg/default.nix b/hosts/vidhar/borg/default.nix index ee5856c9..65c309da 100644 --- a/hosts/vidhar/borg/default.nix +++ b/hosts/vidhar/borg/default.nix | |||
| @@ -1,15 +1,72 @@ | |||
| 1 | { pkgs, lib, ... }: | 1 | { config, pkgs, lib, ... }: |
| 2 | 2 | ||
| 3 | with lib; | 3 | with lib; |
| 4 | 4 | ||
| 5 | { | 5 | let |
| 6 | copyService = { repo, repoEscaped }: let | ||
| 7 | serviceName = "copy-borg@${repoEscaped}"; | ||
| 8 | sshConfig = pkgs.writeText "config" '' | ||
| 9 | Host yggdrasil.borgbase | ||
| 10 | HostName nx69hpl8.repo.borgbase.com | ||
| 11 | User nx69hpl8 | ||
| 12 | IdentityFile /run/credentials/${serviceName}.service/ssh-identity | ||
| 13 | IdentitiesOnly yes | ||
| 14 | |||
| 15 | BatchMode yes | ||
| 16 | ServerAliveInterval 10 | ||
| 17 | ServerAliveCountMax 30 | ||
| 18 | ''; | ||
| 19 | in nameValuePair serviceName { | ||
| 20 | serviceConfig = { | ||
| 21 | Type = "oneshot"; | ||
| 22 | ExecStart = "${copyBorg}/bin/copy ${escapeShellArg repo} yggdrasil.borgbase:repo"; | ||
| 23 | User = "borg"; | ||
| 24 | Group = "borg"; | ||
| 25 | StateDirectory = "borg"; | ||
| 26 | Environment = [ | ||
| 27 | "BORG_RSH=\"${pkgs.openssh}/bin/ssh -F ${sshConfig}\"" | ||
| 28 | "BORG_CACHE_DIR=/var/lib/borg/cache" | ||
| 29 | "BORG_SECURITY_DIR=/var/lib/borg/security" | ||
| 30 | "BORG_KEYS_DIR=/var/lib/borg/keys" | ||
| 31 | "BORG_KEY_FILE=/run/credentials/${serviceName}.service/keyfile" | ||
| 32 | ]; | ||
| 33 | LoadCredential = [ | ||
| 34 | "ssh-identity:${config.sops.secrets."append.borgbase".path}" | ||
| 35 | "keyfile:${config.sops.secrets."yggdrasil.borgkey".path}" | ||
| 36 | ]; | ||
| 37 | }; | ||
| 38 | }; | ||
| 39 | |||
| 40 | copyBorg = pkgs.stdenv.mkDerivation rec { | ||
| 41 | name = "copy"; | ||
| 42 | src = ./copy.py; | ||
| 43 | |||
| 44 | phases = ["buildPhase" "checkPhase" "installPhase"]; | ||
| 45 | |||
| 46 | python = pkgs.python39.withPackages (ps: with ps; [humanize tqdm dateutil xdg python-unshare halo]); | ||
| 47 | |||
| 48 | buildPhase = '' | ||
| 49 | substituteAll $src copy | ||
| 50 | ''; | ||
| 51 | |||
| 52 | doCheck = true; | ||
| 53 | checkPhase = '' | ||
| 54 | ${python}/bin/python -m py_compile copy | ||
| 55 | ''; | ||
| 56 | |||
| 57 | installPhase = '' | ||
| 58 | install -m 0755 -D -t $out/bin \ | ||
| 59 | copy | ||
| 60 | ''; | ||
| 61 | }; | ||
| 62 | in { | ||
| 6 | config = { | 63 | config = { |
| 7 | services.borgbackup.repos.jotnar = { | 64 | services.borgbackup.repos.jotnar = { |
| 8 | path = "/srv/backup/borg/jotnar"; | 65 | path = "/srv/backup/borg/jotnar"; |
| 9 | authorizedKeysAppendOnly = let | 66 | authorizedKeysAppendOnly = let |
| 10 | dir = ./jotnar; | 67 | dir = ./jotnar; |
| 11 | toAuthKey = fname: ftype: if ftype != "regular" || !(hasSuffix ".pub" fname) then null else builtins.readFile (dir + "/${fname}"); | 68 | toAuthKey = fname: ftype: if ftype != "regular" || !(hasSuffix ".pub" fname) then null else builtins.readFile (dir + "/${fname}"); |
| 12 | in filter (v: v != null) (lib.mapAttrsToList toAuthKey (builtins.readDir dir)); | 69 | in filter (v: v != null) (mapAttrsToList toAuthKey (builtins.readDir dir)); |
| 13 | }; | 70 | }; |
| 14 | 71 | ||
| 15 | boot.postBootCommands = mkBefore '' | 72 | boot.postBootCommands = mkBefore '' |
| @@ -25,5 +82,24 @@ with lib; | |||
| 25 | 82 | ||
| 26 | Match All | 83 | Match All |
| 27 | ''; | 84 | ''; |
| 85 | |||
| 86 | sops.secrets."append.borgbase" = { | ||
| 87 | format = "binary"; | ||
| 88 | sopsFile = ./append.borgbase; | ||
| 89 | }; | ||
| 90 | sops.secrets."yggdrasil.borgkey" = { | ||
| 91 | format = "binary"; | ||
| 92 | sopsFile = ./yggdrasil.borgkey; | ||
| 93 | }; | ||
| 94 | |||
| 95 | systemd.services = listToAttrs (map copyService [{ repo = "/srv/backup/borg/jotnar"; repoEscaped = "srv-backup-borg-jotnar"; }]); | ||
| 96 | |||
| 97 | # systemd.timers."copy-borg@srv-backup-borg-jotnar" = { | ||
| 98 | # wantedBy = ["multi-user.target"]; | ||
| 99 | |||
| 100 | # timerConfig = { | ||
| 101 | # OnCalendar = "*-*-* 00/4:00:00 Europe/Berlin"; | ||
| 102 | # }; | ||
| 103 | # }; | ||
| 28 | }; | 104 | }; |
| 29 | } | 105 | } |
diff --git a/hosts/vidhar/borg/yggdrasil.borgkey b/hosts/vidhar/borg/yggdrasil.borgkey new file mode 100644 index 00000000..3540792b --- /dev/null +++ b/hosts/vidhar/borg/yggdrasil.borgkey | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | { | ||
| 2 | "data": "ENC[AES256_GCM,data:/iyvIA5kkXfeyOb7tVakXHI0UN36GQCB7iyPmC9TsTecYWWo9FM4H3jYI10s7NV0ljysnV9IlPqjGGRIbZJ5M9uZluXOmir5uxPZXO7mn6ZWTPKux6OWkWzLOzgebXBx/wqt3y7EwvPg4wKlIC4+xN5Qyn1YT0RuviMNSB7yVRFn5J6OsE3fVssGw5KB7cw3169JhNpNn8LR6zR/3kai5A7FeFxgjvehDwi/t/gIMjWMsfMgdimILVLyTmE1Q2CKHYIZH69/V3/lzThoxNz3EGgj+XTs116DTZaE0nRadvbUfT+mh0CMV/1RKhfaRZ5qbLWDJdihxcKse4f/k0264upzcnwnbDVz3CYgzTaby9vcMra1/Heb6s0lEV8dyXQUiaBsO6CYnTsNC0ogmwIqINZfGiJOQLBIk4ja5dj60UcO9uKYXr3ZkGYuaVowpWOmgpAfy3PE2eLIj1ZTKAE2Llw2vQFUG9X4i9YI2EHjXCnars88Uoa16Hb6/zbypMh3IKw/Yor/3CKkTIdtiuGZ10/09qlIHnfVjy1tL9lPS92vvoDPA2o1z0utrd8qEvsvS4JiZvz9x3JWUKZN2/DQedarzg53FiFhuvSc4BRtGD5Dqo/2kHGDzzL1BmBVrBfiYtzQmMaXmyOe4WvnK/umujVP7LwhrQheER0Jsg8atQmiPGp4fUQEpo9TPuHx0DRnpEpfVSVXqROGF6Q3KiV492hUGb0JISgfUFpN2ZhvhQS2lgaBGnwfb1CJGgkSKhMQIrvWWVqvwHYE90vIGNZLnsSKedHXxy+jbJqyU4uCKVaRRdXOBVtcFOeNekQO3Ayl0YQTZqi7U174h/6oS2ECePUsKWyYXGiQoNKjyd+edG//IIIFvBgjqBeMmsdzGlxxJE47gMRefSXwk8qk/QZG320uiJtz+G0jVpUUrGwL4ujPXwi1Nj099Oi0Q3SJJ1qvZy4kekvFaoyRlTmXHziupyWKhgnTPj6DCWqI03ORvB5m4y5eWdg90yuj+FyT3FSuIJrbbk8r8HaiirCXEfDE/oTK2A4OCfxph/ebi+WrDQYEXNSCbAMmVQAhSINs,iv:VPlUsquBSSxWcQFQG9Eb/kN/VCRp1ULs2Mixe/M9dNo=,tag:D8isf1CanHzXSNSJ8EK+5g==,type:str]", | ||
| 3 | "sops": { | ||
| 4 | "kms": null, | ||
| 5 | "gcp_kms": null, | ||
| 6 | "azure_kv": null, | ||
| 7 | "hc_vault": null, | ||
| 8 | "age": null, | ||
| 9 | "lastmodified": "2022-02-17T11:07:24Z", | ||
| 10 | "mac": "ENC[AES256_GCM,data:5dmDZTT0+xwtUMLRHxQ8O8pviyzZOtcZXufdRkpbQrCImhk1B4eSm2gaT8GavJYswu3I/Z7Yt6BNeiKkccf/PXWAFsOn7L6R2B52X5TdgUD49HXiLcu9V5Sy2/YDqlCcC1IpxwylilxypP1ht/M19VdPl/vFClQTwsQcwpBujtE=,iv:u90ozqlzOnvp0ly/x1hZAnR67XPo5pWGSvPSbzI5eA4=,tag:WKc64wNitiU/x0Baugky9w==,type:str]", | ||
| 11 | "pgp": [ | ||
| 12 | { | ||
| 13 | "created_at": "2022-02-17T11:07:24Z", | ||
| 14 | "enc": "-----BEGIN PGP MESSAGE-----\n\nhF4DbYDvGI0HDr0SAQdAnvb/5Kls/HsLN0dWxkew1E9ppPKI3IDS8fHUun+emnYw\nNJ4XjE2VbM2sdPaAsdeEtmONof8r8k0EEmvV8YFb2iH4EvuwB/LE3sb5Ldjp2QHm\n0l4BS/e7YzESnua/yHA26caeRaqBBbD8mXpKjTaA40v9mbOkpcQpqqP62WO1ox6J\nXLBuV7O1gGjaoWfN/xjkzB2PVsAs5WeTBelMQc0M0/RmlPgOQmOD19SWQop+4npR\n=qLw1\n-----END PGP MESSAGE-----\n", | ||
| 15 | "fp": "A1C7C95E6CAF0A965CB47277BCF50A89C1B1F362" | ||
| 16 | }, | ||
| 17 | { | ||
| 18 | "created_at": "2022-02-17T11:07:24Z", | ||
| 19 | "enc": "-----BEGIN PGP MESSAGE-----\n\nhF4DXxoViZlp6dISAQdACICrWK61VYsHz08d5cUN4S0zOsmas6/KMs9Eok2+hyAw\n5H5cWdUMtKXCG70Cws3pP9Xq0fRrAQ4ta+HBd38w+fDhm/y4HQPcdv7T7ekcEMHH\n0l4BDO10UfkHAiVrhp5jbpdolkH/0uOb90tZPvN1RGJkDoyJjqp5XTn13c9kfsFg\ni5txaJPTp7XvIBiLLwhmb2z3a1XCDjd1qS2hiaD9c7+fxcanU5a9QwlT5ANnzm/X\n=/xps\n-----END PGP MESSAGE-----\n", | ||
| 20 | "fp": "30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51" | ||
| 21 | } | ||
| 22 | ], | ||
| 23 | "unencrypted_suffix": "_unencrypted", | ||
| 24 | "version": "3.7.1" | ||
| 25 | } | ||
| 26 | } \ No newline at end of file | ||
