mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
130 lines
4.2 KiB
TypeScript
130 lines
4.2 KiB
TypeScript
import { Host, ProvisionSsl } from "Common/Server/EnvironmentConfig";
|
|
import AcmeCertificate from "Common/Models/DatabaseModels/AcmeCertificate";
|
|
import AcmeCertificateService from "Common/Server/Services/AcmeCertificateService";
|
|
import BasicCron from "Common/Server/Utils/BasicCron";
|
|
import LocalFile from "Common/Server/Utils/LocalFile";
|
|
import logger from "Common/Server/Utils/Logger";
|
|
import Domain from "Common/Types/Domain";
|
|
import { EVERY_MINUTE } from "Common/Utils/CronTime";
|
|
import NginxConfigurator from "../Utils/NginxConfigurator";
|
|
|
|
const JOB_NAME: string = "CoreSSL:WritePrimaryHostCertificateToDisk";
|
|
const SERVER_CERTS_DIRECTORY: string = "/etc/nginx/certs/ServerCerts";
|
|
|
|
export default class WriteServerCertToDiskJob {
|
|
public static init(): void {
|
|
BasicCron({
|
|
jobName: JOB_NAME,
|
|
options: {
|
|
schedule: EVERY_MINUTE,
|
|
runOnStartup: true,
|
|
},
|
|
runFunction: async () => {
|
|
if (!ProvisionSsl) {
|
|
logger.debug(
|
|
`${JOB_NAME}: SSL provisioning disabled; skipping write.`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
const normalizedHost: string = Host.trim().toLowerCase();
|
|
const hostnameOnly: string = normalizedHost.split(":")[0] || "";
|
|
|
|
if (!hostnameOnly) {
|
|
logger.warn(
|
|
`${JOB_NAME}: HOST environment variable is empty; cannot write certificate.`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (!Domain.isValidDomain(hostnameOnly)) {
|
|
logger.warn(
|
|
`${JOB_NAME}: HOST "${hostnameOnly}" is not a valid domain; skipping write.`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
const certificate: AcmeCertificate | null =
|
|
await AcmeCertificateService.findOneBy({
|
|
query: {
|
|
domain: hostnameOnly,
|
|
},
|
|
select: {
|
|
certificate: true,
|
|
certificateKey: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (!certificate?.certificate || !certificate.certificateKey) {
|
|
logger.debug(
|
|
`${JOB_NAME}: certificate data not yet available for ${hostnameOnly}; will retry later.`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
await LocalFile.makeDirectory(SERVER_CERTS_DIRECTORY);
|
|
|
|
const certificatePath: string = `${SERVER_CERTS_DIRECTORY}/${hostnameOnly}.crt`;
|
|
const keyPath: string = `${SERVER_CERTS_DIRECTORY}/${hostnameOnly}.key`;
|
|
|
|
const certificatePem: string = certificate.certificate.toString();
|
|
const certificateKeyPem: string = certificate.certificateKey.toString();
|
|
|
|
const existingCertificate: string | null =
|
|
(await LocalFile.doesFileExist(certificatePath))
|
|
? await LocalFile.read(certificatePath)
|
|
: null;
|
|
const existingKey: string | null = (await LocalFile.doesFileExist(
|
|
keyPath,
|
|
))
|
|
? await LocalFile.read(keyPath)
|
|
: null;
|
|
|
|
const certificateChanged: boolean =
|
|
existingCertificate !== certificatePem ||
|
|
existingKey !== certificateKeyPem;
|
|
|
|
if (!certificateChanged) {
|
|
logger.debug(
|
|
`${JOB_NAME}: certificate for ${hostnameOnly} already up to date; no changes written.`,
|
|
);
|
|
try {
|
|
await NginxConfigurator.ensurePrimarySslConfigured({
|
|
hostname: hostnameOnly,
|
|
forceReload: false,
|
|
});
|
|
} catch (err) {
|
|
logger.error(
|
|
`${JOB_NAME}: failed to ensure nginx configuration for ${hostnameOnly} while certificate unchanged.`,
|
|
);
|
|
logger.error(err);
|
|
}
|
|
return;
|
|
}
|
|
|
|
await LocalFile.write(certificatePath, certificatePem);
|
|
await LocalFile.write(keyPath, certificateKeyPem);
|
|
|
|
logger.debug(
|
|
`${JOB_NAME}: wrote certificate for ${hostnameOnly} to disk.`,
|
|
);
|
|
|
|
try {
|
|
await NginxConfigurator.ensurePrimarySslConfigured({
|
|
hostname: hostnameOnly,
|
|
forceReload: true,
|
|
});
|
|
} catch (err) {
|
|
logger.error(
|
|
`${JOB_NAME}: failed to reload nginx after writing certificate for ${hostnameOnly}.`,
|
|
);
|
|
logger.error(err);
|
|
}
|
|
},
|
|
});
|
|
}
|
|
}
|