# backup-bot-two (M5): the Co-op Cloud backup service. `abra app backup create ` / 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"; # Serialized last (chain proxy→drone→bridge→dashboard→backupbot) to avoid the concurrent abra-init # race on a fresh host (see bridge.nix). Ordering-only; transitively after deploy-proxy. after = [ "deploy-dashboard.service" "deploy-proxy.service" "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"; }; }; }