Add AppFactory restore script for server migration
This commit is contained in:
+139
-92
@@ -1,116 +1,163 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
CONFIG_FILE="/opt/appfactory/config/appfactory.env"
|
APPFACTORY_ROOT="/opt/appfactory"
|
||||||
|
|
||||||
if [ ! -f "$CONFIG_FILE" ]; then
|
|
||||||
echo "Missing config file: $CONFIG_FILE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
source "$CONFIG_FILE"
|
|
||||||
|
|
||||||
FORCE="false"
|
|
||||||
BACKUP_FILE=""
|
|
||||||
|
|
||||||
if [ "${1:-}" = "--force" ]; then
|
|
||||||
FORCE="true"
|
|
||||||
BACKUP_FILE="${2:-}"
|
|
||||||
else
|
|
||||||
BACKUP_FILE="${1:-}"
|
BACKUP_FILE="${1:-}"
|
||||||
fi
|
TARGET_USER="${2:-${SUDO_USER:-$USER}}"
|
||||||
|
|
||||||
if [ "$FORCE" != "true" ]; then
|
fail() {
|
||||||
echo "REFUSING TO RUN RESTORE."
|
echo "[restore][FAIL] $*" >&2
|
||||||
echo ""
|
|
||||||
echo "Usage:"
|
|
||||||
echo " sudo ./restore-appfactory.sh --force /path/to/backup.tar.gz"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
}
|
||||||
|
|
||||||
|
ok() {
|
||||||
|
echo "[restore][OK] $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "[restore] $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<USAGE
|
||||||
|
Usage:
|
||||||
|
sudo ./restore-appfactory.sh /path/to/appfactory-backup-YYYYMMDD-HHMMSS.tar.gz [target_user]
|
||||||
|
|
||||||
|
Example:
|
||||||
|
sudo ./restore-appfactory.sh ./appfactory-backup-20260608-095020.tar.gz karel
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
if [ -z "$BACKUP_FILE" ]; then
|
if [ -z "$BACKUP_FILE" ]; then
|
||||||
echo "Missing backup file."
|
usage
|
||||||
exit 1
|
fail "Backup file argument is required"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -f "$BACKUP_FILE" ]; then
|
if [ ! -f "$BACKUP_FILE" ]; then
|
||||||
echo "Backup file not found: $BACKUP_FILE"
|
fail "Backup file not found: $BACKUP_FILE"
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "======================================================"
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
echo "DANGEROUS OPERATION: APPFACTORY RESTORE"
|
fail "Run as root via sudo"
|
||||||
echo "======================================================"
|
|
||||||
echo ""
|
|
||||||
echo "Backup file:"
|
|
||||||
echo " $BACKUP_FILE"
|
|
||||||
echo ""
|
|
||||||
echo "This will overwrite:"
|
|
||||||
echo " $APPFACTORY_DIR"
|
|
||||||
echo " $WORKSPACE_DIR"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
read -r -p "Type RESTORE to continue: " CONFIRM
|
|
||||||
|
|
||||||
if [ "$CONFIRM" != "RESTORE" ]; then
|
|
||||||
echo "Cancelled."
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
|
if ! id "$TARGET_USER" >/dev/null 2>&1; then
|
||||||
EMERGENCY_BACKUP_FILE="$BACKUP_DIR/emergency-before-restore-$TIMESTAMP.tar.gz"
|
fail "Target user does not exist: $TARGET_USER"
|
||||||
|
|
||||||
mkdir -p "$BACKUP_DIR"
|
|
||||||
|
|
||||||
echo "Creating emergency backup:"
|
|
||||||
echo "$EMERGENCY_BACKUP_FILE"
|
|
||||||
|
|
||||||
tar -czf "$EMERGENCY_BACKUP_FILE" \
|
|
||||||
"$APPFACTORY_DIR" \
|
|
||||||
"$WORKSPACE_DIR"
|
|
||||||
|
|
||||||
echo "Stopping AppFactory containers..."
|
|
||||||
|
|
||||||
if [ -d "$APPFACTORY_DIR/deploy" ]; then
|
|
||||||
cd "$APPFACTORY_DIR/deploy"
|
|
||||||
docker compose -f docker-compose.yml -f apps-compose.yml down || true
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Restoring backup..."
|
command -v tar >/dev/null 2>&1 || fail "tar missing"
|
||||||
tar -xzf "$BACKUP_FILE" -C /
|
command -v docker >/dev/null 2>&1 || fail "docker missing"
|
||||||
|
|
||||||
echo "Fixing ownership..."
|
APPFACTORY_UID="$(id -u "$TARGET_USER")"
|
||||||
chown -R "$APP_USER:$APP_GROUP" "$APPFACTORY_DIR"
|
APPFACTORY_GID="$(id -g "$TARGET_USER")"
|
||||||
chown -R "$APP_USER:$APP_GROUP" "$WORKSPACE_DIR"
|
|
||||||
chmod 600 "$APPFACTORY_DIR/config/appfactory.env" || true
|
|
||||||
|
|
||||||
echo "Starting core services..."
|
if [ ! -S /var/run/docker.sock ]; then
|
||||||
cd "$APPFACTORY_DIR/deploy"
|
fail "Docker socket missing: /var/run/docker.sock"
|
||||||
docker compose -f docker-compose.yml up -d
|
|
||||||
|
|
||||||
TOOLS_DIR="$WORKSPACE_DIR/appfactory-tools"
|
|
||||||
|
|
||||||
if [ -d "$TOOLS_DIR" ]; then
|
|
||||||
cd "$TOOLS_DIR"
|
|
||||||
|
|
||||||
./scripts/generate-apps-compose.sh || true
|
|
||||||
./scripts/generate-caddyfile.sh || true
|
|
||||||
./scripts/generate-apps-page.sh || true
|
|
||||||
|
|
||||||
for app_dir in "$WORKSPACE_DIR"/*; do
|
|
||||||
if [ -f "$app_dir/app.yml" ]; then
|
|
||||||
app_id="$(basename "$app_dir")"
|
|
||||||
echo "Rebuilding app: $app_id"
|
|
||||||
./scripts/deploy-app.sh "$app_id" || true
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
APPFACTORY_DOCKER_GID="$(stat -c '%g' /var/run/docker.sock)"
|
||||||
|
|
||||||
|
log "Backup: $BACKUP_FILE"
|
||||||
|
log "Target user: $TARGET_USER"
|
||||||
|
log "APPFACTORY_UID=$APPFACTORY_UID"
|
||||||
|
log "APPFACTORY_GID=$APPFACTORY_GID"
|
||||||
|
log "APPFACTORY_DOCKER_GID=$APPFACTORY_DOCKER_GID"
|
||||||
|
|
||||||
|
log "Stopping existing AppFactory containers if present"
|
||||||
|
docker ps -a --format '{{.Names}}' | grep -E '^(appfactory-|.*-service$|test-|microsoft-365-service)' | while read -r container; do
|
||||||
|
[ -n "$container" ] || continue
|
||||||
|
docker rm -f "$container" >/dev/null 2>&1 || true
|
||||||
|
echo "[restore] Removed container: $container"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
log "Removing existing AppFactory root"
|
||||||
|
rm -rf "$APPFACTORY_ROOT"
|
||||||
|
|
||||||
|
log "Recreating AppFactory root"
|
||||||
|
mkdir -p "$APPFACTORY_ROOT"
|
||||||
|
|
||||||
|
log "Extracting backup"
|
||||||
|
cd /
|
||||||
|
tar -xzf "$BACKUP_FILE"
|
||||||
|
|
||||||
|
if [ ! -d "$APPFACTORY_ROOT" ]; then
|
||||||
|
fail "Backup did not create $APPFACTORY_ROOT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Final compose up..."
|
log "Ensuring required directories"
|
||||||
cd "$APPFACTORY_DIR/deploy"
|
mkdir -p \
|
||||||
docker compose -f docker-compose.yml -f apps-compose.yml up -d
|
"$APPFACTORY_ROOT/apps" \
|
||||||
|
"$APPFACTORY_ROOT/backups" \
|
||||||
|
"$APPFACTORY_ROOT/config" \
|
||||||
|
"$APPFACTORY_ROOT/data" \
|
||||||
|
"$APPFACTORY_ROOT/deploy" \
|
||||||
|
"$APPFACTORY_ROOT/gateway" \
|
||||||
|
"$APPFACTORY_ROOT/services" \
|
||||||
|
"$APPFACTORY_ROOT/templates" \
|
||||||
|
"$APPFACTORY_ROOT/tools" \
|
||||||
|
"$APPFACTORY_ROOT/workspace"
|
||||||
|
|
||||||
echo ""
|
ENV_FILE="$APPFACTORY_ROOT/config/appfactory.env"
|
||||||
echo "DONE"
|
|
||||||
echo "Emergency backup:"
|
if [ ! -f "$ENV_FILE" ]; then
|
||||||
echo "$EMERGENCY_BACKUP_FILE"
|
fail "Missing env file after restore: $ENV_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Updating runtime ids in appfactory.env"
|
||||||
|
|
||||||
|
set_env_value() {
|
||||||
|
local key="$1"
|
||||||
|
local value="$2"
|
||||||
|
|
||||||
|
if grep -q "^${key}=" "$ENV_FILE"; then
|
||||||
|
sed -i "s|^${key}=.*|${key}=${value}|" "$ENV_FILE"
|
||||||
|
else
|
||||||
|
echo "${key}=${value}" >> "$ENV_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
set_env_value "APPFACTORY_UID" "$APPFACTORY_UID"
|
||||||
|
set_env_value "APPFACTORY_GID" "$APPFACTORY_GID"
|
||||||
|
set_env_value "APPFACTORY_DOCKER_GID" "$APPFACTORY_DOCKER_GID"
|
||||||
|
|
||||||
|
grep -q '^APPFACTORY_ENABLE_HTTPS=' "$ENV_FILE" || echo 'APPFACTORY_ENABLE_HTTPS=false' >> "$ENV_FILE"
|
||||||
|
grep -q '^APPFACTORY_DOMAIN=' "$ENV_FILE" || echo 'APPFACTORY_DOMAIN=' >> "$ENV_FILE"
|
||||||
|
grep -q '^APPFACTORY_PORTAL_DOMAIN=' "$ENV_FILE" || echo 'APPFACTORY_PORTAL_DOMAIN=' >> "$ENV_FILE"
|
||||||
|
grep -q '^APPFACTORY_GITEA_DOMAIN=' "$ENV_FILE" || echo 'APPFACTORY_GITEA_DOMAIN=' >> "$ENV_FILE"
|
||||||
|
grep -q '^APPFACTORY_REGISTRY_DOMAIN=' "$ENV_FILE" || echo 'APPFACTORY_REGISTRY_DOMAIN=' >> "$ENV_FILE"
|
||||||
|
grep -q '^GOOGLE_CLIENT_ID=' "$ENV_FILE" || echo 'GOOGLE_CLIENT_ID=' >> "$ENV_FILE"
|
||||||
|
grep -q '^GOOGLE_CLIENT_SECRET=' "$ENV_FILE" || echo 'GOOGLE_CLIENT_SECRET=' >> "$ENV_FILE"
|
||||||
|
grep -q '^GOOGLE_REDIRECT_URI=' "$ENV_FILE" || echo 'GOOGLE_REDIRECT_URI=' >> "$ENV_FILE"
|
||||||
|
|
||||||
|
log "Fixing ownership"
|
||||||
|
chown -R "$APPFACTORY_UID:$APPFACTORY_GID" "$APPFACTORY_ROOT"
|
||||||
|
|
||||||
|
log "Fixing script permissions"
|
||||||
|
find "$APPFACTORY_ROOT/workspace/appfactory-tools" -type f -name '*.sh' -exec chmod +x {} \; 2>/dev/null || true
|
||||||
|
|
||||||
|
BOOTSTRAP="$APPFACTORY_ROOT/workspace/appfactory-tools/maintenance/bootstrap-v2.sh"
|
||||||
|
PREFLIGHT="$APPFACTORY_ROOT/workspace/appfactory-tools/maintenance/preflight-check.sh"
|
||||||
|
GENERATE_CADDY="$APPFACTORY_ROOT/workspace/appfactory-tools/scripts/generate-caddyfile.sh"
|
||||||
|
|
||||||
|
[ -f "$BOOTSTRAP" ] || fail "Missing bootstrap script: $BOOTSTRAP"
|
||||||
|
[ -f "$PREFLIGHT" ] || fail "Missing preflight script: $PREFLIGHT"
|
||||||
|
|
||||||
|
chmod +x "$BOOTSTRAP" "$PREFLIGHT"
|
||||||
|
[ -f "$GENERATE_CADDY" ] && chmod +x "$GENERATE_CADDY"
|
||||||
|
|
||||||
|
log "Running bootstrap repair"
|
||||||
|
sudo -u "$TARGET_USER" "$BOOTSTRAP" repair
|
||||||
|
|
||||||
|
if [ -f "$GENERATE_CADDY" ]; then
|
||||||
|
log "Generating Caddyfile"
|
||||||
|
sudo -u "$TARGET_USER" "$GENERATE_CADDY" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Running bootstrap deploy"
|
||||||
|
sudo -u "$TARGET_USER" "$BOOTSTRAP" deploy
|
||||||
|
|
||||||
|
log "Running final preflight"
|
||||||
|
sudo -u "$TARGET_USER" "$PREFLIGHT"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "APPFACTORY RESTORE: READY"
|
||||||
|
|||||||
Reference in New Issue
Block a user