From 458a7c40d54497a08baf43a7aade8f0a1230aa1d Mon Sep 17 00:00:00 2001 From: Philip Peterson <1326208+philip-peterson@users.noreply.github.com> Date: Thu, 8 Oct 2026 05:15:25 -0800 Subject: [PATCH] Add build & CI guidelines Moved from the now-abandoned quine-os repo. Documents the Taskfile/ Dagger/Nix conventions already in use across swap-watchdog, quine-linux, quintodrome, and hyper, plus a simple identifying header for Taskfiles and Dagger files, and a researched note on how much of the cross-platform desktop exception can actually be collapsed into Dagger via cross- compilation. Co-Authored-By: Claude Sonnet 5 --- GUIDELINES.md | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 GUIDELINES.md diff --git a/GUIDELINES.md b/GUIDELINES.md new file mode 100644 index 0000000..337b2a4 --- /dev/null +++ b/GUIDELINES.md @@ -0,0 +1,177 @@ +# Quine OS: build & CI guidelines + +These guidelines describe how build tooling and CI should be structured +across Quine OS's apps and subsystems. They're drawn from, and should stay +consistent with, patterns already established in swap-watchdog, quine-linux, +quintodrome, and hyper. + +## 1. Every app gets a Taskfile + +Every standalone app (buildable, runnable, testable on its own) ships a +`Taskfile.yml` as its entrypoint — `task build`, `task install`, `task test`, +etc. Taskfile is the human-friendly front door; it is **not** where build +logic lives (see §3). Dev-only apps with no real build step just wrap the run +command directly (e.g. a Flutter app's Taskfile can be just +`flutter run -d linux` — nothing more needed). + +## 2. Build tooling preference order + +When an app needs a real build step, prefer in this order: + +1. **Nix, as a hermetic dependency-provisioning layer inside the Dagger + container** — not necessarily `nix build`/flakes, but `nix-shell -p + ` to pull build-time headers/toolchains into an ephemeral + container, while the actual compile still runs the language's native + builder (`go build`, `cargo build`, etc.). For example: Nix supplies + `gtk3-devel`/`dbus-devel`-equivalent packages inside the container, the + host's own Go toolchain is mounted in to avoid version drift against + go.mod, and `patchelf` retargets the ELF interpreter so the binary still + links against the host's real runtime libs. Nothing gets installed on + the host or layered into the image for a one-off build dependency. +2. **If Nix genuinely can't cover it, the next-most-hermetic sandbox** — + e.g. a `flatpak-builder` sandbox in place of Nix, when a dependency's + header/binding generation isn't well supported in nixpkgs. That's a + legitimate, documented exception, not drift. +3. **Never**: reaching for `rpm-ostree install`, `dnf`, or any host/image + mutation just to satisfy a build-time dependency. If a dependency + genuinely belongs in the OS image (not just at build time), that's a + deliberate rpm-ostree layering decision made separately, not a build + script shortcut. + +## 3. CI: Dagger owns the logic, Gitea Actions stays thin + +One Forgejo instance, Gitea Actions as the trigger, Dagger as the engine. +The workflow YAML should do as little as possible: + +```yaml +- uses: actions/checkout@v6 +- run: curl -fsSL https://dl.dagger.io/dagger/install.sh | BIN_DIR="$HOME/.local/bin" sh +- run: dagger call ci +``` + +All real logic — build steps, test invocation, lint — lives in `.dagger/` +(Go SDK), never inlined as shell in the workflow file. If a workflow step is +doing anything beyond checkout, installing the Dagger CLI, and one +`dagger call`, that's a signal the logic should move into the Dagger module +instead. + +## 4. Apps vs. OS subsystems: different rigor, same tools + +- **Apps**: full Taskfile, Dagger-driven CI as above, real test coverage + expected — they're independently buildable and testable, so there's no + excuse not to. +- **Larger OS subsystems** (not independently buildable/testable the way an + app is — e.g. quine-linux itself): still require a Taskfile, and CI + through *either* Dagger *or* a Taskfile-driven Gitea Actions job — not + mandatory Dagger. Some kind of test/smoke-check should exist, but it's + scoped to what's actually checkable (`bootc status`, image boots, a + precondition script), not full unit/integration coverage. +- **Privileged/hardware-bound operations are a legitimate reason to skip + Dagger entirely.** An ISO build needing root podman/buildah, + `bootc-image-builder --privileged`, a specific runner label, and tens of + GB of disk doesn't containerize cleanly inside Dagger's own container + model. Driving that build directly via Taskfile + CLI tools, with the + reasoning commented inline, is the right call — document the "why no + Dagger here" rather than treating it as a gap to close later. + +## 5. The cross-platform desktop exception + +Dagger's execution model is Linux/OCI containers. There is no Windows-container +execution mode in Dagger, and no native-macOS-app-building container mode +either. Running the Dagger *client* from a Windows or Mac host (which does +work) just dispatches into a Linux VM underneath (WSL2, Docker Desktop, +etc.) — that doesn't change the target you're building for. + +**But check how much of this is actually a container-engine limitation +before reaching for a native runner matrix — less than it looks like:** + +- **Windows: cross-compile from inside the Dagger Linux container first.** + Go cross-compiles to Windows trivially (`GOOS=windows`). Rust/Tauri + cross-compiles via [`cargo-xwin`](https://github.com/Jake-Shadle/xwin) + (MSVC target) or mingw-w64 (GNU target) — Tauri has first-class support + for this (`tauri build --runner cargo-xwin --target + x86_64-pc-windows-msvc`). Authenticode signing doesn't need `signtool` or + a Windows host either: `osslsigncode` signs PE/MSI/CAB files from Linux, + including against a real HSM-backed cert via PKCS11. Put together, this + likely covers the *entire* Windows leg (build + sign) inside Dagger, with + no native Windows runner at all — try this before assuming the matrix + approach quintodrome's `desktop.yml` currently uses is necessary. The one + soft spot is native Node module rebuilds (e.g. node-pty, for Hyper): + cross-compiling node-gyp addons to Windows from Linux is less reliable + per-dependency, so that leg specifically may still need a real Windows + runner — check it, don't assume either way. +- **macOS: don't cross-compile, and this isn't a Dagger limitation to wait + out.** `osxcross` exists, but it works by extracting Apple's SDK, and + Apple's EULA restricts that SDK to execution on Apple-branded hardware — + not something to adopt as org policy. More fundamentally, codesigning and + notarization need Apple's own tools (`codesign`, `notarytool`) talking to + Apple's signing infrastructure with real credentials — that's a + credentials/API constraint, not a sandboxing one, so no amount of + container tech (present or future) removes the need for a real Mac for + that step. Apple's `container` project (WWDC 2025, the subject of + [dagger/dagger#11205](https://github.com/dagger/dagger/issues/11205) on + adding it as a Dagger runtime) doesn't change this either: it runs + *Linux* containers natively on Apple Silicon (one lightweight VM per + container, via `Virtualization.framework`), which would make running + Dagger's existing Linux pipelines on a Mac host faster/more native — it + is not a way to build or sign macOS apps inside a container. Keep the + native macOS runner for this leg. +- **There's no "Dagger for Windows containers" to switch to, either.** + BuildKit (the engine Dagger is built on) added *experimental* Windows + container support in v0.13, but Dagger hasn't built Windows-container + execution on top of it, and general Windows-native CI platforms (Azure + DevOps, TeamCity) aren't containerized-pipeline-as-code tools the way + Dagger is — they're a different trade-off, not a drop-in replacement. + +So: cross-platform desktop apps (Quintodrome, Hyper) remain the exception to +"everything through Dagger" — but narrower than it first appears. Try to +collapse the Windows leg into Dagger via cross-compilation before matrixing +it onto a native `windows-latest` runner; keep the macOS leg (and whichever +Node-native-module rebuilds don't cross-compile cleanly) on real runners, +since that part isn't going away. Re-verify this against Dagger's current +docs before trusting it indefinitely — this is a fast-moving project — but +the macOS signing constraint specifically is structural, not a version gap. + +- Keep Dagger for whatever portion *is* containerizable — a Go/Node + server-side build, lint, or test job stays thin-Gitea-Actions-calls-Dagger + as normal, and per the above, try to pull the Windows build+sign leg in + here too rather than defaulting it onto a native runner. +- Whatever's left over (macOS always; Windows only for legs that genuinely + don't cross-compile) gets a real OS matrix in Gitea Actions (`macos-latest`, + `macos-13` for Intel, and `windows-latest` only if needed) with native + toolchain setup steps. This is allowed to be as "fat" as it needs to be, + since there's no Dagger path to thin it against. +- Mark this divergence explicitly in the workflow (a comment explaining why + Dagger isn't used there) so it reads as a deliberate, bounded exception + rather than unexplained drift from the Dagger-first rule. +- Electron apps follow the same shape: macOS signing/notarization and + whichever native per-arch module rebuilds don't cross-compile cleanly mean + that part of packaging CI stays on real runners, even once migrated off + GitHub Actions onto Forgejo. + +## 6. Mark Taskfiles and Dagger files as part of Quine OS + +Since apps and subsystems get forked/vendored/copied around (and some +started life as upstream forks on their own hosting), every `Taskfile.yml` +and every `.dagger/main.go` should carry a one-line header identifying it as +a Quine OS file. This is purely an identifying tag — not a license or +copyright notice — so it stays short and makes no legal claim. + +`Taskfile.yml`, as the first line: + +```yaml +# Part of Quine OS. See GUIDELINES.md in the quine-linux repo for conventions. +version: "3" +``` + +`.dagger/main.go`, as the leading comment above `package main` (ahead of +any file-specific build-rationale comment already there): + +```go +// Part of Quine OS. See GUIDELINES.md in the quine-linux repo for conventions. +package main +``` + +If a file already has a longer header comment explaining its own build +rationale (e.g. swap-watchdog's `.dagger/main.go`), add this as the first +line of that comment block rather than a second, separate block.