-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdeploy.sh
More file actions
executable file
·143 lines (124 loc) · 6.74 KB
/
deploy.sh
File metadata and controls
executable file
·143 lines (124 loc) · 6.74 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
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────
# zentryx-status · enterprise-mode deploy script
# ─────────────────────────────────────────────────────────────────
# Run this on the deploy host (zentryx-web via Tailscale). It:
#
# 1. Reads the current commit sha from `git rev-parse HEAD` and
# uses it as deploy_id — the unseal token will be bound to it
# 2. Asks the operator for the 6-digit TOTP code from their
# Google Authenticator / Authy / 1Password
# 3. Mints a 60-second JWS via `sealed-env unseal`
# 4. Builds the Docker image (if needed) and starts the container
# with the token + deploy_id injected as env vars
# 5. Tails the startup logs so the operator can confirm the
# service unsealed successfully (look for "Started ApplicationContext")
#
# If the token expires before the JVM finishes booting, the deploy
# fails — re-run with a fresh code. By design: secrets never live
# longer than necessary.
#
# Requirements on the host:
# • sealed-env CLI installed globally (npm i -g sealed-env)
# • The master key + signing key + TOTP secret in ./.env.local
# (or wherever sealed-env is configured to look — see
# `sealed-env config show`)
# • git, docker, docker compose
# ─────────────────────────────────────────────────────────────────
set -euo pipefail
# ── Pre-flight ───────────────────────────────────────────────────
command -v git >/dev/null || { echo "✗ git not installed"; exit 1; }
command -v docker >/dev/null || { echo "✗ docker not installed"; exit 1; }
command -v sealed-env >/dev/null || { echo "✗ sealed-env CLI not installed (npm i -g sealed-env)"; exit 1; }
if [[ ! -f docker-compose.yml ]]; then
echo "✗ run this from the repo root (docker-compose.yml not found here)"
exit 1
fi
if [[ ! -f src/main/resources/.env.sealed ]]; then
echo "✗ src/main/resources/.env.sealed missing — seal it first:"
echo " sealed-env encrypt src/main/resources/.env --mode enterprise \\"
echo " -o src/main/resources/.env.sealed"
exit 1
fi
# ── Resolve deploy_id ────────────────────────────────────────────
DEPLOY_ID="$(git rev-parse HEAD)"
DEPLOY_ID_SHORT="${DEPLOY_ID:0:7}"
# Refuse to deploy with a dirty tree — the deploy_id binds to the
# committed sha, so uncommitted changes would silently NOT be in
# the build. Better to fail loud than ship a stale deploy.
if ! git diff-index --quiet HEAD --; then
echo "✗ working tree is dirty. Commit or stash before deploying."
echo " Files changed:"
git status --short
exit 1
fi
cat <<EOF
┌──────────────────────────────────────────────────────────┐
│ zentryx-status · enterprise deploy │
├──────────────────────────────────────────────────────────┤
│ branch: $(git rev-parse --abbrev-ref HEAD)
│ commit: ${DEPLOY_ID_SHORT}
│ message: $(git log -1 --pretty=%s | head -c 50)
└──────────────────────────────────────────────────────────┘
EOF
# ── TOTP prompt ──────────────────────────────────────────────────
read -r -p " TOTP code (6 digits, no spaces): " TOTP_CODE
echo
if [[ ! "${TOTP_CODE}" =~ ^[0-9]{6}$ ]]; then
echo "✗ invalid TOTP — must be exactly 6 digits"
exit 1
fi
# ── Source .env.local so sealed-env CLI sees SEALED_ENV_KEY etc.
# The CLI doesn't auto-discover .env.local when invoked from a script
# (different from interactive use). `set -a` exports every var defined
# in the file; we scope this to a subshell so the master keys don't
# leak into the child docker compose process below.
if [[ ! -f .env.local ]]; then
echo "✗ .env.local not found in $(pwd) — copy it from the operator's laptop:"
echo " scp ~/.../.env.local root@$(hostname):/opt/zentryx/status/.env.local"
exit 1
fi
# ── Mint the unseal token ────────────────────────────────────────
echo "▸ Minting unseal token (TTL 60s, bound to ${DEPLOY_ID_SHORT})..."
SEALED_ENV_UNSEAL_TOKEN="$(
set -a
# shellcheck disable=SC1091
. ./.env.local
set +a
sealed-env unseal \
--file src/main/resources/.env.sealed \
--totp "${TOTP_CODE}" \
--deploy-id "${DEPLOY_ID}" \
--ttl 60 \
| grep -oE 'usl_[A-Za-z0-9._-]+' | head -1
)"
if [[ -z "${SEALED_ENV_UNSEAL_TOKEN}" ]]; then
echo "✗ unseal failed — check TOTP code (rotates every 30s) and try again"
exit 1
fi
export SEALED_ENV_UNSEAL_TOKEN
export SEALED_ENV_DEPLOY_ID="${DEPLOY_ID}"
# ── Build + run ──────────────────────────────────────────────────
echo "▸ Building image..."
docker compose build --quiet status
echo "▸ Starting container with unseal token..."
docker compose up -d status
# ── Tail startup logs ────────────────────────────────────────────
echo "▸ Watching logs for 25s (Ctrl+C to detach early)..."
echo
( docker compose logs -f --tail=0 status & ) > /tmp/zsdeploy.log 2>&1 &
LOGS_PID=$!
sleep 25
kill "${LOGS_PID}" 2>/dev/null || true
# ── Verify health ────────────────────────────────────────────────
echo
echo "▸ Health check..."
if curl -sf http://127.0.0.1:8090/actuator/health >/dev/null; then
echo "✓ zentryx-status is UP (deploy ${DEPLOY_ID_SHORT})"
echo " Status JSON: curl http://127.0.0.1:8090/api/v1/status"
else
echo "✗ health check failed — service didn't unseal in time?"
echo " Check logs: docker compose logs --tail 100 status"
echo " If 'unseal token expired', re-run this script (token TTL is 60s)"
exit 1
fi