Skip to content

Enable first-run user units one at a time - #10582

Open
fresh3nough wants to merge 4 commits into
omacom:quattrofrom
fresh3nough:fix/10484-enable-user-units-resilient
Open

fresh3nough wants to merge 4 commits into
omacom:quattrofrom
fresh3nough:fix/10484-enable-user-units-resilient

Conversation

@fresh3nough

Copy link
Copy Markdown
Contributor

Summary

Fixes #10484.

install/user/first-run/enable-user-units.sh ran a single multi-unit:

systemctl --user enable --now \
  bt-agent.service \
  ... \
  omarchy-crash-watch.service

under set -euo pipefail. systemctl enable validates the entire list before applying any unit. One missing unit (the reporter hit this with a package that never installed omarchy-brightness-keyboard-auto.service into /usr/lib/systemd/user/) fails the call, aborts the script, and leaves every later omarchy user unit disabled — bt-agent, sleep-lock, fcitx5, crash-watch, migrate-notify, recover-internal-monitor.

Change

  • Enable/start each shipped unit in a loop
  • Warn on individual failure; continue
  • Exit non-zero only if none of the units could be enabled

Note: this tree does not currently ship omarchy-brightness-keyboard-auto.service under default/systemd/user/ (the reporter saw it only under /usr/share/omarchy/... on an older package build). The blast-radius fix is still required so any future missing unit cannot disable the rest.

Evidence

Bug reproduced (source + systemctl semantics)

# pre-fix script
set -euo pipefail
systemctl --user enable --now a.service missing.service
# → Failed to enable unit: Unit missing.service does not exist
# → under set -e, nothing after runs; a.service never enabled if it wasn't already

Fix validated

bash test/shell.d/enable-user-units-test.sh
ok - enable-user-units enables units one at a time
ok - enable-user-units continues after a missing unit
ok - multi-arg enable fails the whole list when one unit is missing
ok - enable-user-units enables every shipped unit when all exist

bash test/shell.d/systemd-test.sh
# still green

Test plan

  • Stub harness: missing mid-list unit still enables later units
  • Stub harness: all present → quiet success
  • Contrast: multi-arg enable fails whole list
  • bash test/shell.d/systemd-test.sh
  • Manual first-run / re-run enable-user-units with a deliberately missing unit name

