Webius
A small launcher for every app published on the app server through Tailscale Serve. Open one page, see all your apps, click a card and the app opens in a new tab.
- SvelteKit (SSR,
@sveltejs/adapter-node) — needs a server to read the Tailscale Serve configuration. - Tailwind CSS v4 for styling.
- PWA — installable, standalone window, offline fallback screen.
- Live discovery — the app list comes from
tailscale serve status --json, so new routes appear automatically. - Friendly metadata — names, icons, colours and tags you edit in
/manageand that persist in a Docker volume.
Production URL (tailnet only): https://app.tail60a042.ts.net:10008
How it works
browser ──HTTPS──▶ Tailscale Serve (app server, :10008)
│ proxy
▼
Webius container 127.0.0.1:8788 ──▶ node build
│ reads /var/run/tailscale/tailscaled.sock
▼
`tailscale serve status --json`
Webius asks Tailscale for the current Serve routes, builds one card per route
(https://<host>:<port>), and merges them with your saved metadata. Routes are
cached to data/discovery-cache.json so the dashboard still renders when the
Tailscale daemon is briefly unavailable.
Discovering routes on the app server currently yields:
| Port | Target | App |
|---|---|---|
| 10000 | 127.0.0.1:8080 |
SuperiorNL |
| 10006 | 127.0.0.1:8765 |
TaliusTool |
| 10007 | 127.0.0.1:8093 |
ImagenContent Studio |
| 10004 | 127.0.0.1:8092 |
Excalidraw |
| 443 | 127.0.0.1:8180 |
Trilium Notes |
| 8181 | 127.0.0.1:8180 |
(duplicate, hidden) |
| 8443 | 127.0.0.1:3000 |
Gitea |
Usage
- Dashboard (
/) — search (/focuses the box), filter by tag, click a card to open that app in a new tab.Installappears when the browser offers the PWA install prompt. - Manage (
/manage) — rename a discovered route, pick an icon/colour/tags, hide duplicate routes, reset to defaults, and add or remove custom apps that are not (yet) behind Tailscale Serve.
Only give trusted users tailnet access: Webius is intentionally unauthenticated,
like the other apps on the server. robots.txt disallows indexing.
Configuration
All settings are optional; see .env.example.
| Variable | Purpose |
|---|---|
WEBIUS_DATA_DIR |
Where apps.json and the discovery cache live (/app/data). |
WEBIUS_SERVE_HOST |
Host used to build URLs when Tailscale is unavailable. |
WEBIUS_SELF_TARGET |
Loopback target of this instance, to flag its own card. |
WEBIUS_PORT |
Host loopback port to publish (default 8788). |
WEBIUS_DISCOVERY |
off skips tailscale serve status (plain local dev). |
WEBIUS_DISCOVERY_FILE |
Read Serve JSON from a file instead of the CLI (tests/dev). |
WEBIUS_TAILSCALE_BIN |
Override the tailscale executable name/path. |
ORIGIN |
Documented canonical origin; adapter-node derives it from the request. |
Local development
npm install
npm run dev # http://127.0.0.1:5173
The Vite dev server has no Tailscale socket, so point discovery at a fixture:
WEBIUS_DISCOVERY_FILE=/tmp/serve.json WEBIUS_SERVE_HOST=app.tail60a042.ts.net npm run dev
(/tmp/serve.json is the output of tailscale serve status --json.)
Checks:
npm run check # svelte-check + sync
npm test # unit tests (node --test)
npm run build # adapter-node output in build/
npm run preview # serve the production build on :3000
Docker
docker compose up -d --build # build + run locally on 127.0.0.1:8788
docker compose logs --tail=100 webius
The container binds 127.0.0.1:8788 only and mounts /usr/bin/tailscale and
/var/run/tailscale read-only so it can list routes.
Production (app server)
See AGENTS.md for the full, safeguarded procedure. In short:
./scripts/deploy.sh # rsync source + docker compose up -d --build
Then ensure the HTTPS route exists (only this port is touched):
ssh app@100.119.243.121 \
'tailscale serve --bg --https=10008 http://127.0.0.1:8788'
Open https://app.tail60a042.ts.net:10008.
Data
data/apps.json holds your overrides and custom apps and is not committed.
Back it up; it is small and human-readable. The discovery cache
(data/discovery-cache.json) is disposable.