Make restore and bootstrap work on clean server
This commit is contained in:
@@ -2,89 +2,62 @@
|
||||
set -euo pipefail
|
||||
|
||||
APPFACTORY_ROOT="/opt/appfactory"
|
||||
BACKUP_FILE="${1:-}"
|
||||
BACKUP_FILE_INPUT="${1:-}"
|
||||
TARGET_USER="${2:-${SUDO_USER:-$USER}}"
|
||||
|
||||
fail() {
|
||||
echo "[restore][FAIL] $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
ok() {
|
||||
echo "[restore][OK] $*"
|
||||
}
|
||||
|
||||
log() {
|
||||
echo "[restore] $*"
|
||||
}
|
||||
log(){ echo "[restore] $*"; }
|
||||
ok(){ echo "[restore][OK] $*"; }
|
||||
warn(){ echo "[restore][WARN] $*"; }
|
||||
fail(){ echo "[restore][FAIL] $*" >&2; exit 1; }
|
||||
|
||||
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
|
||||
sudo ./restore-appfactory.sh /path/to/appfactory-backup.tar.gz [target_user]
|
||||
USAGE
|
||||
}
|
||||
|
||||
if [ -z "$BACKUP_FILE" ]; then
|
||||
usage
|
||||
fail "Backup file argument is required"
|
||||
fi
|
||||
|
||||
if [ ! -f "$BACKUP_FILE" ]; then
|
||||
fail "Backup file not found: $BACKUP_FILE"
|
||||
fi
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
fail "Run as root via sudo"
|
||||
fi
|
||||
|
||||
if ! id "$TARGET_USER" >/dev/null 2>&1; then
|
||||
fail "Target user does not exist: $TARGET_USER"
|
||||
fi
|
||||
[ -n "$BACKUP_FILE_INPUT" ] || { usage; fail "Backup file argument is required"; }
|
||||
[ "$(id -u)" -eq 0 ] || fail "Run as root via sudo"
|
||||
[ -f "$BACKUP_FILE_INPUT" ] || fail "Backup file not found: $BACKUP_FILE_INPUT"
|
||||
id "$TARGET_USER" >/dev/null 2>&1 || fail "Target user does not exist: $TARGET_USER"
|
||||
|
||||
command -v realpath >/dev/null 2>&1 || fail "realpath missing"
|
||||
command -v tar >/dev/null 2>&1 || fail "tar missing"
|
||||
command -v docker >/dev/null 2>&1 || fail "docker missing"
|
||||
|
||||
BACKUP_FILE="$(realpath "$BACKUP_FILE_INPUT")"
|
||||
|
||||
APPFACTORY_UID="$(id -u "$TARGET_USER")"
|
||||
APPFACTORY_GID="$(id -g "$TARGET_USER")"
|
||||
|
||||
[ -S /var/run/docker.sock ] || fail "Docker socket missing: /var/run/docker.sock"
|
||||
|
||||
APPFACTORY_DOCKER_GID="$(stat -c '%g' /var/run/docker.sock)"
|
||||
|
||||
docker ps >/dev/null 2>&1 || fail "docker ps failed. Is $TARGET_USER in docker group and was session restarted?"
|
||||
|
||||
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"
|
||||
|
||||
log "Removing existing AppFactory containers if present"
|
||||
mapfile -t APPFACTORY_CONTAINERS < <(
|
||||
docker ps -a --format '{{.Names}}' \
|
||||
| grep -E '^(appfactory-|.*-service$|test-|microsoft-365-service)' \
|
||||
|| true
|
||||
)
|
||||
|
||||
if [ "${#APPFACTORY_CONTAINERS[@]}" -eq 0 ]; then
|
||||
ok "No existing AppFactory containers found"
|
||||
else
|
||||
for container in "${APPFACTORY_CONTAINERS[@]}"; do
|
||||
[ -n "$container" ] || continue
|
||||
docker rm -f "$container" >/dev/null 2>&1 || true
|
||||
ok "Removed container: $container"
|
||||
done
|
||||
fi
|
||||
for container in "${APPFACTORY_CONTAINERS[@]}"; do
|
||||
[ -n "$container" ] || continue
|
||||
docker rm -f "$container" >/dev/null 2>&1 || true
|
||||
ok "Removed container: $container"
|
||||
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"
|
||||
@@ -105,11 +78,8 @@ mkdir -p \
|
||||
"$APPFACTORY_ROOT/workspace"
|
||||
|
||||
ENV_FILE="$APPFACTORY_ROOT/config/appfactory.env"
|
||||
|
||||
[ -f "$ENV_FILE" ] || fail "Missing env file after restore: $ENV_FILE"
|
||||
|
||||
log "Updating runtime ids in appfactory.env"
|
||||
|
||||
set_env_value() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
@@ -121,6 +91,10 @@ set_env_value() {
|
||||
fi
|
||||
}
|
||||
|
||||
log "Updating appfactory.env for target server"
|
||||
set_env_value "APPFACTORY_DIR" "$APPFACTORY_ROOT"
|
||||
set_env_value "WORKSPACE_DIR" "$APPFACTORY_ROOT/workspace"
|
||||
set_env_value "BACKUP_DIR" "$APPFACTORY_ROOT/backups"
|
||||
set_env_value "APPFACTORY_UID" "$APPFACTORY_UID"
|
||||
set_env_value "APPFACTORY_GID" "$APPFACTORY_GID"
|
||||
set_env_value "APPFACTORY_DOCKER_GID" "$APPFACTORY_DOCKER_GID"
|
||||
@@ -134,32 +108,88 @@ grep -q '^GOOGLE_CLIENT_ID=' "$ENV_FILE" || echo 'GOOGLE_CLIENT_ID=' >> "$ENV_FI
|
||||
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"
|
||||
|
||||
DB_FILE="$APPFACTORY_ROOT/data/appfactory/appfactory.db"
|
||||
[ -f "$DB_FILE" ] || fail "Missing AppFactory DB after restore: $DB_FILE"
|
||||
|
||||
COMPOSE_FILE="$APPFACTORY_ROOT/deploy/docker-compose.yml"
|
||||
[ -f "$COMPOSE_FILE" ] || fail "Missing compose file: $COMPOSE_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"
|
||||
build_image() {
|
||||
local image="$1"
|
||||
local dir="$2"
|
||||
|
||||
[ -d "$dir" ] || fail "Missing build directory: $dir"
|
||||
[ -f "$dir/Dockerfile" ] || fail "Missing Dockerfile: $dir/Dockerfile"
|
||||
|
||||
log "Building image: $image from $dir"
|
||||
sudo -u "$TARGET_USER" docker build -t "$image:latest" "$dir"
|
||||
}
|
||||
|
||||
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"
|
||||
log "Generating Caddyfile before starting Caddy"
|
||||
sudo -u "$TARGET_USER" "$GENERATE_CADDY" || true
|
||||
fi
|
||||
|
||||
log "Running bootstrap deploy"
|
||||
sudo -u "$TARGET_USER" "$BOOTSTRAP" deploy
|
||||
build_image "appfactory-portal" "$APPFACTORY_ROOT/workspace/appfactory-portal"
|
||||
build_image "appfactory-worker" "$APPFACTORY_ROOT/workspace/appfactory-worker"
|
||||
build_image "appfactory-monitor" "$APPFACTORY_ROOT/workspace/appfactory-monitor"
|
||||
build_image "appfactory-webhook" "$APPFACTORY_ROOT/workspace/appfactory-webhook"
|
||||
|
||||
log "Deploying core compose stack"
|
||||
sudo -u "$TARGET_USER" docker compose \
|
||||
--env-file "$ENV_FILE" \
|
||||
-f "$COMPOSE_FILE" \
|
||||
up -d --force-recreate
|
||||
|
||||
log "Waiting for core containers"
|
||||
CORE_CONTAINERS=(
|
||||
appfactory-caddy
|
||||
appfactory-gitea
|
||||
appfactory-registry
|
||||
appfactory-portal
|
||||
appfactory-worker
|
||||
appfactory-monitor
|
||||
appfactory-webhook
|
||||
)
|
||||
|
||||
deadline=$((SECONDS + 180))
|
||||
|
||||
for container in "${CORE_CONTAINERS[@]}"; do
|
||||
while true; do
|
||||
if docker inspect "$container" >/dev/null 2>&1; then
|
||||
state="$(docker inspect -f '{{.State.Status}}' "$container" 2>/dev/null || true)"
|
||||
health="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container" 2>/dev/null || true)"
|
||||
|
||||
if [ "$state" = "running" ] && [ "$health" != "unhealthy" ]; then
|
||||
ok "Container ready: $container health=$health"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$SECONDS" -ge "$deadline" ]; then
|
||||
docker ps -a
|
||||
fail "Timeout waiting for container: $container"
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
done
|
||||
done
|
||||
|
||||
if [ -f "$GENERATE_CADDY" ]; then
|
||||
log "Regenerating/reloading Caddyfile"
|
||||
sudo -u "$TARGET_USER" "$GENERATE_CADDY"
|
||||
fi
|
||||
|
||||
PREFLIGHT="$APPFACTORY_ROOT/workspace/appfactory-tools/maintenance/preflight-check.sh"
|
||||
[ -f "$PREFLIGHT" ] || fail "Missing preflight script: $PREFLIGHT"
|
||||
chmod +x "$PREFLIGHT"
|
||||
|
||||
log "Running final preflight"
|
||||
sudo -u "$TARGET_USER" "$PREFLIGHT"
|
||||
|
||||
Reference in New Issue
Block a user