diff --git a/deploy/deploy_iuri_backend_to_hetzner.sh b/deploy/deploy_iuri_backend_to_hetzner.sh index 76001e2f0..2f90f8ffd 100755 --- a/deploy/deploy_iuri_backend_to_hetzner.sh +++ b/deploy/deploy_iuri_backend_to_hetzner.sh @@ -20,12 +20,20 @@ ENV_SRC="/etc/iuri/iuri-backend.env" ENV_DST="${REPO_DIR}/backend/.env" ENV_FILE="${ENV_DST}" +timestamp_utc() { date -u +"%Y-%m-%dT%H:%M:%SZ"; } +log_stage() { echo "[${1}] $(timestamp_utc)"; } + +trap 'echo "[FAIL] $(timestamp_utc) Deploy failed"' ERR + echo "Deploy backend to ${REMOTE_HOST}" echo "Local git SHA: ${GIT_COMMIT}" REPO_DIR="${REPO_DIR}" REPO_URL="${REPO_URL}" GIT_COMMIT="${GIT_COMMIT}" ssh -tt ${SSH_OPTS} "${REMOTE_HOST}" bash -s <<'EOS' set -euo pipefail +timestamp_utc() { date -u +"%Y-%m-%dT%H:%M:%SZ"; } +log_stage() { echo "[${1}] $(timestamp_utc)"; } + BACKEND_ROOT="${BACKEND_ROOT:-/opt/iuri/iuri-react-codex}" REPO_DIR="${REPO_DIR:-${BACKEND_ROOT}}" REPO_URL="${REPO_URL:-https://github.com/Cheewye/iuri-react-codex.git}" @@ -51,6 +59,7 @@ if ! sudo -n /usr/bin/true 2>/dev/null; then exit 1 fi +log_stage "SYNC" if [ ! -d "${REPO_DIR}/.git" ]; then echo "REPO_DIR missing or not a git repo: ${REPO_DIR}" mkdir -p "$(dirname "${REPO_DIR}")" @@ -70,14 +79,17 @@ else fi if [ ! -d "${VENV_DIR}" ]; then + log_stage "VENV" python3 -m venv "${VENV_DIR}" fi +log_stage "PIP" timeout 5m "${VENV_DIR}/bin/pip" install --upgrade pip timeout 10m "${VENV_DIR}/bin/pip" install -r "${REQ_FILE}" mkdir -p "${REPO_DIR}/backend" +log_stage "SERVICE" sudo -n mkdir -p /etc/iuri if [ ! -f "${ENV_SRC}" ]; then sudo -n tee "${ENV_SRC}" >/dev/null </dev/null; then @@ -123,5 +136,15 @@ fi echo "OK: backend /health is up" EOS -echo "Remote health check:" -timeout 15s curl -fsS https://iuriapp.com/api/health +log_stage "PUBLIC" +if command -v getent >/dev/null 2>&1 && getent hosts iuriapp.com >/dev/null 2>&1; then + if curl -fsS --max-time 5 https://iuriapp.com/health >/dev/null 2>&1; then + echo "OK: public /health responded" + else + echo "WARN: public /health check failed (non-fatal)" + fi +else + echo "WARN: DNS lookup failed for iuriapp.com (skipping public health check)" +fi + +echo "[OK] $(timestamp_utc) Deploy finished" diff --git a/deploy/nginx/iuriapp.com.conf b/deploy/nginx/iuriapp.com.conf index d43e1a66f..71fd3fa5b 100644 --- a/deploy/nginx/iuriapp.com.conf +++ b/deploy/nginx/iuriapp.com.conf @@ -33,4 +33,9 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } + + # Legacy probe noise: redirect /v1/* to /api/v1/* + location = /v1/metrics/persistence { + return 308 /api/v1/metrics/persistence; + } } diff --git a/docs/deploy/RUNTIME_MAP_HETZNER.md b/docs/deploy/RUNTIME_MAP_HETZNER.md new file mode 100644 index 000000000..a9b3f8dc6 --- /dev/null +++ b/docs/deploy/RUNTIME_MAP_HETZNER.md @@ -0,0 +1,53 @@ +# Runtime Map — Hetzner (iuriapp.com) + +Goal: make deploy + runtime mapping explicit and verifiable without guesswork. + +## Services (expected) +- **nginx**: edge HTTP/HTTPS, static frontend + reverse proxy +- **iuri-backend** (systemd): uvicorn bound to **127.0.0.1:8001** + +## Domain routing (expected) +- `https://iuriapp.com` (or `https://www.iuriapp.com`) + - Static frontend from nginx docroot + - `/api/*` proxied to backend + - `/health` proxied to backend +- `https://api.iuriapp.com` (legacy compatibility) + - Proxies all paths to backend + +> Repo has multiple nginx configs. Verify which one is active on server: +> - `/etc/nginx/conf.d/iuriapp.conf` (docker upstream: `iuri-backend:8000`) +> - `/etc/nginx/sites-enabled/iuriapp.conf` (if using sites-enabled) +> - `/etc/nginx/sites-enabled/iuriapp.com.conf` (if using deploy/nginx templates) + +## Health endpoints +- **Internal**: `http://127.0.0.1:8001/health` +- **Public**: `https://iuriapp.com/health` + +## Quick verification (run on server, not automated) +```bash +systemctl status iuri-backend +curl -i http://127.0.0.1:8001/health +curl -I https://iuriapp.com/health +``` + +## Deploy workflow (v2) +- GitHub Actions uses SSH + `./deploy/deploy_iuri_backend_to_hetzner.sh` +- Pre-flight checks: + - `sudo -n true` (NOPASSWD required) + - repo dir exists: `/opt/iuri/iuri-react-codex` + +### Required sudo NOPASSWD commands (deploy user) +- `/bin/systemctl daemon-reload` +- `/bin/systemctl restart iuri-backend` +- `/bin/systemctl --no-pager --full status iuri-backend` + +## Known noise: /v1/metrics/persistence +Some probes hit `/v1/metrics/persistence` (missing `/api`), generating 404s. +Mitigation in nginx (safe, non-breaking): +- `return 308 /api/v1/metrics/persistence;` + +## Rollback notes +- Revert the nginx rule or deploy script changes via git. +- Validate with: + - `nginx -t` (then reload) if nginx config is changed. + - `systemctl status iuri-backend` after rollback deploy. diff --git a/nginx/conf.d/iuriapp.conf b/nginx/conf.d/iuriapp.conf index 5fcb14ef4..f3d1ef30b 100644 --- a/nginx/conf.d/iuriapp.conf +++ b/nginx/conf.d/iuriapp.conf @@ -80,26 +80,31 @@ server { proxy_set_header Connection "upgrade"; } + # Legacy probe noise: redirect /v1/* to /api/v1/* + location = /v1/metrics/persistence { + return 308 /api/v1/metrics/persistence; + } + # WebSocket endpoint (same-origin) location /ws/ { proxy_pass http://iuri-backend:8000; proxy_http_version 1.1; - + # WebSocket headers proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; - + # Standard proxy headers proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - + # WebSocket timeouts (largos) proxy_connect_timeout 7d; proxy_send_timeout 7d; proxy_read_timeout 7d; - + # Buffering off para WebSocket proxy_buffering off; } @@ -240,8 +245,13 @@ server { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; - + # NOTA: CORS no necesario aquí porque todo debería ser same-origin (www.iuriapp.com/api/*) # Este servidor (api.iuriapp.com) se mantiene solo por compatibilidad/SSL } + + # Legacy probe noise: redirect /v1/* to /api/v1/* + location = /v1/metrics/persistence { + return 308 /api/v1/metrics/persistence; + } }