Skip to content

feat(bundles): every app with a web page says how its first admin is created, and Crow tells you to finish setup when it can't do it for you - #791

Merged
kh0pper merged 5 commits into
mainfrom
feat/bundle-first-admin
Nov 6, 2026
Merged

kh0pper merged 5 commits into
mainfrom
feat/bundle-first-admin

Conversation

@kh0pper

@kh0pper kh0pper commented Nov 6, 2026 •

Copy link
Copy Markdown
Owner

Security audit N3-4, phase 1.

A freshly installed app's own first-run admin page could be claimed by anyone or anything on the machine before the owner got there. An audit VM run showed another account reaching uptime-kuma's setup page on loopback first.

The operator ruled that Crow creates the app's first admin where the app supports it, with a random password in the keychain, and that every other app gets a "finish this app's setup now" notice. This phase adds the declaration, the check and the notice. Converting more apps to Crow-created admins comes in later PRs. No compose or env file changes here.

firstAdmin in the manifest (schema plus bundle-contract.mjs rules). There are five modes:

  • crow: Crow creates the admin, and the declaration says how:

    • via: "env" with secretEnv, which must be a keychain-generated variable that the compose file actually uses;
    • or via: "postInstall", whose script must read one.

    A token that isn't the admin password (vaultwarden's /admin token, for example) does not pass.

  • finish-setup: the first visitor claims it.

  • default-login: the app ships a known login. Requires user.

  • external: it connects to an app you already run, and that app's login applies.

  • none: no login of its own.

Every bundle with a webUI must declare one, and build-registry --check fails otherwise.

Declared on every web-UI bundle (42 manifests, patch bumps so installed copies refresh):

  • crow: actual-budget, maker-lab-advanced, miniflux, minio, unsloth.
  • default-login: shiori, motioneye.
  • finish-setup: uptime-kuma, gitea, forgejo, vaultwarden (signups stay open until you turn them off), plex (claimable by the first Plex account until it has a claim token), changedetection (set its password), frigate, paperless and the other first-visit apps.
  • external: immich, kodi.
  • none: ntfy (nothing to claim: deny-all, no signup), the token-gated browser, and the apps with no login of their own.

After installing a finish-setup or default-login app (also an installed web-UI bundle with no declaration, such as a community bundle, and also when the install half-fails):

  • The Extensions page shows "Finish 's setup now". It says either that anyone on this machine can claim the admin account, or that the app ships a known login.
  • The card links to the app the same way the Nest tile does. It says "Restart Crow first" while the proxy route isn't live yet, or that the link is unavailable if the proxy failed to mount.
  • A Done button clears the card.
  • The bell rings once per install.
  • The markers are per-install settings and never sync.
  • en + es.

The developer docs describe firstAdmin and prefer crow whenever an app can take an admin from env or a CLI.

Tests:

  • the schema and contract rules, including a web-UI fixture without firstAdmin failing the check;
  • a repo walk: every real web-UI bundle declares it, and every crow passes its rule;
  • the card for each mode, Done, restart-pending wording, and the bell once per install;
  • i18n parity and the design system.

Nest tiles are unchanged: proxied apps still open in a new tab, pinned by a test against the previous output.

After deploy, existing finish-setup installs show the card once, without a bell.

Host: build-registry --check and check-port-allocation pass. 1600 tests pass across the bundle, manifest, registry, extensions, keychain, install, i18n, design-system, settings-scope, nest and public-hygiene files.

Next phases, tracked in the private engineering notes:

  • convert apps that can take an env or CLI admin to crow, each checked against its pinned image;
  • decide webUI for seven federated apps that have admin pages but no webUI today;
  • wire or remove twelve post-install.sh scripts that nothing runs.

https://claude.ai/code/session_015Vve3soBJ9qTcyeCCLyYyh

…created, and Crow tells you to finish setup when it can't do it for you

A freshly installed app's own first-run admin page could be claimed by
anyone or anything on the machine before the owner got there. Every bundle
with a web UI now declares firstAdmin: crow (Crow creates it, with a
keychain-stored generated secret or a post-install step), finish-setup (the
first visitor claims it), default-login (it ships a known login) or none.
The registry check fails a web-UI bundle without one.

After installing a finish-setup or default-login app, its Extensions page
shows 'Finish <app>'s setup now' with a link (or 'restart Crow first' when
the proxy route is not live yet) and a Done button, and the bell rings
once. The markers are per install and never sync.

Claude-Session: https://claude.ai/code/session_015Vve3soBJ9qTcyeCCLyYyh
… open in a new tab again, and Plex is flagged

Review follow-ups:
- A crow declaration now names how: via env with secretEnv (a keychain-
  generated variable the compose file actually uses) or via postInstall
  (a script that reads one). A token that is not the admin password no
  longer passes.
- Nest tiles for proxied apps open in a new tab again, exactly as before.
- Plex can be claimed by the first Plex account until it has a claim
  token: finish-setup. ntfy has nothing to claim: none. immich and kodi
  connect to an app you already run: a new external mode.
  changedetection gets a note to set its password.
- The bell rings even when the install half-fails, an installed web app
  with no declaration is treated as finish-setup, a failed proxy mount no
  longer reads as 'restart pending', and the Done endpoint returns a fixed
  error message.

Claude-Session: https://claude.ai/code/session_015Vve3soBJ9qTcyeCCLyYyh
@kh0pper
kh0pper force-pushed the feat/bundle-first-admin branch from 68be76c to 8a8c54d Compare November 6, 2026 16:21
@kh0pper
kh0pper merged commit 2bebd6e into main Nov 6, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant