profileShare

rasmusjy / roundtable

Read-only snapshot

No repository description.

main default branch 181 files Expires Sep 13, 2026, 9:06 AM
deploy.sh 3,439 bytes
1 #!/usr/bin/env bash
2 # One-command redeploy for roundtable.rasmusj.com.
3 #
4 # Ships the source to the VPS, builds the image there and restarts the stack
5 # described by docker-compose.vps.yml. Run from Git Bash on Windows: ./deploy.sh
6 #
7 # Two things this script deliberately never touches:
8 #
9 # .env lives on the server only. It holds ENCRYPTION_KEY, which every
10 # saved BYOK key is encrypted with: overwrite it and those keys
11 # can never be read back.
12 # Caddyfile the shared reverse proxy at /opt/caddy serves every site on the
13 # box, so a routine redeploy cannot take the others down with it.
14 #
15 # Postgres lives in a named Docker volume and survives every rebuild.
16 #
17 # ./deploy.sh build, restart, wait for health
18 # ./deploy.sh --seed the same, then re-seed the public /demo debates
19 set -euo pipefail
20
21 # Deploy target. Kept out of the repository on purpose: this code is shared
22 # read-only with people outside the project, and the server address and
23 # login are not theirs to have. Set it once in your shell:
24 #
25 # export VPS=user@host
26 #
27 VPS=${VPS:?set VPS=user@host before deploying}
28 DEST=/opt/projects/roundtable
29 COMPOSE="docker compose -f docker-compose.vps.yml"
30
31 cd "$(dirname "$0")"
32
33 SEED=0
34 [ "${1:-}" = "--seed" ] && SEED=1
35
36 echo "==> Running the test suite before shipping anything..."
37 # Git Bash on Windows does not see a globally installed pnpm, so fall back to
38 # the one npx fetches rather than failing before anything has been checked.
39 if command -v pnpm >/dev/null 2>&1; then PNPM=pnpm; else PNPM="npx --yes pnpm"; fi
40 $PNPM test
41
42 echo "==> Ensuring VPS project dir exists..."
43 ssh "$VPS" "mkdir -p '$DEST'"
44
45 echo "==> Checking that .env exists on the server..."
46 if ! ssh "$VPS" "test -f '$DEST/.env'"; then
47 echo ""
48 echo " $DEST/.env is missing. Create it on the server first, see DEPLOY.md."
49 echo " Nothing has been changed."
50 exit 1
51 fi
52
53 echo "==> Uploading source (node_modules, .next, .env and coverage stay out of it)..."
54 tar --exclude=node_modules --exclude=.next --exclude=.env --exclude=.git \
55 --exclude=coverage --exclude=tsconfig.tsbuildinfo -czf - . | ssh "$VPS" "
56 rm -rf '$DEST/src.tmp' &&
57 mkdir -p '$DEST/src.tmp' &&
58 tar -C '$DEST/src.tmp' -xzf - &&
59 cp '$DEST/.env' '$DEST/src.tmp/.env' &&
60 rm -rf '$DEST/app' &&
61 mv '$DEST/src.tmp' '$DEST/app'
62 "
63
64 echo "==> Building and restarting the stack..."
65 ssh "$VPS" "cd '$DEST/app' && $COMPOSE up -d --build"
66
67 if [ "$SEED" = 1 ]; then
68 echo "==> Seeding the public demo debates..."
69 # The runtime image is the pruned standalone bundle and has no tsx, so the
70 # seed runs from the build stage instead, on the same network as Postgres.
71 ssh "$VPS" "cd '$DEST/app' &&
72 docker build --target builder -t roundtable-seed . &&
73 docker run --rm --network roundtable_internal --env-file .env \
74 -e DATABASE_URL=\"postgresql://roundtable:\$(grep '^POSTGRES_PASSWORD=' .env | cut -d= -f2-)@db:5432/roundtable?schema=public\" \
75 roundtable-seed pnpm db:seed
76 "
77 fi
78
79 echo "==> Waiting for the health check to go green..."
80 ssh "$VPS" "
81 for i in \$(seq 1 20); do
82 state=\$(docker inspect -f '{{.State.Health.Status}}' roundtable 2>/dev/null || echo starting)
83 [ \"\$state\" = healthy ] && echo ' healthy' && exit 0
84 sleep 3
85 done
86 echo ' still not healthy, check: docker logs roundtable'
87 exit 1
88 "
89
90 echo ""
91 echo "==> Done. Live at https://roundtable.rasmusj.com"
92