summaryrefslogtreecommitdiff
path: root/deploy
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-23 11:36:11 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-23 11:36:11 +0200
commit1abb7649b930d35d1f5a76fd72856659b1ee8275 (patch)
tree50d1d3dd905056b75749e22a58e7247e4a4bb0e2 /deploy
parent71a702f375750829c634b552217c9925d549828b (diff)
downloadbokf-0.1.69.tar.gz
bokf-0.1.69.zip
web: bokftui in the browser (ttyd + bokfweb login gate); per-user login limitv0.1.69
New image bokf-web (Dockerfile target "web", compose service "web" on 127.0.0.1:8790): Caddy routing with forward_auth, the bokfweb login gate (C, authenticates with bokfd's session.open, per-address limit, cookie + terminal handle, one login handed to the TUI via /redeem) and ttyd running bokftui in web mode in an isolated throwaway HOME. TLS stays with the host's reverse proxy. BOKF_WEB=1 blocks every local file and viewer path in the TUI. bokfd's login limiter is now per user name instead of one global counter (5 wrong guesses from anyone locked out everybody), and a full counter table no longer disables it. The cross build and deploy.sh build and ship both images. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'deploy')
-rw-r--r--deploy/Caddyfile47
-rw-r--r--deploy/Dockerfile.cross8
-rwxr-xr-xdeploy/bokftui-web40
-rw-r--r--deploy/cross-build.sh12
-rwxr-xr-xdeploy/web-entrypoint.sh26
5 files changed, 125 insertions, 8 deletions
diff --git a/deploy/Caddyfile b/deploy/Caddyfile
new file mode 100644
index 0000000..c86f4e5
--- /dev/null
+++ b/deploy/Caddyfile
@@ -0,0 +1,47 @@
+# Web frontend routing inside the bokf-web container: the login gate
+# (bokfweb, 127.0.0.1:7682) and the browser terminal (ttyd, 127.0.0.1:7681).
+# Nothing reaches ttyd without the gate's OK.
+#
+# Plain HTTP on :8790, published on the host's loopback only. TLS for
+# https://bokf.makandra.eu is the host's reverse proxy (the NAS Caddy):
+#
+# bokf.makandra.eu {
+# tls { dns inwx ... } # as for the other sites
+# reverse_proxy 127.0.0.1:8790
+# }
+{
+ auto_https off
+ admin off
+ servers {
+ # the host proxy's X-Forwarded-For names the real client (the
+ # gate limits failed logins per client address)
+ trusted_proxies static private_ranges
+ }
+}
+
+:8790 {
+ header {
+ Strict-Transport-Security "max-age=31536000"
+ -Server
+ }
+
+ redir / /web/ 302
+
+ # the terminal: only with a live login whose handle is in the URL
+ @tty path /web/tty /web/tty/*
+ handle @tty {
+ forward_auth 127.0.0.1:7682 {
+ uri /auth
+ }
+ reverse_proxy 127.0.0.1:7681
+ }
+
+ # login page, login and logout
+ handle /web* {
+ reverse_proxy 127.0.0.1:7682
+ }
+
+ handle {
+ respond "not found" 404
+ }
+}
diff --git a/deploy/Dockerfile.cross b/deploy/Dockerfile.cross
index ab0504c..a14ac0f 100644
--- a/deploy/Dockerfile.cross
+++ b/deploy/Dockerfile.cross
@@ -1,12 +1,12 @@
-# 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.
+# Cross-compile the static arm64 binaries on an amd64 host: the backend
+# (bokfd, bokfctl) and the web frontend's bokftui and bokfweb. The images
+# are assembled later on the target host from the produced binaries.
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 \
+ libc6-dev:arm64 libssl-dev:arm64 libncurses-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
diff --git a/deploy/bokftui-web b/deploy/bokftui-web
new file mode 100755
index 0000000..b6ec9b5
--- /dev/null
+++ b/deploy/bokftui-web
@@ -0,0 +1,40 @@
+#!/bin/sh
+# Started by ttyd for every browser terminal, with the terminal handle from
+# the URL (?arg=) as $1. Trades the handle for the bokfd session at the
+# gate (bokfweb /redeem, internal only), then runs bokftui already logged
+# in, in a private throwaway HOME, in web mode (no local files), with
+# resource limits. No shell is ever offered: when bokftui exits, the
+# terminal ends.
+set -u
+gate="${BOKFWEB_INTERNAL:-http://127.0.0.1:7682}"
+
+msg() {
+ printf '\r\n %s\r\n\r\n' "$1"
+ sleep 4
+ exit 1
+}
+
+handle="${1:-}"
+case "$handle" in
+ "" | *[!A-Za-z0-9_-]*) msg "Ogiltig länk. Öppna /web/ och logga in igen." ;;
+esac
+
+session=$(wget -q -O - --post-data "handle=$handle" "$gate/redeem" 2>/dev/null) ||
+ msg "Sessionen har gått ut. Öppna /web/ och logga in igen."
+case "$session" in
+ "" | *[!A-Za-z0-9_-]*) msg "Sessionen har gått ut. Öppna /web/ och logga in igen." ;;
+esac
+
+home=$(mktemp -d /tmp/bokf-web.XXXXXXXX) || msg "Kunde inte starta sessionen."
+trap 'rm -rf "$home"' EXIT HUP INT TERM
+chmod 0700 "$home"
+
+# per-session limits: memory, CPU time, open files (the process count is
+# capped per container in compose.yaml: every session runs as one uid)
+ulimit -v 524288 2>/dev/null || true
+ulimit -t 7200 2>/dev/null || true
+ulimit -n 256 2>/dev/null || true
+
+HOME="$home" XDG_CONFIG_HOME="$home/.config" XDG_CACHE_HOME="$home/.cache" \
+BOKF_WEB=1 BOKFD_SESSION="$session" \
+ bokftui --socket "${BOKFD_SOCKET:-/run/bokfd/bokfd.sock}"
diff --git a/deploy/cross-build.sh b/deploy/cross-build.sh
index 19d07b7..54a2929 100644
--- a/deploy/cross-build.sh
+++ b/deploy/cross-build.sh
@@ -1,16 +1,20 @@
#!/bin/sh
-# Cross-compile the static aarch64 backend binaries (bokfd, bokfctl).
+# Cross-compile the static aarch64 binaries: bokfd, bokfctl and, for the
+# web image, bokftui and bokfweb.
# 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 \
+# ncursesw needs its tinfo half spelled out for a static link
+make -C /src -j"$(nproc)" BUILD=/tmp/build \
+ backend /tmp/build/bokftui /tmp/build/bokfweb \
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" \
+ SSL_LIBS="-l:libssl.a -l:libcrypto.a -l:libtinfo.a -ldl -lpthread" \
VERSION="${VERSION:-0.1.0-dev}"
mkdir -p /out
-cp /tmp/build/bokfd /tmp/build/bokfctl /out/
+cp /tmp/build/bokfd /tmp/build/bokfctl /tmp/build/bokftui \
+ /tmp/build/bokfweb /out/
diff --git a/deploy/web-entrypoint.sh b/deploy/web-entrypoint.sh
new file mode 100755
index 0000000..38e07f9
--- /dev/null
+++ b/deploy/web-entrypoint.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+# Web frontend container: bokfweb (login gate), ttyd (browser terminal)
+# and Caddy (routing on :8790, behind the host's TLS proxy). The gate and
+# ttyd listen on 127.0.0.1 only. Each helper is restarted if it dies; Caddy
+# is the main process, so the container stops (and compose restarts it)
+# when Caddy does.
+set -eu
+
+export BOKFD_SOCKET="${BOKFD_SOCKET:-/run/bokfd/bokfd.sock}"
+export XDG_DATA_HOME=/tmp/caddy-data XDG_CONFIG_HOME=/tmp/caddy-config
+
+keep() {
+ while :; do
+ "$@" || true
+ echo "web: $1 exited, restarting" >&2
+ sleep 1
+ done
+}
+
+keep bokfweb &
+keep ttyd -i 127.0.0.1 -p 7681 -b /web/tty -a -W -O \
+ -m "${BOKF_WEB_MAX_SESSIONS:-20}" \
+ -t titleFixed=bokf -t fontSize=15 -t disableLeaveAlert=true \
+ bokftui-web &
+
+exec caddy run --config /etc/caddy/Caddyfile --adapter caddyfile