feat(install): pick per-distro defaults from user intent #439

Merged
enricobuehler merged 4 commits from luxus/punktfunk:feat/install-intent-defaults into main 2026-08-29 17:40:49 +00:00
Member

User-facing fact changed? Yes — docs-site/content/docs/install.mdx, bazzite.mdx, gamescope.md.

The guided installer asked about internals (usbip, linger, GameStream) and defaulted every optional to no. Holding Enter — or curl | sh with no TTY — on Bazzite/Nobara produced a degraded pad and a host that died at logout, which those distro pages already tell people not to do.

This PR:

  • asks about intent (full controller, third-party clients, clipboard, start at boot)
  • derives defaults from the already-detected family (sysext / Bazzite / Nobara / ujust → group + linger; active Sunshine/Apollo → GameStream; no graphical seat → linger)
  • prints a one-screen summary before the first sudo so --yes is auditable
  • keeps every flag and PUNKTFUNK_INSTALL_* twin; --yes uses sudo -n so a missing ticket fails instead of hanging
  • asserts the matrix in scripts/ci/check-install-defaults.sh (docs-drift + installer-smoke)

Checked --dry-run --yes --no-start on lea-oma (Omarchy 4.0.1): SSH/seatless → linger yes, group no; fake desktop seat → both no.

Fixes #431

**User-facing fact changed?** Yes — `docs-site/content/docs/install.mdx`, `bazzite.mdx`, `gamescope.md`. The guided installer asked about internals (usbip, linger, GameStream) and defaulted every optional to no. Holding Enter — or `curl | sh` with no TTY — on Bazzite/Nobara produced a degraded pad and a host that died at logout, which those distro pages already tell people not to do. This PR: - asks about intent (full controller, third-party clients, clipboard, start at boot) - derives defaults from the already-detected family (`sysext` / Bazzite / Nobara / `ujust` → group + linger; active Sunshine/Apollo → GameStream; no graphical seat → linger) - prints a one-screen summary before the first sudo so `--yes` is auditable - keeps every flag and `PUNKTFUNK_INSTALL_*` twin; `--yes` uses `sudo -n` so a missing ticket fails instead of hanging - asserts the matrix in `scripts/ci/check-install-defaults.sh` (docs-drift + installer-smoke) Checked `--dry-run --yes --no-start` on lea-oma (Omarchy 4.0.1): SSH/seatless → linger yes, group no; fake desktop seat → both no. Fixes #431
luxus added 1 commit 2026-08-28 22:27:50 +00:00
feat(install): pick per-distro defaults from user intent
android / android (pull_request) Canceled after 0s
apple / swift (pull_request) Canceled after 0s
apple / distribute (pull_request) Canceled after 0s
apple / screenshots (pull_request) Canceled after 0s
ci / rust (pull_request) Canceled after 0s
ci / rust-arm64 (pull_request) Canceled after 0s
ci / web (pull_request) Canceled after 0s
ci / docs-site (pull_request) Canceled after 0s
ci / bun-nix (pull_request) Canceled after 0s
ci / docs-drift (pull_request) Canceled after 0s
installer-smoke / derived defaults (pull_request) Canceled after 0s
installer-smoke / smoke (arch) (pull_request) Canceled after 0s
installer-smoke / smoke (debian-13) (pull_request) Canceled after 0s
installer-smoke / smoke (fedora-44) (pull_request) Canceled after 0s
edf4effb1e
The guided installer defaulted every option to no, so Bazzite and
Nobara users who held Enter got a degraded pad and no linger —
the opposite of those distro pages. Prompts now name the intent,
defaults follow the box, and a summary prints before the first sudo.

Fixes #431
luxus requested review from enricobuehler 2026-08-28 22:39:41 +00:00
enricobuehler added 1 commit 2026-08-29 16:12:43 +00:00
fix(install): sudo -n, --no-start linger, and couch-box scope
apple / swift (pull_request) Skipped
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
ci / docs-drift (pull_request) Successful in 38s
ci / docs-site (pull_request) Successful in 47s
ci / web (pull_request) Successful in 55s
installer-smoke / derived defaults (pull_request) Successful in 17s
installer-smoke / smoke (debian-13) (pull_request) Failing after 24s
installer-smoke / smoke (arch) (pull_request) Failing after 25s
ci / bun-nix (pull_request) Successful in 1m49s
android / android (pull_request) Failing after 2m17s
ci / rust-arm64 (pull_request) Failing after 2m10s
ci / rust (pull_request) Failing after 2m23s
installer-smoke / smoke (fedora-44) (pull_request) Failing after 1m18s
9bc6c606fb
Review of the intent-based defaults. Five fixes, three of them defects
the new matrix could not see, so each one gains a case that fails on the
code as it stood.

run(): the `sudo -n` rewrite keyed off --yes, but no terminal already
implies --yes and never the reverse, so `sh install.sh --yes` typed at a
prompt sent `sudo -n` and died on the first step without a cached ticket.
sudo reads /dev/tty, not stdin, so a terminal under --yes prompts fine.
Gated on `[ -z "$TTY" ]` instead, which is the case that actually has
nobody to type a password.

