@@ -0,0 +1,211 @@
2+ description = "A Nix-built OCI image for Apple's `container` CLI";
5+ nixpkgs.url = "github:NixOS/nixpkgs";
6+ flake-utils.url = "github:numtide/flake-utils";
7+ nur.url = "github:nix-community/NUR";
9+ url = "github:nlewo/nix2container";
10+ inputs.nixpkgs.follows = "nixpkgs";
22+ # Iterate over the systems you might run `nix run` from. The image
23+ # *contents* are always Linux; the system here only decides where the
24+ # `copyTo`/skopeo step executes (e.g. your Mac).
25+ flake-utils.lib.eachSystem
35+ # Map the host system to the Linux system the container targets, so
36+ # `nix run` on a Mac still produces a Linux image.
39+ aarch64-darwin = "aarch64-linux";
40+ x86_64-darwin = "x86_64-linux";
41+ aarch64-linux = "aarch64-linux";
42+ x86_64-linux = "x86_64-linux";
48+ aarch64-linux = "arm64";
49+ x86_64-linux = "amd64";
53+ pkgsLinux = import nixpkgs {
54+ system = linuxSystem;
55+ config.allowUnfree = true;
58+ # NUR for the container's Linux arch. Pass BOTH `pkgs` and `nurpkgs`
59+ # explicitly: otherwise the unset one defaults to `import <nixpkgs>`,
60+ # which fails in pure flake eval. `pkgs` builds the repo packages, so
61+ # give it `allowUnfree` (charmbracelet.crush is unfree); `nurpkgs` is
62+ # only NUR's own machinery and can stay the plain set.
63+ nurPkgs = import nur {
68+ # nix2container instantiated for the host system, so `copyTo` and its
69+ # skopeo run natively here while contents come from `pkgsLinux`.
70+ n2c = nix2container.packages.${system}.nix2container;
72+ # Host package set, for the wrapper app below.
73+ pkgsHost = nixpkgs.legacyPackages.${system};
75+ # skopeo writes the OCI archive's ref.name from the *destination*
76+ # string, not from the image config — so the wrapper app must spell
77+ # these out, else the archive loads as `untagged`.
78+ imageName = "nixos-container";
81+ # Fish prompt config, copied from smol-nix's mkSmol.nix. Installed at
82+ # /root/.config/fish/config.fish (fish reads $HOME/.config/fish).
83+ fishConfig = pkgsLinux.writeText "config.fish" ''
87+ # Define the prompt as a function so fish renders it before every
88+ # command; at top level it would run once at startup and be ignored.
90+ set -l nix_shell_info
91+ if set -q IN_NIX_SHELL; or set -q IN_NIX_RUN
92+ set nix_shell_info ' ❄️'
95+ set -l cwd (prompt_pwd)
98+ if jj root 2>/dev/null >/dev/null
99+ set -l jj_bookmark (jj log -r 'heads(::@ & bookmarks())' -T 'local_bookmarks ++ "\n"' --no-graph 2>/dev/null | tail -1)
100+ if test -n "$jj_bookmark"
101+ set bookmark_info ' ' (set_color brmagenta) $jj_bookmark (set_color normal)
105+ echo -n -s (set_color $fish_color_cwd) $cwd (set_color normal) $bookmark_info ' 🏗️' $nix_shell_info '> '
109+ # Lay the config out at the path fish reads.
110+ fishRoot = pkgsLinux.runCommand "fish-config-root" { } ''
111+ mkdir -p "$out/root/.config/fish"
112+ cp ${fishConfig} "$out/root/.config/fish/config.fish"
115+ image = n2c.buildImage {
120+ # A plain, single-process root filesystem. No NixOS, no systemd.
121+ copyToRoot = pkgsLinux.buildEnv {
123+ paths = with pkgsLinux; [
134+ nurPkgs.repos.charmbracelet.crush
162+ # C toolchain — rustc links final binaries through `cc`/`ld`.
163+ gcc # provides cc/gcc; pulls in binutils (ld) via its closure
171+ initializeNixDatabase = true;
174+ # `cmd` (not `entrypoint`): it's the default command and is
175+ # *replaced* by `container run … -- <cmd>`. An entrypoint would be
176+ # prepended instead, so `run … -- /bin/sh` would become
177+ # `/bin/bash /bin/sh` and fail with "cannot execute binary file".
178+ cmd = [ "/bin/fish" ];
181+ # fish reads its prompt from $HOME/.config/fish/config.fish.
187+ # `nix run . -- <path>` writes an OCI archive tagged
188+ # <imageName>:<imageTag>. The raw `image.copyTo` is a passthrough
189+ # (`skopeo copy nix:<image> "$@"`), so an `oci-archive:<path>` with no
190+ # ref loads as `untagged`; this bakes the ref into the destination.
191+ copyToOCI = pkgsHost.writeShellApplication {
192+ name = "copy-to-oci";
194+ out="''${1:-image.tar}"
195+ exec ${image.copyTo}/bin/copy-to "oci-archive:$out:${imageName}:${imageTag}"
201+ # nix run . -- <path> → OCI archive tagged <name>:<tag>,
202+ # loadable with `container image load -i <path>`.
203+ default = flake-utils.lib.mkApp { drv = copyToOCI; };
205+ # Generic skopeo passthrough for any other destination, e.g.
206+ # nix run .#copyTo -- docker://ghcr.io/me/img:latest
207+ copyTo = flake-utils.lib.mkApp { drv = image.copyTo; };