Add Alpine build

- Add build args for user/group.
- Lots of cleanup and adjustments for safety and efficiency.
- Update LSB submodule ref.
This commit is contained in:
Corey 2025-04-13 22:28:40 -04:00
parent dcd8479c79
commit 48d87f667d
Signed by: coco
GPG Key ID: 051138DA6AEE3E60
12 changed files with 323 additions and 102 deletions

View File

@ -1,8 +1,9 @@
# Nothing tracked by LSB git should be in here.
# Keeps git hash clean during build.
server/build
server/documentation
server/log
server/losmeshes
server/navmeshes
server/scripts
server/settings
server/sql
server/log/*.log
server/losmeshes/**
server/navmeshes/**
server/settings/*.lua
server/sql/backups/*.sql
server/tools/config.yaml

3
.gitignore vendored
View File

@ -1 +1,2 @@
config
config/**
!.gitkeep

View File

@ -1,62 +0,0 @@
##############
# Base stage #
##############
FROM ubuntu:latest AS base
ENV DEBIAN_FRONTEND=noninteractive
RUN rm -f /etc/apt/apt.conf.d/docker-clean && \
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' \
> /etc/apt/apt.conf.d/keep-cache
###############
# Build stage #
###############
FROM base AS build
# Install build exclusive dependencies.
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt update && apt install -yqq --no-install-recommends \
binutils-dev build-essential cmake git python3 python3-dev python3-pip \
software-properties-common libluajit-5.1-dev libmariadb-dev-compat \
libssl-dev libzmq3-dev zlib1g-dev
# Install Python dependencies as user to facilitate copying to runtime image.
RUN --mount=type=bind,source=./server/tools/requirements.txt,target=/tmp/requirements.txt \
--mount=type=cache,target=/root/.cache/pip \
pip3 install --break-system-packages --user --requirement /tmp/requirements.txt
# LSB requires Git for the build, and emits version.cpp along with the executables.
# The bind mounted server directory does not persist changes, so copy these from/to
# the cached build folder, preserving timestamps to avoid unnecessary rebuilds.
# Then make a copy of the executables that can be passed to the runtime stage.
RUN --mount=type=cache,target=/build \
--mount=type=bind,source=./.git,target=/.git \
--mount=type=bind,source=./server,target=/server,rw \
cp -p /build/version.cpp /server/src/common 2> /dev/null || true && \
cp -p /build/xi_* /server 2> /dev/null || true && \
cmake -S /server -B /build -DCMAKE_BUILD_TYPE=Release && \
cmake --build /build -j$(nproc) && \
cp -p /server/src/common/version.cpp /build && \
cp -p /server/xi_* /build && \
cp /server/xi_* /root
#################
# Runtime stage #
#################
FROM base AS service
# Install runtime dependencies.
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt update && apt install -yqq --no-install-recommends \
binutils git libluajit-5.1-2 libssl3t64 libzmq5 mariadb-client \
python3 zlib1g \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /server
RUN git config --global --add safe.directory /server
# Copy installed Python dependencies and built executables from build stage.
COPY --from=build /root/.local /root/.local
COPY --from=build /root/xi_* /server
COPY entrypoint.sh /entrypoint.sh

View File

@ -19,10 +19,10 @@ That's it! All the setup is handled for you. Check out the [official LSB documen
- Use these to stop/start the server when rebuilding isn't necessary.
- `docker compose build`
- Builds the image. With caching, this basically means rebuild the executables.
- Builds the image. With caching, this basically just rebuilds the executables if needed. Use `--no-cache` for a full clean build.
- `docker compose down`
- Shuts down and deletes the containers.
- Shuts down and removes the containers.
----
@ -30,10 +30,21 @@ That's it! All the setup is handled for you. Check out the [official LSB documen
- `git submodule update --remote --merge`
- This will pull the latest changes from upstream LSB. Run `docker compose up --build --detach` again to rebuild and start/restart the containers.
- You can also just change to the server directory and treat it as its own repo (change branch, fork, apply patches, etc.)
## Alpine
Alpine Linux is a minimal image that comes in at about half the size of the Ubuntu image (excluding bind mounted files/directories). The Alpine build is experimental and unsupported by LSB. It uses a newer version of GCC and the [musl libc](https://wiki.musl-libc.org/functional-differences-from-glibc.html). Use at your own risk.
`docker compose -f ./docker-compose.yml -f ./docker-compose.alpine.yml up --build --detach`
## Notes
- The image is built with a default user and group named `xiadmin` with UID and GID `1000`.
- You can supply the build args `UNAME`, `UGROUP`, `UID`, `GID` to change these.
- `docker compose build --build-arg UID="$(id -u)" --build-arg GID="$(id -g)" --build-arg UNAME="$(whoami)"`
- If using WSL with Windows, make sure [the project is stored in the WSL file system](https://learn.microsoft.com/en-us/windows/wsl/filesystems#file-storage-and-performance-across-file-systems) for best performance.
- You may experience issues related to the system clock. Using `sudo hwclock --hctosys` in WSL may help.
- Secure database passwords and a default user and database name are generated into `config/.env` to simplify the setup process.
- **Optional** - You can manually create this _before_ building the containers with these variables:
```
@ -41,12 +52,10 @@ That's it! All the setup is handled for you. Check out the [official LSB documen
MARIADB_USER=
MARIADB_PASSWORD=
MARIADB_ROOT_PASSWORD=
XI_NETWORK_ENABLE_HTTP=
```
Otherwise, the default user is `xiadmin`, the default database is `xidb`, and both passwords are randomly generated.
- The LSB HTTP API is enabled by default and can be accessed at http://localhost:8088/.
- This can be disabled by setting `XI_NETWORK_ENABLE_HTTP=0`
- Because environment variables are used, the server settings `SQL_LOGIN`, `SQL_PASSWORD`, `SQL_DATABASE`, and `ENABLE_HTTP` (located in network.lua) are not used.
Otherwise, the default user is `xiadmin`, the default database is `xidb`, and the password is randomly generated.
- If `config/.env` is automatically generated, a random initial password for the root user will be generated and printed to stdout (check your build log!)
- Because environment variables are used, the server settings `SQL_LOGIN`, `SQL_PASSWORD`, and `SQL_DATABASE` (all located in network.lua) are not used.
- Because LSB uses Git during the build and update process, changes to the Git metadata will trigger an image rebuild, but with caching this shouldn't be significant.
----

0
config/.gitkeep Normal file
View File

18
docker-compose.alpine.yml Normal file
View File

@ -0,0 +1,18 @@
services:
dbtool:
image: lsd_service:alpine
build:
context: .
dockerfile: docker/alpine.Dockerfile
connect:
image: lsd_service:alpine
search:
image: lsd_service:alpine
world:
image: lsd_service:alpine
map:
image: lsd_service:alpine

View File

@ -1,20 +1,18 @@
x-common: &common # Configs used by more than one service.
image: lsd_service
image: lsd_service:ubuntu
pull_policy: never
entrypoint: ["/entrypoint.sh"] # Copies the MariaDB env vars into corresponding LSB settings.
environment:
XI_NETWORK_HTTP_HOST: 0.0.0.0
XI_NETWORK_ZMQ_IP: world
XI_NETWORK_SQL_HOST: database
volumes: # YAML merge can't be used with sequences, so bind everything here. (SEE NOTE ABOUT WSL IN README!)
volumes:
- ./.git:/.git
- ./config:/config
- ./.git:/.git # Since LSB is a submodule, we want to preserve
- ./server/.git:/server/.git # the directory structure inside the container.
- ./server/.git:/server/.git
- ./server/log:/server/log
- ./server/losmeshes:/server/losmeshes
- ./server/modules:/server/modules
- ./server/navmeshes:/server/navmeshes
- ./server/res:/server/res
- ./server/scripts:/server/scripts
- ./server/settings:/server/settings
- ./server/sql:/server/sql
@ -24,22 +22,24 @@ services:
database:
image: mariadb
entrypoint: ["/mariadb-entrypoint.sh"]
command: ["mariadbd"]
restart: always
volumes:
- ./config:/config
- ./mariadb-entrypoint.sh:/mariadb-entrypoint.sh # Custom entrypoint to generate .env.
- ./server/sql:/docker-entrypoint-initdb.d # Initializes the database.
- database:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
start_period: 3m # First time initialization takes a while, so give it extra time.
interval: 15s # TODO: use dbtool setup, set lower healthcheck.
start_period: 10s
interval: 10s
timeout: 5s
retries: 4
retries: 3
dbtool:
<<: *common # Import common configs.
build: . # This service builds the image shared by all LSB services.
build:
context: .
dockerfile: docker/ubuntu.Dockerfile # This service builds the image shared by all LSB services.
command: ["python3", "/server/tools/dbtool.py", "update"]
restart: no
depends_on:

133
docker/alpine.Dockerfile Normal file
View File

@ -0,0 +1,133 @@
# syntax=docker/dockerfile:1-labs
##############
# Base stage #
##############
FROM alpine:latest AS base
ARG UNAME=xiadmin
ARG UGROUP=xiadmin
ARG UID=1000
ARG GID=1000
RUN addgroup --gid $GID $UGROUP && \
adduser --uid $UID $UNAME --ingroup $UGROUP --home /xiadmin --disabled-password
# Install runtime dependencies at the base level.
RUN --mount=type=cache,target=/var/cache/apk,sharing=locked \
apk --update-cache add \
binutils \
git \
luajit \
mariadb-client \
mariadb-connector-c \
openssl \
python3 \
zeromq \
zlib
RUN git config --system --add safe.directory /server
ENV PATH=/xiadmin/.local/bin:$PATH
###############
# Build stage #
###############
FROM base AS build
# Install build dependencies.
RUN --mount=type=cache,target=/var/cache/apk,sharing=locked \
apk --update-cache add \
binutils-dev \
ccache \
cmake \
g++ \
linux-headers \
luajit-dev \
make \
mariadb-dev \
openssl-dev \
python3-dev \
py3-pip \
zeromq-dev \
zlib-dev
USER $UNAME
WORKDIR /server
# Install Python dependencies here, copied into runtime stage.
RUN --mount=type=bind,source=server/tools/requirements.txt,target=/tmp/requirements.txt \
--mount=type=cache,target=/xiadmin/.cache/pip,uid=$UID,gid=$GID \
pip3 install --break-system-packages --user --ignore-installed --requirement /tmp/requirements.txt
# Exclude changes to git metadata, scripts, and sql not needed for build.
# Excluded here instead of dockerignore so they can be bind mounted during build.
# Saves from copying everything whenever scripts/sql change.
# https://docs.docker.com/reference/dockerfile/#copy---exclude (docker/dockerfile:1.7-labs)
COPY --chown=$UNAME:$UGROUP --exclude=.git --exclude=scripts --exclude=sql server /server
# --- PATCH LSB ---
RUN LSB_FILE="/server/cmake/FindMariaDBCPP.cmake" && \
if [ -f "$LSB_FILE" ]; then \
# Check if replacement is needed.
if grep -qF 'b09555de99ed4b1d054a88ff85acbae996bce1d1' "$LSB_FILE"; then \
echo "Patching $LSB_FILE: Update MariaDB Connector/C++ to 1.0.5 (fixes compilation on Alpine)"; \
sed -i 's/b09555de99ed4b1d054a88ff85acbae996bce1d1/a36ff95ac6a6236a2faaaa6ec710219c8aabe35d/g' "$LSB_FILE"; \
fi; \
else \
echo "Warning: $LSB_FILE not found, skipping patch."; \
fi;
# --- End Patch ---
# Cache the build. Bind mounts to save copy time and keep clean git hash.
ENV CCACHE_DIR=/xiadmin/.ccache
RUN --mount=type=cache,target=/xiadmin/build,uid=$UID,gid=$GID \
--mount=type=cache,target=/xiadmin/.ccache,uid=$UID,gid=$GID \
--mount=type=bind,source=.git,target=/.git \
--mount=type=bind,source=server/.git,target=/server/.git \
--mount=type=bind,source=server/scripts,target=/server/scripts \
--mount=type=bind,source=server/sql,target=/server/sql \
# --- CACHE ---
cp -p /xiadmin/build/version.cpp /server/src/common/ 2> /dev/null; \
cp -p /xiadmin/build/xi_* /server/ 2> /dev/null; \
# --- End ---
cmake -S /server -B /xiadmin/build -DCMAKE_BUILD_TYPE=Release && \
# --- PATCH efsw ---
EFSW_FILE="/xiadmin/build/_deps/efsw-src/src/efsw/FileWatcherInotify.cpp"; \
if [ -f "$EFSW_FILE" ]; then \
# Check if include is missing.
if ! grep -qF '#include <sys/select.h>' "$EFSW_FILE"; then \
echo "Patching $EFSW_FILE: Adding #include <sys/select.h>"; \
sed -i '1i #include <sys/select.h>' "$EFSW_FILE"; \
fi; \
# Check if replacement is needed.
if grep -qF 'u_int32_t' "$EFSW_FILE"; then \
echo "Patching $EFSW_FILE: Replacing u_int32_t with uint32_t"; \
sed -i 's/u_int32_t/uint32_t/g' "$EFSW_FILE"; \
fi; \
else \
echo "Warning: $EFSW_SRC_FILE not found, skipping patch."; \
fi; \
# --- End Patch ---
cmake --build /xiadmin/build -j$(nproc) && \
# --- CACHE ---
# Alpine seems to always re-link the executables.
cp -p /server/xi_* /xiadmin/build/; \
cp -p /server/src/common/version.cpp /xiadmin/build/;
# --- End ---
#################
# Runtime stage #
#################
FROM base AS service
USER $UNAME
WORKDIR /server
COPY server/res/compress.dat server/res/decompress.dat /server/res/
# Copy installed Python dependencies and built executables from build stage.
COPY --from=build /xiadmin/.local /xiadmin/.local
COPY --from=build /server/xi_* /server/
COPY --chmod=0755 entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

110
docker/ubuntu.Dockerfile Normal file
View File

@ -0,0 +1,110 @@
# syntax=docker/dockerfile:1-labs
##############
# Base stage #
##############
FROM ubuntu:latest AS base
ARG UNAME=xiadmin
ARG UGROUP=xiadmin
ARG UID=1000
ARG GID=1000
ARG DEBIAN_FRONTEND=noninteractive
RUN rm -f /etc/apt/apt.conf.d/docker-clean && \
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' \
> /etc/apt/apt.conf.d/keep-cache
RUN userdel --remove ubuntu && \
groupadd --gid $GID $UNAME && \
useradd --uid $UID $UNAME --gid $UGROUP --home-dir /xiadmin --create-home --skel /dev/null
# Install runtime dependencies at the base level.
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install --assume-yes --no-install-recommends --quiet \
binutils \
git \
libluajit-5.1-2 \
libssl3t64 \
libzmq5 \
mariadb-client \
python3 \
zlib1g
RUN git config --system --add safe.directory /server
ENV PATH=/xiadmin/.local/bin:$PATH
###############
# Build stage #
###############
FROM base AS build
# Install build dependencies.
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install --assume-yes --no-install-recommends --quiet \
binutils-dev \
build-essential \
ccache \
cmake \
g++ \
git \
libluajit-5.1-dev \
libmariadb-dev-compat \
libssl-dev \
libzmq3-dev \
make \
python3-dev \
python3-pip \
zlib1g-dev
USER $UNAME
WORKDIR /server
# Install Python dependencies here, copied into runtime stage.
RUN --mount=type=bind,source=server/tools/requirements.txt,target=/tmp/requirements.txt \
--mount=type=cache,target=/xiadmin/.cache/pip,uid=$UID,gid=$GID \
pip3 install --break-system-packages --user --ignore-installed --requirement /tmp/requirements.txt
# Exclude changes to git metadata, scripts, and sql not needed for build.
# Excluded here instead of dockerignore so they can be bind mounted during build.
# Saves from copying everything whenever scripts/sql change.
# https://docs.docker.com/reference/dockerfile/#copy---exclude (docker/dockerfile:1.7-labs)
COPY --chown=$UNAME:$UGROUP --exclude=.git --exclude=scripts --exclude=sql server /server
# Cache the build. Bind mounts to save copy time and keep clean git hash.
ENV CCACHE_DIR=/xiadmin/.ccache
RUN --mount=type=cache,target=/xiadmin/build,uid=$UID,gid=$GID \
--mount=type=cache,target=/xiadmin/.ccache,uid=$UID,gid=$GID \
--mount=type=bind,source=.git,target=/.git \
--mount=type=bind,source=server/.git,target=/server/.git \
--mount=type=bind,source=server/scripts,target=/server/scripts \
--mount=type=bind,source=server/sql,target=/server/sql \
# --- CACHE ---
cp -p /xiadmin/build/version.cpp /server/src/common/ 2> /dev/null; \
cp -p /xiadmin/build/xi_* /server/ 2> /dev/null; \
# --- End ---
cmake -S /server -B /xiadmin/build -DCMAKE_BUILD_TYPE=Release && \
cmake --build /xiadmin/build -j$(nproc) && \
# --- CACHE ---
cp -p /server/xi_* /xiadmin/build/; \
cp -p /server/src/common/version.cpp /xiadmin/build/;
# --- End ---
#################
# Runtime stage #
#################
FROM base AS service
USER $UNAME
WORKDIR /server
COPY server/res/compress.dat server/res/decompress.dat /server/res/
# Copy installed Python dependencies and built executables from build stage.
COPY --from=build /xiadmin/.local /xiadmin/.local
COPY --from=build /server/xi_* /server/
COPY --chmod=0755 entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View File

@ -1,11 +1,18 @@
#!/bin/bash
#!/bin/sh
set -e
# Export XI_ variables from corresponding MARIADB_ variables.
set -a
source "/config/.env"
set +a
export XI_NETWORK_SQL_DATABASE=$MARIADB_DATABASE
export XI_NETWORK_SQL_LOGIN=$MARIADB_USER
export XI_NETWORK_SQL_PASSWORD="$MARIADB_PASSWORD"
echo "Loading configuration from /config/.env..."
if [ -f "/config/.env" ]; then
set -a
. "/config/.env"
set +a
unset MARIADB_ROOT_PASSWORD
else
echo "Warning: /config/.env not found."
fi
export XI_NETWORK_SQL_PASSWORD="$MARIADB_PASSWORD" && unset MARIADB_PASSWORD
export XI_NETWORK_SQL_LOGIN="$MARIADB_USER" && unset MARIADB_USER
export XI_NETWORK_SQL_DATABASE="$MARIADB_DATABASE" && unset MARIADB_DATABASE
exec "$@"

View File

@ -1,34 +1,38 @@
#!/bin/bash
set -e
ENV_FILE="/config/.env"
echo "Running custom entrypoint..."
# Set default values if the .env file doesn't exist.
if [ ! -e "$ENV_FILE" ]; then
echo "$ENV_FILE not found, generating one..."
MARIADB_DATABASE=xidb
MARIADB_USER=xiadmin
XI_NETWORK_ENABLE_HTTP=1
# Generate random passwords. (MARIADB_RANDOM_ROOT_PASSWORD)
# https://github.com/MariaDB/mariadb-docker/blob/87a043a031e8c56ba66a0ad06e633417ae75ee1e/docker-entrypoint.sh#L378-L382
MARIADB_PASSWORD=$(pwgen --numerals --capitalize --symbols --remove-chars="'\\" -1 32)
MARIADB_ROOT_PASSWORD=$(pwgen --numerals --capitalize --symbols --remove-chars="'\\" -1 32)
export MARIADB_RANDOM_ROOT_PASSWORD=1
# Write the variables to the .env file.
cat <<-EOF > "$ENV_FILE"
MARIADB_DATABASE=$MARIADB_DATABASE
MARIADB_USER=$MARIADB_USER
MARIADB_PASSWORD='$MARIADB_PASSWORD'
MARIADB_ROOT_PASSWORD='$MARIADB_ROOT_PASSWORD'
XI_NETWORK_ENABLE_HTTP=$XI_NETWORK_ENABLE_HTTP # Enables the LSB HTTP API
EOF
chown mysql:mysql "$ENV_FILE"
fi
# Load environment variables.
if [ -f "$ENV_FILE" ]; then
echo "Loading environment vairables from $ENV_FILE..."
set -a # Automatically export all sourced variables.
source "$ENV_FILE"
set +a
fi
# Execute the original MariaDB entrypoint.
exec /usr/local/bin/docker-entrypoint.sh mariadbd "$@"
echo "Handing over to official MariaDB entrypoint..."
exec /usr/local/bin/docker-entrypoint.sh "$@"

2
server

@ -1 +1 @@
Subproject commit 0985720f78dfc8cfc5b93b55e2ea672baba4cd26
Subproject commit f283f72fdbf83f718b250032411a0bb0430ec775