apps/actual-budget/backup.sh
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env bash
set -Eeuo pipefail
# The archive carries every budget file, the server password hash and .env
# itself, so nothing this script writes may be readable by another account on
# the server.
umask 077
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$script_dir"
env_file="${FOSSARY_ENV_FILE:-$script_dir/.env}"
if [[ ! -e "$env_file" ]]; then
echo "Missing $env_file" >&2
exit 2
fi
set -a
# shellcheck disable=SC1091
. "$env_file"
set +a
compose=(docker compose --env-file "$env_file")
data_volume="${ACTUAL_DATA_VOLUME:-actual-data}"
backup_dir="${ACTUAL_BACKUP_DIR:-$script_dir/backups}"
timestamp="$(date -u +%Y%m%dT%H%M%SZ)"
staging="$(mktemp -d)"
actual_stopped=false
cleanup() {
status=$?
if [[ "$actual_stopped" == true ]]; then
"${compose[@]}" start actual >/dev/null || true
fi
rm -rf "$staging"
exit "$status"
}
trap cleanup EXIT INT TERM
mkdir -p "$backup_dir"
# Actual keeps SQLite files under /data: account.sqlite with the server password
# and the session, plus one database per budget. They are copied with the server
# stopped, because a live SQLite file can be archived mid-write.
"${compose[@]}" stop actual
actual_stopped=true
docker run --rm -v "${data_volume}:/source:ro" -v "${staging}:/backup" alpine:3.22 tar -C /source -czf /backup/data.tar.gz .
cp -- "$env_file" "$staging/configuration.env"
cp -- compose.yaml "$staging/compose.yaml"
archive="$backup_dir/actual-budget-${timestamp}.tar"
tar -C "$staging" -cf "$archive" data.tar.gz configuration.env compose.yaml
echo "Backup created: $archive"
echo "This archive holds every budget file and the server password hash; encrypt it and copy it off the server."