Quasar v0.4.0 New

Applications that arrive with their own dashboard.

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.

What it is

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.

One YAML document

A deployment, a set of permissions, an interface and a script — pasted or imported by URL, exactly like a catalogue.

A control surface for one service

Tabs, actions and views written by somebody who knows what running that service actually involves, and which Quasar has no way to guess.

Shareable

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

Not a catalogue entry

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.

Not a separate class of application

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.

Not an add-on for an existing app

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 page

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.

Minecraft 1.21.4 (Fabric) Deployed from a station running

Players online

4 / 20

Memory

2.1 / 4 GB

Build

Fabric 0.16.9

Console

say the server is going down in 5 Send

Sent over RCON, inside this server's own container.

Below this block: Build, Routing, TLS, Storage, Environment — Quasar's own, untouched.

19 panel types, four families

Structure sectiongriddividerbanner

Data tablestatlistkeyvaluemarkdowncodeloggaugetimelineimage

Input formbuttonsearchconfirm

Embedding iframe

What an action returns is what happens

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.

The document

Five blocks, and you have read the format.

station.yaml
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.

An id collision is refused, not resolved

A 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.

A list that grows can ask the script

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.

Permissions

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.

exec

Run a command inside the named services

The strongest one, and the one the install screen says so about in those words. Reuses the Tasks machinery.

logs

Read the named services' container logs

Separate from exec because it is far weaker, and far more often all a station needs.

files

Read and write under the application's own folder

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.

env

Read or write the named environment keys

Per key, not wholesale: a station has no business reading a database password it did not generate.

net.internal

Reach the application's own containers

Named services and ports only — which is what lets a station embed a service's page without publishing a port to the world.

net.external

Reach the named hosts, over HTTPS

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.

lifecycle

Start, stop, restart, redeploy, set image

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.

notify

Send to the webhook you configured

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.

The sandbox

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.

A disposable process, one per call

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.

The worker holds nothing

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.

Bounded from outside

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.

It cannot take the dashboard with it

A panic, a stack overflow, an allocation storm: the worker dies, the panel says why, and every application on the server keeps running.

The bounds

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.

Theme

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.

Derived, not trusted

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.

Scope stops at the block

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.

Fonts are embedded, never fetched

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.

Installing and updating

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.

Re-fetching updates the surface, never what is running

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.

New permissions are held until accepted

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.

Every accepted revision is kept

If a new one breaks a panel, reverting is one click — and the application never stopped.

Hooks, and why they never block

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".

Where stations will come from Coming soon

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.

Find one

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.

Publish your own

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.

Follow a version

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.

Why the permission model comes first

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.

Nothing about your station changes

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.

Read the format, then write one.

The documentation covers the whole format. Everything it describes is in the release you would install with the line below.

root shell on the server
curl -sSL https://raw.githubusercontent.com/AymericChaverot/quasar/main/setup.sh | sudo bash