@@ -72,53 +72,22 @@
72 # Host package set, for the wrapper app below.
72 # Host package set, for the wrapper app below.
73 pkgsHost = nixpkgs.legacyPackages.${system};
73 pkgsHost = nixpkgs.legacyPackages.${system};
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.
76 imageName = "nix-container";
77 imageName = "nix-container";
77 imageTag = "latest";
78 imageTag = "latest";
79− # Fish prompt config. Installed at /etc/fish/prompt.fish and loaded via
80− # `fish --no-config --init-command` (see `cmd` below), NOT from
81− # $HOME/.config/fish — so the `~/.config` mount can't shadow it. Built
82− # on the host arch (just a text file), so no Linux builder is needed.
83− fishConfig = pkgsHost.writeText "prompt.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 prompt out at the path the cmd sources.
110− fishRoot = pkgsHost.runCommand "fish-prompt" { } ''
111− mkdir -p "$out/etc/fish"
112− cp ${fishConfig} "$out/etc/fish/prompt.fish"
115− # Build the OCI image from a `{ pkgs, nur }: [ ... ]` package function.
80+ # Build the OCI image (named `name`) from a `{ pkgs, nur }: [ ... ]`
116− # The image always runs /bin/fish; fish, its prompt config, and Nix are
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`
117− # bundled automatically, so `container.nix` lists only your extras.
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).
118 mkImage =
87 mkImage =
119− packages:
88+ name: shell: packages:
120 n2c.buildImage {
89 n2c.buildImage {
121− name = imageName;
90+ inherit name;
122 tag = imageTag;
91 tag = imageTag;
123 inherit arch;
92 inherit arch;
@@ -126,10 +95,6 @@
126 # buildEnv only *symlinks* paths (never runs them), so build it on
95 # buildEnv only *symlinks* paths (never runs them), so build it on
127 # the host arch — the tree is arch-neutral and links the
96 # the host arch — the tree is arch-neutral and links the
128 # (substituted) Linux store paths, sparing the Linux builder.
97 # (substituted) Linux store paths, sparing the Linux builder.
130− # Always shipped: fish (the cmd) + its prompt, and Nix (+ CA certs)
131− # so every container can use Nix; `initializeNixDatabase` registers
133 copyToRoot = pkgsHost.buildEnv {
98 copyToRoot = pkgsHost.buildEnv {
134 name = "root";
99 name = "root";
@@ -137,19 +102,27 @@
137 pkgs = pkgsLinux;
102 pkgs = pkgsLinux;
138 nur = nurPkgs;
103 nur = nurPkgs;
105+ # The shell (cmd + $SHELL), when it exists in nixpkgs.
106+ ++ pkgsLinux.lib.optional (pkgsLinux ? ${shell}) pkgsLinux.${shell}
143 pkgsLinux.nix
108 pkgsLinux.nix
144 pkgsLinux.cacert
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
145 # Networking glue glibc needs: /etc/nsswitch.conf (+passwd,
113 # Networking glue glibc needs: /etc/nsswitch.conf (+passwd,
146 # group) so DNS/host lookups resolve, and /etc/protocols,
114 # group) so DNS/host lookups resolve, and /etc/protocols,
147 # /etc/services. Without these, hostname resolution fails.
115 # /etc/services. Without these, hostname resolution fails.
148 pkgsLinux.fakeNss
116 pkgsLinux.fakeNss
149 pkgsLinux.iana-etc
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.
121+ # The session `c start` always launches.
151 # Link all of /etc so the above files (nsswitch.conf, protocols,
124 # Link all of /etc so the above files (nsswitch.conf, protocols,
152− # services, passwd, group, ssl certs, fish prompt) are present.
125+ # services, passwd, group, ssl certs) are present.
153 pathsToLink = [
126 pathsToLink = [
@@ -160,20 +133,25 @@
161 config = {
134 config = {
162 # `cmd` (not `entrypoint`): the default command, *replaced* by any
135 # `cmd` (not `entrypoint`): the default command, *replaced* by any
163− # command passed to `container run/create`. `--no-config` skips
136+ # command passed to `container run/create`. Plain herdr here;
164− # $HOME/.config/fish (so a mounted ~/.config can't override the
137+ # `c init` overrides it with `herdr session attach <dir>-container` so
165− # prompt); `--init-command` loads the bundled prompt instead.
138+ # the session is named per project. `c start` attaches via
139+ # `container start -ai`; exiting herdr (PID1) stops the container.
166− cmd = [
140+ cmd = [ "/bin/herdr" ];
170− "source /etc/fish/prompt.fish"
173 "PATH=/bin"
142 "PATH=/bin"
174 "HOME=/root"
143 "HOME=/root"
144+ # Default shell for tools that spawn one (herdr panes, etc.).
145+ "SHELL=/bin/${shell}"
175 # Marker so shells/scripts can detect they're in here.
146 # Marker so shells/scripts can detect they're in here.
176 "NIX_CONTAINER=1"
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
177 # CA trust for every TLS client (curl/git/…) and Nix.
155 # CA trust for every TLS client (curl/git/…) and Nix.
178 "SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt"
156 "SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt"
179 "NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt"
157 "NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt"
@@ -186,7 +164,7 @@
187 # `nix run . -- init|start` runs the bundled `c` manager script. Its
165 # `nix run . -- init|start` runs the bundled `c` manager script. Its
188 # `init` builds the image from the project's `container.nix` (via
166 # `init` builds the image from the project's `container.nix` (via
189− # `lib.copyWith`); the script finds the flake from its own (store)
167+ # `lib.copyWithShell`); the script finds the flake from its own (store)
190 # location, so this works from a checkout or straight from
168 # location, so this works from a checkout or straight from
191 # `nix run github:…/nix-container`.
169 # `nix run github:…/nix-container`.
192 cApp = pkgsHost.writeShellScriptBin "c" ''
170 cApp = pkgsHost.writeShellScriptBin "c" ''
@@ -199,15 +177,22 @@
199 default = flake-utils.lib.mkApp { drv = cApp; };
177 default = flake-utils.lib.mkApp { drv = cApp; };
202− # Build helpers. `c init` uses `copyWith` to honour a per-project
180+ # Build helpers take a `{ pkgs, nur }: [ ... ]` packages function (the
203− # `container.nix`:
181+ # `packages` attr of a project's `container.nix`). `c init` uses
182+ # `copyWithShell` (host shell baked in); `copyWith` is the generic form:
204 # (builtins.getFlake "github:…/nix-container")
183 # (builtins.getFlake "github:…/nix-container")
205− # .lib.<system>.copyWith (import ./container.nix)
184+ # .lib.<system>.copyWith (import ./container.nix).packages
207 inherit mkImage;
186 inherit mkImage;
208− # skopeo copy app (`skopeo copy nix:<image> "$@"`) for an image built
187+ # Build the image's `copyTo` script (a skopeo-copy wrapper) from a
209− # from a `{ pkgs, nur }: [ ... ]` function.
188+ # `{ pkgs, nur }: [ ... ]` function, named `imageName`. Defaults the
189+ # shell (cmd + $SHELL) to bash; use `copyWithShell` to pick another.
210− copyWith = packages: (mkImage packages).copyTo;
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
195+ copyWithShell = name: shell: packages: (mkImage name shell packages).copyTo;