blob: b625ac5ce7a5ae9cdc6818b43179ab64809f9590 (
plain)
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
|
#!/usr/bin/env bash
# Deploy a locally built bokf image to BOKF_HOST over SSH. See docs/DEPLOY.md.
#
# scripts/deploy.sh [--dev] [tag]
#
# Normal mode cross-compiles the static aarch64 backend binaries on this
# machine and assembles the image on the host when the architectures differ
# (otherwise it builds and ships the image locally). --dev skips the image: it
# copies fresh binaries into the running container and reloads the daemon with
# SIGHUP, which re-execs them in place; the binaries are cross-compiled when
# the host runs a different architecture.
#
# 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")
DEV=0
if [ "${1:-}" = "--dev" ]; then
DEV=1
shift
fi
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"; }
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
}
cross_build() {
echo "deploy: cross-compiling static $host_arch binaries on this machine"
rm -rf .prebuilt
mkdir -p .prebuilt
docker build -q -f deploy/Dockerfile.cross -t bokf-cross deploy/ >/dev/null
docker run --rm -e "VERSION=$TAG" -v "$PWD":/src:ro \
-v "$PWD/.prebuilt":/out bokf-cross
if command -v file >/dev/null 2>&1 &&
! file .prebuilt/bokfd | grep -q aarch64; then
echo "deploy: cross-build produced no arm64 binary" >&2
rm -rf .prebuilt
exit 1
fi
}
echo "deploy: gate: build + tests"
make -j"$(nproc)"
make test
BOKF_BUILD="${BOKF_BUILD:-auto}"
host_arch="$(uname -m)"
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, cross-compiling here"
BOKF_BUILD=remote
fi
;;
local | remote)
host_arch="$(remote 'uname -m' 2>/dev/null || echo unknown)"
;;
*)
echo "deploy: BOKF_BUILD must be auto, local or remote" >&2
exit 1
;;
esac
if [ "$DEV" = 1 ]; then
if ! remote "test -f '$BOKF_REMOTE_DIR/var/db/bokfd.db'"; then
echo "deploy: no database on $BOKF_HOST; run a normal deploy first" >&2
exit 1
fi
if [ "$BOKF_BUILD" = local ]; then
prebin="build"
echo "deploy: dev: using the locally built binaries"
else
cross_build
prebin=".prebuilt"
fi
echo "deploy: dev: copying the binaries into the running container"
remote "mkdir -p /tmp/bokf-dev"
scp -q "$prebin/bokfd" "$prebin/bokfctl" "$BOKF_HOST:/tmp/bokf-dev/"
remote "cd '$BOKF_REMOTE_DIR'
cid=\$(docker compose ps -q bokfd)
test -n \"\$cid\" || { echo 'deploy: bokfd is not running' >&2; exit 1; }
docker cp /tmp/bokf-dev/bokfd \"\$cid:/usr/local/bin/bokfd\"
docker cp /tmp/bokf-dev/bokfctl \"\$cid:/usr/local/bin/bokfctl\"
docker kill --signal=HUP \"\$cid\""
if [ "$prebin" = ".prebuilt" ]; then
rm -rf .prebuilt
fi
ver=""
for _ in $(seq 1 15); do
ver="$(remote "cd '$BOKF_REMOTE_DIR' && docker exec \$(docker compose ps -q bokfd) bokfctl meta 2>/dev/null" |
sed -n 's/.*"version": *"\([^"]*\)".*/\1/p' || true)"
[ "$ver" = "$TAG" ] && break
sleep 1
done
if [ "$ver" = "$TAG" ]; then
echo "deploy: dev reloaded, daemon reports $ver"
exit 0
fi
echo "deploy: dev reload did not come back with $TAG (got '${ver:-nothing}')" >&2
remote "cd '$BOKF_REMOTE_DIR' && docker compose logs --tail=30 bokfd" >&2 || true
exit 1
fi
if [ "$BOKF_BUILD" = local ]; then
echo "deploy: building image bokf:$TAG locally"
docker build --build-arg "VERSION=$TAG" -t "bokf:$TAG" .
echo "deploy: shipping image to $BOKF_HOST"
docker save "bokf:$TAG" | gzip | "${SSH[@]}" 'gunzip | docker load'
else
cross_build
echo "deploy: assembling 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 --build-arg 'VERSION=$TAG' -t 'bokf:$TAG' -"
rm -rf .prebuilt
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"
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
|