Standardize paths, add version endpoint, request timeouts, HOST env var, graceful shutdown
Cross-platform hardening — removes all hardcoded /app/ paths from route files and routes them through platform-paths.js so the app works the same way regardless of Docker layout (single-file mount vs consolidated data dir). Changes: - platform-paths.js: add generatedCertsDir, pkiDir, containerUpdatesDir, containerFrontendDir, containerAssetsDir, resolveAssetsPath() - self-updater.js: UPDATE_URL/MIRROR_URL/CHANNEL env var overrides - routes/ca.js: use platformPaths for cert paths and generated certs dir - routes/services.js: use platformPaths.pkiRootCert - routes/themes.js: derive THEMES_DIR from platformPaths.servicesFile - routes/config/assets.js + backup.js: use resolveAssetsPath() fallback - routes/services.js + src/app.js: use platformPaths.pkiRootCert - server.js: HOST env var support, parse PORT as int - src/app.js: GET /api/v1/version (public, no auth), global request timeout, disable x-powered-by, trust proxy - pylon/dashcaddy-pylon.js: PYLON_HOST env var, graceful shutdown on SIGTERM/SIGINT A fresh user can now deploy with a custom Docker layout (e.g. /opt/dc/data/ as a single volume mount) and the app finds its files automatically, no env var configuration required.
This commit is contained in:
+10
-16
@@ -12,14 +12,11 @@ module.exports = function(ctx) {
|
||||
|
||||
// Get CA certificate information
|
||||
router.get('/info', ctx.asyncHandler(async (req, res) => {
|
||||
const certInfoPath = '/app/ca/cert-info.json';
|
||||
const fallbackCertInfoPath = path.join(platformPaths.caCertDir, 'cert-info.json');
|
||||
const certInfoPath = path.join(platformPaths.caCertDir, 'cert-info.json');
|
||||
|
||||
let certInfoFile;
|
||||
if (await exists(certInfoPath)) {
|
||||
certInfoFile = certInfoPath;
|
||||
} else if (await exists(fallbackCertInfoPath)) {
|
||||
certInfoFile = fallbackCertInfoPath;
|
||||
} else {
|
||||
const { NotFoundError } = require('../errors');
|
||||
throw new NotFoundError('CA certificate information');
|
||||
@@ -46,13 +43,11 @@ module.exports = function(ctx) {
|
||||
|
||||
// Serve root CA certificate directly (works even without DashCA deployed)
|
||||
router.get('/root.crt', ctx.asyncHandler(async (req, res) => {
|
||||
const pkiCertPath = '/app/pki/root.crt';
|
||||
const hostCertPath = platformPaths.pkiRootCert;
|
||||
const dashcaCertPath = path.join(platformPaths.caCertDir, 'root.crt');
|
||||
|
||||
let certPath;
|
||||
if (await exists(pkiCertPath)) certPath = pkiCertPath;
|
||||
else if (await exists(dashcaCertPath)) certPath = dashcaCertPath;
|
||||
if (await exists(dashcaCertPath)) certPath = dashcaCertPath;
|
||||
else if (await exists(hostCertPath)) certPath = hostCertPath;
|
||||
else {
|
||||
const { NotFoundError } = require('../errors');
|
||||
@@ -72,13 +67,12 @@ module.exports = function(ctx) {
|
||||
}
|
||||
|
||||
// Load cert info to get the fingerprint
|
||||
const certInfoPath = '/app/ca/cert-info.json';
|
||||
const fallbackCertInfoPath2 = path.join(platformPaths.caCertDir, 'cert-info.json');
|
||||
const certInfoPath = path.join(platformPaths.caCertDir, 'cert-info.json');
|
||||
|
||||
let certInfoFile;
|
||||
if (await exists(certInfoPath)) certInfoFile = certInfoPath;
|
||||
else if (await exists(fallbackCertInfoPath2)) certInfoFile = fallbackCertInfoPath2;
|
||||
else {
|
||||
if (await exists(certInfoPath)) {
|
||||
certInfoFile = certInfoPath;
|
||||
} else {
|
||||
const { NotFoundError } = require('../errors');
|
||||
throw new NotFoundError('CA certificate information. Deploy DashCA first or ensure cert-info.json exists.');
|
||||
}
|
||||
@@ -100,7 +94,7 @@ module.exports = function(ctx) {
|
||||
// Look for template in multiple locations (packaged app vs dev)
|
||||
const templatePaths = [
|
||||
path.join(__dirname, '..', 'scripts', templateName),
|
||||
path.join('/app', 'scripts', templateName)
|
||||
path.join(platformPaths.caddyBase, 'scripts', templateName)
|
||||
];
|
||||
|
||||
let templateContent;
|
||||
@@ -142,8 +136,8 @@ module.exports = function(ctx) {
|
||||
return ctx.errorResponse(res, 400, `Invalid domain name. Must be a valid hostname (e.g., dns1${ctx.siteConfig.tld})`);
|
||||
}
|
||||
|
||||
const pkiPath = '/app/pki';
|
||||
const certsDir = '/app/generated-certs';
|
||||
const pkiPath = platformPaths.pkiDir;
|
||||
const certsDir = platformPaths.generatedCertsDir;
|
||||
const domainDir = path.join(certsDir, domain);
|
||||
|
||||
const intermediateCert = path.join(pkiPath, 'intermediate.crt');
|
||||
@@ -246,7 +240,7 @@ ${safeDomain.includes('.') ? `DNS.2 = *.${safeDomain}` : ''}`;
|
||||
|
||||
// List generated certificates
|
||||
router.get('/certs', ctx.asyncHandler(async (req, res) => {
|
||||
const certsDir = '/app/generated-certs';
|
||||
const certsDir = platformPaths.generatedCertsDir;
|
||||
|
||||
if (!await exists(certsDir)) {
|
||||
return res.json({ success: true, certificates: [] });
|
||||
|
||||
@@ -4,6 +4,7 @@ const path = require('path');
|
||||
const { LIMITS } = require('../../constants');
|
||||
const { exists } = require('../../fs-helpers');
|
||||
const { ValidationError } = require('../../errors');
|
||||
const platformPaths = require('../../platform-paths');
|
||||
/**
|
||||
* Config assets routes factory
|
||||
* @param {Object} deps - Explicit dependencies
|
||||
@@ -51,7 +52,7 @@ module.exports = function({ servicesStateManager: _servicesStateManager, asyncHa
|
||||
const buffer = Buffer.from(base64Data, 'base64');
|
||||
|
||||
// Determine assets path (mounted volume)
|
||||
const assetsPath = process.env.ASSETS_PATH || '/app/assets';
|
||||
const assetsPath = platformPaths.resolveAssetsPath(process.env.ASSETS_PATH);
|
||||
|
||||
// Ensure directory exists
|
||||
if (!await exists(assetsPath)) {
|
||||
@@ -96,7 +97,7 @@ module.exports = function({ servicesStateManager: _servicesStateManager, asyncHa
|
||||
const extension = matches[1] === 'svg+xml' ? 'svg' : matches[1];
|
||||
const buffer = Buffer.from(matches[2], 'base64');
|
||||
|
||||
const assetsPath = process.env.ASSETS_PATH || '/app/assets';
|
||||
const assetsPath = platformPaths.resolveAssetsPath(process.env.ASSETS_PATH);
|
||||
if (!await exists(assetsPath)) {
|
||||
await fsp.mkdir(assetsPath, { recursive: true });
|
||||
}
|
||||
@@ -170,7 +171,7 @@ module.exports = function({ servicesStateManager: _servicesStateManager, asyncHa
|
||||
// Reset all branding to defaults
|
||||
router.delete('/logo', asyncHandler(async (req, res) => {
|
||||
const config = await ctx.readConfig();
|
||||
const assetsPath = process.env.ASSETS_PATH || '/app/assets';
|
||||
const assetsPath = platformPaths.resolveAssetsPath(process.env.ASSETS_PATH);
|
||||
|
||||
// Delete all custom logo files
|
||||
const logoPaths = [config.customLogo, config.customLogoDark, config.customLogoLight].filter(Boolean);
|
||||
@@ -234,7 +235,7 @@ module.exports = function({ servicesStateManager: _servicesStateManager, asyncHa
|
||||
const base64Data = matches[2];
|
||||
const buffer = Buffer.from(base64Data, 'base64');
|
||||
|
||||
const assetsPath = process.env.ASSETS_PATH || '/app/assets';
|
||||
const assetsPath = platformPaths.resolveAssetsPath(process.env.ASSETS_PATH);
|
||||
if (!await exists(assetsPath)) {
|
||||
await fsp.mkdir(assetsPath, { recursive: true });
|
||||
}
|
||||
@@ -279,7 +280,7 @@ module.exports = function({ servicesStateManager: _servicesStateManager, asyncHa
|
||||
const config = await ctx.readConfig();
|
||||
|
||||
// Delete custom favicon files
|
||||
const assetsPath = process.env.ASSETS_PATH || '/app/assets';
|
||||
const assetsPath = platformPaths.resolveAssetsPath(process.env.ASSETS_PATH);
|
||||
const filesToDelete = ['favicon.ico', 'favicon.png'];
|
||||
for (const file of filesToDelete) {
|
||||
const filePath = `${assetsPath}/${file}`;
|
||||
|
||||
@@ -4,6 +4,7 @@ const path = require('path');
|
||||
const { CADDY } = require('../../constants');
|
||||
const { exists } = require('../../fs-helpers');
|
||||
const { ValidationError, AuthenticationError } = require('../../errors');
|
||||
const platformPaths = require('../../platform-paths');
|
||||
|
||||
/**
|
||||
* Config backup routes factory
|
||||
@@ -115,7 +116,7 @@ module.exports = function(deps) {
|
||||
|
||||
// Include custom assets (logo, favicon) as base64
|
||||
try {
|
||||
const assetsDir = process.env.ASSETS_DIR || '/app/assets';
|
||||
const assetsDir = platformPaths.resolveAssetsPath(process.env.ASSETS_DIR);
|
||||
const configData = backup.files.config?.data || {};
|
||||
const assetFiles = [configData.customLogo, configData.customFavicon]
|
||||
.filter(Boolean)
|
||||
@@ -346,7 +347,7 @@ module.exports = function(deps) {
|
||||
|
||||
// Restore custom assets from base64
|
||||
if (backup.assets && typeof backup.assets === 'object') {
|
||||
const assetsDir = process.env.ASSETS_DIR || '/app/assets';
|
||||
const assetsDir = platformPaths.resolveAssetsPath(process.env.ASSETS_DIR);
|
||||
for (const [name, b64] of Object.entries(backup.assets)) {
|
||||
try {
|
||||
const safeName = path.basename(name); // prevent path traversal
|
||||
|
||||
@@ -46,7 +46,7 @@ module.exports = function({
|
||||
dns
|
||||
}) {
|
||||
const router = express.Router();
|
||||
const CA_CERT_PATH = process.env.CA_CERT_PATH || '/app/pki/root.crt';
|
||||
const CA_CERT_PATH = process.env.CA_CERT_PATH || platformPaths.pkiRootCert;
|
||||
const PROBE_CONCURRENCY = 6;
|
||||
let probeHttpsAgent;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { success } = require('../response-helpers');
|
||||
const { ValidationError, NotFoundError } = require('../errors');
|
||||
const platformPaths = require('../platform-paths');
|
||||
|
||||
/**
|
||||
* Themes routes factory
|
||||
@@ -13,7 +14,7 @@ const { ValidationError, NotFoundError } = require('../errors');
|
||||
*/
|
||||
module.exports = function({ asyncHandler, log }) {
|
||||
const router = express.Router();
|
||||
const THEMES_DIR = process.env.THEMES_DIR || path.join(path.dirname(process.env.SERVICES_FILE || '/app/services.json'), 'themes');
|
||||
const THEMES_DIR = process.env.THEMES_DIR || path.join(path.dirname(platformPaths.servicesFile), 'themes');
|
||||
|
||||
// Ensure themes directory exists
|
||||
if (!fs.existsSync(THEMES_DIR)) {
|
||||
|
||||
Reference in New Issue
Block a user