Administration

Deckgauge has no separate "admin panel" — being an administrator is a flag that widens what the existing API and UI let you do. This page covers where that flag comes from, what it unlocks, and — because a prior release shipped this leaving no admin and no documentation — exactly how to recover an instance where nobody can grant anyone access.

Roles at a glance

Three things decide what a signed-in user can reach, and they stack rather than replace each other:

GrantWhere it livesUnlocks
cockpit-adminKeycloak realm role, read from the JWTEverything below, plus the database admin flag's effects (they're OR'd together)
Database admin flag (users.is_admin)Postgres, read fresh on every API requestThe same things cockpit-admin unlocks — this is the flag bootstrap:admin sets, so recovery doesn't depend on Keycloak being reachable
cockpit-analyticsKeycloak realm role, read from the JWTEngineering-intelligence dashboards, and — combined with Viewer on the org tree named in the request — timesheet reads

Holding cockpit-admin (or the database flag) automatically satisfies the cockpit-analytics check too — an admin never loses intelligence or timesheet pages just because nobody has assigned cockpit-analytics yet. The reverse does not hold: holding cockpit-analytics makes you neither an admin nor Owner-equivalent on anything.

Concretely, an admin (by either grant) can:

  • Act as Owner on every org tree, without an OrgTreeAccess row — admin-ness is checked as a rule at evaluation time, not backfilled as data, which is what keeps an admin from being locked out of a tree they were never explicitly granted.
  • Read and write salary fields on org-tree employees, and read them in every response — the one check in the product that isn't a board or org-tree role at all.
  • Change the timesheet in-progress status-rules configuration, and the Advisor's provider configuration.
  • Trigger a manual engineering-intelligence sync.
What admin does not unlockSkip to "What admin does not do" below — the short version is boards.

First run

Nothing to configure. The first person who ever signs in on a fresh install is granted the database admin flag automatically, the moment their token is verified. This only fires when the users table is empty at that instant — an install that already has users never grants this way, on purpose: "grant admin when no admin exists yet" would hand admin to whichever employee happened to sign in first after an upgrade, not necessarily the person running the install. On an instance that already has users, use bootstrap:admin (below) instead.

The shipped cockpit-admin and cockpit-analytics realm roles exist in Keycloak from a fresh import, but nobody holds either one until you assign it — see Configuration reference.

Upgrading into org-tree access control

Existing users lose org-tree accessPer-tree OrgTreeAccess rows don't exist until someone creates them, and this release ships with that table empty. Every org tree that already existed loses everyone's read and write access the moment you upgrade, until an Owner or admin shares it again — this is intended, not a bug: there was no reliable record of who used to informally rely on the old "any signed-in user can read and edit" behaviour to carry forward as real grants.

Two things soften this, and one thing does not:

  • A tree created after this ships stamps its creator as Owner automatically — this only bites pre-existing trees.
  • An admin (either grant) is Owner-equivalent on every tree regardless, so at least one path back in survives the upgrade — if an admin exists.
  • It does not lock anyone out of creating their own new tree: creating one only requires being signed in, and the creator becomes its Owner — so a team can keep working with brand-new trees even with zero admins on the instance. What upgrading removes is access to the trees that already existed.

If no admin exists on the instance either, see Recovery below.

Granting access

Org-tree access, day to day: use the in-app Share control. An Owner opens the tree and adds a registered user at Viewer, Editor, or Owner — the same UI a board's Owner uses; see Access & security. This is the normal path and needs no shell access.

Realm roles, from a shell: cockpit-admin and cockpit-analytics are Keycloak realm roles — nobody can grant themselves one, and there's no in-app control for them. Assign one with kcadm.sh against an already-running, already-imported realm:

docker exec deckgauge-keycloak /opt/keycloak/bin/kcadm.sh config credentials \
  --server http://localhost:8080 --realm master --user admin --password admin
docker exec deckgauge-keycloak /opt/keycloak/bin/kcadm.sh create roles \
  -r deckgauge -s name=cockpit-analytics
docker exec deckgauge-keycloak /opt/keycloak/bin/kcadm.sh add-roles \
  -r deckgauge --uusername <email> --rolename cockpit-analytics

Swap the role name for cockpit-admin to grant that instead; the create roles step is only needed the first time a role doesn't already exist yet on this realm (the shipped realm already declares both, so on an install using the stock realm-export.json you can usually skip straight to add-roles). The admin credentials above are Keycloak's own shipped defaults (KEYCLOAK_ADMIN_USER / KEYCLOAK_ADMIN_PASSWORD) — use whatever you've set in .env if you've changed them.

realm-export.json only seeds a fresh importKeycloak reads keycloak/realm-export.json once, the very first time the realm is created — kc.sh start-dev --import-realm skips the import entirely if the realm already exists in keycloak-db's volume. Adding a role, or anything else, to that file and recreating the keycloak container does nothing to a realm that's already been imported once. Use the admin console or kcadm.sh against the running realm instead — the commands above work either way.

A person granted a realm role this way won't see its effect until their browser is holding a token minted after the grant: the role list is baked into the JWT at sign-in (or at the app's automatic silent token refresh), not looked up fresh on each request. Signing out and back in is the immediate, certain way to pick it up; otherwise it arrives on its own the next time the browser's access token refreshes.

