From 8dc2e7d2f11aee4e70c3675e4ba6dd255a47e0e4 Mon Sep 17 00:00:00 2001 From: Anders Betts Date: Wed, 23 Sep 2026 11:51:44 +0200 Subject: web: ship glibc C.utf8 so the web TUI shows åäö MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cross-built bokftui is static glibc; in the Alpine web image it found no locale data, setlocale() failed and åäö were invalid bytes. The cross build now copies Debian's C.utf8 into the image and the image build runs bokfweb --check-locale, so a TUI without UTF-8 fails the build. Co-Authored-By: Claude Opus 5.5 --- Dockerfile | 10 ++++++++-- clients/bokfweb.c | 26 +++++++++++++++++++++++++- deploy/cross-build.sh | 6 ++++++ docs/DEPLOY.md | 4 ++++ docs/STATE.md | 6 ++++++ 5 files changed, 49 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index a46e15e..5102a42 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,9 @@ RUN if [ -x .prebuilt/bokfd ]; then \ VERSION="$VERSION" \ && make test-core VERSION="$VERSION"; \ fi \ - && strip build/bokfd build/bokfctl build/bokftui build/bokfweb + && strip build/bokfd build/bokfctl build/bokftui build/bokfweb \ + && mkdir -p build/locale \ + && if [ -d .prebuilt/locale ]; then cp -a .prebuilt/locale/. build/locale/; fi # The web frontend (image bokf-web, `--target web`): Caddy for routing, the # bokfweb login gate, ttyd and bokftui in web mode. It reaches bokfd only @@ -36,9 +38,13 @@ RUN apk add --no-cache ca-certificates caddy ttyd ncurses-terminfo-base \ && addgroup -S bokfd \ && adduser -S -D -H -u 10001 -G bokfd -s /sbin/nologin bokfd COPY --from=build /src/build/bokftui /src/build/bokfweb /usr/local/bin/ +# glibc locale data for the cross-built static binaries (empty for a native +# musl build, which needs none); the check fails the build without UTF-8 +COPY --from=build /src/build/locale/ /usr/lib/locale/ COPY deploy/bokftui-web deploy/web-entrypoint.sh /usr/local/bin/ COPY deploy/Caddyfile /etc/caddy/Caddyfile -RUN chmod 0755 /usr/local/bin/bokftui-web /usr/local/bin/web-entrypoint.sh +RUN chmod 0755 /usr/local/bin/bokftui-web /usr/local/bin/web-entrypoint.sh \ + && LANG=C.UTF-8 bokfweb --check-locale ENV BOKFD_SOCKET=/run/bokfd/bokfd.sock \ TERM=xterm-256color \ LANG=C.UTF-8 diff --git a/clients/bokfweb.c b/clients/bokfweb.c index d04e2d6..cc19826 100644 --- a/clients/bokfweb.c +++ b/clients/bokfweb.c @@ -15,6 +15,7 @@ directly: only Caddy and the wrapper in the same container talk to it. */ #include #include +#include #include #include #include @@ -23,6 +24,7 @@ #include #include #include +#include #include "client.h" #include "log.h" @@ -446,8 +448,30 @@ static void serve(int cfd, struct gate *g) memset(buf, 0, n); /* the body may hold a password */ } -int main(void) +/* `bokfweb --check-locale`, run while the web image is built: bokftui in + the same image needs a UTF-8 locale for åäö, and a statically linked + glibc finds none unless its locale data is installed. Exits 1 without. */ +static int check_locale(void) { + const char *l = setlocale(LC_ALL, ""); + wchar_t w = 0; + mbstate_t st; + memset(&st, 0, sizeof st); + size_t n = mbrtowc(&w, "\xc3\xa5", 2, &st); /* å */ + if (!l || n != 2 || w != 0xE5) { + fprintf(stderr, "bokfweb: no UTF-8 locale (setlocale: %s); bokftui " + "would show no åäö\n", + l ? l : "failed"); + return 1; + } + printf("locale ok: %s\n", l); + return 0; +} + +int main(int argc, char **argv) +{ + if (argc > 1 && strcmp(argv[1], "--check-locale") == 0) + return check_locale(); signal(SIGPIPE, SIG_IGN); const char *lvl = getenv("BOKFWEB_LOG_LEVEL"); log_set_level(lvl ? log_level_from_name(lvl) : LOG_INFO); diff --git a/deploy/cross-build.sh b/deploy/cross-build.sh index 54a2929..75c181c 100644 --- a/deploy/cross-build.sh +++ b/deploy/cross-build.sh @@ -18,3 +18,9 @@ make -C /src -j"$(nproc)" BUILD=/tmp/build \ mkdir -p /out cp /tmp/build/bokfd /tmp/build/bokfctl /tmp/build/bokftui \ /tmp/build/bokfweb /out/ + +# A static glibc program loads its locale data from /usr/lib/locale, which +# the Alpine runtime does not have: without C.utf8 setlocale() fails and +# the TUI shows no åäö. Ship Debian's C.utf8 with the binaries. +mkdir -p /out/locale +cp -a /usr/lib/locale/C.utf8 /out/locale/ diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index 99b7661..008093f 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -276,6 +276,10 @@ browser ──443──▶ host Caddy (TLS) ──▶ 127.0.0.1:8790 bokf-web c Port 443 is already forwarded for the other sites. The container publishes 8790 on the host's loopback only, so it is unreachable until that block exists. +- **UTF-8**: the cross-built binaries are static glibc, which needs its own + locale data; the image ships Debian's `C.utf8` in `/usr/lib/locale` and + the build runs `bokfweb --check-locale`, so an image whose TUI could not + show åäö fails to build instead of shipping. - `BOKF_WEB_MAX_SESSIONS` (default 20) caps concurrent terminals. `docker compose logs web` shows logins, logouts and failed attempts (never passwords). diff --git a/docs/STATE.md b/docs/STATE.md index c33d80e..10286fb 100644 --- a/docs/STATE.md +++ b/docs/STATE.md @@ -14,6 +14,12 @@ unit tests and the docs consistency check. ## Resume here (2026-09-23) +- **Web åäö fix (2026-09-23, `v0.1.70`)**: the web TUI showed no åäö on + the NAS: the cross-built static glibc bokftui found no locale data in + the Alpine image (setlocale failed, åäö were invalid bytes). The image + now ships Debian's `C.utf8` and the build checks it + (`bokfweb --check-locale`). Local amd64 tests missed it because the + native build uses musl. - **Web frontend (2026-09-23, live since `v0.1.69`: https://bokf.makandra.eu/web)**: image `bokf-web` (Caddy routing + `bokfweb` login gate + ttyd + bokftui in web mode) as compose service `web` on `127.0.0.1:8790`; see -- cgit v1.3