ADR 0203: the account's environment is one module's (seat node-environment); every module contributes variables and PATH entries, rendered by the controller as a POSIX file and as environment.d. ADR 0204: shell code is contributed to the login shell in named slots, and login-shell becomes the mesh's node-login-shell. ADR 0205: software the distribution does not package ships as a pinned archive of the module. Issue 225: undeclaring a user stops a node applying; the shell is never given back or checked. To-be 41 carries the work packages; to-be 38 WP5 points to it.
132 lines
6.7 KiB
Markdown
132 lines
6.7 KiB
Markdown
---
|
|
topic: what runs on it
|
|
status: accepted
|
|
date: 2026-10-04
|
|
deciders: jochen
|
|
reconstructed: false
|
|
extends: 02-DECISIONS/0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md
|
|
---
|
|
|
|
# 204. A module contributes shell code to the login shell in named slots, and the login shell is the mesh's seat
|
|
|
|
## Context
|
|
|
|
Some of what a shell runs at start is code in that shell's own syntax, and it belongs to other
|
|
modules:
|
|
|
|
- a prompt theme loads itself and its configuration;
|
|
- plugins load themselves;
|
|
- a version manager sources its loader.
|
|
|
|
Order matters: a prompt's instant-prompt cache must run first, and syntax highlighting last. The
|
|
predecessor kept all of this in one file per machine, and installed the theme and plugins by cloning
|
|
them in a hook.
|
|
[Research 025](../01-RESEARCH/025-how-a-module-plugs-into-the-shell/00-overview.md) measured that file
|
|
as byte-identical on four machines. It carries:
|
|
|
|
- the shell's defaults;
|
|
- code belonging to four other pieces of software;
|
|
- a handful of the operator's own lines.
|
|
|
|
Nothing gave the other pieces a way in.
|
|
|
|
Two further facts bear on the seat itself:
|
|
|
|
- **The seat is declared by the zsh module** ([ADR 0176](0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md)
|
|
§1, under [ADR 0126](0126-a-module-declares-its-own-seats.md)). The controller refuses a second
|
|
module declaring a seat name, so fish or bash could only ever claim it, and the seat exists only
|
|
while zsh's definition is registered.
|
|
- **The kept region is the other way round.** [ADR 0174](0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md)
|
|
describes a kept region as a marked block holding the operator's lines. The host built the inverse:
|
|
the mesh's region is the marked block, and every byte outside it is kept, verified unchanged, and
|
|
given back when the module goes.
|
|
|
|
## Considered Options
|
|
|
|
1. **A drop-in directory** that each module places a file in, and the shell sources. Rejected: every
|
|
contributor names a path inside the shell module's territory
|
|
([ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md)); order becomes a naming
|
|
convention nothing checks; and nothing ties the file to the shell actually being the one it is
|
|
written for.
|
|
2. **Facts the holder renders**, through `contributes` / `receives`. Rejected: code is not a fact,
|
|
and the holder would only paste it.
|
|
3. **Code contributed for a named shell in a named slot, assembled by the controller into the
|
|
holder's file.** This is what the controller already does for fail2ban jails: each module supplies
|
|
text in the tool's own format, and the controller sorts and concatenates it into the holder's file
|
|
without interpreting it. Chosen.
|
|
|
|
For order, numbers (`10`, `50`, `90`) were rejected: every contributor guesses one, and collisions are
|
|
silent. **Three named slots** were chosen: `first`, `normal`, `last`.
|
|
|
|
## Decision
|
|
|
|
**1. `node-login-shell` is a node seat in the mesh's own set,** with the verb `execute`. It replaces
|
|
the module-declared `login-shell`. Everything else ADR 0176 decided stands: the holder sets the
|
|
account's login shell through the `user` shape, `execute` is the contract, and any node may call it.
|
|
A shell module claims the seat; none declares it.
|
|
|
|
**2. Any module contributes shell code with `shell`:** entries naming the shell they are for (`zsh`,
|
|
`bash`, `fish`), the slot, and the code. The controller does not read the code.
|
|
|
|
**3. The holder places the code with placeholders** in its own files: `${shell:<shell>:<slot>}`. Each
|
|
is filled with that shell's code for that slot, from every module on the node:
|
|
|
|
- ordered by module name;
|
|
- each piece preceded by a line naming its module;
|
|
- empty when nothing is contributed.
|
|
|
|
A shell-code placeholder in a module that does not claim `node-login-shell` is refused.
|
|
|
|
**4. The holder's duties, which are the seat's protocol:**
|
|
|
|
- Source the account's environment ([ADR 0203](0203-the-accounts-environment-is-one-modules-and-every-module-contributes-to-it.md))
|
|
from the startup file every start of that shell reads. For zsh that is `.zshenv`, which a script, a
|
|
login and `execute` all read.
|
|
- Write its interactive block at the **start** of the interactive startup file, so the operator's own
|
|
lines run after the mesh's and win.
|
|
- Run `execute` as a non-interactive login shell in the account's home:
|
|
- bounded below the runtime's call limit;
|
|
- its output bounded;
|
|
- its whole process group ended on timeout.
|
|
|
|
**5. The marked block is the mesh's; everything outside it is the operator's.** This is how ADR 0174's
|
|
"kept region" is built. That record keeps its decision and gains a note saying where the mechanism
|
|
lives.
|
|
|
|
## Consequences
|
|
|
|
- A prompt, a plugin and a version manager are each a module with its own package or archive, its own
|
|
configuration file, and a contribution. Assigning one adds its line to the shell, and unassigning it
|
|
takes the line away at the next composition.
|
|
- Assigning the shell module loses nothing the machine does today:
|
|
- what is common to every machine becomes the shell module's default or another module's
|
|
contribution;
|
|
- what is the operator's stays below the block.
|
|
- **The one-off migration is a person's act**, listed in the shell module's documentation
|
|
([ADR 0182](0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md)):
|
|
delete the lines the block now carries from the found file.
|
|
- `login-shell.execute` becomes `node-login-shell.execute`. Nothing has called it yet; the shell
|
|
module was never assigned.
|
|
- **What got harder:** a module wanting a line in the shell must say which shell and which slot, and a
|
|
module supporting three shells writes its code three times. That is the honest cost of code in
|
|
three syntaxes.
|
|
|
|
## How it is checked
|
|
|
|
| Rule | Checked by |
|
|
|---|---|
|
|
| Code lands in its slot, in module order, only for its shell | the controller's shell-contribution tests |
|
|
| A shell-code placeholder outside the holder is refused | the catalogue check |
|
|
| `node-login-shell` is the mesh's, and no module may declare it | the seat table's tests |
|
|
| The zsh block sits at the start, sources the environment from `.zshenv`, and holds the three slots | the catalogue's zsh test |
|
|
| `execute` is bounded in time and output and kills its process group | the zsh module's tool tests over real child processes |
|
|
|
|
## References
|
|
|
|
- [Research 025](../01-RESEARCH/025-how-a-module-plugs-into-the-shell/00-overview.md)
|
|
- [ADR 0174](0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md),
|
|
[ADR 0176](0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md),
|
|
[ADR 0203](0203-the-accounts-environment-is-one-modules-and-every-module-contributes-to-it.md),
|
|
[to-be 31](../03-DESIGN/01-to-be/31-a-module-declares-its-fail2ban-jail.md)
|
|
- [To-be 41](../03-DESIGN/01-to-be/41-the-shell-and-the-accounts-environment.md)
|