ci / rust (push) Failing after 2m31s
ci / docs-site (push) Successful in 1m22s
ci / web (push) Successful in 1m48s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 1m1s
ci / rust-arm64 (push) Successful in 2m2s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 11s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 9s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 9s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / builders-arm64cross (push) Successful in 20s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 36s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 36s
docker / deploy-docs (push) Canceled after 0s
~1150 feat/fix commits landed since v0.19 and the docs drifted badly. This is a full sweep of every page against the code as shipped: ~280 verified corrections, nine new pages, and one deletion. The worst of what was wrong: the quickstart's five-minute path could not work (`serve` never started the web console, so step 3 had no PIN to read); every packaged Linux host runs `serve --gamestream` while security.md told readers to leave GameStream off; HDR was documented as Windows-only; `PUNKTFUNK_SECURE_DDA` was documented as a working knob that nothing reads; `PUNKTFUNK_INPUT_BACKEND` listed a `uinput` value that does not exist and named libei for KDE instead of kwin; README linked three pages deleted on 2026-07-05; and the rpm-ostree update command pointed at a script no package installs. Completeness: about half of what shipped since v0.19 had no page at all. New: support-matrix (what works where, from 217 verified capability cells), input (mouse/touch/pen — and the in-stream chords, so the docs finally say how to get your mouse back), client-settings, profiles-and-links, game-library, clipboard, wake-on-lan, hdr, uninstall. Updating existed but had zero inbound links. status.md is gone: its facts moved into the support matrix, its shell stays as a redirect so the public URL does not 404. roadmap.md is themes now, not a feature checklist — checkboxes are what rotted. Debian is no longer claimed. The .deb's Depends resolve against Ubuntu images, nothing in CI builds or tests Debian, and Debian 12 is below the glibc 2.39 floor. The `debian` in the repo URL is the package format. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
330 lines
16 KiB
Plaintext
330 lines
16 KiB
Plaintext
---
|
|
title: Plugins
|
|
description: First-party plugins — sync your ROM collection or Playnite library into the game library, or hand a real USB device on the couch to the host — and how to install them.
|
|
---
|
|
|
|
Plugins extend the host through the **scripting runner** (see [Events & hooks](/docs/automation)). A
|
|
plugin runs alongside the host, reconciles titles into your [game library](/docs/game-library) as a
|
|
provider — so they appear in the grid on every client — and can add its own page to the [web console](/docs/web-console).
|
|
|
|
Three first-party plugins today:
|
|
|
|
| Plugin | What it does |
|
|
|---|---|
|
|
| **ROM Manager** | Scans your ROM directories, matches each platform to an installed emulator, and syncs them into the library with box art. |
|
|
| **Playnite** | Mirrors your [Playnite](https://playnite.link) library — every store and emulator it manages — into the library, launched back through Playnite. |
|
|
| **VirtualHere** | Hands a real USB device on the couch — wheel, HOTAS, pad — to the host while you play, and gives it back after. Needs [VirtualHere](https://www.virtualhere.com/), sold separately. |
|
|
|
|
## Installing from the console
|
|
|
|
Every plugin runs inside the **plugin runner**, a separate service that is off until you switch it
|
|
on. Installing a plugin while it's off succeeds and starts nothing, so do this first:
|
|
|
|
1. Open the [web console](/docs/web-console) → **Plugins** → **Installed** and look at the **Plugin
|
|
runner** card. If it says *Not installed*, install the runner package first (see
|
|
[Troubleshooting](#troubleshooting) below). If it says *Disabled*, press **Enable runner** —
|
|
once per host. *Running* means you're set; *Stopped* means the runner is enabled but not up right
|
|
now, and its log says why (see [Troubleshooting](#troubleshooting)).
|
|
2. Go to **Browse**, pick a plugin from the catalog and confirm. The host installs it and restarts
|
|
the runner, and the plugin's own page appears in the console's nav.
|
|
|
|
**Sources** is the third tab: where catalogs come from.
|
|
|
|
The rest of this page covers the CLI, which does the same thing, and the trust model behind the
|
|
badges — worth reading once, because a plugin runs with the same privileges as the host.
|
|
|
|
### What "Verified" means
|
|
|
|
Every catalogued plugin pins **one exact version** and that version's package hash. A verified entry
|
|
means somebody at unom reviewed *that exact package* — not the project in general, and not whatever
|
|
it publishes next. When a plugin releases a new version, the store keeps offering the reviewed one
|
|
until the new release is reviewed too. Before anything is downloaded, the host re-checks the pinned
|
|
hash against the registry, so a package that was quietly republished under the same version number
|
|
is refused rather than installed.
|
|
|
|
A catalog can also **revoke** a version. When an advisory covers an entry, the console shows the
|
|
reason against it and won't install that version — on **Browse** as a red-ringed panel with the
|
|
install button disabled, on **Installed** as a warning against the plugin you already have. It
|
|
never removes running code for you; that stays your decision.
|
|
|
|
Three things a plugin can be:
|
|
|
|
| Badge | Where it came from |
|
|
|---|---|
|
|
| **Verified** | The built-in catalog. unom reviewed this exact package. |
|
|
| **External source**, *from <source>* | A catalog **you** added. Still pinned and hash-checked, but curated by somebody else — unom has not looked at the code. This badge is amber, not the red **Unverified** below. |
|
|
| **Unverified** | Installed by hand from a package spec. Nobody reviewed it and nothing pins it; the console asks you to type the name to confirm, and the plugin stays marked this way for as long as it is installed. |
|
|
|
|
### Adding another catalog
|
|
|
|
**Sources** → *Add a catalog source*: a name and the URL of its index. Optionally paste the source's
|
|
`ed25519:…` public key — with a key set, the host refuses any index from that source that isn't
|
|
correctly signed, rather than falling back to an unsigned one.
|
|
|
|
Adding a source is a trust decision you make once: its plugins become installable on this host.
|
|
They are always attributed to it and never carry the Verified badge, which belongs to the built-in
|
|
catalog alone.
|
|
|
|
To publish a plugin to the built-in catalog, open a pull request against
|
|
[`punktfunk-plugin-index`](https://git.unom.io/unom/punktfunk-plugin-index) — its README covers the
|
|
format and what review looks for.
|
|
|
|
## Installing from the CLI
|
|
|
|
Two commands: install the plugin, then turn the runner on. The host CLI handles the rest — creating
|
|
the plugins directory, pointing it at the package registry, and starting the supervisor.
|
|
|
|
<Tabs items={['Linux', 'Windows']}>
|
|
<Tab value="Linux">
|
|
|
|
```sh
|
|
punktfunk-host plugins add playnite # or: rom-manager
|
|
punktfunk-host plugins enable # turn the runner on (once)
|
|
```
|
|
|
|
On **SteamOS** the [host installer](/docs/steamos-host) ships the runner automatically (user-scoped
|
|
under `~/.local` — the read-only `/usr` can't take the package). If the console reports the runner
|
|
isn't installed on an older setup, re-run `scripts/steamdeck/update.sh` once.
|
|
|
|
</Tab>
|
|
<Tab value="Windows">
|
|
|
|
Run these from an **elevated** PowerShell — right-click **PowerShell** → **Run as administrator**.
|
|
The plugins directory lives under `%ProgramData%\punktfunk`, which is admin-owned. The runner task
|
|
itself runs as the low-privilege `NT AUTHORITY\LocalService` account — `plugins enable` sets that
|
|
up (including read access to the runner's scoped API token).
|
|
|
|
```powershell
|
|
punktfunk-host plugins add playnite # or: rom-manager
|
|
punktfunk-host plugins enable # turn the runner on (once)
|
|
```
|
|
|
|
If `punktfunk-host` isn't found, open a **new** terminal (the installer adds it to `PATH`), or use
|
|
the full path: `& "$env:ProgramFiles\punktfunk\punktfunk-host.exe" plugins add playnite`.
|
|
|
|
</Tab>
|
|
</Tabs>
|
|
|
|
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.
|
|
|
|
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
|
|
and it carries its catalog badge instead.
|
|
|
|
### The rest of the commands
|
|
|
|
| Command | What it does |
|
|
|---|---|
|
|
| `punktfunk-host plugins add <name…>` | Install one or more plugins. |
|
|
| `punktfunk-host plugins remove <name…>` | Uninstall. |
|
|
| `punktfunk-host plugins list` | List what's installed, with versions. |
|
|
| `punktfunk-host plugins enable` | Enable + start the runner. |
|
|
| `punktfunk-host plugins disable` | Stop + disable the runner. |
|
|
| `punktfunk-host plugins status` | Is the runner enabled and running? |
|
|
|
|
A bare name resolves to the first-party package — `playnite` installs `@punktfunk/plugin-playnite`,
|
|
always from Punktfunk's own package registry. Any other name (`punktfunk-plugin-*`, a foreign
|
|
`@scope/pkg`) would install from the **public npm registry** and is refused unless you add
|
|
`--allow-public-registry` — a guard against typos and look-alike packages pulling untrusted code
|
|
onto your host.
|
|
|
|
> Plugins are operator-installed code with operator privileges — they can launch games and run
|
|
> commands. Install only plugins you trust, from a registry you control.
|
|
|
|
## Updating and removing a plugin
|
|
|
|
**Update.** The console's **Installed** tab grows an **Update to <version>** button whenever the
|
|
catalog pins a newer version than the one you have; it installs through the same confirmation as a
|
|
fresh install, and the console restarts the runner for you. There is no `plugins update` command —
|
|
from a terminal, re-run `punktfunk-host plugins add <name>`. That installs the newest version the
|
|
package registry has rather than the version the catalog pins, and it does **not** restart the
|
|
runner: restart it yourself so the new code is picked up
|
|
(`systemctl --user restart punktfunk-scripting`, or `Restart` the `PunktfunkScripting` task).
|
|
|
|
**Remove.** The **Uninstall** (bin) button on the **Installed** tab removes the package *and*
|
|
restarts the runner, so the plugin stops straight away. `punktfunk-host plugins remove <name>`
|
|
removes the package only — restart the runner yourself, as above, to stop a plugin that is still
|
|
running.
|
|
|
|
Uninstalling removes the package and nothing else. A plugin's own config and cache stay where it
|
|
wrote them — `~/.config/punktfunk/plugin-state/<plugin>/` on Linux,
|
|
`%ProgramData%\punktfunk\plugin-state\<plugin>\` on Windows — so re-installing later picks your
|
|
settings back up. Delete that directory yourself if you want it gone.
|
|
|
|
To stop *every* plugin without uninstalling anything, turn the runner off: **Disable runner** on the
|
|
Installed tab, or `punktfunk-host plugins disable`.
|
|
|
|
## ROM Manager
|
|
|
|
`@punktfunk/plugin-rom-manager` — point it at your ROM directories and it scans them, matches each
|
|
platform to an installed emulator, fetches box art (SteamGridDB, or the keyless libretro thumbnails),
|
|
and reconciles the result into your library as the `rom-manager` provider. ~25 built-in platforms
|
|
(NES through Switch, PS1/2/PSP, Dreamcast, and more), per-game overrides, and a console page to
|
|
configure it all.
|
|
|
|
```sh
|
|
punktfunk-host plugins add rom-manager
|
|
```
|
|
|
|
Then add a root or two — from the console's **ROM Manager** page, or in the config file:
|
|
|
|
<Tabs items={['Linux', 'Windows']}>
|
|
<Tab value="Linux">
|
|
|
|
`~/.config/punktfunk/plugin-state/rom-manager/config.json`:
|
|
|
|
```jsonc
|
|
{
|
|
"roots": [
|
|
{ "dir": "/mnt/roms/snes", "platform": "snes" },
|
|
{ "dir": "/mnt/roms/ps1", "platform": "ps1", "excludes": ["*.sav"] }
|
|
],
|
|
"art": { "provider": "auto", "steamGridDbKey": "" }
|
|
}
|
|
```
|
|
|
|
</Tab>
|
|
<Tab value="Windows">
|
|
|
|
`%ProgramData%\punktfunk\plugin-state\rom-manager\config.json`:
|
|
|
|
```jsonc
|
|
{
|
|
"roots": [
|
|
{ "dir": "D:\\roms\\snes", "platform": "snes" },
|
|
{ "dir": "D:\\roms\\ps1", "platform": "ps1", "excludes": ["*.sav"] }
|
|
],
|
|
"art": { "provider": "auto", "steamGridDbKey": "" }
|
|
}
|
|
```
|
|
|
|
</Tab>
|
|
</Tabs>
|
|
|
|
`plugin-state` is where a plugin reads and writes its own files — on Windows it is the one
|
|
directory `plugins enable` grants the low-privilege runner write access to. A config file placed
|
|
anywhere else under `%ProgramData%\punktfunk` is not read.
|
|
|
|
Full options and the platform/emulator list are in
|
|
[the plugin's repo](https://git.unom.io/unom/punktfunk-plugin-rom-manager).
|
|
|
|
## Playnite
|
|
|
|
`@punktfunk/plugin-playnite` — mirrors your **[Playnite](https://playnite.link)** library (Steam,
|
|
GOG, Epic, Xbox, itch, emulators, manually-added games — everything Playnite manages) into your
|
|
library. Launching a title hands it back to Playnite, which performs the real launch, so there are no
|
|
per-store launch commands to maintain. Covers are served by the host, so it scales to large libraries.
|
|
|
|
Playnite is Windows-only, so both halves of this one live on the **Windows host**. Because Playnite
|
|
keeps its library locked while running, there are **two parts**:
|
|
|
|
1. **The plugin** — from an elevated PowerShell:
|
|
|
|
```powershell
|
|
punktfunk-host plugins add playnite
|
|
punktfunk-host plugins enable
|
|
```
|
|
|
|
2. **The Punktfunk Sync extension** (in Playnite) — download `punktfunk-sync.pext` from the
|
|
[plugin's builds](https://git.unom.io/unom/punktfunk-plugin-playnite/actions) and **double-click
|
|
it** to install it in Playnite like any add-on, then restart Playnite once.
|
|
|
|
Open the console's **Playnite** page — it shows "Exporter connected", and your games sync within
|
|
seconds of any library change. Filters (installed-only, per-store, hidden) live on that page or in
|
|
`%ProgramData%\punktfunk\plugin-state\playnite\config.json`. Details are in
|
|
[the plugin's repo](https://git.unom.io/unom/punktfunk-plugin-playnite).
|
|
|
|
## VirtualHere (USB passthrough)
|
|
|
|
`@punktfunk/plugin-virtualhere` — hands a **physical USB device** on your couch machine to the
|
|
host while you play, and gives it back afterwards. The game sees the real device, so this is the
|
|
answer for a racing wheel, a HOTAS, pedals, an arcade stick, or any controller whose value is that
|
|
it is not emulated.
|
|
|
|
<Callout type="warn">
|
|
This plugin drives [VirtualHere](https://www.virtualhere.com/), a commercial USB-over-IP product
|
|
**sold separately** by VirtualHere Pty. Ltd. Nothing from VirtualHere is bundled or downloaded by
|
|
Punktfunk — you install and license it yourself. The plugin is not affiliated with or endorsed by
|
|
VirtualHere.
|
|
</Callout>
|
|
|
|
You need both halves of VirtualHere running before the plugin is any use:
|
|
|
|
- **The USB Server on the couch**, sharing the device. Free for one device; beyond that, and to run
|
|
the client as a service, VirtualHere requires a purchased licence.
|
|
- **The USB Client on the host**, ideally installed as a service so it survives logging out.
|
|
|
|
Servers exist for Windows, Linux, macOS and Android couches. **There is no VirtualHere server for
|
|
iOS or tvOS**, so iPhones, iPads and Apple TVs cannot pass devices through — nothing on the
|
|
Punktfunk side can change that.
|
|
|
|
```sh
|
|
punktfunk-host plugins add virtualhere
|
|
punktfunk-host plugins enable
|
|
```
|
|
|
|
Then open the console's **VirtualHere** page. The **Devices** tab lists whatever the couch is
|
|
sharing; pick one and it writes a rule matching the device *by name*, which keeps working after the
|
|
couch reboots or the device moves to another port. By default the device is handed over when video
|
|
starts and returned when it stops, so the couch keeps its own controller the rest of the time —
|
|
you can widen that to the whole session, or to the entire time a client is connected.
|
|
|
|
If nothing happens, the **Diagnostics** tab walks the whole two-sided setup and tells you which
|
|
part to fix. The same checks are available as `punktfunk-plugin-virtualhere doctor`, which is the
|
|
useful thing to paste into a support thread.
|
|
|
|
Full configuration is in
|
|
[the plugin's repo](https://git.unom.io/unom/punktfunk-plugin-virtualhere).
|
|
|
|
## Troubleshooting
|
|
|
|
**`punktfunk-host: command not found`** — on Windows, open a new terminal so it picks up the
|
|
installer's `PATH` change, or call the exe by full path. On Linux the host package installs it to
|
|
`/usr/bin/punktfunk-host`.
|
|
|
|
**"the plugin runner isn't installed"** — the runner ships as its own package. On Ubuntu:
|
|
`sudo apt install punktfunk-scripting`. On Fedora: `sudo dnf install punktfunk-scripting` from the
|
|
[same RPM repo you installed the host from](/docs/fedora). On Arch:
|
|
`sudo pacman -Syu punktfunk-scripting` (a full `-Syu`, like every other install from that repo).
|
|
On SteamOS, re-run `scripts/steamdeck/install.sh` (or
|
|
`scripts/steamdeck/update.sh`). On Windows, re-run the installer and keep the scripting component.
|
|
|
|
**The plugin doesn't show up in the console** — check the runner is actually running with
|
|
`punktfunk-host plugins status`, then look at its log:
|
|
|
|
<Tabs items={['Linux', 'Windows']}>
|
|
<Tab value="Linux">
|
|
|
|
```sh
|
|
journalctl --user -u punktfunk-scripting -f
|
|
```
|
|
|
|
</Tab>
|
|
<Tab value="Windows">
|
|
|
|
The runner task doesn't write a log file, so run it in the foreground to watch it start your
|
|
plugins (stop it with <kbd>Ctrl</kbd>+<kbd>C</kbd>):
|
|
|
|
```powershell
|
|
& "$env:ProgramFiles\punktfunk\bun\bun.exe" "$env:ProgramFiles\punktfunk\scripting\runner-cli.js"
|
|
```
|
|
|
|
</Tab>
|
|
</Tabs>
|
|
|
|
## Writing your own
|
|
|
|
A plugin is a TypeScript module built on **`@punktfunk/plugin-kit`** (`definePluginKit`), supervised
|
|
by the runner. The kit owns lifecycle, config and state, the library sync engine, and serving the
|
|
plugin's console page; `@punktfunk/host` (`definePlugin`) is the lower-level host client underneath
|
|
it. Start from the
|
|
[plugin-kit README](https://git.unom.io/unom/punktfunk/src/branch/main/plugin-kit) and
|
|
[ROM Manager](https://git.unom.io/unom/punktfunk-plugin-rom-manager), which is the reference
|
|
implementation — or any of the three plugins above.
|