feat: add restore functionality (#12)
All checks were successful
CD / Check changes (push) Successful in 14s
CD / Create tag (push) Successful in 11s
CD / Build and push (amd64) (push) Successful in 26s
CD / Build and push (arm64) (push) Successful in 1m14s
CI / Build Docker image (pull_request) Successful in 18s
CD / Create manifest (push) Successful in 25s
All checks were successful
CD / Check changes (push) Successful in 14s
CD / Create tag (push) Successful in 11s
CD / Build and push (amd64) (push) Successful in 26s
CD / Build and push (arm64) (push) Successful in 1m14s
CI / Build Docker image (pull_request) Successful in 18s
CD / Create manifest (push) Successful in 25s
Reviewed-on: #12 Co-authored-by: Timo Behrendt <t.behrendt@t00n.de> Co-committed-by: Timo Behrendt <t.behrendt@t00n.de>
This commit was merged in pull request #12.
This commit is contained in:
158
README.md
158
README.md
@@ -1,6 +1,6 @@
|
||||
# BackupSidecar
|
||||
|
||||
BackupSidecar is a lightweight backup solution designed to run as a cron job in Kubernetes. It automates backups using Restic and supports both directory and PostgreSQL database backups. Optional notifications can be sent via Gotify to keep you informed of backup results.
|
||||
BackupSidecar is a lightweight backup and restore solution designed to run as a cron job in Kubernetes. It automates backups and restores using Restic and supports both directory and PostgreSQL database operations. Optional notifications can be sent via Gotify to keep you informed of operation results.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -8,8 +8,9 @@ BackupSidecar is configured through environment variables. Below is a breakdown
|
||||
|
||||
### General Settings
|
||||
|
||||
These variables apply to both directory and PostgreSQL backups.
|
||||
These variables apply to both backup and restore operations.
|
||||
|
||||
- **`OPERATION_MODE`** _(optional)_ - Defines the operation type (`backup` or `restore`). Defaults to `backup`.
|
||||
- **`BACKUP_MODE`** _(optional)_ - Defines the backup type (`directory` or `postgres`). Defaults to `directory`.
|
||||
- **`RESTIC_PASSWORD`** _(required)_ - The encryption password for Restic.
|
||||
- **`RESTIC_REPOSITORY`** _(required)_ - The URI of the Restic repository (e.g., `rest:http://your-rest-server:8000/backup`).
|
||||
@@ -20,23 +21,40 @@ These variables apply to both directory and PostgreSQL backups.
|
||||
- **`GOTIFYTOKEN`** _(required when ENABLE_GOTIFY=true)_ - The API token for Gotify.
|
||||
- **`GOTIFYTOPIC`** _(required when ENABLE_GOTIFY=true)_ - The topic under which backup notifications will be sent.
|
||||
|
||||
### Directory Backup
|
||||
### Directory Operations
|
||||
|
||||
When running in `directory` mode, the following variable must be set:
|
||||
When running in `directory` mode, the following variables must be set:
|
||||
|
||||
**For Backup Operations:**
|
||||
|
||||
- **`SOURCEDIR`** _(required)_ - The path of the directory to be backed up.
|
||||
|
||||
### PostgreSQL Backup
|
||||
**For Restore Operations:**
|
||||
|
||||
- **`RESTOREDIR`** _(required)_ - The path where files should be restored to.
|
||||
- **`RESTORE_SNAPSHOT_ID`** _(optional)_ - The specific snapshot ID to restore (defaults to `latest`).
|
||||
|
||||
### PostgreSQL Operations
|
||||
|
||||
For `postgres` mode, the following database-related variables are required:
|
||||
|
||||
**Common Variables:**
|
||||
|
||||
- **`PGHOST`** _(required)_ - The hostname of the PostgreSQL server.
|
||||
- **`PGDATABASE`** _(required)_ - The name of the database to back up.
|
||||
- **`PGDATABASE`** _(required)_ - The name of the database.
|
||||
- **`PGUSER`** _(required)_ - The PostgreSQL username.
|
||||
- **`PGPORT`** _(optional)_ - The port for PostgreSQL (defaults to `5432`).
|
||||
- **`PGPASSWORD`** _(optional)_ - The password for authentication. Setting this prevents interactive prompts.
|
||||
|
||||
**Backup-Specific Variables:**
|
||||
|
||||
- **`PG_DUMP_ARGS`** _(optional)_ - Additional flags for `pg_dump`.
|
||||
|
||||
**Restore-Specific Variables:**
|
||||
|
||||
- **`RESTORE_SNAPSHOT_ID`** _(optional)_ - The specific snapshot ID to restore (defaults to `latest`).
|
||||
- **`PSQL_ARGS`** _(optional)_ - Additional flags for `psql` (e.g., `--single-transaction`).
|
||||
|
||||
## Dependencies
|
||||
|
||||
Ensure the following commands are available in the container:
|
||||
@@ -44,10 +62,13 @@ Ensure the following commands are available in the container:
|
||||
- `restic`
|
||||
- `curl`
|
||||
- `jq`
|
||||
- `pg_dump` _(only required for `postgres` mode)_
|
||||
- `pg_dump` _(only required for PostgreSQL backup operations)_
|
||||
- `psql` _(only required for PostgreSQL restore operations)_
|
||||
|
||||
## Usage
|
||||
|
||||
### Backup Operations
|
||||
|
||||
Example Kubernetes CronJob manifest for running BackupSidecar as a cron job for directory backups in minimal configuration:
|
||||
|
||||
```yaml
|
||||
@@ -105,19 +126,116 @@ spec:
|
||||
claimName: source-data-pvc
|
||||
```
|
||||
|
||||
### Restore Operations
|
||||
|
||||
Example Kubernetes Job manifest for running BackupSidecar to restore a directory:
|
||||
|
||||
```yaml
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: backupsidecar-restore
|
||||
namespace: authentik
|
||||
spec:
|
||||
backoffLimit: 3
|
||||
activeDeadlineSeconds: 600
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: backupsidecar
|
||||
image: backupsidecar:latest
|
||||
env:
|
||||
- name: OPERATION_MODE
|
||||
value: "restore"
|
||||
- name: BACKUP_MODE
|
||||
value: "directory"
|
||||
- name: RESTOREDIR
|
||||
value: "/data/restore"
|
||||
- name: RESTORE_SNAPSHOT_ID
|
||||
value: "abc123def456" # optional, defaults to latest
|
||||
- name: RESTIC_REPOSITORY
|
||||
value: "rest:http://rest-server:8000/backup"
|
||||
- name: RESTIC_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: backupsidecar-secret
|
||||
key: restic_password
|
||||
- name: GOTIFYHOST
|
||||
value: "http://gotify.example.com"
|
||||
- name: GOTIFYTOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: backupsidecar-secret
|
||||
key: gotify_token
|
||||
- name: GOTIFYTOPIC
|
||||
value: "Restore Notification"
|
||||
volumeMounts:
|
||||
- name: restore-data
|
||||
mountPath: /data/restore
|
||||
volumes:
|
||||
- name: restore-data
|
||||
persistentVolumeClaim:
|
||||
claimName: restore-data-pvc
|
||||
```
|
||||
|
||||
Example Kubernetes Job manifest for running BackupSidecar to restore a PostgreSQL database:
|
||||
|
||||
```yaml
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: backupsidecar-postgres-restore
|
||||
namespace: authentik
|
||||
spec:
|
||||
backoffLimit: 3
|
||||
activeDeadlineSeconds: 600
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: backupsidecar
|
||||
image: backupsidecar:latest
|
||||
env:
|
||||
- name: OPERATION_MODE
|
||||
value: "restore"
|
||||
- name: BACKUP_MODE
|
||||
value: "postgres"
|
||||
- name: PGHOST
|
||||
value: "postgres.example.com"
|
||||
- name: PGDATABASE
|
||||
value: "mydatabase"
|
||||
- name: PGUSER
|
||||
value: "myuser"
|
||||
- name: PGPASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-secret
|
||||
key: password
|
||||
- name: PGPORT
|
||||
value: "5432"
|
||||
- name: RESTORE_SNAPSHOT_ID
|
||||
value: "abc123def456" # optional, defaults to latest
|
||||
- name: PSQL_ARGS
|
||||
value: "--single-transaction" # optional
|
||||
- name: RESTIC_REPOSITORY
|
||||
value: "rest:http://rest-server:8000/backup"
|
||||
- name: RESTIC_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: backupsidecar-secret
|
||||
key: restic_password
|
||||
- name: GOTIFYHOST
|
||||
value: "http://gotify.example.com"
|
||||
- name: GOTIFYTOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: backupsidecar-secret
|
||||
key: gotify_token
|
||||
- name: GOTIFYTOPIC
|
||||
value: "Database Restore Notification"
|
||||
```
|
||||
|
||||
## Notifications
|
||||
|
||||
The script can send success or failure notifications via Gotify when enabled. To enable notifications, set `ENABLE_GOTIFY=true` and provide the required Gotify configuration variables (`GOTIFYHOST`, `GOTIFYTOKEN`, `GOTIFYTOPIC`). When notifications are disabled, backup status messages are still logged to the console.
|
||||
|
||||
Example success notification:
|
||||
|
||||
```
|
||||
Backup successful. Snapshot 56ff6a909a44e01f67d2d88f9a76aa713d437809d7ed14a2361e28893f38befb: files new: 1, files changed: 0, data added: 1019 bytes in 0.277535184 sec
|
||||
```
|
||||
|
||||
When Gotify is disabled, you'll see a single message at startup indicating notifications are disabled, followed by normal backup status messages:
|
||||
|
||||
```
|
||||
2024-01-15T10:30:00 - Gotify notifications disabled. Backup status will be logged to console only.
|
||||
2024-01-15T10:30:05 - Backup successful. Snapshot 56ff6a909a44e01f67d2d88f9a76aa713d437809d7ed14a2361e28893f38befb: files new: 1, files changed: 0, data added: 1019 bytes in 0.277535184 sec
|
||||
```
|
||||
|
||||
154
src/backup.sh
154
src/backup.sh
@@ -15,6 +15,13 @@ log() {
|
||||
echo "$(date +"$LOG_DATE_FORMAT") - $*"
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Determine operation mode from the environment only.
|
||||
# Valid values: "backup" or "restore".
|
||||
# Default to "backup" if not provided.
|
||||
#######################################
|
||||
OPERATION_MODE="${OPERATION_MODE:-backup}"
|
||||
|
||||
#######################################
|
||||
# Determine backup mode from the environment only.
|
||||
# Valid values: "directory" or "postgres".
|
||||
@@ -27,7 +34,11 @@ BACKUP_MODE="${BACKUP_MODE:-directory}"
|
||||
#######################################
|
||||
REQUIRED_CMDS=(restic curl jq)
|
||||
if [ "$BACKUP_MODE" = "postgres" ]; then
|
||||
REQUIRED_CMDS+=(pg_dump)
|
||||
if [ "$OPERATION_MODE" = "backup" ]; then
|
||||
REQUIRED_CMDS+=(pg_dump)
|
||||
elif [ "$OPERATION_MODE" = "restore" ]; then
|
||||
REQUIRED_CMDS+=(psql)
|
||||
fi
|
||||
fi
|
||||
|
||||
for cmd in "${REQUIRED_CMDS[@]}"; do
|
||||
@@ -59,21 +70,41 @@ fi
|
||||
# Example: export RESTIC_REPOSITORY="rest:http://your-rest-server:8000/backup"
|
||||
: "${RESTIC_REPOSITORY:?Environment variable RESTIC_REPOSITORY is not set}"
|
||||
|
||||
#######################################
|
||||
# Validate operation mode.
|
||||
#######################################
|
||||
case "$OPERATION_MODE" in
|
||||
backup|restore)
|
||||
;;
|
||||
*)
|
||||
echo "Error: Unknown operation mode '$OPERATION_MODE'. Valid modes are 'backup' and 'restore'." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
#######################################
|
||||
# Validate mode-specific environment variables.
|
||||
#######################################
|
||||
case "$BACKUP_MODE" in
|
||||
directory)
|
||||
: "${SOURCEDIR:?Environment variable SOURCEDIR is not set (required for directory backup mode)}"
|
||||
if [ "$OPERATION_MODE" = "backup" ]; then
|
||||
: "${SOURCEDIR:?Environment variable SOURCEDIR is not set (required for directory backup mode)}"
|
||||
elif [ "$OPERATION_MODE" = "restore" ]; then
|
||||
: "${RESTOREDIR:?Environment variable RESTOREDIR is not set (required for directory restore mode)}"
|
||||
fi
|
||||
;;
|
||||
postgres)
|
||||
: "${PGHOST:?Environment variable PGHOST is not set (required for PostgreSQL backup mode)}"
|
||||
: "${PGDATABASE:?Environment variable PGDATABASE is not set (required for PostgreSQL backup mode)}"
|
||||
: "${PGUSER:?Environment variable PGUSER is not set (required for PostgreSQL backup mode)}"
|
||||
: "${PGHOST:?Environment variable PGHOST is not set (required for PostgreSQL mode)}"
|
||||
: "${PGDATABASE:?Environment variable PGDATABASE is not set (required for PostgreSQL mode)}"
|
||||
: "${PGUSER:?Environment variable PGUSER is not set (required for PostgreSQL mode)}"
|
||||
# Optional: default PGPORT to 5432.
|
||||
: "${PGPORT:=5432}"
|
||||
if [ -z "${PGPASSWORD:-}" ]; then
|
||||
echo "Warning: Environment variable PGPASSWORD is not set. pg_dump may fail if authentication is required."
|
||||
if [ "$OPERATION_MODE" = "backup" ]; then
|
||||
echo "Warning: Environment variable PGPASSWORD is not set. pg_dump may fail if authentication is required."
|
||||
elif [ "$OPERATION_MODE" = "restore" ]; then
|
||||
echo "Warning: Environment variable PGPASSWORD is not set. psql may fail if authentication is required."
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
@@ -173,6 +204,89 @@ backup_postgres() {
|
||||
run_restic_backup "${TEMP_BACKUP_DIR}"
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Run the restore using restic.
|
||||
# Arguments:
|
||||
# $1 - The target directory to restore to.
|
||||
# $2 - Optional snapshot ID to restore (defaults to latest).
|
||||
#######################################
|
||||
run_restic_restore() {
|
||||
local target_dir="$1"
|
||||
local snapshot_id="$2"
|
||||
|
||||
log "Starting restore from repository ${RESTIC_REPOSITORY} to '${target_dir}'"
|
||||
log "Using snapshot: ${snapshot_id}"
|
||||
|
||||
# Create target directory if it doesn't exist
|
||||
mkdir -p "${target_dir}"
|
||||
|
||||
# Capture both stdout and stderr in a variable
|
||||
restore_output=$(restic -r "${RESTIC_REPOSITORY}" restore "${snapshot_id}" --target "${target_dir}" --no-cache --json --verbose 2>&1)
|
||||
# Optionally, also print the output to the console:
|
||||
echo "$restore_output"
|
||||
|
||||
# Parse the JSON lines output for the summary message
|
||||
summary=$(echo "$restore_output" | jq -r 'select(.message_type=="summary") | "Restore completed: " + (.files_restored|tostring) + " files restored, " + (.bytes_restored|tostring) + " bytes in " + (.total_duration|tostring) + " sec"' 2>/dev/null || echo "Restore completed")
|
||||
|
||||
# Check exit code of restic restore
|
||||
if [ $? -eq 0 ]; then
|
||||
msg="Restore successful. $summary"
|
||||
log "$msg"
|
||||
send_notification "$msg"
|
||||
else
|
||||
exit_code=$?
|
||||
msg="Restore failed with error code ${exit_code}. $restore_output"
|
||||
log "$msg"
|
||||
send_notification "$msg"
|
||||
exit "$exit_code"
|
||||
fi
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Restore a directory (regular mode).
|
||||
#######################################
|
||||
restore_directory() {
|
||||
local snapshot_id="${RESTORE_SNAPSHOT_ID:-latest}"
|
||||
run_restic_restore "${RESTOREDIR}" "${snapshot_id}"
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Restore a PostgreSQL database.
|
||||
# Restores the database dump from the backup and applies it to the database.
|
||||
#######################################
|
||||
restore_postgres() {
|
||||
local snapshot_id="${RESTORE_SNAPSHOT_ID:-latest}"
|
||||
log "Starting PostgreSQL restore for database '${PGDATABASE}' on host '${PGHOST}'"
|
||||
|
||||
# Create a temporary directory for the restore.
|
||||
TEMP_RESTORE_DIR=$(mktemp -d)
|
||||
log "Created temporary directory: ${TEMP_RESTORE_DIR}"
|
||||
|
||||
# Restore the backup to the temporary directory
|
||||
run_restic_restore "${TEMP_RESTORE_DIR}" "${snapshot_id}"
|
||||
|
||||
local dump_file="${TEMP_RESTORE_DIR}/dump.sql"
|
||||
if [ ! -f "${dump_file}" ]; then
|
||||
local msg="PostgreSQL restore failed. Database dump file not found at ${dump_file}"
|
||||
log "$msg"
|
||||
send_notification "$msg"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Restoring PostgreSQL database from ${dump_file}..."
|
||||
if psql -h "${PGHOST}" -p "${PGPORT}" -U "${PGUSER}" -d "${PGDATABASE}" ${PSQL_ARGS:-} < "${dump_file}"; then
|
||||
local msg="PostgreSQL database restored successfully"
|
||||
log "$msg"
|
||||
send_notification "$msg"
|
||||
else
|
||||
local exit_code=$?
|
||||
local msg="PostgreSQL restore failed with error code ${exit_code}"
|
||||
log "$msg"
|
||||
send_notification "$msg"
|
||||
exit "$exit_code"
|
||||
fi
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Cleanup temporary resources.
|
||||
#######################################
|
||||
@@ -181,6 +295,10 @@ cleanup() {
|
||||
rm -rf "${TEMP_BACKUP_DIR}"
|
||||
log "Removed temporary directory ${TEMP_BACKUP_DIR}"
|
||||
fi
|
||||
if [ -n "${TEMP_RESTORE_DIR:-}" ] && [ -d "${TEMP_RESTORE_DIR}" ]; then
|
||||
rm -rf "${TEMP_RESTORE_DIR}"
|
||||
log "Removed temporary directory ${TEMP_RESTORE_DIR}"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
@@ -188,12 +306,26 @@ trap cleanup EXIT
|
||||
# Main routine.
|
||||
#######################################
|
||||
main() {
|
||||
case "$BACKUP_MODE" in
|
||||
directory)
|
||||
backup_directory
|
||||
case "$OPERATION_MODE" in
|
||||
backup)
|
||||
case "$BACKUP_MODE" in
|
||||
directory)
|
||||
backup_directory
|
||||
;;
|
||||
postgres)
|
||||
backup_postgres
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
postgres)
|
||||
backup_postgres
|
||||
restore)
|
||||
case "$BACKUP_MODE" in
|
||||
directory)
|
||||
restore_directory
|
||||
;;
|
||||
postgres)
|
||||
restore_postgres
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user