@@ -1,7 +1,7 @@
11#!/usr/bin/env fish
22# Manage a container named after the current directory.
33# c init [-c FILE] [extra create args] build + load the image, then (re)create it
4−# (packages from FILE, default ./container.nix)
4+# (config from FILE, default ./container.nix)
55# c start start it and attach
66
77set -l name (path basename $PWD)
@@ -10,18 +10,6 @@ set -l flake (path dirname (path resolve (status filename)))
1010# Per-directory image ref this script loads and runs (archive tag + `create`).
1111set -l image "nix-container-$name:latest"
1212
13−# Terminal type to pass into the container via `-e TERM` (both create and exec):
14−# the runtime hands processes a bare "xterm", so we set the host's real terminal
15−# instead. Map it to an entry the bundled ncurses ships — Ghostty reports
16−# "xterm-ghostty", which ncurses ships as "ghostty" — falling back to
17−# xterm-256color for an empty or bare-"xterm" value.
18−set -l term xterm-256color
19−if test "$TERM" = xterm-ghostty
20− set term ghostty
21−else if test -n "$TERM"; and test "$TERM" != xterm
22− set term $TERM
23−end
24−
2513switch $argv[1]
2614 case init
2715 # -c/--config overrides the default ./container.nix; leftover args (e.g.
@@ -34,23 +22,23 @@ switch $argv[1]
3422 end
3523
3624 if not test -e $config
37− echo "c: package file not found: $config" >&2
38− echo " create one ('{ pkgs, nur }: [ ... ]') or pass -c FILE" >&2
25+ echo "c: config file not found: $config" >&2
26+ echo " create one ('{ packages = { pkgs, nur }: [ ... ]; mounts = [ ]; }') or pass -c FILE" >&2
3927 exit 1
4028 end
4129
4230 # Host login shell (by name): added to the image automatically and baked
43− # in as the cmd + default $SHELL (what zellij panes etc. spawn), so it
31+ # in as the cmd + default $SHELL (what herdr panes etc. spawn), so it
4432 # needn't be listed in container.nix. Falls back to bash.
4533 set -l shell bash
4634 test -n "$SHELL"; and set shell (path basename $SHELL)
4735
48− # Build an image with the packages from $config (plus the shell) into a
36+ # Build an image with the config's `packages` (plus the shell) into a
4937 # temp dir, load it, then clean up.
5038 set -l tmp (mktemp -d)
5139 set -l archive $tmp/image.tar.gz
52− set -lx NIX_CONTAINER_PKGS (path resolve $config)
53− set -l expr "(builtins.getFlake \"$flake\").lib.\${builtins.currentSystem}.copyWithShell \"$shell\" (import (/. + builtins.getEnv \"NIX_CONTAINER_PKGS\"))"
40+ set -lx NIX_CONTAINER_CONFIG (path resolve $config)
41+ set -l expr "(builtins.getFlake \"$flake\").lib.\${builtins.currentSystem}.copyWithShell \"nix-container-$name\" \"$shell\" (import (/. + builtins.getEnv \"NIX_CONTAINER_CONFIG\")).packages"
5442 set -l copyer (nix build --impure --no-link --print-out-paths --expr "$expr")
5543 set -l rc $status
5644 if test $rc -eq 0
@@ -65,33 +53,41 @@ switch $argv[1]
6553 rm -rf $tmp
6654 test $rc -eq 0; or exit $rc
6755
68− # Mount the project at /workspace (the default cwd).
56+ # Mount the project at /workspace (the default cwd), plus any extra
57+ # mounts from the config (`mounts = [ "host:container" … ]`). A leading
58+ # ~/ in the host path expands to $HOME (nix leaves it literal).
6959 set -l mounts -v "$PWD:/workspace"
60+ for m in (nix eval --impure --raw --expr "builtins.concatStringsSep \"\n\" ((import (/. + builtins.getEnv \"NIX_CONTAINER_CONFIG\")).mounts or [])")
61+ set -a mounts -v (string replace -r '^~/' "$HOME/" -- $m)
62+ end
63+
64+ # TERM to bake into the container env (`-e TERM`): the runtime hands the
65+ # `start -ai` session a bare "xterm", so set the host's real terminal,
66+ # mapped to an entry the bundled ncurses ships — Ghostty reports
67+ # "xterm-ghostty", which ncurses ships as "ghostty" — falling back to
68+ # xterm-256color for an empty or bare-"xterm" value.
69+ set -l term xterm-256color
70+ if test "$TERM" = xterm-ghostty
71+ set term ghostty
72+ else if test -n "$TERM"; and test "$TERM" != xterm
73+ set term $TERM
74+ end
7075
71− # Replace any existing container of the same name. The image's cmd is the
72− # detected shell (holds the container open), so no command is passed.
76+ # Replace any existing container of the same name, then create it running
77+ # herdr in a session named after the project (`<dir>-container`). This
78+ # overrides the image's default plain-herdr cmd; `c start` attaches to it.
7379 container rm --force $name 2>/dev/null
74− # -e TERM: the runtime would otherwise force a bare "xterm" (see above).
75− #
7680 # The image only lays down /bin and /etc, so /tmp and /run don't exist;
77− # without them anything that writes temp files (zellij, build tools, …)
81+ # without them anything that writes temp files (herdr, build tools, …)
7882 # fails with ENOENT. Mount writable tmpfs at both.
7983 container create --name $name --ssh -it -e "TERM=$term" \
80− --tmpfs /tmp --tmpfs /run --memory 8g --cwd /workspace $mounts $argv $image
84+ --tmpfs /tmp --tmpfs /run --memory 8g --cwd /workspace $mounts $argv \
85+ $image /bin/herdr session attach "$name-container"
8186 case start
82− # Boot the container detached (its PID1 shell keeps it alive), then attach
83− # the session with `exec -it`. `container start -ai` has no `-t` flag, so
84− # it never hands the guest a properly sized TTY: the real window size
85− # isn't delivered via the TIOCGWINSZ ioctl, so ioctl-based TUIs
86− # (crossterm/ncurses — helix, zellij, btop…) see 0×0 and render garbled,
87− # misaligned output. `exec -it` opens a real, correctly sized TTY, and
88− # `-e TERM` gives it the right terminal type.
89− #
90− # When the session exits, stop the container so it doesn't linger (the
91− # PID1 shell would otherwise keep it running); `c start` boots it again.
92− container start $name >/dev/null 2>&1; or exit 1
93− container exec -it -e "TERM=$term" --cwd /workspace $name zellij
94− container stop $name >/dev/null 2>&1
87+ # Start the container and attach to its cmd (herdr). TERM was baked into
88+ # the container env at create time (`-e TERM`), which survives this path.
89+ # Exiting herdr (PID1) stops the container; `c start` boots it again.
90+ container start -ai $name
9591 case '*'
9692 echo "usage: c init [-c FILE] [create args] | start" >&2
9793 exit 1