diff --git a/docs-site/content/docs/plugins.mdx b/docs-site/content/docs/plugins.mdx index 9de18f07..dc137d56 100644 --- a/docs-site/content/docs/plugins.mdx +++ b/docs-site/content/docs/plugins.mdx @@ -108,10 +108,16 @@ the full path: `& "$env:ProgramFiles\punktfunk\punktfunk-host.exe" plugins add p Open the [web console](/docs/web-console) and the plugin's page appears in the nav automatically — that's the whole install. -The runner is **opt-in**: `plugins add` installs, `plugins enable` turns it on. You only need -`enable` once. The runner discovers plugins when it starts, so one installed later needs a restart -to come up (`systemctl --user restart punktfunk-scripting`, or `Restart` the `PunktfunkScripting` -task) — the console does that restart for you as part of installing. +The runner is **on by default** on a new install — your game sources are plugins, so a host without +it would show an empty library. (On a host that predates this, it stays however you left it; turn it +on with `punktfunk-host plugins enable`, which you only need once.) The runner discovers plugins +when it starts, so one installed later needs a restart to come up +(`systemctl --user restart punktfunk-scripting`, or `Restart` the `PunktfunkScripting` task) — the +console does that restart for you as part of installing. + +Don't want it? It is a normal service you can switch off: `systemctl --user mask punktfunk-scripting` +on Linux, or disable the `PunktfunkScripting` scheduled task on Windows. Your host keeps streaming; +you just lose plugin-provided game sources and any automation. A plugin installed from the CLI shows up in the console as **Installed via CLI**: the console knows what is installed, but not who vouched for it. Install the same plugin from the store's Browse tab @@ -301,8 +307,8 @@ host's, on one timeline, with the same search and download. Each is tagged `plug plugin's own name for lines it logged itself, `plugin:runner` for the supervisor's (starting a plugin, restarting a crashed one, refusing an unsafe file). -An empty Plugins view almost always means the runner isn't running — it is a separate service, and -opt-in on Linux. Check with `punktfunk-host plugins status`. +An empty Plugins view almost always means the runner isn't running — it is a separate service. Check +with `punktfunk-host plugins status`. Nothing is lost if the host is down: the runner keeps buffering and sends the backlog when the host diff --git a/packaging/bazzite/build-sysext.sh b/packaging/bazzite/build-sysext.sh index 6d043bac..c88acd62 100644 --- a/packaging/bazzite/build-sysext.sh +++ b/packaging/bazzite/build-sysext.sh @@ -98,6 +98,23 @@ if [ -n "$GAMESCOPE" ]; then install -Dm0755 "$GAMESCOPE" "$STAGE/usr/bin/punktfunk-gamescope" fi +# Enable the plugin/script runner for every user, by baking its `[Install] WantedBy=default.target` +# symlink straight into the image. +# +# A sysext carries only /usr, and RPM scriptlets never run from one — so the `systemctl --global +# enable` the .rpm/.deb do at install time has no equivalent here, and without this the runner would +# ship present-but-off on exactly the platform (Bazzite / Fedora Atomic) where an operator is least +# likely to go hunting for it. The game-library scanners are plugins now (design D9), so an +# unenabled runner means an empty library. +# +# Opt-out is unchanged and still wins: `systemctl --user mask punktfunk-scripting` in the user's own +# ~/.config/systemd/user takes precedence over anything under /usr. +if [ -f "$STAGE/usr/lib/systemd/user/punktfunk-scripting.service" ]; then + install -d "$STAGE/usr/lib/systemd/user/default.target.wants" + ln -sf ../punktfunk-scripting.service \ + "$STAGE/usr/lib/systemd/user/default.target.wants/punktfunk-scripting.service" +fi + # Self-update: the helper rides inside the image. install -Dm0755 "$HERE/punktfunk-sysext.sh" "$STAGE/usr/bin/punktfunk-sysext" diff --git a/packaging/debian/build-scripting-deb.sh b/packaging/debian/build-scripting-deb.sh index 8d9030cd..7920cfd6 100755 --- a/packaging/debian/build-scripting-deb.sh +++ b/packaging/debian/build-scripting-deb.sh @@ -114,20 +114,36 @@ Description: punktfunk plugin/script runner (Effect SDK on bun) capped-jittered restart; SIGTERM shuts the whole tree down structurally so plugin finalizers run). Bundles its own bun runtime (no system nodejs/bun dependency). . - OPT-IN: the systemd --user unit is installed but not auto-enabled (the runner is inert until you add - scripts or plugins). A plugin auto-wires to the host's mgmt token + identity cert on the same box — - no env editing. Enable it with: systemctl --user enable --now punktfunk-scripting + ON BY DEFAULT: the systemd --user unit is enabled for every user (systemctl --global). The runner is + inert until you add scripts or plugins, and the game-library scanners now ship AS plugins — so a + host without the runner has an empty library and no obvious reason why. A plugin auto-wires to the + host's mgmt token + identity cert on the same box — no env editing. + Opt out per user with: systemctl --user mask punktfunk-scripting EOF cat > "$STAGE/DEBIAN/postinst" <<'EOF' #!/bin/sh set -e if [ "$1" = "configure" ]; then - echo "punktfunk-scripting installed. It runs your automation — add scripts to" + # `--global`, not `--user`: a maintainer script has no user session to act on, and this is the + # only mechanism that makes a `--user` unit on-by-default for everyone (it symlinks into + # /etc/systemd/user/…wants/). The library's scanners are plugins now, so the runner is a default + # component rather than an add-on (design D9) — but installing it stays opt-OUT, and the opt-out + # is `systemctl --user mask punktfunk-scripting`, since a plain `--user disable` cannot remove a + # global symlink. + # + # Only on FIRST configure ($2 empty): re-running it on every upgrade would silently undo the + # mask of anyone who turned it off. + if [ -z "$2" ] && command -v systemctl >/dev/null 2>&1; then + systemctl --global enable punktfunk-scripting.service >/dev/null 2>&1 || true + fi + echo "punktfunk-scripting installed and enabled for all users." + echo "It runs your automation — game-library sources, scripts in" echo " ~/.config/punktfunk/scripts/ (loose .ts/.js files)" - echo "or install plugins into ~/.config/punktfunk/plugins/ (bun add punktfunk-plugin-)," - echo "then enable the runner for your user:" - echo " systemctl --user enable --now punktfunk-scripting" + echo "and plugins under ~/.config/punktfunk/plugins/." + echo "It starts with your next login; start it now with:" + echo " systemctl --user start punktfunk-scripting" + echo "Don't want it? systemctl --user mask punktfunk-scripting" fi exit 0 EOF diff --git a/packaging/rpm/punktfunk.spec b/packaging/rpm/punktfunk.spec index 43dcfdc4..5be4012f 100644 --- a/packaging/rpm/punktfunk.spec +++ b/packaging/rpm/punktfunk.spec @@ -191,9 +191,10 @@ The plugin/script runner for a punktfunk streaming host: it discovers loose scri ~/.config/punktfunk/scripts and installed punktfunk-plugin-* packages under ~/.config/punktfunk/ plugins, and supervises each as an Effect fiber (capped-jittered restart; SIGTERM shuts the whole tree down structurally so plugin finalizers run). A plugin auto-wires to the host's mgmt token + -identity cert on the same box — no env editing. Bundles its own bun runtime. OPT-IN: the systemd ---user unit ships disabled (the runner is inert until you add scripts/plugins). Enable with -`systemctl --user enable --now punktfunk-scripting`. +identity cert on the same box — no env editing. Bundles its own bun runtime. ON BY DEFAULT: the +systemd --user unit is enabled for every user (systemctl --global). The game-library scanners ship +as plugins, so a host without the runner has an empty library. Opt out per user with +`systemctl --user mask punktfunk-scripting`. %endif %prep @@ -590,10 +591,22 @@ echo "Then open https://:47992" %if %{with scripting} %post scripting -echo "punktfunk-scripting installed. It runs your automation — add scripts to" +# `--global`, not `--user`: a scriptlet has no user session to act on, and this is the only +# mechanism that makes a `--user` unit on-by-default for everyone (it symlinks into +# /etc/systemd/user/…wants/). The game-library scanners are plugins now, so the runner is a default +# component rather than an add-on (design D9); it stays opt-OUT via +# `systemctl --user mask punktfunk-scripting`, since a plain `--user disable` cannot remove a global +# symlink. $1 == 1 is a first INSTALL — on an upgrade ($1 > 1) this must not undo an operator's mask. +if [ "$1" -eq 1 ] && command -v systemctl >/dev/null 2>&1; then + systemctl --global enable punktfunk-scripting.service >/dev/null 2>&1 || : +fi +echo "punktfunk-scripting installed and enabled for all users." +echo "It runs your automation — game-library sources, scripts in" echo " ~/.config/punktfunk/scripts/ (loose .ts/.js files)" -echo "or install plugins into ~/.config/punktfunk/plugins/ (bun add punktfunk-plugin-)," -echo "then enable the runner: systemctl --user enable --now punktfunk-scripting" +echo "and plugins under ~/.config/punktfunk/plugins/." +echo "It starts with your next login; start it now with:" +echo " systemctl --user start punktfunk-scripting" +echo "Don't want it? systemctl --user mask punktfunk-scripting" %endif %changelog diff --git a/packaging/windows/punktfunk-host.iss b/packaging/windows/punktfunk-host.iss index 4be77614..e845eb72 100644 --- a/packaging/windows/punktfunk-host.iss +++ b/packaging/windows/punktfunk-host.iss @@ -329,9 +329,8 @@ Filename: "{app}\punktfunk-host.exe"; Parameters: "web setup {code:WebSetupParam ; converges tasks an older installer registered as SYSTEM. ; Best-effort (-ErrorAction SilentlyContinue): a task hiccup never fails the whole install. No braces ; in the command, so no Inno {{ }} escaping needed. -Filename: "powershell.exe"; \ - Parameters: "-NoProfile -ExecutionPolicy Bypass -Command ""$a=New-ScheduledTaskAction -Execute '{app}\scripting\scripting-run.cmd'; $t=New-ScheduledTaskTrigger -AtStartup; $p=New-ScheduledTaskPrincipal -UserId 'LocalService' -LogonType ServiceAccount; $s=New-ScheduledTaskSettingsSet -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries; Register-ScheduledTask -TaskName PunktfunkScripting -Action $a -Trigger $t -Principal $p -Settings $s -Force -ErrorAction SilentlyContinue | Out-Null; Disable-ScheduledTask -TaskName PunktfunkScripting -ErrorAction SilentlyContinue | Out-Null"""; \ - StatusMsg: "Registering the Punktfunk script runner (disabled; opt-in)..."; Flags: runhidden waituntilterminated +Filename: "powershell.exe"; Parameters: "{code:ScriptingRegisterParams}"; \ + StatusMsg: "Registering the Punktfunk script runner..."; Flags: runhidden waituntilterminated #endif #if defined(WithWeb) || defined(WithScripting) ; Put back what StopBunRuntimes disabled to unlock bun.exe. Deliberately the LAST [Run] entry that @@ -619,6 +618,12 @@ end; it disabled would switch it off for everyone who had it on. } var WebTaskWasEnabled, ScriptingTaskWasEnabled: Boolean; + { Did PunktfunkScripting exist AT ALL before this install (enabled or not)? That is what + distinguishes a FRESH scripting install — where the runner is now registered enabled by default + (design D9: the library moves into plugins, and a flagship surface cannot depend on an opt-in + subsystem, or a fresh box would come up with an empty library) — from an UPGRADE, where the + operator's own choice is the only thing that may decide it. } + ScriptingTaskExisted: Boolean; { Escape a value for embedding in a single-quoted PowerShell literal ('' is PS's escaped quote). The install dir is user-chosen, so it can legitimately contain an apostrophe. } @@ -643,6 +648,22 @@ begin Result := ResultCode = 1; end; +{ Is the task registered at all, whatever its state? Distinct from TaskEnabled: an operator who + deliberately DISABLED the runner must keep it disabled across an upgrade, which is indistinguishable + from a fresh install if you only ask "was it enabled". } +function TaskExists(TaskName: String): Boolean; +var + ResultCode: Integer; +begin + Result := False; + if Exec('powershell.exe', + '-NoProfile -ExecutionPolicy Bypass -Command "' + + '$t=Get-ScheduledTask -TaskName ''' + PsLiteral(TaskName) + ''' -ErrorAction SilentlyContinue; ' + + 'if($t){exit 1}; exit 0"', + '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then + Result := ResultCode = 1; +end; + { Free the bundled bun.exe (and the console's own files) BEFORE the copy. Windows will not delete a running image, so a surviving bun means "DeleteFile failed; code 5" on bun\bun.exe - the modal a user hit updating to 0.22.1. @@ -664,6 +685,9 @@ var begin WebTaskWasEnabled := TaskEnabled('PunktfunkWeb'); ScriptingTaskWasEnabled := TaskEnabled('PunktfunkScripting'); + { Probed BEFORE the Disable below, which would otherwise make every upgrade look like a fresh + install to the registration entry. } + ScriptingTaskExisted := TaskExists('PunktfunkScripting'); Exec('powershell.exe', '-NoProfile -ExecutionPolicy Bypass -Command "' + '$ErrorActionPreference=''SilentlyContinue''; ' + @@ -689,6 +713,35 @@ end; DELETED the legacy task (the console runs under the host service now), so Enable-ScheduledTask hits nothing and no-ops under SilentlyContinue. If the user cancels mid-install, though, DeinitializeSetup runs this same restore and puts the old (task-owned) world back intact. } +{ Register PunktfunkScripting, and decide whether it comes up ENABLED. + `Register-ScheduledTask` registers enabled, so the state is decided by what follows: + * FRESH install (the task did not exist) -> leave it enabled and start it now, so the runner is + live without waiting for a reboot. Since the library's scanners become plugins (design D9), + shipping this opt-in would mean a fresh box comes up with an empty library and no obvious + reason why. + * UPGRADE (the task existed) -> disable here and let RestoreTasksParams put the operator's own + state back. That order is deliberate: this entry cannot know what they chose, and defaulting + to "on" here would silently switch the runner on for everyone who had turned it off. + It remains opt-OUT: `punktfunk-host plugins disable`, or the task's own Disable, still wins and + survives every later upgrade through exactly this path. } +function ScriptingRegisterParams(Param: String): String; +begin + Result := '-NoProfile -ExecutionPolicy Bypass -Command "' + + '$ErrorActionPreference=''SilentlyContinue''; ' + + '$a=New-ScheduledTaskAction -Execute ''' + + PsLiteral(ExpandConstant('{app}\scripting\scripting-run.cmd')) + '''; ' + + '$t=New-ScheduledTaskTrigger -AtStartup; ' + + '$p=New-ScheduledTaskPrincipal -UserId ''LocalService'' -LogonType ServiceAccount; ' + + '$s=New-ScheduledTaskSettingsSet -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) ' + + '-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries; ' + + 'Register-ScheduledTask -TaskName PunktfunkScripting -Action $a -Trigger $t -Principal $p ' + + '-Settings $s -Force | Out-Null; '; + if ScriptingTaskExisted then + Result := Result + 'Disable-ScheduledTask -TaskName PunktfunkScripting | Out-Null"' + else + Result := Result + 'Start-ScheduledTask -TaskName PunktfunkScripting | Out-Null"'; +end; + function RestoreTasksParams(Param: String): String; begin Result := '-NoProfile -ExecutionPolicy Bypass -Command "$ErrorActionPreference=''SilentlyContinue''; '; diff --git a/scripts/punktfunk-scripting.service b/scripts/punktfunk-scripting.service index c6bb2dfb..f6886035 100644 --- a/scripts/punktfunk-scripting.service +++ b/scripts/punktfunk-scripting.service @@ -6,9 +6,16 @@ # SIGTERM interrupts the whole tree STRUCTURALLY, so every plugin's scoped finalizers run before # exit (clean deregister / preset release) — hence the generous stop timeout below. # -# OPT-IN — unlike punktfunk-web, the package does NOT auto-enable this: the runner does nothing until -# you add scripts or install plugins. Turn it on once you have automation to run: -# systemctl --user enable --now punktfunk-scripting +# ON BY DEFAULT — the packages enable this for every user (`systemctl --global enable` from the +# .deb/.rpm scriptlets; a baked-in default.target.wants symlink in the sysext image). It used to be +# opt-in, on the reasoning that the runner does nothing until you add scripts or plugins. That +# stopped being true when the game-library scanners became plugins: the library is a flagship +# surface, and a host whose runner is off now comes up with an empty library and no obvious reason +# why (design/library-scanner-plugins.md D9). +# +# It remains opt-OUT, per user: +# systemctl --user mask punktfunk-scripting +# (`mask`, not `disable` — a plain disable cannot remove a symlink that lives in /etc or /usr.) # # Auto-wired like the console: a plugin's connect() reads the host's SCOPED plugin token + identity # cert from ~/.config/punktfunk/{plugin-token,cert.pem} (written by the host's `serve`) — no env