Files
cc-ci/modules/backupbot.nix
autonomic-bot 451cca3ebd
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
fix: set_env newline-safe — RESTIC_REPOSITORY was glued onto a comment line (backups broke)
backup-bot-two's .env.sample ends with a newline-less comment, so set_env's bare
append concatenated RESTIC_REPOSITORY onto it (commenting it out). The backupbot
container then lacked RESTIC_REPOSITORY and 'abra app backup create' KeyError'd —
breaking the backup stage for recipes without a custom backup hook (cryptpad).
set_env now ensures a trailing newline before appending (applied to drone.nix too,
same latent bug). Re-verify keycloak backup, which earlier passed off an older deploy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 04:50:16 +01:00

54 lines
2.4 KiB
Nix

# backup-bot-two (M5): the Co-op Cloud backup service. `abra app backup create <app>` / restore
# talk to it; it snapshots volumes labelled `backupbot.backup=true` into a local restic repo.
# Idempotent-reconcile oneshot (same pattern as proxy/drone). restic_password is abra-generated
# (class-B-style internal secret) and kept stable across reconciles (only generated if missing).
{ pkgs, ... }:
let
reconcile = pkgs.writeShellApplication {
name = "cc-ci-reconcile-backupbot";
runtimeInputs = with pkgs; [ abra docker gnused gnugrep coreutils git ];
text = ''
DOMAIN="backups.ci.commoninternet.net" # identity/stack name only; no web route
ENV_FILE="$HOME/.abra/servers/default/$DOMAIN.env"
abra server ls -m -n >/dev/null 2>&1 || abra server add --local -n || true
abra recipe fetch backup-bot-two -n >/dev/null
[ -f "$ENV_FILE" ] || abra app new backup-bot-two -s default -D "$DOMAIN" -n
set_env() {
sed -i -E "/^[[:space:]]*#?[[:space:]]*$1=/d" "$ENV_FILE"
# Ensure the file ends in a newline before appending backup-bot-two's .env.sample ends
# with a newline-less comment line, so a bare append would glue the var onto that comment
# (commenting it out). `$(tail -c1)` is empty iff the last byte is already a newline.
if [ -s "$ENV_FILE" ] && [ -n "$(tail -c1 "$ENV_FILE")" ]; then printf '\n' >> "$ENV_FILE"; fi
printf '%s=%s\n' "$1" "$2" >> "$ENV_FILE"
}
set_env RESTIC_REPOSITORY /backups/restic
set_env SECRET_RESTIC_PASSWORD_VERSION v1
set_env CRONJOB_VERSION v1
have_secret() { docker secret ls --format '{{.Name}}' | grep -q "_$1_v1$"; }
# -m avoids the TTY/table (ioctl) path; redirect stdout so generated values never hit logs (D6).
have_secret restic_password || abra app secret generate "$DOMAIN" --all -m -n >/dev/null
abra app deploy "$DOMAIN" -n -C
'';
};
in
{
systemd.services.deploy-backupbot = {
description = "Reconcile backup-bot-two (volume backups via restic) via abra";
after = [ "swarm-init.service" "docker.service" "network-online.target" ];
requires = [ "swarm-init.service" "docker.service" ];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
environment.HOME = "/root";
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = "${reconcile}/bin/cc-ci-reconcile-backupbot";
};
};
}