Commit 6241f670 authored by jan.koester's avatar jan.koester
Browse files

Docker: build on FenrirOS like bolli

Build stage on fenriros-devel (libpq via the FenrirOS CMake port in
docker/postgresql/, SQLite amalgamation, libdbpp, MediaDB client,
libgameinfoplus, CPP-SMTPClient, authcli), runtime on the minimal
fenriros image with the needed shared libraries and dlopen()'d
backends copied in; targets blogi (default) and blogi-editor.
run.sh: no pwgen on FenrirOS, exec the daemons. Ignore packaging/output.

Co-Authored-By: default avatarClaude Opus 5.5 <noreply@anthropic.com>
parent 8d9180c8
Loading
Loading
Loading
Loading

.dockerignore

0 → 100644
+17 −0
Changes for .dockerignore: 17 added lines, 0 removed lines.
Original line number Diff line number Diff line
build/
build*/
out/
.vs/
.cache/
.git/
obj-*-linux-gnu/
packaging/output/
*.o
*.so
*.a
test_get_bool
test_json
test_json_false
test_require
Dockerfile
.dockerignore
+1 −0
Changes for .gitignore: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -45,3 +45,4 @@ src/blogi.code-workspace
t2388 psql -X -P pager=off host=pg-database.tuxist.de user=blog dbname=blog hostaddr=10.1.2.240 -c SELECT count(*) FROM users
t2388 psql host=pg-database.tuxist.de user=blog dbname=blog hostaddr=10.1.2.240 -c /pset pager off -c SELECT id::text, username, displayname FROM users; -c SELECT c.id::text, c.title, c.author::text FROM content c ORDER BY c.created DESC LIMIT 5;
t2388 psql host=pg-database.tuxist.de user=blog dbname=blog hostaddr=10.1.2.240 -c /pset pager off -c SELECT id::text, username, displayname FROM users; -c SELECT c.id::text, c.title, c.author::text FROM content c ORDER BY c.created DESC LIMIT 5;
packaging/output/
+191 −180
Changes for Dockerfile: 191 added lines, 180 removed lines.
Original line number Diff line number Diff line
# ==========================================
# STAGE 1: Build Environment
# ==========================================
FROM node:alpine AS build  
WORKDIR /build/alpine

# Install build dependencies
RUN apk update --no-cache && apk upgrade --no-cache && \
    apk add --no-cache \
    alpine-sdk \
    brotli-dev \
    cmake \
    clang \
    fakeroot \
    ffmpeg-dev \
    json-c-dev \
    libc-dev \
    libpq-dev \
    lua-aports \
    mariadb-dev \
    ninja \
    openssh-client \
    openssh-keygen \
    sqlite-dev \
    sudo \
    tinyxml2-dev \
    util-linux-dev \
    yaml-dev \
    libarchive-dev

# Clone and build C++ dependencies sequentially
# Using cmake -B/build -S/source to avoid sloppy 'cd' tracking
RUN git clone https://git.tuxist.de/git/jan.koester/libcmdplus.git && \
    cmake -B libcmdplus/build -S libcmdplus -DCMAKE_CXX_FLAGS="-fPIC" -G Ninja && \
    ninja -C libcmdplus/build install

RUN git clone https://git.tuxist.de/git/tuxist/uuidplus.git && \
    cmake -B uuidplus/build -S uuidplus -G Ninja && \
    ninja -C uuidplus/build install

RUN git clone https://git.tuxist.de/git/jan.koester/libnetplus.git && \
    cmake -B libnetplus/build -S libnetplus -G Ninja && \
    ninja -C libnetplus/build install

RUN git clone https://git.tuxist.de/git/jan.koester/libhtmlpp.git && \
    cmake -B libhtmlpp/build -S libhtmlpp -G Ninja && \
    ninja -C libhtmlpp/build install

RUN git clone https://git.tuxist.de/git/jan.koester/libhttppp.git && \
    cmake -B libhttppp/build -S libhttppp -G Ninja && \
    ninja -C libhttppp/build install

RUN git clone https://git.tuxist.de/git/jan.koester/libconfplus.git && \
    cmake -B libconfplus/build -S libconfplus -G Ninja && \
    ninja -C libconfplus/build install

RUN git clone https://git.tuxist.de/git/jan.koester/libdbpp.git && \
    cmake -B libdbpp/build -S libdbpp -G Ninja && \
    ninja -C libdbpp/build install

