FanaticPythoner (Nathan Trudeau) e27c8a2bd6 Rewrite onboarding prose to a neutral voice
2026-04-26 12:26:19 -04:00
2026-04-19 17:11:58 -04:00

welcome-to-codevalet-as-a-contributor

Onboarding for new contributors to the codevalet Gitea instance. Produces an authenticated local checkout of forge-stack-orchestrator with git operations against Gitea authenticated silently through an OAuth2 (PKCE) token shared with the orchestrator's devpi gateway.

Quick start

just setup

just setup runs doctor, init-env, check-gitea, login, check-access, and clone-orchestrator in sequence and prompts only when a decision requires a human (missing Gitea username, existing stored session, existing orchestrator clone). Idempotent.

Individual recipes remain available; see just --list.

Prerequisites

Linux, macOS, or Windows-via-WSL with:

  • git, bash, curl
  • Python 3.11+ (a uv python install 3.11 interpreter counts)
  • just
  • uv
  • graphical web browser (optional; see Headless and SSH hosts)
  • ~/.local/bin on PATH

just doctor verifies all of these and, for each miss, prints the exact install command for the detected platform (apt / dnf / pacman / zypper / brew) and a consolidated block at the end. No recipe runs sudo or installs anything system-wide.

A Gitea account on FORGE_GITEA_URL with membership in the codevalet organisation is also required.

Configuring .env

just init-env copies .env.example.env. The only required edit is FORGE_GITEA_USERNAME. Defaults:

Variable Default Change when
FORGE_GITEA_URL https://gitea.cvgitea.ddns.net:6006 different Gitea instance
FORGE_GITEA_ORG codevalet fork or sibling org
FORGE_ORCHESTRATOR_REPO_URL .../codevalet/forge-stack-orchestrator.git different fork
FORGE_ORCHESTRATOR_BRANCH (empty: default branch) branch pin
FORGE_WORKSPACE_ROOT . (clone at ./forge-stack-orchestrator, gitignored) clone elsewhere
FSDGG_CLI_CLIENT_ID registered PKCE CLI client never
FSDGG_CLI_REDIRECT_URI http://127.0.0.1:38111/callback port conflict only; must stay http:// + loopback per RFC 8252 §7.3 (127.0.0.1, [::1], or localhost) and match the Gitea OAuth app registration

FSDGG_CLI_REDIRECT_URI is not the Gitea URL. The Gitea server is at FORGE_GITEA_URL (remote, HTTPS). The redirect URI is the local loopback HTTP listener the CLI binds on the local machine so Gitea can hand back the OAuth authorisation code; OAuth 2.0 for Native Apps (RFC 8252 §7.3) prohibits any non-loopback / non-HTTP scheme here, and no public CA will issue a cert for 127.0.0.1 so HTTPS on loopback is not a meaningful option. On a shared or remote host, SSH-forward the port (see Headless and SSH hosts) rather than trying to publish the callback over the network.

.env is gitignored. OAuth client IDs are public by design; PKCE requires no client secret.

Recipes

Recipe Effect
just setup Full interactive onboarding. FORGE_SETUP_YES=1 accepts every default.
just setup --headless Same, but skips webbrowser.open; alias --no-browser.
just welcome Onboarding plan.
just doctor Prerequisite checks with copy-pasteable fixes.
just init-env Copies .env.example.env. Never overwrites.
just check-gitea Hits FORGE_GITEA_URL/api/v1/version.
just login Browser PKCE OAuth2 flow; installs the git credential helper.
just login-headless just login without opening a browser.
just status Stored-token state. Exits non-zero when not live.
just refresh Forces a token refresh (normally automatic).
just logout Clears stored Gitea tokens; keeps the credential helper and orchestrator-gateway fields.
just relogin logout + login.
just check-access git ls-remote against the orchestrator.
just clone-orchestrator Clones into $FORGE_WORKSPACE_ROOT. Idempotent.
just next-steps Commands to run inside the orchestrator checkout.
just uninstall Reverses login and clone-orchestrator.
just test Full test suite.

Switching Gitea users

just relogin           # or: just logout && just login

just logout leaves the credential helper installed and preserves orchestrator-gateway fields in ~/.forge-stack-devpi-gateway-gitea/client-auth.json. Git operations against FORGE_GITEA_URL fail loudly between logout and the next login.

Wrong-user guard

just login refuses to persist tokens that do not match FORGE_GITEA_USERNAME:

  1. The authorise URL carries prompt=login (OIDC Core §3.1.2.1), forcing Gitea to re-display its login form even under an active session; when FORGE_GITEA_USERNAME is set, login_hint=<username> is also included.
  2. After the token exchange, userinfo.preferred_username is compared case-insensitively to FORGE_GITEA_USERNAME. On mismatch the token is discarded, the auth file is left untouched, the Gitea logout URL (<FORGE_GITEA_URL>/user/logout?redirect_to=/user/login) is opened in the browser, and the CLI exits non-zero with recovery steps.

After just clone-orchestrator

cd ./forge-stack-orchestrator    # or $FORGE_WORKSPACE_ROOT/<repo>
just bootstrap
just repos-sync
just dev-env-refresh
just test-all

just login already wrote the OAuth token to the shared auth file, so the orchestrator's recipes reuse it without a second login. If the orchestrator reports a missing gateway bearer, run just repos-login inside the orchestrator checkout; it layers a gateway bearer on top of the existing Gitea token without a second Gitea login.

