This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
snosi is a bootable container image build system using mkosi to produce Debian Trixie-based immutable OS images and system extensions (sysexts). Images are deployed via bootc/systemd-boot with atomic updates.
Outputs: 4 OCI desktop images (snow, snowloaded, snowfield, snowfieldloaded), 2 OCI server images (cayo, cayoloaded), and 10 sysext overlay images (1password-cli, code-server, debdev, dev, docker, himmelblau, incus, nix, podman, tailscale).
Requires: mkosi v24+, just, root/sudo access.
just # List targets
just sysexts # Build base + all 10 sysexts
just snow # Build snow desktop image
just snowloaded # Build snowloaded variant
just snowfield # Build snowfield (Surface kernel)
just snowfieldloaded # Build snowfieldloaded variant
just cayo # Build cayo server image
just cayoloaded # Build cayoloaded variant
just clean # Remove build artifacts
just test-install # Run bootc install test
just run-qemu # Run image in QEMUAll just targets run mkosi clean first (clean build every time).
mkosi configs use Include= directives to compose reusable fragments. The composition chain:
mkosi.conf(root) declares base + sysext dependenciesmkosi.images/contains base image and sysext definitionsmkosi.profiles/defines desktop and server image variantsshared/contains reusable fragments: kernel configs, package sets, output format, scripts
Each profile composes: package sets + kernel variant + output format + build/postinstall/finalize/postoutput scripts.
Scripts execute in order: BuildScripts (in chroot) -> PostInstallationScripts (after packages) -> FinalizeScripts (pre-output) -> PostOutputScripts (after image creation).
/usr/- Read-only. All binaries and libraries must live here./etc/- Overlay on/usr/etc. Base configs in image, user changes persist./var/- Persistent, writable. State, logs, container storage./opt/- Bind mount to/var/opt. Writable at runtime but shadowed by sysext overlays.
Critical pattern: Packages installing to /opt must be relocated to /usr/lib/<package> at build time with symlinks in /usr/bin. This applies to both desktop images and sysexts.
Sysexts can ONLY provide files under /usr. They cannot modify /etc or /var at runtime. Configs needed in /etc must be:
- Captured to
/usr/share/factory/etcduring build (viamkosi.finalize) - Injected at boot via systemd-tmpfiles
Every sysext must have matching <name>.transfer and <name>.feature files in mkosi.images/base/mkosi.extra/usr/lib/sysupdate.d/. The .transfer file defines how systemd-sysupdate downloads the sysext; the .feature file provides metadata and defaults to Enabled=false. Use existing files as templates.
Service activation in sysexts: Do NOT rely on WantedBy=multi-user.target + preset alone. At boot, the sysext is not yet merged when PID 1 scans units — the .wants/ symlink is dangling and silently dropped. Always ship a usr/lib/systemd/system/multi-user.target.d/10-<name>.conf drop-in inside the sysext with [Unit]\nUpholds=<name>.service. This drop-in is new to systemd after the post-merge daemon-reload, so activation fires correctly. The preset is still required for enabled state; the drop-in handles timing.
The shared sysext postoutput script (shared/sysext/postoutput/sysext-postoutput.sh) handles versioned naming and manifest processing. It requires the KEYPACKAGE env var set in each sysext's mkosi.conf.
shared/download/- Verified download system:checksums.jsonpins URLs+SHA256s,verified-download.shprovides theverified_download()helpershared/kernel/- Kernel configs (backports, surface, stock) and dracut scriptsshared/packages/- Package set definitions, some with postinstall scripts for relocationshared/outformat/image/- Image output format config (directory), finalize scripts, andbuildah-package.shfor OCI packagingshared/sysext/postoutput/- Shared sysext postoutput logicmkosi.sandbox/etc/apt/- External APT repo configs (Docker, Incus, linux-surface, Frostyard)
- Use
set -euo pipefailat the top of all scripts - Build scripts running in chroot use
.chrootextension - External downloads must go through
verified_download()with entries inchecksums.json - Pin external URLs to specific versions/commits, never
latestor branch names - When adding a new verified download, also add a corresponding update check to
.github/workflows/check-dependencies.yml
systemctl --user enable does not work inside a mkosi chroot (no user session/D-Bus). System services are enabled via systemctl enable in snow.postinst.chroot, but user services require manually creating symlinks:
mkdir -p /etc/systemd/user/<target>.wants
ln -sf /usr/lib/systemd/user/<service> /etc/systemd/user/<target>.wants/<service>The target (e.g. gnome-session.target) comes from the service's WantedBy= in its [Install] section.
Known issue: deb-systemd-helper creates .dsh-also tracking files in /var/lib/systemd/deb-systemd-user-helper-enabled/ during the build but may not create the actual enablement symlinks in /etc/systemd/user/. If a user service isn't auto-starting after reboot, check whether its symlink is missing from /etc/systemd/user/<target>.wants/ and compare against its .dsh-also file. There may be other services with this same gap that haven't been noticed yet.
build.yml- Builds base + sysexts, publishes to Frostyard repo (Cloudflare R2)build-images.yml- Matrix build of 6 profiles (4 desktop + 2 server), pushes OCI to ghcr.io, generates SBOMs (Syft), attaches via ORAS, signs with Cosign. A non-blockingreleasejob runs after the matrix on main-branch pushes and creates a GitHub Release whose body is a changelog generated byfrostyard/changelog-generatordiffing the newsnowloadedimage against the previously published one.check-dependencies.yml- Weekly check for external dependency updates, creates PRs with updated checksumscheck-packages.yml- Daily check for APT package version updates, creates PRsvalidate.yml- shellcheck + mkosi summary validation on PRstest-install.yml- Manual bootc installation test in QEMU/KVMscorecard.yml- Weekly OpenSSF supply-chain security analysis
update documentation After any change to source code, update relevant documentation in CLAUDE.md, README.md and the yeti/ folder. A task is not complete without reviewing and updating relevant documentation.
yeti/ directory The yeti/ directory contains documentation written for AI consumption and context enhancement, not primarily for humans. Jobs like doc-maintainer and issue-worker instruct the AI to read yeti/OVERVIEW.md and related files for codebase context before performing tasks. Write content in this directory to be maximally useful to an AI agent understanding the codebase — detailed architecture, patterns, and decision rationale rather than user-facing guides.