mirror of
https://github.com/saltbo/zpan.git
synced 2026-08-29 00:01:42 +08:00
104 lines
3.3 KiB
JavaScript
104 lines
3.3 KiB
JavaScript
import { spawnSync } from 'node:child_process'
|
|
import { randomBytes } from 'node:crypto'
|
|
|
|
const INTERNAL_TOKEN_ENV = 'ZPAN_INTERNAL_API_TOKEN'
|
|
const REPORT_PATH = '/api/internal/instance-telemetry/report'
|
|
const TOP_LEVEL_ENV_ARG = '--env='
|
|
const SECRET_PROPAGATION_DELAY_MS = 5000
|
|
const REPORT_RETRY_COUNT = 12
|
|
const REPORT_RETRY_DELAY_MS = 5000
|
|
|
|
class ReportError extends Error {
|
|
constructor(status, body) {
|
|
super(`HTTP ${status}: ${body}`)
|
|
this.status = status
|
|
}
|
|
}
|
|
|
|
const token = randomBytes(32).toString('hex')
|
|
if (process.env.GITHUB_ACTIONS === 'true') {
|
|
console.log(`::add-mask::${token}`)
|
|
}
|
|
|
|
let secretSet = false
|
|
try {
|
|
const deployUrl = resolveDeployUrl()
|
|
putInternalToken(token)
|
|
secretSet = true
|
|
const reported = await reportDeployTelemetry(token, deployUrl)
|
|
if (!reported) {
|
|
console.warn('Deploy telemetry report was not delivered after retries; continuing deploy.')
|
|
}
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : String(err)
|
|
console.warn(`Deploy telemetry setup failed; continuing deploy. error=${message}`)
|
|
} finally {
|
|
if (secretSet) {
|
|
deleteInternalToken()
|
|
}
|
|
}
|
|
|
|
function putInternalToken(internalToken) {
|
|
console.log(`Setting ${INTERNAL_TOKEN_ENV}`)
|
|
const res = spawnSync('wrangler', ['secret', 'put', INTERNAL_TOKEN_ENV, TOP_LEVEL_ENV_ARG], {
|
|
input: internalToken,
|
|
encoding: 'utf8',
|
|
stdio: ['pipe', 'inherit', 'inherit'],
|
|
})
|
|
if (res.status !== 0) throw new Error(`wrangler secret put failed status=${res.status ?? 1}`)
|
|
}
|
|
|
|
function deleteInternalToken() {
|
|
console.log(`Deleting ${INTERNAL_TOKEN_ENV}`)
|
|
const res = spawnSync('wrangler', ['secret', 'delete', INTERNAL_TOKEN_ENV, TOP_LEVEL_ENV_ARG], {
|
|
input: 'y\n',
|
|
encoding: 'utf8',
|
|
stdio: ['pipe', 'inherit', 'inherit'],
|
|
})
|
|
if (res.status !== 0) throw new Error(`wrangler secret delete failed status=${res.status ?? 1}`)
|
|
}
|
|
|
|
async function reportDeployTelemetry(internalToken, deployUrl) {
|
|
const url = new URL(REPORT_PATH, deployUrl)
|
|
await sleep(SECRET_PROPAGATION_DELAY_MS)
|
|
|
|
for (let attempt = 1; attempt <= REPORT_RETRY_COUNT; attempt += 1) {
|
|
try {
|
|
const res = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
authorization: `Bearer ${internalToken}`,
|
|
},
|
|
})
|
|
if (!res.ok) throw new ReportError(res.status, await res.text())
|
|
console.log(`Reported deployed instance telemetry: ${url}`)
|
|
return true
|
|
} catch (err) {
|
|
if (err instanceof ReportError && err.status === 404) {
|
|
console.warn(`Deploy telemetry endpoint is waiting for secret propagation attempt=${attempt}`)
|
|
} else {
|
|
const code = err instanceof Error ? err.message : String(err)
|
|
console.warn(`Deploy telemetry report failed attempt=${attempt} error=${code}`)
|
|
}
|
|
if (attempt < REPORT_RETRY_COUNT) await sleep(REPORT_RETRY_DELAY_MS)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
function resolveDeployUrl() {
|
|
const value = process.argv[2]?.trim()
|
|
if (!value) {
|
|
throw new Error('Deploy telemetry URL is required. Usage: node scripts/report-deploy-telemetry.mjs https://your-zpan.example')
|
|
}
|
|
const url = new URL(value)
|
|
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
throw new Error(`Deploy telemetry URL must use http or https: ${value}`)
|
|
}
|
|
return url.origin
|
|
}
|
|
|
|
function sleep(ms) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
}
|