enricobuehler aee3c194af
CI / publish (push) Has been cancelled
CI / build (push) Has been cancelled
fix(dist): ship a self-contained bundle so it installs from the registry
The unscoped package lives on the Gitea npm registry but its deps span two
registries (`@punktfunk/host` on Gitea, `effect`/`undici` on npmjs). Installing
by name with `--registry <gitea>` sets the default registry for the whole tree,
so `effect` 404s (Gitea doesn't proxy npm), and an unscoped name can't be
scope-mapped. Fix: bundle the backend into ONE self-contained `dist/index.js`
(+ `dist/cli.js`) via `bun build --target=bun` with `@punktfunk/host` + `effect`
inlined — mirroring the scripting runner. The published package now has zero
runtime dependencies and installs with a single `--registry` flag.

- build → `bun build src/{index,cli}.ts --target=bun --outdir dist --external undici`
- @punktfunk/host + effect → devDependencies (bundled, not resolved at install)
- resolveUiDir(): walk to the SPA dir so staticDir works in the bundled
  (dist/index.js) and unbundled (dist/server/index.js) layouts
- version 0.1.0 → 0.1.1

Verified: the bundle loads + scans/detects/previews with NO node_modules present.
2026-07-18 11:18:38 +02:00

punktfunk-plugin-rom-manager

A punktfunk plugin that scans your ROM folders, maps them to emulators, fetches box art, and reconciles everything into the host game library — so your retro games appear automatically in the punktfunk web console, native clients, and Moonlight /applist, each launchable and covered in art. It ships a web UI that lives inside the punktfunk console (no second password, no second port).

Think of it as Steam ROM Manager for punktfunk: it even uses the same art source (SteamGridDB) when you give it an API key.


Requirements

  • A punktfunk host (Linux or Windows) with the scripting runner (punktfunk-scripting) enabled.
  • Bun (the runner is Bun).
  • @punktfunk/host with the plugin-UI surface (servePluginUi). This is what powers the console-hosted UI; the standalone fallback works without it.

Install

cd "$(punktfunk config-dir)/plugins"     # e.g. ~/.config/punktfunk/plugins
bun add punktfunk-plugin-rom-manager --registry https://git.unom.io/api/packages/unom/npm/
# then enable the runner (opt-in):
systemctl --user enable --now punktfunk-scripting          # Linux
Enable-ScheduledTask PunktfunkScripting                    # Windows

Open the punktfunk console — a ROM Manager entry appears in the nav. That's it.

Headless / host-only boxes without the console can use the standalone UI or just drop a config.json (below) — the sync engine is fully functional from the file alone.

Quick start (config file)

The plugin owns <config_dir>/rom-manager/config.json. A minimal example:

{
  "roots": [
    { "dir": "/mnt/roms/snes", "platform": "snes" },
    { "dir": "/mnt/roms/ps1",  "platform": "ps1", "excludes": ["*.sav", "bios/**"] }
  ],
  "art": { "provider": "auto", "steamGridDbKey": "" }
}

See config.example.json for every option. Then, from the plugin dir:

bunx punktfunk-plugin-rom-manager scan       # what the scanner finds
bunx punktfunk-plugin-rom-manager detect     # which emulators are installed
bunx punktfunk-plugin-rom-manager preview    # the desired library (no host write)
bunx punktfunk-plugin-rom-manager sync       # reconcile into the live library

Box art

Two sources, selected by art.provider:

Provider Key? Art types Notes
steamgriddb yes portrait + hero + logo + header What Steam ROM Manager uses; best coverage + fuzzy title match. Free key from steamgriddb.com profile preferences.
libretro no box-art portrait only Keyless libretro-thumbnails; works out of the box.
auto (default) SteamGridDB if a key is set, else libretro

Art URLs are handed to the host as-is; native clients/console fetch them directly (so clients need internet for covers), and Moonlight covers are proxied host-side. Verdicts are cached in cache.json and never re-probed unless you change provider/key.

Platforms & emulators

~25 built-in platforms (NES, SNES, N64, GameCube, Wii, Switch, GB/GBC/GBA, NDS, 3DS, PS1/2/PSP, Genesis, Master System, Game Gear, Saturn, Dreamcast, PC Engine, Neo Geo Pocket, WonderSwan, Lynx, Atari 2600/7800, Xbox). Arcade (MAME/FBNeo romset versioning) is intentionally out of scope.

Emulators are detected best-effort (PATH → Flatpak → known install paths): RetroArch (+ cores), Dolphin, PCSX2, DuckStation, PPSSPP, mGBA, melonDS, Flycast, xemu, plus Azahar/Ryujinx. Any platform's emulator, core, and args are overridable per-platform and per-game (config or UI). Missing an emulator? Add a custom-* template.

Disc games are folded so a set is one entry: .m3u playlists absorb their discs, .cue/.gdi sheets hide their tracks, .chd stands alone.

The UI

Console-hosted (default)

servePluginUi serves the SPA on a loopback ephemeral port behind a per-boot secret and registers it with the host; the console reverse-proxies it and gates it with your existing console sign-in. Four pages: Setup (ROM roots), Emulators (detected + per-platform overrides), Games (scan preview with covers + per-title include toggles), Sync (status, art settings, sync options).

Standalone UI

For host-only installs, set ui.standalone: true (config). It serves the same SPA on a fixed port:

"ui": { "standalone": true, "port": 47993, "bind": "127.0.0.1" }

A non-loopback bind requires a password (fail-closed — the UI can edit launch commands, i.e. host-user code):

bunx punktfunk-plugin-rom-manager set-password 'your-password'

Security

  • ROM filenames are untrusted input that ends up in a sh -c / cmd.exe /c string run as the host user. Every path goes through strict per-OS quoting (src/quote.ts); on Windows, names containing cmd.exe metacharacters (" % ! ^ & | < >) are refused rather than risk injection.
  • config.json lives in the hardened <config_dir>; the plugin refuses a group/world-writable one.
  • Outbound network in v1: SteamGridDB (if keyed) and libretro-thumbnails only. No telemetry.

Development

bun install                   # resolves @punktfunk/host from the Gitea npm registry
bun test                      # engine unit tests (pure core: quoting, scanner, reconcile, art)
bun run typecheck
bun run build:all             # backend → dist/, SPA → dist/ui/
cd ui && bun run dev          # SPA dev server (point it at a running standalone instance)

This plugin requires @punktfunk/host ≥ 0.1.0 (the version that introduced servePluginUi).

License

MIT OR Apache-2.0.

S
Description
A Punktfunk plugin for managing ROMs & Emulators
Readme 541 KiB
Languages
TypeScript 97.1%
JavaScript 2.3%
CSS 0.4%
HTML 0.2%