diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..bae4bbe --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +# Root build context is used only by web/Dockerfile, which needs web/ and +# docs/api/openapi.json. Allowlist those; keep everything else (target/, .git, crates) +# out of the context upload. +* +!web +!docs/api/openapi.json +web/node_modules +web/.output +web/dist +web/src/api/gen +web/src/paraglide diff --git a/.gitea/workflows/apple.yml b/.gitea/workflows/apple.yml new file mode 100644 index 0000000..6810907 --- /dev/null +++ b/.gitea/workflows/apple.yml @@ -0,0 +1,39 @@ +# Apple client CI — runs on the self-hosted macOS runner (home-mac-mini-1, host mode; +# see scripts/ci/setup-macos-runner.sh). Builds the Rust core into +# PunktfunkCore.xcframework, then builds + tests the Swift package. Network-dependent +# tests (RemoteFirstLightTests) self-skip without PUNKTFUNK_REMOTE_HOST. +name: apple + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + swift: + runs-on: macos-arm64 + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + + - name: Rust toolchain (self-healing on a fresh runner) + run: | + if ! command -v rustup >/dev/null && [ ! -x "$HOME/.cargo/bin/rustup" ]; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ + | sh -s -- -y --no-modify-path --profile minimal + fi + RUSTUP="$(command -v rustup || echo "$HOME/.cargo/bin/rustup")" + dirname "$RUSTUP" >> "$GITHUB_PATH" + "$RUSTUP" target add aarch64-apple-darwin x86_64-apple-darwin + + - name: Build PunktfunkCore.xcframework + run: bash scripts/build-xcframework.sh + + - name: Build (PunktfunkKit + PunktfunkClient app shell) + working-directory: clients/apple + run: swift build + + - name: Test (unit + real-codec round trip; remote tests self-skip) + working-directory: clients/apple + run: swift test diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index d3ae1fb..627abd5 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,5 +1,7 @@ -# CI for punktfunk (Gitea Actions, GitHub-Actions-compatible syntax). -# Adjust `runs-on` to match your runner labels if not using the default ubuntu image. +# CI for punktfunk (Gitea Actions). Linux jobs run on the `ubuntu-latest` runner; the Rust +# job runs inside the prebuilt builder image (ci/rust-ci.Dockerfile — system FFmpeg 8, +# PipeWire, GL/GBM, libcuda link stub, pinned-channel rustup) so the workspace links the +# same libs as the dev boxes. Apple client CI lives in apple.yml (macOS runner). name: ci on: @@ -10,22 +12,36 @@ on: jobs: rust: runs-on: ubuntu-latest + container: + image: git.unom.io/unom/punktfunk-rust-ci:latest + timeout-minutes: 90 steps: - uses: actions/checkout@v4 - - name: Install Rust toolchain - run: | - if ! command -v cargo >/dev/null; then - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - fi - rustup component add rustfmt clippy + # Best-effort caches (act_runner's built-in cache server). Keyed on Cargo.lock: + # registry/git are download caches, target/ the incremental build. The target key + # carries the rustc version — rust-toolchain.toml pins the floating "stable" + # channel, so the file alone wouldn't invalidate stale incremental state. + - name: Cache keys + run: echo "rustc=$(rustc --version | cut -d' ' -f2)" >> "$GITHUB_ENV" + - uses: actions/cache@v4 + with: + path: | + /usr/local/cargo/registry + /usr/local/cargo/git + key: cargo-home-${{ hashFiles('Cargo.lock') }} + restore-keys: cargo-home- + - uses: actions/cache@v4 + with: + path: target + key: cargo-target-${{ env.rustc }}-${{ hashFiles('Cargo.lock') }} + restore-keys: cargo-target-${{ env.rustc }}- - name: Format run: cargo fmt --all --check - name: Clippy (deny warnings) - run: cargo clippy --workspace --all-targets -- -D warnings + run: cargo clippy --workspace --all-targets --locked -- -D warnings - name: Build run: cargo build --workspace --locked @@ -38,6 +54,52 @@ jobs: - name: Verify generated header is committed & up to date run: | - cargo build -p punktfunk-core + cargo build -p punktfunk-core --locked + git config --global --add safe.directory "$PWD" git diff --exit-code include/punktfunk_core.h \ || (echo "include/punktfunk_core.h is stale — commit the regenerated header" && exit 1) + + web: + runs-on: ubuntu-latest + container: + image: oven/bun:1 + timeout-minutes: 30 + defaults: + run: + working-directory: web + steps: + # oven/bun ships neither git nor a real node (only a bun shim) — actions/checkout + # needs both. + - name: Install git + node + working-directory: / + run: apt-get update && apt-get install -y --no-install-recommends git nodejs + - uses: actions/checkout@v4 + - name: Install dependencies + run: bun install --frozen-lockfile --ignore-scripts + # Build first: it generates the orval API client + paraglide messages that + # typechecking imports. + - name: Build + run: bun run build + - name: Typecheck + run: bun run lint + + docs-site: + runs-on: ubuntu-latest + container: + image: oven/bun:1 + timeout-minutes: 30 + defaults: + run: + working-directory: docs-site + steps: + - name: Install git + working-directory: / + run: apt-get update && apt-get install -y --no-install-recommends git + - uses: actions/checkout@v4 + - name: Install dependencies + run: bun install --frozen-lockfile --ignore-scripts + # Build first: fumadocs-mdx emits the .source typegen the typecheck imports. + - name: Build + run: bun run build + - name: Typecheck + run: bun run lint diff --git a/.gitea/workflows/docker.yml b/.gitea/workflows/docker.yml new file mode 100644 index 0000000..7bed8c4 --- /dev/null +++ b/.gitea/workflows/docker.yml @@ -0,0 +1,61 @@ +# Build + push the dockerized pieces to the Gitea container registry: +# punktfunk-web — management console (web/Dockerfile, repo-root context) +# punktfunk-docs — documentation site (docs-site/Dockerfile) +# punktfunk-rust-ci — Rust CI builder image consumed by ci.yml +# Host and clients are intentionally NOT containerized (see CLAUDE.md "What's left"). +# +# REGISTRY_TOKEN: repo Actions secret, a PAT with write:package scope. +# +# Bootstrap note: ci.yml's rust job pulls punktfunk-rust-ci:latest from the registry, so +# this workflow (or a manual push) must have succeeded once before that job can run; on +# the same push, ci.yml builds against the PREVIOUS image. All three were seeded manually +# on 2026-06-12. +name: docker + +on: + push: + branches: [main] + tags: ['v*'] + workflow_dispatch: + +env: + REGISTRY: git.unom.io + OWNER: unom + +jobs: + build-push: + runs-on: ubuntu-latest + timeout-minutes: 45 + strategy: + matrix: + include: + - image: punktfunk-web + dockerfile: web/Dockerfile + context: . + - image: punktfunk-docs + dockerfile: docs-site/Dockerfile + context: docs-site + - image: punktfunk-rust-ci + dockerfile: ci/rust-ci.Dockerfile + context: ci + steps: + - uses: actions/checkout@v4 + + - name: Login to registry + # Username must be the owner of the REGISTRY_TOKEN PAT, not the push actor. + run: | + echo "${{ secrets.REGISTRY_TOKEN }}" \ + | docker login "$REGISTRY" -u enricobuehler --password-stdin + + - name: Build + run: | + docker build --pull \ + -f "${{ matrix.dockerfile }}" \ + -t "$REGISTRY/$OWNER/${{ matrix.image }}:latest" \ + -t "$REGISTRY/$OWNER/${{ matrix.image }}:sha-${GITHUB_SHA::8}" \ + "${{ matrix.context }}" + + - name: Push + run: | + docker push "$REGISTRY/$OWNER/${{ matrix.image }}:sha-${GITHUB_SHA::8}" + docker push "$REGISTRY/$OWNER/${{ matrix.image }}:latest" diff --git a/ci/rust-ci.Dockerfile b/ci/rust-ci.Dockerfile new file mode 100644 index 0000000..35321f3 --- /dev/null +++ b/ci/rust-ci.Dockerfile @@ -0,0 +1,42 @@ +# CI builder for the Rust workspace — Ubuntu 26.04 to match the dev/host boxes +# (FFmpeg 8 / libavcodec 62, PipeWire 1.6). Used by .gitea/workflows/ci.yml as the job +# container; rebuilt+pushed by .gitea/workflows/docker.yml. +# +# docker build -f ci/rust-ci.Dockerfile -t punktfunk-rust-ci ci +# +# The workspace links real system libs at build time (CLAUDE.md "Pinned crate facts"): +# FFmpeg, PipeWire, Opus, GL/EGL/GBM — and libcuda, which has no real driver here; the +# zerocopy path only needs the symbols at link time, so a driver userspace package plus a +# libcuda.so -> libcuda.so.1 symlink stands in for it (CI never executes the CUDA path). +FROM ubuntu:26.04 +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ + # toolchain + bindgen; nodejs runs the JS actions (checkout/cache) inside this container + build-essential clang libclang-dev pkg-config cmake git curl ca-certificates nodejs \ + # ffmpeg-next 8 (system FFmpeg 8 / libavcodec 62 on 26.04) + libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavfilter-dev \ + libavdevice-dev \ + # capture / audio / display stacks (+xkbcommon for the wlr input backend) + libpipewire-0.3-dev libopus-dev libwayland-dev libxkbcommon-dev \ + # zerocopy link deps (GL via libglvnd, EGL, GBM) + libgl-dev libegl-dev libgbm-dev \ + && rm -rf /var/lib/apt/lists/* + +# libcuda link stub: the NVIDIA userspace library (no kernel module needed) provides +# every cuXxx symbol. On 26.04 the package already ships the libcuda.so dev symlink; +# -sf keeps this idempotent if a future package drops it again. +RUN apt-get update \ + && apt-get install -y --no-install-recommends libnvidia-compute-580-server \ + && rm -rf /var/lib/apt/lists/* \ + && ln -sf libcuda.so.1 /usr/lib/x86_64-linux-gnu/libcuda.so \ + && test -e /usr/lib/x86_64-linux-gnu/libcuda.so.1 + +# Toolchain shared across CI users (jobs may run as different uids). +ENV RUSTUP_HOME=/usr/local/rustup \ + CARGO_HOME=/usr/local/cargo \ + PATH=/usr/local/cargo/bin:$PATH +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ + | sh -s -- -y --no-modify-path --profile minimal \ + --component rustfmt,clippy \ + && chmod -R a+w "$RUSTUP_HOME" "$CARGO_HOME" \ + && rustc --version && cargo clippy --version && cargo fmt --version diff --git a/docs-site/.dockerignore b/docs-site/.dockerignore new file mode 100644 index 0000000..090bcd2 --- /dev/null +++ b/docs-site/.dockerignore @@ -0,0 +1,5 @@ +node_modules +.output +dist +Dockerfile +.dockerignore diff --git a/docs-site/Dockerfile b/docs-site/Dockerfile new file mode 100644 index 0000000..06a6fb4 --- /dev/null +++ b/docs-site/Dockerfile @@ -0,0 +1,23 @@ +# punktfunk documentation site — Fumadocs on TanStack Start, built with Bun, served by +# the Nitro `bun` preset bundle. Self-contained context: +# +# docker build -t punktfunk-docs docs-site +# +# Runtime: PORT (default 3000). +FROM oven/bun:1 AS build +WORKDIR /app + +COPY package.json bun.lock ./ +RUN bun install --frozen-lockfile --ignore-scripts + +COPY . . +# fumadocs-mdx's vite plugin emits the .source typegen + content collections during build. +RUN bun run build + +FROM oven/bun:1-slim +WORKDIR /app +COPY --from=build /app/.output ./.output +USER bun +ENV PORT=3000 +EXPOSE 3000 +CMD ["bun", "run", ".output/server/index.mjs"] diff --git a/docs-site/content/docs/ci.md b/docs-site/content/docs/ci.md new file mode 100644 index 0000000..4a2dce0 --- /dev/null +++ b/docs-site/content/docs/ci.md @@ -0,0 +1,67 @@ +--- +title: "CI & Docker" +description: "Gitea Actions setup — workflows, the dockerized pieces, and the runners." +--- + +CI runs on **Gitea Actions** (`git.unom.io`, org `unom`). Three workflows in +`.gitea/workflows/`, two runners, three images in the Gitea container registry. + +## Workflows + +| Workflow | Trigger | Runner | What it does | +|---|---|---|---| +| `ci.yml` | push to `main`, PRs | `ubuntu-latest` | Rust workspace (fmt · clippy `-D warnings` · build · test · C-ABI harness · generated-header drift) inside the `punktfunk-rust-ci` image; `web/` and `docs-site/` build + typecheck in `oven/bun:1` | +| `docker.yml` | push to `main`, `v*` tags, manual | `ubuntu-latest` | Builds + pushes the three images below (`latest` + `sha-` tags) | +| `apple.yml` | push to `main`, PRs, manual | `macos-arm64` | Rust core → `PunktfunkCore.xcframework` → `swift build` + `swift test` in `clients/apple` | + +## Dockerized pieces + +The host and the native clients are intentionally **not** containerized (the host needs +the GPU/compositor stack of the box it runs on). What is: + +| Image | Source | Notes | +|---|---|---| +| `git.unom.io/unom/punktfunk-web` | `web/Dockerfile` (repo-root context — orval needs `docs/api/openapi.json`) | Nitro `bun` bundle; `PORT` (3000) and `PUNKTFUNK_MGMT_URL` env at runtime | +| `git.unom.io/unom/punktfunk-docs` | `docs-site/Dockerfile` | This site; `PORT` (3000) | +| `git.unom.io/unom/punktfunk-rust-ci` | `ci/rust-ci.Dockerfile` | Ubuntu 26.04 + FFmpeg 8/PipeWire/GL/GBM dev libs + a libcuda **link stub** (driver userspace, no kernel module) + pinned rustup — the container `ci.yml`'s Rust job runs in | + +Registry pushes authenticate with the repo Actions secret **`REGISTRY_TOKEN`** (a PAT +with `write:package`; the login username in `docker.yml` is the token owner, not the +push actor). + +## Runners + +- **`ubuntu-latest`** — the pre-existing Linux runner; runs the Rust/web/docs jobs (as + docker containers) and the image build+push jobs. +- **`macos-arm64`** — `home-mac-mini-1` (M-series, macOS 26), a **host-mode** + `act_runner` (upstream now ships it as `gitea-runner`) provisioned by + [`scripts/ci/setup-macos-runner.sh`](https://git.unom.io/unom/punktfunk/src/branch/main/scripts/ci/setup-macos-runner.sh): + rustup (+ both darwin targets for the universal xcframework), Node.js (host-mode runners + execute JS actions via `node` from PATH — nothing auto-provisions it), the runner binary + in `~/.local/bin`, state in `~/ci/act-runner/` (config, `.runner` registration, + `runner.log`), kept alive by the `io.gitea.act_runner` LaunchAgent. Needs full **Xcode** + for `xcodebuild -create-xcframework` (CLT alone only covers `swift build/test`); if + `xcode-select` still points at CLT, the script auto-detects `/Applications/Xcode*.app` + and bakes a `DEVELOPER_DIR` override into the LaunchAgent — no sudo required. + +Re-provisioning (idempotent) or first-time registration from a dev box: + +```sh +# token: org unom → Settings → Actions → Runners → Create new runner +ssh enricobuehler@192.168.1.135 GITEA_RUNNER_TOKEN= bash -s \ + < scripts/ci/setup-macos-runner.sh +``` + +## Troubleshooting + +- **Mac runner offline** — `ssh tail -50 '~/ci/act-runner/runner.log'`; restart with + `launchctl kickstart -k gui/$(id -u)/io.gitea.act_runner`. After a reboot with nobody + logged in, the LaunchAgent only starts once auto-login is enabled (or promote the plist + to a LaunchDaemon). +- **`apple.yml` fails at the xcframework step** — Xcode missing or unselected: + `sudo xcode-select -s /Applications/Xcode.app/Contents/Developer` and accept the license + (`sudo xcodebuild -license accept`), then re-run. +- **Rust job can't pull `punktfunk-rust-ci`** — the runner host's docker daemon needs a + `docker login git.unom.io` if the org/registry isn't anonymously readable. +- **Stale builder image after toolchain/dep changes** — `docker.yml` re-pushes it on every + `main` push; a manual `workflow_dispatch` of `docker.yml` forces a rebuild. diff --git a/docs-site/content/docs/meta.json b/docs-site/content/docs/meta.json index a8245d4..e63b9bd 100644 --- a/docs-site/content/docs/meta.json +++ b/docs-site/content/docs/meta.json @@ -7,12 +7,14 @@ "implementation-plan", "roadmap", "m2-plan", + "apple-stage2-presenter", "---Setup---", "linux-setup", "headless-box", "gnome-box", "bazzite", "windows-host", - "dualsense-haptics" + "dualsense-haptics", + "ci" ] } diff --git a/scripts/ci/setup-macos-runner.sh b/scripts/ci/setup-macos-runner.sh new file mode 100644 index 0000000..e6f2a1c --- /dev/null +++ b/scripts/ci/setup-macos-runner.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env bash +# Provision a Mac as the Gitea Actions runner for the Apple client CI +# (.gitea/workflows/apple.yml). Idempotent — safe to re-run. Run ON THE MAC, or from a +# dev box: +# +# ssh GITEA_RUNNER_TOKEN= bash -s < scripts/ci/setup-macos-runner.sh +# +# Installs: rustup (+ both darwin targets for the universal xcframework), Node.js (the +# runner executes JS actions like actions/checkout via `node` from PATH — host mode does +# not auto-provision it), the act_runner binary (host mode — jobs run directly on macOS, +# no containers), and a LaunchAgent that keeps the runner daemon alive. Registration only +# happens once (.runner file); the token is NOT persisted by this script. +# +# Env knobs: GITEA_INSTANCE (default https://git.unom.io), GITEA_RUNNER_TOKEN (required +# for first-time registration only), RUNNER_NAME (default: LocalHostName), RUNNER_LABELS +# (default macos-arm64:host — matches apple.yml's runs-on), ACT_RUNNER_VERSION, +# NODE_VERSION. +# +# NOT installed here: Xcode. swift build/test work with Command Line Tools, but +# scripts/build-xcframework.sh needs xcodebuild (-create-xcframework) from a full Xcode. +set -euo pipefail + +INSTANCE="${GITEA_INSTANCE:-https://git.unom.io}" +VERSION="${ACT_RUNNER_VERSION:-1.0.8}" +RUNNER_NAME="${RUNNER_NAME:-$(scutil --get LocalHostName)}" +LABELS="${RUNNER_LABELS:-macos-arm64:host}" +RUNNER_HOME="$HOME/ci/act-runner" +BIN_DIR="$HOME/.local/bin" +PLIST="$HOME/Library/LaunchAgents/io.gitea.act_runner.plist" + +# --- Rust toolchain (the xcframework is built from the Rust core) ----------------------- +if [ ! -x "$HOME/.cargo/bin/rustup" ]; then + echo "==> installing rustup" + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ + | sh -s -- -y --no-modify-path --profile minimal +fi +"$HOME/.cargo/bin/rustup" target add aarch64-apple-darwin x86_64-apple-darwin + +# --- Node.js (actions runtime; sudo-free tarball install) -------------------------------- +NODE_VERSION="${NODE_VERSION:-22.22.3}" +mkdir -p "$BIN_DIR" +if ! "$BIN_DIR/node" --version 2>/dev/null | grep -q "^v${NODE_VERSION}$"; then + echo "==> installing node v$NODE_VERSION" + NODE_DIR="$HOME/.local/node-v$NODE_VERSION" + mkdir -p "$NODE_DIR" + curl -fL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-darwin-arm64.tar.gz" \ + | tar -xz --strip-components=1 -C "$NODE_DIR" + ln -sf "$NODE_DIR/bin/node" "$BIN_DIR/node" +fi +"$BIN_DIR/node" --version + +# --- act_runner binary ------------------------------------------------------------------- +# Renamed upstream to "gitea-runner" as of 1.0 (dl.gitea.com/act_runner/ stops at 0.6.x); +# we keep the local name act_runner — the CLI surface is unchanged. +mkdir -p "$BIN_DIR" "$RUNNER_HOME" +if ! "$BIN_DIR/act_runner" --version 2>/dev/null | grep -q "$VERSION"; then + echo "==> installing act_runner (gitea-runner) $VERSION" + curl -fL "https://dl.gitea.com/gitea-runner/${VERSION}/gitea-runner-${VERSION}-darwin-arm64" \ + -o "$BIN_DIR/act_runner.tmp" + chmod +x "$BIN_DIR/act_runner.tmp" + mv "$BIN_DIR/act_runner.tmp" "$BIN_DIR/act_runner" +fi +"$BIN_DIR/act_runner" --version + +# --- config + one-time registration ------------------------------------------------------ +cd "$RUNNER_HOME" +[ -f config.yaml ] || "$BIN_DIR/act_runner" generate-config > config.yaml +if [ ! -f .runner ]; then + if [ -z "${GITEA_RUNNER_TOKEN:-}" ]; then + echo "ERROR: not registered yet — re-run with GITEA_RUNNER_TOKEN=" >&2 + echo " (org unom -> Settings -> Actions -> Runners -> Create new runner)" >&2 + exit 1 + fi + "$BIN_DIR/act_runner" register --no-interactive \ + --instance "$INSTANCE" \ + --token "$GITEA_RUNNER_TOKEN" \ + --name "$RUNNER_NAME" \ + --labels "$LABELS" +fi + +# --- LaunchAgent: keep the daemon alive across crashes and (GUI) logins ------------------ +# PATH must carry the CLT tools, cargo and act_runner itself; jobs inherit it. +# If the system developer dir is CLT-only but a full Xcode is installed, hand jobs a +# DEVELOPER_DIR override — the per-process equivalent of `xcode-select -s`, no sudo needed. +DEVELOPER_DIR_XML="" +if ! /usr/bin/xcodebuild -version >/dev/null 2>&1; then + for app in /Applications/Xcode.app /Applications/Xcode*.app; do + if DEVELOPER_DIR="$app/Contents/Developer" /usr/bin/xcodebuild -version >/dev/null 2>&1; then + DEVELOPER_DIR_XML="DEVELOPER_DIR$app/Contents/Developer" + echo "==> using full Xcode at $app via DEVELOPER_DIR" + break + fi + done +fi + +mkdir -p "$(dirname "$PLIST")" +cat > "$PLIST" < + + + + Labelio.gitea.act_runner + ProgramArguments + + $BIN_DIR/act_runner + daemon + --config + $RUNNER_HOME/config.yaml + + WorkingDirectory$RUNNER_HOME + EnvironmentVariables + + PATH + $HOME/.cargo/bin:$BIN_DIR:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin + HOME$HOME + $DEVELOPER_DIR_XML + + RunAtLoad + KeepAlive + StandardOutPath$RUNNER_HOME/runner.log + StandardErrorPath$RUNNER_HOME/runner.log + + +EOF + +UID_NUM="$(id -u)" +launchctl bootout "gui/$UID_NUM/io.gitea.act_runner" 2>/dev/null || true +if launchctl bootstrap "gui/$UID_NUM" "$PLIST" 2>/dev/null; then + echo "==> runner LaunchAgent bootstrapped (gui/$UID_NUM)" +else + # No GUI session (pure-SSH box, nobody logged in): land it in the user domain for now. + # For boot persistence without a GUI login, either enable auto-login for this user or + # promote the plist to a root-owned LaunchDaemon in /Library/LaunchDaemons (sudo). + launchctl bootout "user/$UID_NUM/io.gitea.act_runner" 2>/dev/null || true + launchctl bootstrap "user/$UID_NUM" "$PLIST" + echo "==> runner LaunchAgent bootstrapped (user/$UID_NUM — no GUI session)" + echo " NOTE: won't auto-start after reboot until auto-login is enabled or the" + echo " plist is promoted to a LaunchDaemon." +fi + +sleep 2 +tail -5 "$RUNNER_HOME/runner.log" 2>/dev/null || true + +if ! /usr/bin/xcodebuild -version >/dev/null 2>&1 && [ -z "$DEVELOPER_DIR_XML" ]; then + echo "WARNING: xcodebuild not usable (Command Line Tools only, no full Xcode found) —" + echo " apple.yml's xcframework step needs a full Xcode in /Applications, with" + echo " its license accepted once: sudo xcodebuild -license accept" +fi +echo "OK: runner '$RUNNER_NAME' labels=$LABELS instance=$INSTANCE" diff --git a/web/Dockerfile b/web/Dockerfile new file mode 100644 index 0000000..38a2dbf --- /dev/null +++ b/web/Dockerfile @@ -0,0 +1,29 @@ +# punktfunk management console — TanStack Start built with Bun, served by the Nitro `bun` +# preset bundle. Build context is the REPO ROOT (orval generates the API client from +# docs/api/openapi.json, referenced as ../docs/api/openapi.json from web/): +# +# docker build -f web/Dockerfile -t punktfunk-web . +# +# Runtime: PORT (default 3000) and PUNKTFUNK_MGMT_URL (upstream management API the Nitro +# server proxies /api to; see web/server/routes). +FROM oven/bun:1 AS build +WORKDIR /repo/web + +# Dependency layer: lockfile only, so source edits don't re-install. +# --ignore-scripts: the root `prepare` script runs codegen, which needs sources that +# aren't copied yet — `bun run build` regenerates everything below. +COPY web/package.json web/bun.lock ./ +RUN bun install --frozen-lockfile --ignore-scripts + +COPY docs/api/openapi.json /repo/docs/api/openapi.json +COPY web/ ./ +# prebuild runs orval (openapi → src/api/gen); the paraglide vite plugin compiles i18n. +RUN bun run build + +FROM oven/bun:1-slim +WORKDIR /app +COPY --from=build /repo/web/.output ./.output +USER bun +ENV PORT=3000 +EXPOSE 3000 +CMD ["bun", "run", ".output/server/index.mjs"]