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
7.7 KiB
Markdown
132 lines
7.7 KiB
Markdown
---
|
|
topic: what runs on it
|
|
status: accepted
|
|
date: 2026-10-04
|
|
deciders: jochen
|
|
reconstructed: false
|
|
extends: 02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md
|
|
---
|
|
|
|
# 203. The account's environment is one module's, and every module contributes to it
|
|
|
|
## Context
|
|
|
|
A variable or a `PATH` entry is a fact about the operator's account. A toolchain needs its directory
|
|
on `PATH`, a version manager needs a variable naming its directory, an agent needs a variable that
|
|
turns one of its behaviours off, and the shell sets an editor. Today every one of these is a line of
|
|
one shell's syntax in one hand-written startup file. [Research 025](../01-RESEARCH/025-how-a-module-plugs-into-the-shell/00-overview.md)
|
|
measured on four machines:
|
|
|
|
- about a quarter of the 65 lines a workstation runs at shell start are environment;
|
|
- written into `.zshrc`, that environment reaches only interactive zsh. It misses the login shell's
|
|
`execute` verb ([ADR 0176](0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md)),
|
|
every script, and every program a graphical session starts;
|
|
- the service manager's place for the account's environment, `~/.config/environment.d/`, holds
|
|
nothing on any machine.
|
|
|
|
The shell module as first written carried some of these lines in its own block and dropped the rest.
|
|
|
|
## Considered Options
|
|
|
|
1. **Each module writes its own lines into the shell's startup file.** Rejected: one shell's syntax,
|
|
read by one kind of start, and the same lines rewritten by every shell module.
|
|
2. **Contribute the facts to the login shell, whose holder renders them.** Rejected: the environment
|
|
then depends on which module holds the shell, every shell module renders the same facts again, and
|
|
the graphical session sees nothing.
|
|
3. **Option 2, and the service manager's holder renders the same facts a second time** into
|
|
`environment.d`. Rejected: one fact set with two owners, whose renderings can disagree, and a
|
|
duty for the service manager unrelated to managing services.
|
|
4. **One file in `environment.d` syntax, sourced by shells.** Rejected: that syntax is close to
|
|
POSIX assignment but not equal, and a value one reader accepts breaks the other.
|
|
5. **Shells read the service manager's environment generator.** Rejected: every shell start then
|
|
runs a process and depends on the service manager, and the output is unquoted.
|
|
6. **A module of its own holds the environment.** One mesh seat, held by one module per node,
|
|
whose files are the account's environment. Every module contributes facts to it, and those facts
|
|
are written in each reader's format. Chosen. It was the operator's proposal.
|
|
|
|
Within option 6, two ways to write the files:
|
|
|
|
- **a. The holder's own code renders what it receives.** This was research 025's starting position.
|
|
Rejected: the code needs something to run it whenever a contribution changes, and the result exists
|
|
only after a machine has applied and run it.
|
|
- **b. The controller renders the facts into the holder's files,** in two named formats, at
|
|
composition. Chosen. The result is in the declaration before any machine applies it, nothing has to
|
|
trigger anything, and the two formats are standards: POSIX shell assignment and the service
|
|
manager's `environment.d`. The controller learns no shell. It writes an assignment in a standard
|
|
syntax, as it already writes a fail2ban stanza a module supplied.
|
|
|
|
## Decision
|
|
|
|
**1. The environment is a node seat, `node-environment`, in the mesh's own set.** One module per node
|
|
holds it, and it is the only writer of the account's environment. The first holder is a module of its
|
|
own (working name `node-env`), with no package and no process.
|
|
|
|
**2. Any module contributes to it with `environment`:**
|
|
|
|
- **`variables`:** names and values. A name is a POSIX variable name and never `PATH`. A value is
|
|
literal; it may use `${machine:…}`, resolved first, and may not contain `$`, a quote, a backslash or
|
|
a line break. The only expansion is the mesh's own, so the two formats cannot read one value
|
|
differently.
|
|
- **`path`:** entries, each placed at the `start` or the `end` of the account's `PATH`.
|
|
|
|
**3. The holder places the rendered environment with two placeholders** in its own files:
|
|
|
|
- **`${environment:posix}`** renders lines a POSIX shell sources:
|
|
- every variable exported;
|
|
- every `PATH` entry added only if missing, so sourcing twice changes nothing.
|
|
- **`${environment:systemd}`** renders the same facts as the service manager's user environment, with
|
|
the account's existing `PATH` kept between the start and the end entries.
|
|
|
|
Each rendered line names the module that contributed it, so the file answers *where did this come
|
|
from*. Contributions are ordered by module name, and then in the order a module declared them.
|
|
|
|
**4. The seat's protocol fixes where the POSIX file is:** `~/.config/mesh/environment.sh` under the
|
|
account's home. A shell sources that path without knowing which module wrote it. The service
|
|
manager's file is `~/.config/environment.d/50-mesh.conf`.
|
|
|
|
**5. Refused at composition:**
|
|
|
|
- two modules on one node setting the same variable, both named;
|
|
- an environment placeholder in a module that does not claim `node-environment`.
|
|
|
|
A node with contributions and no holder writes them nowhere. The holder's absence is visible in the
|
|
node's assignments, and no contributor is refused for it, because a missing `PATH` entry is a gap, not
|
|
a broken machine.
|
|
|
|
## Consequences
|
|
|
|
- A shell's part in the environment is one line in its always-read startup file, sourcing the
|
|
POSIX file. A second shell module writes the same line in its own syntax, and no contributor
|
|
changes when the login shell does.
|
|
- The graphical session sees the same `PATH` as the terminal, from the same facts.
|
|
- The controller gains one gathered field and two renderers. Both are tested byte for byte, like
|
|
the jails a node composes ([to-be 31](../03-DESIGN/01-to-be/31-a-module-declares-its-fail2ban-jail.md)).
|
|
- What a person sets for themselves stays theirs: variables of their own sit in their own lines of
|
|
their shell's file, read after the mesh's.
|
|
- **What got harder:** a value that needs another variable expanded (`$HOME`, `$XDG_CONFIG_HOME`)
|
|
must be written with the mesh's own `${machine:…}` facts, or it is refused. Expansion at shell start
|
|
is exactly what made one value mean two things in two readers.
|
|
- Once [issue 168](../04-ISSUES/168-a-setting-reaches-every-file-and-contribution/00-report.md)
|
|
closes, a value a person varies becomes a setting of the module that contributes it
|
|
([ADR 0174](0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md)).
|
|
|
|
## How it is checked
|
|
|
|
| Rule | Checked by |
|
|
|---|---|
|
|
| Both renderings, byte for byte, from a fixed set of contributions | the controller's environment tests |
|
|
| Sourcing the POSIX rendering twice leaves `PATH` unchanged | the same tests, running `sh` over the rendering |
|
|
| A variable set by two modules is refused, naming both | the controller's resolve test |
|
|
| An environment placeholder outside the holder is refused | the catalogue check, which registration runs |
|
|
| A value with `$`, a quote, a backslash or a line break is refused | the manifest's parse test |
|
|
| The zsh holder sources the path the seat fixes | the catalogue's zsh test |
|
|
|
|
## 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 0177](0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md),
|
|
[ADR 0182](0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md)
|
|
- [To-be 41](../03-DESIGN/01-to-be/41-the-shell-and-the-accounts-environment.md)
|