on master help sign in
Use container start -ai: zellij as cmd, TERM via create env
commitf96a9981352af54af1e18e440b24f839202928d2 authoriko <mail@iko.soy> dateSun 28 Jun 2026, 09:39 (3 months ago) parent590e716
3 files changed+36−41
README.md+6−5 c+9−17 flake.nix+21−19
README.md+6−5
@@ -90,18 +90,19 @@ nix run github:ilyakooo0/nix-container -- init
9090## Customize further
9191
9292Fork or clone to change what isn't per-project — `config.cmd`/`env` or the image
93−name — in [`flake.nix`](./flake.nix). The flake also exposes build helpers, both
94−taking a `{ pkgs, nur }: [ ... ]` function:
93+name — in [`flake.nix`](./flake.nix). The flake also exposes build helpers:
9594
96−- `lib.<system>.mkImage` → an OCI image.
97−- `lib.<system>.copyWith` → a skopeo copy app, for building/pushing the image
98− anywhere skopeo can write (e.g. a registry):
95+- `lib.<system>.copyWith` → a skopeo copy app for a `{ pkgs, nur }: [ ... ]`
96+ function, for building/pushing the image anywhere skopeo can write (e.g. a
97+ registry):
9998
10099 ```sh
101100 drv=$(nix build --no-link --print-out-paths --impure --expr \
102101 '(builtins.getFlake "github:ilyakooo0/nix-container").lib.${builtins.currentSystem}.copyWith (import ./container.nix)')
103102 $drv/bin/copy-to docker://ghcr.io/me/img:latest
104103 ```
104+- `lib.<system>.mkImage <name> <shell>` → the underlying OCI image (`copyWith`
105+ defaults these to `nix-container` / `bash`).
105106
106107The flake uses `flake-utils` over `{aarch64,x86_64}-{darwin,linux}`; the host
107108system maps to the matching Linux target automatically (an Intel Mac builds an
c+9−17
@@ -50,7 +50,7 @@ switch $argv[1]
5050 set -l tmp (mktemp -d)
5151 set -l archive $tmp/image.tar.gz
5252 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\"))"
53+ set -l expr "(builtins.getFlake \"$flake\").lib.\${builtins.currentSystem}.copyWithShell \"nix-container-$name\" \"$shell\" (import (/. + builtins.getEnv \"NIX_CONTAINER_PKGS\"))"
5454 set -l copyer (nix build --impure --no-link --print-out-paths --expr "$expr")
5555 set -l rc $status
5656 if test $rc -eq 0
@@ -68,10 +68,11 @@ switch $argv[1]
6868 # Mount the project at /workspace (the default cwd).
6969 set -l mounts -v "$PWD:/workspace"
7070
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.
71+ # Replace any existing container of the same name. The image's cmd is
72+ # zellij (`c start` attaches to it), so no command is passed here.
7373 container rm --force $name 2>/dev/null
74− # -e TERM: the runtime would otherwise force a bare "xterm" (see above).
74+ # -e TERM: baked into the container env so `container start -ai` gets it
75+ # (the runtime would otherwise force a bare "xterm").
7576 #
7677 # The image only lays down /bin and /etc, so /tmp and /run don't exist;
7778 # without them anything that writes temp files (zellij, build tools, …)
@@ -79,19 +80,10 @@ switch $argv[1]
7980 container create --name $name --ssh -it -e "TERM=$term" \
8081 --tmpfs /tmp --tmpfs /run --memory 8g --cwd /workspace $mounts $argv $image
8182 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
83+ # Start the container and attach to its cmd (zellij). TERM was baked into
84+ # the container env at create time (`-e TERM`), which survives this path.
85+ # Exiting zellij (PID1) stops the container; `c start` boots it again.
86+ container start -ai $name
9587 case '*'
9688 echo "usage: c init [-c FILE] [create args] | start" >&2
9789 exit 1
flake.nix+21−19
@@ -72,21 +72,22 @@
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− # for the named shell. The shell is the image's `cmd` (a PID1 that just
81− # holds the container open — `c start` execs the real session into it)
82− # and the default `$SHELL`; it's added automatically, so `container.nix`
80+ # Build the OCI image (named `name`) from a `{ pkgs, nur }: [ ... ]`
81+ # package function, for the named shell. The image's `cmd` is zellij
82+ # (the session `c start` attaches to); the shell is the default `$SHELL`
83+ # (what zellij panes spawn) and is added automatically, so `container.nix`
8384 # lists only your extras. Nix (+ CA certs) and the ncurses terminfo
8485 # database are always shipped too. Nothing here is fish-specific: the
85− # TERM fixup now happens host-side in `c` (passed via `-e TERM`).
86+ # TERM fixup happens host-side in `c` (passed via `-e TERM` at create).
8687 mkImage =
87− shell: packages:
88+ name: shell: packages:
8889 n2c.buildImage {
89− name = imageName;
90+ inherit name;
9091 tag = imageTag;
9192 inherit arch;
9293
@@ -132,10 +133,10 @@
132133
133134 config = {
134135 # `cmd` (not `entrypoint`): the default command, *replaced* by any
135− # command passed to `container run/create`. It's the detected
136− # shell — as PID1 it just holds the container open; `c start`
137− # execs the real interactive session (zellij) into it.
138− cmd = [ "/bin/${shell}" ];
136+ # command passed to `container run/create`. It's zellij — the
137+ # session `c start` attaches to via `container start -ai`; exiting
138+ # it (PID1) stops the container.
139+ cmd = [ "/bin/zellij" ];
139140 env = [
140141 "PATH=/bin"
141142 "HOME=/root"
@@ -182,13 +183,14 @@
182183 lib = {
183184 inherit mkImage;
184185 # Build the image's `copyTo` script (a skopeo-copy wrapper) from a
185− # `{ pkgs, nur }: [ ... ]` function. Defaults the shell (cmd + $SHELL)
186− # to bash; use `copyWithShell` to pick another.
187− copyWith = packages: (mkImage "bash" packages).copyTo;
188− # Like `copyWith`, but for the named shell — `c init` passes the host
189− # login shell, which is added to the image and used as cmd + $SHELL
190− # without being listed in `container.nix`.
191− copyWithShell = shell: packages: (mkImage shell packages).copyTo;
186+ # `{ pkgs, nur }: [ ... ]` function, named `imageName`. Defaults the
187+ # shell (cmd + $SHELL) to bash; use `copyWithShell` to pick another.
188+ copyWith = packages: (mkImage imageName "bash" packages).copyTo;
189+ # Like `copyWith`, but for the given image `name` and shell — `c init`
190+ # passes `nix-container-<dir>` and the host login shell, which is added
191+ # to the image and used as cmd + $SHELL without being listed in
192+ # `container.nix`.
193+ copyWithShell = name: shell: packages: (mkImage name shell packages).copyTo;
192194 };
193195 }
194196 );