Files
thoughtsync/ci-requirements.md
T
bvandeusenandClaude Opus 5 8b6dfab3a7 ci-requirements: the two things that cost a cycle each to rediscover
`git push origin dev` fails outright now that the rolling channel put a TAG
named `dev` beside the branch, and the error names neither. And nothing in CI
lints the packaging shell scripts, so a broken installer surfaces when a user
runs it rather than when it's built — record how to check them locally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MKsUY9Z45KQd34V956hZ9Q
2026-07-27 23:04:41 -04:00

9.5 KiB
Raw Blame History

CI Requirements — ThoughtSync

Spec lives in docs/process.md in the CI-Runner repo.

Runtime image

git.fabledsword.com/bvandeusen/ci-python:3.14

Selected via container.image (not a runs-on label) on all four jobs in .forgejo/workflows/ci.yml: typecheck (Vue/TS), lint (ruff), test (pytest), build (docker buildx).

Image deps used

  • python 3.12+ (the runtime Dockerfile targets python:3.12-slim; tests run on the image's 3.14 — both >=3.12, so results stay representative)
  • node 24 — npm ci + vue-tsc in the typecheck job, and the frontend builder stage inside the production Dockerfile. (Also required by the JS-based actions/checkout action — a Node-less runner fails every job at checkout.)
  • ruff — lint job runs ruff check src/ with zero install overhead
  • uv — test job creates the venv (uv venv /opt/venv) and installs the package with dev deps
  • docker CLI + buildx — build job pushes the dev/release image to the Forgejo registry

Per-job tool installs

Nothing installed at job time beyond what the image provides — all four jobs run entirely on ci-python:3.14.

Notes

  • No actions/cache. Deliberately omitted for npm/uv: it's a GitHub-fetched JS action and on a cold runner concurrent jobs race fetching it. We lean on the pinned ci-python image's pre-installed toolchain instead; npm ci / uv pip install cold cost is a non-blocker.
  • Build gates on typecheck + lint only. The test job runs in parallel for visibility but does not block the dev image push. DB-backed / integration tests run against the dev image manually — ThoughtSync's unit tests are DB-free (no Postgres service lane in CI yet).
  • dev push -> :dev + :<sha>; v* tag -> :latest + :<version> + :<sha> (family rule 46).
  • The production runtime Dockerfile tracks python:3.12 so test results stay representative of the deployed image.

Desktop (Tauri) lane — separate workflow

The Tauri desktop client (desktop/) builds in its own workflow, .forgejo/workflows/desktop.yml, NOT in ci.yml — it's a heavy Rust + AppImage build (~2040 min) that should only run on desktop/** changes, not on every backend/frontend push.

  • Image: git.fabledsword.com/bvandeusen/ci-tauri:1.97 (Rust + Node + WebKitGTK 4.1 + Tauri v2 Linux deps + tauri-cli). Selected via container.image; runs-on: python-ci is only a scheduling label.
  • Steps: build the shared frontend (embedded by generate_context!) → cargo tauri icon app-icon.png (platform icon set from the committed 1024px source) → cargo fmt --checkcargo clippy -D warningscargo testcargo tauri build (produces .deb + .AppImage) → de-bundle the AppImage's graphics libs → verify the .deb → repackage for pacman.
  • APPIMAGE_EXTRACT_AND_RUN=1 is set: AppImage tooling FUSE-mounts by default and CI containers have no /dev/fuse.
  • Packaging tools used from the image (none installed at job time, rule 5): dpkg-deb / dpkg-query / apt-cache and dpkg-shlibdeps (from dpkg-dev, pulled in by build-essential) for desktop/packaging/deb/verify.sh; tar + a compressor for desktop/packaging/arch/package-prebuilt.sh. Both scripts degrade gracefully rather than hard-failing on an absent optional tool: bsdtar (libarchive-tools) is used for the pacman package's .MTREE when present and skipped when not, compression falls back zstd → xz → gzip, and the .deb clean-container install test runs only if a docker CLI is available. Run 2872 confirmed all three optional tools are ABSENT today, so the current build takes every fallback: the pacman package ships as .pkg.tar.xz with no .MTREE, and the .deb clean-container install test is skipped. All three are functional outcomes — pacman installs an .xz package fine, and only pacman -Qkk file verification needs .MTREE. Adding libarchive-tools + zstd + a docker CLI to ci-tauri would upgrade these paths; none of them block a green build.
  • libssl-dev + pkg-config are load-bearing (both already in ci-tauri). Since M10.6 the desktop crate depends on reqwest with the native-tls backend, which on Linux compiles against OpenSSL. Do NOT drop either package from ci-tauri in a future slim-down — the Rust build fails at openssl-sys. (They're part of Tauri's own documented Linux prerequisites, so they should stay regardless.)
  • libssl3 is covered transitively, on purpose — don't "fix" it. Since M10.6 dpkg-shlibdeps lists libssl3 among the binary's needs, but the .deb declares only libwebkit2gtk-4.1-0 + libgtk-3-0. verify.sh passes it because webkit's own recursive dependency closure includes OpenSSL, so apt installs it either way. Declaring it explicitly would be worse: the package name is release-dependent (libssl3 on bookworm, libssl3t64 after the 64-bit-time_t transition in trixie/Ubuntu 24.04), so a hardcoded name freezes the package to the build distro. Leaning on webkit's closure adapts. If webkit ever stops pulling OpenSSL, verify.sh fails the build loudly — that guard is what makes the indirection safe.
  • Not verifiable in CI: the runner is Debian, so the pacman package cannot be pacman -U-tested here. That step logs .PKGINFO + the full file listing so the package is auditable from the run log; a real Arch install is the operator's confirm.

Windows lane — second job, second image

desktop.yml also runs a windows job that cross-compiles the NSIS installer.

  • Image: git.fabledsword.com/bvandeusen/ci-tauri-win:1.97 (Rust + Node + cargo-xwin + LLVM/lld + NSIS). A separate image from ci-tauri per CI-Runner's docs/process.md fork rule — the MSVC CRT/SDK cache alone is >1 GB. Its pins are held in lockstep with ci-tauri; bump them together, since both lanes compile the same source.
  • Why cross-compile: there is no Windows build host, and a Windows container cannot run on a Linux host (containers share the host kernel). cargo-xwin, lld-link and makensis are Linux programs that emit Windows PE output.
  • NSIS only. .msi requires WiX v3, a Windows program — per Tauri, ".msi installers can only be created on Windows."
  • Separate job on purpose: a Windows failure must not block the Linux artifacts, which are the primary product today.
  • Weakest verification of any lane. Tauri documents this path as "not as straight forward as compiling on Windows directly and is not tested as much", to be used "only as a last resort" — and a Linux runner cannot execute a Windows binary. Green means it built. A real Windows machine check is mandatory before trusting a release.
  • Unsigned. Installers will trip SmartScreen until a code-signing certificate exists; that is a purchasing decision, not a CI one.
  • TLS backend is chosen for this lane's sake. The desktop crate pins reqwest to native-tls, which on x86_64-pc-windows-msvc resolves to schannel — pure-Rust bindings to the OS TLS stack. That keeps C/assembly out of the cross-compile entirely. Switching to rustls would pull in ring/aws-lc-rs and their assembler, which is exactly the class of dependency that broke this lane before (libsqlite3-sysllvm-lib). Treat a TLS-backend change as a change to this lane, not just a dependency bump.
  • No Postgres lane (unchanged): the desktop app's local store + sync behavior is verified on the operator's machine, not in CI.

Formatting the Rust lane before pushing

cargo fmt --check runs in CI and had failed on four consecutive desktop pushes by itself, each costing a full cycle to learn a whitespace nit. There is no Rust toolchain on the workstation (rule 10), but the CI image is pullable, and running a formatter is neither a test run nor a local stack:

docker run --rm --user "$(id -u):$(id -g)" -e CARGO_HOME=/tmp/cargo \
  -v "$PWD/desktop/src-tauri:/w" -w /w \
  git.fabledsword.com/bvandeusen/ci-tauri:1.97 cargo fmt --check

Drop --check to apply. --user keeps the container from leaving root-owned files behind; CARGO_HOME points somewhere writable for that user.

Don't infer formatting from existing code. Several lines in local/store.rs exceed 100 characters and survive only because rustfmt cannot break a string literal — copying that shape caused one of the four failures.

Pushing: dev is both a branch and a tag

git push origin dev fails in this repo:

error: src refspec dev matches more than one

The rolling update channel is a release on a fixed tag named dev (the tag never moves — Forgejo has no /releases/latest/download/<asset> route, so the updater needs a permanent URL). Once that tag is fetched locally, the short name dev resolves to both refs/heads/dev and refs/tags/dev. Fully qualify it:

git push origin refs/heads/dev:refs/heads/dev

Shell scripts have no CI lane

Nothing lints desktop/packaging/*.sh, and a broken installer or publish script fails at the moment a user runs it, not in a build. Check them before pushing — install.sh is POSIX sh, the rest are bash:

dash -n desktop/packaging/install.sh          # or: sh -n
bash -n desktop/packaging/publish-release.sh

Where a script resolves URLs from the Forgejo API, exercise the resolution against the live instance (plain curl reads, no install) rather than trusting the regex by eye. Both channel paths in install.sh were verified that way.