Skip to main content

max / alloy

manual: state the ssh posture, and how a key gets onto a machine
Author: Max Johnson <me@maxj.phd> · 2026-08-21 01:45 UTC
Signed with PGP, not checked
Commit: b147a34064a0e0cd43f3d2e05792bbc7bc867288
Parent: b4fb21c
3 files changed, +122 insertions, -6 deletions
@@ -68,6 +68,34 @@
68 68 no account and a locked root. The Alloy installer ISO comes from
69 69 `build/build-iso.sh` and nowhere else.
70 70
71 + ## Baking in a key and a hostname
72 +
73 + A medium can carry the public key and the machine name the install will use,
74 + which is what makes a headless install possible: you reach the installer over
75 + SSH rather than typing at a console the machine does not have.
76 +
77 + build/build-iso.sh \
78 + --build-arg ALLOY_SSH_KEY="$(cat ~/.ssh/id_ed25519.pub)" \
79 + --build-arg ALLOY_HOSTNAME=workshop
80 +
81 + `ALLOY_SSH_KEY` takes the key itself and not a path to it, because the build
82 + container cannot see your filesystem. The build refuses anything that is not a
83 + public key, and refuses outright if it looks like a private one.
84 +
85 + The key lands at `/usr/lib/alloy/authorized_keys` on the medium and does two
86 + jobs from there. It is the credential for the `installer` account, which exists
87 + only on the live medium and drops you straight into `alloy install`. And the
88 + installer's account screen offers it back to you as the key for the machine
89 + being installed, so the credential you connect with is the one that ends up on
90 + the finished system unless you replace it.
91 +
92 + `alloy image` is the same thing with a form around it: it asks for a profile, a
93 + browser, languages, a hostname and a path to a public key, and runs the script
94 + above with what you chose.
95 +
96 + Chapter 3 covers the install itself, and what the SSH posture means once the
97 + machine is up.
98 +
71 99 ## Changing what goes in
72 100
73 101 The Containerfile at the repo root is the whole package list, and it is meant
@@ -18,8 +18,11 @@
18 18 **2. Hostname.** What the machine is called on your network and in your
19 19 prompt.
20 20
21 - **3. Account.** Your username and password. Alloy creates one account and does
22 - not enable a root login.
21 + **3. Account.** Your username, your password, and an SSH public key. Alloy
22 + creates one account and does not enable a root login. The key is optional at
23 + this screen and it is not optional if you ever want to reach this machine over
24 + the network; see "Signing in over SSH" below. On a medium you minted with a key
25 + baked in, this field arrives already filled with that key.
23 26
24 27 **4. Encryption.** Optional, and if you say yes you type a passphrase and then
25 28 see a recovery phrase. Read the next section before deciding.
@@ -64,9 +67,64 @@
64 67 A machine installed from a locally built medium has its update timer disabled.
65 68 It will never poll anything. Chapter 8 covers what updating means instead.
66 69
70 + ## Signing in over SSH
71 +
72 + Alloy accepts a key and nothing else. `PasswordAuthentication` is off, the
73 + keyboard-interactive path that can authenticate a password behind it is off,
74 + and root is key-only. That is the policy for every install, both profiles, and
75 + it is set in `/etc/ssh/sshd_config.d/10-alloy.conf` rather than left to
76 + whatever the Fedora base happens to default to.
77 +
78 + The account screen is where a key gets onto the machine. Paste the public half
79 + of a keypair you already hold, usually the contents of `~/.ssh/id_ed25519.pub`
80 + on the machine you will connect from. The installer checks it is a public key
81 + sshd will actually read, writes it to the new account's
82 + `~/.ssh/authorized_keys`, and sets the modes sshd insists on.
83 +
84 + You may finish an install without a key, and for a desktop that is a reasonable
85 + answer: you are sitting at the greeter, so you log in on the console and add a
86 + key afterwards. The installer says so on the summary and again on the gate
87 + before it writes the disk, because from the far side of a reboot there is no
88 + way to tell a machine you did that to from a broken one.
89 +
90 + On a machine with no screen it is not a reasonable answer, and there is nothing
91 + to recover with. Put a key on the account screen.
92 +
93 + ### Adding a key after the fact
94 +
95 + From a console session on the machine:
96 +
97 + ```
98 + mkdir -p ~/.ssh
99 + chmod 700 ~/.ssh
100 + cat >> ~/.ssh/authorized_keys # paste the key, then Ctrl+D
101 + chmod 600 ~/.ssh/authorized_keys
102 + ```
103 +
104 + The modes are not decoration. sshd refuses to read an `authorized_keys` that
105 + anyone but its owner can write, and it refuses silently as far as the client is
106 + concerned: you get "permission denied" and the reason goes only to the server's
107 + journal.
108 +
67 109 ## Installing without a screen
68 110
69 - `alloy install` needs a terminal, which means a headless install is not a
70 - one-command flow today. Collecting an SSH key at install time, and minting
71 - media with a key and hostname already baked in, are both filed work rather than
72 - shipped features. For now, install on a machine with a display attached.
111 + `alloy install` needs a terminal, and on a machine with no display that
112 + terminal comes over the network. Mint the medium with a key and a hostname
113 + baked in, boot it, and continue from another machine:
114 +
115 + ```
116 + ssh installer@<hostname>.local
117 + ```
118 +
119 + The connection lands in the installer itself rather than in a shell, so there
120 + is no command to get wrong. The key you connect with is also what the account
121 + screen offers you for the machine being installed, so you are not pasting back
122 + the credential you are already holding.
123 +
124 + Two things make that flow work and both are worth knowing. The `installer`
125 + account exists only on the live medium, so an installed machine has no such
126 + user and no way to reach `alloy install` over SSH. And the machine is found by
127 + mDNS, which means the LAN it is on and not Tailscale; a freshly booted medium
128 + has joined no mesh.
129 +
130 + Chapter 2, "Baking in a key and a hostname", covers minting such a medium.
@@ -167,6 +167,36 @@
167 167
168 168 ---
169 169
170 + ## Network access
171 +
172 + ### SSH says permission denied, and I know the password is right
173 +
174 + The password is not the problem. Alloy accepts a key and nothing else:
175 + `PasswordAuthentication` is off, so is the keyboard-interactive path behind it,
176 + and root is key-only. A correct password gets the same refusal a wrong one
177 + does.
178 +
179 + If you can reach a console on the machine, add your key there. Chapter 3,
180 + "Adding a key after the fact", is the four lines.
181 +
182 + If you cannot reach a console, this is the case with no remote fix. A headless
183 + machine installed with no key has no way in, and the way back is a screen, a
184 + keyboard, or a reinstall from a medium with a key baked into it.
185 +
186 + ### I added a key and it still refuses
187 +
188 + Check the modes. sshd will not read an `authorized_keys` that anyone but its
189 + owner can write, and it declines without telling the client why:
190 +
191 + chmod 700 ~/.ssh
192 + chmod 600 ~/.ssh/authorized_keys
193 +
194 + Then check the file holds the **public** half, one key per line, starting with
195 + something like `ssh-ed25519`. The reason for a refusal is always in the
196 + server's journal even when the client is told nothing:
197 +
198 + journalctl -u sshd -n 50
199 +
170 200 ## Disks and boot
171 201
172 202 ### The machine asks for a passphrase it never asked for before