Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions deploy/deploy_iuri_backend_to_hetzner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand All @@ -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}")"
Expand All @@ -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 <<EOF
Expand All @@ -99,7 +111,8 @@ timeout 30s sudo -n systemctl daemon-reload
timeout 30s sudo -n systemctl restart iuri-backend
timeout 30s sudo -n systemctl --no-pager --full status iuri-backend | head -n 20

echo "Waiting for backend /health on 127.0.0.1:8001..."
log_stage "HEALTH"
echo "CHECK: waiting for http://127.0.0.1:8001/health"
ok=0
for i in $(seq 1 45); do
if curl -fsS --max-time 2 http://127.0.0.1:8001/health >/dev/null; then
Expand All @@ -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"
5 changes: 5 additions & 0 deletions deploy/nginx/iuriapp.com.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
53 changes: 53 additions & 0 deletions docs/deploy/RUNTIME_MAP_HETZNER.md
Original file line number Diff line number Diff line change
@@ -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.
20 changes: 15 additions & 5 deletions nginx/conf.d/iuriapp.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
}
Loading