mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-16 07:40:04 +02:00
Atomically write files to avoid races with traefik
This commit is contained in:
@@ -717,10 +717,9 @@ export class TraefikConfigManager {
|
|||||||
}
|
}
|
||||||
if (shouldWrite) {
|
if (shouldWrite) {
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(
|
this.atomicWriteFileSync(
|
||||||
traefikDynamicConfigPath,
|
traefikDynamicConfigPath,
|
||||||
yaml.dump(traefikConfig, { noRefs: true }),
|
yaml.dump(traefikConfig, { noRefs: true })
|
||||||
"utf8"
|
|
||||||
);
|
);
|
||||||
logger.info("Traefik dynamic config updated");
|
logger.info("Traefik dynamic config updated");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -822,7 +821,7 @@ export class TraefikConfigManager {
|
|||||||
// Only write the config if it has changed
|
// Only write the config if it has changed
|
||||||
const newConfigYaml = yaml.dump(dynamicConfig, { noRefs: true });
|
const newConfigYaml = yaml.dump(dynamicConfig, { noRefs: true });
|
||||||
if (newConfigYaml !== originalConfigYaml) {
|
if (newConfigYaml !== originalConfigYaml) {
|
||||||
fs.writeFileSync(dynamicConfigPath, newConfigYaml, "utf8");
|
this.atomicWriteFileSync(dynamicConfigPath, newConfigYaml);
|
||||||
logger.info("Dynamic cert config updated from local certificates");
|
logger.info("Dynamic cert config updated from local certificates");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -900,26 +899,23 @@ export class TraefikConfigManager {
|
|||||||
`Processing certificate for domain: ${cert.domain}`
|
`Processing certificate for domain: ${cert.domain}`
|
||||||
);
|
);
|
||||||
|
|
||||||
fs.writeFileSync(certPath, cert.certFile, "utf8");
|
// Write atomically (temp file + rename) so Traefik's
|
||||||
fs.writeFileSync(keyPath, cert.keyFile, "utf8");
|
// file watcher never observes a partially written
|
||||||
|
// cert/key and fails with "failed to find any PEM data".
|
||||||
// Set appropriate permissions (readable by owner only for key file)
|
this.atomicWriteFileSync(certPath, cert.certFile, 0o644);
|
||||||
fs.chmodSync(certPath, 0o644);
|
this.atomicWriteFileSync(keyPath, cert.keyFile, 0o600);
|
||||||
fs.chmodSync(keyPath, 0o600);
|
|
||||||
|
|
||||||
// Write/update .last_update file with current timestamp
|
// Write/update .last_update file with current timestamp
|
||||||
fs.writeFileSync(
|
this.atomicWriteFileSync(
|
||||||
lastUpdatePath,
|
lastUpdatePath,
|
||||||
new Date().toISOString(),
|
new Date().toISOString()
|
||||||
"utf8"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check if this is a wildcard certificate and store it
|
// Check if this is a wildcard certificate and store it
|
||||||
const wildcardPath = path.join(domainDir, ".wildcard");
|
const wildcardPath = path.join(domainDir, ".wildcard");
|
||||||
fs.writeFileSync(
|
this.atomicWriteFileSync(
|
||||||
wildcardPath,
|
wildcardPath,
|
||||||
cert.wildcard ? "true" : "false",
|
cert.wildcard ? "true" : "false"
|
||||||
"utf8"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -931,10 +927,9 @@ export class TraefikConfigManager {
|
|||||||
// even if the cert content didn't change
|
// even if the cert content didn't change
|
||||||
if (cert.expiresAt) {
|
if (cert.expiresAt) {
|
||||||
const expiresAtPath = path.join(domainDir, ".expires_at");
|
const expiresAtPath = path.join(domainDir, ".expires_at");
|
||||||
fs.writeFileSync(
|
this.atomicWriteFileSync(
|
||||||
expiresAtPath,
|
expiresAtPath,
|
||||||
cert.expiresAt.toString(),
|
cert.expiresAt.toString()
|
||||||
"utf8"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -970,7 +965,7 @@ export class TraefikConfigManager {
|
|||||||
// Only write the config if it has changed
|
// Only write the config if it has changed
|
||||||
const newConfigYaml = yaml.dump(dynamicConfig, { noRefs: true });
|
const newConfigYaml = yaml.dump(dynamicConfig, { noRefs: true });
|
||||||
if (newConfigYaml !== originalConfigYaml) {
|
if (newConfigYaml !== originalConfigYaml) {
|
||||||
fs.writeFileSync(dynamicConfigPath, newConfigYaml, "utf8");
|
this.atomicWriteFileSync(dynamicConfigPath, newConfigYaml);
|
||||||
logger.info("Dynamic cert config updated");
|
logger.info("Dynamic cert config updated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1141,10 +1136,9 @@ export class TraefikConfigManager {
|
|||||||
|
|
||||||
if (configChanged) {
|
if (configChanged) {
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(
|
this.atomicWriteFileSync(
|
||||||
dynamicConfigPath,
|
dynamicConfigPath,
|
||||||
yaml.dump(dynamicConfig, { noRefs: true }),
|
yaml.dump(dynamicConfig, { noRefs: true })
|
||||||
"utf8"
|
|
||||||
);
|
);
|
||||||
logger.info("Dynamic config updated after cleanup");
|
logger.info("Dynamic config updated after cleanup");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -1171,6 +1165,36 @@ export class TraefikConfigManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a file atomically by writing to a temp file in the same
|
||||||
|
* directory and renaming it into place. This avoids Traefik (which
|
||||||
|
* watches these files/directories) picking up a partially written
|
||||||
|
* file and failing to parse it (e.g. "failed to find any PEM data").
|
||||||
|
*/
|
||||||
|
private atomicWriteFileSync(
|
||||||
|
filePath: string,
|
||||||
|
data: string,
|
||||||
|
mode?: number
|
||||||
|
): void {
|
||||||
|
const dir = path.dirname(filePath);
|
||||||
|
const tmpPath = path.join(
|
||||||
|
dir,
|
||||||
|
`.${path.basename(filePath)}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(tmpPath, data, "utf8");
|
||||||
|
if (mode !== undefined) {
|
||||||
|
fs.chmodSync(tmpPath, mode);
|
||||||
|
}
|
||||||
|
fs.renameSync(tmpPath, filePath);
|
||||||
|
} catch (error) {
|
||||||
|
try {
|
||||||
|
fs.rmSync(tmpPath, { force: true });
|
||||||
|
} catch {}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if file exists
|
* Check if file exists
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user