From 380195f7cd5e57acf2c1cf2bc41069e6b0b979ed Mon Sep 17 00:00:00 2001 From: Anders Betts Date: Thu, 17 Sep 2026 19:55:36 +0200 Subject: Initial commit: daemon, clients, docs, Docker deploy pipeline --- docs/DEPLOY.md | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 docs/DEPLOY.md (limited to 'docs/DEPLOY.md') diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md new file mode 100644 index 0000000..5def5df --- /dev/null +++ b/docs/DEPLOY.md @@ -0,0 +1,156 @@ +# bokf — running in Docker + +Status: Draft 0.1 · 2026-09-17 · License: GPL-3.0-or-later + +Two roles: + +- **host** — runs Docker + the compose plugin only. No toolchain, no registry. +- **dev machine** — has the source and builds the image; deploys with + `scripts/deploy.sh` over SSH. + +There is no forge or registry in this flow. Releases are git tags, images are +transferred directly with `docker save | ssh docker load`. + +State lives in two bind mounts next to `compose.yaml`: + +| Host path | Container | Contents | +|---|---|---| +| `var/db` | `/var/lib/bokfd` | SQLite database, `backup/`, `export/` | +| `var/run` | `/run/bokfd` | Unix socket (mode 0660, owned by uid 10001) | + +The database is a single SQLite file. Back up with `backup.snapshot` +(`VACUUM INTO`) and point restic at `var/db/backup` — never at the live file. + +## First run + +On the dev machine, put the SSH target in `.env`: + +```sh +cp .env.example .env +# set BOKF_HOST=user@host and BOKF_REMOTE_DIR=/srv/bokf +scripts/deploy.sh v0.1.0 +``` + +The first run stops after shipping the image and compose.yaml, and prints the +one-time init command. Run it on the host: + +```sh +cd /srv/bokf +docker compose run --rm -e BOKFD_PASSWORD='' bokfd init +docker compose up -d +docker compose ps # wait for "healthy" +``` + +`init` creates the admin user and must run before the first `up`; it refuses +to touch an already initialized database. Subsequent `scripts/deploy.sh` +runs update the image, tag and compose file, restart the daemon and wait for +the healthcheck. + +## Deploying upgrades + +```sh +git tag v0.1.1 +scripts/deploy.sh # tag defaults to git describe +scripts/deploy.sh v0.1.1 # or pass one explicitly +``` + +The script: + +1. `make` + `make test` on the dev machine, +2. builds `bokf:`: 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, +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, +5. on failure, puts the previous `BOKF_TAG` back and rolls back to the image + that is still loaded on the host. + +Cross-architecture builds are automatic: `uname -m` is compared over SSH and +a mismatch switches to a remote build. Override with `BOKF_BUILD=local` or +`BOKF_BUILD=remote` (also settable in `.env`). A remote build pulls the +Debian base image inside a container, so the host needs outbound network +access but still no toolchain. + +Tags are `git describe` output unless passed. Tag releases (`v*`) so rollback +and support have meaningful versions. The rollback image must still exist on +the host; don't prune before the new version has proven itself. + +Manual rollback — set `BOKF_TAG` on the host and restart: + +```sh +cd /srv/bokf +sed -i 's/^BOKF_TAG=.*/BOKF_TAG=v0.1.0/' .env +docker compose up -d --no-build +``` + +## Optional: a bare repository on the host + +For an off-machine copy of the source and an optional auto-deploy hook: + +```sh +ssh host 'git init --bare /srv/git/bokf.git' +git remote add host ssh://host/srv/git/bokf.git +git push host main +``` + +If the host also has the toolchain and a checkout whose origin is that bare +repo, `deploy/post-receive.sample` can build, test and restart on every push +to `main`. The normal `scripts/deploy.sh` flow does not need any of this. + +## Clients + +Run clients inside the container — no host toolchain needed: + +```sh +docker compose exec bokfd bokftui # interactive TUI +docker compose exec -e BOKFD_PASSWORD='' bokfd \ + bokfctl --user admin fiscal_year.list +``` + +The clients honor `BOKFD_SOCKET`; a host-installed client can also point at +`var/run/bokfd.sock`, but that file is owned by uid 10001, so the host user +must be in that group (or use `sudo`). + +## Mock company + +```sh +docker compose exec -e BOKFD_PASSWORD='' bokfd \ + bokfctl --user admin org.create \ + '{"name":"Mock AB","org_nr":"556000-0000","fiscal_year_start_month":1}' +``` + +Develop against this org; agents get their own API token +(`bokfctl token.create ...`, shown once). + +## Backup and restore + +```sh +docker compose exec -e BOKFD_PASSWORD='' bokfd \ + bokfctl --user admin backup.snapshot +ls var/db/backup # -.db + .sha256 +``` + +Restore: + +```sh +docker compose stop +cp var/db/backup/.db var/db/bokfd.db +rm -f var/db/bokfd.db-wal var/db/bokfd.db-shm +docker compose start +``` + +To replace the mock with a real database from another host, restore its +snapshot the same way (same or newer bokf version; older schemas migrate +forward). SIE import is the alternative once the importer's CRLF/`#RAR` +fixes land. + +## Local development + +```sh +make -j"$(nproc)" && make test # no Docker required +docker compose up --build # same image, local var/ data dir +``` + +`scripts/deploy.sh` always runs the tests before shipping. `var/`, `.env`, +`*.db` and `*.se` are gitignored — real books never enter the repository. -- cgit v1.3