11 KiB
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.11interpreter counts) justuv- graphical web browser (optional; see Headless and SSH hosts)
~/.local/binonPATH
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:
- 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; whenFORGE_GITEA_USERNAMEis set,login_hint=<username>is also included. - After the token exchange,
userinfo.preferred_usernameis compared case-insensitively toFORGE_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
- Reads
FORGE_GITEA_URL,FSDGG_CLI_CLIENT_ID, optionalFORGE_GITEA_USERNAMEfrom.env. - GET
<gitea>/.well-known/openid-configuration; no endpoints are hardcoded. - PKCE verifier (
secrets.token_urlsafe(64)) and S256 challenge (RFC 7636). - HMAC-signed CSRF
state; session key held in process memory only. - Authorise URL carries
prompt=loginand, if set,login_hint=<FORGE_GITEA_USERNAME>. - Opens the default browser (or prints the URL under
--no-browser) and starts a one-shot loopback HTTP listener on the port fromFSDGG_CLI_REDIRECT_URI. - Verifies the returned state, POSTs the code + verifier to the token
endpoint, receives
access_token,refresh_token,expires_in. - GET
/login/oauth/userinfofor the authenticated username. - If
FORGE_GITEA_USERNAMEis set and does not match, discard the token and open the Gitea logout URL (see Wrong-user guard). - Writes
~/.forge-stack-devpi-gateway-gitea/client-auth.jsonatomically (.tmp+chmod 0600+rename). Gateway-owned fields are preserved. - Installs
git-credential-forge+forge_auth.pyunder~/.local/bin/and setsgit config --global credential.<FORGE_GITEA_URL>.helper. Scope isFORGE_GITEA_URLonly; 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-access → Repository 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. .envholds configuration only. Tokens live in~/.forge-stack-devpi-gateway-gitea/client-auth.jsonat mode0600.- The credential helper is scoped to
FORGE_GITEA_URL; requests for other hosts flow through git's normal credential chain. - The OAuth
stateis 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 duringjust loginorjust refreshleaves the previous valid state intact. just check-accessandjust clone-orchestratorneuterGIT_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.