-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdeploy-demo.sh
More file actions
executable file
·157 lines (141 loc) · 6.5 KB
/
Copy pathdeploy-demo.sh
File metadata and controls
executable file
·157 lines (141 loc) · 6.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!/usr/bin/env bash
#
# Deploy / update the public demo environment at *.demo.tarassov.me.
#
# Stands up the cpp-env umbrella (API + worker + frontend + Mailpit) in ns
# `demo`, wired to the cluster's SHARED infra (see values-demo.yaml's header):
# - Postgres: CNPG cluster `postgresql` in ns `db` — this script bootstraps
# the `cpp-api-demo` role + database + required extensions idempotently,
# and mirrors the credentials into the ns-db secret `postgresql-cpp-api-demo`
# (the naming pattern every other tenant of that cluster follows);
# - Redis: shared Sentinel Redis in ns `db`, logical DB 1 (REDIS_DB
# isolation — needs images ≥ 1.5.4); the password is READ from the ns-db
# secret `redis`, never generated here;
# - Traces: cluster Tempo in ns `monitoring`, viewed via Grafana.
#
# Idempotent — re-run to update. App secrets are generated once into a
# gitignored file and reused, so the Postgres password stays stable across
# re-deploys.
#
# Usage:
# ./scripts/deploy-demo.sh
#
# Env overrides: KUBE_CONTEXT, DEMO_NAMESPACE, DEMO_RELEASE, DEMO_ADMIN_EMAIL.
set -euo pipefail
CTX="${KUBE_CONTEXT:-admin@talos-nbg1}"
NS="${DEMO_NAMESPACE:-demo}"
RELEASE="${DEMO_RELEASE:-demo}"
ADMIN_EMAIL="${DEMO_ADMIN_EMAIL:-admin@demo.tarassov.me}"
# Fixed (not random) so it can be documented in the README; override if you fork.
ADMIN_PASS="${DEMO_ADMIN_PASS:-DemoAdmin-2026}"
DB_NS="db"
DB_CLUSTER="postgresql"
DB_NAME="cpp-api-demo"
DB_ROLE="cpp-api-demo"
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
CHART="$ROOT/helm/cpp-env"
SECRETS="$CHART/.demo-secrets.env" # gitignored — generated once, reused
for bin in helm kubectl openssl; do
command -v "$bin" >/dev/null 2>&1 || {
echo "ERROR: '$bin' not found" >&2
exit 2
}
done
# ── App secrets: generate once, reuse afterwards (stable DB password) ──
if [[ -f "$SECRETS" ]]; then
echo "==> Reusing demo secrets ($SECRETS)"
# shellcheck source=/dev/null
source "$SECRETS"
else
echo "==> Generating fresh demo secrets → $SECRETS"
DB_PASS="$(openssl rand -hex 24)"
JWT_SECRET="$(openssl rand -hex 32)"
(
umask 177
cat >"$SECRETS" <<EOF
DB_PASS=$DB_PASS
JWT_SECRET=$JWT_SECRET
EOF
)
fi
: "${DB_PASS:?$SECRETS is missing DB_PASS}" "${JWT_SECRET:?$SECRETS is missing JWT_SECRET}"
# ── Shared Redis password: read, never generate ──
REDIS_PASS="$(kubectl --context "$CTX" -n "$DB_NS" get secret redis \
-o jsonpath='{.data.redis-password}' | base64 -d)"
[[ -n "$REDIS_PASS" ]] || {
echo "ERROR: could not read secret/redis (key redis-password) from ns $DB_NS" >&2
exit 2
}
# ── Shared Postgres: bootstrap role + database + extensions (idempotent) ──
PRIMARY="$(kubectl --context "$CTX" -n "$DB_NS" get cluster "$DB_CLUSTER" \
-o jsonpath='{.status.currentPrimary}')"
[[ -n "$PRIMARY" ]] || {
echo "ERROR: CNPG cluster $DB_CLUSTER in ns $DB_NS has no currentPrimary" >&2
exit 2
}
pg() { kubectl --context "$CTX" -n "$DB_NS" exec "$PRIMARY" -c postgres -- psql -U postgres -Atc "$1"; }
echo "==> Bootstrapping role/db '$DB_NAME' on $DB_CLUSTER (primary: $PRIMARY)"
if [[ "$(pg "SELECT 1 FROM pg_roles WHERE rolname='$DB_ROLE'")" == "1" ]]; then
# Keep the live role in sync with the secrets file (handles rotation).
pg "ALTER ROLE \"$DB_ROLE\" WITH LOGIN PASSWORD '$DB_PASS'" >/dev/null
else
pg "CREATE ROLE \"$DB_ROLE\" LOGIN PASSWORD '$DB_PASS'" >/dev/null
fi
if [[ "$(pg "SELECT 1 FROM pg_database WHERE datname='$DB_NAME'")" != "1" ]]; then
pg "CREATE DATABASE \"$DB_NAME\" OWNER \"$DB_ROLE\"" >/dev/null
fi
# Extensions the migrations need but a non-superuser owner cannot create.
for ext in pgcrypto citext; do
kubectl --context "$CTX" -n "$DB_NS" exec "$PRIMARY" -c postgres -- \
psql -U postgres -d "$DB_NAME" -Atc "CREATE EXTENSION IF NOT EXISTS \"$ext\"" >/dev/null
done
# Mirror the credentials in the ns-db secret, following the cluster's
# postgresql-<tenant> convention (informational for operators; the app itself
# takes the password via --set below).
kubectl --context "$CTX" -n "$DB_NS" create secret generic "postgresql-$DB_NAME" \
--type=kubernetes.io/basic-auth \
--from-literal=username="$DB_ROLE" --from-literal=password="$DB_PASS" \
--dry-run=client -o yaml | kubectl --context "$CTX" apply -f - >/dev/null
echo "==> helm dependency build"
helm dependency build "$CHART" >/dev/null
echo "==> helm upgrade --install $RELEASE → ns/$NS (context $CTX)"
helm --kube-context "$CTX" upgrade --install "$RELEASE" "$CHART" \
-n "$NS" --create-namespace \
-f "$CHART/values-demo.yaml" \
--set credentials.dbPassword="$DB_PASS" \
--set credentials.redisPassword="$REDIS_PASS" \
--set credentials.jwtSecret="$JWT_SECRET" \
--set cpp-api.externalDatabase.password="$DB_PASS" \
--set cpp-api.externalRedis.password="$REDIS_PASS" \
--set cpp-api.auth.jwtSecret="$JWT_SECRET" \
--set cpp-worker.externalDatabase.password="$DB_PASS" \
--set cpp-worker.externalRedis.password="$REDIS_PASS" \
--set cpp-worker.auth.jwtSecret="$JWT_SECRET" \
--wait --timeout 10m
echo "==> Waiting for the API rollout"
kubectl --context "$CTX" -n "$NS" rollout status deploy/api --timeout=5m
# ── Demo admin + sample data (idempotent: create-admin is a no-op if it exists) ──
echo "==> Ensuring demo admin ($ADMIN_EMAIL) + seeding sample users"
# Newest RUNNING pod: right after a rollout the label still matches the old
# replica set's terminating pod, and exec-ing into it fails with "container
# not found" (bit this deploy script on the 1.5.4 rollout).
POD="$(kubectl --context "$CTX" -n "$NS" get pod -l app.kubernetes.io/name=cpp-api \
--field-selector=status.phase=Running \
--sort-by=.metadata.creationTimestamp \
-o jsonpath='{.items[-1:].metadata.name}')"
# config path is positional and must precede the mode flag.
kubectl --context "$CTX" -n "$NS" exec "$POD" -- \
/app/cpp_api_template config/config.json --create-admin "$ADMIN_EMAIL" "$ADMIN_PASS" || true
kubectl --context "$CTX" -n "$NS" exec "$POD" -- \
/app/cpp_api_template config/config.json --seed-fake 8 || true
cat <<EOF
============================================================
Demo is live (give DNS + TLS a minute on first deploy):
App https://app.demo.tarassov.me
API https://api.demo.tarassov.me/healthz
Mailbox https://mail.demo.tarassov.me
Traces Grafana → Explore → Tempo (grafana.tarassov.me)
Demo admin: $ADMIN_EMAIL
Password: $ADMIN_PASS
============================================================
EOF