feat(packaging): the plugin runner becomes a default component
WP6.1 of design/library-scanner-plugins-implementation-plan.md. The library is a flagship surface and cannot depend on an opt-in subsystem (design D9, closing G9): once the scanners are plugins, a host whose runner is off comes up with an empty library and no obvious reason why. The security posture for on-by-default was already built and shipped — LocalService on Windows, a sandboxed systemd --user unit on Linux, the scoped plugin-token lane. Windows (.iss): the PunktfunkScripting task is registered ENABLED and started on a FRESH install, and left to the existing restore path on an upgrade. The distinction is a new TaskExists probe taken before StopBunRuntimes disables anything — TaskEnabled alone cannot tell a fresh install from an operator who deliberately turned the runner off, and defaulting to "on" would silently switch it back on for them. deb/rpm: `systemctl --global enable` from the postinst/%post, guarded to first install only so an upgrade never undoes a mask. `--global` because a maintainer script has no user session to act on, and it is the only mechanism that makes a --user unit on-by-default for everyone. sysext: RPM scriptlets never run from a sysext image, so the enablement symlink is baked in directly (/usr/lib/systemd/user/default.target.wants/). Without it the runner would ship present-but-off on exactly the platform where an operator is least likely to go looking for it. Opt-out throughout is `systemctl --user mask punktfunk-scripting` — `mask`, not `disable`, since a plain disable cannot remove a symlink under /etc or /usr. The unit comment, both package descriptions, and the docs-site plugins page all say so; the page also gains the Windows equivalent. Not gated on hardware: none of this is verifiable from a Mac. The .iss change needs an installer run (fresh + upgrade, and an upgrade with the task deliberately disabled), and the deb/rpm/sysext changes need a package build.
This commit is contained in:
@@ -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`.
|
||||
|
||||
<Callout>
|
||||
Nothing is lost if the host is down: the runner keeps buffering and sends the backlog when the host
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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-<name>),"
|
||||
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
|
||||
|
||||
@@ -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://<host-ip>: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-<name>),"
|
||||
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
|
||||
|
||||
@@ -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''; ';
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user