| 1 |
# The installer, reachable over ssh on the installer medium. |
| 2 |
# |
| 3 |
# The headless flow (wiki `alloy-image-minting`, GO alloy 1fc19a8d): a machine |
| 4 |
# with no screen boots the ISO, and the operator continues the install from |
| 5 |
# another machine with `ssh installer@<name>.local`. The credential is the |
| 6 |
# public key baked into the medium at mint time by `alloy image` — see the |
| 7 |
# identity block in the Containerfile. No password auth, no one-time code on a |
| 8 |
# console the machine does not have, no window in which a box on the LAN is |
| 9 |
# takeoverable. |
| 10 |
# |
| 11 |
# THE GATE IS THE ACCOUNT, NOT THIS FILE. |
| 12 |
# |
| 13 |
# `alloy install` writes disks. Reaching it over ssh must be impossible on an |
| 14 |
# installed machine, and the flag that says which machine this is lives on the |
| 15 |
# kernel command line, which sshd_config cannot read: `Match` takes User, |
| 16 |
# Group, Host, LocalAddress, LocalPort, RDomain and Address, and nothing else. |
| 17 |
# (`Match exec` is ssh_config, the client side. It is not available here.) |
| 18 |
# |
| 19 |
# So this file ships everywhere and does nothing on its own. The `installer` |
| 20 |
# account it names is created by alloy-installer-ssh.service, which carries the |
| 21 |
# same `ConditionKernelCommandLine=alloy.installer` as alloy-installer.service |
| 22 |
# and so runs only on the live medium. On an installed system there is no such |
| 23 |
# user, `Match User installer` matches nothing, and the block below is unread. |
| 24 |
# That direction matters: the failure mode of the unit not running is a login |
| 25 |
# that does not happen, rather than one that should not have. |
| 26 |
# |
| 27 |
# It is also why nothing here is written at boot. Copying a drop-in into |
| 28 |
# /etc/ssh at runtime would put the gate in a race with sshd's own start, and |
| 29 |
# losing that race fails open. |
| 30 |
|
| 31 |
Match User installer |
| 32 |
|
| 33 |
# The baked key, at an absolute path. The account has no home directory to |
| 34 |
# hold a `~/.ssh`, and giving it one would mean the credential lived somewhere |
| 35 |
# a half-finished install could have written to. |
| 36 |
AuthorizedKeysFile /usr/lib/alloy/authorized_keys |
| 37 |
|
| 38 |
# The session IS the installer. Not a shell that offers to start it: whatever |
| 39 |
# the client asked to run is replaced by this, so there is no command line to |
| 40 |
# get wrong and no fallback to a prompt. |
| 41 |
# |
| 42 |
# Its exit status is the session's exit status, and there is no retry here or |
| 43 |
# in the unit. A crashed installer has left a disk in an unknown state, and the |
| 44 |
# next person to connect should meet a machine that says so rather than a fresh |
| 45 |
# wizard offering to go over it again. That is the same ruling |
| 46 |
# alloy-installer.service records for tty1 in its `Restart=no`. |
| 47 |
# |
| 48 |
# UNDER `run0`, AND WITHOUT IT THIS DOOR OPENS ONTO NOTHING. `alloy install` |
| 49 |
# does not escalate: on tty1 it does not need to, because |
| 50 |
# alloy-installer.service runs it as root, and here it would land in the |
| 51 |
# unprivileged account this file matches. Measured 2026-08-26 (GO alloy |
| 52 |
# 2cf04f20): the wizard drew and answered all six steps and the erase |
| 53 |
# confirmation, then failed on `Querying root privilege` and every command |
| 54 |
# after it. The whole headless flow reached a wizard that could not write a |
| 55 |
# disk. |
| 56 |
# |
| 57 |
# run0 rather than sudo, per wiki `alloy-privilege`, and the grant it needs is |
| 58 |
# usr/share/polkit-1/rules.d/50-alloy-installer.rules — one action, one user, |
| 59 |
# and inert wherever this file is inert, because both hang off an account that |
| 60 |
# only the live medium creates. |
| 61 |
# |
| 62 |
# `--background=` empty on purpose. run0 tints the terminal to signal a |
| 63 |
# privileged session, which is right for a shell and wrong in front of a |
| 64 |
# full-screen TUI that paints its own surface from the theme. The cue this |
| 65 |
# session needs is the one the installer draws itself. |
| 66 |
ForceCommand run0 --background= /usr/bin/alloy install |
| 67 |
|
| 68 |
# A TUI needs a terminal. ForceCommand alone does not allocate one, and the |
| 69 |
# installer would draw into a pipe and be unusable. |
| 70 |
PermitTTY yes |
| 71 |
|
| 72 |
# Back to global scope, and this line is load-bearing rather than tidy. |
| 73 |
# |
| 74 |
# sshd applies a `Match` to everything that follows it, and `Include` does not |
| 75 |
# end that scope: /etc/ssh/sshd_config puts its |
| 76 |
# `Include /etc/ssh/sshd_config.d/*.conf` at line 15 and then sets keywords for |
| 77 |
# another two hundred. Without this, every one of those — and every drop-in |
| 78 |
# sorting after this one, which is most of Fedora's — would be read as part of |
| 79 |
# the block above and apply to the installer account alone. The symptom is not |
| 80 |
# a parse error; it is a machine whose global ssh policy quietly stopped |
| 81 |
# existing. |
| 82 |
Match all |
| 83 |
|