Docker images / prepare-release (push) Successful in 12s
Docker images / backend-test (push) Successful in 4m9s
Docker images / frontend-image (push) Successful in 4m28s
Docker images / updater-image (push) Successful in 58s
Docker images / backend-image (push) Successful in 2m49s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 11s
При старте backend накатывает схему PostgreSQL 18 и, если база пустая, один раз импортирует mikrotik.db с тома. Повторный старт не копирует данные. Бэкап в UI идёт через pg_dump. Co-authored-by: Cursor <cursoragent@cursor.com>
101 lines
3.3 KiB
TypeScript
101 lines
3.3 KiB
TypeScript
import { parseJsonArray } from "../db/json.js"
|
|
import { appendEvent } from "../modules/events/service/events-service.js"
|
|
import { issueCertificateWithCloudflareDns } from "./acme-cloudflare.js"
|
|
import {
|
|
getIssueJobRecord,
|
|
getServerRowByIdString,
|
|
updateIssueJobRecord,
|
|
} from "./certificates-service.js"
|
|
|
|
const runningJobs = new Set<string>()
|
|
|
|
export async function runCertificateIssueJob(jobId: string): Promise<void> {
|
|
if (runningJobs.has(jobId)) return
|
|
runningJobs.add(jobId)
|
|
const row = await getIssueJobRecord(jobId)
|
|
if (!row) {
|
|
runningJobs.delete(jobId)
|
|
return
|
|
}
|
|
|
|
const server = await getServerRowByIdString(row.serverId)
|
|
if (!server) {
|
|
await updateIssueJobRecord(jobId, {
|
|
status: "failed",
|
|
step: "failed",
|
|
finishedAt: new Date().toISOString(),
|
|
error: "Сервер не найден",
|
|
})
|
|
runningJobs.delete(jobId)
|
|
return
|
|
}
|
|
|
|
const domainNames = parseJsonArray(row.domainNames).map(String)
|
|
|
|
const trustStore = row.trustStore.split(",").map((s) => s.trim()).filter(Boolean)
|
|
const startedAt = new Date().toISOString()
|
|
const isScheduler = row.source === "scheduler"
|
|
await updateIssueJobRecord(jobId, { status: "running", step: "acme_order", startedAt, error: null })
|
|
|
|
if (isScheduler) {
|
|
await appendEvent({
|
|
level: "info",
|
|
eventType: "certificates.renew.started",
|
|
sourceModule: "certificates",
|
|
title: "Запущено автообновление сертификата",
|
|
message: `${row.certName} · ${domainNames.join(", ")} · ${server.name}`,
|
|
entityType: "server",
|
|
entityId: String(server.id),
|
|
})
|
|
}
|
|
|
|
try {
|
|
await issueCertificateWithCloudflareDns({
|
|
server,
|
|
certName: row.certName,
|
|
domainNames,
|
|
keyType: row.keyType === "ec256" ? "ec256" : "rsa2048",
|
|
trustStore: trustStore.length > 0 ? trustStore : ["www", "api"],
|
|
onStep: (step) => { void updateIssueJobRecord(jobId, { step }) },
|
|
})
|
|
await updateIssueJobRecord(jobId, {
|
|
status: "done",
|
|
step: "done",
|
|
finishedAt: new Date().toISOString(),
|
|
error: null,
|
|
})
|
|
await appendEvent({
|
|
level: "info",
|
|
eventType: isScheduler ? "certificates.renew.done" : "certificates.issue.done",
|
|
sourceModule: "certificates",
|
|
title: isScheduler ? "Сертификат обновлён" : "Сертификат выпущен",
|
|
message: `${row.certName} · ${domainNames.join(", ")} · ${server.name}`,
|
|
entityType: "server",
|
|
entityId: String(server.id),
|
|
})
|
|
} catch (e) {
|
|
const message = e instanceof Error ? e.message : String(e)
|
|
await updateIssueJobRecord(jobId, {
|
|
status: "failed",
|
|
step: "failed",
|
|
finishedAt: new Date().toISOString(),
|
|
error: message,
|
|
})
|
|
await appendEvent({
|
|
level: "warning",
|
|
eventType: isScheduler ? "certificates.renew.failed" : "certificates.issue.failed",
|
|
sourceModule: "certificates",
|
|
title: isScheduler ? "Ошибка автообновления сертификата" : "Ошибка выпуска сертификата",
|
|
message,
|
|
entityType: "server",
|
|
entityId: String(server.id),
|
|
})
|
|
} finally {
|
|
runningJobs.delete(jobId)
|
|
}
|
|
}
|
|
|
|
export function queueCertificateIssueJob(jobId: string): void {
|
|
queueMicrotask(() => { void runCertificateIssueJob(jobId) })
|
|
}
|