feat(ci): Gitea Actions — dockerized web/docs/rust-ci images, Apple client CI, Mac runner
apple / swift (push) Failing after 3s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / web (push) Has been cancelled
ci / rust (push) Has been cancelled
apple / swift (push) Failing after 3s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / web (push) Has been cancelled
ci / rust (push) Has been cancelled
Three workflows: ci.yml (Rust workspace inside the punktfunk-rust-ci builder image + web/docs-site build+typecheck), docker.yml (build+push punktfunk-web, punktfunk-docs, punktfunk-rust-ci to git.unom.io — host and native clients stay un-dockerized by design), apple.yml (host-mode macos-arm64 runner: Rust core -> PunktfunkCore.xcframework -> swift build + swift test). ci/rust-ci.Dockerfile: Ubuntu 26.04 with the workspace's link deps (FFmpeg 8, PipeWire, Opus, GL/EGL/GBM, xkbcommon, libcuda via the 580-server userspace as a link stub) + pinned rustup + node for the JS actions. Verified end to end in-container: build, 141/141 tests, C ABI harness; all three images seeded to the registry manually. scripts/ci/setup-macos-runner.sh provisions the Mac (rustup + darwin targets, Node tarball, gitea-runner 1.0.8 host mode, LaunchAgent with DEVELOPER_DIR auto-detect for sudo-free Xcode selection). Docs in docs-site/content/docs/ci.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
+73
-11
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
.output
|
||||
dist
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
@@ -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"]
|
||||
@@ -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-<short>` 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=<token> bash -s \
|
||||
< scripts/ci/setup-macos-runner.sh
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Mac runner offline** — `ssh <mac> 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.
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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 <mac> GITEA_RUNNER_TOKEN=<registration 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=<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="<key>DEVELOPER_DIR</key><string>$app/Contents/Developer</string>"
|
||||
echo "==> using full Xcode at $app via DEVELOPER_DIR"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$PLIST")"
|
||||
cat > "$PLIST" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key><string>io.gitea.act_runner</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>$BIN_DIR/act_runner</string>
|
||||
<string>daemon</string>
|
||||
<string>--config</string>
|
||||
<string>$RUNNER_HOME/config.yaml</string>
|
||||
</array>
|
||||
<key>WorkingDirectory</key><string>$RUNNER_HOME</string>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>$HOME/.cargo/bin:$BIN_DIR:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
||||
<key>HOME</key><string>$HOME</string>
|
||||
$DEVELOPER_DIR_XML
|
||||
</dict>
|
||||
<key>RunAtLoad</key><true/>
|
||||
<key>KeepAlive</key><true/>
|
||||
<key>StandardOutPath</key><string>$RUNNER_HOME/runner.log</string>
|
||||
<key>StandardErrorPath</key><string>$RUNNER_HOME/runner.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
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"
|
||||
@@ -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"]
|
||||
Reference in New Issue
Block a user