Add Gitea integration; fix .env never being loaded outside Docker

Second live integration: repo list with last CI run status, and re-running
failed jobs on a workflow run — matching the "dashboard + basic actions"
depth from the plan. Adapter built directly against the real Gitea 1.27
swagger spec (fetched from the user's own instance) rather than guessing at
the API shape: GET /user/repos for the repo list, GET
/repos/{owner}/{repo}/actions/runs?limit=1 for the latest run per repo (only
for repos with Actions enabled), and POST .../rerun-failed-jobs for retrying
just the failed jobs in a run. Follows the same config-in-UI +
encrypted-credential pattern as Tailscale and DNS providers.

Since Gitea collects its own base URL as a config field (unlike Tailscale,
which always talks to a fixed api.tailscale.com), generalized the
"integrations.baseUrl" bookkeeping into resolveBaseUrl() instead of the
one-fixed-URL-per-type map used previously.

Also fixed a real gap found while setting this up: server/src/env.ts reads
process.env directly, but nothing in the app ever loaded .env into
process.env for plain `node dist/index.js` / `tsx src/index.ts` runs — only
Docker's `env_file` config populated it, by injecting vars before Node even
starts. Every local (non-Docker) run silently had every setting at its
insecure default. Added server/src/loadEnv.ts (dotenv, pointed at the
repo-root .env) as the first import in both server/src/index.ts and
server/src/db/migrate.ts's standalone entrypoint.

Verified against the user's real, reachable services — not mocks:
- Authentik (auth.labsconnect.se): full OIDC login completed by the user
  through the real UI; confirmed their account landed as admin (first user).
- Gitea (gitea.labsconnect.se): the compiled adapter run directly against a
  real API token correctly listed all 11 real repos; a full HTTP-layer test
  against the live server (8 checks) additionally covered a real
  test-connection ping, credential non-leakage in list responses, and role
  gating (403) on the rerun-failed-jobs action even with a valid token
  behind it. None of the real repos have any workflow run history yet, so
  the success/failure status badge and the rerun action itself are
  implemented per the swagger spec but not yet exercised against a real run
  — worth checking once one of those repos has actual CI activity.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
bobbanandClaude Sonnet 5 committed 2026-09-14 23:55:41 +02:00
1 parent 069225c656
commit 79710aa7a5
12 files changed
+479 -6

No files matched your search

+18
View File
@@ -15,6 +15,10 @@ export const INTEGRATION_FIELDS: Partial<Record<IntegrationType, IntegrationFiel
},
{ key: "apiKey", label: "API key", secret: true, type: "password" },
],
gitea: [
{ key: "url", label: "Gitea URL", secret: false, placeholder: "https://gitea.example.lan" },
{ key: "token", label: "API token", secret: true, type: "password" },
],
};
/** Fixed base URL per integration type, stored on the row for display/reference. */
@@ -22,6 +26,20 @@ export const INTEGRATION_BASE_URLS: Partial<Record<IntegrationType, string>> = {
tailscale: "https://api.tailscale.com",
};
/**
* Resolves the value to store in the integrations.baseUrl column: types that
* collect their own URL as a config field (e.g. Gitea) use that; others fall
* back to a fixed constant (e.g. Tailscale's API is always api.tailscale.com).
*/
export function resolveBaseUrl(
type: IntegrationType,
nonSecretFields: Record<string, string | boolean | undefined>,
): string {
const url = nonSecretFields.url;
if (typeof url === "string" && url) return url;
return INTEGRATION_BASE_URLS[type] ?? "";
}
export function splitIntegrationConfig(
type: IntegrationType,
input: Record<string, string | boolean>,