Skip to main content

max / alloy

11.7 KB · 215 lines History Blame Raw
1 # Alloy system-service presets
2 #
3 # systemd reads this at boot / on first user login to decide which
4 # units are enabled or disabled by default. Rationale for each entry:
5 #
6 # - greetd: boot lands on the tuigreet TUI on VT1.
7 # - swayosd-libinput-backend: the caps-lock and num-lock overlays.
8 # - tailscaled: opt-in only. Enrollment happens via `alloy tail`
9 # (see docs/CONTINUITY.md — Alloy does not phone home
10 # before the user says yes).
11 #
12 # The base image ships its browser as an RPM; sandboxed apps are
13 # user-installed on demand from a remote the user adds (Alloy names
14 # no catalog — see docs/STACK.md), so no first-boot Flatpak
15 # provisioning is needed.
16 #
17 # Format: `enable|disable <unit>`. First match wins; entries without
18 # a match fall through to /usr/lib/systemd/system-preset/ defaults.
19
20 enable greetd.service
21 # Paints the console's ANSI slots with Akari before greetd's tuigreet draws on
22 # them, so the first surface a user sees on boot is themed rather than stock.
23 # See alloy-vtrgb.service; the map it applies is generated in the Containerfile.
24 enable alloy-vtrgb.service
25 # Inert unless the kernel command line carries `alloy.installer`, which only
26 # the installer ISO sets, so enabling it on every install is safe and keeps
27 # the live medium from needing a modified copy of the image.
28 enable alloy-installer.service
29 # The ssh half of the same flow, and inert by the same flag. It creates the
30 # `installer` account that etc/ssh/sshd_config.d/20-alloy-installer.conf names,
31 # so a headless machine can be installed from another one. Enabled on every
32 # install for the reason above: the live medium is the image, not a modified
33 # copy of it.
34 #
35 # This line is the whole gate. Without the account the sshd drop-in matches
36 # nothing, so removing this entry disables the feature rather than opening it —
37 # but a line that read `enable` on a unit missing its condition would be the
38 # other direction, which is why the condition lives in the unit and not here.
39 enable alloy-installer-ssh.service
40 # The third piece of the same flow, gated by the same flag, added with the
41 # firewall on 2026-08-22. A server-profile medium's zone does not allow mDNS,
42 # so without this a headless install medium boots and cannot be found at
43 # `<name>.local`. It opens mDNS at runtime only; see the unit.
44 enable alloy-installer-firewall.service
45 # Lays the console and shop down as layered packages on a freshly installed
46 # machine, from the repo that rides inside the image at /usr/share/alloy/rpm,
47 # so it needs no network and nothing copied off the medium. They cannot ship
48 # inside the image: a component the base carries can never be replaced
49 # client-side, so an image carrying them is an image whose console can never be
50 # fixed. See alloy-layer-components.service and build/layertest.
51 #
52 # Enabled on every install, and gated in the unit rather than here. Both of its
53 # conditions have to hold — the repo present, and the console absent — so on a
54 # machine that already has a console it is a no-op, and on one installed from
55 # an older medium it is not its business. Reading "already ran" off the
56 # filesystem rather than a stamp is what makes it answer correctly after a
57 # rollback or a deliberate uninstall.
58 enable alloy-layer-components.service
59 # alloy-debug-shell@ is deliberately not listed here. It is a template, and
60 # `systemctl preset-all` cannot instantiate one from a preset line: a line
61 # naming `alloy-debug-shell@tty9.service` matches no unit file and is ignored
62 # without a word, which reads exactly like the feature being broken. Its
63 # instances are enabled by name in the Containerfile instead.
64
65 # Volume and brightness overlays need no daemon beyond `swayosd-server`,
66 # which the sway config exec's, but the lock-key ones do: reading caps-lock
67 # and num-lock state means watching the input devices, which a user session
68 # cannot do. That is why this half of SwayOSD ships as a system unit, and
69 # it is the only part of the package that has a unit at all. Without it the
70 # sway config's `bindsym --release Caps_Lock` has nothing behind it, which
71 # was the state until 2026-07-21.
72 #
73 # The privilege is real and worth naming: the daemon opens the libinput
74 # devices to watch modifier state. Accepted because the alternative is a
75 # bind that silently does nothing, and because it reads state rather than
76 # injecting it.
77 enable swayosd-libinput-backend.service
78
79 # rasdaemon ships its unit disabled, so installing the package is not enough:
80 # without this line nothing collects machine-check and EDAC events, and
81 # `ras-mc-ctl --error-count` reads zero because zero is what was counted, not
82 # what happened. That is the failure mode ECC memory is bought to avoid, and
83 # it is silent. Its two siblings need no line here: smartmontools and
84 # lm_sensors both enable their own units from %post scriptlets.
85 #
86 # Cost is a daemon on every install, including laptops with no ECC. It is
87 # small, it reads rather than writes, and on a machine with no EDAC driver it
88 # simply collects nothing.
89 enable rasdaemon.service
90
91 # avahi, like sshd below, was already enabled by its own package preset and by
92 # nobody's decision. Stated here because it is now load-bearing: a headless
93 # machine minted with a baked hostname is found at `<name>.local`, so the
94 # responder going away would break the install flow rather than merely remove a
95 # convenience. The resolving half is nss-mdns, which needs no unit.
96 #
97 # The exposure is a multicast responder on every install, answering on the local
98 # link only. Bounded by what mDNS is: TTL 1, no routing past the subnet.
99 enable avahi-daemon.service
100
101 # sshd was already on before this line existed: openssh-server is in
102 # fedora-bootc:43 and Fedora enables it in 90-default.preset, which this file
103 # never overrode. So the state does not change here. What changes is that it
104 # is now a decision (Max, 2026-07-25) rather than something inherited from a
105 # server base and never looked at.
106 #
107 # Kept on because a machine you cannot reach when the display stack is the
108 # thing that broke is a machine you debug with a USB stick. That argument is
109 # not specific to the bench build host; it is the same on a laptop.
110 #
111 # The exposure is real and worth stating rather than eliding: a listening
112 # daemon on every install, on any network the machine joins. And it is wider
113 # than "you need a key" — `sshd -T` on fedora-bootc:43 reports
114 # `passwordauthentication yes`. Root is key-only (`permitrootlogin
115 # without-password`) and keyboard-interactive is off, but the uid 1000 account
116 # `alloy install` creates is reachable with its password from anywhere that can
117 # route to the machine. Whether that stays true is a separate decision from
118 # this line; see GoingsOn alloy d1fed0d7.
119 #
120 # Note this is the opposite default from tailscaled below, which is opt-in:
121 # tailscale reaches OUT to a service and is a privacy question, sshd listens
122 # and is an access question, and they do not resolve the same way.
123 enable sshd.service
124
125 # firewalld, and it is the enforcement of every exposure comment in this file
126 # rather than a new capability. Until 2026-08-22 there was no host firewall at
127 # all, so each `enable` line above stated an exposure that nothing bounded: the
128 # sshd note two paragraphs up describes a listener reachable from any network
129 # the machine joins, and that was literally true.
130 #
131 # The zone is what the policy is, and the two profiles differ there rather than
132 # here: `public` on a client (ssh, mdns, dhcpv6-client, which is exactly what
133 # this file enables), Alloy's own `alloy-server` zone on a server, which is the
134 # same minus mdns. Both are asserted at build time; see the Containerfile.
135 #
136 # The exposure this line changes is inbound only. Outbound is untouched, and
137 # firewalld is not an application firewall.
138 #
139 # One interface is exempt and it is deliberate: tailscale0 is bound to the
140 # `trusted` zone (etc/firewalld/zones/trusted.xml), because tailscaled writes
141 # its own nftables tables and firewalld would otherwise drop every inbound
142 # tailnet connection. That file carries the measurement and the cost.
143 enable firewalld.service
144
145 disable tailscaled.service
146
147 # bluetooth was on before this line existed, and that was the problem: Fedora's
148 # 90-default.preset carries `enable bluetooth.*`, so every Alloy install has had
149 # a Bluetooth daemon by inheritance rather than by decision. Same shape as sshd
150 # and avahi above, and stated here for the same reason.
151 #
152 # Kept on. Alloy is laptop-first, and on a laptop wireless headphones and a
153 # wireless mouse are ordinary rather than exotic. The audio half already works
154 # and needs nothing here: PipeWire's bluez5 SPA plugins ship in the image,
155 # including the LC3 and AAC codecs, so a paired headset plays without further
156 # packages.
157 #
158 # The surface that was missing is `alloy bluetooth`, shipped 2026-08-05. It
159 # fronts `bluetoothctl` rather than bluez's D-Bus, which is a correction to what
160 # this comment said before: the console's promise is that the log pane shows the
161 # argv that ran, and a D-Bus call has no argv to show. bluetuith was the obvious
162 # ready-made TUI and is not in the Fedora repositories, so taking it would mean
163 # another COPR; not worth it for a view this project builds itself anyway.
164 #
165 # The verb is not offered on the server profile, where the Containerfile's
166 # overlay disables this unit.
167 #
168 # The exposure is a daemon holding an adapter that can be discoverable. bluez
169 # does not make it discoverable on its own — that is a `bluetoothctl` command a
170 # user runs — so the default state is a radio that scans when asked and answers
171 # nobody.
172 enable bluetooth.service
173
174 # cups.socket, not cups.service, and the distinction is the whole point: the
175 # socket is what makes printing cost nothing on the machines that never print.
176 # Nothing runs until something connects to /run/cups/cups.sock, and then systemd
177 # starts the daemon. Enabling cups.service instead would buy a daemon on every
178 # laptop for a feature most sessions never touch.
179 #
180 # cups.path goes with it: it watches the spool directory so a job queued while
181 # the daemon is down still wakes it.
182 #
183 # Fedora's 90-default.preset already enables both, so this changes no state. It
184 # is here because "printing works" should be an Alloy decision with the
185 # driverless-only position behind it (see the Containerfile printing block and
186 # docs/STACK.md), rather than a default inherited from a server base and never
187 # examined. That is the same correction made for sshd and avahi.
188 #
189 # The exposure is a unix socket under /run, owned by root and reachable by the
190 # local machine only. CUPS' web UI on localhost:631 rides the same daemon and is
191 # not a network listener.
192 enable cups.socket
193 enable cups.path
194
195 # The USB keyboard gate, which is what makes deny-unknown safe to arm at all.
196 # It drops USB enforcement whenever the machine has zero usable keyboards and
197 # puts it back when one appears (usr/bin/alloy-usb-gate, and the udev rule that
198 # makes it continuous).
199 #
200 # Enabled on every client install, including the overwhelming majority where
201 # usbguard is not armed and this does nothing. That is safe by construction
202 # rather than by luck: the script only ever *starts* usbguard to undo a stop it
203 # performed itself, recorded in a stamp file under /run, so on a machine where
204 # nobody turned enforcement on it reads the keyboard count and exits. It cannot
205 # be the thing that arms deny-everything.
206 #
207 # Client only. This line matches nothing on the server profile, which is the
208 # silent no-op this file warns about above and is deliberate here: that profile
209 # has no keyboard by design, so a gate reading "zero keyboards" would hold
210 # enforcement off permanently on exactly the machines that need it most. Its
211 # recovery path is the provider console. The Containerfile removes the unit
212 # there and asserts it is gone, which is what keeps this no-op intentional
213 # rather than a rename nobody noticed.
214 enable alloy-usb-gate.service
215