#!/usr/bin/env bash set -euo pipefail # Deploy lecoffre-front (ext) via CI image and validate environment & CORS/state # # Usage: # scripts/deploy_front_ext.sh [--ci] [--no-pull] [--no-up] [--validate-only] # # Behavior: # - By default pulls CI image and restarts only the frontend service, then validates: # 1) NEXT_PUBLIC_* variables in running app match lecoffre_node/.env.master # 2) CORS preflight on dev3 OK (204) with Access-Control-Allow-Origin for dev4 # 3) POST /api/v1/idnot/state on dev3 OK (200) and returns a state # - --ci: print a reminder to push branch/tag ext to trigger CI (no git ops here for safety) # - --no-pull: skip docker compose pull # - --no-up: skip docker compose up (restart) # - --validate-only: skip pull & up, run validations only ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" NODE_DIR="$ROOT_DIR/lecoffre_node" DO_PULL=1 DO_UP=1 DO_CI_HINT=0 for arg in "$@"; do case "$arg" in --ci) DO_CI_HINT=1 ;; --no-pull) DO_PULL=0 ;; --no-up) DO_UP=0 ;; --validate-only) DO_PULL=0; DO_UP=0 ;; *) echo "Unknown arg: $arg" >&2; exit 2 ;; esac done if [[ $DO_CI_HINT -eq 1 ]]; then echo "[HINT] Trigger CI by pushing branch 'ext' and tag 'ext' with commit message prefix 'ci: docker_tag=ext' (no git ops run by this script)." fi if [[ ! -f "$NODE_DIR/.env.master" ]]; then echo "[ERROR] Missing $NODE_DIR/.env.master" >&2 exit 1 fi # Pull and up only the frontend service if [[ $DO_PULL -eq 1 ]]; then echo "[STEP] docker compose pull lecoffre-front" (cd "$NODE_DIR" && docker compose pull lecoffre-front) fi if [[ $DO_UP -eq 1 ]]; then echo "[STEP] docker compose up -d --no-deps lecoffre-front" (cd "$NODE_DIR" && docker compose up -d --no-deps lecoffre-front) fi # Wait for front to respond FRONT_URL="http://localhost:3004" echo "[STEP] Waiting for $FRONT_URL to be ready..." ATTEMPTS=40 until curl -fsS "$FRONT_URL/" >/dev/null 2>&1; do ATTEMPTS=$((ATTEMPTS-1)) || true if [[ $ATTEMPTS -le 0 ]]; then echo "[ERROR] Frontend not responding on $FRONT_URL" >&2 exit 1 fi sleep 2 done echo "[OK] Frontend is responding" # 0) Public URL sanity check (dev4) to catch 404/misrouting early echo "[STEP] Public URL check https://dev4.4nkweb.com/lecoffre (expect 301->/lecoffre/)" PUB_RESP=$(curl -siS "https://dev4.4nkweb.com/lecoffre?nocache=$(date +%s)" 2>/dev/null | sed -n '1,20p') echo "${PUB_RESP}" echo "${PUB_RESP}" | grep -qE "^HTTP/(1.1|2) 301" || { echo "[FAIL] Public URL /lecoffre not 301" >&2; exit 1; } echo "${PUB_RESP}" | grep -qi "^location: .*?/lecoffre/" || { echo "[FAIL] /lecoffre does not redirect to /lecoffre/" >&2; exit 1; } echo "[OK] /lecoffre redirects to /lecoffre/" echo "[STEP] Public URL check https://dev4.4nkweb.com/lecoffre/ (expect 200)" PUB_RESP_SLASH=$(curl -siS "https://dev4.4nkweb.com/lecoffre/?nocache=$(date +%s)" 2>/dev/null | sed -n '1,20p') echo "${PUB_RESP_SLASH}" echo "${PUB_RESP_SLASH}" | grep -qE "^HTTP/(1.1|2) 200" || { echo "[FAIL] Public URL /lecoffre/ not 200" >&2; exit 1; } echo "[OK] /lecoffre/ returns 200" # 1) Validate NEXT_PUBLIC_* variables against .env.master (container env is source of truth) echo "[STEP] Validating NEXT_PUBLIC_* variables vs .env.master (container env)" if ! command -v jq >/dev/null 2>&1; then echo "[ERROR] jq is required for validation" >&2 exit 1 fi # Extract NEXT_PUBLIC_* from .env.master (ignore comments/blank) mapfile -t FILE_KVS < <(grep -E '^[[:space:]]*NEXT_PUBLIC_[A-Za-z0-9_]+=' "$NODE_DIR/.env.master" | sed 's/^[[:space:]]*//' | sort) MISMATCH=0 for kv in "${FILE_KVS[@]}"; do key="${kv%%=*}" expect="${kv#*=}" # Normalize line endings and trim surrounding quotes if any expect="$(printf '%s' "$expect" | tr -d '\r' | sed 's/^\"//; s/\"$//')" # Read from running env # Read from container environment (single source of truth at runtime) actual="$(cd "$NODE_DIR" && docker compose exec -T lecoffre-front /bin/sh -lc "env | grep -E '^${key}=' | head -n1 | sed 's/^${key}=//'" || true)" if [[ "$actual" != "$expect" ]]; then echo "[MISMATCH] $key: running='$actual' vs .env.master='$expect'" MISMATCH=1 fi done if [[ $MISMATCH -ne 0 ]]; then echo "[FAIL] NEXT_PUBLIC_* variables mismatch." exit 1 fi echo "[OK] NEXT_PUBLIC_* variables match .env.master" # 2) CORS preflight check on dev3 echo "[STEP] CORS preflight (OPTIONS) to dev3 idnot/state" PRE="$(curl -i -X OPTIONS 'https://dev3.4nkweb.com/api/v1/idnot/state' \ -H 'Origin: https://dev4.4nkweb.com' \ -H 'Access-Control-Request-Method: POST' \ -H 'Access-Control-Request-Headers: content-type' 2>/dev/null | sed -n '1,20p')" echo "$PRE" echo "$PRE" | grep -q "^HTTP/1.1 204" || { echo "[FAIL] Preflight not 204" >&2; exit 1; } echo "$PRE" | grep -qi "Access-Control-Allow-Origin: https://dev4.4nkweb.com" || { echo "[FAIL] A-C-A-Origin not dev4" >&2; exit 1; } echo "[OK] Preflight CORS ok" # 3) POST state on dev3 echo "[STEP] POST state to dev3" STATE_RESP="$(curl -i -X POST 'https://dev3.4nkweb.com/api/v1/idnot/state' \ -H 'Origin: https://dev4.4nkweb.com' \ -H 'Content-Type: application/json' \ --data '{"next_url":"https://dev4.4nkweb.com/lecoffre/authorized-client"}' 2>/dev/null)" echo "$STATE_RESP" | sed -n '1,30p' echo "$STATE_RESP" | grep -q "^HTTP/1.1 200" || { echo "[FAIL] state endpoint not 200" >&2; exit 1; } echo "$STATE_RESP" | grep -qi "Access-Control-Allow-Origin: https://dev4.4nkweb.com" || { echo "[FAIL] A-C-A-Origin not dev4 on POST" >&2; exit 1; } STATE_JSON="$(echo "$STATE_RESP" | awk 'BEGIN{p=0} /^\r?$/{p=1;next} p{print}')" STATE_VAL="$(echo "$STATE_JSON" | jq -r '.state // empty')" if [[ -z "$STATE_VAL" ]]; then echo "[FAIL] Missing state in response" >&2 exit 1 fi echo "[OK] state received" echo "[SUCCESS] Deployment validation completed."