.
This commit is contained in:
17
settings.env
Normal file
17
settings.env
Normal file
@@ -0,0 +1,17 @@
|
||||
# ==========================================
|
||||
# Portainer Stack Backup – Settings
|
||||
# ==========================================
|
||||
|
||||
# Portainer Verbindung
|
||||
PORTAINER_URL="https://192.168.178.25:9443"
|
||||
|
||||
# Portainer API Key
|
||||
API_KEY="ptr_QX6nhHj2A9wF/XD4wEQ6z/b+udOkIC3Ocao+bC1PP7M="
|
||||
|
||||
# Zielverzeichnis für Backups
|
||||
OUT_DIR="/home/thorsten/Schreibtisch/Backup"
|
||||
|
||||
# Archiv erstellen?
|
||||
# 1 = tar.gz erzeugen
|
||||
# 0 = nur Ordner behalten
|
||||
CREATE_ARCHIVE=1
|
||||
164
shell_portainer_stack_backup.sh
Normal file
164
shell_portainer_stack_backup.sh
Normal file
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
############################################
|
||||
# Portainer Stack Backup (quiet/basic logging)
|
||||
# Settings file: settings.env (same dir as script)
|
||||
# Required settings:
|
||||
# PORTAINER_URL
|
||||
# API_KEY
|
||||
# OUT_DIR
|
||||
# CREATE_ARCHIVE (0/1)
|
||||
#
|
||||
# Behavior:
|
||||
# - Always writes a log file in the run directory while running
|
||||
# - If CREATE_ARCHIVE=1: creates RUN_DIR.tar.gz and then removes RUN_DIR
|
||||
# - If CREATE_ARCHIVE=0: keeps RUN_DIR (and the log inside it)
|
||||
############################################
|
||||
|
||||
# ---------- load settings ----------
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SETTINGS_FILE="$SCRIPT_DIR/settings.env"
|
||||
|
||||
if [[ -f "$SETTINGS_FILE" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$SETTINGS_FILE"
|
||||
else
|
||||
echo "ERROR: settings.env not found in: $SCRIPT_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
: "${PORTAINER_URL:?PORTAINER_URL not set in settings.env}"
|
||||
: "${API_KEY:?API_KEY not set in settings.env}"
|
||||
: "${OUT_DIR:?OUT_DIR not set in settings.env}"
|
||||
: "${CREATE_ARCHIVE:?CREATE_ARCHIVE not set in settings.env}"
|
||||
|
||||
if [[ "$CREATE_ARCHIVE" != "0" && "$CREATE_ARCHIVE" != "1" ]]; then
|
||||
echo "ERROR: CREATE_ARCHIVE must be 0 or 1 (got: '$CREATE_ARCHIVE')" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
command -v curl >/dev/null 2>&1 || { echo "ERROR: curl missing"; exit 1; }
|
||||
command -v jq >/dev/null 2>&1 || { echo "ERROR: jq missing"; exit 1; }
|
||||
|
||||
# tar is only required when CREATE_ARCHIVE=1
|
||||
if [[ "$CREATE_ARCHIVE" == "1" ]]; then
|
||||
command -v tar >/dev/null 2>&1 || { echo "ERROR: tar missing (required because CREATE_ARCHIVE=1)"; exit 1; }
|
||||
fi
|
||||
|
||||
# ---------- output + logging ----------
|
||||
BASE_OUT="$OUT_DIR"
|
||||
RUN_NAME="portainer-stack-backup-$(date +%F_%H%M%S)"
|
||||
RUN_DIR="$BASE_OUT/$RUN_NAME"
|
||||
STACKS_DIR="$RUN_DIR/stacks"
|
||||
mkdir -p "$STACKS_DIR"
|
||||
|
||||
LOG_FILE="$RUN_DIR/backup.log"
|
||||
# Mirror ALL output (stdout+stderr) to terminal + logfile
|
||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||
|
||||
# ---------- minimal log helpers ----------
|
||||
say() { echo "$*"; }
|
||||
ok() { echo "[OK] $*"; }
|
||||
fail() { echo "[FAIL] $*"; }
|
||||
|
||||
# ---------- curl defaults ----------
|
||||
# -k to tolerate self-signed certs on 9443
|
||||
CURL_BASE=(curl -skS)
|
||||
HDR=(-H "X-API-Key: ${API_KEY}")
|
||||
|
||||
############################################
|
||||
# Header
|
||||
############################################
|
||||
say "Portainer Stack Backup started"
|
||||
say "Target: $RUN_DIR"
|
||||
say
|
||||
|
||||
############################################
|
||||
# Preflight
|
||||
############################################
|
||||
if "${CURL_BASE[@]}" "$PORTAINER_URL/api/status" >/dev/null; then
|
||||
say "Portainer reachable"
|
||||
else
|
||||
say "Portainer not reachable"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if "${CURL_BASE[@]}" "${HDR[@]}" "$PORTAINER_URL/api/users/me" >/dev/null; then
|
||||
say "API key valid"
|
||||
else
|
||||
say "API key invalid"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
stacks_json="$("${CURL_BASE[@]}" "${HDR[@]}" "$PORTAINER_URL/api/stacks")"
|
||||
if ! echo "$stacks_json" | jq -e 'type=="array"' >/dev/null 2>&1; then
|
||||
say "Unexpected response while fetching stacks (not a JSON array)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
stack_count="$(echo "$stacks_json" | jq 'length')"
|
||||
say "Stacks found: $stack_count"
|
||||
say
|
||||
say "Starting backup"
|
||||
say
|
||||
|
||||
############################################
|
||||
# Export stacks (one line per stack)
|
||||
############################################
|
||||
ok_count=0
|
||||
fail_count=0
|
||||
|
||||
while IFS= read -r s; do
|
||||
id="$(echo "$s" | jq -r '.Id')"
|
||||
name_raw="$(echo "$s" | jq -r '.Name')"
|
||||
endpoint="$(echo "$s" | jq -r '.EndpointId')"
|
||||
|
||||
# sanitize name for filesystem
|
||||
name_fs="$(echo "$name_raw" | tr '/ ' '__' | tr -cd '[:alnum:]_.-')"
|
||||
[[ -n "$name_fs" ]] || name_fs="stack_${id}"
|
||||
|
||||
stack_dir="$STACKS_DIR/$name_fs"
|
||||
mkdir -p "$stack_dir"
|
||||
|
||||
# minimal meta (helps mapping)
|
||||
echo "$s" | jq '.' > "$stack_dir/meta.json"
|
||||
|
||||
# fetch stack file and extract StackFileContent
|
||||
file_json="$("${CURL_BASE[@]}" "${HDR[@]}" \
|
||||
"$PORTAINER_URL/api/stacks/$id/file?endpointId=$endpoint" 2>/dev/null || true)"
|
||||
|
||||
content="$(echo "$file_json" | jq -r '.StackFileContent // empty' 2>/dev/null || true)"
|
||||
|
||||
if [[ -n "$content" && "$content" != "null" ]]; then
|
||||
printf "%s\n" "$content" > "$stack_dir/docker-compose.yml"
|
||||
ok "$name_raw"
|
||||
ok_count=$((ok_count+1))
|
||||
else
|
||||
fail "$name_raw"
|
||||
fail_count=$((fail_count+1))
|
||||
# keep raw response for troubleshooting (quiet, but useful)
|
||||
printf "%s\n" "$file_json" > "$stack_dir/_file_response.json" 2>/dev/null || true
|
||||
fi
|
||||
done < <(echo "$stacks_json" | jq -c '.[]')
|
||||
|
||||
say
|
||||
say "Backup finished: OK=$ok_count FAIL=$fail_count"
|
||||
|
||||
############################################
|
||||
# Optional archive + cleanup
|
||||
############################################
|
||||
if [[ "$CREATE_ARCHIVE" == "1" ]]; then
|
||||
tarball="${RUN_DIR}.tar.gz"
|
||||
tar -C "$RUN_DIR" -czf "$tarball" .
|
||||
say "Archive created: $(basename "$tarball")"
|
||||
|
||||
# Cleanup: remove the run directory after successful archive creation
|
||||
rm -rf "$RUN_DIR"
|
||||
say "Cleanup done: removed $RUN_DIR"
|
||||
else
|
||||
say "Keeping directory: $RUN_DIR"
|
||||
say "Log: $LOG_FILE"
|
||||
fi
|
||||
|
||||
say "Done."
|
||||
Reference in New Issue
Block a user