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:
@@ -16,6 +16,7 @@ const { asyncHandler } = require('./utils/async-handler');
|
||||
|
||||
// Managers and utilities
|
||||
const StateManager = require('../state-manager');
|
||||
const platformPaths = require('../platform-paths');
|
||||
const { LicenseManager } = require('../license-manager');
|
||||
const credentialManager = require('../credential-manager');
|
||||
const authManager = require('../auth-manager');
|
||||
@@ -96,6 +97,19 @@ const { APP } = require('../constants');
|
||||
async function createApp() {
|
||||
const app = express();
|
||||
|
||||
// Global request timeout (default 5 minutes — covers slow Docker pulls)
|
||||
// Routes that need longer can override per-request with req.setTimeout()
|
||||
const REQUEST_TIMEOUT_MS = parseInt(process.env.REQUEST_TIMEOUT_MS, 10) || 5 * 60 * 1000;
|
||||
app.use((req, res, next) => {
|
||||
req.setTimeout(REQUEST_TIMEOUT_MS);
|
||||
res.setTimeout(REQUEST_TIMEOUT_MS);
|
||||
next();
|
||||
});
|
||||
// Disable x-powered-by header for security (don't advertise framework)
|
||||
app.disable('x-powered-by');
|
||||
// Trust first proxy (Caddy/nginx in front of us) so req.ip works correctly
|
||||
app.set('trust proxy', 1);
|
||||
|
||||
// Initialize logging
|
||||
const log = createLogger(config.LOG_LEVEL);
|
||||
|
||||
@@ -111,7 +125,7 @@ async function createApp() {
|
||||
licenseManager.loadSecret(config.LICENSE_SECRET_FILE);
|
||||
|
||||
// HTTPS agent for internal CA
|
||||
const CA_CERT_PATH = process.env.CA_CERT_PATH || '/app/pki/root.crt';
|
||||
const CA_CERT_PATH = process.env.CA_CERT_PATH || platformPaths.pkiRootCert;
|
||||
let httpsAgent;
|
||||
try {
|
||||
const caCert = fs.readFileSync(CA_CERT_PATH);
|
||||
@@ -380,6 +394,29 @@ async function createApp() {
|
||||
// Build versioned API router
|
||||
const apiRouter = express.Router();
|
||||
|
||||
// Version endpoint — public, no auth required
|
||||
// Reads version from package.json at startup so the response always matches the running code
|
||||
let appVersion = '0.0.0';
|
||||
let appName = 'dashcaddy-api';
|
||||
try {
|
||||
const pkg = require('../package.json');
|
||||
appVersion = pkg.version || appVersion;
|
||||
appName = pkg.name || appName;
|
||||
} catch { /* package.json unreadable — keep fallback */ }
|
||||
apiRouter.get('/version', (req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
name: appName,
|
||||
version: appVersion,
|
||||
node: process.version,
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
uptime: process.uptime(),
|
||||
instanceId: process.env.DASHCADDY_INSTANCE_ID || null
|
||||
});
|
||||
});
|
||||
log.info('app', `Version endpoint available at /api/v1/version (v${appVersion})`);
|
||||
|
||||
// Wire up notification listeners for resourceMonitor and backupManager
|
||||
if (ctx.notification && ctx.resourceMonitor) {
|
||||
ctx.resourceMonitor.on('alert', (alertData) => {
|
||||
|
||||
Reference in New Issue
Block a user