diff --git a/.order b/.order new file mode 100644 index 0000000..a61b198 --- /dev/null +++ b/.order @@ -0,0 +1,8 @@ +sign-in-options +entra-id +forgejo-identity-provider +github-sign-in +spaces-and-git-providers +configuration-reference +backup-and-restore +operations diff --git a/README.md b/README.md deleted file mode 100644 index 11e73fe..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# admin-guide - diff --git a/backup-and-restore/index.md b/backup-and-restore/index.md new file mode 100644 index 0000000..dd4be59 --- /dev/null +++ b/backup-and-restore/index.md @@ -0,0 +1,69 @@ +--- +id: backup-and-restore +title: Backup and restore +description: What actually needs a backup — Git holds the content, the database only holds a derived index. +tags: [admin, backup, restore] +lang: en +--- + +# Backup and restore + +f451's split between "Git is the source of truth" and "Postgres is only an +index" (see *Principles* in the Developer Guide) turns backup planning +into a short list, not a long one. + +## The one thing that matters + +> [!IMPORTANT] +> **The Forgejo data volume is the only backup-critical piece of a whole +> f451 deployment.** It holds every space's repository content plus +> Forgejo's own configuration and account database. Losing it without a +> backup means the wiki content is gone — there is nothing else it can be +> rebuilt from. + +If a space lives on GitHub instead of the bundled Forgejo, that content is +already backed up by GitHub itself; only Forgejo-hosted spaces are this +deployment's own responsibility. + +## The database is a rebuildable cache + +The Postgres volume holds the search index, rendered page content, the +knowledge-graph edges, sessions, and encrypted provider tokens. All of the +content-derived pieces are rebuilt automatically: + +- `POST /admin/reindex` (bearer-token-protected, see + [[operations]]) rebuilds the full index for one or all spaces from Git, + on demand. +- The drift job (see [[spaces-and-git-providers]]) rebuilds it + automatically within about five minutes of a missed webhook, without any + manual step. + +Losing the database without a backup costs two things, neither of them +content: + +- **Sessions** — everyone has to sign in again. +- **Encrypted provider tokens** — everyone has to reconnect their Forgejo + or GitHub account once (see [[sign-in-options]]). + +A database backup is still worth having in practice, purely to skip the +reindex time and avoid the mass reconnect — but it is never the *only* +copy of anything. + +## Restore, in outline + +1. Stop both stacks (the Git stack and the application stack). +2. Restore the Forgejo data volume from backup. +3. Start the Git stack and wait for it to report healthy. +4. Start the application stack. The database starts empty — apply pending + migrations before traffic reaches the new containers (the production + image ships a compiled migration runner for exactly this). +5. Trigger a full reindex of all spaces (`POST /admin/reindex`) rather than + waiting for the drift job — after a restore there is no reason to wait + up to five minutes for content to reappear. +6. Verify: the space list responds, a known page is readable, and + `GET /admin/status` reports no indexing errors for the reindex that just + ran. + +Sessions and account connections are deliberately not part of this +procedure — people sign in again and, if needed, reconnect their Git +account once, exactly as described above. diff --git a/configuration-reference/index.md b/configuration-reference/index.md new file mode 100644 index 0000000..e00040c --- /dev/null +++ b/configuration-reference/index.md @@ -0,0 +1,123 @@ +--- +id: configuration-reference +title: Configuration reference +description: Every environment variable the api service reads, grouped by concern. +tags: [admin, reference, configuration] +lang: en +--- + +# Configuration reference + +All variables below are read by the `api` service. None are required to +start f451 at all — an instance with nothing set runs in a minimal, +read-only, unauthenticated mode (`/healthz`/`/readyz` only). Each group +becomes meaningful once you set its first variable. + +## Spaces and providers + +See [[spaces-and-git-providers]] for the full explanation. + +| Variable | Required when | Default | +|---|---|---| +| `F451_SPACES` | Any space should be visible | — (minimal mode) | +| `F451_FORGEJO_URL` | A space uses `provider: "forgejo"`, or Forgejo account linking is enabled | — | +| `F451_FORGEJO_TOKEN` | A space uses `provider: "forgejo"` | — | +| `F451_GITHUB_TOKEN` | A space uses `provider: "github"` | — | +| `F451_WEBHOOK_SECRET_FORGEJO` | Forgejo webhooks should be accepted | — | +| `F451_WEBHOOK_SECRET_GITHUB` | GitHub webhooks should be accepted | — | +| `F451_GLOBAL_TEMPLATES` | A provider-wide template repository is shared across spaces | — | + +## Sign-in (OIDC) + +See [[sign-in-options]], [[entra-id]], [[forgejo-identity-provider]]. + +| Variable | Required when | Default | +|---|---|---| +| `F451_OIDC_ISSUER` | Auth should be enabled at all — unset means the entire API is unauthenticated | — (auth off) | +| `F451_OIDC_CLIENT_ID` | `F451_OIDC_ISSUER` is set | — | +| `F451_OIDC_CLIENT_SECRET` | `F451_OIDC_ISSUER` is set | — | +| `F451_OIDC_REDIRECT_URL` | `F451_OIDC_ISSUER` is set — must equal `https:///auth/callback` | — | +| `F451_OIDC_PROVIDER_NAME` | Never required | unset → plain "Sign in" button | +| `F451_TOKEN_KEY` | `F451_OIDC_ISSUER` is set — otherwise the api fails to start | — | + +## Sign-in (GitHub) + +See [[github-sign-in]]. + +| Variable | Required when | Default | +|---|---|---| +| `F451_GITHUB_LOGIN` | GitHub should appear as a sign-in method | `0` (off) | +| `F451_GITHUB_OAUTH_CLIENT_ID` | `F451_GITHUB_LOGIN=1` | — | +| `F451_GITHUB_OAUTH_CLIENT_SECRET` | `F451_GITHUB_LOGIN=1` | — | + +## Account linking (reading and writing) + +Independent of which method someone signed in with — see +[[sign-in-options]]. + +| Variable | Required when | Default | +|---|---|---| +| `F451_FORGEJO_OAUTH_CLIENT_ID` / `_SECRET` | "Connect Forgejo" should be offered (needs `F451_FORGEJO_URL` too) | — (linking off) | +| `F451_GITHUB_OAUTH_CLIENT_ID` / `_SECRET` | "Connect GitHub" should be offered | — (linking off) | + +## Cookies and running more than one instance on a host + +| Variable | Required when | Default | +|---|---|---| +| `F451_COOKIE_PREFIX` | A second instance shares a host with this one — browsers do not separate cookies by port | `f451` | +| `F451_WEB_PORT` | A second instance needs a different host port for the web UI | `8080` | +| `F451_DRAWIO_PORT` | A second instance needs a different host port for the diagram editor | `8081` | +| `F451_FORGEJO_HTTP_PORT` / `F451_FORGEJO_SSH_PORT` | A second Forgejo instance shares a host | Forgejo defaults | +| `F451_FORGEJO_ROOT_URL` | A second Forgejo instance shares a host | Forgejo default | + +## Local development only + +> [!WARNING] +> Never set either of these in production. + +| Variable | Effect | +|---|---| +| `F451_INSECURE_COOKIES=1` | Drops `secure` on session cookies (plain HTTP). | +| `F451_OIDC_ALLOW_INSECURE=1` | Allows an `http://` OIDC issuer. | + +## Admin and operations + +See [[operations]] and [[backup-and-restore]]. + +| Variable | Required when | Default | +|---|---|---| +| `F451_ADMIN_TOKEN` | `POST /admin/reindex` and `GET /admin/status` should work at all — unset means both are fail-closed | — (disabled) | +| `F451_MAX_UPLOAD_MB` | A non-default media upload limit is needed | `10` | + +## Rate limits and reverse proxy + +| Variable | Required when | Default | +|---|---|---| +| `F451_RATE_LIMIT_AUTH_MAX` | A non-default auth rate limit is needed (per route, per client IP) | `10`/min | +| `F451_RATE_LIMIT_SEARCH_MAX` | A non-default search rate limit is needed | `60`/min | +| `F451_RATE_LIMIT_API_TOKEN_MAX` | A non-default limit for API-token traffic (MCP agents) is needed, per user across their tokens | `300`/min | +| `F451_TRUST_PROXY` | This deployment always runs behind a reverse proxy — without it, every user shares one rate-limit bucket | — (no proxy trusted) | + +> [!IMPORTANT] +> `F451_TRUST_PROXY` needs the number of trusted hops between the reverse +> proxy and the `api` service, not just "on" or "off". Setting it to `true` +> trusts the left-most `X-Forwarded-For` entry, which a client can set +> itself — that makes the rate limits trivially bypassable. Verify the +> correct hop count against your actual proxy chain before relying on the +> limits in production; see [[operations]]. + +## Public URL + +| Variable | Required when | Default | +|---|---|---| +| `F451_PUBLIC_BASE_URL` | Production, effectively always | — (falls back to the request's `Host` header) | + +`F451_PUBLIC_BASE_URL` is the exact scheme and host (no path) that users +type into their browser. It is the basis for the OAuth `redirect_uri` used +when connecting a Forgejo or GitHub account, and it is the "our own +origin" side of the CSRF origin check on every write request. + +> [!WARNING] +> A wrong value here does not fail loudly at startup — it fails at the +> first save, with every mutating browser request rejected with `403`. See +> [[operations]] for this symptom in the troubleshooting table. diff --git a/entra-id/index.md b/entra-id/index.md new file mode 100644 index 0000000..f43a090 --- /dev/null +++ b/entra-id/index.md @@ -0,0 +1,77 @@ +--- +id: entra-id +title: Microsoft Entra ID +description: Registering an app in Entra and pointing f451's OIDC configuration at it, step by step. +tags: [admin, auth, entra] +lang: en +--- + +# Microsoft Entra ID + +Entra ID is a standard OpenID Connect provider from f451's point of view — +nothing here is Entra-specific in the code, only in the app registration +steps below. + +## 1. Register an application + +In the Entra admin center, create a new **app registration**: + +- **Single tenant** (unless you specifically need multi-tenant sign-in). +- **Redirect URI**, platform *Web*: `https:///auth/callback` — this + must match `F451_OIDC_REDIRECT_URL` exactly, including scheme and any + trailing slash. +- Under **Certificates & secrets**, create a **client secret**. + +> [!WARNING] +> Client secrets expire. Note the expiry date somewhere your team actually +> looks (a calendar reminder, not just the Entra portal) — an expired +> secret takes sign-in down for everyone until it is rotated. Rotating it +> means generating a new secret in Entra and updating +> `F451_OIDC_CLIENT_SECRET`, then restarting the `api` service. + +## 2. Configure the ID token claims + +Recommended: add the optional claim **`email`** to the ID token (**Token +configuration** → **Add optional claim** → ID). f451 reads the user's +email from the `email` claim, falling back to `preferred_username` or +`upn` if either of those looks like an email address (contains `@`). +Without a usable email claim, sign-in still works, but the identity f451 +records for that person is less predictable. + +## 3. Restrict who may sign in + +By default, anyone in the tenant with access to the app can sign in. To +restrict that, open the **enterprise application** for this app +registration and turn on **Assignment required** under **Properties**, +then assign the users or groups who should have access under **Users and +groups**. This is the only access control at the sign-in layer — it does +not grant access to any space; that still comes from the person's Forgejo +or GitHub permissions (see [[sign-in-options]]). + +## 4. Configure f451 + +Set on the `api` service: + +| Variable | Value | +|---|---| +| `F451_OIDC_ISSUER` | `https://login.microsoftonline.com//v2.0` | +| `F451_OIDC_CLIENT_ID` | the app registration's Application (client) ID | +| `F451_OIDC_CLIENT_SECRET` | the client secret from step 1 | +| `F451_OIDC_REDIRECT_URL` | `https:///auth/callback` | +| `F451_TOKEN_KEY` | 32 random bytes, base64-encoded (`openssl rand -base64 32`) — encrypts stored provider tokens, independent of Entra | +| `F451_OIDC_PROVIDER_NAME` | optional, e.g. `Microsoft Entra` — labels the sign-in button "Sign in with Microsoft Entra"; unset shows a plain "Sign in" | + +f451 requests the scopes `openid email profile` — no Entra-side API +permissions need to be granted beyond what a standard app registration +gets by default. + +## Forgejo can use Entra too + +If your Forgejo instance should show the same people as f451 without a +separate identity system, configure Entra as an **authentication source** +in Forgejo itself (Forgejo admin panel → Authentication Sources → add an +OAuth2/OIDC source pointed at the same Entra app, or a dedicated one). +Users then still take the one extra step of linking their Forgejo account +under **Settings → Connections** in f451 — Entra authenticating both +systems does not by itself connect them. See +[[forgejo-identity-provider]] for the setup that removes even that step. diff --git a/forgejo-identity-provider/index.md b/forgejo-identity-provider/index.md new file mode 100644 index 0000000..4e2ae54 --- /dev/null +++ b/forgejo-identity-provider/index.md @@ -0,0 +1,67 @@ +--- +id: forgejo-identity-provider +title: Forgejo as the identity provider +description: Using the bundled Forgejo's built-in OIDC provider for sign-in, and linking accounts in one step. +tags: [admin, auth, forgejo] +lang: en +--- + +# Forgejo as the identity provider + +Forgejo ships a built-in OpenID Connect provider +(`/.well-known/openid-configuration`). f451 can point its one configured +OIDC issuer at it instead of an external identity provider — useful for +local development, and viable for production if Forgejo is already your +single source of identity (see [[entra-id]] for federating Forgejo's own +accounts to Entra, so user management still lives in one place). + +## Create the OAuth2 application in Forgejo + +In Forgejo, as an administrator (or as the account that should own the +app): **Settings → Applications → OAuth2 Applications**, create a new +application with redirect URI `https:///auth/callback`. Forgejo +gives you a **Client ID** and **Client Secret** — set these as +`F451_OIDC_CLIENT_ID` / `F451_OIDC_CLIENT_SECRET`, and set +`F451_OIDC_ISSUER` to the Forgejo instance's base URL. + +## The one-app trick: sign-in and account linking together + +Normally, signing in and connecting a Forgejo account (for reading and +writing spaces, see [[sign-in-options]]) are two separate steps: sign in +via whatever OIDC provider is configured, then separately connect Forgejo +under **Settings → Connections**. + +> [!TIP] +> If f451's OIDC client (`F451_OIDC_CLIENT_ID`/`_SECRET`) and the Forgejo +> account-linking connection (`F451_FORGEJO_OAUTH_CLIENT_ID`/`_SECRET`) +> point at the **same** Forgejo OAuth2 application, the first sign-in +> already links the Forgejo account — there is no second step. This is +> exactly the setup `scripts/dev-local-setup.sh` creates for local +> development. + +Using two different Forgejo OAuth2 applications for the two purposes also +works — it just means a person signs in, then still has to connect Forgejo +separately before they see any space. + +## User management lives in Forgejo + +Once Forgejo is the identity source, account lifecycle — creating +accounts, password resets, two-factor authentication, or federating a +further upstream source like LDAP — happens entirely in Forgejo's own +admin panel. f451 does not duplicate any of it; it only consumes the +resulting OIDC identity. + +## Local-only settings + +Running Forgejo as the identity provider over plain HTTP (typical for +local development) needs two additional variables on the `api` service: + +| Variable | Effect | +|---|---| +| `F451_INSECURE_COOKIES=1` | Drops the `secure` flag on session cookies so they survive over HTTP. | +| `F451_OIDC_ALLOW_INSECURE=1` | Allows an `http://` issuer during OIDC discovery. | + +> [!WARNING] +> Both are for local HTTP development only. Never set either in +> production — a production Entra issuer only ever speaks HTTPS anyway, so +> there is nothing to gain and a cookie without `secure` to lose. diff --git a/github-sign-in/index.md b/github-sign-in/index.md new file mode 100644 index 0000000..53f9411 --- /dev/null +++ b/github-sign-in/index.md @@ -0,0 +1,48 @@ +--- +id: github-sign-in +title: GitHub sign-in +description: Enabling GitHub as a sign-in method, independent of or alongside OIDC. +tags: [admin, auth, github] +lang: en +--- + +# GitHub sign-in + +GitHub can be enabled as its own sign-in method, separate from the single +configured OIDC provider (see [[sign-in-options]]). It works whether or +not an OIDC provider is configured at all. + +## Enable it + +| Variable | Effect | +|---|---| +| `F451_GITHUB_LOGIN` | Set to `1` to turn on "Sign in with GitHub". | +| `F451_GITHUB_OAUTH_CLIENT_ID` | Client ID of a GitHub OAuth app. | +| `F451_GITHUB_OAUTH_CLIENT_SECRET` | Client secret of the same app. | + +## Register the OAuth app on GitHub + +Under `https://github.com/settings/developers`, create an **OAuth App** +with **Authorization callback URL** set to `https:///auth/`. Use +exactly that path, not a more specific one — it must cover both +`/auth/github/callback` (sign-in) and `/auth/connect/github/callback` +(account linking, used when GitHub is only being connected rather than +signed in with). + +## Sign-in also links the account + +Signing in with GitHub links the GitHub account in the same step — there +is no separate "connect GitHub" action needed afterwards, unlike signing +in through OIDC and connecting a Git account separately (see +[[sign-in-options]]). + +## One person, two users + +> [!WARNING] +> f451 has no concept of "the same person across sign-in methods." Someone +> who signs in once via your OIDC provider and once via GitHub becomes +> **two** separate f451 users — with separate sessions and separate sets of +> connected accounts. This is rarely a problem in practice (most people use +> one method consistently), but worth knowing before you enable a second +> method for a team that already uses the first: tell people to pick one +> method and stick with it, rather than discovering the split themselves. diff --git a/index.md b/index.md new file mode 100644 index 0000000..d07f7a9 --- /dev/null +++ b/index.md @@ -0,0 +1,46 @@ +--- +id: admin-home +title: Admin Guide +description: Deploying and running f451 — sign-in setup, spaces, configuration, backup, and day-to-day operations. +tags: [admin, guide] +lang: en +--- + +# Admin Guide + +f451 is git-native: every space is a repository on Forgejo or GitHub, every +page a Markdown file in it. Postgres holds only a derived index (rendered +content, search vectors, graph edges) plus sessions and encrypted provider +tokens — nothing that lives exclusively in the database is irreplaceable. +That single fact shapes most of the operational decisions in this guide, +especially backup and restore. + +This guide is for the people who deploy and run an instance, not for people +writing pages in it — for that, see the User Guide space. + +## How this guide is organised + +- [[sign-in-options]] — the identity providers f451 supports, and why there + are no local accounts +- [[entra-id]] — step-by-step setup with Microsoft Entra ID +- [[forgejo-identity-provider]] — using the bundled Forgejo as the identity + source, and linking accounts in one step +- [[github-sign-in]] — enabling GitHub as a sign-in method +- [[spaces-and-git-providers]] — configuring spaces, service tokens, + webhooks, and the drift job +- [[configuration-reference]] — every environment variable, grouped by + concern +- [[backup-and-restore]] — what actually needs a backup, and what a reindex + rebuilds for you +- [[operations]] — health checks, troubleshooting, and running two + instances on one host + +> [!NOTE] +> **f451 has no role system of its own.** Who may sign in is decided by +> your identity provider; what a signed-in person can see and write is +> decided entirely by their linked Git account's permissions on the +> repository behind each space. See [[sign-in-options]]. + +If you are setting up sign-in for the first time, start with +[[sign-in-options]] to decide which provider fits your setup, then follow +the matching page. diff --git a/operations/index.md b/operations/index.md new file mode 100644 index 0000000..0a7aeac --- /dev/null +++ b/operations/index.md @@ -0,0 +1,72 @@ +--- +id: operations +title: Operations +description: Health checks, the admin reindex endpoint, a troubleshooting playbook, and running two instances on one host. +tags: [admin, operations, monitoring] +lang: en +--- + +# Operations + +## Health and readiness + +| Endpoint | Meaning | +|---|---| +| `GET /healthz` | Always `200` if the process is running — no database access. | +| `GET /readyz` | `200` if a query against Postgres succeeds, `503` otherwise. Use this for orchestration readiness probes. | +| `GET /admin/status` | Bearer-token-protected (`F451_ADMIN_TOKEN`, see [[configuration-reference]]). Returns in-memory counters (`webhookErrors`, `indexerErrors`, `driftErrors`) and a `since` timestamp. The counters reset on every process restart — treat this as "is something wrong right now", not as a historical audit log. | + +## Triggering a reindex + +```bash +curl -X POST https:///admin/reindex \ + -H "Authorization: Bearer $F451_ADMIN_TOKEN" \ + -H 'Content-Type: application/json' \ + -d '{}' +``` + +An empty body reindexes every configured space; `{"space":""}` +reindexes just one. The response reports success or failure per space — +`200` if at least one space succeeded, `502` only if all of them failed +(check `GET /admin/status` and the `api` logs next). + +> [!NOTE] +> If OIDC sign-in is active, `/admin/*` routes need a valid session **in +> addition to** the bearer token — a plain `curl` call without a browser +> session behind it gets a reproducible `401`. Run admin calls from a +> browser context where you are already signed in (developer tools' +> network/fetch panel with the session cookie present), not from a bare +> terminal. + +## Troubleshooting playbook + +| Symptom | Likely cause | +|---|---| +| Every API route is reachable without signing in | `F451_OIDC_ISSUER` is unset — auth is intentionally off in that state. | +| Sign-in redirects to an error page after the identity provider | `F451_OIDC_REDIRECT_URL` (or the app registration's redirect URI) doesn't match the actual callback URL exactly. | +| Every save/publish/release fails with `403` | `F451_PUBLIC_BASE_URL` doesn't match the URL users actually browse to — the CSRF origin check rejects the mismatch. See [[configuration-reference]]. | +| A webhook call fails with `401` | `F451_WEBHOOK_SECRET_FORGEJO`/`_GITHUB` doesn't match the secret configured on the repository's webhook. | +| A page edited directly in Git takes a few minutes to show up | Expected — the drift job catches up within about 5 minutes if the webhook was missed. See [[spaces-and-git-providers]]. | +| `POST /admin/reindex` / `GET /admin/status` returns `503` | `F451_ADMIN_TOKEN` is unset — both endpoints are fail-closed without it. | +| `POST /admin/reindex` returns `401` from a plain terminal call | Missing session — see the note above. | +| The rate limits seem to apply to "everyone at once" instead of per person | `F451_TRUST_PROXY` is unset or wrong for the real proxy chain — see [[configuration-reference]]. | +| The same person appears as two different users | They signed in through two different methods (e.g. OIDC once, GitHub once) — see [[github-sign-in]]. Not a bug, a consequence of having no shared identity across methods. | +| A user reports "I don't see any spaces" | Their account is signed in but has no linked Forgejo/GitHub account yet, or that account lacks repository access — see [[sign-in-options]]. | + +## Running a second instance on the same host + +Both the Forgejo stack and the application stack expose their ports and +cookie naming as variables, so a second instance (for example a demo or a +staging copy) can run alongside the first without conflicting: + +| Variable | What it separates | +|---|---| +| `F451_COOKIE_PREFIX` | Session cookie names — without a distinct value, two instances sharing a host would overwrite each other's cookies in the browser. | +| `F451_WEB_PORT` | Host port for the web UI. | +| `F451_DRAWIO_PORT` | Host port for the diagram editor. | +| `F451_FORGEJO_HTTP_PORT` / `F451_FORGEJO_SSH_PORT` | Host ports for the second Forgejo instance. | +| `F451_FORGEJO_ROOT_URL` | The second Forgejo instance's own public URL. | + +Give the second instance's Git stack its own Compose project name +(`docker compose -p ...`) so its containers and volumes don't +collide with the first instance's. diff --git a/sign-in-options/index.md b/sign-in-options/index.md new file mode 100644 index 0000000..afde170 --- /dev/null +++ b/sign-in-options/index.md @@ -0,0 +1,60 @@ +--- +id: sign-in-options +title: Sign-in options +description: The identity providers f451 supports, how they compare, and why there are no local accounts. +tags: [admin, auth] +lang: en +--- + +# Sign-in options + +f451 never stores a password. Every sign-in goes through an external +identity provider, and every permission check after that goes through the +Git provider behind the space a person is looking at — see *Principles* in the Developer Guide +in the developer guide for why that split exists. This page is about the +first half: who is allowed to sign in at all. + +## Why no local accounts + +A local account system would need its own password reset, its own +lockout policy, its own audit trail — none of which f451 could do better +than the identity provider your organisation already runs and already +trusts for offboarding. Instead, f451 delegates entirely: one configured +OIDC issuer, optionally GitHub as a second method, and permissions that +come from the linked Forgejo or GitHub account rather than from anything +f451 tracks itself. + +## Comparison + +| Provider | Use case | What f451 needs | Notes | +|---|---|---|---| +| **Microsoft Entra ID** | Production, organisation-managed identities | `F451_OIDC_ISSUER`/`_CLIENT_ID`/`_CLIENT_SECRET`/`_REDIRECT_URL`, `F451_TOKEN_KEY` | See [[entra-id]]. Sign-in only — reading/writing still needs a separately connected Forgejo or GitHub account, unless Forgejo itself also authenticates against Entra (see next row). | +| **Forgejo (bundled)** | Local development, or production where Forgejo is the single identity source | Same OIDC variables, pointed at Forgejo's built-in OIDC provider | See [[forgejo-identity-provider]]. Can also make the *first* sign-in link the Forgejo account automatically. | +| **Other OIDC provider** (Keycloak, Authentik, Zitadel, Okta, Google, …) | Any organisation already standardised on a different IdP | Same OIDC variables, pointed at that provider's issuer | Configuration only — f451 speaks standard OpenID Connect, nothing provider-specific. | +| **GitHub** | Teams whose spaces already live on GitHub | `F451_GITHUB_LOGIN=1`, `F451_GITHUB_OAUTH_CLIENT_ID`/`_SECRET` | See [[github-sign-in]]. Works with or without an OIDC provider configured; signing in with GitHub also links the GitHub account in the same step. | + +Only one OIDC issuer can be configured at a time — f451 does not offer a +picker between several OIDC providers. GitHub sign-in is independent of +that and can be enabled alongside it, or on its own. + +## What sign-in does and does not unlock + +Signing in only authenticates *who someone is*. It does not, by itself, +grant access to any space's content: + +> [!IMPORTANT] +> **Reading and writing follow the linked Git account, not the sign-in +> method.** A person who signs in but never connects a Forgejo or GitHub +> account sees no spaces at all — not because f451 hides them, but because +> there is no permission to check against. + +See [[spaces-and-git-providers]] for how a space is tied to a repository, +and the User Guide's *Getting started* page for what connecting an account +looks like for the person doing it. + +## One person, several identities + +Because each sign-in method produces its own identity, a person who signs +in once through Entra and once through GitHub becomes **two** separate +f451 users, each with its own set of connected Git accounts — see +[[github-sign-in]] for the practical consequence of that. diff --git a/spaces-and-git-providers/index.md b/spaces-and-git-providers/index.md new file mode 100644 index 0000000..71cdeab --- /dev/null +++ b/spaces-and-git-providers/index.md @@ -0,0 +1,75 @@ +--- +id: spaces-and-git-providers +title: Spaces and Git providers +description: Configuring which repositories are wiki spaces, service tokens, webhooks, and how the index stays current. +tags: [admin, spaces, git] +lang: en +--- + +# Spaces and Git providers + +A space is one repository on Forgejo or GitHub, declared to f451 as one +entry in `F451_SPACES` — a JSON array on the `api` service: + +```json +[ + { + "id": "handbook", + "name": "Handbook", + "provider": "forgejo", + "owner": "docs", + "repo": "handbook", + "defaultLang": "en" + } +] +``` + +Without `F451_SPACES` set, the API starts in a minimal mode: only +`/healthz` and `/readyz` respond, and no space is visible at all. + +## Service tokens are for indexing, not for user access + +Each provider used by at least one space needs a service-account token: + +| Variable | Used for | +|---|---| +| `F451_FORGEJO_URL` | Base URL of the Forgejo instance (no `/api/v1`). Also the one source for the service-account registry, the write-permission check, and — if Forgejo doubles as identity provider — the OIDC issuer. | +| `F451_FORGEJO_TOKEN` | Service-account token Forgejo spaces are indexed with. | +| `F451_GITHUB_TOKEN` | Service-account token GitHub spaces are indexed with. | + +> [!NOTE] +> These tokens exist purely so the indexer can *read* repository content to +> build the search index and page tree. They are never used to write on a +> user's behalf — every write is committed with the writer's own linked +> provider token (see [[sign-in-options]]). A misconfigured or missing +> service token breaks indexing for that provider's spaces, not writing. + +## Webhooks keep the index current + +Configure a webhook in each space's repository pointing at f451, and set a +matching secret on the `api` service so f451 can verify it: + +| Variable | Effect | +|---|---| +| `F451_WEBHOOK_SECRET_FORGEJO` | HMAC secret for Forgejo webhooks. Must match the secret entered when creating the webhook in the Forgejo repository. | +| `F451_WEBHOOK_SECRET_GITHUB` | Same, for GitHub webhooks. | + +Without the matching secret set, every webhook signature check fails +(`401`) and pushes never trigger an incremental reindex — see +[[operations]] for how that shows up and how it self-heals. + +## The drift job is the safety net + +A background job compares each space's current `main` HEAD against the +last fully indexed commit every 5 minutes, and triggers a full reindex on +a mismatch. This means a webhook that never arrives — a firewall rule, a +wrong secret, a Git provider outage — is not a lasting problem: the index +catches up on its own within 5 minutes, no manual action required. See +[[operations]] for how to observe this happening. + +## Templates across spaces + +`F451_GLOBAL_TEMPLATES` optionally names one more repository +(`{"provider","owner","repo"}`, same shape as an `F451_SPACES` entry) that +supplies templates available to every space, in addition to a space's own +`_templates/*.md`.