aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.dockerignore1
-rw-r--r--Dockerfile19
-rw-r--r--deploy/Dockerfile.cross13
-rw-r--r--deploy/cross-build.sh16
-rw-r--r--docs/DEPLOY.md21
-rw-r--r--docs/STATE.md12
-rwxr-xr-xscripts/deploy.sh52
7 files changed, 100 insertions, 34 deletions
diff --git a/.dockerignore b/.dockerignore
index 6e6f325..110fe6f 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,6 +1,5 @@
.git
.github
-.prebuilt/
build/
var/
.env
diff --git a/Dockerfile b/Dockerfile
index 65ce8df..63d120f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,14 +7,23 @@ RUN apk add --no-cache build-base openssl-dev
WORKDIR /src
COPY . .
ARG VERSION=0.1.0-dev
-RUN make -j"$(nproc)" backend VERSION="$VERSION" \
- && make test-core VERSION="$VERSION" \
+# With .prebuilt/ the static aarch64 binaries are cross-compiled on the
+# development machine (deploy/Dockerfile.cross); otherwise build and test from
+# source on this host.
+RUN if [ -x .prebuilt/bokfd ]; then \
+ mkdir -p build && \
+ cp .prebuilt/bokfd .prebuilt/bokfctl build/; \
+ else \
+ make -j"$(nproc)" backend VERSION="$VERSION" \
+ && make test-core VERSION="$VERSION"; \
+ fi \
&& strip build/bokfd build/bokfctl
-# The runtime image carries the daemon and bokfctl only. The ncurses TUI is a
-# frontend: build it from source on the machine you sit at.
+# The runtime image carries the daemon and bokfctl only, statically linked
+# against OpenSSL and the C library; the ncurses TUI is a frontend built on the
+# machine you sit at.
FROM alpine:3.24 AS runtime
-RUN apk add --no-cache libssl3 ca-certificates util-linux \
+RUN apk add --no-cache ca-certificates util-linux \
&& addgroup -S bokfd \
&& adduser -S -D -H -u 10001 -G bokfd -s /sbin/nologin bokfd \
&& install -d -o bokfd -g bokfd /var/lib/bokfd /var/lib/bokfd/backup \
diff --git a/deploy/Dockerfile.cross b/deploy/Dockerfile.cross
new file mode 100644
index 0000000..ab0504c
--- /dev/null
+++ b/deploy/Dockerfile.cross
@@ -0,0 +1,13 @@
+# Cross-compile the static arm64 backend binaries on an amd64 host. The
+# runtime image is assembled later on the target host from the produced
+# binaries; there is no TUI in the runtime image.
+FROM debian:bookworm
+RUN dpkg --add-architecture arm64 \
+ && apt-get update \
+ && apt-get install -y --no-install-recommends \
+ make ca-certificates gcc-aarch64-linux-gnu \
+ libc6-dev:arm64 libssl-dev:arm64 \
+ && rm -rf /var/lib/apt/lists/*
+COPY cross-build.sh /usr/local/bin/cross-build
+RUN chmod 0755 /usr/local/bin/cross-build
+ENTRYPOINT ["/usr/local/bin/cross-build"]
diff --git a/deploy/cross-build.sh b/deploy/cross-build.sh
new file mode 100644
index 0000000..19d07b7
--- /dev/null
+++ b/deploy/cross-build.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+# Cross-compile the static aarch64 backend binaries (bokfd, bokfctl).
+# Expects the source read-only at /src, writes the binaries to /out and takes
+# the version from $VERSION. Run through deploy/Dockerfile.cross. OpenSSL and
+# glibc are linked statically, so the Alpine runtime image needs no shared
+# libraries (the kernel ABI is all that matters).
+set -eu
+
+make -C /src -j"$(nproc)" BUILD=/tmp/build backend \
+ CC=aarch64-linux-gnu-gcc \
+ CFLAGS="-O2 -g -static -L/usr/lib/aarch64-linux-gnu" \
+ SSL_LIBS="-l:libssl.a -l:libcrypto.a -ldl -lpthread" \
+ VERSION="${VERSION:-0.1.0-dev}"
+
+mkdir -p /out
+cp /tmp/build/bokfd /tmp/build/bokfctl /out/
diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md
index 7385a8f..6fe6a53 100644
--- a/docs/DEPLOY.md
+++ b/docs/DEPLOY.md
@@ -56,6 +56,8 @@ Normal deploys build an image and recreate the container. While developing,
`scripts/deploy.sh --dev` skips the image entirely:
- the gate (`make` + `make test`) still runs locally,
+- on an architecture mismatch the static aarch64 binaries are
+ cross-compiled here (about 20 s, `deploy/Dockerfile.cross`),
- the binaries are copied into the running container with `docker cp`,
- the daemon is reloaded with `SIGHUP`, which re-execs the binary in place
(in-memory sessions are lost, clients reconnect),
@@ -63,9 +65,7 @@ Normal deploys build an image and recreate the container. While developing,
No image is built and the container is not recreated; a later normal deploy
replaces the copied binaries. Use a descriptive tag, e.g.
-`scripts/deploy.sh --dev v0.2.0-rc1`. Hot reload requires the host to share
-the dev machine's architecture; on a mismatch `--dev` falls back to a full
-remote build.
+`scripts/deploy.sh --dev v0.2.0-rc1`.
## Deploying upgrades
@@ -80,7 +80,8 @@ The script:
1. `make` + `make test` on the dev machine,
2. builds `bokf:<tag>`: locally and ships it with `docker save | gzip | ssh
docker load`, or — when the host runs a different CPU architecture —
- builds it natively on the host from a source tar,
+ cross-compiles the backend here and assembles the image on the host from
+ a source tar,
3. copies `compose.yaml` and writes `BOKF_IMAGE`/`BOKF_TAG` into the host's
`.env` (other keys are preserved),
4. `docker compose up -d --no-build`, then polls the container healthcheck,
@@ -88,11 +89,13 @@ The script:
that is still loaded on the host.
Architecture mismatches are automatic: `uname -m` is compared over SSH and a
-mismatch switches to a remote build of the same Alpine image (compiled inside
-the host's Docker, so the host still needs no toolchain). Override with
-`BOKF_BUILD=local` or `BOKF_BUILD=remote` (also settable in `.env`). The
-runtime image is Alpine and carries `bokfd` + `bokfctl` only; the ncurses TUI
-is a frontend and never shipped.
+mismatch runs `deploy/Dockerfile.cross`, which links `bokfd`/`bokfctl`
+statically for aarch64 (glibc + OpenSSL archives; it runs on the Alpine
+runtime directly, DNS included). The image is then assembled in the host's
+Docker — no compilation there. Override with `BOKF_BUILD=local` or
+`BOKF_BUILD=remote` (also settable in `.env`). The runtime image is Alpine,
+carries `bokfd` + `bokfctl` only (no `libssl3`; the binaries are static) and
+never ships the ncurses TUI, which is a frontend built on the client.
Tags are `git describe` output unless passed. Tag releases (`v*`) so rollback
and support have meaningful versions. The rollback image must still exist on
diff --git a/docs/STATE.md b/docs/STATE.md
index 21995c6..b6c5d4f 100644
--- a/docs/STATE.md
+++ b/docs/STATE.md
@@ -80,11 +80,13 @@ check.
get accounts/roles/tokens, never VPN access. The runtime image is
**Alpine + backend only** (`bokfd`, `bokfctl`; the ncurses TUI is a
frontend built on the client machine). `scripts/deploy.sh` builds locally
- and ships over SSH, or builds the same image natively on the host when
- architectures differ (no cross-compilation — a musl cross toolchain is
- not trusted yet). `scripts/deploy.sh --dev` hot-reloads the binaries in
- the running container (SIGHUP re-exec via `docker cp`) when the
- architecture matches, otherwise it falls back to a full remote build.
+ and ships over SSH, or — when the architectures differ —
+ cross-compiles the backend on the dev machine as **static aarch64
+ (glibc + OpenSSL archives, runs directly on Alpine, DNS verified) and
+ assembles the image in the host's Docker** (`deploy/Dockerfile.cross`,
+ ~20 s; the image carries no `libssl3`). `scripts/deploy.sh --dev`
+ hot-reloads the binaries in the running container (SIGHUP re-exec via
+ `docker cp`), cross-compiling first when the architecture differs.
15. **Reports in the TUI**: rendered as fixed-width Swedish tables that mirror
the Kapitas PDF exports (Saldobalans, Resultatrapport with previous-year
column and 89xx bokfört/ej bokfört, Balansrapport with Ing balans/Ing
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
index ad5350c..b625ac5 100755
--- a/scripts/deploy.sh
+++ b/scripts/deploy.sh
@@ -3,10 +3,12 @@
#
# scripts/deploy.sh [--dev] [tag]
#
-# Normal mode builds an image and recreates the container, building natively on
-# the host when the target architecture differs. --dev (same architecture only)
-# skips the image: it copies the fresh binaries into the running container and
-# reloads the daemon with SIGHUP, which re-execs them in place.
+# 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.
@@ -61,6 +63,21 @@ wait_healthy() {
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
@@ -73,7 +90,7 @@ case "$BOKF_BUILD" in
if [ "$(uname -m)" = "$host_arch" ]; then
BOKF_BUILD=local
else
- echo "deploy: host is $host_arch, building the image on the host"
+ echo "deploy: host is $host_arch, cross-compiling here"
BOKF_BUILD=remote
fi
;;
@@ -86,20 +103,30 @@ case "$BOKF_BUILD" in
;;
esac
-if [ "$DEV" = 1 ] && [ "$BOKF_BUILD" = local ]; then
+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
- echo "deploy: dev: copying the locally built binaries into the running container"
+ 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 build/bokfd build/bokfctl "$BOKF_HOST:/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" |
@@ -116,23 +143,20 @@ if [ "$DEV" = 1 ] && [ "$BOKF_BUILD" = local ]; then
exit 1
fi
-if [ "$DEV" = 1 ]; then
- echo "deploy: dev: host is $host_arch, hot reload needs a matching" \
- "architecture; doing a full deploy" >&2
-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
- echo "deploy: assembling image bokf:$TAG natively on $BOKF_HOST"
+ 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'"