feat(deploy): Staging-Umgebung und einheitliches Deploy-Skript
Fügt docker-compose.staging.yml, Staging-Dokumentation und -dest prod|stage in update-prod.sh hinzu, damit Prod und Staging über ein Skript deploybar sind. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -15,6 +15,11 @@ DeepLAPIKey=
|
|||||||
# Production (kapteins-daagbok.eu):
|
# Production (kapteins-daagbok.eu):
|
||||||
# RP_ID=kapteins-daagbok.eu
|
# RP_ID=kapteins-daagbok.eu
|
||||||
# ORIGIN=https://kapteins-daagbok.eu
|
# ORIGIN=https://kapteins-daagbok.eu
|
||||||
|
# Staging (staging.kapteins-daagbok.eu):
|
||||||
|
# RP_ID=staging.kapteins-daagbok.eu
|
||||||
|
# ORIGIN=https://staging.kapteins-daagbok.eu
|
||||||
|
# POSTGRES_DB=daagbox_staging
|
||||||
|
# NTFY_TOPIC=kapteins-daagbok-staging-feedback
|
||||||
RP_ID=localhost
|
RP_ID=localhost
|
||||||
# Must match the frontend URL exactly (Vite dev: http://localhost:5173; Docker: http://localhost)
|
# Must match the frontend URL exactly (Vite dev: http://localhost:5173; Docker: http://localhost)
|
||||||
ORIGIN=http://localhost:5173
|
ORIGIN=http://localhost:5173
|
||||||
|
|||||||
@@ -251,15 +251,25 @@ Produktions-Update auf den Server (konfigurierbar via Umgebungsvariablen). Führ
|
|||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./scripts/update-prod.sh
|
./scripts/update-prod.sh -dest prod
|
||||||
```
|
```
|
||||||
|
|
||||||
Standard-Ziel: `root@10.0.0.25:/opt/kapteins-daagbok` — per `REMOTE_HOST`, `REMOTE_USER`, `REMOTE_DIR` überschreibbar.
|
Standard-Ziel Prod: `root@10.0.0.25:/opt/kapteins-daagbok` — per `REMOTE_HOST`, `REMOTE_USER`, `REMOTE_DIR` überschreibbar.
|
||||||
|
|
||||||
Auf dem Server müssen `.env` u. a. `POSTGRES_PASSWORD`, `RP_ID`, `ORIGIN` (`https://kapteins-daagbok.eu`), `SESSION_SECRET` (≥ 32 Zeichen), `TRUST_PROXY` (NPM, z. B. `172.16.10.10` oder `1`) und bei Push `VAPID_*` enthalten. Optional `NTFY_*` für Feedback. Nach Schema-Änderungen: `npx prisma db push` im Backend-Container.
|
Auf dem Server müssen `.env` u. a. `POSTGRES_PASSWORD`, `RP_ID`, `ORIGIN` (`https://kapteins-daagbok.eu`), `SESSION_SECRET` (≥ 32 Zeichen), `TRUST_PROXY` (NPM, z. B. `172.16.10.10` oder `1`) und bei Push `VAPID_*` enthalten. Optional `NTFY_*` für Feedback. Nach Schema-Änderungen: `npx prisma db push` im Backend-Container.
|
||||||
|
|
||||||
Hinter **Nginx Proxy Manager**: [docs/deployment/npm-security.md](docs/deployment/npm-security.md).
|
Hinter **Nginx Proxy Manager**: [docs/deployment/npm-security.md](docs/deployment/npm-security.md).
|
||||||
|
|
||||||
|
### Staging
|
||||||
|
|
||||||
|
Testumgebung unter [staging.kapteins-daagbok.eu](https://staging.kapteins-daagbok.eu) — Deploy ohne Release-Tag:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/update-prod.sh -dest stage
|
||||||
|
```
|
||||||
|
|
||||||
|
Standard-Ziel Staging: `root@10.0.0.27:/opt/kapteins-daagbok-staging` — per `REMOTE_HOST`, `REMOTE_DIR`, `DEPLOY_BRANCH` überschreibbar. Details: [docs/deployment/staging.md](docs/deployment/staging.md).
|
||||||
|
|
||||||
## Dokumentation
|
## Dokumentation
|
||||||
|
|
||||||
| Dokument | Inhalt |
|
| Dokument | Inhalt |
|
||||||
@@ -267,6 +277,7 @@ Hinter **Nginx Proxy Manager**: [docs/deployment/npm-security.md](docs/deploymen
|
|||||||
| [docs/deployment/npm-security.md](docs/deployment/npm-security.md) | NPM, TLS, `trust proxy`, Security-Header |
|
| [docs/deployment/npm-security.md](docs/deployment/npm-security.md) | NPM, TLS, `trust proxy`, Security-Header |
|
||||||
| [docs/deployment/predeploy.md](docs/deployment/predeploy.md) | Pre-Deploy-Checks ohne CI |
|
| [docs/deployment/predeploy.md](docs/deployment/predeploy.md) | Pre-Deploy-Checks ohne CI |
|
||||||
| [docs/deployment/postgres-password.md](docs/deployment/postgres-password.md) | PostgreSQL-Passwort rotieren / App-Rolle |
|
| [docs/deployment/postgres-password.md](docs/deployment/postgres-password.md) | PostgreSQL-Passwort rotieren / App-Rolle |
|
||||||
|
| [docs/deployment/staging.md](docs/deployment/staging.md) | Staging-VM, Deploy, `.env` |
|
||||||
| [docs/plausible-events.md](docs/plausible-events.md) | Custom Events für Plausible Analytics |
|
| [docs/plausible-events.md](docs/plausible-events.md) | Custom Events für Plausible Analytics |
|
||||||
| [docs/push-notifications-plan.md](docs/push-notifications-plan.md) | Web Push: Architektur, API, Testplan |
|
| [docs/push-notifications-plan.md](docs/push-notifications-plan.md) | Web Push: Architektur, API, Testplan |
|
||||||
| [docs/plan-compass-course-dial.md](docs/plan-compass-course-dial.md) | Kompass-Dial: UX- und Implementierungsplan |
|
| [docs/plan-compass-course-dial.md](docs/plan-compass-course-dial.md) | Kompass-Dial: UX- und Implementierungsplan |
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
container_name: daagbox-staging-db
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER:-postgres}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB:-daagbox_staging}
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U \"$$POSTGRES_USER\" -d \"$$POSTGRES_DB\""]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ./server
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: daagbox-staging-backend
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
PORT: 5000
|
||||||
|
DATABASE_URL: "postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-daagbox_staging}?schema=public"
|
||||||
|
RP_ID: ${RP_ID:-localhost}
|
||||||
|
ORIGIN: ${ORIGIN:-http://localhost}
|
||||||
|
TRUST_PROXY: ${TRUST_PROXY:-1}
|
||||||
|
VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY:-}
|
||||||
|
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY:-}
|
||||||
|
VAPID_SUBJECT: ${VAPID_SUBJECT:-mailto:support@kapteins-daagbok.eu}
|
||||||
|
OpenWeatherMapAPIKey: ${OpenWeatherMapAPIKey:-}
|
||||||
|
OpenRouterAPIKey: ${OpenRouterAPIKey:-}
|
||||||
|
OpenRouterModel: ${OpenRouterModel:-anthropic/claude-3.5-haiku}
|
||||||
|
SESSION_SECRET: ${SESSION_SECRET:-}
|
||||||
|
ADMIN_USER_IDS: ${ADMIN_USER_IDS:-}
|
||||||
|
NTFY_SERVER: ${NTFY_SERVER:-https://ntfy.sh}
|
||||||
|
NTFY_TOPIC: ${NTFY_TOPIC:-}
|
||||||
|
NTFY_TOKEN: ${NTFY_TOKEN:-}
|
||||||
|
command: sh -c "npx prisma db push && node dist/index.js"
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: client/Dockerfile
|
||||||
|
args:
|
||||||
|
APP_VERSION: ${APP_VERSION:-0.1.0.0-dev}
|
||||||
|
container_name: daagbox-staging-frontend
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
depends_on:
|
||||||
|
backend:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
||||||
|
name: daagbox-staging-pgdata
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
# Deployment: Nginx Proxy Manager & Security (Sprint 1)
|
# Deployment: Nginx Proxy Manager & Security (Sprint 1)
|
||||||
|
|
||||||
Kapteins Daagbok läuft öffentlich unter **https://kapteins-daagbok.eu/** hinter **Nginx Proxy Manager** (NPM, z. B. `172.16.10.10`) mit Upstream auf den App-Stack (`172.16.10.110`).
|
Kapteins Daagbok läuft öffentlich unter **https://kapteins-daagbok.eu/** (Produktion) und **https://staging.kapteins-daagbok.eu/** (Staging) hinter **Nginx Proxy Manager** (NPM, z. B. `172.16.10.10`) mit Upstream auf die App-VMs (`10.0.0.25` Prod, `10.0.0.27` Staging).
|
||||||
|
|
||||||
## NPM Proxy Host
|
## NPM Proxy Host
|
||||||
|
|
||||||
| Einstellung | Wert |
|
| Einstellung | Wert |
|
||||||
|-------------|------|
|
|-------------|------|
|
||||||
| Domain | `kapteins-daagbok.eu` |
|
| Domain | `kapteins-daagbok.eu` / `staging.kapteins-daagbok.eu` |
|
||||||
| Scheme | `https` |
|
| Scheme | `https` |
|
||||||
| Forward Hostname / IP | `172.16.10.110` (oder Container-Port auf dem Host) |
|
| Forward Hostname / IP | `10.0.0.25` (Prod) / `10.0.0.27` (Staging) |
|
||||||
| Forward Port | `80` (Frontend-Nginx) |
|
| Forward Port | `80` (Frontend-Nginx) |
|
||||||
| Websockets | an, falls genutzt |
|
| Websockets | an, falls genutzt |
|
||||||
| Block Common Exploits | an |
|
| Block Common Exploits | an |
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ cd server && npm test
|
|||||||
[`scripts/update-prod.sh`](../../scripts/update-prod.sh) führt `predeploy-check.sh` **automatisch** aus (nach Release-Vorbereitung, vor dem SSH-Deploy).
|
[`scripts/update-prod.sh`](../../scripts/update-prod.sh) führt `predeploy-check.sh` **automatisch** aus (nach Release-Vorbereitung, vor dem SSH-Deploy).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./scripts/update-prod.sh
|
./scripts/update-prod.sh -dest prod
|
||||||
```
|
```
|
||||||
|
|
||||||
Notfall ohne Checks (nur wenn nötig): `SKIP_PREDEPLOY_CHECK=1 ./scripts/update-prod.sh`
|
Notfall ohne Checks (nur wenn nötig): `SKIP_PREDEPLOY_CHECK=1 ./scripts/update-prod.sh -dest prod`
|
||||||
|
|
||||||
Manuell auf dem Server: `git pull`, `docker compose build`, `docker compose up -d` (siehe [npm-security.md](npm-security.md)).
|
Manuell auf dem Server: `git pull`, `docker compose build`, `docker compose up -d` (siehe [npm-security.md](npm-security.md)).
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
# Staging-Umgebung
|
||||||
|
|
||||||
|
Staging läuft auf **VM3** (`10.0.0.27`) unter **https://staging.kapteins-daagbok.eu/** — hinter Nginx Proxy Manager wie Produktion.
|
||||||
|
|
||||||
|
## Unterschiede zu Produktion
|
||||||
|
|
||||||
|
| | Staging | Produktion |
|
||||||
|
|---|---------|------------|
|
||||||
|
| Host | `10.0.0.27` | `10.0.0.25` |
|
||||||
|
| Verzeichnis | `/opt/kapteins-daagbok-staging` | `/opt/kapteins-daagbok` |
|
||||||
|
| Compose | `docker-compose.staging.yml` | `docker-compose.yml` |
|
||||||
|
| Deploy-Skript | `./scripts/update-prod.sh -dest stage` | `./scripts/update-prod.sh -dest prod` |
|
||||||
|
| Release-Tag | nein | ja (`v*`) |
|
||||||
|
| Datenbank-Volume | `daagbox-staging-pgdata` | `daagbox-prod-pgdata` |
|
||||||
|
|
||||||
|
Staging ist **vollständig isoliert**: eigene DB, Session-Secrets, Passkeys (`RP_ID=staging.kapteins-daagbok.eu`) und optional eigene VAPID-/Ntfy-Konfiguration.
|
||||||
|
|
||||||
|
## Erstinstallation (VM3)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh root@10.0.0.27
|
||||||
|
|
||||||
|
git clone https://gitea.elpatron.me/elpatron/kapteins-daagbok.git /opt/kapteins-daagbok-staging
|
||||||
|
cd /opt/kapteins-daagbok-staging
|
||||||
|
git checkout master
|
||||||
|
|
||||||
|
# .env anlegen — Secrets neu generieren, nicht von Prod kopieren
|
||||||
|
openssl rand -hex 24 # POSTGRES_PASSWORD
|
||||||
|
openssl rand -base64 48 # SESSION_SECRET
|
||||||
|
|
||||||
|
nano .env
|
||||||
|
docker compose -f docker-compose.staging.yml up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
### `.env` (Staging)
|
||||||
|
|
||||||
|
```env
|
||||||
|
ORIGIN=https://staging.kapteins-daagbok.eu
|
||||||
|
RP_ID=staging.kapteins-daagbok.eu
|
||||||
|
TRUST_PROXY=1
|
||||||
|
|
||||||
|
POSTGRES_USER=postgres
|
||||||
|
POSTGRES_PASSWORD=<generiert>
|
||||||
|
POSTGRES_DB=daagbox_staging
|
||||||
|
|
||||||
|
SESSION_SECRET=<generiert>
|
||||||
|
|
||||||
|
NTFY_SERVER=https://ntfy.sh
|
||||||
|
NTFY_TOPIC=kapteins-daagbok-staging-feedback
|
||||||
|
```
|
||||||
|
|
||||||
|
Optional: `VAPID_*`, `OpenWeatherMapAPIKey`, `OpenRouterAPIKey`, `ADMIN_USER_IDS`, `NTFY_TOKEN`.
|
||||||
|
|
||||||
|
## Deploy vom Entwicklungsrechner
|
||||||
|
|
||||||
|
Führt `npm run check` aus, dann SSH-Deploy ohne Release-Tag:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/update-prod.sh -dest stage
|
||||||
|
```
|
||||||
|
|
||||||
|
Konfiguration via Umgebungsvariablen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
REMOTE_HOST=10.0.0.27 \
|
||||||
|
REMOTE_DIR=/opt/kapteins-daagbok-staging \
|
||||||
|
DEPLOY_BRANCH=master \
|
||||||
|
./scripts/update-prod.sh -dest stage
|
||||||
|
```
|
||||||
|
|
||||||
|
Notfall ohne Checks: `SKIP_PREDEPLOY_CHECK=1 ./scripts/update-prod.sh -dest stage`
|
||||||
|
|
||||||
|
## NPM (VM1)
|
||||||
|
|
||||||
|
| Einstellung | Wert |
|
||||||
|
|-------------|------|
|
||||||
|
| Domain | `staging.kapteins-daagbok.eu` |
|
||||||
|
| Forward Hostname / IP | `10.0.0.27` |
|
||||||
|
| Forward Port | `80` |
|
||||||
|
| SSL | Let's Encrypt |
|
||||||
|
|
||||||
|
Empfohlen: Custom Header `X-Robots-Tag: noindex, nofollow` (Staging nicht indexieren).
|
||||||
|
|
||||||
|
Details zu Proxy-Headern und Security: [npm-security.md](npm-security.md).
|
||||||
|
|
||||||
|
## Nach Deploy prüfen
|
||||||
|
|
||||||
|
1. https://staging.kapteins-daagbok.eu/api/health — `status: ok`
|
||||||
|
2. Neuen Test-Account registrieren (Prod-Passkeys funktionieren nicht auf Staging)
|
||||||
|
3. Passkey Login
|
||||||
|
4. Cookie `daagbok_session`: `Secure`, `HttpOnly`, `SameSite=Lax`
|
||||||
|
|
||||||
|
## Daten zurücksetzen
|
||||||
|
|
||||||
|
Staging-Daten sind wegwerfbar:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/kapteins-daagbok-staging
|
||||||
|
docker compose -f docker-compose.staging.yml down
|
||||||
|
docker volume rm daagbox-staging-pgdata
|
||||||
|
docker compose -f docker-compose.staging.yml up -d
|
||||||
|
```
|
||||||
+150
-54
@@ -2,28 +2,102 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Remote deployment configuration
|
usage() {
|
||||||
# Override any of these via environment variables if needed, e.g.:
|
cat <<EOF
|
||||||
# REMOTE_HOST=192.168.1.10 ./scripts/update-prod.sh
|
Usage: $(basename "$0") [-dest prod|stage]
|
||||||
REMOTE_USER="${REMOTE_USER:-root}"
|
|
||||||
REMOTE_HOST="${REMOTE_HOST:-10.0.0.25}"
|
|
||||||
REMOTE_DIR="${REMOTE_DIR:-/opt/kapteins-daagbok}"
|
|
||||||
REMOTE_TARGET="${REMOTE_USER}@${REMOTE_HOST}"
|
|
||||||
|
|
||||||
# Configuration
|
Deploy Kapteins Daagbok to production or staging.
|
||||||
COMPOSE_FILE="docker-compose.yml"
|
|
||||||
BACKEND_CONTAINER="daagbox-prod-backend"
|
-dest prod Production (default): release tag, bump VERSION, deploy to 10.0.0.25
|
||||||
MAX_WAIT=35
|
-dest stage Staging: no release tag, deploy branch to 10.0.0.27
|
||||||
|
|
||||||
|
Environment overrides (optional):
|
||||||
|
REMOTE_HOST, REMOTE_USER, REMOTE_DIR, COMPOSE_FILE, BACKEND_CONTAINER
|
||||||
|
DEPLOY_BRANCH (stage only, default: master)
|
||||||
|
SKIP_PREDEPLOY_CHECK=1
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$(basename "$0") -dest prod
|
||||||
|
$(basename "$0") -dest stage
|
||||||
|
DEPLOY_BRANCH=feature/foo $(basename "$0") -dest stage
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
DEST="prod"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-dest)
|
||||||
|
if [[ $# -lt 2 ]]; then
|
||||||
|
echo "Error: -dest requires an argument (prod or stage)." >&2
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
DEST="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-dest=*)
|
||||||
|
DEST="${1#*=}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Error: Unknown argument: $1" >&2
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
case "$DEST" in
|
||||||
|
prod|stage) ;;
|
||||||
|
*)
|
||||||
|
echo "Error: Invalid -dest '$DEST' (use prod or stage)." >&2
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
VERSION_FILE="$REPO_ROOT/VERSION"
|
VERSION_FILE="$REPO_ROOT/VERSION"
|
||||||
DEFAULT_VERSION="0.1.0.0"
|
DEFAULT_VERSION="0.1.0.0"
|
||||||
|
MAX_WAIT=35
|
||||||
|
|
||||||
|
REMOTE_USER="${REMOTE_USER:-root}"
|
||||||
|
|
||||||
|
if [[ "$DEST" == "stage" ]]; then
|
||||||
|
REMOTE_HOST="${REMOTE_HOST:-10.0.0.27}"
|
||||||
|
REMOTE_DIR="${REMOTE_DIR:-/opt/kapteins-daagbok-staging}"
|
||||||
|
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.staging.yml}"
|
||||||
|
BACKEND_CONTAINER="${BACKEND_CONTAINER:-daagbox-staging-backend}"
|
||||||
|
APP_URL="${APP_URL:-https://staging.kapteins-daagbok.eu}"
|
||||||
|
DEPLOY_BRANCH="${DEPLOY_BRANCH:-master}"
|
||||||
|
ENV_LABEL="Staging"
|
||||||
|
else
|
||||||
|
REMOTE_HOST="${REMOTE_HOST:-10.0.0.25}"
|
||||||
|
REMOTE_DIR="${REMOTE_DIR:-/opt/kapteins-daagbok}"
|
||||||
|
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
|
||||||
|
BACKEND_CONTAINER="${BACKEND_CONTAINER:-daagbox-prod-backend}"
|
||||||
|
APP_URL="${APP_URL:-https://kapteins-daagbok.eu}"
|
||||||
|
DEPLOY_BRANCH=""
|
||||||
|
ENV_LABEL="Production"
|
||||||
|
fi
|
||||||
|
|
||||||
|
REMOTE_TARGET="${REMOTE_USER}@${REMOTE_HOST}"
|
||||||
|
|
||||||
echo "=================================================="
|
echo "=================================================="
|
||||||
echo " Kapteins Daagbok Prod Environment Update "
|
echo " Kapteins Daagbok ${ENV_LABEL} Update"
|
||||||
echo "=================================================="
|
echo "=================================================="
|
||||||
echo "Target: ${REMOTE_TARGET}:${REMOTE_DIR}"
|
echo "Destination: ${DEST}"
|
||||||
|
echo "Target: ${REMOTE_TARGET}:${REMOTE_DIR}"
|
||||||
|
if [[ "$DEST" == "stage" ]]; then
|
||||||
|
echo "Branch: ${DEPLOY_BRANCH}"
|
||||||
|
fi
|
||||||
|
echo "URL: ${APP_URL}"
|
||||||
echo "=================================================="
|
echo "=================================================="
|
||||||
|
|
||||||
cd "$REPO_ROOT"
|
cd "$REPO_ROOT"
|
||||||
@@ -123,7 +197,11 @@ prepare_release() {
|
|||||||
export APP_VERSION="$release_version"
|
export APP_VERSION="$release_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
prepare_release
|
if [[ "$DEST" == "prod" ]]; then
|
||||||
|
prepare_release
|
||||||
|
else
|
||||||
|
APP_VERSION="$(read_current_version)"
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "${SKIP_PREDEPLOY_CHECK:-}" == "1" ]]; then
|
if [[ "${SKIP_PREDEPLOY_CHECK:-}" == "1" ]]; then
|
||||||
echo "Skipping pre-deploy checks (SKIP_PREDEPLOY_CHECK=1)."
|
echo "Skipping pre-deploy checks (SKIP_PREDEPLOY_CHECK=1)."
|
||||||
@@ -135,56 +213,77 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "=================================================="
|
echo "=================================================="
|
||||||
echo "Deploying ${APP_VERSION} to ${REMOTE_TARGET}:${REMOTE_DIR}"
|
echo "Deploying v${APP_VERSION} to ${REMOTE_TARGET}:${REMOTE_DIR}"
|
||||||
echo "=================================================="
|
echo "=================================================="
|
||||||
|
|
||||||
# Run the whole update procedure remotely over SSH.
|
|
||||||
ssh -o ConnectTimeout=10 "$REMOTE_TARGET" 'bash -s' -- \
|
ssh -o ConnectTimeout=10 "$REMOTE_TARGET" 'bash -s' -- \
|
||||||
"$REMOTE_DIR" "$COMPOSE_FILE" "$BACKEND_CONTAINER" "$MAX_WAIT" "$REMOTE_HOST" "$APP_VERSION" <<'REMOTE_SCRIPT'
|
"$REMOTE_DIR" "$COMPOSE_FILE" "$BACKEND_CONTAINER" "$MAX_WAIT" "$APP_URL" "$APP_VERSION" "$DEST" "$DEPLOY_BRANCH" <<'REMOTE_SCRIPT'
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
REMOTE_DIR="$1"
|
REMOTE_DIR="$1"
|
||||||
COMPOSE_FILE="$2"
|
COMPOSE_FILE="$2"
|
||||||
BACKEND_CONTAINER="$3"
|
BACKEND_CONTAINER="$3"
|
||||||
MAX_WAIT="$4"
|
MAX_WAIT="$4"
|
||||||
REMOTE_HOST="$5"
|
APP_URL="$5"
|
||||||
APP_VERSION="$6"
|
APP_VERSION="$6"
|
||||||
|
DEST="$7"
|
||||||
|
DEPLOY_BRANCH="$8"
|
||||||
|
|
||||||
cd "$REMOTE_DIR" || { echo "Error: Remote directory '$REMOTE_DIR' not found."; exit 1; }
|
cd "$REMOTE_DIR" || { echo "Error: Remote directory '$REMOTE_DIR' not found."; exit 1; }
|
||||||
|
|
||||||
echo "Syncing repository from origin..."
|
|
||||||
CURRENT_BRANCH="$(git branch --show-current)"
|
|
||||||
if [ -z "$CURRENT_BRANCH" ]; then
|
|
||||||
echo "Error: Could not determine current Git branch."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! git diff-index --quiet HEAD -- || [ -n "$(git status --porcelain)" ]; then
|
if ! git diff-index --quiet HEAD -- || [ -n "$(git status --porcelain)" ]; then
|
||||||
echo "Warning: Local changes on deployment host will be discarded."
|
echo "Warning: Local changes on deployment host will be discarded."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git fetch --tags origin
|
if [[ "$DEST" == "stage" ]]; then
|
||||||
if [ $? -ne 0 ]; then
|
echo "Syncing repository from origin/${DEPLOY_BRANCH}..."
|
||||||
echo "Error: Git fetch failed."
|
git fetch origin
|
||||||
exit 1
|
if [ $? -ne 0 ]; then
|
||||||
fi
|
echo "Error: Git fetch failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
git checkout "$DEPLOY_BRANCH" 2>/dev/null || git checkout -b "$DEPLOY_BRANCH" "origin/${DEPLOY_BRANCH}"
|
||||||
|
git reset --hard "origin/${DEPLOY_BRANCH}"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Error: Git reset to origin/${DEPLOY_BRANCH} failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Syncing repository from origin..."
|
||||||
|
CURRENT_BRANCH="$(git branch --show-current)"
|
||||||
|
if [ -z "$CURRENT_BRANCH" ]; then
|
||||||
|
echo "Error: Could not determine current Git branch."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
git reset --hard "origin/${CURRENT_BRANCH}"
|
git fetch --tags origin
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "Error: Git reset to origin/${CURRENT_BRANCH} failed."
|
echo "Error: Git fetch failed."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
REMOTE_VERSION="$(tr -d '[:space:]' < VERSION)"
|
git reset --hard "origin/${CURRENT_BRANCH}"
|
||||||
if [ "$REMOTE_VERSION" != "$APP_VERSION" ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "Note: Remote VERSION file already points to next release (v${REMOTE_VERSION})."
|
echo "Error: Git reset to origin/${CURRENT_BRANCH} failed."
|
||||||
echo " Building deployed release v${APP_VERSION}."
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
REMOTE_VERSION="$(tr -d '[:space:]' < VERSION)"
|
||||||
|
if [ "$REMOTE_VERSION" != "$APP_VERSION" ]; then
|
||||||
|
echo "Note: Remote VERSION file already points to next release (v${REMOTE_VERSION})."
|
||||||
|
echo " Building deployed release v${APP_VERSION}."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export APP_VERSION="$APP_VERSION"
|
export APP_VERSION="$APP_VERSION"
|
||||||
|
|
||||||
echo "Rebuilding Docker images without cache (APP_VERSION=${APP_VERSION})..."
|
if [[ "$DEST" == "prod" ]]; then
|
||||||
docker compose -f "$COMPOSE_FILE" build --no-cache
|
echo "Rebuilding Docker images without cache (APP_VERSION=${APP_VERSION})..."
|
||||||
|
docker compose -f "$COMPOSE_FILE" build --no-cache
|
||||||
|
else
|
||||||
|
echo "Rebuilding Docker images (APP_VERSION=${APP_VERSION})..."
|
||||||
|
docker compose -f "$COMPOSE_FILE" build
|
||||||
|
fi
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "Error: Docker compose build failed."
|
echo "Error: Docker compose build failed."
|
||||||
exit 1
|
exit 1
|
||||||
@@ -198,10 +297,7 @@ if [ $? -ne 0 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Cleaning up old/unused Docker resources..."
|
echo "Cleaning up old/unused Docker resources..."
|
||||||
docker system prune -f
|
docker system prune -f || echo "Warning: Docker system prune failed."
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "Warning: Docker system prune failed to run completely."
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Waiting for services to become healthy..."
|
echo "Waiting for services to become healthy..."
|
||||||
COUNTER=0
|
COUNTER=0
|
||||||
@@ -227,15 +323,15 @@ docker compose -f "$COMPOSE_FILE" ps
|
|||||||
echo "=================================================="
|
echo "=================================================="
|
||||||
|
|
||||||
if [ "$IS_READY" = true ]; then
|
if [ "$IS_READY" = true ]; then
|
||||||
echo "SUCCESS: Production environment updated and healthy!"
|
echo "SUCCESS: ${DEST} environment updated and healthy!"
|
||||||
echo " -> Version: v${APP_VERSION}"
|
echo " -> Version: v${APP_VERSION}"
|
||||||
echo " -> App Frontend (Nginx): http://${REMOTE_HOST}"
|
echo " -> App Frontend: ${APP_URL}"
|
||||||
echo " -> Backend API Health: http://${REMOTE_HOST}/api/health"
|
echo " -> Backend API Health: ${APP_URL}/api/health"
|
||||||
echo "=================================================="
|
echo "=================================================="
|
||||||
else
|
else
|
||||||
echo "WARNING: Backend did not transition to healthy in time."
|
echo "WARNING: Backend did not transition to healthy in time."
|
||||||
echo "Check backend container logs for details:"
|
echo "Check backend container logs:"
|
||||||
echo " -> docker compose logs backend"
|
echo " -> docker compose -f ${COMPOSE_FILE} logs backend"
|
||||||
echo "=================================================="
|
echo "=================================================="
|
||||||
exit 3
|
exit 3
|
||||||
fi
|
fi
|
||||||
@@ -244,11 +340,11 @@ REMOTE_SCRIPT
|
|||||||
REMOTE_EXIT=$?
|
REMOTE_EXIT=$?
|
||||||
echo "=================================================="
|
echo "=================================================="
|
||||||
if [ $REMOTE_EXIT -eq 0 ]; then
|
if [ $REMOTE_EXIT -eq 0 ]; then
|
||||||
echo "Remote update completed successfully on ${REMOTE_TARGET} (v${APP_VERSION})."
|
echo "${ENV_LABEL} update completed successfully on ${REMOTE_TARGET} (v${APP_VERSION})."
|
||||||
elif [ $REMOTE_EXIT -eq 3 ]; then
|
elif [ $REMOTE_EXIT -eq 3 ]; then
|
||||||
echo "Remote update finished, but the backend was not healthy in time on ${REMOTE_TARGET}."
|
echo "${ENV_LABEL} update finished, but the backend was not healthy in time on ${REMOTE_TARGET}."
|
||||||
else
|
else
|
||||||
echo "Remote update FAILED on ${REMOTE_TARGET} (exit code: ${REMOTE_EXIT})."
|
echo "${ENV_LABEL} update FAILED on ${REMOTE_TARGET} (exit code: ${REMOTE_EXIT})."
|
||||||
fi
|
fi
|
||||||
echo "=================================================="
|
echo "=================================================="
|
||||||
exit $REMOTE_EXIT
|
exit $REMOTE_EXIT
|
||||||
|
|||||||
Executable
+5
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Backward-compatible wrapper — prefer: ./scripts/update-prod.sh -dest stage
|
||||||
|
set -euo pipefail
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
exec "$SCRIPT_DIR/update-prod.sh" -dest stage "$@"
|
||||||
Reference in New Issue
Block a user