diff --git a/production/scripts/docker-prod-common.sh b/production/scripts/docker-prod-common.sh index 9e5b9a0..74b896d 100644 --- a/production/scripts/docker-prod-common.sh +++ b/production/scripts/docker-prod-common.sh @@ -422,6 +422,94 @@ run_prod_migrations() { prod_compose run --rm migrate } +# Ensures a platform admin account exists for the production database, prompting +# interactively only when stdin is a TTY. In non-interactive deploys, set +# ADMIN_PASSWORD for first-time setup, and set ADMIN_UPDATE_EXISTING=true to +# intentionally rotate an existing admin password. +ensure_prod_admin() { + export_env_value_if_unset ADMIN_SEED_EMAIL + export_env_value_if_unset ADMIN_SEED_FIRST_NAME + export_env_value_if_unset ADMIN_SEED_LAST_NAME + + local email="${ADMIN_SEED_EMAIL:-rentaldrivego@gmail.com}" + local first_name="${ADMIN_SEED_FIRST_NAME:-Super}" + local last_name="${ADMIN_SEED_LAST_NAME:-Admin}" + + local admin_exists=0 + echo "Checking admin user ${email}..." + if prod_compose run --rm \ + -v "${ROOT_DIR}/scripts:/app/scripts:ro" \ + -e ADMIN_EMAIL="${email}" \ + api node scripts/create-admin-user.cjs --check; then + admin_exists=1 + fi + + if [[ ! -t 0 ]]; then + if [[ -z "${ADMIN_PASSWORD:-}" ]]; then + echo "Skipping admin setup (stdin is not a TTY and ADMIN_PASSWORD is not set)." + echo "Set ADMIN_PASSWORD for first-time setup. Set ADMIN_UPDATE_EXISTING=true to rotate an existing admin password." >&2 + return 0 + fi + + if [[ ${admin_exists} -eq 1 && "${ADMIN_UPDATE_EXISTING:-false}" != "true" ]]; then + echo "Admin user ${email} already exists; keeping existing password." + echo "Set ADMIN_UPDATE_EXISTING=true with ADMIN_PASSWORD to rotate it non-interactively." >&2 + return 0 + fi + + prod_compose run --rm \ + -v "${ROOT_DIR}/scripts:/app/scripts:ro" \ + -e ADMIN_EMAIL="${email}" \ + -e ADMIN_PASSWORD="${ADMIN_PASSWORD}" \ + -e ADMIN_FIRST_NAME="${first_name}" \ + -e ADMIN_LAST_NAME="${last_name}" \ + -e ADMIN_UPDATE_EXISTING="${ADMIN_UPDATE_EXISTING:-false}" \ + api node scripts/create-admin-user.cjs + + echo "Admin user ${email} is ready." + return 0 + fi + + if [[ ${admin_exists} -eq 1 ]]; then + echo "Admin user ${email} already exists." + local update_answer + read -r -p "Update its password? [y/N] " update_answer + case "${update_answer}" in + y|Y|yes|YES|Yes) ;; + *) + echo "Keeping existing admin password." + return 0 + ;; + esac + else + echo "Admin user ${email} does not exist; it will be created." + fi + + local password="" + while [[ ${#password} -lt 8 ]]; do + if ! read -r -s -p "Enter admin password (min 8 characters): " password; then + echo + echo "No password provided; aborting admin setup." >&2 + return 1 + fi + echo + if [[ ${#password} -lt 8 ]]; then + echo "Password must be at least 8 characters." >&2 + fi + done + + prod_compose run --rm \ + -v "${ROOT_DIR}/scripts:/app/scripts:ro" \ + -e ADMIN_EMAIL="${email}" \ + -e ADMIN_PASSWORD="${password}" \ + -e ADMIN_FIRST_NAME="${first_name}" \ + -e ADMIN_LAST_NAME="${last_name}" \ + -e ADMIN_UPDATE_EXISTING=true \ + api node scripts/create-admin-user.cjs + + echo "Admin user ${email} is ready." +} + wait_for_healthy() { local service="$1" local timeout="${2:-120}" diff --git a/production/scripts/docker-prod-deploy.sh b/production/scripts/docker-prod-deploy.sh index d4bf67e..01f48d0 100644 --- a/production/scripts/docker-prod-deploy.sh +++ b/production/scripts/docker-prod-deploy.sh @@ -25,6 +25,9 @@ wait_for_healthy postgres 120 echo "Running database migrations" run_prod_migrations +echo "Ensuring platform admin account" +ensure_prod_admin + echo "Starting application services" prod_compose up -d api homepage carplace dashboard admin wait_for_healthy api 180 diff --git a/scripts/docker-prod-common.sh b/scripts/docker-prod-common.sh index 8242cc7..349c6cc 100644 --- a/scripts/docker-prod-common.sh +++ b/scripts/docker-prod-common.sh @@ -425,11 +425,6 @@ ensure_prod_admin() { local first_name="${ADMIN_SEED_FIRST_NAME:-Super}" local last_name="${ADMIN_SEED_LAST_NAME:-Admin}" - if [[ ! -t 0 ]]; then - echo "Skipping interactive admin setup (stdin is not a TTY)." - return 0 - fi - local admin_exists=0 echo "Checking admin user ${email}..." if prod_compose run --rm \ @@ -439,6 +434,32 @@ ensure_prod_admin() { admin_exists=1 fi + if [[ ! -t 0 ]]; then + if [[ -z "${ADMIN_PASSWORD:-}" ]]; then + echo "Skipping admin setup (stdin is not a TTY and ADMIN_PASSWORD is not set)." + echo "Set ADMIN_PASSWORD for first-time setup. Set ADMIN_UPDATE_EXISTING=true to rotate an existing admin password." >&2 + return 0 + fi + + if [[ ${admin_exists} -eq 1 && "${ADMIN_UPDATE_EXISTING:-false}" != "true" ]]; then + echo "Admin user ${email} already exists; keeping existing password." + echo "Set ADMIN_UPDATE_EXISTING=true with ADMIN_PASSWORD to rotate it non-interactively." >&2 + return 0 + fi + + prod_compose run --rm \ + -v "${ROOT_DIR}/scripts:/app/scripts:ro" \ + -e ADMIN_EMAIL="${email}" \ + -e ADMIN_PASSWORD="${ADMIN_PASSWORD}" \ + -e ADMIN_FIRST_NAME="${first_name}" \ + -e ADMIN_LAST_NAME="${last_name}" \ + -e ADMIN_UPDATE_EXISTING="${ADMIN_UPDATE_EXISTING:-false}" \ + api node scripts/create-admin-user.cjs + + echo "Admin user ${email} is ready." + return 0 + fi + if [[ ${admin_exists} -eq 1 ]]; then echo "Admin user ${email} already exists." local update_answer