9af52fd7547f6a8ba3b86a4874cb7b6d380f7610
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a9bf51407e |
cliamp moves into the plugin, and the platform loses its last music file
The owner read the code and asked why `plugins/music/api/router.ts` was three
lines importing `@@/api/music/router` — platform code that knows the string
'music'. He was right, and tracing it found the justification was hollow.
The chain: server.tsx:20 imported the cliamp relay's two exports, which are
used only on commented-out lines; so the relay's functions were never invoked;
so its call to getMusicServerWsUrl never ran; and the file's other export,
getMusicServerUrl, had no consumers at all. A dead import held a music-named
file in the platform, and I documented that as a "seam" last night after
checking the import existed and stopping there.
Everything cliamp now lives in plugins/music/cliamp/:
sidecar/music/{cliamp-ws,pulse-audio}.ts, asoundrc, the test
api/cliamp/relay.ts
apps/FileBrowser/{CliampPanel,AudioStreamPlayer}.tsx
src/servers/sidecar/music/, src/servers/api/cliamp/ and src/servers/api/music/
are gone. server.tsx has no cliamp import, provider name, handler entry or
route. The platform contains no file named for music or cliamp.
Two of the things that moved were live, not inert.
The file browser's `Play` context-menu item, on any audio file or folder, set
?play= and rendered a cliamp terminal pointed at /api/cliamp/ws — a route that
upgraded into a handlers entry that was commented out, so handlers[provider]!
asserted non-null on undefined. Using that menu item crashed the socket
handler. Removed: the action, the layout, the panel wiring and both menu
entries. Verified the routes now 404 rather than crash.
That closed the totality drift as a side effect. server.tsx's route table and
its handlers map agree again for the first time since 2026-08-13, and
registry.test.ts now asserts it rather than pinning the hole.
The proxy is built in the plugin now, and its prefix is DERIVED. It was the
literal '/api/music', which the proxy uses to strip characters off the path —
correct only because mountPrefix returns /music for a first-party publisher.
The same plugin published by anyone else mounts at /api/p/<publisher>/music and
would have forwarded /alice/music/stream to a sidecar expecting /stream. A
latent bug only third parties would ever hit, and a quiet violation of the rule
that mountPrefix is the one function allowed to know about provenance. Offscale
has the identical hardcode and still needs it.
Still open there: appName is passed as a literal, because a plugin's router
cannot see its own directory name — the platform imports the module and reads
`router`, so there is nowhere to inject it. The fix is a factory the installer
calls with the plugin's identity.
Plugin backend coupling is down to 7 imports, all of them "a plugin talks to
its host": data-path, sidecar/connect, sidecar/protocol, officer-url, the
manifest type, officerdb/db and the users.id FK. Nothing music-shaped left.
bunx tsgo clean. 797 tests, 787 pass, same 7 pre-existing failures. Verified
live: manifest 200, favorites 200, stream 206, /api/cliamp/ws 404.
|
||
|
|
e930586878 |
plugins declare the host binaries they need, and the installer checks
Offscale was self-sufficient. Music is not — it shells out to ffmpeg and ffprobe — and the way it fails without them is the reason this is a check rather than a line in a README. It does not fail. Missing ffprobe means the indexer catches the spawn error and returns a track carrying its filename and nothing else: no title, artist, album, duration or embedded lyrics. It then walks the whole library, writes a complete cache tree and reports success. Five swallowed catches, no log, no counter, and the only tell is coversSaved: 0 in a report nobody reads. So `osDependencies` is a manifest field: the binary to probe on PATH, why it is needed, and a package name per package manager. The shape is taken from scripts/setup-old/setup.sh rather than invented — probe the binary, case on $PM — and the names are per-manager rather than canonical-with-overrides because lib/packages.sh already recorded why that indirection was rejected. Probing the binary is what makes "built-in on this OS" free: on PATH means the package map is never consulted. Four decisions worth naming. Missing and uninstallable REFUSES the install, first, before a table is created or a row written — so there is nothing to undo, and the alternative is a plugin that installs, answers 200 and quietly produces nothing. The status is on GET /api/plugins and rendered before the button, because the owner is deciding whether to let the server run a package manager as root and that needs answering first. Installing by hand and watching it flip to present is the escape hatch on a machine without passwordless sudo. Package names get a deliberately narrow regex and reach Bun.spawn as an argv ARRAY, never a shell. Both halves are load-bearing: the regex means a metacharacter cannot get there, argv means it would be an argument rather than syntax if it did. Narrower than package managers actually accept — no `:`, no `+` version pins — because a plugin needing one wants a conversation. Success is OBSERVED, not inferred: after installing, the binaries are re-probed. A package manager exiting 0 having installed something that does not provide the binary is exactly the failure this exists to catch. installCommand mirrors lib/packages.sh's pkg_install_now exactly, including apt's non-interactive environment, so there is one definition of "install a package" rather than two that drift. sudo always gets -n: under PM2 a password prompt is not a slow path, it is a hang. brew never escalates. Verified live. ffmpeg and ffprobe were absent on this machine all evening; the page showed both missing with the exact root command, the install streamed `dependencies: installing ffmpeg with apt` then `ffprobe, ffmpeg now on PATH`, and X-Audio-Duration appeared on a stream response for the first time. The refusal path was exercised against a temporary probe dependency: HTTP 400, steps: [], reason named. THIS CHANGED THE MACHINE: ffmpeg 6.1.1-3ubuntu5 is now installed via apt. Found on the way: a manifest is read once per process. Discovery does `await import()` and the module cache holds it, so editing a manifest changes nothing until pm2 restart officer — including `outdated`. Cost ten minutes and is now in the runbook. bunx tsgo clean. 797 tests, 787 pass, 7 fail — the same seven, +25 new. |
||
|
|
de3340398c |
music becomes a plugin, and the player stays behind
The whole of music moves to plugins/music/: the sidecar (index, indexer,
stream-audio, nightly-reindex), the four Postgres tables and their queries,
the /music workspace panels, MUSIC_API.md and the reindex CLI. The platform
keeps no music routes, no music capability entry, no music screen and no
music schema.
Three things stayed, each on purpose.
cliamp and the widget were out of scope by the owner's decision. The plugin's
sidecar still serves the two cliamp sockets, so it imports cliamp-ws.ts and
pulse-audio.ts from @@/sidecar/music/ — the files stay where they were.
The player did not move, and that was the open judgement call. Deciding it
took one fact: the dashboard widget imports useMusicPlayer and PlayerTrack
from officerdev, and the platform cannot import from a plugin. So the player
STATE stays whatever is decided about the UI around it, and two copies would
mean two audio engines. Given that, the engine and the bar stayed with the
state rather than being split from the thing they drive. Moving them would
also have needed a shell slot rendering a plugin-provided component on every
route — the one escape hatch this system deleted on purpose. MusicPlayerHost
gates on can('music'), which is now the plugin's permission, so the seam
switches itself off with the plugin.
api/music/router.ts stays too: api/cliamp/relay.ts imports getMusicServerWsUrl
from it. The plugin's api/router.ts re-exports that proxy rather than building
a second one — two subscribers to the one-shot music:server port announcement
would work today and 503 on the first reconnect where only one was listening.
Two bugs found on the way, neither visible from reading.
The app-store catalogue still listed music. Availability is derived from
sidecar_installs and a PLUGIN never gets a row there, so `music` would have
been permanently unavailable — which puts /music into deniedRoutes and blanks
the screen on a server where the plugin was installed and healthy. Exactly
the headscale bug documented six lines above it in the same file, and it would
have fired on the first install. Entry removed.
[test] root was "./src", so moving lyrics.test.ts into plugins/ stopped it
running and said nothing — the count fell by nine and the suite still read
green. Root is now the repo. Positional filters cannot fix this: `bun test
plugins` matches under root and finds src/servers/plugins/ instead.
registry.test.ts tested the `personal` mechanism THROUGH the music capability.
Re-anchored on a fixture rather than on another entry, because borrowing a
feature only moves the problem to the next extraction — and three of those
four tests had been passing for the wrong reason since music's api was
commented out on 2026-08-13, when everything started resolving to "refused
because nothing is claimed". The cliamp sockets being claimed by nothing is
now pinned by a test instead of being rediscovered.
music's `personal` paths ride across on readOnlyWrites, the one field a
manifest has. isRequestAllowedAtLevel concatenates the two lists, so a read
grant permits exactly the four paths it permitted yesterday, and no field was
added to the manifest to design a per-user model that is not this work.
bunx tsgo clean. 772 tests, 762 pass, 7 fail — all seven pre-existing and
unrelated (cliamp, pty, and five capability tests that other switched-off
plugins break). Baseline was 757/10; the three that went green are the ones
re-anchored above.
Not yet verified on the live server — that is next.
|