Graduate research 025: the environment and the shell's contributions
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.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-host, mesh-controller, mesh-tools, mesh-catalog]
|
||||
updated: 2026-10-02
|
||||
updated: 2026-10-04
|
||||
decisions:
|
||||
- 02-DECISIONS/0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md
|
||||
- 02-DECISIONS/0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md
|
||||
@@ -33,12 +33,16 @@ This is the design [to-be 29](29-a-node-has-operator-accounts.md) §2 called *a
|
||||
Worked on the first one, a shell. The `zsh` module declares:
|
||||
|
||||
- a **package**, `zsh`;
|
||||
- **files under the home**, owned by the account: the shell's rc file with the module's default
|
||||
configuration, carrying a kept region for the operator's own lines, and `${setting:…}`
|
||||
placeholders for the few values a node varies; the account and its home are machine facts the
|
||||
controller resolves ([ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md),
|
||||
to-be 29 §2);
|
||||
- a **seat declaration**, `login-shell`, node-scoped, with its one verb; and a **claim** on it;
|
||||
- **files under the home**, owned by the account: the mesh's block at the start of the shell's rc
|
||||
file with the module's default configuration and the slots other modules' code lands in, the
|
||||
operator's own lines kept after it, and `${setting:…}` placeholders for the few values a node
|
||||
varies; the account and its home are machine facts the controller resolves
|
||||
([ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md),
|
||||
to-be 29 §2). Its environment is a contribution to the environment module, not lines of its own
|
||||
([ADR 0203](../../02-DECISIONS/0203-the-accounts-environment-is-one-modules-and-every-module-contributes-to-it.md),
|
||||
[ADR 0204](../../02-DECISIONS/0204-a-module-contributes-shell-code-to-the-login-shell-in-named-slots.md),
|
||||
[to-be 41](41-the-shell-and-the-accounts-environment.md));
|
||||
- a **claim** on the mesh's node-scoped seat `node-login-shell`, with its one verb;
|
||||
- a **`user` shape** naming the shell, applied only where the module holds the seat;
|
||||
- a **tools bundle**, the artifact kind for interpreted code, with `execute` and the module's own
|
||||
`show-config`.
|
||||
@@ -80,7 +84,8 @@ root escalates itself.
|
||||
|
||||
## 4. The seats of the environment
|
||||
|
||||
Decided now: **`login-shell`** (module-declared; zsh, fish, bash; verb `execute`) and
|
||||
Decided now: **`node-login-shell`** (the mesh's own, ADR 0204; zsh, fish, bash; verb `execute`),
|
||||
**`node-environment`** (the mesh's own, ADR 0203; the environment module; no verbs) and
|
||||
**`node-service-manager`** (the mesh's own; systemd; verbs over units in both scopes). The rest
|
||||
are candidates from [research 018](../../01-RESEARCH/018-the-operators-machine-as-modules/04-the-seats-of-the-environment.md),
|
||||
one record each when its first holder is written: display server, display session, terminal
|
||||
|
||||
@@ -15,6 +15,7 @@ decisions:
|
||||
- 02-DECISIONS/0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md
|
||||
- 02-DECISIONS/0193-every-bundle-the-runtime-serves-is-launched-and-the-runtime-knows-no-language.md
|
||||
- 02-DECISIONS/0198-a-modules-long-running-code-is-launched-by-the-node-runtime-and-reaches-the-bus-through-it.md
|
||||
- 02-DECISIONS/0204-a-module-contributes-shell-code-to-the-login-shell-in-named-slots.md
|
||||
---
|
||||
|
||||
# 38. Building the operator's machine
|
||||
@@ -330,6 +331,13 @@ tool of each.
|
||||
|
||||
## WP5 — The shell, on a server first
|
||||
|
||||
*Replaced on 2026-10-04 by [to-be 41](41-the-shell-and-the-accounts-environment.md).* A review before
|
||||
assigning found that the shell module would duplicate every machine's existing startup file, drop
|
||||
lines from it, leave the prompt uninstalled, and could not be unassigned
|
||||
([issue 225](../../04-ISSUES/225-a-login-the-mesh-set-is-never-given-back/00-report.md)). The shell,
|
||||
its environment, the modules that plug into it, and the host's fix are built and proven there. What
|
||||
follows is the original plan, kept for the record.
|
||||
|
||||
*mesh-catalog #224, already written. Half a day to assign and prove.*
|
||||
|
||||
**Order.** Assign `zsh` to one server; push; `login-shell.execute@<server> command="uptime"`
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-host, mesh-controller, mesh-catalog]
|
||||
updated: 2026-10-04
|
||||
decisions:
|
||||
- 02-DECISIONS/0203-the-accounts-environment-is-one-modules-and-every-module-contributes-to-it.md
|
||||
- 02-DECISIONS/0204-a-module-contributes-shell-code-to-the-login-shell-in-named-slots.md
|
||||
- 02-DECISIONS/0205-software-the-distribution-does-not-package-ships-as-a-pinned-archive-of-the-module.md
|
||||
- 02-DECISIONS/0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md
|
||||
- 02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md
|
||||
- 02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md
|
||||
---
|
||||
|
||||
# 41. The shell and the account's environment
|
||||
|
||||
What it takes for the operator's shell to be modules, without a machine losing anything it does
|
||||
today. This design replaces the shell half of
|
||||
[to-be 38](38-building-the-operators-machine.md) WP5, and finishes the service-manager module of WP6
|
||||
short of its user-scoped units. The decisions are [ADR 0203](../../02-DECISIONS/0203-the-accounts-environment-is-one-modules-and-every-module-contributes-to-it.md)
|
||||
(the environment), [ADR 0204](../../02-DECISIONS/0204-a-module-contributes-shell-code-to-the-login-shell-in-named-slots.md)
|
||||
(shell code and the seat) and [ADR 0205](../../02-DECISIONS/0205-software-the-distribution-does-not-package-ships-as-a-pinned-archive-of-the-module.md)
|
||||
(vendored software). The evidence is [research 025](../../01-RESEARCH/025-how-a-module-plugs-into-the-shell/00-overview.md).
|
||||
|
||||
## What a node with a shell looks like
|
||||
|
||||
```
|
||||
toolchain, agent, … prompt, plugins, version manager
|
||||
│ environment │ shell (zsh, slot)
|
||||
▼ ▼
|
||||
node-environment ── holds ── node-env node-login-shell ── holds ── zsh
|
||||
│ │
|
||||
├─▶ ~/.config/mesh/environment.sh ◀─ sourced from zsh's block in ~/.zshenv
|
||||
└─▶ ~/.config/environment.d/50-mesh.conf ◀─ read by the account's service manager
|
||||
│
|
||||
~/.zshrc: the mesh's block FIRST — defaults and the
|
||||
three slots — then the operator's own lines, kept
|
||||
```
|
||||
|
||||
**The environment module** (`node-env`) holds `node-environment`. It has no package and no process.
|
||||
Its two files are written by the host from placeholders the controller fills.
|
||||
|
||||
**The shell module** (`zsh`) holds `node-login-shell`. It:
|
||||
|
||||
- installs the package;
|
||||
- sets the login shell through the `user` shape;
|
||||
- writes two blocks:
|
||||
- one in `.zshenv`, sourcing the environment;
|
||||
- one at the start of `.zshrc`, holding the defaults every machine shares today: the title, the
|
||||
keybindings, the aliases and the two small functions, with the three slots in place;
|
||||
- contributes its own environment: the editor, the configuration home, and `~/.local/bin` plus the
|
||||
two script directories on `PATH`;
|
||||
- serves `execute` and its own `zsh_config`.
|
||||
|
||||
**The prompt module** (`powerlevel10k`) ships the theme as a pinned vendored archive, and the prompt's
|
||||
configuration as its own file in a directory it owns. It contributes the zsh code that loads both.
|
||||
|
||||
**Two plugin modules** (`zsh-autosuggestions`, `zsh-syntax-highlighting`) each install their
|
||||
distribution package and contribute one line. Syntax highlighting goes in the `last` slot, which is
|
||||
what its upstream asks for.
|
||||
|
||||
**What stays the operator's** is everything below the block in `.zshrc`, and `~/.zshrc.local`, which
|
||||
the operator's lines source as they do today. On every machine today that means:
|
||||
|
||||
- the version manager's lines and the toolchain's `PATH` entry, until those modules exist;
|
||||
- the two variables naming the operator's own script library;
|
||||
- the agent's title variable;
|
||||
- the port aliases;
|
||||
- the workstation's desktop variables, which live in `~/.zshrc.local` already.
|
||||
|
||||
Nothing is lost at any step, because a line moves out of the operator's part only when a module
|
||||
carries it.
|
||||
|
||||
**The migration is a person's act**, listed in the zsh module's documentation (ADR 0182): after the
|
||||
first push, delete from `.zshrc` the lines the block now carries. Until then they run twice, which is
|
||||
harmless and visible.
|
||||
|
||||
## Work packages
|
||||
|
||||
```
|
||||
WP1 the host gives a login back (mesh-host) issue 225
|
||||
WP2 the controller composes environment and shell code (mesh-controller)
|
||||
WP3 the modules (mesh-catalog) needs WP2 to resolve
|
||||
WP4 the service manager's module, finished (mesh-catalog) independent
|
||||
WP5 assign and prove (operator-gated) needs WP1–WP3 merged and rolled
|
||||
```
|
||||
|
||||
WP1, WP2 and WP4 are independent, and are built in parallel on one feature branch per repository
|
||||
([playbook 07](../../00-META/process/07-feature-branches.md)). WP3 is written in parallel and proven
|
||||
against WP2's controller before anything is published.
|
||||
|
||||
## WP1 — The host gives a login back
|
||||
|
||||
*mesh-host. Half a day. [Issue 225](../../04-ISSUES/225-a-login-the-mesh-set-is-never-given-back/00-report.md).*
|
||||
|
||||
**What changes.**
|
||||
|
||||
- The `user` shape records, in its applied record, the login shell it found whenever it changes it.
|
||||
- Removing a `user` never deletes the account, whether or not the mesh created it. If the account's
|
||||
shell is still the one the mesh set, and the recorded shell is still executable, the recorded shell
|
||||
is set back. Otherwise the shell is left as it is, and the outcome says why.
|
||||
- Before a shell is set, it is refused unless it is executable and listed among the machine's shells.
|
||||
The refusal fails that resource and leaves the account untouched.
|
||||
|
||||
**Proof.** The host's tests:
|
||||
|
||||
- an undeclared `user` no longer stops the apply;
|
||||
- the found shell comes back;
|
||||
- a shell changed by a person since is left alone;
|
||||
- a missing shell is refused before `usermod` runs;
|
||||
- a created account survives its removal.
|
||||
|
||||
## WP2 — The controller composes environment and shell code
|
||||
|
||||
*mesh-controller. One to two days.*
|
||||
|
||||
**What changes.**
|
||||
|
||||
- **The seat table.** It gains `node-environment` (node scope, no verbs) and `node-login-shell`
|
||||
(node scope, the verb `execute`). A module may no longer declare a seat named `login-shell` or
|
||||
`node-login-shell`. Both new seats are seeded into a live store by the existing additive seeding.
|
||||
- **The manifest.** It gains two contribution fields, each refused at parse when malformed:
|
||||
- `environment`, with `variables` and `path`: a variable name must be a POSIX name and not `PATH`;
|
||||
a value may not contain `$`, a quote, a backslash or a line break; a path entry's place is
|
||||
`start` or `end`;
|
||||
- `shell`: each entry names a known shell, a known slot, and non-empty code.
|
||||
- **Composition.** It fills `${environment:posix}`, `${environment:systemd}` and
|
||||
`${shell:<shell>:<slot>}` in the claiming holder's file contents, from every module assigned to
|
||||
the node. The rendering is ADR 0203's and ADR 0204's: module order, a naming line per contribution,
|
||||
`PATH` entries added only when missing. `${machine:…}` in a contributed value is resolved first.
|
||||
- **Refusals.** A variable set by two modules on one node is refused, naming both. A placeholder in a
|
||||
module that does not claim the matching seat is refused, both at the catalogue check and at
|
||||
composition.
|
||||
|
||||
**Proof.** The controller's tests:
|
||||
|
||||
- both environment renderings, byte for byte, from a fixed set of contributions;
|
||||
- the POSIX rendering sourced twice by `sh` leaves `PATH` unchanged;
|
||||
- slot order and per-shell filtering;
|
||||
- each refusal, by name;
|
||||
- the seat table carries both seats and refuses a module declaring either.
|
||||
|
||||
The catalogue check over the whole catalogue passes.
|
||||
|
||||
## WP3 — The modules
|
||||
|
||||
*mesh-catalog. One day.*
|
||||
|
||||
**What changes.**
|
||||
|
||||
- **`node-env`, new.** It claims `node-environment` and declares two owned files: the POSIX file at
|
||||
the path the seat fixes, and the service manager's file, each holding its placeholder. It declares
|
||||
no tools.
|
||||
- **`zsh`, rewritten.**
|
||||
- It drops its seat declaration and claims `node-login-shell`.
|
||||
- Its environment moves to a contribution.
|
||||
- It writes a `.zshenv` block that sources the environment file.
|
||||
- Its `.zshrc` block goes at the start and carries today's shared defaults, with the three slots.
|
||||
- It keeps the `user` shape.
|
||||
- `execute` runs `zsh -lc` in the account's home, with the runtime's session words for the user
|
||||
manager. Its timeout is bounded below the runtime's thirty-second call limit; its output is cut at
|
||||
a bound and marked as cut; on timeout it kills the process group.
|
||||
- Tests cover the tool over real child processes and the manifest's shape.
|
||||
- Its documentation lists the one-off migration.
|
||||
- **`powerlevel10k`, new.**
|
||||
- The theme is vendored at a pinned upstream release, with its licence, as an archive artifact
|
||||
unpacked into the module's directory under the account's home.
|
||||
- The prompt configuration is today's file, as its own owned file in the same directory.
|
||||
- It contributes the zsh code that loads the theme and the configuration.
|
||||
- Today's file has the instant-prompt cache commented out, so the module does not turn it on.
|
||||
- **`zsh-autosuggestions` and `zsh-syntax-highlighting`, new.** Each declares its package and
|
||||
contributes its loader from the distribution's path, in the `normal` and `last` slots.
|
||||
|
||||
**Proof.** The controller's catalogue check over the whole catalogue passes. The modules' tests pass.
|
||||
A rehearsal composition for a node holding all five shows:
|
||||
|
||||
- the `.zshrc` block with the prompt in `normal` and highlighting in `last`;
|
||||
- the environment file with the shell's `PATH` entries;
|
||||
- the service manager's file.
|
||||
|
||||
## WP4 — The service manager's module, finished
|
||||
|
||||
*mesh-catalog. Half a day. From the review of 2026-10-04.*
|
||||
|
||||
**What changes.**
|
||||
|
||||
- System-scope `start`, `stop`, `restart`, `enable` and `disable` escalate with `sudo -n` when the
|
||||
runtime is not root, as the packet filter and intrusion modules do. They name a refusal by how it
|
||||
failed.
|
||||
- User scope reaches the account's manager by its runtime directory, which the runtime's environment
|
||||
lacks.
|
||||
- A failed `systemctl` is an error, not an empty list.
|
||||
- The package resource goes: the service manager is always present, and it collided with the network
|
||||
module's identical declaration on a machine running both.
|
||||
- `status` says whether the mesh declares the unit. The restore note is attached only to such a unit.
|
||||
- Tests cover a fake runner.
|
||||
|
||||
The user-scoped units of mesh-host #72 stay to-be 38's WP6.
|
||||
|
||||
**Proof.** The module's tests. Live, after WP5:
|
||||
|
||||
- `node-service-manager.units` answers in both scopes on a workstation and on a server;
|
||||
- `restart` of a harmless unit answers `ok`.
|
||||
|
||||
## WP5 — Assign and prove
|
||||
|
||||
*Operator-gated. Nothing here runs without the operator's go-ahead.*
|
||||
|
||||
**Order.**
|
||||
|
||||
1. Merge WP1 and roll the host.
|
||||
2. Merge WP2, and push the controller.
|
||||
3. Merge WP3 and WP4, and build the new modules by hand: a new catalogue module's first build is asked
|
||||
for, not automatic.
|
||||
4. On one server, assign `node-env`, `zsh`, `zsh-autosuggestions` and `zsh-syntax-highlighting`, and
|
||||
push. Then check:
|
||||
- `node-login-shell.execute@<server> command="echo $PATH"` shows the shell's entries;
|
||||
- `.zshrc` begins with the block;
|
||||
- the operator's lines follow untouched;
|
||||
- the environment file and the service manager's file exist.
|
||||
5. The operator deletes the duplicated lines, per the zsh module's documentation.
|
||||
6. The other server, then the two workstations, the workstations also with `powerlevel10k`.
|
||||
7. Assign `systemd` everywhere, and prove WP4.
|
||||
8. Unassign one plugin module on one machine. Its line leaves the block at the next push, and nothing
|
||||
else changes.
|
||||
|
||||
The follow-up records to-be 38 names are still owed:
|
||||
|
||||
- what a shell module assigned beside the holder does;
|
||||
- how a person's own environment variable is a setting rather than a line, once issue 168 closes.
|
||||
@@ -43,6 +43,7 @@ document is written and this one's status becomes `implemented`.
|
||||
| [`32-what-a-module-declares.md`](32-what-a-module-declares.md) | **Proposed.** What a module declares and what the bus derives from it: three namespaces, subjects from local names, queues never declared, the five relationships, and the build-publish-deploy lifecycle on one bus | [ADR 0126](../../02-DECISIONS/0126-a-module-declares-its-own-seats.md), [ADR 0127](../../02-DECISIONS/0127-amqp-is-a-provision-not-the-bus.md), superseded by [ADR 0131](../../02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md) (superseding [ADR 0125](../../02-DECISIONS/0125-the-bus-is-the-only-broker.md)), [ADR 0041](../../02-DECISIONS/0041-events-are-a-relationship.md) |
|
||||
| [`37-the-operators-machine.md`](37-the-operators-machine.md) | **In progress.** Every configurable thing on a node is a module, the home included; one default per module varied by settings or kept regions; roles a machine has once as seats with tool contracts; one tool runtime per node on the host side | [ADR 0173](../../02-DECISIONS/0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md), [0174](../../02-DECISIONS/0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md), [0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md), [0176](../../02-DECISIONS/0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md), [0177](../../02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md) |
|
||||
| [`38-building-the-operators-machine.md`](38-building-the-operators-machine.md) | **In progress.** The work of design 37 as packages: the runtime serves many modules, the controller composes one per node, the console becomes its serving mode, the packet filter moves first, then the shell and the service manager — tested on the live mesh by the operator's decision | [ADR 0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md), [0160](../../02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md), [0149](../../02-DECISIONS/0149-the-live-mesh-is-the-test-bed.md) |
|
||||
| [`41-the-shell-and-the-accounts-environment.md`](41-the-shell-and-the-accounts-environment.md) | **In progress.** The shell and the account's environment as modules: an environment module every module contributes variables and `PATH` entries to, shell code contributed to the login shell in named slots, the prompt and plugins as modules, the host giving a login back, and the service manager's module finished | [ADR 0203](../../02-DECISIONS/0203-the-accounts-environment-is-one-modules-and-every-module-contributes-to-it.md), [ADR 0204](../../02-DECISIONS/0204-a-module-contributes-shell-code-to-the-login-shell-in-named-slots.md), [ADR 0205](../../02-DECISIONS/0205-software-the-distribution-does-not-package-ships-as-a-pinned-archive-of-the-module.md) |
|
||||
|
||||
## Not yet written
|
||||
|
||||
|
||||
Reference in New Issue
Block a user