#!/usr/bin/env bash # scripts/startup.sh # Startup helper for tdc114plus development environment # Usage: ./scripts/startup.sh [--dry-run] [--auto] [--wait=N] [--skip-android-check] set -euo pipefail ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" BARON_DIR="${BARON_SSO_WORKTREE:-/home/ubuntu/workspace/baron-sso-tdc114plus-api}" LOG_BASE_DIR="${TDC114_LOG_BASE:-$ROOT_DIR/logs}" LOG_DIR="$LOG_BASE_DIR/$(date +%F)" LOG_FILE="$LOG_DIR/startup.log" CHECK_SCRIPT="${TDC114_CHECK_SCRIPT:-$ROOT_DIR/scripts/check-baron-api-env.sh}" API_SMOKE_SCRIPT="${TDC114_API_SMOKE_SCRIPT:-$ROOT_DIR/scripts/api-smoke.sh}" DOCKER_BIN="${DOCKER_BIN:-docker}" MAKE_BIN="${MAKE_BIN:-make}" CHMOD_BIN="${CHMOD_BIN:-chmod}" REQUIRE_AUTH_SMOKE="${TDC114_REQUIRE_AUTH_SMOKE:-false}" ANDROID_PRECHECK_SCRIPT="${TDC114_ANDROID_PRECHECK_SCRIPT:-$ROOT_DIR/scripts/check-android-device-env.sh}" AUTH_SERVER_SCRIPT="${TDC114_AUTH_SERVER_SCRIPT:-$ROOT_DIR/scripts/start-auth-server.sh}" START_AUTH_SERVER="${TDC114_START_AUTH_SERVER:-true}" DAILY_HANDOFF_DIR="${TDC114_DAILY_HANDOFF_DIR:-$ROOT_DIR/docs/daily-issues}" REQUIRE_DAILY_HANDOFF="${TDC114_REQUIRE_DAILY_HANDOFF:-true}" WSL_MAINTENANCE_SCRIPT="${TDC114_WSL_MAINTENANCE_SCRIPT:-$ROOT_DIR/scripts/check-wsl-maintenance.sh}" WSL_MAINTENANCE_ENABLED="${TDC114_WSL_MAINTENANCE_ENABLED:-true}" DRY_RUN=true AUTO=false WAIT=30 RETRIES=6 SLEEP_BETWEEN=10 SKIP_ANDROID_CHECK=false for arg in "$@"; do case "$arg" in --dry-run) DRY_RUN=true ;; --auto) DRY_RUN=false; AUTO=true ;; --wait=*) WAIT=${arg#*=} ;; --skip-android-check|--skip-device-check) SKIP_ANDROID_CHECK=true ;; --help|-h) cat <&2; exit 2 ;; *) echo "Unknown arg: $arg" >&2; exit 2 ;; esac done case "$WAIT" in ''|*[!0-9]*) echo "--wait must be a non-negative integer" >&2 exit 2 ;; esac mkdir -p "$LOG_DIR" log() { echo "$*" | tee -a "$LOG_FILE" } run_cmd() { log "+ $*" if [ "$DRY_RUN" = false ]; then set +e bash -lc "$*" 2>&1 | tee -a "$LOG_FILE" local rc=${PIPESTATUS[0]} set -e return "$rc" fi } run_cmd_capture() { local output_file="$1" shift log "+ $*" if [ "$DRY_RUN" = false ]; then set +e bash -lc "$*" 2>&1 | tee "$output_file" | tee -a "$LOG_FILE" local rc=${PIPESTATUS[0]} set -e return "$rc" fi } compose_cmd() { printf "%s compose -f docker-compose.yaml -f compose.infra.yaml -f compose.ory.yaml %s" \ "$DOCKER_BIN" "$1" } ensure_required_paths() { if [ ! -d "$BARON_DIR" ]; then log "Startup aborted: Baron worktree not found at $BARON_DIR" exit 1 fi if [ ! -x "$CHECK_SCRIPT" ]; then log "Startup aborted: check script is missing or not executable at $CHECK_SCRIPT" exit 1 fi if [ ! -x "$API_SMOKE_SCRIPT" ]; then log "Startup aborted: api smoke script is missing or not executable at $API_SMOKE_SCRIPT" exit 1 fi if [ "$SKIP_ANDROID_CHECK" = false ] && [ ! -x "$ANDROID_PRECHECK_SCRIPT" ]; then log "Startup aborted: Android precheck script is missing or not executable at $ANDROID_PRECHECK_SCRIPT" exit 1 fi if [ "$START_AUTH_SERVER" = true ] && [ ! -x "$AUTH_SERVER_SCRIPT" ]; then log "Startup aborted: auth server script is missing or not executable at $AUTH_SERVER_SCRIPT" exit 1 fi if [ ! -d "$DAILY_HANDOFF_DIR" ]; then log "Startup aborted: daily handoff directory is missing at $DAILY_HANDOFF_DIR" exit 1 fi if [ "$WSL_MAINTENANCE_ENABLED" = true ] && [ ! -x "$WSL_MAINTENANCE_SCRIPT" ]; then log "Startup aborted: WSL maintenance script is missing or not executable at $WSL_MAINTENANCE_SCRIPT" exit 1 fi } review_daily_handoff() { local today local handoff_file today="$(date +%F)" handoff_file="$(find "$DAILY_HANDOFF_DIR" -maxdepth 1 -type f -name '20*.md' ! -name "$today*" | sort | tail -n 1 || true)" if [ -z "$handoff_file" ]; then log "Daily handoff review: no previous handoff file found in $DAILY_HANDOFF_DIR" if [ "$REQUIRE_DAILY_HANDOFF" = true ]; then log "Startup aborted: previous daily handoff is required. Set TDC114_REQUIRE_DAILY_HANDOFF=false only for exceptional recovery." exit 1 fi return 0 fi log "Daily handoff review required before startup" log "Daily handoff file: $handoff_file" log "----- daily handoff excerpt start -----" sed -n '1,160p' "$handoff_file" | tee -a "$LOG_FILE" log "----- daily handoff excerpt end -----" } review_wsl_maintenance() { if [ "$WSL_MAINTENANCE_ENABLED" != true ]; then log "WSL maintenance reminder skipped by TDC114_WSL_MAINTENANCE_ENABLED=$WSL_MAINTENANCE_ENABLED" return 0 fi log "WSL maintenance review started: $WSL_MAINTENANCE_SCRIPT status" set +e ( cd "$ROOT_DIR" "$WSL_MAINTENANCE_SCRIPT" status ) 2>&1 | tee -a "$LOG_FILE" local rc=${PIPESTATUS[0]} set -e if [ "$rc" -ne 0 ]; then log "Startup aborted: WSL maintenance review script failed" exit 1 fi } run_android_precheck() { if [ "$SKIP_ANDROID_CHECK" = true ]; then log "Android target preflight skipped by option" return 0 fi log "Android target preflight started: $ANDROID_PRECHECK_SCRIPT" set +e ( cd "$ROOT_DIR" "$ANDROID_PRECHECK_SCRIPT" ) 2>&1 | tee -a "$LOG_FILE" local rc=${PIPESTATUS[0]} set -e if [ "$rc" -ne 0 ]; then log "Startup paused: Android target preflight did not pass" log "Resolve the physical-device or ADB steps above, then rerun startup." exit 1 fi log "Android target preflight passed" } ensure_auth_server() { if [ "$START_AUTH_SERVER" != true ]; then log "tdc114plus-auth startup skipped by TDC114_START_AUTH_SERVER=$START_AUTH_SERVER" return 0 fi log "tdc114plus-auth startup started: $AUTH_SERVER_SCRIPT --restart" set +e ( cd "$ROOT_DIR" "$AUTH_SERVER_SCRIPT" --restart ) 2>&1 | tee -a "$LOG_FILE" local rc=${PIPESTATUS[0]} set -e if [ "$rc" -ne 0 ]; then log "Startup aborted: tdc114plus-auth did not start" exit 1 fi log "tdc114plus-auth ready" } ensure_generated_config_writable() { local generated_dir="$BARON_DIR/config/.generated" run_cmd "mkdir -p '$generated_dir'" run_cmd "cd '$BARON_DIR' && $CHMOD_BIN -R u+w config/.generated 2>/dev/null || true" if [ "$DRY_RUN" = false ] && [ ! -w "$generated_dir" ]; then log "Startup aborted: $generated_dir is not writable by $(id -un). Fix ownership/permissions before startup." exit 1 fi } sanitize_rendered_kratos_config() { local kratos_config="$BARON_DIR/config/.generated/ory/kratos/kratos.yml" if [ ! -f "$kratos_config" ]; then log "Startup aborted: rendered Kratos config is missing at $kratos_config" exit 1 fi if grep -Fq " - []" "$kratos_config"; then log "Rendered Kratos config contains invalid empty allowed_return_urls entry; removing it automatically" set +e perl -0pi -e 's/^[ ]*-\s*\[\]\n//mg' "$kratos_config" local rc=$? set -e if [ "$rc" -ne 0 ]; then log "Startup aborted: failed to sanitize $kratos_config" exit 1 fi fi } ensure_rendered_ory_files() { local oathkeeper_entrypoint="$BARON_DIR/config/.generated/ory/oathkeeper/entrypoint.sh" sanitize_rendered_kratos_config if [ ! -x "$oathkeeper_entrypoint" ]; then log "Startup aborted: rendered Oathkeeper entrypoint is missing or not executable at $oathkeeper_entrypoint" exit 1 fi } capture_failure_diagnostics() { local prefix="${1:-runtime}" if [ "$DRY_RUN" = true ]; then return 0 fi run_cmd "mkdir -p '$LOG_DIR'" run_cmd "cd '$BARON_DIR' && $(compose_cmd "ps") > '$LOG_DIR/${prefix}-compose-ps.log' 2>&1 || true" run_cmd "$DOCKER_BIN ps -a --format '{{.Names}} {{.Status}}' | grep -E 'baron|ory' > '$LOG_DIR/${prefix}-docker-ps.log' 2>&1 || true" run_cmd "$DOCKER_BIN logs --tail 120 ory_kratos > '$LOG_DIR/${prefix}-ory_kratos.log' 2>&1 || true" run_cmd "$DOCKER_BIN logs --tail 120 ory_stack_check > '$LOG_DIR/${prefix}-ory_stack_check.log' 2>&1 || true" run_cmd "$DOCKER_BIN logs --tail 120 oathkeeper > '$LOG_DIR/${prefix}-oathkeeper.log' 2>&1 || true" run_cmd "$DOCKER_BIN logs --tail 120 baron_backend > '$LOG_DIR/${prefix}-baron_backend.log' 2>&1 || true" } run_check_script() { local attempt="$1" local output_file="$LOG_DIR/check-baron-api-env.attempt-${attempt}.log" set +e ( cd "$ROOT_DIR" "$CHECK_SCRIPT" ) 2>&1 | tee "$output_file" | tee -a "$LOG_FILE" local rc=${PIPESTATUS[0]} set -e if [ "$rc" -ne 0 ]; then log "check-baron-api-env.sh failed on attempt $attempt" return 1 fi if ! grep -Fq "RESULT ready-ish: 0 failure(s), 0 warning(s)" "$output_file"; then log "check-baron-api-env.sh reported warnings on attempt $attempt" return 1 fi log "check-baron-api-env.sh passed with zero warnings" return 0 } run_api_smoke_script() { local attempt="$1" local output_file="$LOG_DIR/api-smoke.attempt-${attempt}.log" local fallback_output_file="$LOG_DIR/api-smoke.base-only.attempt-${attempt}.log" set +e ( cd "$ROOT_DIR" "$API_SMOKE_SCRIPT" ) 2>&1 | tee "$output_file" | tee -a "$LOG_FILE" local rc=${PIPESTATUS[0]} set -e if [ "$rc" -ne 0 ]; then if [ "$REQUIRE_AUTH_SMOKE" != "true" ] && grep -Fq '"code":"login_failed"' "$output_file"; then log "api-smoke authenticated login failed; retrying in base-smoke-only mode because TDC114_REQUIRE_AUTH_SMOKE is false" set +e ( cd "$ROOT_DIR" TDC114_SKIP_AUTH_SMOKE=true "$API_SMOKE_SCRIPT" ) 2>&1 | tee "$fallback_output_file" | tee -a "$LOG_FILE" rc=${PIPESTATUS[0]} set -e if [ "$rc" -eq 0 ]; then log "api-smoke base-smoke-only mode passed" return 0 fi fi log "api-smoke.sh failed on attempt $attempt" return 1 fi log "api-smoke.sh passed" return 0 } start_stack() { local down_cmd="cd '$BARON_DIR' && $(compose_cmd "down")" local up_cmd="cd '$BARON_DIR' && $(compose_cmd "up -d")" local output_file="$LOG_DIR/compose-up.initial.log" run_cmd_capture "$LOG_DIR/compose-down.initial.log" "$down_cmd" || true if run_cmd_capture "$output_file" "$up_cmd"; then return 0 fi if grep -Eq "name conflict|is already in use by container" "$output_file"; then log "compose up failed due to stale or conflicting containers; attempting automatic cleanup" run_cmd "cd '$BARON_DIR' && $(compose_cmd "down") >/dev/null 2>&1 || true" run_cmd "$DOCKER_BIN ps -a --format '{{.Names}}' | grep -iE 'baron|ory' | xargs -r $DOCKER_BIN rm -f || true" run_cmd_capture "$LOG_DIR/compose-up.retry.log" "$up_cmd" return $? fi if grep -Eq "dependency failed to start|didn't complete successfully|is unhealthy" "$output_file"; then log "compose up reported dependency or health issues; continuing to stabilization checks before declaring failure" return 0 fi return 1 } ensure_required_paths log "Startup started: $(date)" # 0) Always review the previous daily handoff before starting runtime work. review_daily_handoff review_wsl_maintenance # 1) Verify Android target readiness before backend startup work run_android_precheck # 2) Ensure networks and config rendered (if Makefile target exists) if [ -f "$BARON_DIR/Makefile" ]; then ensure_generated_config_writable run_cmd "cd '$BARON_DIR' && $MAKE_BIN ensure-networks || true" if ! run_cmd "cd '$BARON_DIR' && $MAKE_BIN render-ory-config"; then log "Startup aborted: make render-ory-config failed" exit 1 fi ensure_rendered_ory_files fi # 3) Start all necessary compose stacks, retrying once after conflict cleanup start_stack # 4) Wait for services to stabilize run_cmd "sleep $WAIT" # 5) Show container status run_cmd "$DOCKER_BIN ps --format '{{.Names}} {{.Status}}' | grep -E 'baron|ory' || true" # 5-1) Start local auth broker used by the app. ensure_auth_server # 6) Run health checks and smoke tests with retries startup_ok=false i=1 while [ "$i" -le "$RETRIES" ]; do log "Health check attempt $i/$RETRIES" if [ "$DRY_RUN" = false ]; then if run_check_script "$i" && run_api_smoke_script "$i"; then startup_ok=true break fi else log "(dry-run) would run check-baron-api-env.sh and api-smoke.sh" startup_ok=true break fi i=$((i + 1)) if [ "$i" -le "$RETRIES" ]; then run_cmd "sleep $SLEEP_BETWEEN" fi done # 7) Save logs run_cmd "cd '$BARON_DIR' && $(compose_cmd "logs --no-color") > '$LOG_DIR/baron-compose.log' 2>&1 || true" if [ "$startup_ok" = false ]; then capture_failure_diagnostics "startup-failure" log "Startup failed: health checks did not pass after $RETRIES attempt(s)" exit 1 fi log "Startup finished: $(date)" if [ "$DRY_RUN" = true ]; then log "Dry-run mode: no actions were actually performed. Use --auto to run for real." fi exit 0