backup stuff
This commit is contained in:
parent
01b83a80ec
commit
27e036eccd
16 changed files with 513 additions and 0 deletions
69
ansible/roles/backup_source/templates/backup.sh.j2
Normal file
69
ansible/roles/backup_source/templates/backup.sh.j2
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/env bash
|
||||
# {{ backup_source_description }} backup — managed by Ansible (roles/backup_source)
|
||||
#
|
||||
# Dumps to stdout, encrypts with age, writes {{ backup_source_dir }}.
|
||||
# The host holds only the age PUBLIC key, so it cannot read its own backups.
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
|
||||
BACKUP_DIR="{{ backup_source_dir }}"
|
||||
RETENTION_DAYS={{ backup_source_retention_days }}
|
||||
RECIPIENT="{{ backup_source_recipient }}"
|
||||
SUFFIX="{{ backup_source_artifact_suffix }}"
|
||||
NAME="{{ backup_source_name }}"
|
||||
{% if backup_source_stop_service %}
|
||||
SERVICE="{{ backup_source_stop_service }}"
|
||||
{% endif %}
|
||||
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
ARTIFACT="${BACKUP_DIR}/${NAME}_${TIMESTAMP}.${SUFFIX}"
|
||||
|
||||
die() { echo "FATAL: $*" >&2; exit 1; }
|
||||
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
||||
|
||||
# --- Pre-flight ---
|
||||
[[ -n "$RECIPIENT" ]] || die "no age recipient configured"
|
||||
command -v age >/dev/null || die "age is not installed"
|
||||
|
||||
# Mode must agree with what the role sets, or each undoes the other every run.
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
{% if backup_source_pull_user %}
|
||||
chown root:{{ backup_source_pull_user }} "$BACKUP_DIR"
|
||||
chmod 750 "$BACKUP_DIR"
|
||||
{% else %}
|
||||
chmod 700 "$BACKUP_DIR"
|
||||
{% endif %}
|
||||
|
||||
# A run that died mid-dump leaves a .partial. It is not a backup, and the prune
|
||||
# glob below cannot match it (it ends .partial, not .${SUFFIX}), so clear them
|
||||
# here or they accumulate forever.
|
||||
rm -f "${BACKUP_DIR}/${NAME}_"*.partial
|
||||
|
||||
{% if backup_source_stop_service %}
|
||||
# --- Stop the service, and guarantee it comes back ---
|
||||
# The trap is the point: without it a failed dump leaves the service down until
|
||||
# the next timer fires. Every hand-written script this replaced had that bug.
|
||||
log "Stopping ${SERVICE}..."
|
||||
systemctl stop "$SERVICE"
|
||||
trap 'log "Restarting ${SERVICE}..."; systemctl start "${SERVICE}" || true' EXIT
|
||||
{% endif %}
|
||||
|
||||
# --- Dump straight into age; plaintext never touches the disk ---
|
||||
log "Writing ${ARTIFACT}..."
|
||||
{{ backup_source_dump_command }} | age -r "$RECIPIENT" -o "${ARTIFACT}.partial"
|
||||
mv "${ARTIFACT}.partial" "$ARTIFACT"
|
||||
{% if backup_source_pull_user %}
|
||||
# Readable by the pull account and nobody else. The contents are age-encrypted
|
||||
# regardless, so this is depth rather than the actual protection.
|
||||
chown root:{{ backup_source_pull_user }} "$ARTIFACT"
|
||||
chmod 640 "$ARTIFACT"
|
||||
{% else %}
|
||||
chmod 600 "$ARTIFACT"
|
||||
{% endif %}
|
||||
log "Wrote ${ARTIFACT} ($(du -h "$ARTIFACT" | cut -f1))"
|
||||
|
||||
# --- Prune ---
|
||||
log "Pruning local artefacts older than ${RETENTION_DAYS} days..."
|
||||
find "$BACKUP_DIR" -maxdepth 1 -type f -name "${NAME}_*.${SUFFIX}" -mtime +"${RETENTION_DAYS}" -delete
|
||||
|
||||
log "Done."
|
||||
Loading…
Add table
Add a link
Reference in a new issue