2026-04-29 12:16:44 -04:00
2026-04-29 09:39:59 -04:00
2026-04-19 17:11:58 -04:00
2026-04-29 09:39:59 -04:00

welcome-to-codevalet-as-a-contributor

Onboarding for new contributors to the codevalet Gitea instance. The production Gitea host is published through the Pangolin HTTPS edge at https://gitea.cvgitea.ddns.net. 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. Current production value: https://gitea.cvgitea.ddns.net.

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 different Gitea instance
FORGE_GITEA_ORG codevalet fork or sibling org
FORGE_ORCHESTRATOR_REPO_URL https://gitea.cvgitea.ddns.net/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.

FORGE_GITEA_URL is the canonical public HTTPS endpoint. Do not append any port suffix; Pangolin terminates TLS on the standard public HTTPS port and the onboarding repo, orchestrator clone URL, and global git credential scope all assume the portless form.

.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, confirm it is https://gitea.cvgitea.ddns.net for production, and do not append any port suffix.
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.
just login exits with Gitea server_error: "a grant exists with different scope" Run just revoke-grant (opens <FORGE_GITEA_URL>/user/settings/applications and prints the matching FSDGG_CLI_CLIENT_ID). Revoke the matching app, then re-run just login. Required only once after a scope-set change. Full reference: docs/oauth-grant-scope-mismatch.md.
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%