diff --git a/.env.example b/.env.example index 136a267..785d327 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,15 @@ PORT="3000" +# Gitea base url (like https://git.pihkaal.xyz) +GITEA_URL="" +# Gitea main username (for me it's pihkaal) +GITEA_USERNAME="" +# Create an access token here /user/settings/applications +# Required permissions: +# - read:repository +# - read:user +GITEA_TOKEN="" +# Create your own in /user/settings/hooks +GITEA_WEBHOOK_SECRET="" +# File used by traefik to create the middleware +TEMPLATE_FILE="gitea-rewrites.yml.in" +OUTPUT_FILE="gitea-rewrites.yml" diff --git a/example/gitea-rewrites.yml.in b/example/gitea-rewrites.yml.in new file mode 100644 index 0000000..771e163 --- /dev/null +++ b/example/gitea-rewrites.yml.in @@ -0,0 +1,7 @@ +# example template file +http: + middlewares: + gitea-rewrites: + replacePathRegex: + regex: "^/({{REPOSITORIES}})(.*)$" + replacement: "/{{USERNAME}}/$1$2" diff --git a/src/env.ts b/src/env.ts index fe6c982..5b4c511 100644 --- a/src/env.ts +++ b/src/env.ts @@ -3,7 +3,12 @@ import { z } from "zod"; const schema = z.object({ PORT: z.coerce.number(), + GITEA_URL: z.url(), + GITEA_USERNAME: z.string(), + GITEA_TOKEN: z.string(), GITEA_WEBHOOK_SECRET: z.string(), + TEMPLATE_FILE: z.string(), + OUTPUT_FILE: z.string(), }); const result = schema.safeParse(process.env); diff --git a/src/sync.ts b/src/sync.ts new file mode 100644 index 0000000..37f5b62 --- /dev/null +++ b/src/sync.ts @@ -0,0 +1,68 @@ +import { readFile, writeFile } from "fs/promises"; +import { z } from "zod"; +import { env } from "./env"; + +const giteaRepoSchema = z.object({ + name: z.string(), + owner: z.object({ + login: z.string(), + }), +}); + +const fetchGiteaRepos = async (): Promise => { + try { + const response = await fetch( + `${env.GITEA_URL}/api/v1/users/${env.GITEA_USERNAME}/repos`, + { + headers: { + Authorization: `token ${env.GITEA_TOKEN}`, + }, + }, + ); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const json = await response.json(); + const repos = z.array(giteaRepoSchema).parse(json); + + const userRepos = repos + .filter((repo) => repo.owner.login === env.GITEA_USERNAME) + .map((repo) => repo.name); + + return userRepos; + } catch (error) { + console.error( + `ERROR: can't fetch repos from Gitea: ${error instanceof Error ? error.message : error}`, + ); + process.exit(1); + } +}; + +const createOutputFile = async (repos: string[]): Promise => { + try { + const template = await readFile(env.TEMPLATE_FILE, "utf-8"); + const reposPattern = repos.join("|"); + + let output = template.replace("{{REPOSITORIES}}", reposPattern); + output = output.replace("{{USERNAME}}", env.GITEA_USERNAME); + + await writeFile(env.OUTPUT_FILE, output); + console.log(`Successfully wrote ${env.OUTPUT_FILE}`); + } catch (error) { + if (error instanceof Error && "code" in error && error.code === "ENOENT") { + console.error(`ERROR: Template file '${env.TEMPLATE_FILE}' not found`); + } else { + console.error( + `ERROR: can't read/write file: ${error instanceof Error ? error.message : error}`, + ); + } + process.exit(1); + } +}; + +export const syncGiteaRewrites = async (): Promise => { + const repos = await fetchGiteaRepos(); + await createOutputFile(repos); +};