server/deploy.sh: build here, back up, migrate, switch, verify
Production ran code from 31 August and answered 404 to most of the API the merchant and mobile clients are written against. The script builds the web app into a static linux binary on the developer machine (the host has 3.6 GB shared with other services and must not compile), backs the database up, runs the migrations with the new binary while the old server still serves so a failure stops with nothing changed, switches, and proves the routes over the public URL. API.md now names the API host correctly: mcp.loyaly.ai, not the console's platform.loyaly.ai. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
This commit is contained in:
14
server/Dockerfile.runtime
Normal file
14
server/Dockerfile.runtime
Normal file
@@ -0,0 +1,14 @@
|
||||
# Runtime-only image, for a host that must not compile.
|
||||
#
|
||||
# The production box has 3.6 GB of RAM shared with other tenants' services;
|
||||
# `Dockerfile` pulls a Go toolchain and builds there, which is how deploys
|
||||
# became something nobody wanted to run. deploy.sh builds the static binary
|
||||
# on the developer's machine and ships only that. Same runtime layer as
|
||||
# Dockerfile, on purpose - the two must not drift.
|
||||
FROM alpine:3.20
|
||||
RUN apk add --no-cache ca-certificates tzdata && \
|
||||
adduser -D -u 10001 behavision
|
||||
COPY behavision-server /usr/local/bin/behavision-server
|
||||
USER behavision
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["/usr/local/bin/behavision-server"]
|
||||
71
server/deploy.sh
Executable file
71
server/deploy.sh
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
# Deploy the server to the production host, from this checkout.
|
||||
#
|
||||
# Until this existed the deployment was manual to a box nobody had written
|
||||
# down, and production ran code months behind main - three of the desktop
|
||||
# app's screens talked to routes that were not there. A deploy that is a
|
||||
# script gets run; one that is a memory does not.
|
||||
#
|
||||
# server/deploy.sh build, back up the database, migrate, switch
|
||||
# BASELINE=3 server/deploy.sh first run against a database that predates
|
||||
# migration tracking: adopt 1..3 unrun
|
||||
# DRY_RUN=1 server/deploy.sh build and ship, touch nothing running
|
||||
#
|
||||
# What it does, in order, and why the order matters:
|
||||
# 1. builds the head-office web app INTO the Go module, then a static
|
||||
# linux/amd64 binary here - the host has 3.6 GB of RAM and must not compile
|
||||
# 2. pg_dumps the database to backups/ on the host BEFORE anything changes
|
||||
# 3. builds the runtime-only image on the host from the shipped binary
|
||||
# 4. runs `migrate` with the NEW binary while the OLD server still serves;
|
||||
# a failing migration therefore stops here with production untouched
|
||||
# 5. switches the container, then proves the routes answer over the public URL
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
HOST=${HOST:-root@66.116.226.161}
|
||||
KEY=${KEY:-$HOME/.ssh/behavision_deploy}
|
||||
REMOTE_DIR=/root/behavision
|
||||
PUBLIC=https://mcp.loyaly.ai
|
||||
SSH=(ssh -i "$KEY" -o BatchMode=yes -o ConnectTimeout=10 "$HOST")
|
||||
|
||||
VERSION=$(git describe --tags --always --dirty)
|
||||
case "$VERSION" in *-dirty) echo "refusing to deploy uncommitted changes ($VERSION)" >&2; exit 1;; esac
|
||||
|
||||
step() { printf '\n\033[1m%s\033[0m\n' "$*"; }
|
||||
|
||||
step "1. Build $VERSION"
|
||||
(cd ../web && npm run build >/dev/null)
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath \
|
||||
-ldflags "-s -w -X main.version=${VERSION}" -o /tmp/behavision-server ./cmd/behavision-server
|
||||
ls -la /tmp/behavision-server | awk '{print " " $5 " bytes"}'
|
||||
|
||||
step "2. Ship"
|
||||
"${SSH[@]}" "mkdir -p $REMOTE_DIR/release/$VERSION $REMOTE_DIR/backups"
|
||||
scp -q -i "$KEY" /tmp/behavision-server Dockerfile.runtime "$HOST:$REMOTE_DIR/release/$VERSION/"
|
||||
git rev-parse HEAD | "${SSH[@]}" "cat > $REMOTE_DIR/release/$VERSION/GIT_SHA"
|
||||
|
||||
if [ "${DRY_RUN:-}" != "" ]; then echo "DRY_RUN: shipped to $REMOTE_DIR/release/$VERSION, nothing changed"; exit 0; fi
|
||||
|
||||
step "3. Back up the database"
|
||||
"${SSH[@]}" "docker exec behavision-db sh -c 'PGPASSWORD=\$POSTGRES_PASSWORD pg_dump -U behavision -d behavision' | gzip > $REMOTE_DIR/backups/pre-$VERSION-\$(date +%Y%m%d-%H%M%S).sql.gz && ls -la $REMOTE_DIR/backups | tail -1"
|
||||
|
||||
step "4. Image"
|
||||
"${SSH[@]}" "cd $REMOTE_DIR/release/$VERSION && docker build -q -t behavision-backend:$VERSION -f Dockerfile.runtime . && docker tag behavision-backend:$VERSION behavision-backend:latest"
|
||||
|
||||
step "5. Migrate (old server still serving)"
|
||||
# `run` uses the compose service's environment and network, so the new binary
|
||||
# reaches postgres exactly as the server will. --no-deps: do not restart the
|
||||
# broker or the database to run a migration.
|
||||
if [ -n "${BASELINE:-}" ]; then
|
||||
"${SSH[@]}" "cd $REMOTE_DIR && docker compose run --rm --no-deps -T backend migrate -baseline $BASELINE"
|
||||
fi
|
||||
"${SSH[@]}" "cd $REMOTE_DIR && docker compose run --rm --no-deps -T backend migrate && docker compose run --rm --no-deps -T backend migrate -status"
|
||||
|
||||
step "6. Switch"
|
||||
"${SSH[@]}" "cd $REMOTE_DIR && docker compose up -d --no-build --no-deps backend && sleep 4 && docker logs --tail 15 behavision-backend"
|
||||
|
||||
step "7. Verify over $PUBLIC"
|
||||
for p in /healthz /api/admin/clients /api/team /api/visits /api/cameras; do
|
||||
printf ' %-20s %s\n' "$p" "$(curl -s -o /dev/null -w '%{http_code}' -m 15 "$PUBLIC$p")"
|
||||
done
|
||||
curl -s -m 15 "$PUBLIC/healthz" | head -c 300; echo
|
||||
Reference in New Issue
Block a user