Recovery — nobody can get in

This is the path for the failure mode this page exists to prevent: no account holds cockpit-admin, the install isn't fresh (so the automatic first-user grant won't fire), and nobody can share an org tree because nobody has a role on it. Run this from a shell with DATABASE_URL pointed at the instance's Postgres — the CLI reads it the same way the API does, and does not fall back to a repository .env file on its own:

DATABASE_URL="postgresql://cockpit:cockpit@localhost:5433/cockpit" \
  pnpm --filter @deckgauge/db bootstrap:admin --email [email protected]

Unlike the Keycloak-role path above, this one needs no sign-out. The database admin flag it sets is read straight from Postgres on every API request the granted user makes — not from their JWT — so it takes effect on their very next request, using whatever session they're already signed in with. (bootstrap:admin --list shows who currently holds it; --revoke removes it, and refuses to drop the last admin on the instance.)

That flag makes the person Owner-equivalent on every org tree immediately, which is often enough on its own. If you also want real OrgTreeAccess rows — for example so a specific person shows up as Owner in the Share dialog rather than relying on the standing admin rule — run the org-tree grant script too:

DATABASE_URL="postgresql://cockpit:cockpit@localhost:5433/cockpit" \
  pnpm --filter @deckgauge/db grant:org-access --email [email protected] --tree all --role OWNER

Both scripts look the user up by email, so they must have signed in at least once already for a local User row to exist. See Configuration reference for the full variable reference.

Recovery — you're an admin, but connections say "you need to be an organization administrator"

The admin flag and organization membership are two different axes, and connection management is decided on the second one. Being an instance administrator does not make you an administrator of a tenant: the membership check deliberately ignores the admin flag, because that flag comes from a Keycloak realm role or a Postgres column, neither of which is scoped to an organization — honouring it there would silently make one break-glass account an administrator of every organization at once.

So an account that holds cockpit-admin or the database flag but has no organization membership is refused on all 21 connection routes — creating, editing, deleting, testing and re-tokening Jira, GitHub, Azure DevOps and GitLab connections, plus the Azure DevOps production-deploy configuration — and on the project-sync lists, which are scoped to the caller's organization and so need a membership to scope to. The UI states the reason rather than showing an empty screen.

Changed 2026-08-27 — triggering a sync now needs a membership tooThe three manual sync triggers (POST /github/sync, POST /azure-devops/sync and POST /intelligence/sync, the last of which is the Sync / Sync All button on the intelligence screens) now refuse a caller with no organization membership, answering 403 NO_ORGANIZATION. A break-glass account holding only cockpit-admin or users.is_admin could previously trigger a sync and can no longer. This is deliberate and not a bug: the job the trigger enqueues is scoped to the caller's organization, so a caller with no organization has no connections to sync — and before the change the job carried no scope at all and swept every tenant's connections, spending their stored credentials. If you are recovering an instance and need a sync, take a membership first via the adoption path below.

The way back in is to become a member, and there are only two paths:

  • The organization still has a living administrator — ask them to invite you. This is the normal path, and bootstrap below will refuse with 409 ORGANIZATION_EXISTS precisely so that it cannot be used to walk into a tenant that is being administered.
  • The organization has no active administrator left — an instance admin can adopt it:
    curl -X POST http://localhost:4000/organizations/bootstrap \
      -H "Authorization: Bearer <your token>" \
      -H 'content-type: application/json' \
      -d '{"name":"Acme","slug":"acme"}'
    That endpoint is gated on the instance admin flag, not on membership, which is what makes it reachable when nothing else is. On a single-organization install it adopts the organization that already exists — your name and slug are only used if there is none to adopt — and enrols you as an ACTIVE ADMIN member of it. Your very next request has connection management back.
Adoption is not a takeover"No active administrator left" means no membership row with role ADMIN, status ACTIVE and a user actually bound to it. If one exists, bootstrap returns 409 and changes nothing.

What admin does not do

Admin does not manage connections. Creating or editing a Jira/GitHub/Azure DevOps/GitLab connection requires being an ADMIN member of the organization that owns it — the instance admin flag is not consulted, for the reason given under "you're an admin, but connections say..." above. An instance admin with no membership is refused there like anyone else.

Admin does not trigger syncs. As of 2026-08-27 the manual sync triggers require an organization membership, for the same reason connection management does: the enqueued job is scoped to one organization, and the instance admin flag names none. See the callout under "you're an admin, but connections say...".

Admin does not touch boards. The board-access check has no admin short-circuit at all — reading, editing, or owning a board always requires that board's own BoardAccess row at the required rank, admin or not. An admin who wants into a board still needs to be added to it like anyone else. This is a deliberate, narrower blast radius than the org-tree rule above: admin closes the "nobody can manage a tree" gap this page exists for, without also making every board on the instance readable by whoever holds the role.

Related

  • Access & security — the full role model: board roles, org-tree roles, and the cockpit-analytics rules in detail.
  • Configuration referenceCOCKPIT_ADMIN_ROLE, COCKPIT_ANALYTICS_ROLE, and the grant:org-access script.
  • Keycloak & identity — realm import behaviour and identity providers in depth.

Last updated