just login internals

  1. Reads FORGE_GITEA_URL, FSDGG_CLI_CLIENT_ID, optional FORGE_GITEA_USERNAME from .env.
  2. GET <gitea>/.well-known/openid-configuration; no endpoints are hardcoded.
  3. PKCE verifier (secrets.token_urlsafe(64)) and S256 challenge (RFC 7636).
  4. HMAC-signed CSRF state; session key held in process memory only.
  5. Authorise URL carries prompt=login and, if set, login_hint=<FORGE_GITEA_USERNAME>.
  6. Opens the default browser (or prints the URL under --no-browser) and starts a one-shot loopback HTTP listener on the port from FSDGG_CLI_REDIRECT_URI.
  7. Verifies the returned state, POSTs the code + verifier to the token endpoint, receives access_token, refresh_token, expires_in.
  8. GET /login/oauth/userinfo for the authenticated username.
  9. If FORGE_GITEA_USERNAME is set and does not match, discard the token and open the Gitea logout URL (see Wrong-user guard).
  10. Writes ~/.forge-stack-devpi-gateway-gitea/client-auth.json atomically (.tmp + chmod 0600 + rename). Gateway-owned fields are preserved.
  11. Installs git-credential-forge + forge_auth.py under ~/.local/bin/ and sets git config --global credential.<FORGE_GITEA_URL>.helper. Scope is FORGE_GITEA_URL only; other git hosts are untouched.

Token refresh

Access-token lifetime is set by Gitea (typically one hour). When it expires the credential helper calls the token endpoint with the stored refresh token and retries the git operation once; no user interaction. When the refresh token also expires (Gitea default: 30 days), the helper emits a one-line pointer at just login and the git operation fails.

Headless and SSH hosts

just setup --headless          # full onboarding, no webbrowser.open
just login-headless            # login step only

Both print the authorise URL on stderr. Paste it into any browser that can reach the loopback callback port (FSDGG_CLI_REDIRECT_URI, default 38111). For a remote host:

ssh -L 38111:127.0.0.1:38111 <host>
# then, on <host>:
just setup --headless

--no-browser is an alias for --headless.

just setup --headless refreshes silently when the access token is stale but the refresh token is still valid, avoiding the URL-paste step entirely.

Combining --headless with FORGE_SETUP_YES=1 while a fresh browser OAuth flow is required is contradictory: the authorise URL must be pasted into a browser, but FORGE_SETUP_YES=1 forbids interaction. just setup detects this and exits immediately rather than hanging. Either drop FORGE_SETUP_YES=1, or run just login once on a host with a browser to populate a valid refresh token before running just setup --headless on the headless host.

Troubleshooting

Symptom Resolution
just doctor reports a missing tool Run the fix: command printed beside it.
~/.local/bin not on PATH Add export PATH="$HOME/.local/bin:$PATH" to the shell rc and reopen.
just check-gitea → connection refused Verify FORGE_GITEA_URL and network access.
just login → browser does not open Run just login-headless.
just login → timed out waiting for OAuth callback Consent was not completed in the browser; re-run.
just login → cannot bind 127.0.0.1:38111 Another just login is running; wait or kill it.
"Why is the redirect URI http://127.0.0.1? The gateway is remote and HTTPS." FSDGG_CLI_REDIRECT_URI is the CLI's local loopback listener, not the Gitea or gateway URL. OAuth 2.0 for Native Apps (RFC 8252 §7.3) requires http on a loopback address. The Gitea server (FORGE_GITEA_URL) is the remote HTTPS endpoint, reached during the authorise step. On a remote host, SSH-forward the redirect port.
just setup --headless → "cannot complete a fresh login under --headless + FORGE_SETUP_YES=1" Drop FORGE_SETUP_YES=1, or run just login once on a host with a browser to populate a refresh token, then re-run just setup --headless.
just login rejects with a username-mismatch error Follow the logout link printed, sign in as FORGE_GITEA_USERNAME, re-run.
just check-accessRepository not found Account not in the codevalet org yet.
just check-access → asks for a password just login did not complete. Re-run.
Git prompts for a password on pull/push Refresh token expired. Run just relogin.
just status shows live: False Run just refresh; also happens automatically on the next git op.
just clone-orchestrator prints already cloned Intended; idempotent.
Reset local state just uninstall.

Security properties

  • Only a public OAuth client ID ships in .env.example; PKCE removes the need for a client secret.
  • .env holds configuration only. Tokens live in ~/.forge-stack-devpi-gateway-gitea/client-auth.json at mode 0600.
  • The credential helper is scoped to FORGE_GITEA_URL; requests for other hosts flow through git's normal credential chain.
  • The OAuth state is HMAC-signed with an in-memory session key; a replayed state from another session does not verify.
  • Writes to the auth file are atomic (.tmp + rename); a crash during just login or just refresh leaves the previous valid state intact.
  • just check-access and just clone-orchestrator neuter GIT_TERMINAL_PROMPT, GIT_ASKPASS, and the VSCode / X11 askpass variables so auth failures surface loudly instead of triggering GUI credential prompts.

Tests

just test

See tests/README.md.

Description
No description provided
Readme 190 KiB
Languages
Python 63.6%
Shell 32.5%
Just 3.9%