Files
tdc114plus/scripts/startup.sh
T

444 lines
13 KiB
Bash
Executable File

#!/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 <<EOF
Usage: $0 [--dry-run] [--auto] [--wait=N] [--skip-android-check]
--dry-run : show commands and write non-destructive logs (default)
--auto : actually perform startup actions
--wait=N : seconds to wait for services to stabilize (default 30)
--skip-android-check : skip Android target preflight (not recommended)
--help : show this message
EOF
exit 0
;;
--wait) echo "Please use --wait=N" >&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