Adds a prioritized security-hardening checklist, a PostgreSQL logical-backup script (pg-backup.sh) with a documented restore procedure, and Grafana alerting provisioning (peer-down, flap-storm, RPKI-invalid, router-down rules plus a contact-point template). The alerting YAML and contact points need operator review before being relied on for paging. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
106 lines
4.2 KiB
Bash
Executable File
106 lines
4.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# pg-backup.sh — logical backup of the OpenBMP PostgreSQL database.
|
|
#
|
|
# Performs a `pg_dump` of the `openbmp` database inside the obmp-psql
|
|
# container, writes a timestamped compressed dump to a backup directory,
|
|
# and prunes dumps older than the configured retention.
|
|
#
|
|
# Usage:
|
|
# ./pg-backup.sh
|
|
#
|
|
# Configuration (environment variables, all optional):
|
|
# OBMP_DATA_ROOT Base data dir. Default: /var/openbmp
|
|
# Backups go to ${OBMP_DATA_ROOT}/backups unless
|
|
# OBMP_BACKUP_DIR is set.
|
|
# OBMP_BACKUP_DIR Explicit backup directory. Overrides the default.
|
|
# OBMP_PG_CONTAINER Postgres container name. Default: obmp-psql
|
|
# OBMP_PG_DB Database name. Default: openbmp
|
|
# OBMP_PG_USER Database user. Default: openbmp
|
|
# OBMP_BACKUP_RETENTION_DAYS Prune dumps older than N days. Default: 14
|
|
#
|
|
# Output format:
|
|
# pg_dump custom format (-Fc), gzip-level compressed by pg_dump itself.
|
|
# Restore with `pg_restore` — see docs/backup-restore.md.
|
|
#
|
|
# This script is idempotent and safe to run repeatedly. It does not stop
|
|
# the database; pg_dump takes a consistent MVCC snapshot of a live DB.
|
|
#
|
|
# Make it executable once:
|
|
# chmod +x scripts/pg-backup.sh
|
|
#
|
|
# ----------------------------------------------------------------------
|
|
# Scheduling via cron
|
|
# ----------------------------------------------------------------------
|
|
# Run `crontab -e` and add (daily at 02:30, log to a file):
|
|
#
|
|
# 30 2 * * * OBMP_DATA_ROOT=/var/openbmp /home/user/obmp-docker/scripts/pg-backup.sh >> /var/openbmp/backups/pg-backup.log 2>&1
|
|
#
|
|
# The script must be able to reach the Docker daemon, so run it as a user
|
|
# in the `docker` group (or root). For systemd-based hosts a
|
|
# systemd timer is an equally good alternative to cron.
|
|
# ----------------------------------------------------------------------
|
|
|
|
set -euo pipefail
|
|
|
|
# --- Configuration -----------------------------------------------------
|
|
OBMP_DATA_ROOT="${OBMP_DATA_ROOT:-/var/openbmp}"
|
|
BACKUP_DIR="${OBMP_BACKUP_DIR:-${OBMP_DATA_ROOT}/backups}"
|
|
PG_CONTAINER="${OBMP_PG_CONTAINER:-obmp-psql}"
|
|
PG_DB="${OBMP_PG_DB:-openbmp}"
|
|
PG_USER="${OBMP_PG_USER:-openbmp}"
|
|
RETENTION_DAYS="${OBMP_BACKUP_RETENTION_DAYS:-14}"
|
|
|
|
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
|
|
DUMP_NAME="openbmp-${TIMESTAMP}.dump"
|
|
DUMP_PATH="${BACKUP_DIR}/${DUMP_NAME}"
|
|
DUMP_TMP="${DUMP_PATH}.partial"
|
|
|
|
log() { printf '%s [pg-backup] %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*"; }
|
|
fail() { log "ERROR: $*" >&2; exit 1; }
|
|
|
|
# --- Pre-flight checks -------------------------------------------------
|
|
command -v docker >/dev/null 2>&1 || fail "docker command not found in PATH"
|
|
|
|
if ! docker inspect -f '{{.State.Running}}' "${PG_CONTAINER}" 2>/dev/null | grep -q true; then
|
|
fail "container '${PG_CONTAINER}' is not running"
|
|
fi
|
|
|
|
mkdir -p "${BACKUP_DIR}" || fail "cannot create backup directory ${BACKUP_DIR}"
|
|
|
|
# --- Backup ------------------------------------------------------------
|
|
# Write to a .partial file first, then atomically rename on success so a
|
|
# crashed/interrupted run never leaves a truncated dump that looks valid.
|
|
log "starting backup of database '${PG_DB}' from container '${PG_CONTAINER}'"
|
|
|
|
if docker exec "${PG_CONTAINER}" \
|
|
pg_dump -U "${PG_USER}" -d "${PG_DB}" -Fc --no-owner --no-privileges \
|
|
> "${DUMP_TMP}"; then
|
|
mv -f "${DUMP_TMP}" "${DUMP_PATH}"
|
|
else
|
|
rm -f "${DUMP_TMP}"
|
|
fail "pg_dump failed; no backup written"
|
|
fi
|
|
|
|
DUMP_SIZE="$(du -h "${DUMP_PATH}" | cut -f1)"
|
|
log "backup complete: ${DUMP_PATH} (${DUMP_SIZE})"
|
|
|
|
# --- Prune old backups -------------------------------------------------
|
|
# Only prune files matching our own naming pattern, so nothing else in the
|
|
# directory (logs, manual dumps) is touched.
|
|
log "pruning dumps older than ${RETENTION_DAYS} days"
|
|
PRUNED=0
|
|
while IFS= read -r -d '' old; do
|
|
rm -f "${old}"
|
|
log " removed $(basename "${old}")"
|
|
PRUNED=$((PRUNED + 1))
|
|
done < <(find "${BACKUP_DIR}" -maxdepth 1 -type f \
|
|
-name 'openbmp-*.dump' -mtime "+${RETENTION_DAYS}" -print0)
|
|
log "pruned ${PRUNED} old dump(s)"
|
|
|
|
# Also clean up any stale .partial files from previous crashed runs.
|
|
find "${BACKUP_DIR}" -maxdepth 1 -type f -name 'openbmp-*.dump.partial' \
|
|
-mtime +1 -delete 2>/dev/null || true
|
|
|
|
log "done"
|