Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/gunnar-ui

README.md

gunnar-ui

The gunnar console: a passkey-guarded web UI (Leptos, WebAssembly) and its backend-for-frontend, which speaks sshsig-signed gRPC to a gunnar appliance.

Install

The console is one of Gunnar's three products (gunnar, gunnar-ui, gunnar-cli), one crate each:

cargo install gunnar-ui

crates/gunnar-ui is a thin program over the gunnar-ui-server library, with the console bundle built in (cargo xtask console puts it there before a publish); gunnar-ui-server is the same program under its old name. Its extras (public-site, mcp, engine-gix, online, federation, full) are off by default: the default build is AIRGAP. The guide "Gunnar by hand" in the gunnar repository walks through a first start with a passkey.

Building

git clone ssh://git.gunnar.rs:2222/nordisk/gunnar-ui
cd gunnar-ui
cargo build --release -p gunnar-ui-server

The console itself is a WebAssembly bundle, built by trunk, which is required:

cd crates/gunnar-ui-web
trunk build --release

This page documents gunnar-ui 1.6.0 — the version gunnar-ui-server --version prints.

License: AGPL-3.0-only, © 2026 Vetra Automation AB and Ignalina ApS (Rickard Ernst Björn Lundin). Commercial terms: COMMERCIAL-LICENSING.md.

Routes

The console's pages, routed in the browser. A visitor has no session; signed in is any passkey session; admin is the role a session carries.

page who may open it
/repos visitor
/repo/{namespace}/{name} visitor
/repo/{namespace}/{name}/files · /graph · /diff · /blame · /search · /chain visitor
/repo/{namespace}/{name}/pulls · /pulls/{n} · /issues · /issues/{n} visitor
/ signed in
/storage · /usage · /usage/{namespace} · /events signed in
/account signed in
/access signed in; every mutation on it is admin
/mirrors · /mirrors/import signed in; every mutation on it is admin

The server's own routes, read off serve.rs. Everything under /api/ that is not listed as a visitor's is refused without a session; the role floor is decided by method and prefix in one place (auth::middleware::role_floor).

route group who may open it
/health · /login · /login/repos · /auth/status · /auth/logout visitor
/auth/passkey/login/* · /auth/passkey/register/* visitor
/welcome · /auth/signup/status (a metered build that sells) visitor
/signup — the ONE sign-up page: ?plan=private a new private Gunnar, ?plan=10gb an account here (or, signed in, growing it); /private redirects to it · /auth/private/ladder · /auth/private/{quote,start,status} · /auth/signup/{ladder,name,start,rename} (a metered build that sells) visitor; a signed-in buyer's order is theirs (the session's name, locked)
GET /api/repos visitor
GET /api/repos/{namespace}/{name} and its refs · head · log · browse · diff · blame · search · object · chain leaves visitor
every other GET/HEAD under /api/, including /api/events signed in
/api/account/keys — a person's own keys signed in
every other mutation under /api/ signed in, operator
/api/principals · /api/keys · /api/grants · /api/namespaces · /api/accounts · /api/forges · /api/mirrors, mutating admin
/api/namespaces/{name}/increase (a metered build holding a monetize bearer) signed in to read, admin to spend
/auth/passkey/invites · /auth/passkey/credentials signed in, a fresh ceremony to mint or revoke; gunnar decides who may invite whom and whose passkeys a person may see
/auth/sessions/revoke-all admin

Passkeys, invites and sessions live in gunnar

The console keeps no passkey, no invite and no key of anybody's. gunnar issues the WebAuthn challenge, verifies every login against the passkey it stores on the person's principal (Access.WebLogin), and enrols a short-lived session key the console minted for that login; everything the session does is signed with that key, as the person, and gunnar's grants decide. Logging out revokes the session key. Registration (Access.WebRegister) redeems an invite gunnar minted (Access.CreateInvite, asked as the person who invites), or is the operator's first passkey through the installer's break-glass token. A passkey this console registered before 2026-10-04 moves to gunnar when the console starts. The console's own store keeps cookie sessions, preferences and caches only.

Thanks

Sponsors

None so far — we would love to have one. Contact gunnar@gunnar.rs.