Files
Frametop--frametop/pack/Containerfile
T
0x1f6 ea47422872 Frametop as an OCI image: pack/Containerfile, the ft wrapper, just, CI
The container becomes an artifact instead of a recipe: the base toolbox image
is pinned by digest, Python dependencies come from the committed uv.lock
(uv-managed CPython, clean venv at /opt/frametop/venv, no system-site-packages),
and OpenVR's header and libopenvr_api come from the same pinned public tag the
Frame-side build scripts pin. The native binaries compile inside the image the
same way the build.sh scripts compile them; hand tracking stays deferred as in
install.sh.

- ft: one wrapper for everything that runs (build, shell, run, test, update);
  Frame mounts (ipc=host, /run/user, /opt/steamvr) are designed behind
  FT_FRAME=1 until validated on the device (pack/README.md)
- justfile: runner for the in-image actions (test, lint as report, check)
- CI: native arm64 runner builds and pushes ghcr.io/0x1f6/frametop and runs
  the suites inside the built image as a smoke job; actions pinned to SHAs

Validated locally (docker, aarch64): full build, all six binaries, and
./ft test green (unittest suites, check scripts, 11 pytest tests).
2026-10-06 01:11:04 +02:00

108 lines
6.8 KiB
Docker

# Frametop runtime image (see pack/README.md).
#
# One image that holds everything Frametop needs: the frozen toolchain and C
# libraries, its native binaries, and its locked Python environment. A given
# image tag always behaves the same, on a PC and on the Frame.
#
# setup/dev-container.sh stays the source of truth for the package list on the
# Frame's dev container; the list below freezes the runtime-relevant subset of
# it plus the toolchain (the binaries are built in this image). Keep the two in
# sync on purpose: dev-container.sh documents *why* each package is there.
FROM registry.fedoraproject.org/fedora-toolbox:44@sha256:034cb7c472038e2d879ddc19568106a1342aa24403f0d641a0f73dda638707ad
# Toolchain (the binaries are built in here) and the libraries they link.
RUN dnf -y install --setopt=install_weak_deps=False \
gcc gcc-c++ make pkgconf-pkg-config git curl unzip just \
pipewire-devel libxkbcommon-devel libinput-devel systemd-devel dbus-devel \
libdrm-devel mesa-libgbm-devel wayland-devel vulkan-loader-devel \
vulkan-headers plasma-wayland-protocols wlroots-devel pixman-devel \
libstdc++-static glibc-static jsoncpp-devel zstd \
# Frametop Input Settings (Kirigami, PySide6) and remote desktop (krdp):
# the KDE/Qt stack has no PyPI wheels, so it stays a dnf package
python3-pyside6 kf6-kirigami kf6-qqc2-desktop-style qt6-qtwayland \
breeze-icon-theme plasma-breeze krdp freerdp tigervnc-x11-server xrandr \
&& dnf clean all
# uv, pinned. Python dependencies come from the committed uv.lock, not from
# whatever PyPI serves on build day.
RUN curl -fsSL https://github.com/astral-sh/uv/releases/download/0.12.23/uv-aarch64-unknown-linux-gnu.tar.gz \
| tar -xz --strip-components=1 -C /usr/local/bin
# OpenVR: the pinned public tag the Frame-side build scripts pin
# (screens/build.sh; the header SteamVR ships on the Frame predates the
# interface versions Frametop uses). The openvr repo carries the prebuilt
# linuxarm64 API library in its tree, so header and library come from the same
# commit; at runtime the library lives in /opt/frametop/lib, found through the
# binaries' rpath. (Valve no longer publishes release archives.)
RUN install -d /opt/frametop/include /opt/frametop/lib \
&& curl -fsSL https://github.com/ValveSoftware/openvr/raw/v2.15.6/headers/openvr.h -o /opt/frametop/include/openvr.h \
&& curl -fsSL https://github.com/ValveSoftware/openvr/raw/v2.15.6/headers/openvr_driver.h -o /opt/frametop/include/openvr_driver.h \
&& curl -fsSL https://github.com/ValveSoftware/openvr/raw/v2.15.6/bin/linuxarm64/libopenvr_api.so -o /opt/frametop/lib/libopenvr_api.so \
&& curl -fsSL https://raw.githubusercontent.com/nothings/stb/2c980bb59875b0d32144a71867fbdebb2f77cd20/stb_truetype.h \
-o /opt/frametop/include/stb_truetype.h
# Python environment from the lockfile (uv-managed CPython, not the system one:
# the venv must not depend on which Fedora is current). Rebuilt only when the
# lockfile changes, so source edits below reuse this layer. The environment
# variable comes first so the venv lands in /opt/frametop/venv.
ENV UV_PROJECT_ENVIRONMENT=/opt/frametop/venv
COPY pyproject.toml uv.lock .python-version /src/frametop/
RUN cd /src/frametop && uv sync --frozen --all-groups
# The sources, then the binaries (built as the Frame-side build scripts build
# them; outputs go to /opt/frametop/bin). Hand tracking is deferred exactly as
# in install.sh: it builds ncnn from source and isn't needed until
# hands/run.sh install runs.
COPY . /src/frametop
WORKDIR /src/frametop
RUN set -eux; mkdir -p /opt/frametop/bin
# ft-screens and ft-handtest (screens/build.sh, against /opt/frametop instead of /opt/steamvr)
RUN set -eux; cd screens; mkdir -p build; \
gcc -std=c11 -O2 -Wall -Wno-unused-parameter -c -o build/compositor.o compositor.c \
$(pkg-config --cflags wlroots-0.20 wayland-server xkbcommon libdrm pixman-1); \
cxx="g++ -std=c++17 -O2 -Wall -Wno-missing-field-initializers -I/opt/frametop/include $(pkg-config --cflags egl glesv2 gbm libdrm)"; \
$cxx -c -o build/vr.o vr.cpp; \
$cxx -c -o build/keyboard.o keyboard.cpp; \
$cxx -c -o build/handcut.o handcut.cpp; \
$cxx -c -o build/handtest.o handtest.cpp; \
vrlibs="$(pkg-config --libs egl glesv2 gbm) -L/opt/frametop/lib -lopenvr_api -Wl,-rpath,/opt/frametop/lib"; \
g++ -o /opt/frametop/bin/ft-screens build/compositor.o build/vr.o build/keyboard.o build/handcut.o \
$(pkg-config --libs wlroots-0.20 wayland-server xkbcommon pixman-1) $vrlibs; \
g++ -o /opt/frametop/bin/ft-handtest build/handtest.o build/handcut.o $vrlibs
# ft-pointer (pointer/helper/build.sh) and the ft_pointer driver (pointer/driver/build.sh;
# static C++ runtime, checked against the host's glibc 2.39)
RUN set -eux; cd pointer/helper; mkdir -p build; \
g++ -std=c++17 -O2 -Wall -Wno-unused-parameter -I/opt/frametop/include -I../common \
-o /opt/frametop/bin/ft-pointer ft-pointer.cpp \
-L/opt/frametop/lib -lopenvr_api -Wl,-rpath,/opt/frametop/lib -lpthread; \
cd ../driver; mkdir -p build; \
g++ -std=c++17 -O2 -fPIC -shared -fvisibility=hidden -fno-math-errno -Wall -Wno-unused-parameter \
-static-libstdc++ -static-libgcc -Wl,--exclude-libs,ALL \
-I/opt/frametop/include -o build/driver_ft_pointer.so driver_ft_pointer.cpp -lpthread; \
max=$(objdump -T build/driver_ft_pointer.so | grep -oE "GLIBC_[0-9.]+" | sort -uV | tail -1); \
[ "$(printf "%s\n" "$max" GLIBC_2.39 | sort -V | tail -1)" = GLIBC_2.39 ] \
|| { echo "driver needs newer glibc than the host has" >&2; exit 1; }; \
install -D build/driver_ft_pointer.so /opt/frametop/lib/driver_ft_pointer.so
# ft-gaze and ft-gazepanel (gaze/build.sh)
RUN set -eux; cd gaze; mkdir -p build; \
g++ -std=c++17 -O2 -Wall -Wno-unused-parameter -Wno-missing-field-initializers \
-I/opt/frametop/include -I../pointer/common \
-o /opt/frametop/bin/ft-gaze ft-gaze.cpp \
-L/opt/frametop/lib -lopenvr_api -Wl,-rpath,/opt/frametop/lib -lpthread; \
g++ -std=c++17 -O2 -Wall -Wno-unused-parameter -Wno-missing-field-initializers \
-I/opt/frametop/include $(pkg-config --cflags gbm libdrm) \
-o /opt/frametop/bin/ft-gazepanel panel/ft-gazepanel.cpp \
-L/opt/frametop/lib -lopenvr_api -Wl,-rpath,/opt/frametop/lib \
$(pkg-config --libs gbm libdrm) -lpthread
# ft-powerd (power/build.sh)
RUN set -eux; cd power; mkdir -p build; \
g++ -std=c++17 -O2 -Wall -Wno-unused-parameter -I/opt/frametop/include \
-o /opt/frametop/bin/ft-powerd ft-powerd.cpp \
-L/opt/frametop/lib -lopenvr_api -Wl,-rpath,/opt/frametop/lib
# The wrapper's default environment: Frametop's binaries and locked Python
# first, the system (dnf Qt stack) after. sleep infinity as the default command
# keeps the image usable with distrobox create, like the toolbox base.
ENV PATH=/opt/frametop/venv/bin:/opt/frametop/bin:/usr/local/bin:/usr/bin:/usr/sbin
CMD ["sleep", "infinity"]