Files
punktfunk/docs-site
enricobuehlerandClaude Opus 5 365caa23be fix(plugins): plugin output reaches the console's log page, and /tmp is no longer hidden from the runner
A user could not get the VirtualHere plugin to use their VirtualHere client
and asked, reasonably, where the logs were. There was no good answer, and the
reason they were stuck turned out to be ours.

**The runner could not see /tmp.** `punktfunk-scripting.service` set
PrivateTmp=yes, which hands the unit a private tmpfs. But integrating with
things already running on the box is the entire job of a plugin, and on Linux
those talk over /tmp: VirtualHere's client IPC is the FIFO pair /tmp/vhclient +
/tmp/vhclient_response, X11 is /tmp/.X11-unix. So the plugin launched the vendor
binary happily and could then never reach the daemon behind it — while the same
command worked perfectly in the operator's own shell, because that shell has the
real /tmp. No config change could fix it, which is exactly the loop the report
described. PrivateTmp is now off, with /tmp added to ReadWritePaths (which
ProtectSystem=strict would otherwise make read-only).

**Plugin logs now land in the console.** Plugins are not host child processes —
the runner is a separate bun process that import()s each plugin in-process — so
nothing they print passed through the host's tracing, and the console's Logs
page could not show a single plugin line. The fallback was journalctl on Linux;
on Windows the runner's scheduled task writes no log file at all, so a failing
plugin was diagnosable only by stopping the task and re-running the runner by
hand. Both mean shell access on the host box, which is what the console exists
to avoid — and it left the one question a stuck user asks with no answer.

So the runner now tees its output to POST /api/v1/plugins/logs, and those lines
join the host's own ring under one cursor, targeted plugin:<name>. The console
grows a Host/Plugins switch beside the level filter; an empty Plugins view says
the thing that is actually usually wrong (the runner isn't running) rather than
"adjust the filter".

The shipper keeps stdout authoritative — journald and foreground output are
unchanged whatever the host is doing — and is built so that logging can never
hurt the thing being logged: it never throws into a caller, holds a bounded
queue that drops oldest and then says how many, backs off when the host is away
(a restart is normal), and re-sends a batch the host failed to take. Lines
logged while a POST is in flight are kept, which cost one round to get right:
the first version held its recursion guard across the await and silently dropped
exactly the lines a busy plugin produces.

Runner lines that report a failure (a refused unit file, a crashed plugin, a
give-up) now go out at warn/error instead of all arriving as INFO, so the
console's level filter means something for them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 15:47:51 +02:00
..

punktfunk-docs

The Punktfunk documentation site: Fumadocs on TanStack Start (Vite + Nitro/bun preset).

Content lives in content/docs/ as .md/.mdx. This site is the source of truth for the user-facing guides; design rationale lives in the internal punktfunk-planning repo.

API reference

/api renders the host's management REST API as an interactive Scalar reference (linked from the top nav, the docs sidebar, and the landing page). It reads public/openapi.json — a snapshot of the repo's generated spec. Refresh it after a management-API change:

# from the repo root — regenerate the spec, then copy the snapshot in:
cargo run -p punktfunk-host -- openapi > api/openapi.json
cp api/openapi.json docs-site/public/openapi.json

Nothing in CI diffs the two, so the snapshot goes stale silently — that manual cp is the only thing keeping them in sync. Before publishing docs, check that they match:

diff <(jq -S . api/openapi.json) <(jq -S . docs-site/public/openapi.json)

That should print nothing. Right now it doesn't: the committed snapshot predates the /api/v1/update/check, /api/v1/update/apply and /api/v1/update/status endpoints, so the published /api reference is missing the host self-update surface — re-copy it.

Develop

bun install
bun run dev        # http://localhost:3001  (docs at /docs)

CI gates every change on bun run build followed by bun run lint (the TypeScript typecheck), in that order — the build emits the .source typegen the typecheck imports. Run both before you push.

Build & serve

bun run build
bun run start      # serves .output/ via Bun

Layout

source.config.ts          Fumadocs MDX collection (content/docs)
content/docs/             the docs content (.md/.mdx) + meta.json nav
src/
  routes/
    __root.tsx            RootProvider + html shell
    index.tsx            landing page
    docs/$.tsx           catch-all docs renderer (Fumadocs DocsLayout)
    api/index.tsx        Scalar API reference (reads public/openapi.json)
    api/search.ts        Orama search endpoint
  lib/source.ts          Fumadocs loader over the generated collection
  lib/layout.shared.tsx  shared nav chrome
  components/mdx.tsx      MDX component map
  styles/app.css          Tailwind 4 + Fumadocs preset