The GitHub account is gone again, so git.zacharyzhang.com/ZacharyZhang-NY/Kigi-CLI is the canonical repo. Reversef7a4cb6: - .gitea/workflows/release.yml: restore the Gitea edition retired inf7a4cb6(draft release, per-target upload, SHA256SUMS, then publish). The Windows leg runs on the windows-amd64 label the current runner advertises. - .github/workflows/{release,warm-cache}.yml: drop them; they target hosted GitHub runners and softprops, and Gitea ignores them once .gitea exists. - kigi-env: update_base_url defaults to the Gitea Releases API again. - install.sh / install.ps1: resolve releases from git.zacharyzhang.com. - feedback, /docs, SECURITY.md, frontend worker + site: repo, issues and raw-file links point at git.zacharyzhang.com. - docs/RELEASE.md: Gitea edition of the checklist with the current runners. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
115 lines
3.5 KiB
TypeScript
115 lines
3.5 KiB
TypeScript
/**
|
|
* kigicli.dev — static site + installer proxy.
|
|
*
|
|
* Static assets are served directly by the assets binding (SPA fallback). Only
|
|
* paths that do not match an asset reach this Worker, which is where the two
|
|
* installer routes live:
|
|
*
|
|
* GET https://kigicli.dev/install.sh → install.sh from the Kigi repo
|
|
* GET https://kigicli.dev/install.ps1 → install.ps1 from the Kigi repo
|
|
*
|
|
* Keeping these behind our own domain means the published install command never
|
|
* has to change when the source host does.
|
|
*/
|
|
|
|
interface Env {
|
|
ASSETS: Fetcher;
|
|
}
|
|
|
|
/** Raw-file base for the canonical Kigi repo (Gitea, `main` branch). */
|
|
const UPSTREAM = "https://git.zacharyzhang.com/ZacharyZhang-NY/Kigi-CLI/raw/branch/main";
|
|
|
|
/** Human-facing repo URL, used in the installer-unavailable message. */
|
|
const REPO_URL = "https://git.zacharyzhang.com/ZacharyZhang-NY/Kigi-CLI";
|
|
|
|
interface Installer {
|
|
file: string;
|
|
contentType: string;
|
|
/** Emitted when upstream is unreachable — must be valid in the target shell. */
|
|
unavailable: (status: number) => string;
|
|
}
|
|
|
|
const INSTALLERS: Record<string, Installer> = {
|
|
"/install.sh": {
|
|
file: "install.sh",
|
|
contentType: "text/x-shellscript; charset=utf-8",
|
|
unavailable: (status) =>
|
|
`echo "kigicli.dev: installer unavailable (upstream ${status}). Try ${REPO_URL}" >&2; exit 1\n`,
|
|
},
|
|
"/install.ps1": {
|
|
file: "install.ps1",
|
|
contentType: "text/plain; charset=utf-8",
|
|
unavailable: (status) =>
|
|
`Write-Error "kigicli.dev: installer unavailable (upstream ${status}). Try ${REPO_URL}"; exit 1\n`,
|
|
},
|
|
};
|
|
|
|
async function serveInstaller(
|
|
request: Request,
|
|
path: string,
|
|
ctx: ExecutionContext,
|
|
): Promise<Response> {
|
|
const { file, contentType, unavailable } = INSTALLERS[path];
|
|
const cache = caches.default;
|
|
const cacheKey = new Request(`${UPSTREAM}/${file}`, { method: "GET" });
|
|
|
|
let upstream = await cache.match(cacheKey);
|
|
if (!upstream) {
|
|
upstream = await fetch(cacheKey, {
|
|
headers: { "User-Agent": "kigicli.dev-installer-proxy" },
|
|
cf: { cacheTtl: 600, cacheEverything: true },
|
|
});
|
|
if (upstream.ok) {
|
|
const cacheable = new Response(upstream.clone().body, upstream);
|
|
cacheable.headers.set("Cache-Control", "public, max-age=600");
|
|
ctx.waitUntil(cache.put(cacheKey, cacheable));
|
|
}
|
|
}
|
|
|
|
if (!upstream.ok) {
|
|
return new Response(unavailable(upstream.status), {
|
|
status: 502,
|
|
headers: { "Content-Type": contentType },
|
|
});
|
|
}
|
|
|
|
const headers = new Headers({
|
|
"Content-Type": contentType,
|
|
"Cache-Control": "public, max-age=600",
|
|
"X-Content-Type-Options": "nosniff",
|
|
});
|
|
|
|
console.log(
|
|
JSON.stringify({
|
|
event: "install_script",
|
|
script: path,
|
|
ua: request.headers.get("User-Agent") ?? "",
|
|
ref: request.headers.get("Referer") ?? "",
|
|
}),
|
|
);
|
|
|
|
return new Response(upstream.body, { status: 200, headers });
|
|
}
|
|
|
|
export default {
|
|
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
|
const url = new URL(request.url);
|
|
|
|
if (url.pathname in INSTALLERS) {
|
|
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
return new Response("Method Not Allowed", {
|
|
status: 405,
|
|
headers: { Allow: "GET, HEAD" },
|
|
});
|
|
}
|
|
return serveInstaller(request, url.pathname, ctx);
|
|
}
|
|
|
|
if (url.pathname === "/health") {
|
|
return new Response("ok\n", { headers: { "Content-Type": "text/plain" } });
|
|
}
|
|
|
|
return env.ASSETS.fetch(request);
|
|
},
|
|
} satisfies ExportedHandler<Env>;
|