import { DAEMON_RELEASE_POLICY } from "../release/daemon-policy.mjs"; import { compareStableVersions, MINIMUM_CLOUD_DAEMON_VERSION, parseStableVersion, } from "../release/daemon-version.ts"; export { MINIMUM_CLOUD_DAEMON_VERSION }; type ReleaseEnvironment = Partial<{ NEKONEST_CLOUD_DAEMON_RELEASE_VERSION: string; NEKONEST_CLOUD_DAEMON_RELEASE_BASE_URL: string; NEKONEST_CLOUD_DAEMON_WINDOWS_AMD64_SHA256: string; NEKONEST_CLOUD_DAEMON_LINUX_AMD64_SHA256: string; NEKONEST_CLOUD_DAEMON_LINUX_ARM64_SHA256: string; }>; export type DaemonReleaseAsset = { platform: "windows" | "linux"; architecture: "amd64" | "arm64"; label: string; filename: string; downloadUrl: string; sha256: string; }; export type DaemonReleaseState = | { available: false; reason: "not_configured" | "invalid_config" | "incompatible_version"; minimumVersion: string; } | { available: true; version: string; minimumVersion: string; releasePageUrl: string; checksumsUrl: string; assets: DaemonReleaseAsset[]; }; const ASSETS = DAEMON_RELEASE_POLICY.assets; function safeReleaseBaseUrl(raw: string | undefined, version: string): string { const fallback = `https://github.com/${DAEMON_RELEASE_POLICY.repository}/releases/download/v${version}`; const parsed = new URL(raw?.trim() || fallback); if ( parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.search || parsed.hash ) { throw new Error("unsafe_release_base_url"); } parsed.pathname = parsed.pathname.replace(/\/+$/, ""); return parsed.toString().replace(/\/$/, ""); } function normalizedChecksum(value: string | undefined): string { const checksum = value?.trim().toLowerCase() ?? ""; if (!/^[0-9a-f]{64}$/.test(checksum)) throw new Error("invalid_release_checksum"); return checksum; } export function parseDaemonReleaseEnvironment(config: ReleaseEnvironment): DaemonReleaseState { const version = config.NEKONEST_CLOUD_DAEMON_RELEASE_VERSION?.trim() ?? ""; if (!version) { return { available: false, reason: "not_configured", minimumVersion: MINIMUM_CLOUD_DAEMON_VERSION, }; } const parsedVersion = parseStableVersion(version); const minimumVersion = parseStableVersion(MINIMUM_CLOUD_DAEMON_VERSION); if (!parsedVersion || !minimumVersion) { return { available: false, reason: "invalid_config", minimumVersion: MINIMUM_CLOUD_DAEMON_VERSION, }; } if (compareStableVersions(parsedVersion, minimumVersion) < 0) { return { available: false, reason: "incompatible_version", minimumVersion: MINIMUM_CLOUD_DAEMON_VERSION, }; } try { const baseUrl = safeReleaseBaseUrl( config.NEKONEST_CLOUD_DAEMON_RELEASE_BASE_URL, version, ); const assets = ASSETS.map((asset) => ({ platform: asset.platform as DaemonReleaseAsset["platform"], architecture: asset.architecture as DaemonReleaseAsset["architecture"], label: asset.label, filename: asset.filename, downloadUrl: `${baseUrl}/${asset.filename}`, sha256: normalizedChecksum(config[asset.checksumEnv as keyof ReleaseEnvironment]), })); return { available: true, version, minimumVersion: MINIMUM_CLOUD_DAEMON_VERSION, releasePageUrl: `https://github.com/${DAEMON_RELEASE_POLICY.repository}/releases/tag/v${version}`, checksumsUrl: `${baseUrl}/checksums.txt`, assets, }; } catch { return { available: false, reason: "invalid_config", minimumVersion: MINIMUM_CLOUD_DAEMON_VERSION, }; } } export async function getDaemonReleaseState(): Promise { const { env } = await import("cloudflare:workers"); return parseDaemonReleaseEnvironment(env); } export function checksumVerificationCommand(asset: DaemonReleaseAsset): string { if (asset.platform === "windows") { return [ `$expected = '${asset.sha256}'`, `$actual = (Get-FileHash -LiteralPath '.\\${asset.filename}' -Algorithm SHA256).Hash.ToLowerInvariant()`, `if ($actual -ne $expected) { throw 'SHA-256 不匹配,请删除文件' }`, `Write-Host 'SHA-256 校验通过'`, ].join("\n"); } return `printf '%s %s\\n' '${asset.sha256}' '${asset.filename}' | sha256sum -c -`; }