linger: the summary promised "start at boot" unconditionally, but the
`loginctl enable-linger` sat inside `if [ "$START" = 1 ]`, so
`--no-start` printed the promise and dropped it. Every case in the matrix
passes --no-start, so the gate was certifying exactly that. Linger is
configuration, not starting — moved out of the START branch, still ahead
of the `systemctl --user` probe it has to precede.

couch_box(): keying off FAMILY=sysext and a `ujust` on PATH covered
Silverblue, Kinoite, Bluefin and Aurora — desktop workstations that ship
rpm-ostree and ujust and got the group plus linger under --yes without
being asked. Narrowed to `like bazzite || like nobara`, which is what
install.mdx already claimed; gamescope.md corrected to match.

The group prompts name the grant again (usbip attach, only on a machine
you trust, with the docs link). Asking about intent is right, but under
--yes the summary is the only place it is stated, so that line names the
group too.

sunshine_active(): any non-zero exit from `detect-conflicts` read as
"Sunshine is running", so a crashed or too-old host binary would flip
GameStream on and open the plain-HTTP pairing port unasked. Only exit 1
is an answer now; anything else falls through to the unit probe.

check-install-defaults.sh also lifts run() out of the script and drives
it against a stub sudo — --dry-run returns before run() executes
anything, so no dry-run case can reach it.
enricobuehler added 1 commit 2026-08-29 16:49:13 +00:00
fix(install): drop the sudo -n rewrite that broke the shim
apple / swift (pull_request) Skipped
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
android / android (pull_request) Failing after 1m15s
ci / web (pull_request) Successful in 1m29s
ci / docs-drift (pull_request) Successful in 58s
ci / docs-site (pull_request) Successful in 1m15s
installer-smoke / derived defaults (pull_request) Successful in 28s
ci / rust-arm64 (pull_request) Failing after 2m7s
installer-smoke / smoke (arch) (pull_request) Failing after 44s
ci / bun-nix (pull_request) Successful in 2m47s
installer-smoke / smoke (fedora-44) (pull_request) Failing after 1m14s
installer-smoke / smoke (debian-13) (pull_request) Failing after 1m34s
ci / rust (pull_request) Canceled after 5m59s
581e25dd4a
installer-smoke went red on the root-container leg:

  /tmp/tmp.9gBiJE8x4x/sudo: 2: exec: -n: not found

That shim is install.sh's own — the one it writes when running as root
with no sudo, so the verbatim `sudo …` lines from platforms.json still
work — and it is `exec "$@"`. Rewriting `sudo install -d …` to
`sudo -n install -d …` makes it `exec -n install -d …`, which is not a
command. The smoke job covers that path on purpose.

Not introduced by the previous commit: driving the run() from edf4effb
with no TTY and --yes against the same shim fails identically. Narrowing
the condition moved which sessions hit it, not whether they do.

So the rewrite goes. It buys nothing it claimed: with no terminal, sudo
already exits at once with "no tty present and no askpass program
specified" rather than hanging, and where an askpass helper IS set up,
-n breaks the one unattended path that works. Deleting it also drops a
global sed over an arbitrary command string.

The gate grows the case that would have caught this. Its stub sudo is
now that shim byte for byte, and it asserts the command actually runs.
First attempt at that check was vacuous — run() echoes each command
before running it, so a marker word matched the echo and passed on known
-bad code. The probe is `sudo expr 40 + 2` and the assertion is on 42,
which appears nowhere in the command text.
enricobuehler added 1 commit 2026-08-29 16:55:27 +00:00
fix(install): skip linger where systemd is not PID 1
apple / swift (pull_request) Skipped
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
ci / docs-drift (pull_request) Successful in 34s
ci / bun-nix (pull_request) Successful in 53s
ci / docs-site (pull_request) Successful in 56s
installer-smoke / derived defaults (pull_request) Successful in 26s
ci / web (pull_request) Successful in 1m11s
android / android (pull_request) Failing after 1m35s
ci / rust-arm64 (pull_request) Failing after 1m43s
installer-smoke / smoke (arch) (pull_request) Successful in 1m29s
ci / rust (pull_request) Failing after 2m15s
installer-smoke / smoke (debian-13) (pull_request) Successful in 2m29s
installer-smoke / smoke (fedora-44) (pull_request) Successful in 2m31s
39a42cf35e
smoke (arch) got past the sudo fix and died one step later, on the
linger step this branch moved out of --no-start:

  ==> Starting at boot with nobody logged in
    + sudo loginctl enable-linger "$USER"
  System has not been booted with systemd as init system (PID 1).

The smoke containers have no logind, so the call cannot work and linger
would mean nothing there. Reverting the move would put back the summary
that promises start-at-boot and silently drops it under --no-start, so
guard the call instead: run it when systemd is PID 1, warn and carry on
when it is not. An install that otherwise succeeded should not fail on
its last step for a setting the box cannot hold.

--dry-run still prints the command — it reports what a real box would do,
and the defaults matrix asserts on it.

The container path itself is checked by installer-smoke, which is where
it surfaced; no dry-run case can reach it.
enricobuehler merged commit 462a0e30c4 into main 2026-08-29 17:40:49 +00:00
enricobuehler deleted branch feat/install-intent-defaults 2026-08-29 17:41:03 +00:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#439