RUN git clone https://git.tuxist.de/git/jan.koester/libgameinfoplus.git && \
    cmake -B libgameinfoplus/build -S libgameinfoplus -G Ninja && \
    ninja -C libgameinfoplus/build install

RUN git clone https://git.tuxist.de/git/tuxist/libparitypp && \
    cmake -B libparitypp/build -S libparitypp -G Ninja && \
    ninja -C libparitypp/build install

RUN git clone https://git.tuxist.de/git/tuxist/authdb.git && \
    cmake -B authdb/build -S authdb -G Ninja -DBUILD_BINDINGS=OFF && \
    ninja -C authdb/build install

RUN git clone https://git.tuxist.de/git/tuxist/mediadb.git && \
    cmake -B mediadb/build -S mediadb -G Ninja && \
    ninja -C mediadb/build install

RUN git clone https://github.com/Tuxist/CPP-SMTPClient-library.git && \
    cmake -B CPP-SMTPClient-library/build -S CPP-SMTPClient-library -G Ninja \
          -DCMAKE_INSTALL_LIBDIR=/usr/local/lib \
          -DCMAKE_INSTALL_INCLUDEDIR=/usr/local/include && \
    ninja -C CPP-SMTPClient-library/build install

COPY . /build/alpine/blogi

RUN cmake -B build -S blogi -G Ninja -DBUILD_EDITOR=ON && \
    ninja -C build install
# blogi container images
#
#   build:    tuxist/fenriros-devel  (every FenrirOS package: clang, cmake, ninja and the
#                                     libnetplus/libhttppp/libconfplus/authdb/... -dev packages)
#   runtime:  tuxist/fenriros        (minimal: systempp, fresh, rpm, dnf5)
#
#   docker build -t blogi .
#   docker build -t blogi-editor --target blogi-editor .
#   docker run -d -v blogi-etc:/etc/blogi -p 443:443 -e CLIENTURL=... -e PORT=443 ... blogi
#
# fenriros-devel lacks libpq, SQLite, libdbpp, the SMTP client, MediaDB's client library,
# libgameinfoplus and authcli, so the build stage builds them first: libpq from the
# PostgreSQL release tarball with the FenrirOS CMake port in docker/postgresql/ (no autoconf
# on FenrirOS), SQLite from its amalgamation, the rest from their git repos.
# libdbpp is built without its MariaDB backend (no MariaDB connector on FenrirOS yet), so
# the DATABASE/DRIVER is "pgsql" or "sqlite".
#
# Use --build-arg IMAGE_PREFIX=registry.tuxist.de/tuxist/ (and FENRIROS_TAG) to pull the base
# images from another registry/namespace -- see DOCKER_PREFIX in systempp/build-chroot.sh.

ARG IMAGE_PREFIX=tuxist/
ARG FENRIROS_TAG=20261017

FROM ${IMAGE_PREFIX}fenriros-devel:${FENRIROS_TAG} AS build

ARG PG_VERSION=17.10
ARG SQLITE_VERSION=3470200
ARG SQLITE_YEAR=2024
ARG TUXIST_GIT=https://git.tuxist.de/git
ARG LIBDBPP_REF=main
ARG MEDIADB_REF=main
ARG AUTHDB_REF=main
ARG GAMEINFOPLUS_REF=main
ARG SMTPCLIENT_REF=master

# Sources are fetched by ADD on the build host: FenrirOS resolves names through
# /etc/resolv.yaml, which docker build does not provide, so curl inside the image can't.
WORKDIR /src
ADD https://ftp.postgresql.org/pub/source/v${PG_VERSION}/postgresql-${PG_VERSION}.tar.gz /src/
ADD https://www.sqlite.org/${SQLITE_YEAR}/sqlite-amalgamation-${SQLITE_VERSION}.zip /src/
ADD ${TUXIST_GIT}/jan.koester/libdbpp.git#${LIBDBPP_REF} /src/libdbpp
ADD ${TUXIST_GIT}/tuxist/mediadb.git#${MEDIADB_REF} /src/mediadb
ADD ${TUXIST_GIT}/tuxist/authdb.git#${AUTHDB_REF} /src/authdb
ADD ${TUXIST_GIT}/jan.koester/libgameinfoplus.git#${GAMEINFOPLUS_REF} /src/libgameinfoplus
ADD https://github.com/Tuxist/CPP-SMTPClient-library.git#${SMTPCLIENT_REF} /src/smtpclient
COPY docker/postgresql/ /src/postgresql-port/

