Compare commits
9 Commits
a9e0f04b8e
...
0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| fb31691451 | |||
| 0c0305301e | |||
| cd92ce697e | |||
| 4cfb634397 | |||
| cb466747cd | |||
| 3f544409f1 | |||
| 6ff6e8759e | |||
| 07832050dc | |||
| 70fb02c0b0 |
28
.drone.yaml
28
.drone.yaml
@@ -1,28 +0,0 @@
|
|||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
|
|
||||||
name: build-and-push-container
|
|
||||||
|
|
||||||
triggers:
|
|
||||||
branch:
|
|
||||||
- main
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
image: plugins/docker
|
|
||||||
commands:
|
|
||||||
- docker build -t gitea.t000-n.de/t.behrendt/backupsidecar:${DRONE_COMMIT:0:8} .
|
|
||||||
|
|
||||||
- name: push
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: REGISTRY_USERNAME
|
|
||||||
password:
|
|
||||||
from_secret: REGISTRY_PASSWORD
|
|
||||||
repo: gitea.t000-n.de/t.behrendt/backupsidecar
|
|
||||||
tags:
|
|
||||||
- latest
|
|
||||||
- ${DRONE_COMMIT:0:8}
|
|
||||||
111
.gitea/workflows/cd.yaml
Normal file
111
.gitea/workflows/cd.yaml
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
name: CD
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
env:
|
||||||
|
DOCKER_REGISTRY: gitea.t000-n.de
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-changes:
|
||||||
|
name: Check changes
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
changes: ${{ steps.filter.outputs.code }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Get changed files
|
||||||
|
id: filter
|
||||||
|
uses: dorny/paths-filter@v3
|
||||||
|
with:
|
||||||
|
filters: |
|
||||||
|
code:
|
||||||
|
- 'src/**'
|
||||||
|
- 'Dockerfile'
|
||||||
|
- 'gitea/workflows/**'
|
||||||
|
|
||||||
|
build_and_push:
|
||||||
|
name: Build and push
|
||||||
|
needs:
|
||||||
|
- check-changes
|
||||||
|
if: ${{ needs.check-changes.outputs.changes != '0' }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
arch:
|
||||||
|
- amd64
|
||||||
|
- arm64
|
||||||
|
runs-on:
|
||||||
|
- ubuntu-latest
|
||||||
|
- linux_${{ matrix.arch }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.REGISTRY_USER }}
|
||||||
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
- id: meta
|
||||||
|
run: |
|
||||||
|
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}' | tr '[:upper:]' '[:lower:]') >> $GITHUB_OUTPUT
|
||||||
|
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT
|
||||||
|
- uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
platforms: linux/${{ matrix.arch }}
|
||||||
|
push: true
|
||||||
|
provenance: false
|
||||||
|
tags: |
|
||||||
|
${{ env.DOCKER_REGISTRY }}/t.behrendt/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}-${{ matrix.arch }}
|
||||||
|
|
||||||
|
create_tag:
|
||||||
|
name: Create tag
|
||||||
|
needs:
|
||||||
|
- check-changes
|
||||||
|
if: ${{ needs.check-changes.outputs.changes != '0' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
tag: ${{ steps.tag.outputs.new-tag }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: https://gitea.t000-n.de/t.behrendt/conventional-semantic-git-tag-increment@0.0.2
|
||||||
|
id: tag
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
- run: |
|
||||||
|
git tag ${{ steps.tag.outputs.new-tag }}
|
||||||
|
git push origin ${{ steps.tag.outputs.new-tag }}
|
||||||
|
- name: Set output
|
||||||
|
run: |
|
||||||
|
echo "tag=${{ steps.tag.outputs.new-tag }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
create_manifest:
|
||||||
|
name: Create manifest
|
||||||
|
needs:
|
||||||
|
- build_and_push
|
||||||
|
- create_tag
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- id: meta
|
||||||
|
run: |
|
||||||
|
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}' | tr '[:upper:]' '[:lower:]') >> $GITHUB_OUTPUT
|
||||||
|
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.REGISTRY_USER }}
|
||||||
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
- run: |
|
||||||
|
docker manifest create ${{ env.DOCKER_REGISTRY }}/t.behrendt/${{ steps.meta.outputs.REPO_NAME }}:${{ needs.create_tag.outputs.tag }} \
|
||||||
|
${{ env.DOCKER_REGISTRY }}/t.behrendt/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}-amd64 \
|
||||||
|
${{ env.DOCKER_REGISTRY }}/t.behrendt/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}-arm64
|
||||||
|
|
||||||
|
docker manifest push ${{ env.DOCKER_REGISTRY }}/t.behrendt/${{ steps.meta.outputs.REPO_NAME }}:${{ needs.create_tag.outputs.tag }}
|
||||||
24
.gitea/workflows/ci.yaml
Normal file
24
.gitea/workflows/ci.yaml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build Docker image
|
||||||
|
runs-on:
|
||||||
|
- ubuntu-latest
|
||||||
|
- linux_amd64
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Build image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: false
|
||||||
|
provenance: false
|
||||||
|
tags: |
|
||||||
|
backupsidecar:ci-test
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
dockerBuildAndPush.sh
|
|
||||||
20
Dockerfile
20
Dockerfile
@@ -1,14 +1,16 @@
|
|||||||
FROM alpine:3.17
|
FROM alpine:3.22
|
||||||
|
|
||||||
# Setup correct TZ
|
RUN apk update && apk add --no-cache \
|
||||||
RUN apk add alpine-conf
|
bash \
|
||||||
RUN /sbin/setup-timezone -z UTC
|
curl \
|
||||||
|
restic \
|
||||||
RUN apk add restic curl
|
postgresql-client \
|
||||||
|
jq
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY ./src/entry.sh /app/
|
COPY src/backup.sh /app/backup.sh
|
||||||
COPY ./src/backup.sh /app/
|
|
||||||
|
|
||||||
CMD [ "/bin/sh", "entry.sh" ]
|
RUN chmod +x /app/backup.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/backup.sh"]
|
||||||
|
|||||||
137
README.md
137
README.md
@@ -1,20 +1,123 @@
|
|||||||
# backupsidecar
|
# BackupSidecar
|
||||||
|
|
||||||
Backup sidecar that automatically creates backups of one PVC and saves it to another PVC via restic
|
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.
|
||||||
|
|
||||||
## Function
|
## Configuration
|
||||||
A cronjob inside the container runs in the configured interval creating the backup and purging old backups.
|
|
||||||
A notification is sent to gotify on completion of the backup or on error of either the backup or purge.
|
|
||||||
|
|
||||||
## Environment Variables
|
BackupSidecar is configured through environment variables. Below is a breakdown of the available settings.
|
||||||
| ENV Variable | Required |Description| Example Value
|
|
||||||
|--------------|----------|--------------|-
|
### General Settings
|
||||||
|INTERVAL|yes|cronjob interval string|15 14 * * *|
|
|
||||||
|RESTIC_REPOSITORY|yes|path of the restic repository|/mnt/backups/gitea|
|
These variables apply to both directory and PostgreSQL backups.
|
||||||
|SOURCEDIR |yes|path of the path to backup|/mnt/toBackup/|
|
|
||||||
|KEEPLAST|yes|number of increments to keep (keep in mind that the number of backups to keep correlates with the interval in which they are created) |10|
|
- **`BACKUP_MODE`** _(optional)_ - Defines the backup type (`directory` or `postgres`). Defaults to `directory`.
|
||||||
|RESTIC_PASSWORD|yes|password for the restic repository|******|
|
- **`RESTIC_PASSWORD`** _(required)_ - The encryption password for Restic.
|
||||||
|RUNONSTART|no|set to true to force a backup at the start of the container|true|
|
- **`RESTIC_REPOSITORY`** _(required)_ - The URI of the Restic repository (e.g., `rest:http://your-rest-server:8000/backup`).
|
||||||
|GOTIFYHOST|yes|URL of the gotify server (without trailing slash)|https://gotify.example.com|
|
- **`RESTIC_REST_USERNAME`** _(optional)_ - The username for REST server authentication.
|
||||||
|GOTIFYTOKEN|yes|gotify app token|******|
|
- **`RESTIC_REST_PASSWORD`** _(optional)_ - The password for REST server authentication.
|
||||||
|GOTIFYTOPIC|yes|gotify topic to include in the notification|gotify|
|
- **`ENABLE_GOTIFY`** _(optional)_ - Enable Gotify notifications. Set to `true` to enable, any other value or unset disables notifications. Defaults to `true`.
|
||||||
|
- **`GOTIFYHOST`** _(required when ENABLE_GOTIFY=true)_ - The Gotify server URL.
|
||||||
|
- **`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
|
||||||
|
|
||||||
|
When running in `directory` mode, the following variable must be set:
|
||||||
|
|
||||||
|
- **`SOURCEDIR`** _(required)_ - The path of the directory to be backed up.
|
||||||
|
|
||||||
|
### PostgreSQL Backup
|
||||||
|
|
||||||
|
For `postgres` mode, the following database-related variables are required:
|
||||||
|
|
||||||
|
- **`PGHOST`** _(required)_ - The hostname of the PostgreSQL server.
|
||||||
|
- **`PGDATABASE`** _(required)_ - The name of the database to back up.
|
||||||
|
- **`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.
|
||||||
|
- **`PG_DUMP_ARGS`** _(optional)_ - Additional flags for `pg_dump`.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
Ensure the following commands are available in the container:
|
||||||
|
|
||||||
|
- `restic`
|
||||||
|
- `curl`
|
||||||
|
- `jq`
|
||||||
|
- `pg_dump` _(only required for `postgres` mode)_
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Example Kubernetes CronJob manifest for running BackupSidecar as a cron job for directory backups in minimal configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: backupsidecar-cron
|
||||||
|
namespace: authentik
|
||||||
|
spec:
|
||||||
|
schedule: "0 7 * * *"
|
||||||
|
concurrencyPolicy: Forbid
|
||||||
|
successfulJobsHistoryLimit: 5
|
||||||
|
failedJobsHistoryLimit: 3
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
backoffLimit: 3
|
||||||
|
activeDeadlineSeconds: 300
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
containers:
|
||||||
|
- name: backupsidecar
|
||||||
|
image: backupsidecar:latest
|
||||||
|
env:
|
||||||
|
- name: RESTIC_REPOSITORY
|
||||||
|
value: "rest:http://rest-server:8000/backup"
|
||||||
|
- name: RESTIC_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: backupsidecar-secret
|
||||||
|
key: restic_password
|
||||||
|
- name: BACKUP_MODE
|
||||||
|
value: "directory" # or "postgres"
|
||||||
|
- name: SOURCEDIR
|
||||||
|
value: "/data/source"
|
||||||
|
- name: ENABLE_GOTIFY
|
||||||
|
value: "true"
|
||||||
|
- name: GOTIFYHOST
|
||||||
|
value: "http://gotify.example.com"
|
||||||
|
- name: GOTIFYTOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: backupsidecar-secret
|
||||||
|
key: gotify_token
|
||||||
|
- name: GOTIFYTOPIC
|
||||||
|
value: "Backup Notification"
|
||||||
|
# (For PostgreSQL mode, add PGHOST, PGDATABASE, PGUSER, PGPORT, PGPASSWORD)
|
||||||
|
volumeMounts:
|
||||||
|
- name: source-data
|
||||||
|
mountPath: /data/source
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
volumes:
|
||||||
|
- name: source-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: source-data-pvc
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: backup-sidecar
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: backup-sidecar
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: backup-sidecar
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: backup-sidecar
|
|
||||||
image: gitea.t000-n.de/t.behrendt/backupsidecar:latest
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: 128m
|
|
||||||
memory: 128Mi
|
|
||||||
limits:
|
|
||||||
cpu: 256m
|
|
||||||
memory: 1024Mi
|
|
||||||
volumeMounts:
|
|
||||||
- mountPath: /mnt/toBackup
|
|
||||||
name: data
|
|
||||||
readOnly: true
|
|
||||||
- mountPath: /mnt/backups/gitea
|
|
||||||
name: nfs-backup
|
|
||||||
subPath: gitea
|
|
||||||
env:
|
|
||||||
- name: INTERVAL
|
|
||||||
value: "15 14 * * *"
|
|
||||||
- name: RESTIC_REPOSITORY
|
|
||||||
value: /mnt/backups/gitea
|
|
||||||
- name: SOURCEDIR
|
|
||||||
value: "/mnt/toBackup/"
|
|
||||||
- name: KEEPLAST
|
|
||||||
value: "100"
|
|
||||||
- name: RESTIC_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: restic-credentials
|
|
||||||
key: password
|
|
||||||
- name: RUNONSTART
|
|
||||||
value: "false"
|
|
||||||
- name: GOTIFYHOST
|
|
||||||
value: "https://<gotify-URL>"
|
|
||||||
- name: GOTIFYTOKEN
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: gotify-credentials
|
|
||||||
key: token
|
|
||||||
- name: GOTIFYTOPIC
|
|
||||||
value: "gitea"
|
|
||||||
volumes:
|
|
||||||
- name: data
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: data
|
|
||||||
- name: nfs-backup
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: backup-nfs
|
|
||||||
3
renovate.json
Normal file
3
renovate.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||||
|
}
|
||||||
222
src/backup.sh
222
src/backup.sh
@@ -1,40 +1,204 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
GOTIFYURL="$GOTIFYHOST/message?token=$GOTIFYTOKEN"
|
#######################################
|
||||||
|
# Date format for logging.
|
||||||
|
#######################################
|
||||||
|
LOG_DATE_FORMAT="%Y-%m-%dT%T"
|
||||||
|
|
||||||
echo "$(date +"%Y-%m-%dT%T") - Starting backup"
|
#######################################
|
||||||
|
# Log a message with a timestamp.
|
||||||
|
# Arguments:
|
||||||
|
# Message to log.
|
||||||
|
#######################################
|
||||||
|
log() {
|
||||||
|
echo "$(date +"$LOG_DATE_FORMAT") - $*"
|
||||||
|
}
|
||||||
|
|
||||||
restic backup \
|
#######################################
|
||||||
--verbose \
|
# Determine backup mode from the environment only.
|
||||||
$SOURCEDIR
|
# Valid values: "directory" or "postgres".
|
||||||
|
# Default to "directory" if not provided.
|
||||||
|
#######################################
|
||||||
|
BACKUP_MODE="${BACKUP_MODE:-directory}"
|
||||||
|
|
||||||
RESTIC_BACKUP_RETURN=$?
|
#######################################
|
||||||
|
# Check for required external commands.
|
||||||
if [ $RESTIC_BACKUP_RETURN -eq 0 ]; then
|
#######################################
|
||||||
MSG_BACKUP_SUCCESS="Backup successful"
|
REQUIRED_CMDS=(restic curl jq)
|
||||||
echo "$(date +"%Y-%m-%dT%T") - $MSG_BACKUP_SUCCESS"
|
if [ "$BACKUP_MODE" = "postgres" ]; then
|
||||||
curl -s -X POST "$GOTIFYURL" -F "title=$GOTIFYTOPIC" -F "message=$MSG_BACKUP_SUCCESS"
|
REQUIRED_CMDS+=(pg_dump)
|
||||||
else
|
|
||||||
MSG_BACKUP_ERR="Backup failed with error code $RESTIC_BACKUP_RETURN"
|
|
||||||
echo "$(date +"%Y-%m-%dT%T") - MSG_BACKUP_ERR"
|
|
||||||
curl -s -X POST "$GOTIFYURL" -F "title=$GOTIFYTOPIC" -F "message=$MSG_BACKUP_ERR"
|
|
||||||
exit $RESTIC_BACKUP_RETURN
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
for cmd in "${REQUIRED_CMDS[@]}"; do
|
||||||
|
if ! command -v "$cmd" &>/dev/null; then
|
||||||
|
log "Error: Required command '$cmd' is not installed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
MSG_PURGE_START="$(date +"%Y-%m-%dT%T") - Removing old backups"
|
#######################################
|
||||||
echo $MSG_PURGE_START
|
# Validate common required environment variables.
|
||||||
|
#######################################
|
||||||
|
# Gotify notification settings (optional).
|
||||||
|
# Set ENABLE_GOTIFY to "true" to enable notifications, any other value or unset disables them.
|
||||||
|
ENABLE_GOTIFY="${ENABLE_GOTIFY:-true}"
|
||||||
|
|
||||||
restic forget --keep-last $KEEPLAST --prune
|
if [ "$ENABLE_GOTIFY" = "true" ]; then
|
||||||
RESTIC_PURGE_RETURN=$?
|
: "${GOTIFYHOST:?Environment variable GOTIFYHOST is not set (required when ENABLE_GOTIFY=true)}"
|
||||||
|
: "${GOTIFYTOKEN:?Environment variable GOTIFYTOKEN is not set (required when ENABLE_GOTIFY=true)}"
|
||||||
if [ $RESTIC_PURGE_RETURN -eq 0 ]; then
|
: "${GOTIFYTOPIC:?Environment variable GOTIFYTOPIC is not set (required when ENABLE_GOTIFY=true)}"
|
||||||
echo "$(date +"%Y-%m-%dT%T") - Purge successful"
|
|
||||||
else
|
else
|
||||||
MSG_PURGE_ERR="Purge failed with error code $MSG_PURGE_ERR"
|
log "Gotify notifications disabled. Backup status will be logged to console only."
|
||||||
echo "$(date +"%Y-%m-%dT%T") - $MSG_PURGE_ERR"
|
|
||||||
curl -s -X POST "$GOTIFYURL" -F "title=$GOTIFYTOPIC" -F "message=$MSG_PURGE_ERR"
|
|
||||||
exit $RESTIC_PURGE_RETURN
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "$(date +"%Y-%m-%dT%T") - Going back to sleep..."
|
# Restic encryption password.
|
||||||
|
: "${RESTIC_PASSWORD:?Environment variable RESTIC_PASSWORD is not set}"
|
||||||
|
|
||||||
|
# Use the repository URI directly from the environment.
|
||||||
|
# Example: export RESTIC_REPOSITORY="rest:http://your-rest-server:8000/backup"
|
||||||
|
: "${RESTIC_REPOSITORY:?Environment variable RESTIC_REPOSITORY is not set}"
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Validate mode-specific environment variables.
|
||||||
|
#######################################
|
||||||
|
case "$BACKUP_MODE" in
|
||||||
|
directory)
|
||||||
|
: "${SOURCEDIR:?Environment variable SOURCEDIR is not set (required for directory backup mode)}"
|
||||||
|
;;
|
||||||
|
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)}"
|
||||||
|
# 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."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Error: Unknown backup mode '$BACKUP_MODE'. Valid modes are 'directory' and 'postgres'." >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Build the Gotify URL (only if Gotify is enabled).
|
||||||
|
#######################################
|
||||||
|
if [ "$ENABLE_GOTIFY" = "true" ]; then
|
||||||
|
GOTIFYURL="${GOTIFYHOST}/message?token=${GOTIFYTOKEN}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Send a notification via Gotify.
|
||||||
|
# Arguments:
|
||||||
|
# message: The message to send.
|
||||||
|
#######################################
|
||||||
|
send_notification() {
|
||||||
|
local message="$1"
|
||||||
|
|
||||||
|
# Only send notification if Gotify is enabled
|
||||||
|
if [ "$ENABLE_GOTIFY" != "true" ]; then
|
||||||
|
log "$message"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! curl -s -X POST "$GOTIFYURL" -F "title=${GOTIFYTOPIC}" -F "message=${message}" >/dev/null; then
|
||||||
|
log "Warning: Failed to send notification with message: ${message}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Run the backup using restic.
|
||||||
|
# The --no-cache flag disables local caching.
|
||||||
|
# Arguments:
|
||||||
|
# $1 - The source directory to back up.
|
||||||
|
#######################################
|
||||||
|
run_restic_backup() {
|
||||||
|
local source_dir="$1"
|
||||||
|
cd "${source_dir}"
|
||||||
|
log "Starting backup of '${source_dir}' to repository ${RESTIC_REPOSITORY}"
|
||||||
|
# Capture both stdout and stderr in a variable
|
||||||
|
backup_output=$(restic -r "${RESTIC_REPOSITORY}" backup --no-cache --json --verbose . 2>&1)
|
||||||
|
# Optionally, also print the output to the console:
|
||||||
|
echo "$backup_output"
|
||||||
|
# Parse the JSON lines output for the summary message
|
||||||
|
summary=$(echo "$backup_output" | jq -r 'select(.message_type=="summary") | "Snapshot " + (.snapshot_id // "none") + ": " + "files new: " + (.files_new|tostring) + ", files changed: " + (.files_changed|tostring) + ", data added: " + (.data_added|tostring) + " bytes in " + (.total_duration|tostring) + " sec"')
|
||||||
|
# Check exit code of restic backup (assuming restic exits non-zero on error)
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
msg="Backup successful. $summary"
|
||||||
|
log "$msg"
|
||||||
|
send_notification "$msg"
|
||||||
|
else
|
||||||
|
exit_code=$?
|
||||||
|
msg="Backup failed with error code ${exit_code}. $backup_output"
|
||||||
|
log "$msg"
|
||||||
|
send_notification "$msg"
|
||||||
|
exit "$exit_code"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Backup a directory (regular mode).
|
||||||
|
#######################################
|
||||||
|
backup_directory() {
|
||||||
|
run_restic_backup "${SOURCEDIR}"
|
||||||
|
}
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Backup a PostgreSQL database.
|
||||||
|
# Dumps the database to a temporary directory and then backs it up.
|
||||||
|
#######################################
|
||||||
|
backup_postgres() {
|
||||||
|
log "Starting PostgreSQL backup for database '${PGDATABASE}' on host '${PGHOST}'"
|
||||||
|
|
||||||
|
# Create a temporary directory for the database dump.
|
||||||
|
TEMP_BACKUP_DIR=$(mktemp -d)
|
||||||
|
log "Created temporary directory: ${TEMP_BACKUP_DIR}"
|
||||||
|
|
||||||
|
local dump_file="${TEMP_BACKUP_DIR}/dump.sql"
|
||||||
|
log "Dumping PostgreSQL database to ${dump_file}..."
|
||||||
|
if pg_dump -h "${PGHOST}" -p "${PGPORT}" -U "${PGUSER}" ${PG_DUMP_ARGS:-} "${PGDATABASE}" > "${dump_file}"; then
|
||||||
|
log "Database dump created successfully."
|
||||||
|
else
|
||||||
|
local exit_code=$?
|
||||||
|
local msg="PostgreSQL dump failed with error code ${exit_code}"
|
||||||
|
log "$msg"
|
||||||
|
send_notification "$msg"
|
||||||
|
exit "$exit_code"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Back up the directory containing the dump.
|
||||||
|
run_restic_backup "${TEMP_BACKUP_DIR}"
|
||||||
|
}
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Cleanup temporary resources.
|
||||||
|
#######################################
|
||||||
|
cleanup() {
|
||||||
|
if [ -n "${TEMP_BACKUP_DIR:-}" ] && [ -d "${TEMP_BACKUP_DIR}" ]; then
|
||||||
|
rm -rf "${TEMP_BACKUP_DIR}"
|
||||||
|
log "Removed temporary directory ${TEMP_BACKUP_DIR}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Main routine.
|
||||||
|
#######################################
|
||||||
|
main() {
|
||||||
|
case "$BACKUP_MODE" in
|
||||||
|
directory)
|
||||||
|
backup_directory
|
||||||
|
;;
|
||||||
|
postgres)
|
||||||
|
backup_postgres
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Trap termination signals to log and exit cleanly.
|
||||||
|
trap 'log "Script interrupted. Exiting."; exit 1' SIGINT SIGTERM
|
||||||
|
|
||||||
|
main
|
||||||
|
|||||||
18
src/entry.sh
18
src/entry.sh
@@ -1,18 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
mkdir /etc/cron.d
|
|
||||||
touch /etc/cron.d/backup
|
|
||||||
echo "$INTERVAL /bin/sh /app/backup.sh" > /etc/cron.d/backup
|
|
||||||
|
|
||||||
# change ownership and make the cron known to crontab
|
|
||||||
chmod 0644 /etc/cron.d/backup && crontab /etc/cron.d/backup
|
|
||||||
|
|
||||||
|
|
||||||
if [ $RUNONSTART = 'true' ]; then
|
|
||||||
echo $(date +"%Y-%m-%dT%T") "- Running initial backup"
|
|
||||||
/bin/sh /app/backup.sh
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Wait until infinity
|
|
||||||
echo $(date +"%Y-%m-%dT%T") "- Starting cron"
|
|
||||||
crond -f
|
|
||||||
Reference in New Issue
Block a user