使用 deno 每天定时重部署 Render 项目(CLI Proxy API)
CLI Proxy API 的核动力驴一天 3 个小版本,每天打开电脑都是打开 render 更新部署。
于是搓了这样一个用 Deno Deploy 的 cron 触发 Render deploy hook 的小工具
每天凌晨 4 点自动更新 render 部署的 cpa(你也可以用来更新其他 render 项目)
去这里复制一下 Deploy Hook 就好了,改一下代码里面占位的 RENDER_HOOK_URL
非常简单的小工具
/**
* Project: render-auto-deploy (sanitized, hardcoded URL version)
* Purpose: 用 Deno Deploy Cron 定时触发 Render Deploy Hook(自动 redeploy)
*
* ✅ 你需要改的地方(部署时只改这两处):
* 1) 把 RENDER_HOOK_URL 改成你自己的 Render Deploy Hook URL(包含 service id + key)
* 2) 如果想换时间,把 CRON_SCHEDULE 改成你要的 cron(注意:Deno.cron 用 UTC)
*
* ✅ 验证方法:
* - 打开 "/":看到 running + 当前 UTC 时间 + schedule
* - 打开 "/trigger":立刻触发一次 Render 部署(返回 deploy.id)
* - 去 Render Events:会出现 “Triggered via Deploy Hook”
*
* 🔥 时区提醒:
* - Deno.cron 的 cron 表达式按 UTC 解释
* - 例:UTC 20:00 = 北京时间次日 04:00
*/
// ======================【部署时改这里 1】======================
// Render Deploy Hook(占位符,换成你自己的)
// 形如:https://api.render.com/deploy/<YOUR_SERVICE_ID>?key=<YOUR_DEPLOY_KEY>
const RENDER_HOOK_URL =
"https://api.render.com/deploy/<YOUR_SERVICE_ID>?key=<YOUR_DEPLOY_KEY>";
// ======================【部署时改这里 2】======================
// Cron 表达式按 UTC 解释:
// - "0 20 * * *" => 每天 UTC 20:00(北京时间次日 04:00)
// - 测试用: "*/1 * * * *" => 每分钟触发一次(用来验证 cron 是否生效)
const CRON_SCHEDULE = "0 20 * * *";
function nowIso() {
return new Date().toISOString();
}
async function triggerRenderDeploy(source: "cron" | "http") {
const runId = crypto.randomUUID();
console.log(`[${nowIso()}] [${source}] runId=${runId} ⏰ trigger start`);
// 超时:避免网络卡住导致任务悬挂
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30_000);
try {
const resp = await fetch(RENDER_HOOK_URL, {
method: "POST", // Render deploy hook 通常 GET/POST 都可,这里用 POST
signal: controller.signal,
headers: { "user-agent": "render-auto-deploy/deno" },
});
const text = await resp.text().catch(() => "");
if (!resp.ok) {
// 抛错 => 让 cron 的 backoffSchedule 生效,自动重试
throw new Error(
`Render hook failed: ${resp.status} ${resp.statusText} body=${text.slice(0, 500)}`,
);
}
console.log(
`[${nowIso()}] [${source}] runId=${runId} ✅ trigger ok body=${text.slice(0, 500)}`,
);
return { ok: true, runId, status: resp.status, body: text };
} finally {
clearTimeout(timeout);
}
}
// ====== 定时任务(必须在模块顶层定义,Deno Deploy 才会识别为 Cron)======
Deno.cron(
"Daily Render Auto Deploy",
CRON_SCHEDULE,
// 失败重试节奏(毫秒):1s, 5s, 10s
{ backoffSchedule: [1000, 5000, 10000] },
async () => {
try {
await triggerRenderDeploy("cron");
} catch (e) {
console.error(`[${nowIso()}] [cron] ❌`, e);
throw e; // 继续抛出以触发重试
}
},
);
// ====== Web:健康检查 + 手动触发(方便部署后立即验证)======
Deno.serve(async (req) => {
const url = new URL(req.url);
// 健康检查:确认服务活着
if (url.pathname === "/") {
return new Response(
`Auto-Deploy is running.\nUTC now: ${nowIso()}\nSchedule(UTC): ${CRON_SCHEDULE}\n`,
{ headers: { "content-type": "text/plain; charset=utf-8" } },
);
}
// 手动触发:GET /trigger
// 部署完立刻访问一次,看返回里有没有 deploy.id
if (url.pathname === "/trigger") {
try {
const result = await triggerRenderDeploy("http");
return new Response(JSON.stringify(result, null, 2), {
headers: { "content-type": "application/json; charset=utf-8" },
});
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
return new Response(JSON.stringify({ ok: false, error: msg }, null, 2), {
status: 500,
headers: { "content-type": "application/json; charset=utf-8" },
});
}
}
return new Response("Not found", { status: 404 });
});
