on master help sign in
nix-container/flake.nix
199 lines 8.7 kB history raw
1{
2 description = "A Nix-built OCI image for Apple's `container` CLI";
4 inputs = {
5 nixpkgs.url = "github:NixOS/nixpkgs";
6 flake-utils.url = "github:numtide/flake-utils";
7 nur.url = "github:nix-community/NUR";
8 nix2container = {
9 url = "github:nlewo/nix2container";
10 inputs.nixpkgs.follows = "nixpkgs";
11 };
12 };
14 outputs =
15 {
16 self,
17 nixpkgs,
18 flake-utils,
19 nur,
20 nix2container,
21 }:
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
26 [
27 "aarch64-darwin"
28 "x86_64-darwin"
29 "aarch64-linux"
30 "x86_64-linux"
31 ]
32 (
33 system:
34 let
35 # Map the host system to the Linux system the container targets, so
36 # `nix run` on a Mac still produces a Linux image.
37 linuxSystem =
38 {
39 aarch64-darwin = "aarch64-linux";
40 x86_64-darwin = "x86_64-linux";
41 aarch64-linux = "aarch64-linux";
42 x86_64-linux = "x86_64-linux";
43 }
44 .${system};
46 arch =
47 {
48 aarch64-linux = "arm64";
49 x86_64-linux = "amd64";
50 }
51 .${linuxSystem};
53 pkgsLinux = import nixpkgs {
54 system = linuxSystem;
55 config.allowUnfree = true;
56 };
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 {
64 nurpkgs = pkgsLinux;
65 pkgs = pkgsLinux;
66 };
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 # Default image name (for `copyWith`); `c init` passes a per-directory
76 # name (`nix-container-<dir>`) to `copyWithShell` instead.
77 imageName = "nix-container";
78 imageTag = "latest";
80 # Build the OCI image (named `name`) from a `{ pkgs, nur }: [ ... ]`
81 # package function, for the named shell. The image's `cmd` is herdr
82 # (the session `c start` attaches to); the shell is the default `$SHELL`
83 # (what herdr panes spawn) and is added automatically, so `container.nix`
84 # lists only your extras. Nix (+ CA certs) and the ncurses terminfo
85 # database are always shipped too. Nothing here is fish-specific: the
86 # TERM fixup happens host-side in `c` (passed via `-e TERM` at create).
87 mkImage =
88 name: shell: packages:
89 n2c.buildImage {
90 inherit name;
91 tag = imageTag;
92 inherit arch;
94 # A plain, single-process root filesystem. No NixOS, no systemd.
95 # buildEnv only *symlinks* paths (never runs them), so build it on
96 # the host arch — the tree is arch-neutral and links the
97 # (substituted) Linux store paths, sparing the Linux builder.
98 copyToRoot = pkgsHost.buildEnv {
99 name = "root";
100 paths =
101 packages {
102 pkgs = pkgsLinux;
103 nur = nurPkgs;
104 }
105 # The shell (cmd + $SHELL), when it exists in nixpkgs.
106 ++ pkgsLinux.lib.optional (pkgsLinux ? ${shell}) pkgsLinux.${shell}
107 ++ [
108 pkgsLinux.nix
109 pkgsLinux.cacert
110 # Basic userland (ls, cat, cp, sleep, …) so the container is
111 # usable out of the box and tools that shell out don't fail.
112 pkgsLinux.coreutils-full
113 # Networking glue glibc needs: /etc/nsswitch.conf (+passwd,
114 # group) so DNS/host lookups resolve, and /etc/protocols,
115 # /etc/services. Without these, hostname resolution fails.
116 pkgsLinux.fakeNss
117 pkgsLinux.iana-etc
118 # The terminfo database (+ tput/clear/reset) so TUIs find the
119 # entry for whatever TERM `c` passes in and render correctly.
120 pkgsLinux.ncurses
121 # The session `c start` always launches.
122 pkgsLinux.herdr
123 ];
124 # Link all of /etc so the above files (nsswitch.conf, protocols,
125 # services, passwd, group, ssl certs) are present.
126 pathsToLink = [
127 "/bin"
128 "/etc"
129 ];
130 };
132 initializeNixDatabase = true;
134 config = {
135 # `cmd` (not `entrypoint`): the default command, *replaced* by any
136 # command passed to `container run/create`. Plain herdr here;
137 # `c init` overrides it with `herdr session attach <dir>-container` so
138 # the session is named per project. `c start` attaches via
139 # `container start -ai`; exiting herdr (PID1) stops the container.
140 cmd = [ "/bin/herdr" ];
141 env = [
142 "PATH=/bin"
143 "HOME=/root"
144 # Default shell for tools that spawn one (herdr panes, etc.).
145 "SHELL=/bin/${shell}"
146 # Marker so shells/scripts can detect they're in here.
147 "NIX_CONTAINER=1"
148 # Advertise truecolor for 24-bit-aware tools; `c` passes the
149 # resolved TERM via -e (the runtime would otherwise force xterm).
150 "COLORTERM=truecolor"
151 # A UTF-8 locale so tools handle multibyte/wide characters
152 # correctly. glibc ships C.UTF-8 built in, so this needs no
153 # locale-archive.
154 "LANG=C.UTF-8"
155 # CA trust for every TLS client (curl/git/…) and Nix.
156 "SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt"
157 "NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt"
158 # Make the bundled Nix usable: flake commands on, and
159 # single-user (no daemon/nixbld).
160 "NIX_CONFIG=experimental-features = nix-command flakes\nbuild-users-group =\nsandbox = false"
161 ];
162 };
163 };
165 # `nix run . -- init|start` runs the bundled `c` manager script. Its
166 # `init` builds the image from the project's `container.nix` (via
167 # `lib.copyWithShell`); the script finds the flake from its own (store)
168 # location, so this works from a checkout or straight from
169 # `nix run github:…/nix-container`.
170 cApp = pkgsHost.writeShellScriptBin "c" ''
171 exec ${pkgsHost.fish}/bin/fish ${self}/c "$@"
172 '';
173 in
174 {
175 apps = {
176 # nix run . -- init|start → build+load+create / start a container
177 default = flake-utils.lib.mkApp { drv = cApp; };
178 };
180 # Build helpers take a `{ pkgs, nur }: [ ... ]` packages function (the
181 # `packages` attr of a project's `container.nix`). `c init` uses
182 # `copyWithShell` (host shell baked in); `copyWith` is the generic form:
183 # (builtins.getFlake "github:…/nix-container")
184 # .lib.<system>.copyWith (import ./container.nix).packages
185 lib = {
186 inherit mkImage;
187 # Build the image's `copyTo` script (a skopeo-copy wrapper) from a
188 # `{ pkgs, nur }: [ ... ]` function, named `imageName`. Defaults the
189 # shell (cmd + $SHELL) to bash; use `copyWithShell` to pick another.
190 copyWith = packages: (mkImage imageName "bash" packages).copyTo;
191 # Like `copyWith`, but for the given image `name` and shell — `c init`
192 # passes `nix-container-<dir>` and the host login shell, which is added
193 # to the image and used as cmd + $SHELL without being listed in
194 # `container.nix`.
195 copyWithShell = name: shell: packages: (mkImage name shell packages).copyTo;
196 };
197 }
198 );