# libpq (shared library + client headers only, the server is not needed)
RUN set -e; \
    tar xzf "postgresql-${PG_VERSION}.tar.gz"; \
    cd "postgresql-${PG_VERSION}"; \
    cp /src/postgresql-port/* .; \
    # fresh's flex can't read PostgreSQL's scanners yet; libpq compiles none of them, so
    # empty placeholders make the port's configure-time generation skip them.
    for f in backend/parser/scan.c backend/replication/repl_scanner.c \
             backend/replication/syncrep_scanner.c backend/bootstrap/bootscanner.c \
             backend/utils/misc/guc-file.c backend/utils/adt/jsonpath_scan.c \
             bin/pgbench/exprscan.c bin/psql/psqlscanslash.c fe_utils/psqlscan.c; do \
        : > "src/$f"; \
    done; \
    cmake -S . -B build -G Ninja \
        -DCMAKE_BUILD_TYPE=Release \
        -DCMAKE_INSTALL_PREFIX=/usr \
        -DPG_USE_OPENSSL=ON; \
    cmake --build build --target pq; \
    cp build/libpq.so /usr/lib/; \
    cp src/interfaces/libpq/libpq-fe.h src/include/libpq/libpq-fs.h src/include/pg_config_ext.h \
       src/include/postgres_ext.h src/include/pg_config.h src/include/pg_config_manual.h \
       src/include/pg_config_os.h /usr/include/; \
    mkdir -p /usr/include/libpq; \
    cp src/include/libpq/*.h /usr/include/libpq/

# SQLite (shared library from the amalgamation -- its configure needs autoconf's shell)
RUN set -e; \
    bsdtar xf "sqlite-amalgamation-${SQLITE_VERSION}.zip"; \
    cd "sqlite-amalgamation-${SQLITE_VERSION}"; \
    # compile and link separately: fenriros-devel has no /tmp for clang's intermediate file
    clang -O2 -fPIC -DSQLITE_THREADSAFE=1 -DSQLITE_ENABLE_COLUMN_METADATA -c -o sqlite3.o sqlite3.c; \
    clang -shared -Wl,-soname,libsqlite3.so.0 -o /usr/lib/libsqlite3.so.0 sqlite3.o -lpthread -lm; \
    ln -sf libsqlite3.so.0 /usr/lib/libsqlite3.so; \
    cp sqlite3.h sqlite3ext.h /usr/include/

# libdbpp (pgsql + sqlite backends), MediaDB client, libgameinfoplus, SMTP client
RUN set -e; \
    build() { src=$1; shift; \
        cmake -S "$src" -B "$src/build" -G Ninja \
            -DCMAKE_BUILD_TYPE=Release \
            -DCMAKE_INSTALL_PREFIX=/usr \
            -DCMAKE_INSTALL_LIBDIR=lib \
            -DCMAKE_POSITION_INDEPENDENT_CODE=ON "$@"; \
        cmake --build "$src/build"; \
        cmake --install "$src/build"; }; \
    build /src/libdbpp; \
    build /src/mediadb -DCLIENT_ONLY=ON; \
    build /src/libgameinfoplus; \
    # CPP-SMTPClient does not include GNUInstallDirs, so its include dir must be given
    build /src/smtpclient -DBUILD_TESTING=OFF -DCMAKE_INSTALL_INCLUDEDIR=include -DCMAKE_INSTALL_BINDIR=bin

# authcli (run.sh registers the blogi client with it); the authdb client library and
# headers come from libauthdb-client-dev, so only the tool is built and installed.
RUN set -e; \
    cmake -S /src/authdb -B /src/authdb/build -G Ninja \
        -DCMAKE_BUILD_TYPE=Release \
        -DBUILD_BINDINGS=OFF \
        -DBUILD_TESTING=OFF; \
    cmake --build /src/authdb/build --target authcli; \
    install -Dm755 /src/authdb/build/cli/authcli /stage/usr/local/bin/authcli

WORKDIR /src/blogi
COPY . .

# blogi itself goes to /usr/local: docker-config.yaml and the editor's configs point there.
RUN cmake -S . -B build -G Ninja \
        -DCMAKE_BUILD_TYPE=Release \
        -DCMAKE_INSTALL_PREFIX=/usr/local \
        -DCMAKE_INSTALL_LIBDIR=lib \
        -DBUILD_EDITOR=ON \
 && cmake --build build \
 && DESTDIR=/stage cmake --install build \
 && rm -rf /stage/usr/local/include /stage/usr/local/lib/blogi/libblogidev.a \
 && install -Dm644 docker-config.yaml /stage/usr/local/share/blogi/docker-config.yaml \
 && install -Dm755 run.sh /stage/usr/local/sbin/blogi-run.sh \
 && install -Dm644 editor/docker-config.yaml /stage/usr/local/share/blogi-editor/config.yaml \
 && install -Dm644 editor/config.test.two-widget-paths.yaml \
        /stage/usr/local/share/blogi-editor/config.test.two-widget-paths.yaml \
 && install -Dm755 editor/run.sh /stage/usr/local/sbin/blogi-editor-run.sh \
 && install -Dm755 docker/entrypoint.sh /stage/usr/libexec/blogi/entrypoint.sh

# fenriros only carries the base system, so copy the shared libraries blogi needs
# (DT_NEEDED, resolved recursively against the build image) into the stage, plus what is
# dlopen()'d and so never shows up as DT_NEEDED: the confplus and dbpp backends (blogi's
# own plugins and editor widgets are already in the stage). Every stage file is scanned:
# fresh's find has no -perm -u+x, and readelf skips whatever is not ELF.
RUN set -e; \
    find_lib() { for d in /usr/lib /lib /usr/local/lib; do [ -e "$d/$1" ] && { echo "$d/$1"; return 0; }; done; return 1; }; \
    mkdir -p /stage/usr/lib; \
    for d in confplus dbpp; do [ -d /usr/lib/$d ] && cp -a /usr/lib/$d /stage/usr/lib/; done; \
    todo=$(find /stage -type f); \
    seen=" "; \
    while [ -n "$todo" ]; do \
        next=""; \
        for f in $todo; do \
            for n in $(readelf -d "$f" 2>/dev/null | sed -n 's/.*(NEEDED).*\[\(.*\)\].*/\1/p'); do \
                case "$seen" in *" $n "*) continue ;; esac; \
                seen="$seen$n "; \
                [ -e "/stage/usr/lib/$n" ] && continue; \
                p=$(find_lib "$n") || { echo "warning: $n (needed by $f) not found" >&2; continue; }; \
                mkdir -p "/stage$(dirname "$p")"; \
                cp -L "$p" "/stage$p"; \
                next="$next /stage$p"; \
            done; \
        done; \
        todo=$next; \
    done

