One YAML document
A deployment, a set of permissions, an interface and a script — pasted or imported by URL, exactly like a catalogue.
Quasar v0.4.0 New
A station does not just deploy a service. It gives that service's page the tabs, the actions and the views it actually wants — a console, a version upgrade, whatever the thing is administered with — because whoever wrote the station knew what running that particular service involves, and Quasar has no way to guess.
One document, three blocks more than a catalogue entry.
A station is a catalogue entry with permissions, an interface and a script bolted on — and it should stay readable as one. Parameter substitution, generated secrets, compose rewriting, port collision refusal: all of it already exists, and applies to stations without a line of new code.
A deployment, a set of permissions, an interface and a script — pasted or imported by URL, exactly like a catalogue.
Tabs, actions and views written by somebody who knows what running that service actually involves, and which Quasar has no way to guess.
A station is a program somebody wrote for a service they run. Handing it to somebody else is handing them a file.
And what it is not
A catalogue entry says what to deploy. A station says that and what the page should look like afterwards. Code stays out of catalogues on purpose: a catalogue is data an operator reads to decide what to run.
A deployed station produces an ordinary application — the same containers, logs, storage explorer, backups, limits, TLS and webhooks — carrying one extra field. Remove the station and a perfectly normal application is left running.
A station describes its own deployment. Grafting one onto an application deployed some other way is a plausible later feature; it is not this one.
The script never produces HTML.
It returns data; Quasar renders it with its own components. That one rule is what makes stations safe to share — there is no markup to sanitise and no injection surface — and what makes them look like Quasar, since a station rendered through Quasar's components inherits all seven themes for free.
An example. A station somebody wrote for a game server — not something Quasar ships. Press the tabs.
Players online
4 / 20
Memory
2.1 / 4 GB
Build
Fabric 0.16.9
Console
Sent over RCON, inside this server's own container.
Online now
| Player | Actions | |
|---|---|---|
| Notch | op | KickBan |
| jeb_ | whitelisted | KickBan |
| dinnerbone | whitelisted | KickBan |
Re-read every 30 seconds, because the panel said so.
Last archive
world-2026-08-21.tar.zst
412 MB · 2 hours ago
A file out of the folder the station was allowed to read.
What this station has done
Installed mods
| Mod | Version | |
|---|---|---|
| Fabric API | 0.115.1 | current |
| Lithium | 0.14.3 | update |
| Simple Voice Chat | 2.5.26 | current |
api.modrinth.com — one of the three hosts this station
named, and the only reason it may reach the network at all.
Structure
sectiongriddividerbanner
Data
tablestatlistkeyvaluemarkdowncodeloggaugetimelineimage
Input
formbuttonsearchconfirm
Embedding
iframe
data renders the panel that asked; toast,
warn and error post a message;
refresh names panels to re-fetch;
navigate switches tab; download hands over
a file out of the application's own folder; progress
runs the whole thing as a background job with a live pane.
Waiting is not failing. A panel whose action fails while the container is still coming up draws a spinner and asks again, rather than a red card somebody has to reload.
Five blocks, and you have read the format.
schema: 1
id: example-station
name: Example
description: What this station is for, in one line
author: you
version: "1.0.0"
deploy: { ... } # what to run
permissions: { ... } # what the script may do
ui: { ... } # what the page shows
hooks: { ... } # when the script runs
script: | # the logic
export function status() { ... }
schema is required, and checked from the first release
A format that cannot say which version it is written in cannot be changed later without breaking every document already in the wild.
id collision is refused, not resolvedA catalogue entry may be overridden by reusing its id — two people can legitimately describe the same third-party software. A station is a program, and silently replacing somebody's program with somebody else's is not a feature.
Every version a project has ever released is not a list a document
can hold, and the thing a document reaches for instead — a free
text box — accepts a version that does not exist and hands you a
container that will not start. So a select may name
an action instead, and what comes back is added to
the written options rather than replacing them: a dropdown that
empties itself because somebody else's API is having a bad
afternoon is worse than a short one.
Nothing is granted by default.
A station with no permissions block gets a runtime that
can compute and return values, and nothing else. Every privileged
thing sits behind a permission the document declares — and a call into
a namespace that was not granted throws an error naming the missing
permission, rather than a mysterious undefined.
The strongest one, and the one the install screen says so about in those words. Reuses the Tasks machinery.
Separate from exec because it is far weaker, and far more often all a station needs.
Restricted to the declared globs. Paths are resolved through their symlinks before the check, or a link left in a volume by the application itself would be a full escape on the first write.
Per key, not wholesale: a station has no business reading a database password it did not generate.
Named services and ports only — which is what lets a station embed a service's page without publishing a port to the world.
Exact hosts, no wildcards, no plain HTTP, and redirects followed only to hosts also on the list. “May reach the internet” tells an operator nothing; a named host tells them everything.
The listed verbs only, and a station that never declares the block never gets any of them. Most have no business taking an application off the network for good, so most should not ask for stop.
Rate-limited.
What this does not defend against. An operator who
accepts exec from a hostile author — that is root on the
container by design, and the install screen says so in those words.
Every other capability is narrowed by name: services for
exec, globs for files, hosts for
net.external, keys for env, verbs for
lifecycle.
A station is untrusted code, and is run like it.
The script is JavaScript, run by goja — a JS interpreter written in
Go, with no fetch, no require, no
filesystem, no timers and one global. But an interpreter is not a
security boundary, so it is the first of two layers rather than the
answer.
The dashboard re-executes its own binary in worker mode, hands it the script and the call over a pipe, reads the result, and the worker dies. Nothing survives between two calls — which was already the rule, and is now enforced by the operating system rather than by convention.
No Docker socket, no filesystem, no network, no database handle. Every capability is a request sent back up the pipe, which the parent checks against the declared permissions and performs on the worker's behalf. An escape from the interpreter buys a process that can do nothing.
The parent samples the worker's resident size every 50 ms and kills it above the ceiling. A watchdog living inside the process it watches only notices after the kernel has already picked a victim — and the victim would be the dashboard.
A panic, a stack overflow, an allocation storm: the worker dies, the panel says why, and every application on the server keeps running.
| Bound | Panel source | Action | Hook |
|---|---|---|---|
| Wall clock | 10 s | 60 s | 120 s |
| Returned value | 1 MB | 1 MB | — |
| Worker memory | 128 MB resident | ||
| exec output | 1 MB per call | ||
| http response | 8 MB per call | ||
| files.read | 4 MB | ||
| store | 256 KB per application | ||
Wall clock is enforced twice: an interrupt from inside the worker, so
a while(true) reports a clean timeout its author can
read, and a hard kill from the parent shortly after, so a worker
ignoring its interrupt still dies. Long actions opt out of the 60-second
ceiling by running as background jobs.
A station brings an accent. The palette is always yours.
A station never sets bg, surface,
text or border. It sets an accent, a
typeface, a shape, a density and a mark — which is why a station
written on Nebula is still legible when it lands on Solarized, and why
its author never has to think about it. Press a swatch in the footer
and watch the block above keep its identity while the ground changes.
accent-hover comes from a color-mix;
accent-text is computed in Go, black or white by real
contrast against the declared accent. A station cannot ship
an unreadable pair. tint is what buys identity
back: one number, the accent mixed into the surfaces, landing the
right way on a dark ground and on a light one.
The navigation, the top bar and every standard section below —
Build, Routing, TLS, Storage, Environment — keep your theme. A
station is a shareable program, and one that can repaint Quasar's
own chrome can draw a convincing "update available" or a convincing
login screen. ok, warn and
err are not overridable either.
A data: URI, capped at 512 KB. The dashboard has to
render identically on a machine with no route to the public
internet, which is the normal case for the servers Quasar runs on. A
station pulling a font from a CDN breaks that, and leaks every page
view to whoever hosts it.
Settings → Stations, beside Catalogues.
Paste a document or give a URL, which keeps the source and gives you a re-fetch button. Nothing re-fetches on its own. Before anything is installed you are shown, in plain words, every privileged thing the document asks to be allowed to do.
A new revision replaces the interface, the theme, the script and the hooks for every application using that station — fix a broken panel once and every server running it gets the fix. It does not touch the deployed compose file, the image or the environment. Changing what runs stays an explicit Update, per application.
If a revision quietly added net.external, re-fetching would hand out a capability nobody granted. So the new document is stored but held: the page shows what changed, and the station keeps running the revision you approved until somebody accepts the new one.
If a new one breaks a panel, reverting is one click — and the application never stopped.
after_deploy, on_start, on_stop, on_health_fail, and actions on a schedule. A failing hook is reported in the deploy panel and the audit log; it does not fail the deployment. Scheduled actions run only while the application is running.
Everything privileged is written down.
station.import, station.permissions.grant,
station.revert, station.action,
station.exec, station.files.write,
station.env.write, station.lifecycle,
station.http.external — enough that "what did this station
do to my server" has an answer that is not "look at the logs and
guess".
A hub, the way a package registry is a hub.
A station is a file, which is a fine way to keep one and a poor way to find one. The intent is an index anyone can publish to and anyone can search — what npm is for packages and Docker Hub is for images, for the programs people write to run their own software.
Browse and search stations by what they run, read the document and the permissions it asks for before installing anything, and import it with the URL the page gives you.
A station is already a file. Publishing is putting that file somewhere other people can find it, under a name and a version — the document format has carried author and version from its first release for exactly this.
An imported station keeps its source and a re-fetch button today. A published one gives that button something stable to point at, and the rule that already governs it stays: a revision asking for more than you accepted is held until you accept it.
None of this exists yet, and there is no public index of stations to install from today. Until there is, a station reaches you the way any file does — you write it, or somebody sends you one — and Settings → Stations takes it pasted or by URL. Everything above this section is what ships now, and it is the whole of what a hub would ever distribute.
An index of third-party programs is only a reasonable thing to build if installing one from a stranger is a decision you can actually make. That is the part that already works: every privileged thing a station asks for is enumerated, named and shown in plain words before anything is installed, and a revision that asks for more is held until you accept it. A hub distributes documents; it does not distribute trust.
The document already carries an author and a
version, refuses an id that collides, and
keeps every accepted revision so a bad one is a click away from
being undone. Whatever you write today is what a hub would carry —
publishing is distribution, not a second format to port to.
The documentation covers the whole format. Everything it describes is in the release you would install with the line below.
curl -sSL https://raw.githubusercontent.com/AymericChaverot/quasar/main/setup.sh | sudo bash