Remove legacy scripts and single-tenant support

- Deleted the migration script `migrate-to-multitenant.sh` as it is no longer needed.
- Removed `rebuild-venv.sh`, `restore.sh`, `run-backup.sh`, and other related scripts that are not compatible with the new multi-tenant architecture.
- Updated `restart.sh`, `start.sh`, `stop.sh`, `update.sh`, and other scripts to eliminate references to single-tenant deployment, ensuring they only support multi-tenant configurations.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-29 10:29:46 +02:00
parent c8f9c87ba4
commit f80430f243
22 changed files with 401 additions and 3898 deletions
Executable → Regular
+115 -490
View File
@@ -1,536 +1,161 @@
#!/usr/bin/env bash
set -euo pipefail
# ========================================================
# Inventarsystem backup helper
# - Creates a dated backup folder/archive under /var/backups (by default)
# - Exports MongoDB via run-backup.sh into mongodb_backup/
# - Cleans up backups older than KEEP_DAYS
#
# Options:
# --dest|--out|--backup-dir <dir> Destination base directory (default: /var/backups)
# --db|--db-name <name> MongoDB database name (default: Inventarsystem)
# --uri|--mongo-uri <uri> MongoDB URI (default: mongodb://localhost:27017/)
# --invoice-archive-dir <dir> Invoice archive directory (default: <dest>/invoice-archive)
# --invoice-keep-days <N> Retention for invoice archive in days (default: 3650)
# --log <file> Log file (default: ./logs/backup.log)
# --no-compress Disable compression (keeps directory instead of .tar.gz)
# --compress-level <0-9> Compression level flag (only 0 is special here)
# --keep-days <N> Age-based retention in days (default: 7; 0 disables age filter)
# --min-keep <N> Always keep at least N most recent backups (default: 7)
# --mode <auto|host|docker> Backup mode (default: auto)
# -h|--help Show help
# ========================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="${PROJECT_DIR:-$SCRIPT_DIR}"
# Default destination now /var/backups; override with --dest or BACKUP_BASE_DIR env
BACKUP_BASE_DIR="${BACKUP_BASE_DIR:-/var/backups}"
LOG_DIR="$PROJECT_DIR/logs"
# Create only the log directory here; backup dir is created later with sudo if needed
mkdir -p "$LOG_DIR"
LOG_FILE="${LOG_FILE:-$LOG_DIR/backup.log}"
COMPRESSION_LEVEL="${COMPRESSION_LEVEL:-9}"
KEEP_DAYS="${KEEP_DAYS:-7}"
MIN_KEEP="${MIN_KEEP:-7}"
DB_NAME="${DB_NAME:-inventar_default}"
MONGO_URI="${MONGO_URI:-mongodb://localhost:27017/}"
INVOICE_KEEP_DAYS="${INVOICE_KEEP_DAYS:-3650}"
INVOICE_ARCHIVE_DIR="${INVOICE_ARCHIVE_DIR:-$BACKUP_BASE_DIR/invoice-archive}"
BACKUP_MODE="${BACKUP_MODE:-auto}"
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose-multitenant.yml}"
DOCKER_AVAILABLE=0
DOCKER_COMPOSE_CMD=()
USE_NULL_OUTPUT=false
COMPOSE_FILE="docker-compose-multitenant.yml"
INVOICE_ARCHIVE_DIR="${INVOICE_ARCHIVE_DIR:-/var/backups/invoice-archive}"
KEEP_DAYS="${INVOICE_KEEP_DAYS:-3650}"
MODE="auto"
BASE_NAME=""
usage() {
cat <<EOF
Usage: $(basename "$0") [options]
Usage: $0 [options]
Options:
--dest|--out|--backup-dir <dir> Destination base directory (default: $BACKUP_BASE_DIR)
--db|--db-name <name> MongoDB database name (default: $DB_NAME)
--uri|--mongo-uri <uri> MongoDB URI (default: $MONGO_URI)
--invoice-archive-dir <dir> Invoice archive directory (default: $INVOICE_ARCHIVE_DIR)
--invoice-keep-days <N> Retention for invoice archive in days (default: $INVOICE_KEEP_DAYS)
--log <file> Log file (default: $LOG_FILE)
--no-compress Disable compression (keeps directory instead of .tar.gz)
--compress-level <0-9> Compression level flag (only 0 is special here)
--keep-days <N> Age-based retention in days (default: $KEEP_DAYS; 0 disables age filter)
--min-keep <N> Always keep at least this many backups (default: $MIN_KEEP)
--mode <auto|host|docker> Backup mode (default: $BACKUP_MODE)
--multitenant Use docker-compose-multitenant.yml (default)
--singletenant Use docker-compose.yml
-h|--help Show this help and exit
--invoice-archive-dir DIR Directory for invoice archive files
(default: $INVOICE_ARCHIVE_DIR)
--invoice-keep-days N Remove archived invoice files older than N days
(default: $KEEP_DAYS)
--mode auto|docker|host Backup mode (default: auto)
--base-name NAME Base filename prefix for archive files
-h, --help Show this help message
EOF
}
# Parse CLI arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--dest|--out|--backup-dir)
BACKUP_BASE_DIR="$2"; shift 2;;
--db|--db-name)
DB_NAME="$2"; shift 2;;
--uri|--mongo-uri)
MONGO_URI="$2"; shift 2;;
--invoice-archive-dir)
INVOICE_ARCHIVE_DIR="$2"; shift 2;;
--invoice-keep-days)
INVOICE_KEEP_DAYS="$2"; shift 2;;
--log)
LOG_FILE="$2"; shift 2;;
--no-compress)
COMPRESSION_LEVEL=0; shift;;
--compress-level)
COMPRESSION_LEVEL="${2:-6}"; shift 2;;
--keep-days)
KEEP_DAYS="$2"; shift 2;;
--min-keep)
MIN_KEEP="$2"; shift 2;;
--mode)
BACKUP_MODE="$2"; shift 2;;
--multitenant)
COMPOSE_FILE="docker-compose-multitenant.yml"; shift;;
--singletenant)
COMPOSE_FILE="docker-compose.yml"; shift;;
-h|--help)
usage; exit 0;;
*)
echo "Unknown option: $1" >&2; usage; exit 2;;
esac
done
# Function to log messages
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" 2>/dev/null || echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--invoice-archive-dir)
INVOICE_ARCHIVE_DIR="$2"
shift 2
;;
--invoice-keep-days)
KEEP_DAYS="$2"
shift 2
;;
--mode)
MODE="$2"
shift 2
;;
--base-name)
BASE_NAME="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Error: unknown option '$1'" >&2
usage
exit 2
;;
esac
done
}
init_docker_compose() {
if docker compose version >/dev/null 2>&1; then
DOCKER_AVAILABLE=1
DOCKER_COMPOSE_CMD=(docker compose -f "$PROJECT_DIR/$COMPOSE_FILE")
return 0
fi
if sudo docker compose version >/dev/null 2>&1; then
DOCKER_AVAILABLE=1
DOCKER_COMPOSE_CMD=(sudo docker compose -f "$PROJECT_DIR/$COMPOSE_FILE")
return 0
fi
DOCKER_AVAILABLE=0
DOCKER_COMPOSE_CMD=()
return 1
ensure_directory() {
mkdir -p "$INVOICE_ARCHIVE_DIR"
}
compose_cmd() {
if [ "$DOCKER_AVAILABLE" -ne 1 ]; then
cleanup_old_archives() {
if [[ -n "$KEEP_DAYS" ]]; then
find "$INVOICE_ARCHIVE_DIR" -maxdepth 1 -type f \( -name 'invoices-*.jsonl' -o -name 'invoices-*.csv' -o -name 'invoices-*.meta.json' \) -mtime +"$KEEP_DAYS" -print -delete || true
fi
}
host_backup() {
if ! command -v python3 >/dev/null 2>&1; then
return 1
fi
"${DOCKER_COMPOSE_CMD[@]}" "$@"
}
try_backup_db_docker() {
local out_dir="$1"
local stamp
stamp="$(date +"%Y-%m-%d_%H-%M-%S")"
local archive_path="$out_dir/mongodb-${stamp}.archive.gz"
[ "$DOCKER_AVAILABLE" -eq 1 ] || return 1
log_message "Attempting Docker-based MongoDB backup..."
compose_cmd up -d mongodb >/dev/null 2>&1 || return 1
if ! compose_cmd ps --status running mongodb | grep -q mongodb; then
if ! python3 -c 'import pymongo' >/dev/null 2>&1; then
return 1
fi
if compose_cmd exec -T mongodb sh -c "mongodump --archive --gzip --db '$DB_NAME'" > "$archive_path"; then
log_message "Docker DB backup successful: $archive_path"
return 0
fi
return 1
python3 "$SCRIPT_DIR/Web/backup_invoices.py" \
--archive-dir "$INVOICE_ARCHIVE_DIR" \
--base-name "$BASE_NAME"
}
try_backup_invoices_docker() {
local archive_base="$1"
[ "$DOCKER_AVAILABLE" -eq 1 ] || return 1
local tmp_base
tmp_base="/tmp/$(basename "$archive_base")"
log_message "Attempting Docker-based invoice archive export..."
compose_cmd up -d mongodb app >/dev/null 2>&1 || return 1
if ! compose_cmd ps --status running app | grep -q app; then
docker_backup() {
if ! command -v docker >/dev/null 2>&1; then
return 1
fi
if ! docker compose -f "$COMPOSE_FILE" ps -q app >/dev/null 2>&1; then
return 1
fi
local app_cid
app_cid="$(compose_cmd ps -q app)"
[ -n "$app_cid" ] || return 1
if ! sudo docker cp "$PROJECT_DIR/Backup-Invoices.py" "$app_cid:/tmp/Backup-Invoices.py" >/dev/null 2>&1; then
local app_container
app_container="$(docker compose -f "$COMPOSE_FILE" ps -q app | tr -d '[:space:]')"
if [[ -z "$app_container" ]]; then
return 1
fi
if ! compose_cmd exec -T app python /tmp/Backup-Invoices.py \
--uri "mongodb://mongodb:27017/" \
--db "$DB_NAME" \
--out "$tmp_base" >/dev/null 2>&1; then
return 1
fi
sudo docker cp "$app_cid:${tmp_base}.jsonl" "${archive_base}.jsonl" >/dev/null 2>&1 || return 1
sudo docker cp "$app_cid:${tmp_base}.csv" "${archive_base}.csv" >/dev/null 2>&1 || return 1
sudo docker cp "$app_cid:${tmp_base}.meta.json" "${archive_base}.meta.json" >/dev/null 2>&1 || return 1
compose_cmd exec -T app sh -c "rm -f /tmp/Backup-Invoices.py ${tmp_base}.jsonl ${tmp_base}.csv ${tmp_base}.meta.json" >/dev/null 2>&1 || true
return 0
}
backup_invoices_archive() {
log_message "Starting invoice archive export..."
# Ensure archive dir exists and is writable
if [ ! -d "$INVOICE_ARCHIVE_DIR" ]; then
sudo mkdir -p "$INVOICE_ARCHIVE_DIR"
sudo chmod 755 "$INVOICE_ARCHIVE_DIR"
fi
local timestamp
timestamp="$(date +"%Y-%m-%d_%H-%M-%S")"
local archive_base
archive_base="$INVOICE_ARCHIVE_DIR/invoices-$timestamp"
timestamp="$(date +'%Y-%m-%d_%H-%M-%S')"
local output_dir
local remote_base
local py_exec
if [ -x "$PROJECT_DIR/.venv/bin/python" ]; then
py_exec="$PROJECT_DIR/.venv/bin/python"
else
py_exec="python3"
fi
output_dir="/tmp/invoice_archive_${timestamp}"
remote_base="${output_dir}/${BASE_NAME}"
local host_invoice_ok=0
local prefer_docker_invoice=0
if [ "$BACKUP_MODE" = "auto" ] && [ "$DOCKER_AVAILABLE" -eq 1 ] && [ "$MONGO_URI" = "mongodb://localhost:27017/" ]; then
prefer_docker_invoice=1
log_message "Auto mode: preferring Docker invoice archive export"
fi
docker compose -f "$COMPOSE_FILE" exec -T app mkdir -p "$output_dir"
docker compose -f "$COMPOSE_FILE" exec -T app python3 /app/Web/backup_invoices.py \
--archive-dir "$output_dir" \
--base-name "$BASE_NAME"
if [ "$BACKUP_MODE" != "docker" ] && [ "$prefer_docker_invoice" -ne 1 ]; then
if [ "${USE_NULL_OUTPUT:-false}" = true ]; then
if sudo -E "$py_exec" "$PROJECT_DIR/Backup-Invoices.py" --uri "$MONGO_URI" --db "$DB_NAME" --out "$archive_base" > /dev/null 2>&1; then
host_invoice_ok=1
fi
else
if sudo -E "$py_exec" "$PROJECT_DIR/Backup-Invoices.py" --uri "$MONGO_URI" --db "$DB_NAME" --out "$archive_base" >> "$PROJECT_DIR/logs/Backup_db.log" 2>&1; then
host_invoice_ok=1
fi
fi
fi
mkdir -p "$INVOICE_ARCHIVE_DIR"
docker cp "$app_container":"${remote_base}.jsonl" "$INVOICE_ARCHIVE_DIR/"
docker cp "$app_container":"${remote_base}.csv" "$INVOICE_ARCHIVE_DIR/"
docker cp "$app_container":"${remote_base}.meta.json" "$INVOICE_ARCHIVE_DIR/"
if [ "$host_invoice_ok" -ne 1 ]; then
if [ "$BACKUP_MODE" = "host" ]; then
log_message "ERROR: Failed to export invoice archive in host mode"
return 1
fi
if ! try_backup_invoices_docker "$archive_base"; then
log_message "ERROR: Failed to export invoice archive"
return 1
fi
fi
sudo chmod 640 "$archive_base".jsonl "$archive_base".csv "$archive_base".meta.json 2>/dev/null || true
log_message "Invoice archive written: $archive_base.(jsonl|csv|meta.json)"
# Retention cleanup for invoice archive (default: 10 years)
if [ "$INVOICE_KEEP_DAYS" -gt 0 ]; then
local deleted_count=0
while IFS= read -r old_file; do
[ -z "$old_file" ] && continue
if sudo rm -f "$old_file"; then
deleted_count=$((deleted_count + 1))
fi
done < <(find "$INVOICE_ARCHIVE_DIR" -maxdepth 1 -type f \
\( -name 'invoices-*.jsonl' -o -name 'invoices-*.csv' -o -name 'invoices-*.meta.json' \) \
-mtime +"$INVOICE_KEEP_DAYS" -print 2>/dev/null)
log_message "Invoice archive cleanup complete (retention: $INVOICE_KEEP_DAYS days, deleted files: $deleted_count)"
fi
return 0
docker compose -f "$COMPOSE_FILE" exec -T app rm -rf "$output_dir"
}
# Function to create backup
create_backup() {
log_message "Starting backup process..."
# Create date-formatted directory name
CURRENT_DATE=$(date +"%Y-%m-%d")
BACKUP_NAME="Inventarsystem-$CURRENT_DATE"
BACKUP_DIR="$BACKUP_BASE_DIR/$BACKUP_NAME"
BACKUP_ARCHIVE="$BACKUP_BASE_DIR/$BACKUP_NAME.tar.gz"
# Create backup directory if it doesn't exist
if [ ! -d "$BACKUP_BASE_DIR" ]; then
sudo mkdir -p "$BACKUP_BASE_DIR"
sudo chmod 755 "$BACKUP_BASE_DIR"
fi
# Remove existing backup with same date if it exists
if [ -d "$BACKUP_DIR" ]; then
log_message "Removing existing backup directory at $BACKUP_DIR"
sudo rm -rf "$BACKUP_DIR"
fi
if [ -f "$BACKUP_ARCHIVE" ]; then
log_message "Removing existing backup archive at $BACKUP_ARCHIVE"
sudo rm -f "$BACKUP_ARCHIVE"
fi
# Create a temporary directory for backup
log_message "Creating backup at $BACKUP_DIR"
sudo mkdir -p "$BACKUP_DIR"
# Copy project files excluding the backups directory itself (and optionally the venv)
if command -v rsync >/dev/null 2>&1; then
sudo rsync -a \
--exclude='backups' \
--exclude='.venv' \
"$PROJECT_DIR"/ "$BACKUP_DIR"/
else
# Fallback to cp while skipping the backups directory
for entry in "$PROJECT_DIR"/*; do
name=$(basename "$entry")
[[ "$name" == "backups" || "$name" == ".venv" ]] && continue
sudo cp -r "$entry" "$BACKUP_DIR/"
done
fi
# Create database backup
log_message "Running database backup..."
# Create mongodb_backup directory with appropriate permissions first
sudo mkdir -p "$BACKUP_DIR/mongodb_backup"
sudo chmod 755 "$BACKUP_DIR/mongodb_backup"
# Run database backup with our helper script
log_message "Executing database backup script..."
db_backup_ok=0
use_host_db_backup=1
if [ "$BACKUP_MODE" = "docker" ]; then
use_host_db_backup=0
elif [ "$BACKUP_MODE" = "auto" ] && [ "$DOCKER_AVAILABLE" -eq 1 ] && [ "$MONGO_URI" = "mongodb://localhost:27017/" ]; then
use_host_db_backup=0
log_message "Auto mode: preferring Docker DB backup"
main() {
if [[ -z "$BASE_NAME" ]]; then
BASE_NAME="invoices-$(date +'%Y-%m-%d_%H-%M-%S')"
fi
if [ "$use_host_db_backup" -eq 1 ]; then
# Create Backup_db.log and ensure it's writable only when host mode is used
touch "$PROJECT_DIR/logs/Backup_db.log" 2>/dev/null
chmod 666 "$PROJECT_DIR/logs/Backup_db.log" 2>/dev/null || {
log_message "WARNING: Failed to set permissions on Backup_db.log. Trying with sudo..."
sudo touch "$PROJECT_DIR/logs/Backup_db.log" 2>/dev/null
sudo chmod 666 "$PROJECT_DIR/logs/Backup_db.log" 2>/dev/null || {
log_message "WARNING: Failed to create writable Backup_db.log. Redirecting output to /dev/null instead."
USE_NULL_OUTPUT=true
}
}
ensure_directory
# Install pymongo only for host backups
log_message "Checking pymongo installation..."
if ! python3 -c "import pymongo" &>/dev/null; then
log_message "Installing pymongo..."
if [ -f "$PROJECT_DIR/.venv/bin/pip" ]; then
if [ "$USE_NULL_OUTPUT" = true ]; then
"$PROJECT_DIR/.venv/bin/pip" install pymongo==4.6.3 > /dev/null 2>&1 || true
else
"$PROJECT_DIR/.venv/bin/pip" install pymongo==4.6.3 >> "$PROJECT_DIR/logs/Backup_db.log" 2>&1 || true
fi
case "$MODE" in
auto)
if docker_backup; then
cleanup_old_archives
return 0
fi
if ! python3 -c "import pymongo" &>/dev/null; then
if [ "$USE_NULL_OUTPUT" = true ]; then
pip3 install pymongo==4.6.3 > /dev/null 2>&1 || true
else
pip3 install pymongo==4.6.3 >> "$PROJECT_DIR/logs/Backup_db.log" 2>&1 || true
fi
if host_backup; then
cleanup_old_archives
return 0
fi
fi
if [ "${USE_NULL_OUTPUT:-false}" = true ]; then
sudo -E "$PROJECT_DIR/run-backup.sh" --db "$DB_NAME" --uri "$MONGO_URI" --out "$BACKUP_DIR/mongodb_backup" > /dev/null 2>&1 && db_backup_ok=1 || {
log_message "ERROR: Failed to backup database with original path"
# Try an alternative approach - use a temporary directory
log_message "Attempting backup via temporary directory..."
tmp_backup_dir="/tmp/mongodb_backup_$$"
mkdir -p "$tmp_backup_dir"
"$PROJECT_DIR/run-backup.sh" --db "$DB_NAME" --uri "$MONGO_URI" --out "$tmp_backup_dir" > /dev/null 2>&1 && {
# Copy temporary backup files to the actual backup directory
log_message "Copying backup files from temporary directory..."
cp -r "$tmp_backup_dir"/* "$BACKUP_DIR/mongodb_backup/"
db_backup_ok=1
} || {
log_message "ERROR: All attempts to backup database failed"
}
}
else
sudo -E "$PROJECT_DIR/run-backup.sh" --db "$DB_NAME" --uri "$MONGO_URI" --out "$BACKUP_DIR/mongodb_backup" >> "$PROJECT_DIR/logs/Backup_db.log" 2>&1 && db_backup_ok=1 || {
log_message "ERROR: Failed to backup database with original path"
# Try an alternative approach - use a temporary directory
log_message "Attempting backup via temporary directory..."
tmp_backup_dir="/tmp/mongodb_backup_$$"
mkdir -p "$tmp_backup_dir"
"$PROJECT_DIR/run-backup.sh" --db "$DB_NAME" --uri "$MONGO_URI" --out "$tmp_backup_dir" >> "$PROJECT_DIR/logs/Backup_db.log" 2>&1 && {
# Copy temporary backup files to the actual backup directory
log_message "Copying backup files from temporary directory..."
cp -r "$tmp_backup_dir"/* "$BACKUP_DIR/mongodb_backup/"
db_backup_ok=1
} || {
log_message "ERROR: All attempts to backup database failed"
}
}
fi
else
log_message "Docker mode selected: skipping host DB backup step"
fi
# Auto-fallback for docker-first environments
if [ "$db_backup_ok" -ne 1 ] && [ "$BACKUP_MODE" != "host" ]; then
if try_backup_db_docker "$BACKUP_DIR/mongodb_backup"; then
db_backup_ok=1
fi
fi
# Check if any CSV files were created during backup
if ! find "$BACKUP_DIR/mongodb_backup" -name "*.csv" -quit; then
log_message "WARNING: No CSV files found in backup directory"
# If we used a temporary directory earlier and it still exists, try to use its contents
if [ -d "$tmp_backup_dir" ]; then
log_message "Backup created in temporary directory, moving to backup location..."
sudo cp -r "$tmp_backup_dir"/* "$BACKUP_DIR/mongodb_backup/"
rm -rf "$tmp_backup_dir"
fi
fi
# Reset to more restrictive permissions after backup completes
sudo chmod -R 755 "$BACKUP_DIR/mongodb_backup"
# Verify that backup files were created
csv_count=$(find "$BACKUP_DIR/mongodb_backup" -name "*.csv" 2>/dev/null | wc -l)
archive_count=$(find "$BACKUP_DIR/mongodb_backup" -name "*.archive.gz" 2>/dev/null | wc -l)
if [ "$csv_count" -gt 0 ] || [ "$archive_count" -gt 0 ]; then
log_message "Database backup successful: ${csv_count} CSV collection(s), ${archive_count} archive file(s)"
else
log_message "WARNING: No database backup files found in backup directory"
fi
# Export legal invoice archive separately with long retention
backup_invoices_archive || log_message "WARNING: Invoice archive export failed"
# Compress the backup
if [ "$COMPRESSION_LEVEL" -gt 0 ]; then
log_message "Compressing backup with level $COMPRESSION_LEVEL..."
# Create compressed archive with pigz when available, otherwise gzip.
if command -v pigz >/dev/null 2>&1; then
gzip_cmd="pigz -${COMPRESSION_LEVEL}"
else
gzip_cmd="gzip -${COMPRESSION_LEVEL}"
fi
if tar --help 2>/dev/null | grep -q -- "--use-compress-program"; then
sudo tar -cf "$BACKUP_ARCHIVE" -I "$gzip_cmd" -C "$BACKUP_BASE_DIR" "$BACKUP_NAME" || {
log_message "ERROR: Failed to compress backup (tar -I)"
return 1
}
else
GZIP="-${COMPRESSION_LEVEL}" sudo -E tar -czf "$BACKUP_ARCHIVE" -C "$BACKUP_BASE_DIR" "$BACKUP_NAME" || {
log_message "ERROR: Failed to compress backup (tar + GZIP env)"
return 1
}
fi
# Remove uncompressed directory after successful compression
if [ -f "$BACKUP_ARCHIVE" ]; then
log_message "Backup compressed successfully to $BACKUP_ARCHIVE"
log_message "Removing uncompressed backup directory"
sudo rm -rf "$BACKUP_DIR"
else
log_message "ERROR: Compressed backup file not found"
echo "Error: could not perform invoice backup in auto mode. Docker or host Python with pymongo is required." >&2
return 1
fi
else
log_message "Compression disabled, keeping uncompressed backup"
fi
# Set appropriate permissions
if [ "$COMPRESSION_LEVEL" -gt 0 ]; then
sudo chmod 644 "$BACKUP_ARCHIVE"
else
sudo chmod -R 755 "$BACKUP_DIR"
fi
# Clean up old backups: age-based filter plus minimum keep safeguard
log_message "Cleaning up old backups (keep at least $MIN_KEEP; days>$KEEP_DAYS)..."
# Build list of all backup artifacts (files and directories) sorted newest first
mapfile -t ALL_BACKUPS < <(find "$BACKUP_BASE_DIR" -maxdepth 1 \
\( -type f -name "Inventarsystem-*.tar.gz" -o -type d -name "Inventarsystem-*" \) \
-printf '%T@\t%p\n' 2>/dev/null | sort -nr | awk -F '\t' '{print $2}')
TOTAL=${#ALL_BACKUPS[@]}
if (( TOTAL > MIN_KEEP )); then
# Determine deletion candidates by age (if KEEP_DAYS>0), otherwise all except the newest MIN_KEEP
if (( KEEP_DAYS > 0 )); then
mapfile -t AGE_CANDIDATES < <(find "$BACKUP_BASE_DIR" -maxdepth 1 \
\( -type f -name "Inventarsystem-*.tar.gz" -o -type d -name "Inventarsystem-*" \) \
-mtime +"$KEEP_DAYS" -printf '%T@\t%p\n' 2>/dev/null | sort -n | awk -F '\t' '{print $2}')
else
AGE_CANDIDATES=()
# If no age filter, consider everything except the newest MIN_KEEP as candidates (oldest first)
if (( TOTAL > MIN_KEEP )); then
# Reverse ALL_BACKUPS to get oldest first
for ((i=TOTAL-1; i>=MIN_KEEP; i--)); do AGE_CANDIDATES+=("${ALL_BACKUPS[$i]}"); done
;;
docker)
if docker_backup; then
cleanup_old_archives
return 0
fi
fi
# Protect the newest MIN_KEEP backups overall
ALLOWED_DELETE=$(( TOTAL - MIN_KEEP ))
DELETED=0
for path in "${AGE_CANDIDATES[@]:-}"; do
(( DELETED >= ALLOWED_DELETE )) && break
if [ -z "$path" ]; then continue; fi
if [ -f "$path" ]; then
sudo rm -f "$path" && ((DELETED++))
elif [ -d "$path" ]; then
sudo rm -rf "$path" && ((DELETED++))
echo "Error: docker backup failed or app container is unavailable." >&2
return 1
;;
host)
if host_backup; then
cleanup_old_archives
return 0
fi
done
log_message "Deleted $DELETED old backup(s); kept $(( TOTAL - DELETED ))"
else
log_message "No cleanup needed (total backups: $TOTAL <= min-keep: $MIN_KEEP)"
fi
log_message "Backup completed successfully"
return 0
echo "Error: host backup failed. Ensure python3 and pymongo are installed." >&2
return 1
;;
*)
echo "Error: unsupported mode '$MODE'" >&2
usage
return 2
;;
esac
}
# Main
log_message "Backup destination: $BACKUP_BASE_DIR"
log_message "Database: $DB_NAME | URI: $MONGO_URI | Keep days: $KEEP_DAYS | Min keep: $MIN_KEEP | Compression: ${COMPRESSION_LEVEL}"
log_message "Invoice archive: $INVOICE_ARCHIVE_DIR | Invoice retention days: $INVOICE_KEEP_DAYS"
log_message "Backup mode: $BACKUP_MODE"
if [[ "$BACKUP_MODE" != "auto" && "$BACKUP_MODE" != "host" && "$BACKUP_MODE" != "docker" ]]; then
log_message "ERROR: Invalid --mode value '$BACKUP_MODE' (allowed: auto, host, docker)"
exit 2
fi
init_docker_compose || true
if create_backup; then
ART_DATE="$(date +"%Y-%m-%d")"
if [[ "$COMPRESSION_LEVEL" -gt 0 ]]; then
echo "$BACKUP_BASE_DIR/Inventarsystem-$ART_DATE.tar.gz"
else
echo "$BACKUP_BASE_DIR/Inventarsystem-$ART_DATE"
fi
fi
parse_args "$@"
main