on master help sign in
Revert to hard-coded fish and restore the bundled prompt
commitc35a6f53ed6983f99f13c168213c766ed8a8aabf authoriko <mail@iko.soy> dateSat 27 Jun 2026, 22:02 (3 months ago) parentd6edb81
3 files changed+102−89
README.md+6−6 c+7−10 flake.nix+89−73
README.md+6−6
@@ -5,8 +5,8 @@ A Nix flake that builds an **OCI image** (via
55Apple's [`container`](https://github.com/apple/container) CLI on macOS.
66
77It's a plain, single-process image — no NixOS, no systemd — defined in
8−[`flake.nix`](./flake.nix). `init` runs your host login shell (`$SHELL`); you
9−choose what else it ships via a per-project `container.nix`.
8+[`flake.nix`](./flake.nix). It runs `fish` by default (with a configured
9+prompt); you choose what else it ships via a per-project `container.nix`.
1010
1111## Prerequisites
1212
@@ -40,9 +40,9 @@ is **required**; there is no default set.
4040]
4141```
4242
43−Your host shell (`$SHELL`) is added and run automatically — no need to list it.
44−**Nix itself** is always included too (with CA certs and `nix-command`/`flakes`
45−enabled, single-user), so you can run `nix` inside the container.
43+**fish** (the default command, with a configured prompt) and **Nix** itself
44+(with CA certs and `nix-command`/`flakes` enabled, single-user) are always
45+included — no need to list them.
4646
4747Point at a different file with `-c`/`--config`:
4848
@@ -59,7 +59,7 @@ that holds a `container.nix`; the container is named after that directory.
5959# build the image, load it, and create the container (named after $PWD):
6060nix run github:ilyakooo0/nix-container -- init
6161
62−# start it and attach — drops you into your shell:
62+# start it and attach — drops you into fish:
6363nix run github:ilyakooo0/nix-container -- start
6464```
6565
c+7−10
@@ -27,17 +27,12 @@ switch $argv[1]
2727 exit 1
2828 end
2929
30− # Host login shell (by name): run as the container cmd and added to the
31− # image automatically, so it needn't be listed in container.nix.
32− set -l shell fish
33− test -n "$SHELL"; and set shell (path basename $SHELL)
34−
35− # Build an image with the packages from $config (plus the shell) into a
36− # temp dir, load it, then clean up.
30+ # Build an image with the packages from $config into a temp dir, load
31+ # it, then clean up.
3732 set -l tmp (mktemp -d)
3833 set -l archive $tmp/image.tar.gz
3934 set -lx NIX_CONTAINER_PKGS (path resolve $config)
40− set -l expr "(builtins.getFlake \"$flake\").lib.\${builtins.currentSystem}.copyWithShell \"$shell\" (import (/. + builtins.getEnv \"NIX_CONTAINER_PKGS\"))"
35+ set -l expr "(builtins.getFlake \"$flake\").lib.\${builtins.currentSystem}.copyWith (import (/. + builtins.getEnv \"NIX_CONTAINER_PKGS\"))"
4136 set -l copyer (nix build --impure --no-link --print-out-paths --expr "$expr")
4237 set -l rc $status
4338 if test $rc -eq 0
@@ -53,12 +48,14 @@ switch $argv[1]
5348 test $rc -eq 0; or exit $rc
5449
5550 # Mount the project at /workspace (the default cwd) and host ~/.config.
51+ # (Mounting ~/.config shadows the image's bundled fish prompt config.)
5652 set -l mounts -v "$PWD:/workspace"
5753 test -d $HOME/.config; and set -a mounts -v "$HOME/.config:/root/.config"
5854
59− # Replace any existing container of the same name.
55+ # Replace any existing container of the same name. The image's cmd is
56+ # /bin/fish, so no command is passed.
6057 container rm --force $name 2>/dev/null
61− container create --name $name --ssh -it --cwd /workspace $mounts $argv $image /bin/$shell
58+ container create --name $name --ssh -it --cwd /workspace $mounts $argv $image
6259 case start
6360 container start -ai $name
6461 case '*'
flake.nix+89−73
@@ -76,74 +76,99 @@
7676 imageName = "nix-container";
7777 imageTag = "latest";
7878
79+ # Fish prompt config, installed at /root/.config/fish/config.fish
80+ # (fish reads $HOME/.config/fish). Built on the host arch — it's just a
81+ # text file, so no Linux builder is needed.
82+ fishConfig = pkgsHost.writeText "config.fish" ''
83+ # No welcome banner.
84+ set -g fish_greeting
85+
86+ # Define the prompt as a function so fish renders it before every
87+ # command; at top level it would run once at startup and be ignored.
88+ function fish_prompt
89+ set -l nix_shell_info
90+ if set -q IN_NIX_SHELL; or set -q IN_NIX_RUN
91+ set nix_shell_info ' ❄️'
92+ end
93+
94+ set -l cwd (prompt_pwd)
95+
96+ set -l bookmark_info
97+ if jj root 2>/dev/null >/dev/null
98+ set -l jj_bookmark (jj log -r 'heads(::@ & bookmarks())' -T 'local_bookmarks ++ "\n"' --no-graph 2>/dev/null | tail -1)
99+ if test -n "$jj_bookmark"
100+ set bookmark_info ' ' (set_color brmagenta) $jj_bookmark (set_color normal)
101+ end
102+ end
103+
104+ echo -n -s (set_color $fish_color_cwd) $cwd (set_color normal) $bookmark_info ' 🏗️' $nix_shell_info '> '
105+ end
106+ '';
107+
108+ # Lay the config out at the path fish reads.
109+ fishRoot = pkgsHost.runCommand "fish-config-root" { } ''
110+ mkdir -p "$out/root/.config/fish"
111+ cp ${fishConfig} "$out/root/.config/fish/config.fish"
112+ '';
113+
79114 # 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.
115+ # The image always runs /bin/fish; fish, its prompt config, and Nix are
116+ # bundled automatically, so `container.nix` lists only your extras.
83117 mkImage =
84118 packages:
85− let
86− pkgList = packages {
87− pkgs = pkgsLinux;
88− nur = nurPkgs;
119+ n2c.buildImage {
120+ name = imageName;
121+ tag = imageTag;
122+ inherit arch;
123+
124+ # A plain, single-process root filesystem. No NixOS, no systemd.
125+ # buildEnv only *symlinks* paths (never runs them), so build it on
126+ # the host arch — the tree is arch-neutral and links the
127+ # (substituted) Linux store paths, sparing the Linux builder.
128+ #
129+ # Always shipped: fish (the cmd) + its prompt, and Nix (+ CA certs)
130+ # so every container can use Nix; `initializeNixDatabase` registers
131+ # the baked store.
132+ copyToRoot = pkgsHost.buildEnv {
133+ name = "root";
134+ paths =
135+ packages {
136+ pkgs = pkgsLinux;
137+ nur = nurPkgs;
138+ }
139+ ++ [
140+ pkgsLinux.fish
141+ fishRoot
142+ pkgsLinux.nix
143+ pkgsLinux.cacert
144+ ];
145+ pathsToLink = [
146+ "/bin"
147+ "/root"
148+ "/etc/ssl"
149+ ];
150+ };
151+
152+ initializeNixDatabase = true;
153+
154+ config = {
155+ # `cmd` (not `entrypoint`): it's the default command and is
156+ # *replaced* by `container run … -- <cmd>`. An entrypoint would be
157+ # prepended instead, so `run … -- /bin/sh` would become
158+ # `/bin/bash /bin/sh` and fail with "cannot execute binary file".
159+ cmd = [ "/bin/fish" ];
160+ env = [
161+ "PATH=/bin"
162+ "HOME=/root"
163+ # Marker so shells/scripts can detect they're in here.
164+ "NIX_CONTAINER=1"
165+ # Make the bundled Nix usable: TLS for substituters/flakes,
166+ # flake commands on, and single-user (no daemon/nixbld).
167+ "NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt"
168+ "NIX_CONFIG=experimental-features = nix-command flakes\nbuild-users-group =\nsandbox = false"
169+ ];
89170 };
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;
106−
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− };
125−
126− initializeNixDatabase = true;
127−
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− );
171+ };
147172
148173 # `nix run . -- init|start` runs the bundled `c` manager script. Its
149174 # `init` builds the image from the project's `container.nix` (via
@@ -169,15 +194,6 @@
169194 # skopeo copy app (`skopeo copy nix:<image> "$@"`) for an image built
170195 # from a `{ pkgs, nur }: [ ... ]` function.
171196 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;
181197 };
182198 }
183199 );