aboutsummaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-17 19:55:36 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-17 19:55:36 +0200
commit380195f7cd5e57acf2c1cf2bc41069e6b0b979ed (patch)
tree32a88fb22a7fbe8f1fd5c105156d1f928c93950d /scripts
downloadbokf-380195f7cd5e57acf2c1cf2bc41069e6b0b979ed.tar.gz
bokf-380195f7cd5e57acf2c1cf2bc41069e6b0b979ed.zip
Initial commit: daemon, clients, docs, Docker deploy pipelinev0.1.0
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/deploy.sh128
1 files changed, 128 insertions, 0 deletions
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
new file mode 100755
index 0000000..2abd1e1
--- /dev/null
+++ b/scripts/deploy.sh
@@ -0,0 +1,128 @@
+#!/usr/bin/env bash
+# Deploy a locally built bokf image to BOKF_HOST over SSH. See docs/DEPLOY.md.
+#
+# scripts/deploy.sh [tag]
+#
+# BOKF_HOST (user@host) and BOKF_REMOTE_DIR are read from the environment or
+# from .env in the repository root. The host needs only Docker and Compose.
+set -euo pipefail
+
+cd "$(dirname "$0")/.."
+
+env_val() {
+ sed -n "s/^$1=//p" .env 2>/dev/null | tail -n 1
+}
+
+if [ -f .env ]; then
+ BOKF_HOST="${BOKF_HOST:-$(env_val BOKF_HOST)}"
+ BOKF_REMOTE_DIR="${BOKF_REMOTE_DIR:-$(env_val BOKF_REMOTE_DIR)}"
+ BOKF_BUILD="${BOKF_BUILD:-$(env_val BOKF_BUILD)}"
+fi
+
+: "${BOKF_HOST:?set BOKF_HOST (user@host) in .env or the environment}"
+BOKF_REMOTE_DIR="${BOKF_REMOTE_DIR:-/srv/bokf}"
+SSH=(ssh "$BOKF_HOST")
+
+TAG="${1:-}"
+if [ -z "$TAG" ]; then
+ TAG="$(git describe --tags --always --dirty 2>/dev/null || echo dev)"
+fi
+case "$TAG" in
+ *[!A-Za-z0-9._-]* | "")
+ echo "deploy: invalid tag '$TAG'" >&2
+ exit 1
+ ;;
+esac
+
+remote() { "${SSH[@]}" "$1"; }
+
+echo "deploy: gate: build + tests"
+make -j"$(nproc)"
+make test
+
+BOKF_BUILD="${BOKF_BUILD:-auto}"
+case "$BOKF_BUILD" in
+ auto)
+ host_arch="$(remote 'uname -m' 2>/dev/null || echo unknown)"
+ if [ "$(uname -m)" = "$host_arch" ]; then
+ BOKF_BUILD=local
+ else
+ echo "deploy: host is $host_arch, building there"
+ BOKF_BUILD=remote
+ fi
+ ;;
+ local | remote) ;;
+ *)
+ echo "deploy: BOKF_BUILD must be auto, local or remote" >&2
+ exit 1
+ ;;
+esac
+
+if [ "$BOKF_BUILD" = local ]; then
+ echo "deploy: building image bokf:$TAG locally"
+ docker build -t "bokf:$TAG" .
+ echo "deploy: shipping image to $BOKF_HOST"
+ docker save "bokf:$TAG" | gzip | "${SSH[@]}" 'gunzip | docker load'
+else
+ echo "deploy: building image bokf:$TAG on $BOKF_HOST"
+ tar -cf - \
+ --exclude=./.git --exclude=./build --exclude=./var --exclude=./.env \
+ --exclude='./*.se' --exclude='./*.db' --exclude='./*.db-wal' \
+ --exclude='./*.db-shm' --exclude=./docs . |
+ "${SSH[@]}" "docker build -t 'bokf:$TAG' -"
+fi
+
+remote "mkdir -p '$BOKF_REMOTE_DIR'"
+scp -q compose.yaml "$BOKF_HOST:$BOKF_REMOTE_DIR/compose.yaml"
+
+prev_tag="$(remote "grep -E '^BOKF_TAG=' '$BOKF_REMOTE_DIR/.env' 2>/dev/null | cut -d= -f2" || true)"
+
+remote "touch '$BOKF_REMOTE_DIR/.env'
+ sed -i '/^BOKF_IMAGE=/d;/^BOKF_TAG=/d' '$BOKF_REMOTE_DIR/.env'
+ printf 'BOKF_IMAGE=bokf\nBOKF_TAG=%s\n' '$TAG' >> '$BOKF_REMOTE_DIR/.env'"
+
+if ! remote "test -f '$BOKF_REMOTE_DIR/var/db/bokfd.db'"; then
+ echo "deploy: fresh host: no database yet."
+ echo "deploy: run once on $BOKF_HOST, then deploy again:"
+ echo " cd $BOKF_REMOTE_DIR"
+ echo " docker compose run --rm -e BOKFD_PASSWORD='<admin-password>' bokfd init"
+ echo " docker compose up -d"
+ exit 0
+fi
+
+echo "deploy: starting containers"
+remote "cd '$BOKF_REMOTE_DIR' && docker compose up -d --no-build"
+
+wait_healthy() {
+ for _ in $(seq 1 30); do
+ cid="$(remote "cd '$BOKF_REMOTE_DIR' && docker compose ps -q bokfd" 2>/dev/null || true)"
+ if [ -n "$cid" ]; then
+ st="$(remote "docker inspect --format '{{.State.Health.Status}}' '$cid'" 2>/dev/null || true)"
+ if [ "$st" = "healthy" ]; then
+ return 0
+ fi
+ fi
+ sleep 2
+ done
+ return 1
+}
+
+if wait_healthy; then
+ echo "deploy: bokf:$TAG is healthy on $BOKF_HOST"
+ exit 0
+fi
+
+echo "deploy: health check failed for bokf:$TAG" >&2
+remote "cd '$BOKF_REMOTE_DIR' && docker compose logs --tail=50 bokfd" >&2 || true
+
+if [ -n "$prev_tag" ] && [ "$prev_tag" != "$TAG" ]; then
+ echo "deploy: rolling back to bokf:$prev_tag" >&2
+ remote "sed -i 's/^BOKF_TAG=.*/BOKF_TAG=$prev_tag/' '$BOKF_REMOTE_DIR/.env'"
+ if remote "cd '$BOKF_REMOTE_DIR' && docker compose up -d --no-build" &&
+ wait_healthy; then
+ echo "deploy: rollback to bokf:$prev_tag is healthy" >&2
+ else
+ echo "deploy: rollback FAILED, manual attention needed" >&2
+ fi
+fi
+exit 1