systemctl enable with multiple unit names validates the whole list before
applying any of it. Under set -e, one missing unit (for example a package
that never installed a unit into /usr/lib/systemd/user/) aborted
enable-user-units.sh and left every later omarchy user unit disabled
(issue omacom#10484).

Enable and start each shipped unit individually, warn on failure, and
only exit non-zero when none of them could be enabled.

Fixes omacom#10484

Signed-off-by: fresh3nough <anonwurcod@proton.me>
omarchybot and others added 3 commits October 2, 2026 09:21
Quattro added owed.service to the first-run list and installs the OWE theme hook after it. Both carry over into the per-unit loop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Quattro's first-run script now installs the OWE theme hook after enabling units. Run against the real command, the test wrote into the caller's ~/.config and failed wherever /usr/share/owe is absent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The exit check named every unit a second time, so a unit added to the list but not to the check was silently left out of it, as owed.service nearly was. Count the enables the loop already made instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@omarchybot omarchybot added the verified Omarchy Triage has verified that this issue is ready for final review label Oct 2, 2026
@omarchybot

Copy link
Copy Markdown
Collaborator

Reviewed by Claude Opus 5.5, with Codex at medium effort as a second opinion. The fix works, and it is still needed: quattro enables every unit in a single systemctl --user enable --now call.

Reproduced on a worker (a VM built from the Omarchy ISO, with real systemd). I hid omarchy-sleep-lock.service and disabled the other units. quattro's script then failed with "Unit omarchy-sleep-lock.service does not exist", exited 1 and left all six other units disabled. It also never got as far as installing the OWE theme hook. This branch enables those six, warns about the missing one, installs the hook and exits 0. With the script reverted to quattro's, the new test fails again.

Pushed to your branch:

  • 557f3033c merges quattro, which had moved under the PR and conflicted. quattro added owed.service to the list and an omarchy-hook-install theme-set /usr/share/owe/10-owe-sync after it. Both carry over: the unit goes into units, and the hook installs after the loop.
  • 7a38fc81d stubs omarchy-hook-install in test/shell.d/enable-user-units-test.sh. Since the merge the script calls it, so the test was running the real command. That wrote into the caller's ~/.config/omarchy/hooks, and the test failed on any machine without /usr/share/owe.
  • 7aa223d5b makes the exit check count the enables the loop already makes. Before, the check listed every unit a second time, so a unit added to units but not to the check would drop out of it silently, as owed.service nearly did. It adds a test that the step fails when no unit enables, and it corrects the comment above the check, since daemon-reload and the hook install can also fail the step.

Tests on the worker: enable-user-units-test.sh (5/5), systemd-test.sh, owe-integration-test.sh and crash-capture-test.sh all pass. In the full ./test/cli and ./test/shell runs, the only failures are the same seven that fail on quattro itself. I also checked that the test fails when the script is reverted to quattro's, and when the all-units-failed exit is disabled.

Second opinion: Codex found no defect in the script and agreed with the reproduction; its independence is not guaranteed. It also pointed out two test blocks I've left in place, since trimming them is your call. The source greps at the top of the test check the script's wording rather than its behaviour, so an equivalent loop written differently would fail them. The "multi-arg enable" block only runs its own systemctl-atomic stub, so it passes whatever the script does. On the pushed head, its second round found nothing left.

For the maintainer: when one unit fails and another enables, the step now succeeds, so first-run marks itself done and the failed unit is never retried. That stops the onboarding notifications replaying on every login, as reported in #10484, but it also means a unit that later becomes available stays disabled until someone enables it. The other way round, the step would fail after trying every unit, and first-run would keep retrying at every login. That choice is yours. This waits on CI and on you.

@omarchybot omarchybot removed the verified Omarchy Triage has verified that this issue is ready for final review label Oct 2, 2026
@greptile-apps

greptile-apps Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 3/5

[Medium risk] Changes how user systemd units are enabled during first-run setup.

The PR is not safe to merge until failed suspend-lock enables remain eligible for retry without preventing other units from enabling.

Findings

  1. P1 Security Failed sleep lock is not retried ▶
  2. P2 Mock misses multi-unit calls ▶

Summary

The PR changes first-run setup to enable user units individually, allowing later units to proceed when one fails, and adds a shell regression test.

  • Individual failures now produce warnings; the step fails only when no unit enables.
  • The partial-success path can permanently skip retries for failed units, including the suspend-lock service.
  • The test mock does not catch every return to multi-unit enables.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[First-run: enable user units] --> B{At least one enables?}
  B -- Yes --> C[Step reports success]
  B -- No --> D[Step reports failure]
  C --> E[First-run marked complete]
  E --> F[Failed units not retried on later logins]
  D --> G[First-run eligible to retry]
Loading

Reviews (1) · Last reviewed commit: "Fail first-run's unit step only when no ..."

Comment on lines +42 to +46
# A unit that fails must not fail the step while others enabled, or first-run
# never completes; fail it only when none did.
if (( ! enabled )); then
echo "error: no omarchy user units could be enabled" >&2
exit 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Failed sleep lock is not retried If omarchy-sleep-lock.service fails to enable while another unit succeeds, this script reports success. First-run is then marked complete, so later logins do not retry the failed unit. The service starts the monitor that locks the session before general suspend; without it, the session can remain unlocked after waking, even if the missing unit is later restored. Preserve the partial enablement, but keep failed units eligible for retry.

How this was verified: The sleep-lock unit starts the suspend-event monitor, while the first-run completion marker prevents another enable attempt.

Comment on lines +35 to +38
# enable --now: fail the injected missing unit, or every unit.
if [[ $1 == "--user" && $2 == "enable" && $3 == "--now" ]]; then
unit=$4
if [[ $unit == "${OMARCHY_TEST_MISSING_UNIT:-}" || -n ${OMARCHY_TEST_ALL_MISSING:-} ]]; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Mock misses multi-unit calls The mock checks only $4 for the missing unit. If the script passed more than one unit per enable call, a missing unit in a later argument could go unnoticed by the mock, while real systemctl would reject the call. The log assertions do not require one unit per call, so the test could pass while missing the regression it is meant to catch. Check every unit argument and assert that each enable call contains just one unit.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

omarchy-brightness-keyboard-auto.service is never installed to /usr/lib/systemd/user/, which also leaves every other omarchy user unit disabled

2 participants