@@ -72,82 +72,99 @@
7272 # Host package set, for the wrapper app below.
7373 pkgsHost = nixpkgs.legacyPackages.${system};
7474
75− # Name/tag baked into the image's OCI config.
75+ # Default image name (for `copyWith`); `c init` passes a per-directory
76+ # name (`nix-container-<dir>`) to `copyWithShell` instead.
7677 imageName = "nix-container";
7778 imageTag = "latest";
7879
79− # Build the OCI image from a `{ pkgs, nur }: [ ... ]` package function.
80− # The default `cmd` is /bin/fish; `c init` runs your host login shell
81− # instead and adds it automatically (see `copyWithShell`). The warning
82− # below only catches an image left with no shell at all.
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).
8387 mkImage =
84− packages:
85− let
86− pkgList = packages {
87− pkgs = pkgsLinux;
88− nur = nurPkgs;
89− };
90− hasShell = builtins.any (
91− p:
92− builtins.elem (p.pname or "") [
93− "fish"
94− "bash"
95− "zsh"
96− ]
97− ) pkgList;
98− in
99− pkgsLinux.lib.warnIf (!hasShell)
100− "nix-container: the package set has no shell (fish/bash/zsh) — the container's command may not exist (`c init` runs your host shell)."
101− (
102− n2c.buildImage {
103− name = imageName;
104− tag = imageTag;
105− inherit arch;
88+ name: shell: packages:
89+ n2c.buildImage {
90+ inherit name;
91+ tag = imageTag;
92+ inherit arch;
10693
107− # A plain, single-process root filesystem. No NixOS, no systemd.
108− # buildEnv only *symlinks* paths (never runs them), so build it
109− # on the host arch — the tree is arch-neutral and links the
110− # (substituted) Linux store paths, sparing the Linux builder.
111− #
112− # Nix itself (+ CA certs) is always shipped, so every container
113− # can use Nix; `initializeNixDatabase` registers the baked store.
114− copyToRoot = pkgsHost.buildEnv {
115− name = "root";
116− paths = pkgList ++ [
117− pkgsLinux.nix
118− pkgsLinux.cacert
119− ];
120− pathsToLink = [
121− "/bin"
122− "/etc/ssl"
123− ];
124− };
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+ };
125131
126− initializeNixDatabase = true;
132+ initializeNixDatabase = true;
127133
128− config = {
129− # `cmd` (not `entrypoint`): it's the default command and is
130− # *replaced* by `container run … -- <cmd>`. An entrypoint would
131− # be prepended instead, so `run … -- /bin/sh` would become
132− # `/bin/bash /bin/sh` and fail with "cannot execute binary file".
133− cmd = [ "/bin/fish" ];
134− env = [
135− "PATH=/bin"
136− "HOME=/root"
137− # Marker so shells/scripts can detect they're in here.
138− "NIX_CONTAINER=1"
139− # Make the bundled Nix usable: TLS for substituters/flakes,
140− # flake commands on, and single-user (no daemon/nixbld).
141− "NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt"
142− "NIX_CONFIG=experimental-features = nix-command flakes\nbuild-users-group =\nsandbox = false"
143− ];
144− };
145− }
146− );
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+ };
147164
148165 # `nix run . -- init|start` runs the bundled `c` manager script. Its
149166 # `init` builds the image from the project's `container.nix` (via
150− # `lib.copyWith`); the script finds the flake from its own (store)
167+ # `lib.copyWithShell`); the script finds the flake from its own (store)
151168 # location, so this works from a checkout or straight from
152169 # `nix run github:…/nix-container`.
153170 cApp = pkgsHost.writeShellScriptBin "c" ''
@@ -160,24 +177,22 @@
160177 default = flake-utils.lib.mkApp { drv = cApp; };
161178 };
162179
163− # Build helpers. `c init` uses `copyWith` to honour a per-project
164− # `container.nix`:
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:
165183 # (builtins.getFlake "github:…/nix-container")
166− # .lib.<system>.copyWith (import ./container.nix)
184+ # .lib.<system>.copyWith (import ./container.nix).packages
167185 lib = {
168186 inherit mkImage;
169− # skopeo copy app (`skopeo copy nix:<image> "$@"`) for an image built
170− # from a `{ pkgs, nur }: [ ... ]` function.
171− copyWith = packages: (mkImage packages).copyTo;
172− # Like `copyWith`, but also adds the named shell package (when it
173− # exists in nixpkgs) so `c init` can run the host shell without it
174− # being listed in `container.nix`.
175− copyWithShell =
176− shell: packages:
177− (mkImage (
178− { pkgs, nur }:
179− packages { inherit pkgs nur; } ++ pkgs.lib.optional (pkgs ? ${shell}) pkgs.${shell}
180− )).copyTo;
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;
181196 };
182197 }
183198 );