Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
This commit is contained in:
@@ -0,0 +1,398 @@
|
||||
#!/usr/bin/env bash
|
||||
# caprover-backup.sh — Comprehensive CapRover backup script
|
||||
# Author: Phil Connor <contact@mylinux.work>
|
||||
# License: MIT
|
||||
# Version: 1.11
|
||||
#
|
||||
# Backs up /captain config, Docker volumes (captain-- prefixed),
|
||||
# and app definitions via CapRover API.
|
||||
# Supports local, NFS, and S3 (via aws cli or rclone) destinations.
|
||||
#
|
||||
# Migration mode (--migrate) stops all CapRover app containers before
|
||||
# backing up volumes, ensuring database-consistent snapshots. Produces
|
||||
# a single migration tarball for transfer to a new server.
|
||||
#
|
||||
# Usage:
|
||||
# ./caprover-backup.sh # local backup to /backups/caprover
|
||||
# ./caprover-backup.sh --migrate # full server migration (stops containers)
|
||||
# BACKUP_DEST=s3 S3_BUCKET=my-bucket ./caprover-backup.sh
|
||||
# BACKUP_DEST=rclone RCLONE_REMOTE=myremote:backups ./caprover-backup.sh
|
||||
# BACKUP_DEST=nfs NFS_MOUNT=/mnt/nfs/backups ./caprover-backup.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Configuration — override via environment variables
|
||||
# ---------------------------------------------------------------------------
|
||||
BACKUP_DIR="${BACKUP_DIR:-/backups/caprover}"
|
||||
BACKUP_DEST="${BACKUP_DEST:-local}" # local | nfs | s3 | rclone
|
||||
RETENTION_DAYS="${RETENTION_DAYS:-30}"
|
||||
DATE=$(date +%Y%m%d-%H%M%S)
|
||||
LOG_FILE="${LOG_FILE:-/var/log/caprover-backup.log}"
|
||||
MIGRATE=false
|
||||
|
||||
# CapRover API settings (for app definition export)
|
||||
CAPROVER_URL="${CAPROVER_URL:-https://captain.apps.example.com}"
|
||||
CAPROVER_PASSWORD="${CAPROVER_PASSWORD:-}"
|
||||
|
||||
# S3 settings
|
||||
S3_BUCKET="${S3_BUCKET:-}"
|
||||
S3_PREFIX="${S3_PREFIX:-caprover-backups}"
|
||||
|
||||
# rclone settings
|
||||
RCLONE_REMOTE="${RCLONE_REMOTE:-}"
|
||||
|
||||
# NFS settings
|
||||
NFS_MOUNT="${NFS_MOUNT:-/mnt/nfs/backups}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Logging
|
||||
# ---------------------------------------------------------------------------
|
||||
log() {
|
||||
local msg
|
||||
msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
||||
echo "$msg" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
log "ERROR: $1"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Argument parsing
|
||||
# ---------------------------------------------------------------------------
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--migrate) MIGRATE=true ;;
|
||||
-h|--help)
|
||||
sed -n '2,/^$/{ s/^# \?//; p }' "$0"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pre-flight checks
|
||||
# ---------------------------------------------------------------------------
|
||||
preflight() {
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
log_error "Run as root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v docker &>/dev/null; then
|
||||
log_error "Docker not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$BACKUP_DEST" = "s3" ] && ! command -v aws &>/dev/null; then
|
||||
log_error "aws CLI not found. Install it or use BACKUP_DEST=rclone."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$BACKUP_DEST" = "rclone" ] && ! command -v rclone &>/dev/null; then
|
||||
log_error "rclone not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Backup /captain directory
|
||||
# ---------------------------------------------------------------------------
|
||||
backup_captain_config() {
|
||||
log "Backing up /captain directory..."
|
||||
local dest="${BACKUP_DIR}/captain-config-${DATE}.tar.gz"
|
||||
|
||||
if [ ! -d /captain ]; then
|
||||
log_error "/captain directory not found. Is CapRover installed?"
|
||||
return 1
|
||||
fi
|
||||
|
||||
tar czf "$dest" -C / captain
|
||||
log "Captain config saved: $dest ($(du -sh "$dest" | cut -f1))"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Backup Docker volumes (captain-- prefixed)
|
||||
# ---------------------------------------------------------------------------
|
||||
backup_volumes() {
|
||||
log "Backing up Docker volumes..."
|
||||
local volumes
|
||||
volumes=$(docker volume ls -q | grep "^captain--" || true)
|
||||
|
||||
if [ -z "$volumes" ]; then
|
||||
log "No captain-- volumes found. Skipping."
|
||||
return 0
|
||||
fi
|
||||
|
||||
for vol in $volumes; do
|
||||
local app_name="${vol#captain--}"
|
||||
local dest="${BACKUP_DIR}/vol-${app_name}-${DATE}.tar.gz"
|
||||
|
||||
log " Backing up volume: $vol"
|
||||
docker run --rm \
|
||||
-v "${vol}:/source:ro" \
|
||||
-v "${BACKUP_DIR}:/backup" \
|
||||
alpine tar czf "/backup/vol-${app_name}-${DATE}.tar.gz" -C /source .
|
||||
|
||||
log " Volume $vol saved: $dest ($(du -sh "$dest" | cut -f1))"
|
||||
done
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Export app definitions via CapRover API
|
||||
# ---------------------------------------------------------------------------
|
||||
export_app_definitions() {
|
||||
if [ -z "$CAPROVER_PASSWORD" ]; then
|
||||
log "CAPROVER_PASSWORD not set. Skipping API export."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Exporting app definitions via CapRover API..."
|
||||
|
||||
# Get auth token
|
||||
local token
|
||||
token=$(curl -s -X POST "${CAPROVER_URL}/api/v2/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "x-namespace: captain" \
|
||||
-d "{\"password\":\"${CAPROVER_PASSWORD}\"}" \
|
||||
| python3 -c "import sys,json; print(json.load(sys.stdin)['data']['token'])" 2>/dev/null) || true
|
||||
|
||||
if [ -z "$token" ]; then
|
||||
log_error "Failed to authenticate with CapRover API."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Export app definitions
|
||||
local dest="${BACKUP_DIR}/app-definitions-${DATE}.json"
|
||||
local http_code
|
||||
http_code=$(curl -s -o "$dest" -w "%{http_code}" \
|
||||
"${CAPROVER_URL}/api/v2/user/apps/appDefinitions" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "x-namespace: captain" \
|
||||
-H "x-captain-auth: ${token}")
|
||||
|
||||
if [ "$http_code" = "200" ]; then
|
||||
log "App definitions saved: $dest"
|
||||
else
|
||||
log_error "API returned HTTP $http_code. App definitions export failed."
|
||||
rm -f "$dest"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Upload to remote destination
|
||||
# ---------------------------------------------------------------------------
|
||||
upload_remote() {
|
||||
case "$BACKUP_DEST" in
|
||||
local)
|
||||
log "Backup stored locally at $BACKUP_DIR"
|
||||
;;
|
||||
nfs)
|
||||
log "Copying backups to NFS mount: $NFS_MOUNT"
|
||||
if ! mountpoint -q "$NFS_MOUNT" 2>/dev/null; then
|
||||
log_error "$NFS_MOUNT is not mounted."
|
||||
return 1
|
||||
fi
|
||||
mkdir -p "${NFS_MOUNT}/caprover"
|
||||
cp "${BACKUP_DIR}"/*-"${DATE}"* "${NFS_MOUNT}/caprover/"
|
||||
log "Copied to NFS."
|
||||
;;
|
||||
s3)
|
||||
log "Uploading backups to S3: s3://${S3_BUCKET}/${S3_PREFIX}/"
|
||||
for f in "${BACKUP_DIR}"/*-"${DATE}"*; do
|
||||
aws s3 cp "$f" "s3://${S3_BUCKET}/${S3_PREFIX}/$(basename "$f")" --quiet
|
||||
done
|
||||
log "S3 upload complete."
|
||||
;;
|
||||
rclone)
|
||||
log "Uploading backups via rclone to: $RCLONE_REMOTE"
|
||||
for f in "${BACKUP_DIR}"/*-"${DATE}"*; do
|
||||
rclone copy "$f" "$RCLONE_REMOTE" --quiet
|
||||
done
|
||||
log "rclone upload complete."
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown BACKUP_DEST: $BACKUP_DEST"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Retention — delete local backups older than RETENTION_DAYS
|
||||
# ---------------------------------------------------------------------------
|
||||
apply_retention() {
|
||||
log "Applying retention policy: deleting backups older than ${RETENTION_DAYS} days..."
|
||||
local count
|
||||
count=$(find "$BACKUP_DIR" -name "*.tar.gz" -o -name "*.json" | \
|
||||
xargs -I{} find {} -mtime +"$RETENTION_DAYS" 2>/dev/null | wc -l)
|
||||
|
||||
find "$BACKUP_DIR" \( -name "*.tar.gz" -o -name "*.json" \) \
|
||||
-mtime +"$RETENTION_DAYS" -delete
|
||||
|
||||
log "Removed $count old backup file(s)."
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Migration — stop all CapRover app containers
|
||||
# ---------------------------------------------------------------------------
|
||||
stop_captain_containers() {
|
||||
log "Stopping all CapRover app containers..."
|
||||
local containers
|
||||
containers=$(docker ps -q --filter "label=com.docker.swarm.service.name" \
|
||||
--filter "name=srv-captain--" 2>/dev/null || true)
|
||||
|
||||
if [ -z "$containers" ]; then
|
||||
# fallback: stop services via Docker Swarm
|
||||
local services
|
||||
services=$(docker service ls -q --filter "name=srv-captain--" 2>/dev/null || true)
|
||||
if [ -n "$services" ]; then
|
||||
local count=0
|
||||
for svc in $services; do
|
||||
local svc_name
|
||||
svc_name=$(docker service inspect --format '{{.Spec.Name}}' "$svc")
|
||||
log " Scaling down: $svc_name"
|
||||
docker service scale "$svc_name=0" --detach 2>/dev/null
|
||||
((count++)) || true
|
||||
done
|
||||
log "Scaled down $count service(s). Waiting 10s for graceful shutdown..."
|
||||
sleep 10
|
||||
else
|
||||
log "No CapRover app services found."
|
||||
fi
|
||||
else
|
||||
local count
|
||||
count=$(echo "$containers" | wc -w)
|
||||
docker stop $containers
|
||||
log "Stopped $count container(s). Waiting 5s..."
|
||||
sleep 5
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Migration — record service state for restore on new server
|
||||
# ---------------------------------------------------------------------------
|
||||
save_service_state() {
|
||||
log "Saving Docker service state..."
|
||||
local dest="${BACKUP_DIR}/service-state-${DATE}.json"
|
||||
docker service ls --format '{{json .}}' > "$dest"
|
||||
log "Service state saved: $dest"
|
||||
|
||||
# Save docker info for reference (Swarm tokens, node info)
|
||||
local info_dest="${BACKUP_DIR}/docker-info-${DATE}.txt"
|
||||
docker info > "$info_dest" 2>&1
|
||||
docker node ls >> "$info_dest" 2>/dev/null || true
|
||||
log "Docker info saved: $info_dest"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Migration — package everything into a single tarball
|
||||
# ---------------------------------------------------------------------------
|
||||
create_migration_bundle() {
|
||||
local bundle="${BACKUP_DIR}/caprover-migration-${DATE}.tar.gz"
|
||||
log "Creating migration bundle..."
|
||||
|
||||
# Collect all files from this run
|
||||
tar czf "$bundle" -C "$BACKUP_DIR" \
|
||||
$(ls -1 "$BACKUP_DIR" | grep "$DATE" | grep -v "caprover-migration")
|
||||
|
||||
local size
|
||||
size=$(du -sh "$bundle" | cut -f1)
|
||||
log "Migration bundle ready: $bundle ($size)"
|
||||
log ""
|
||||
log "========================================="
|
||||
log " MIGRATION INSTRUCTIONS"
|
||||
log "========================================="
|
||||
log "1. Copy bundle to new server:"
|
||||
log " scp $bundle root@new-server:/backups/"
|
||||
log ""
|
||||
log "2. On the new server, install CapRover:"
|
||||
log " docker run -p 80:80 -p 443:443 -p 3000:3000 \\"
|
||||
log " -e ACCEPTED_TERMS=true -v /captain:/captain \\"
|
||||
log " caprover/caprover-edge"
|
||||
log ""
|
||||
log "3. Extract the bundle:"
|
||||
log " mkdir -p /backups/restore && cd /backups/restore"
|
||||
log " tar xzf caprover-migration-${DATE}.tar.gz"
|
||||
log ""
|
||||
log "4. Stop CapRover on new server:"
|
||||
log " docker service rm captain-captain --force"
|
||||
log ""
|
||||
log "5. Restore /captain config:"
|
||||
log " tar xzf captain-config-${DATE}.tar.gz -C /"
|
||||
log ""
|
||||
log "6. Restore Docker volumes:"
|
||||
log " for f in vol-*-${DATE}.tar.gz; do"
|
||||
log ' vol="captain--${f#vol-}"'
|
||||
log ' vol="${vol%-'"${DATE}"'.tar.gz}"'
|
||||
log " docker volume create \"\$vol\""
|
||||
log " docker run --rm -v \"\${vol}:/dest\" -v \"\$(pwd):/backup:ro\" \\"
|
||||
log " alpine sh -c \"tar xzf /backup/\$f -C /dest\""
|
||||
log " done"
|
||||
log ""
|
||||
log "7. Start CapRover and re-deploy apps:"
|
||||
log " docker run -p 80:80 -p 443:443 -p 3000:3000 \\"
|
||||
log " -e ACCEPTED_TERMS=true -v /captain:/captain \\"
|
||||
log " caprover/caprover-edge"
|
||||
log ""
|
||||
log "8. Update DNS to point to the new server IP."
|
||||
log "========================================="
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
main() {
|
||||
if $MIGRATE; then
|
||||
log "========================================="
|
||||
log "CapRover MIGRATION backup — ${DATE}"
|
||||
log "Mode: full migration (containers will be stopped)"
|
||||
log "========================================="
|
||||
|
||||
preflight
|
||||
local errors=0
|
||||
|
||||
export_app_definitions || { ((errors++)) || true; }
|
||||
save_service_state || { ((errors++)) || true; }
|
||||
stop_captain_containers
|
||||
backup_captain_config || { ((errors++)) || true; }
|
||||
backup_volumes || { ((errors++)) || true; }
|
||||
create_migration_bundle
|
||||
|
||||
if [ "$errors" -gt 0 ]; then
|
||||
log "Migration backup completed with $errors error(s)."
|
||||
exit 1
|
||||
else
|
||||
log "Migration backup completed successfully."
|
||||
log "Containers remain stopped. This server is ready to decommission."
|
||||
fi
|
||||
else
|
||||
log "========================================="
|
||||
log "CapRover backup started — ${DATE}"
|
||||
log "Destination: ${BACKUP_DEST}"
|
||||
log "========================================="
|
||||
|
||||
preflight
|
||||
local errors=0
|
||||
|
||||
backup_captain_config || { ((errors++)) || true; }
|
||||
backup_volumes || { ((errors++)) || true; }
|
||||
export_app_definitions || { ((errors++)) || true; }
|
||||
upload_remote || { ((errors++)) || true; }
|
||||
apply_retention
|
||||
|
||||
if [ "$errors" -gt 0 ]; then
|
||||
log "Backup completed with $errors error(s)."
|
||||
exit 1
|
||||
else
|
||||
log "Backup completed successfully."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user