# Common runtime base for both images
FROM ${IMAGE_PREFIX}fenriros:${FENRIROS_TAG} AS runtime

# Libraries the base image already ships (libc, libc++, ...) must win over the build image's
# copies of the same files, so only fill in what is missing.
COPY --from=build /stage/ /stage/
RUN set -e; \
    cd /stage; \
    find . ! -type d | while read -r f; do \
        t="/${f#./}"; \
        case "$t" in /usr/lib/*.so*|/lib/*.so*) [ -e "$t" ] && continue ;; esac; \
        mkdir -p "$(dirname "$t")"; \
        cp -a "$f" "$t"; \
    done; \
    cd /; rm -rf /stage; \
    useradd -d /var/lib/blogi blogi; \
    mkdir -p /etc/blogi /var/lib/blogi /var/log/blogi

ENTRYPOINT ["/usr/libexec/blogi/entrypoint.sh"]

# ==========================================
# STAGE 2: Core Blogi Runtime Target
# Blogi Editor
# ==========================================
FROM node:alpine AS blogi
WORKDIR /blogi

RUN addgroup -S blogi && adduser -h /var/lib/blogi -S -G blogi blogi
RUN apk update && apk upgrade && \
    apk add --no-cache \
    brotli \
    ca-certificates \
    ffmpeg \
    json-c \
    libpq \
    libstdc++ \
    libuuid \
    mariadb-connector-c \
    openssl \
    pwgen \
    sed \
    sqlite-libs \
    tinyxml2 \
    yaml \
    libarchive

RUN mkdir -p /etc/blogi /usr/local/share/blogi /usr/local/sbin /usr/local/bin /usr/local/lib

COPY docker-config.yaml /usr/local/share/blogi
COPY run.sh /usr/local/sbin/run.sh
RUN chmod +x /usr/local/sbin/run.sh

# Copy binaries and libraries from build stage
COPY --from=build /usr/local/bin/blogi /usr/local/bin/blogi
COPY --from=build /usr/local/bin/authcli /usr/local/bin/authcli
COPY --from=build /usr/local/share/blogi /usr/local/share/blogi
COPY --from=build /usr/local/lib /usr/local/lib

RUN chmod +x /usr/local/bin/blogi /usr/local/bin/authcli

# Ensure internal plugin paths exist and match build outputs
COPY --from=build /usr/local/lib/dbpp/pgsql.so /usr/local/lib/blogi/dbpp/pgsql.so
COPY --from=build /usr/local/lib/dbpp/sqlite.so /usr/local/lib/blogi/dbpp/sqlite.so
COPY --from=build /usr/local/lib/dbpp/mariadb.so /usr/local/lib/blogi/dbpp/mariadb.so
COPY --from=build /usr/local/lib/blogi/plugins /usr/local/lib/blogi/plugins

CMD ["sh", "/usr/local/sbin/run.sh"]
FROM runtime AS blogi-editor

RUN mkdir -p /etc/blogi-editor /var/lib/blogi-editor/node/widgets

VOLUME ["/etc/blogi-editor", "/var/lib/blogi-editor"]

CMD ["/usr/local/sbin/blogi-editor-run.sh"]

# ==========================================
# STAGE 3: Blogi Editor Runtime Target
# Blogi (default target)
# ==========================================
FROM node:alpine AS blogi-editor
WORKDIR /blogi-editor

RUN addgroup -S blogi && adduser -h /var/lib/blogi -S -G blogi blogi
RUN apk update && apk upgrade && \
    apk add --no-cache \
    brotli \
    ca-certificates \
    ffmpeg \
    json-c \
    libpq \
    libstdc++ \
    libuuid \
    mariadb-connector-c \
    openssl \
    pwgen \
    sed \
    sqlite-libs \
    tinyxml2 \
    yaml \
    libarchive

RUN mkdir -p /etc/blogi \
    /usr/local/share/blogi \
    /var/lib/blogi-editor/node/widgets \
    /usr/local/sbin \
    /usr/local/bin \
    /usr/local/lib

COPY editor/docker-config.yaml /usr/local/share/blogi
COPY editor/config.test.two-widget-paths.yaml /usr/local/share/blogi-editor/config.test.two-widget-paths.yaml
COPY editor/run.sh /usr/local/sbin/run.sh
RUN chmod +x /usr/local/sbin/run.sh

# Copy binaries and libraries from build stage
COPY --from=build /usr/local/bin/blogi-editor /usr/local/bin/blogi-editor
COPY --from=build /usr/local/bin/authcli /usr/local/bin/authcli
COPY --from=build /usr/local/share/blogi-editor /usr/local/share/blogi-editor
COPY --from=build /usr/local/lib /usr/local/lib

RUN chmod +x /usr/local/bin/blogi-editor /usr/local/bin/authcli

# Ensure internal plugin paths exist and match build outputs
COPY --from=build /usr/local/lib/dbpp/pgsql.so /usr/local/lib/blogi/dbpp/pgsql.so
COPY --from=build /usr/local/lib/dbpp/sqlite.so /usr/local/lib/blogi/dbpp/sqlite.so
COPY --from=build /usr/local/lib/dbpp/mariadb.so /usr/local/lib/blogi/dbpp/mariadb.so
COPY --from=build /usr/local/lib/blogi/plugins /usr/local/lib/blogi/plugins

CMD ["sh", "/usr/local/sbin/run.sh"]
FROM runtime AS blogi

VOLUME ["/etc/blogi"]

# run.sh writes /etc/blogi/config.yaml from docker-config.yaml on first start; the HTTP
# port comes from $PORT.
EXPOSE 443

CMD ["/usr/local/sbin/blogi-run.sh"]

docker/entrypoint.sh

0 → 100755
+10 −0
Changes for docker/entrypoint.sh: 10 added lines, 0 removed lines.
Original line number Diff line number Diff line
#!/bin/sh
# FenrirOS's libc resolves names through /etc/resolv.yaml only, so translate the
# /etc/resolv.conf docker writes at container start (embedded DNS, --dns, ...).
if [ -r /etc/resolv.conf ]; then
    : > /etc/resolv.yaml
    while read -r key addr rest; do
        [ "$key" = nameserver ] && echo "- nameserver: \"$addr\"" >> /etc/resolv.yaml
    done < /etc/resolv.conf
fi
exec "$@"
+1409 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading