Seperate dns from certificates

This commit is contained in:
Owen
2026-09-09 17:02:47 -04:00
parent 9524a11f25
commit a6204ae8da
3 changed files with 88 additions and 51 deletions
+18 -9
View File
@@ -18,17 +18,26 @@ import { jobScheduler } from "./scheduler";
export async function startCertificateManager() { export async function startCertificateManager() {
const acmeConfig = privateConfig.getRawPrivateConfig().acme; const acmeConfig = privateConfig.getRawPrivateConfig().acme;
if (!acmeConfig || acmeConfig.cert_mode !== "pangolin") { if (
return; acmeConfig &&
acmeConfig.cert_mode === "pangolin" &&
acmeConfig.enable_acme_client
) {
logger.info("Starting certificate management server...");
// Initialize ACME client
await acmeClientManager.initialize();
// Start certificate issuance/renewal jobs
await jobScheduler.start();
} }
logger.info("Starting certificate management server..."); if (privateConfig.getRawPrivateConfig().flags.use_pangolin_dns) {
// DNS record validation/reverification doesn't require certs, so it
// Initialize ACME client // runs whenever Pangolin is acting as the authoritative DNS server,
await acmeClientManager.initialize(); // independent of the cert manager above.
await jobScheduler.startDnsJobs();
// Start job scheduler }
await jobScheduler.start();
} }
export async function stopCertificateManager() { export async function stopCertificateManager() {
+69 -42
View File
@@ -28,8 +28,10 @@ import { domainReverifier } from "./domain-reverifier";
const RUN_EXCLUSIVE_TIMEOUT_MS = 30 * 60 * 1000; const RUN_EXCLUSIVE_TIMEOUT_MS = 30 * 60 * 1000;
export class JobScheduler { export class JobScheduler {
private intervals: NodeJS.Timeout[] = []; private certIntervals: NodeJS.Timeout[] = [];
private running = false; private dnsIntervals: NodeJS.Timeout[] = [];
private certRunning = false;
private dnsRunning = false;
// Guards against a slow batch (e.g. 10 certs whose DNS challenges take a // Guards against a slow batch (e.g. 10 certs whose DNS challenges take a
// while) still being processed when the next interval tick fires - // while) still being processed when the next interval tick fires -
@@ -59,19 +61,19 @@ export class JobScheduler {
}; };
} }
// Certificate issuance/renewal - requires an ACME client, so this is
// only started when Pangolin is actually managing certs.
async start(): Promise<void> { async start(): Promise<void> {
if (this.running) { if (this.certRunning) {
logger.warn("Scheduler is already running"); logger.warn("Certificate job scheduler is already running");
return; return;
} }
this.running = true; this.certRunning = true;
logger.info("Starting job scheduler"); logger.info("Starting certificate job scheduler");
const newCertState = { active: false }; const newCertState = { active: false };
const renewalState = { active: false }; const renewalState = { active: false };
const dnsValidationState = { active: false };
const reverifyState = { active: false };
const runNewCertCheck = this.runExclusive( const runNewCertCheck = this.runExclusive(
() => certificateService.processPendingCertificates(), () => certificateService.processPendingCertificates(),
@@ -83,16 +85,6 @@ export class JobScheduler {
renewalState, renewalState,
"processing renewal candidates" "processing renewal candidates"
); );
const runDnsValidation = this.runExclusive(
() => dnsValidator.validateAll(),
dnsValidationState,
"validating DNS records"
);
const runReverify = this.runExclusive(
() => domainReverifier.reverifyAll(),
reverifyState,
"reverifying domains"
);
// Schedule new certificate processing // Schedule new certificate processing
const newCertInterval = setInterval( const newCertInterval = setInterval(
@@ -106,10 +98,51 @@ export class JobScheduler {
config.getRawConfig().acme!.renewal_check_interval_ms config.getRawConfig().acme!.renewal_check_interval_ms
); );
this.certIntervals.push(newCertInterval, renewalInterval);
// Run initial checks
setTimeout(async () => {
try {
await runNewCertCheck();
// await runRenewalCheck();
} catch (error) {
logger.error("Error in initial certificate processing:", error);
}
}, 1000); // Wait 1 second after startup
logger.info("Certificate job scheduler started successfully");
}
// DNS record validation/reverification - doesn't touch certs at all, so
// this runs independently whenever Pangolin is acting as the
// authoritative DNS server, regardless of cert_mode.
async startDnsJobs(): Promise<void> {
if (this.dnsRunning) {
logger.warn("DNS validation job scheduler is already running");
return;
}
this.dnsRunning = true;
logger.info("Starting DNS validation job scheduler");
const dnsValidationState = { active: false };
const reverifyState = { active: false };
const runDnsValidation = this.runExclusive(
() => dnsValidator.validateAll(),
dnsValidationState,
"validating DNS records"
);
const runReverify = this.runExclusive(
() => domainReverifier.reverifyAll(),
reverifyState,
"reverifying domains"
);
// Schedule DNS validation // Schedule DNS validation
const dnsValidationInterval = setInterval( const dnsValidationInterval = setInterval(
runDnsValidation, runDnsValidation,
config.getRawConfig().acme?.dns_check_interval_ms config.getRawConfig().acme?.dns_check_interval_ms ?? 60000
); );
// Schedule periodic reverification of already-verified domains // Schedule periodic reverification of already-verified domains
@@ -119,44 +152,38 @@ export class JobScheduler {
3600000 3600000
); );
this.intervals.push( this.dnsIntervals.push(dnsValidationInterval, reverifyInterval);
newCertInterval,
renewalInterval,
dnsValidationInterval,
reverifyInterval
);
// Run initial checks // Run an initial validation pass shortly after startup
setTimeout(async () => { setTimeout(async () => {
try { try {
await runNewCertCheck();
// await runRenewalCheck();
await runDnsValidation(); await runDnsValidation();
} catch (error) { } catch (error) {
logger.error("Error in initial certificate processing:", error); logger.error("Error in initial DNS validation:", error);
} }
}, 1000); // Wait 5 seconds after startup }, 1000);
logger.info("Job scheduler started successfully"); logger.info("DNS validation job scheduler started successfully");
} }
async stop(): Promise<void> { async stop(): Promise<void> {
if (!this.running) { if (this.certRunning) {
return; logger.info("Stopping certificate job scheduler");
this.certRunning = false;
this.certIntervals.forEach((interval) => clearInterval(interval));
this.certIntervals = [];
} }
logger.info("Stopping job scheduler"); if (this.dnsRunning) {
this.running = false; logger.info("Stopping DNS validation job scheduler");
this.dnsRunning = false;
// Clear all intervals this.dnsIntervals.forEach((interval) => clearInterval(interval));
this.intervals.forEach((interval) => clearInterval(interval)); this.dnsIntervals = [];
this.intervals = []; }
logger.info("Job scheduler stopped");
} }
isRunning(): boolean { isRunning(): boolean {
return this.running; return this.certRunning || this.dnsRunning;
} }
} }
+1
View File
@@ -195,6 +195,7 @@ export const privateConfigSchema = z
.enum(["traefik", "pangolin"]) .enum(["traefik", "pangolin"])
.optional() .optional()
.default("traefik"), .default("traefik"),
enable_acme_client: z.boolean().optional().default(false),
// @deprecated Moved to the public config file // @deprecated Moved to the public config file
// (server/lib/readConfigFile.ts). Kept here only so existing private // (server/lib/readConfigFile.ts). Kept here only so existing private
// config files keep parsing; any value set here is migrated into the // config files keep parsing; any value set here is migrated into the