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

+16 -2
View File
@@ -1,11 +1,25 @@
import type { IntegrationType } from "../db/schema.js";
import type { IntegrationConfig } from "./types.js";
import { createTailscaleAdapter, type TailscaleAdapter } from "./tailscale/adapter.js";
import { createTailscaleAdapter } from "./tailscale/adapter.js";
import { createGiteaAdapter } from "./gitea/adapter.js";
export function createIntegrationAdapter(type: IntegrationType, config: IntegrationConfig): TailscaleAdapter {
export interface PingableAdapter {
ping(): Promise<{ ok: boolean; latencyMs?: number; error?: string }>;
}
/**
* Generic dispatch used only where a ping() is all that's needed (the "test
* connection" endpoint). Type-specific routes construct their own concrete
* adapter directly (see requireTailscaleAdapter / requireGiteaAdapter in
* routes/integrations.ts) so a not-yet-implemented type can never throw here
* for an existing row of some OTHER type.
*/
export function createIntegrationAdapter(type: IntegrationType, config: IntegrationConfig): PingableAdapter {
switch (type) {
case "tailscale":
return createTailscaleAdapter(config as any);
case "gitea":
return createGiteaAdapter(config as any);
default:
throw new Error(`Integration type "${type}" is not implemented yet`);
}