// A Dagger module that builds a reproducible Guix installation image // inside a container (x86_64 ISO by default; pass --arch for arm64). // // Edit channels.scm to change which Guix commit is used, then run: // // dagger call build export --path=./out // dagger call build --arch=aarch64 export --path=./out package main import ( "context" "dagger/guix-iso/internal/dagger" ) const guixVersion = "1.5.0" // platformFor maps a Guix system string to the Dagger container platform. var platformFor = map[string]dagger.Platform{ "x86_64": "linux/amd64", "aarch64": "linux/arm64", } // setupScript runs after the tarball is extracted; it creates the build // users and authorizes the substitute servers. It has no dependency on // channels.scm, so Dagger caches it as a layer. const setupScript = ` set -euo pipefail groupadd --system guixbuild for i in $(seq -w 1 10); do useradd -g guixbuild -G guixbuild -d /var/empty -s /bin/false \ -c "Guix build user $i" --system "guixbuilder$i" done GUIX=/var/guix/profiles/per-user/root/current-guix/bin/guix "$GUIX" archive --authorize < /var/guix/profiles/per-user/root/current-guix/share/guix/bordeaux.guix.gnu.org.pub "$GUIX" archive --authorize < /var/guix/profiles/per-user/root/current-guix/share/guix/ci.guix.gnu.org.pub ` type GuixIso struct{} func normalizeArch(arch string) string { if arch == "" { return "x86_64" } return arch } // setup builds a container with Guix installed for the given architecture // (daemon not started). func setup(arch string) *dagger.Container { tarball := "guix-binary-" + guixVersion + "." + arch + "-linux.tar.xz" return dag.Container(dagger.ContainerOpts{Platform: platformFor[arch]}). From("debian:bookworm-slim"). WithEnvVariable("DEBIAN_FRONTEND", "noninteractive"). WithExec([]string{"apt-get", "update"}). WithExec([]string{"apt-get", "install", "-y", "--no-install-recommends", "curl", "ca-certificates", "xz-utils", "gnupg", "passwd", "netbase", "git", "python3"}). WithExec([]string{"curl", "-fsSL", "-o", "/tmp/" + tarball, "https://ftp.gnu.org/gnu/guix/" + tarball}). WithExec([]string{"tar", "-C", "/", "--warning=no-timestamp", "-xf", "/tmp/" + tarball}). WithExec([]string{"bash", "-c", setupScript}) } // buildContainer runs the full pull + image build. func buildContainer(arch string) *dagger.Container { src := dag.CurrentModule().Source() return setup(arch). WithMountedDirectory("/workspace", src). WithExec([]string{"bash", "/workspace/pull.sh"}, dagger.ContainerWithExecOpts{InsecureRootCapabilities: true}). WithExec([]string{"bash", "/workspace/image.sh", arch}, dagger.ContainerWithExecOpts{InsecureRootCapabilities: true}) } // Build assembles the Guix installation image and returns it as a directory // containing guix-install--linux.. func (m *GuixIso) Build( ctx context.Context, // Architecture of the image: "x86_64" or "aarch64". // +optional arch string, ) *dagger.Directory { arch = normalizeArch(arch) return buildContainer(arch).Directory("/out") } // Debug returns the setup container for inspection without running the build. func (m *GuixIso) Debug( // +optional arch string, ) *dagger.Container { return setup(normalizeArch(arch)) }