Make restore and bootstrap work on clean server
This commit is contained in:
+100
-122
@@ -4,20 +4,21 @@ set -euo pipefail
|
|||||||
APPFACTORY_ROOT="/opt/appfactory"
|
APPFACTORY_ROOT="/opt/appfactory"
|
||||||
ENV_FILE="$APPFACTORY_ROOT/config/appfactory.env"
|
ENV_FILE="$APPFACTORY_ROOT/config/appfactory.env"
|
||||||
PREFLIGHT_SCRIPT="$APPFACTORY_ROOT/workspace/appfactory-tools/maintenance/preflight-check.sh"
|
PREFLIGHT_SCRIPT="$APPFACTORY_ROOT/workspace/appfactory-tools/maintenance/preflight-check.sh"
|
||||||
DEPLOY_CORE_SCRIPT="$APPFACTORY_ROOT/workspace/appfactory-tools/scripts/deploy-core-service.sh"
|
GENERATE_CADDY="$APPFACTORY_ROOT/workspace/appfactory-tools/scripts/generate-caddyfile.sh"
|
||||||
|
COMPOSE_FILE="$APPFACTORY_ROOT/deploy/docker-compose.yml"
|
||||||
CORE_SERVICES=(
|
|
||||||
"caddy"
|
|
||||||
"gitea"
|
|
||||||
"registry"
|
|
||||||
"portal"
|
|
||||||
"worker"
|
|
||||||
"monitor"
|
|
||||||
"webhook"
|
|
||||||
)
|
|
||||||
|
|
||||||
MODE="${1:-}"
|
MODE="${1:-}"
|
||||||
|
|
||||||
|
CORE_CONTAINERS=(
|
||||||
|
"appfactory-caddy"
|
||||||
|
"appfactory-gitea"
|
||||||
|
"appfactory-registry"
|
||||||
|
"appfactory-portal"
|
||||||
|
"appfactory-worker"
|
||||||
|
"appfactory-monitor"
|
||||||
|
"appfactory-webhook"
|
||||||
|
)
|
||||||
|
|
||||||
log(){ echo "[bootstrap-v2] $*"; }
|
log(){ echo "[bootstrap-v2] $*"; }
|
||||||
ok(){ echo "[bootstrap-v2][OK] $*"; }
|
ok(){ echo "[bootstrap-v2][OK] $*"; }
|
||||||
warn(){ echo "[bootstrap-v2][WARN] $*"; }
|
warn(){ echo "[bootstrap-v2][WARN] $*"; }
|
||||||
@@ -29,11 +30,6 @@ Usage:
|
|||||||
bootstrap-v2.sh check
|
bootstrap-v2.sh check
|
||||||
bootstrap-v2.sh repair
|
bootstrap-v2.sh repair
|
||||||
bootstrap-v2.sh deploy
|
bootstrap-v2.sh deploy
|
||||||
|
|
||||||
Modes:
|
|
||||||
check - read-only validation, does not change filesystem or deploy services
|
|
||||||
repair - creates directories, repairs ownership/chmod, then runs preflight
|
|
||||||
deploy - repair + deploys core services + waits for containers + runs preflight
|
|
||||||
USAGE
|
USAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,24 +38,6 @@ require_file() {
|
|||||||
[ -f "$path" ] || fail "Required file missing: $path"
|
[ -f "$path" ] || fail "Required file missing: $path"
|
||||||
}
|
}
|
||||||
|
|
||||||
load_env() {
|
|
||||||
require_file "$ENV_FILE"
|
|
||||||
|
|
||||||
set -a
|
|
||||||
# shellcheck disable=SC1090
|
|
||||||
source "$ENV_FILE"
|
|
||||||
set +a
|
|
||||||
|
|
||||||
[ -n "${APPFACTORY_UID:-}" ] || fail "APPFACTORY_UID missing in $ENV_FILE"
|
|
||||||
[ -n "${APPFACTORY_GID:-}" ] || fail "APPFACTORY_GID missing in $ENV_FILE"
|
|
||||||
[ -n "${APPFACTORY_DOCKER_GID:-}" ] || fail "APPFACTORY_DOCKER_GID missing in $ENV_FILE"
|
|
||||||
|
|
||||||
ok "Environment loaded"
|
|
||||||
ok "APPFACTORY_UID=$APPFACTORY_UID"
|
|
||||||
ok "APPFACTORY_GID=$APPFACTORY_GID"
|
|
||||||
ok "APPFACTORY_DOCKER_GID=$APPFACTORY_DOCKER_GID"
|
|
||||||
}
|
|
||||||
|
|
||||||
check_mode() {
|
check_mode() {
|
||||||
case "$MODE" in
|
case "$MODE" in
|
||||||
check|repair|deploy)
|
check|repair|deploy)
|
||||||
@@ -72,128 +50,127 @@ check_mode() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
load_env() {
|
||||||
|
require_file "$ENV_FILE"
|
||||||
|
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$ENV_FILE"
|
||||||
|
set +a
|
||||||
|
|
||||||
|
[ -n "${APPFACTORY_UID:-}" ] || fail "APPFACTORY_UID missing"
|
||||||
|
[ -n "${APPFACTORY_GID:-}" ] || fail "APPFACTORY_GID missing"
|
||||||
|
[ -n "${APPFACTORY_DOCKER_GID:-}" ] || fail "APPFACTORY_DOCKER_GID missing"
|
||||||
|
|
||||||
|
ok "Environment loaded"
|
||||||
|
ok "APPFACTORY_UID=$APPFACTORY_UID"
|
||||||
|
ok "APPFACTORY_GID=$APPFACTORY_GID"
|
||||||
|
ok "APPFACTORY_DOCKER_GID=$APPFACTORY_DOCKER_GID"
|
||||||
|
}
|
||||||
|
|
||||||
check_docker() {
|
check_docker() {
|
||||||
command -v docker >/dev/null 2>&1 || fail "docker command missing"
|
command -v docker >/dev/null 2>&1 || fail "docker missing"
|
||||||
|
|
||||||
docker version >/dev/null 2>&1 || fail "docker daemon not available"
|
docker version >/dev/null 2>&1 || fail "docker daemon not available"
|
||||||
docker compose version >/dev/null 2>&1 || fail "docker compose not available"
|
docker compose version >/dev/null 2>&1 || fail "docker compose missing"
|
||||||
|
|
||||||
[ -S /var/run/docker.sock ] || fail "Docker socket missing: /var/run/docker.sock"
|
[ -S /var/run/docker.sock ] || fail "Docker socket missing"
|
||||||
|
|
||||||
local sock_gid
|
|
||||||
sock_gid="$(stat -c '%g' /var/run/docker.sock)"
|
sock_gid="$(stat -c '%g' /var/run/docker.sock)"
|
||||||
|
[ "$sock_gid" = "$APPFACTORY_DOCKER_GID" ] || fail "Docker socket GID mismatch. env=$APPFACTORY_DOCKER_GID sock=$sock_gid"
|
||||||
if [ "$sock_gid" != "$APPFACTORY_DOCKER_GID" ]; then
|
|
||||||
fail "Docker socket GID mismatch. APPFACTORY_DOCKER_GID=$APPFACTORY_DOCKER_GID, /var/run/docker.sock gid=$sock_gid"
|
|
||||||
fi
|
|
||||||
|
|
||||||
docker ps >/dev/null 2>&1 || fail "docker ps failed"
|
docker ps >/dev/null 2>&1 || fail "docker ps failed"
|
||||||
|
|
||||||
ok "Docker ready"
|
ok "Docker ready"
|
||||||
}
|
}
|
||||||
|
|
||||||
check_directories_exist() {
|
ensure_directories() {
|
||||||
|
mkdir -p \
|
||||||
|
"$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"
|
||||||
|
|
||||||
|
ok "Directories ready"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_directories() {
|
||||||
for dir in apps backups config data deploy gateway services templates tools workspace; do
|
for dir in apps backups config data deploy gateway services templates tools workspace; do
|
||||||
path="$APPFACTORY_ROOT/$dir"
|
path="$APPFACTORY_ROOT/$dir"
|
||||||
[ -d "$path" ] && ok "Directory exists: $path" || fail "Directory missing: $path"
|
[ -d "$path" ] && ok "Directory exists: $path" || fail "Directory missing: $path"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_directories() {
|
repair_ownership() {
|
||||||
for dir in apps backups config data deploy gateway services templates tools workspace; do
|
log "Repairing ownership"
|
||||||
path="$APPFACTORY_ROOT/$dir"
|
chown -R "$APPFACTORY_UID:$APPFACTORY_GID" "$APPFACTORY_ROOT"
|
||||||
mkdir -p "$path"
|
ok "Ownership repaired"
|
||||||
ok "Directory ready: $path"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
check_required_scripts() {
|
|
||||||
require_file "$PREFLIGHT_SCRIPT"
|
|
||||||
|
|
||||||
if [ "$MODE" = "deploy" ]; then
|
|
||||||
require_file "$DEPLOY_CORE_SCRIPT"
|
|
||||||
else
|
|
||||||
if [ -f "$DEPLOY_CORE_SCRIPT" ]; then
|
|
||||||
ok "Deploy core script exists: $DEPLOY_CORE_SCRIPT"
|
|
||||||
else
|
|
||||||
warn "Deploy core script missing: $DEPLOY_CORE_SCRIPT"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
check_ownership() {
|
check_ownership() {
|
||||||
for path in "$APPFACTORY_ROOT" "$APPFACTORY_ROOT/data" "$APPFACTORY_ROOT/workspace" "$APPFACTORY_ROOT/backups"; do
|
for path in "$APPFACTORY_ROOT" "$APPFACTORY_ROOT/data" "$APPFACTORY_ROOT/workspace" "$APPFACTORY_ROOT/backups"; do
|
||||||
[ -e "$path" ] || fail "Path missing for ownership check: $path"
|
[ -e "$path" ] || fail "Path missing: $path"
|
||||||
|
|
||||||
actual_uid="$(stat -c '%u' "$path")"
|
actual_uid="$(stat -c '%u' "$path")"
|
||||||
actual_gid="$(stat -c '%g' "$path")"
|
actual_gid="$(stat -c '%g' "$path")"
|
||||||
|
|
||||||
if [ "$actual_uid" = "$APPFACTORY_UID" ] && [ "$actual_gid" = "$APPFACTORY_GID" ]; then
|
if [ "$actual_uid" = "$APPFACTORY_UID" ] && [ "$actual_gid" = "$APPFACTORY_GID" ]; then
|
||||||
ok "Ownership OK: $path ($actual_uid:$actual_gid)"
|
ok "Ownership OK: $path"
|
||||||
else
|
else
|
||||||
fail "Ownership mismatch: $path is $actual_uid:$actual_gid, expected $APPFACTORY_UID:$APPFACTORY_GID"
|
fail "Ownership mismatch: $path is $actual_uid:$actual_gid expected $APPFACTORY_UID:$APPFACTORY_GID"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
repair_ownership() {
|
|
||||||
log "Repairing ownership for $APPFACTORY_ROOT to $APPFACTORY_UID:$APPFACTORY_GID"
|
|
||||||
chown -R "$APPFACTORY_UID:$APPFACTORY_GID" "$APPFACTORY_ROOT"
|
|
||||||
ok "Ownership repaired"
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_scripts_executable() {
|
ensure_scripts_executable() {
|
||||||
if [ -d "$APPFACTORY_ROOT/workspace/appfactory-tools/maintenance" ]; then
|
find "$APPFACTORY_ROOT/workspace/appfactory-tools" -type f -name '*.sh' -exec chmod +x {} \; 2>/dev/null || true
|
||||||
find "$APPFACTORY_ROOT/workspace/appfactory-tools/maintenance" -maxdepth 1 -type f -name "*.sh" -exec chmod +x {} \;
|
ok "Scripts executable"
|
||||||
ok "Maintenance scripts executable"
|
|
||||||
else
|
|
||||||
warn "Maintenance scripts directory missing"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d "$APPFACTORY_ROOT/workspace/appfactory-tools/alerts" ]; then
|
|
||||||
find "$APPFACTORY_ROOT/workspace/appfactory-tools/alerts" -maxdepth 1 -type f -name "*.sh" -exec chmod +x {} \;
|
|
||||||
ok "Alert scripts executable"
|
|
||||||
else
|
|
||||||
warn "Alert scripts directory missing"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "$DEPLOY_CORE_SCRIPT" ]; then
|
|
||||||
chmod +x "$DEPLOY_CORE_SCRIPT"
|
|
||||||
ok "Deploy core script executable"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deploy_core_services() {
|
build_image() {
|
||||||
require_file "$DEPLOY_CORE_SCRIPT"
|
local image="$1"
|
||||||
|
local dir="$2"
|
||||||
|
|
||||||
for service in "${CORE_SERVICES[@]}"; do
|
[ -d "$dir" ] || fail "Missing build directory: $dir"
|
||||||
log "Deploying core service: $service"
|
[ -f "$dir/Dockerfile" ] || fail "Missing Dockerfile: $dir/Dockerfile"
|
||||||
|
|
||||||
if "$DEPLOY_CORE_SCRIPT" "$service"; then
|
log "Building image: $image"
|
||||||
ok "Deploy requested: $service"
|
docker build -t "$image:latest" "$dir"
|
||||||
else
|
}
|
||||||
fail "Deploy failed for core service: $service"
|
|
||||||
fi
|
deploy_core_stack() {
|
||||||
done
|
require_file "$COMPOSE_FILE"
|
||||||
|
|
||||||
|
[ -f "$APPFACTORY_ROOT/data/appfactory/appfactory.db" ] || fail "Missing AppFactory DB"
|
||||||
|
|
||||||
|
if [ -f "$GENERATE_CADDY" ]; then
|
||||||
|
chmod +x "$GENERATE_CADDY"
|
||||||
|
"$GENERATE_CADDY" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
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 compose stack"
|
||||||
|
docker compose \
|
||||||
|
--env-file "$ENV_FILE" \
|
||||||
|
-f "$COMPOSE_FILE" \
|
||||||
|
up -d --force-recreate
|
||||||
|
|
||||||
|
ok "Core compose stack deployed"
|
||||||
}
|
}
|
||||||
|
|
||||||
wait_for_containers() {
|
wait_for_containers() {
|
||||||
log "Waiting for core containers"
|
|
||||||
|
|
||||||
local container_names=(
|
|
||||||
"appfactory-caddy"
|
|
||||||
"appfactory-gitea"
|
|
||||||
"appfactory-registry"
|
|
||||||
"appfactory-portal"
|
|
||||||
"appfactory-worker"
|
|
||||||
"appfactory-monitor"
|
|
||||||
"appfactory-webhook"
|
|
||||||
)
|
|
||||||
|
|
||||||
local deadline
|
|
||||||
deadline=$((SECONDS + 180))
|
deadline=$((SECONDS + 180))
|
||||||
|
|
||||||
for name in "${container_names[@]}"; do
|
for name in "${CORE_CONTAINERS[@]}"; do
|
||||||
log "Waiting for container: $name"
|
log "Waiting for container: $name"
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
@@ -208,7 +185,7 @@ wait_for_containers() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$SECONDS" -ge "$deadline" ]; then
|
if [ "$SECONDS" -ge "$deadline" ]; then
|
||||||
docker ps --format "table {{.Names}}\t{{.Status}}" || true
|
docker ps -a
|
||||||
fail "Timeout waiting for container: $name"
|
fail "Timeout waiting for container: $name"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -220,8 +197,6 @@ wait_for_containers() {
|
|||||||
run_preflight() {
|
run_preflight() {
|
||||||
require_file "$PREFLIGHT_SCRIPT"
|
require_file "$PREFLIGHT_SCRIPT"
|
||||||
chmod +x "$PREFLIGHT_SCRIPT"
|
chmod +x "$PREFLIGHT_SCRIPT"
|
||||||
|
|
||||||
log "Running preflight"
|
|
||||||
"$PREFLIGHT_SCRIPT"
|
"$PREFLIGHT_SCRIPT"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,10 +208,9 @@ main() {
|
|||||||
check_mode
|
check_mode
|
||||||
load_env
|
load_env
|
||||||
check_docker
|
check_docker
|
||||||
check_required_scripts
|
|
||||||
|
|
||||||
if [ "$MODE" = "check" ]; then
|
if [ "$MODE" = "check" ]; then
|
||||||
check_directories_exist
|
check_directories
|
||||||
check_ownership
|
check_ownership
|
||||||
run_preflight
|
run_preflight
|
||||||
echo
|
echo
|
||||||
@@ -249,14 +223,18 @@ main() {
|
|||||||
ensure_scripts_executable
|
ensure_scripts_executable
|
||||||
|
|
||||||
if [ "$MODE" = "repair" ]; then
|
if [ "$MODE" = "repair" ]; then
|
||||||
run_preflight
|
|
||||||
echo
|
echo
|
||||||
echo "APPFACTORY BOOTSTRAP REPAIR: READY"
|
echo "APPFACTORY BOOTSTRAP REPAIR: READY"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
deploy_core_services
|
deploy_core_stack
|
||||||
wait_for_containers
|
wait_for_containers
|
||||||
|
|
||||||
|
if [ -f "$GENERATE_CADDY" ]; then
|
||||||
|
"$GENERATE_CADDY"
|
||||||
|
fi
|
||||||
|
|
||||||
run_preflight
|
run_preflight
|
||||||
|
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -2,89 +2,62 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
APPFACTORY_ROOT="/opt/appfactory"
|
APPFACTORY_ROOT="/opt/appfactory"
|
||||||
BACKUP_FILE="${1:-}"
|
BACKUP_FILE_INPUT="${1:-}"
|
||||||
TARGET_USER="${2:-${SUDO_USER:-$USER}}"
|
TARGET_USER="${2:-${SUDO_USER:-$USER}}"
|
||||||
|
|
||||||
fail() {
|
log(){ echo "[restore] $*"; }
|
||||||
echo "[restore][FAIL] $*" >&2
|
ok(){ echo "[restore][OK] $*"; }
|
||||||
exit 1
|
warn(){ echo "[restore][WARN] $*"; }
|
||||||
}
|
fail(){ echo "[restore][FAIL] $*" >&2; exit 1; }
|
||||||
|
|
||||||
ok() {
|
|
||||||
echo "[restore][OK] $*"
|
|
||||||
}
|
|
||||||
|
|
||||||
log() {
|
|
||||||
echo "[restore] $*"
|
|
||||||
}
|
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<USAGE
|
cat <<USAGE
|
||||||
Usage:
|
Usage:
|
||||||
sudo ./restore-appfactory.sh /path/to/appfactory-backup-YYYYMMDD-HHMMSS.tar.gz [target_user]
|
sudo ./restore-appfactory.sh /path/to/appfactory-backup.tar.gz [target_user]
|
||||||
|
|
||||||
Example:
|
|
||||||
sudo ./restore-appfactory.sh ./appfactory-backup-20260608-095020.tar.gz karel
|
|
||||||
USAGE
|
USAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ -z "$BACKUP_FILE" ]; then
|
[ -n "$BACKUP_FILE_INPUT" ] || { usage; fail "Backup file argument is required"; }
|
||||||
usage
|
[ "$(id -u)" -eq 0 ] || fail "Run as root via sudo"
|
||||||
fail "Backup file argument is required"
|
[ -f "$BACKUP_FILE_INPUT" ] || fail "Backup file not found: $BACKUP_FILE_INPUT"
|
||||||
fi
|
id "$TARGET_USER" >/dev/null 2>&1 || fail "Target user does not exist: $TARGET_USER"
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
command -v realpath >/dev/null 2>&1 || fail "realpath missing"
|
||||||
command -v tar >/dev/null 2>&1 || fail "tar missing"
|
command -v tar >/dev/null 2>&1 || fail "tar missing"
|
||||||
command -v docker >/dev/null 2>&1 || fail "docker missing"
|
command -v docker >/dev/null 2>&1 || fail "docker missing"
|
||||||
|
|
||||||
|
BACKUP_FILE="$(realpath "$BACKUP_FILE_INPUT")"
|
||||||
|
|
||||||
APPFACTORY_UID="$(id -u "$TARGET_USER")"
|
APPFACTORY_UID="$(id -u "$TARGET_USER")"
|
||||||
APPFACTORY_GID="$(id -g "$TARGET_USER")"
|
APPFACTORY_GID="$(id -g "$TARGET_USER")"
|
||||||
|
|
||||||
[ -S /var/run/docker.sock ] || fail "Docker socket missing: /var/run/docker.sock"
|
[ -S /var/run/docker.sock ] || fail "Docker socket missing: /var/run/docker.sock"
|
||||||
|
|
||||||
APPFACTORY_DOCKER_GID="$(stat -c '%g' /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 "Backup: $BACKUP_FILE"
|
||||||
log "Target user: $TARGET_USER"
|
log "Target user: $TARGET_USER"
|
||||||
log "APPFACTORY_UID=$APPFACTORY_UID"
|
log "APPFACTORY_UID=$APPFACTORY_UID"
|
||||||
log "APPFACTORY_GID=$APPFACTORY_GID"
|
log "APPFACTORY_GID=$APPFACTORY_GID"
|
||||||
log "APPFACTORY_DOCKER_GID=$APPFACTORY_DOCKER_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 < <(
|
mapfile -t APPFACTORY_CONTAINERS < <(
|
||||||
docker ps -a --format '{{.Names}}' \
|
docker ps -a --format '{{.Names}}' \
|
||||||
| grep -E '^(appfactory-|.*-service$|test-|microsoft-365-service)' \
|
| grep -E '^(appfactory-|.*-service$|test-|microsoft-365-service)' \
|
||||||
|| true
|
|| true
|
||||||
)
|
)
|
||||||
|
|
||||||
if [ "${#APPFACTORY_CONTAINERS[@]}" -eq 0 ]; then
|
for container in "${APPFACTORY_CONTAINERS[@]}"; do
|
||||||
ok "No existing AppFactory containers found"
|
[ -n "$container" ] || continue
|
||||||
else
|
docker rm -f "$container" >/dev/null 2>&1 || true
|
||||||
for container in "${APPFACTORY_CONTAINERS[@]}"; do
|
ok "Removed container: $container"
|
||||||
[ -n "$container" ] || continue
|
done
|
||||||
docker rm -f "$container" >/dev/null 2>&1 || true
|
|
||||||
ok "Removed container: $container"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "Removing existing AppFactory root"
|
log "Removing existing AppFactory root"
|
||||||
rm -rf "$APPFACTORY_ROOT"
|
rm -rf "$APPFACTORY_ROOT"
|
||||||
|
|
||||||
log "Recreating AppFactory root"
|
|
||||||
mkdir -p "$APPFACTORY_ROOT"
|
|
||||||
|
|
||||||
log "Extracting backup"
|
log "Extracting backup"
|
||||||
cd /
|
cd /
|
||||||
tar -xzf "$BACKUP_FILE"
|
tar -xzf "$BACKUP_FILE"
|
||||||
@@ -105,11 +78,8 @@ mkdir -p \
|
|||||||
"$APPFACTORY_ROOT/workspace"
|
"$APPFACTORY_ROOT/workspace"
|
||||||
|
|
||||||
ENV_FILE="$APPFACTORY_ROOT/config/appfactory.env"
|
ENV_FILE="$APPFACTORY_ROOT/config/appfactory.env"
|
||||||
|
|
||||||
[ -f "$ENV_FILE" ] || fail "Missing env file after restore: $ENV_FILE"
|
[ -f "$ENV_FILE" ] || fail "Missing env file after restore: $ENV_FILE"
|
||||||
|
|
||||||
log "Updating runtime ids in appfactory.env"
|
|
||||||
|
|
||||||
set_env_value() {
|
set_env_value() {
|
||||||
local key="$1"
|
local key="$1"
|
||||||
local value="$2"
|
local value="$2"
|
||||||
@@ -121,6 +91,10 @@ set_env_value() {
|
|||||||
fi
|
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_UID" "$APPFACTORY_UID"
|
||||||
set_env_value "APPFACTORY_GID" "$APPFACTORY_GID"
|
set_env_value "APPFACTORY_GID" "$APPFACTORY_GID"
|
||||||
set_env_value "APPFACTORY_DOCKER_GID" "$APPFACTORY_DOCKER_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_CLIENT_SECRET=' "$ENV_FILE" || echo 'GOOGLE_CLIENT_SECRET=' >> "$ENV_FILE"
|
||||||
grep -q '^GOOGLE_REDIRECT_URI=' "$ENV_FILE" || echo 'GOOGLE_REDIRECT_URI=' >> "$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"
|
log "Fixing ownership"
|
||||||
chown -R "$APPFACTORY_UID:$APPFACTORY_GID" "$APPFACTORY_ROOT"
|
chown -R "$APPFACTORY_UID:$APPFACTORY_GID" "$APPFACTORY_ROOT"
|
||||||
|
|
||||||
log "Fixing script permissions"
|
log "Fixing script permissions"
|
||||||
find "$APPFACTORY_ROOT/workspace/appfactory-tools" -type f -name '*.sh' -exec chmod +x {} \; 2>/dev/null || true
|
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"
|
build_image() {
|
||||||
PREFLIGHT="$APPFACTORY_ROOT/workspace/appfactory-tools/maintenance/preflight-check.sh"
|
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"
|
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
|
if [ -f "$GENERATE_CADDY" ]; then
|
||||||
log "Generating Caddyfile"
|
log "Generating Caddyfile before starting Caddy"
|
||||||
sudo -u "$TARGET_USER" "$GENERATE_CADDY" || true
|
sudo -u "$TARGET_USER" "$GENERATE_CADDY" || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Running bootstrap deploy"
|
build_image "appfactory-portal" "$APPFACTORY_ROOT/workspace/appfactory-portal"
|
||||||
sudo -u "$TARGET_USER" "$BOOTSTRAP" deploy
|
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"
|
log "Running final preflight"
|
||||||
sudo -u "$TARGET_USER" "$PREFLIGHT"
|
sudo -u "$TARGET_USER" "$PREFLIGHT"
|
||||||
|
|||||||
Reference in New Issue
Block a user