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

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
This commit is contained in:
luxus
2026-08-29 00:25:40 +02:00
parent 95a2683aae
commit edf4effb1e
7 changed files with 284 additions and 40 deletions
+11
View File
@@ -18,14 +18,25 @@ on:
paths:
- scripts/install.sh
- data/platforms.json
- scripts/ci/check-install-defaults.sh
- .gitea/workflows/installer-smoke.yml
pull_request:
paths:
- scripts/install.sh
- data/platforms.json
- scripts/ci/check-install-defaults.sh
- .gitea/workflows/installer-smoke.yml
jobs:
defaults:
name: derived defaults
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Assert per-family installer defaults
run: sh scripts/ci/check-install-defaults.sh
smoke:
name: smoke (${{ matrix.family }})
runs-on: ubuntu-24.04
+4 -2
View File
@@ -9,8 +9,10 @@ Steam **Gaming Mode** and the **KDE desktop**, and follows the box when you swit
Nothing in the config picks a mode.
**One command instead?** The [guided installer](/docs/install#guided-install-preview) (preview) does
everything on this page — it detects the distro, installs the host, joins the groups, opens the
firewall and starts it:
everything on this page — it detects the distro, installs the host, joins the `input` and
`punktfunk` groups, enables linger, opens the firewall and starts it. Those last two match the
hand steps below (Bazzite hosts are usually headless, and the virtual Steam Deck pad needs the
group):
<Installer />
+4 -3
View File
@@ -54,9 +54,10 @@ Versions before this one stopped the display manager for the stream's duration
root helper, the `punktfunk` group, and lingering, and left the box with nothing able to start a
desktop session, so Steam's own "Switch to Desktop" hung until a reboot.
> **Join the `punktfunk` group on any box you stream Game Mode from.** The takeover itself no
> longer needs it — the group now gates the usbip nodes the virtual Steam Deck pad attaches
> through, so without it the pad arrives as an ordinary Xbox 360 controller:
> **Join the `punktfunk` group on any box you stream Game Mode from.** The guided installer
> defaults to yes on Bazzite, Nobara, and other `ujust` / Fedora Atomic boxes. The takeover
> itself no longer needs the group — it now gates the usbip nodes the virtual Steam Deck pad
> attaches through, so without it the pad arrives as an ordinary Xbox 360 controller:
>
> ```sh
> sudo usermod -aG punktfunk "$USER" # then log out and back in
+11 -9
View File
@@ -35,15 +35,17 @@ Prefer to read what runs first (it's plain `sh`, ~450 lines):
<Installer inspect />
It asks before anything optional (Moonlight compat, the shared clipboard, the `punktfunk` group,
starting at boot) and every answer has a default, so `sh install.sh --yes` — or piping it with no
terminal — runs unattended; `--channel stable|canary`, `--mgmt-port`, `--no-start`, `--uninstall` and the environment
twins (`PUNKTFUNK_INSTALL_YES`, `PUNKTFUNK_INSTALL_CHANNEL`, `PUNKTFUNK_INSTALL_GAMESTREAM`,
`PUNKTFUNK_INSTALL_CLIPBOARD`, `PUNKTFUNK_INSTALL_PUNKTFUNK_GROUP`, `PUNKTFUNK_INSTALL_LINGER`,
`PUNKTFUNK_INSTALL_MGMT_PORT`) are listed by `--help`. It covers Ubuntu/Debian, Fedora, Arch-family
and Bazzite/Fedora Atomic; NixOS, SteamOS and Windows it points at their pages. **Preview:** the
per-system pages above remain the documented path, and the script runs exactly the install commands
they show (CI fails if the two drift apart). Re-running is safe.
It asks about intent, not internals (full controller, third-party Moonlight clients, clipboard,
start at boot) and every answer has a default, so `sh install.sh --yes` — or piping it with no
terminal — runs unattended. The default follows the box: Bazzite and Nobara join the `punktfunk`
group and linger; an active Sunshine/Apollo host opts into Moonlight compat; a seatless SSH
session lingers. Clipboard stays off. `--channel stable|canary`, `--mgmt-port`, `--no-start`,
`--uninstall` and the environment twins (`PUNKTFUNK_INSTALL_YES`, `PUNKTFUNK_INSTALL_CHANNEL`,
`PUNKTFUNK_INSTALL_GAMESTREAM`, `PUNKTFUNK_INSTALL_CLIPBOARD`, `PUNKTFUNK_INSTALL_PUNKTFUNK_GROUP`,
`PUNKTFUNK_INSTALL_LINGER`, `PUNKTFUNK_INSTALL_MGMT_PORT`) are listed by `--help`. It covers
Ubuntu/Debian, Fedora, Arch-family and Bazzite/Fedora Atomic; NixOS, SteamOS and Windows it points
at their pages. **Preview:** the per-system pages above remain the documented path, and the script
runs exactly the install commands they show (CI fails if the two drift apart). Re-running is safe.
`--channel` also **switches** a host that is already installed — see
[Release Channels](/docs/channels#switch-an-installed-box-between-channels). Without the flag the
+2
View File
@@ -31,6 +31,7 @@
# 7. The installer under --dry-run against faked os-release files detects every family it claims
# to (and --uninstall prints each family's removal) — the committed half of the manual
# 16-file matrix PR #345 was verified with. Needs curl on PATH (the script's own prerequisite).
# Per-family *defaults* (group / linger / GameStream) live in check-install-defaults.sh.
# 8. scripts/web-init.sh — the web console's host-readiness gate — actually waits for the host's
# first-run files and actually stops waiting when they land, against a faked config dir. Not
# docs drift, but the same shape: a packaging script no build exercises, whose failure mode
@@ -165,6 +166,7 @@ installer_case fedora-rm 'ID=fedora\nVERSION_ID=44\n' 'yum.rep
installer_case arch-rm 'ID=arch\n' '/etc/pacman.conf' --uninstall
installer_case omarchy-rm 'ID=omarchy\nID_LIKE=arch\n' 'punktfunk-omarchy remove' --uninstall
installer_case bazzite-rm 'ID=bazzite\nID_LIKE="fedora"\nVERSION_ID=43\n' 'punktfunk-sysext remove' --uninstall
sh scripts/ci/check-install-defaults.sh || fail=1
# ---------------------------------------------------------------- gate 8: channel switching
# A box that already has all three binaries, on a repo config naming one channel, told --channel
+120
View File
@@ -0,0 +1,120 @@
#!/bin/sh
# Derived defaults for the guided installer (scripts/install.sh, issue #431).
# Faked os-release + --dry-run --yes --no-start: each family must print the summary that
# --yes would actually take, and that summary must appear before the first sudo.
# DISPLAY / WAYLAND_DISPLAY / XDG_SESSION_TYPE are pinned per case so a graphical seat on
# the machine running the gate cannot leak into a "headless" assertion.
set -u
cd "$(dirname "$0")/../.." || exit 2
fail=0
osr=$(mktemp -d)
gsbin=$(mktemp -d)
ujbin=$(mktemp -d)
trap 'rm -rf "$osr" "$gsbin" "$ujbin"' EXIT
# name os-release-body seat(desktop|headless) extra-PATH-dir expected-substring [install.sh args...]
defaults_case() {
name=$1
printf '%b' "$2" > "$osr/$name"
seat=$3
extra=$4
want=$5
shift 5
case "$seat" in
desktop) _env="DISPLAY=:0 WAYLAND_DISPLAY= XDG_SESSION_TYPE=x11" ;;
headless) _env="DISPLAY= WAYLAND_DISPLAY= XDG_SESSION_TYPE=" ;;
*) echo "::error::defaults_case $name: seat must be desktop or headless, got '$seat'"; fail=1; return ;;
esac
# $_env is expanded on purpose: the seat pins must override a graphical session on the
# machine running the gate. Quoted "$@" are extra install.sh flags (e.g. --no-punktfunk-group).
out=$(env $_env PATH="${extra:+$extra:}$PATH" \
PUNKTFUNK_INSTALL_OS_RELEASE="$osr/$name" \
sh scripts/install.sh --dry-run --yes --no-start "$@" 2>&1) || {
echo "::error::scripts/install.sh --dry-run --yes --no-start on fake $name ($seat) exited $?"
printf '%s\n' "$out" | sed 's/^/ /'
fail=1
return
}
case "$out" in
*"$want"*) ;;
*)
echo "::error::scripts/install.sh --dry-run on fake $name ($seat) did not print '$want':"
printf '%s\n' "$out" | sed 's/^/ /'
fail=1
return ;;
esac
# Summary is the audit trail for --yes; it has to land before any privileged command.
# A re-run on a box that already has the host may print no sudo at all — Choices must
# still appear; when sudo does print, it must not come first.
if ! printf '%s\n' "$out" | awk '
/Choices \(nothing below has run yet\)/ { c=NR }
index($0, " + sudo") && !s { s=NR }
END { if (!c) exit 1; if (s && c > s) exit 1 }
'; then
echo "::error::scripts/install.sh --dry-run on fake $name ($seat): Choices must print before the first '+ sudo':"
printf '%s\n' "$out" | sed 's/^/ /'
fail=1
fi
}
DEB='ID=debian\nVERSION_ID=13\nPRETTY_NAME="Debian GNU/Linux 13"\n'
FED='ID=fedora\nVERSION_ID=44\nPRETTY_NAME="Fedora Linux 44"\n'
ARCH='ID=arch\nPRETTY_NAME="Arch Linux"\n'
OMA='ID=omarchy\nID_LIKE=arch\nVERSION_ID=4.0.1\nPRETTY_NAME="Omarchy"\n'
BAZ='ID=bazzite\nID_LIKE="fedora"\nVERSION_ID=43\nPRETTY_NAME="Bazzite"\n'
NOB='ID=nobara\nID_LIKE="fedora"\nVERSION_ID=43\nPRETTY_NAME="Nobara Linux"\n'
# Desktop seat: only the couch/HTPC distros flip group + linger on.
defaults_case debian-desk "$DEB" desktop '' 'Full controller: no'
defaults_case debian-desk2 "$DEB" desktop '' 'Third-party clients (Moonlight, Artemis): no'
defaults_case debian-desk3 "$DEB" desktop '' 'Shared clipboard: no'
defaults_case debian-desk4 "$DEB" desktop '' 'Start at boot with nobody logged in: no'
defaults_case fedora-desk "$FED" desktop '' 'Full controller: no'
defaults_case fedora-desk2 "$FED" desktop '' 'Start at boot with nobody logged in: no'
defaults_case arch-desk "$ARCH" desktop '' 'Full controller: no'
defaults_case arch-desk2 "$ARCH" desktop '' 'Start at boot with nobody logged in: no'
# Omarchy is a sit-at Arch flavour, not a couch/HTPC default — linger only if the session is seatless.
defaults_case omarchy-desk "$OMA" desktop '' 'Full controller: no'
defaults_case omarchy-desk2 "$OMA" desktop '' 'Start at boot with nobody logged in: no'
defaults_case omarchy-ssh "$OMA" headless '' 'Start at boot with nobody logged in: yes (no graphical session)'
defaults_case bazzite-desk "$BAZ" desktop '' 'Full controller: yes (Bazzite — virtual Steam Deck pad)'
defaults_case bazzite-desk2 "$BAZ" desktop '' 'Start at boot with nobody logged in: yes (Bazzite hosts are usually headless)'
defaults_case bazzite-desk3 "$BAZ" desktop '' 'Third-party clients (Moonlight, Artemis): no'
defaults_case bazzite-desk4 "$BAZ" desktop '' 'Shared clipboard: no'
defaults_case nobara-desk "$NOB" desktop '' 'Full controller: yes (Nobara — virtual Steam Deck pad)'
defaults_case nobara-desk2 "$NOB" desktop '' 'Start at boot with nobody logged in: yes (Nobara hosts are usually headless)'
# Flags/env still win over the distro default.
defaults_case bazzite-flag "$BAZ" desktop '' 'Full controller: no' --no-punktfunk-group
# No graphical seat (SSH, CI, a pipe) → linger even on a generic distro.
defaults_case debian-ssh "$DEB" headless '' 'Start at boot with nobody logged in: yes (no graphical session)'
defaults_case debian-ssh2 "$DEB" headless '' 'Full controller: no'
# Active Sunshine-family host (detect-conflicts exit 1) → Moonlight compat defaults on.
# A dormant leftover is not enough — that is the same split detect-conflicts uses.
cat > "$gsbin/punktfunk-host" <<'EOF'
#!/bin/sh
if [ "${1:-}" = detect-conflicts ]; then
echo "Sunshine is running"
exit 1
fi
echo 0.0.0-test
EOF
chmod +x "$gsbin/punktfunk-host"
defaults_case debian-gs "$DEB" desktop "$gsbin" 'Third-party clients (Moonlight, Artemis): yes (Sunshine/Apollo already on this box)'
defaults_case debian-gs2 "$DEB" desktop "$gsbin" 'Full controller: no'
# ujust is how Bazzite-shaped boxes that aren't ID=bazzite still identify.
printf '#!/bin/sh\nexit 0\n' > "$ujbin/ujust"
chmod +x "$ujbin/ujust"
defaults_case debian-ujust "$DEB" desktop "$ujbin" 'Full controller: yes (Game Mode / HTPC box — virtual Steam Deck pad)'
defaults_case debian-ujust2 "$DEB" desktop "$ujbin" 'Start at boot with nobody logged in: yes (Game Mode / HTPC box)'
if [ "$fail" -ne 0 ]; then
echo "installer default matrix failed"
exit 1
fi
echo "installer default matrix ok"
+132 -26
View File
@@ -4,10 +4,11 @@
# curl -fsSL https://punktfunk.unom.io/install.sh | sh
# curl -fsSLO https://punktfunk.unom.io/install.sh && sh install.sh --help # read it first
#
# Zero-to-streaming for a Linux host: detect the distro → add the package repo → install → deal
# with a Sunshine/Apollo/Vibeshine already on the box (move one port) → groups → options →
# firewall → start the services → verify → print how to pair. Plain POSIX sh, `read` prompts,
# no TUI. Every prompt has a default so `--yes` (or no terminal) runs unattended.
# Zero-to-streaming for a Linux host: detect the distro → ask intent (not internals) → print
# the choices → add the package repo → install → deal with a Sunshine/Apollo/Vibeshine already
# on the box (move one port) → groups → options → firewall → start the services → verify →
# print how to pair. Plain POSIX sh, `read` prompts, no TUI. Every prompt has a default so
# `--yes` (or no terminal) runs unattended; the default follows the box, not a global no.
#
# This is WP4 of the docs-and-onboarding overhaul and is labelled PREVIEW on purpose: the per-distro
# docs pages (https://docs.punktfunk.unom.io/docs/install) remain the documented default until it
@@ -25,10 +26,10 @@ USER=${USER:-$(id -un)}; export USER
YES=${PUNKTFUNK_INSTALL_YES:-0}
CHANNEL=${PUNKTFUNK_INSTALL_CHANNEL:-stable}
CHANNEL_SET=0; [ -n "${PUNKTFUNK_INSTALL_CHANNEL:-}" ] && CHANNEL_SET=1 # asked for, vs. defaulted
GAMESTREAM=${PUNKTFUNK_INSTALL_GAMESTREAM:-} # 1/0, empty = ask (default no)
CLIPBOARD=${PUNKTFUNK_INSTALL_CLIPBOARD:-} # 1/0, empty = ask (default no)
PF_GROUP=${PUNKTFUNK_INSTALL_PUNKTFUNK_GROUP:-} # 1/0, empty = ask (default no)
LINGER=${PUNKTFUNK_INSTALL_LINGER:-} # 1/0, empty = ask (default no)
GAMESTREAM=${PUNKTFUNK_INSTALL_GAMESTREAM:-} # 1/0, empty = ask
CLIPBOARD=${PUNKTFUNK_INSTALL_CLIPBOARD:-} # 1/0, empty = ask
PF_GROUP=${PUNKTFUNK_INSTALL_PUNKTFUNK_GROUP:-} # 1/0, empty = ask
LINGER=${PUNKTFUNK_INSTALL_LINGER:-} # 1/0, empty = ask
MGMT_PORT=${PUNKTFUNK_INSTALL_MGMT_PORT:-47991} # where the management API moves to on a conflict
START=1
DRY=${PUNKTFUNK_INSTALL_DRY_RUN:-0}
@@ -42,10 +43,10 @@ usage: sh install.sh [options]
-y, --yes no prompts: take every default (also the behaviour without a terminal)
--channel stable|canary package channel (default stable; canary = latest main build). On a box
that already has the host this SWITCHES channel, either direction.
--gamestream | --no-gamestream also serve stock Moonlight clients (default no — trusted LANs only)
--clipboard | --no-clipboard allow the shared clipboard on this host (default no)
--punktfunk-group | --no-punktfunk-group join the punktfunk group (virtual Steam Deck pad; default no)
--linger | --no-linger start the host at boot with nobody logged in (default no)
--gamestream | --no-gamestream Moonlight/Artemis/third-party clients (default depends on the box)
--clipboard | --no-clipboard shared clipboard (default no)
--punktfunk-group | --no-punktfunk-group full controller / virtual Steam Deck pad (default depends on the box)
--linger | --no-linger start at boot with nobody logged in (default depends on the box)
--mgmt-port N port to move the management API to if Sunshine/Apollo holds 47990 (default $MGMT_PORT)
--no-start install and configure, but don't enable the services
--uninstall stop the services and remove the packages + repo (config stays: $DOCS/uninstall)
@@ -120,7 +121,13 @@ run() {
fi
printf ' + %s\n' "$cmd"
[ "$DRY" = 1 ] && return 0
if [ -n "$TTY" ]; then sh -ec "$cmd" < "$TTY"; else sh -ec "$cmd" < /dev/null; fi \
# --yes means unattended: never block on a sudo password (sudo reads the TTY, not stdin).
# Printed command stays without -n so the platforms.json verbatim gate still matches.
exec_cmd=$cmd
if [ "$YES" = 1 ]; then
exec_cmd=$(printf '%s' "$cmd" | sed 's/sudo /sudo -n /g')
fi
if [ -n "$TTY" ] && [ "$YES" != 1 ]; then sh -ec "$exec_cmd" < "$TTY"; else sh -ec "$exec_cmd" < /dev/null; fi \
|| die "that step failed — fix it and re-run (the script is safe to repeat), or follow the page by hand: $DOCS_PAGE"
}
@@ -296,6 +303,115 @@ if [ "$FAMILY" = dnf ]; then
esac
fi
# ---------------------------------------------------------------------------- choices
# Empty flag/env still means "ask". The default behind Enter (and --yes) follows the box:
# couch/HTPC distros want the Deck pad and linger; an active Sunshine-family host wants
# Moonlight compat; a seatless session wants linger. Clipboard stays off. Flags/env win.
has_graphical_seat() {
[ -n "${DISPLAY:-}" ] && return 0
[ -n "${WAYLAND_DISPLAY:-}" ] && return 0
case "${XDG_SESSION_TYPE:-}" in x11|wayland) return 0 ;; esac
return 1
}
couch_box() {
[ "$FAMILY" = sysext ] && return 0
[ "$ID" = bazzite ] && return 0
[ "$ID" = nobara ] && return 0
command -v ujust >/dev/null 2>&1 && return 0
return 1
}
# Same split as `punktfunk-host detect-conflicts`: exit 1 only when a Sunshine-family host
# runs or autostarts. A dormant leftover is not a reason to open Moonlight ports.
sunshine_active() {
if command -v punktfunk-host >/dev/null 2>&1; then
punktfunk-host detect-conflicts >/dev/null 2>&1 && return 1 || return 0
fi
for u in sunshine.service apollo.service vibeshine.service; do
systemctl is-active --quiet "$u" 2>/dev/null && return 0
systemctl is-enabled --quiet "$u" 2>/dev/null && return 0
systemctl --user is-active --quiet "$u" 2>/dev/null && return 0
systemctl --user is-enabled --quiet "$u" 2>/dev/null && return 0
done
return 1
}
GROUP_WHY=; GS_WHY=; LINGER_WHY=
DEF_GROUP=n; DEF_GS=n; DEF_LINGER=n
if couch_box; then
DEF_GROUP=y
DEF_LINGER=y
case "$ID" in
bazzite)
GROUP_WHY="Bazzite — virtual Steam Deck pad"
LINGER_WHY="Bazzite hosts are usually headless" ;;
nobara)
GROUP_WHY="Nobara — virtual Steam Deck pad"
LINGER_WHY="Nobara hosts are usually headless" ;;
*)
GROUP_WHY="Game Mode / HTPC box — virtual Steam Deck pad"
LINGER_WHY="Game Mode / HTPC box" ;;
esac
fi
if ! has_graphical_seat; then
DEF_LINGER=y
[ -z "$LINGER_WHY" ] && LINGER_WHY="no graphical session"
fi
if sunshine_active; then
DEF_GS=y
GS_WHY="Sunshine/Apollo already on this box"
fi
if [ -z "$PF_GROUP" ]; then
if [ "$DEF_GROUP" = y ]; then
case "$ID" in
bazzite) q="Bazzite detected — joining the punktfunk group so the virtual Steam Deck pad works" ;;
nobara) q="Nobara detected — joining the punktfunk group so the virtual Steam Deck pad works" ;;
*) q="Game Mode / HTPC box detected — joining the punktfunk group so the virtual Steam Deck pad works" ;;
esac
ask "$q" y && PF_GROUP=1 || PF_GROUP=0
else
ask "Do you want the full controller — paddles, trackpads, gyro? (skip it and the pad arrives as a plain Xbox 360 controller)" n && PF_GROUP=1 || PF_GROUP=0
GROUP_WHY=
fi
[ "$PF_GROUP" = 0 ] && GROUP_WHY=
fi
if [ -z "$GAMESTREAM" ]; then
if [ "$DEF_GS" = y ]; then
ask "Sunshine or Apollo is already on this box — also serve Moonlight, Artemis, or another third-party client? (Punktfunk's own apps don't need this)" y && GAMESTREAM=1 || GAMESTREAM=0
else
ask "Will you connect with Moonlight, Artemis, or another third-party client? (Punktfunk's own apps don't need this)" n && GAMESTREAM=1 || GAMESTREAM=0
GS_WHY=
fi
[ "$GAMESTREAM" = 0 ] && GS_WHY=
fi
if [ -z "$CLIPBOARD" ]; then
ask "Share the clipboard between this host and your clients? (each client still opts in per host)" n && CLIPBOARD=1 || CLIPBOARD=0
fi
if [ -z "$LINGER" ]; then
if [ "$DEF_LINGER" = y ]; then
ask "$LINGER_WHY — start the host at boot with nobody logged in?" y && LINGER=1 || LINGER=0
else
ask "Is this a box you stream from and rarely log into? (starts the host at boot with nobody logged in)" n && LINGER=1 || LINGER=0
LINGER_WHY=
fi
[ "$LINGER" = 0 ] && LINGER_WHY=
fi
yn() { [ "$1" = 1 ] && printf yes || printf no; }
choice_line() {
if [ -n "$3" ] && [ "$2" = 1 ]; then
printf ' %s: %s (%s)\n' "$1" "$(yn "$2")" "$3"
else
printf ' %s: %s\n' "$1" "$(yn "$2")"
fi
}
say "Choices (nothing below has run yet)"
choice_line "Full controller" "$PF_GROUP" "$GROUP_WHY"
choice_line "Third-party clients (Moonlight, Artemis)" "$GAMESTREAM" "$GS_WHY"
choice_line "Shared clipboard" "$CLIPBOARD" ""
choice_line "Start at boot with nobody logged in" "$LINGER" "$LINGER_WHY"
# ---------------------------------------------------------------------------- 1. install
# The snippets below are data/platforms.json's install lines, verbatim (stable channel); canary
# and the Fedora group are edited in. check-docs-drift.sh gate 6 keeps them identical.
@@ -547,9 +663,6 @@ elif ! getent group input >/dev/null 2>&1; then
else
run 'sudo usermod -aG input "$USER"'; RELOGIN=1
fi
if [ -z "$PF_GROUP" ]; then
ask "Also join the punktfunk group? It enables the virtual Steam Deck controller (paddles, trackpads, gyro) by granting usbip attach — only on a machine you trust ($DOCS/gamescope#nobara-and-other-autologin-display-managers)" n && PF_GROUP=1 || PF_GROUP=0
fi
if [ "$PF_GROUP" = 1 ]; then
if id -nG "$USER" 2>/dev/null | tr ' ' '\n' | grep -qx punktfunk; then ok "already in the punktfunk group"
else run 'sudo usermod -aG punktfunk "$USER"'; RELOGIN=1; fi
@@ -557,16 +670,10 @@ fi
# ---------------------------------------------------------------------------- 4. options
say "Options (host.env — everything here is off by default and reversible)"
if [ -z "$GAMESTREAM" ]; then
ask "Also serve stock Moonlight clients (GameStream compat)? Its pairing runs over plain HTTP — trusted LANs only; Punktfunk's own apps don't need it" n && GAMESTREAM=1 || GAMESTREAM=0
fi
if [ "$GAMESTREAM" = 1 ]; then
[ "$CONFLICT" = 1 ] && warn "with another GameStream host running, only one can bind the Moonlight ports — stop the other first or skip this"
set_env PUNKTFUNK_GAMESTREAM 1
fi
if [ -z "$CLIPBOARD" ]; then
ask "Allow the shared clipboard on this host (each client still opts in per host)?" n && CLIPBOARD=1 || CLIPBOARD=0
fi
[ "$CLIPBOARD" = 1 ] && set_env PUNKTFUNK_CLIPBOARD on
# ---------------------------------------------------------------------------- 5. firewall
@@ -591,6 +698,9 @@ fi
# ---------------------------------------------------------------------------- 6. start
if [ "$START" = 1 ]; then
say "Starting the host and the web console"
# Linger is what creates a user manager on a seatless box; apply it before probing
# `systemctl --user`, otherwise SSH/headless installs print the enable command and stop.
[ "$LINGER" = 1 ] && run 'sudo loginctl enable-linger "$USER"'
if ! systemctl --user show-environment >/dev/null 2>&1; then
warn "no user systemd session here (ssh without a login session?) — run this from a terminal in your desktop session:"
echo " systemctl --user enable --now punktfunk-host punktfunk-web"
@@ -606,10 +716,6 @@ if [ "$START" = 1 ]; then
# The plugin runner fills the game library; apt/dnf/sysext start it themselves, Arch doesn't.
if systemctl --user list-unit-files punktfunk-scripting.service 2>/dev/null | grep -q disabled; then units="$units punktfunk-scripting"; fi
run "systemctl --user enable --now $units"
if [ -z "$LINGER" ]; then
ask "Start the host at boot even with nobody logged in (headless box)?" n && LINGER=1 || LINGER=0
fi
[ "$LINGER" = 1 ] && run 'sudo loginctl enable-linger "$USER"'
fi
fi