@@ -72,127 +72,139 @@
7272 # Host package set, for the wrapper app below.
7373 pkgsHost = nixpkgs.legacyPackages.${system};
7474
75− # Default image name (for `copyWith`); `c init` passes a per-directory
76− # name (`nix-container-<dir>`) to `copyWithShell` instead.
77− imageName = "nix-container";
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";
7879 imageTag = "latest";
7980
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;
93−
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− };
131−
132− initializeNixDatabase = true;
133−
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− };
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" ''
84+ # No welcome banner.
85+ set -g fish_greeting
86+
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.
89+ function fish_prompt
90+ set -l nix_shell_info
91+ if set -q IN_NIX_SHELL; or set -q IN_NIX_RUN
92+ set nix_shell_info ' ❄️'
93+ end
94+
95+ set -l cwd (prompt_pwd)
96+
97+ set -l bookmark_info
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)
102+ end
103+ end
104+
105+ echo -n -s (set_color $fish_color_cwd) $cwd (set_color normal) $bookmark_info ' 🏗️' $nix_shell_info '> '
106+ end
107+ '';
164108
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 "$@"
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"
172113 '';
114+
115+ image = n2c.buildImage {
116+ name = imageName;
117+ tag = imageTag;
118+ inherit arch;
119+
120+ # A plain, single-process root filesystem. No NixOS, no systemd.
121+ copyToRoot = pkgsLinux.buildEnv {
122+ name = "root";
123+ paths = with pkgsLinux; [
124+ bashInteractive
125+ fish
126+ fishRoot
127+ openssh
128+ coreutils-full
129+ btop
130+ jujutsu
131+ zellij
132+ micro
133+ helix
134+ nurPkgs.repos.charmbracelet.crush
135+
136+ # LLM tools
137+ git
138+ ripgrep
139+ fd
140+ jq
141+ curl
142+ wget
143+ gnused
144+ gnugrep
145+ gawk
146+ findutils
147+ gnutar
148+ gzip
149+ unzip
150+ less
151+ which
152+ tree
153+ nodejs
154+ python3
155+
156+ # rust
157+ rustc
158+ cargo
159+ rustfmt
160+ clippy
161+
162+ # C toolchain — rustc links final binaries through `cc`/`ld`.
163+ gcc # provides cc/gcc; pulls in binutils (ld) via its closure
164+ ];
165+ pathsToLink = [
166+ "/bin"
167+ "/root"
168+ ];
169+ };
170+
171+ initializeNixDatabase = true;
172+
173+ config = {
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" ];
179+ env = [
180+ "PATH=/bin"
181+ # fish reads its prompt from $HOME/.config/fish/config.fish.
182+ "HOME=/root"
183+ ];
184+ };
185+ };
186+
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";
193+ text = ''
194+ out="''${1:-image.tar}"
195+ exec ${image.copyTo}/bin/copy-to "oci-archive:$out:${imageName}:${imageTag}"
196+ '';
197+ };
173198 in
174199 {
175200 apps = {
176− # nix run . -- init|start → build+load+create / start a container
177− default = flake-utils.lib.mkApp { drv = cApp; };
178− };
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; };
179204
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;
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; };
196208 };
197209 }
198210 );