refactor: replace CADDY_MANAGER with DEPLOY_ENV for instance-role awareness

DEPLOY_ENV=production now marks the primary instance globally - used for
Caddy ownership, the Dev/Prod header badge, and Caddy UI gating. Removes
build-time VITE_DEPLOY_ENV/import.meta.env.DEV from the header in favour
of the runtime API response (isProduction field in /api/auth/config).
This commit is contained in:
Brückner
2026-06-10 14:43:31 +02:00
parent 49cd0ae4f6
commit 515052fbda
5 changed files with 34 additions and 30 deletions

View File

@ -15,10 +15,10 @@ const uid = (prefix: string) => `${prefix}-${Date.now()}-${Math.random().toStrin
const JWT_SECRET = process.env.JWT_SECRET || 'ghostgrid-dev-secret-change-in-production';
const JWT_EXPIRY = '24h';
// One Caddy serves the whole container; only the instance with CADDY_MANAGER=true
// owns it (pushes config, seeds routes, accepts route edits). The other instance
// must never push — POST /load replaces the entire config and would clobber it.
const IS_CADDY_MANAGER = process.env.CADDY_MANAGER === 'true';
// DEPLOY_ENV=production marks the primary instance: it owns Caddy (pushes config,
// seeds routes, accepts route edits) and shows "Production" in the UI. The dev
// instance must never push to Caddy — POST /load replaces the entire config.
const IS_PRODUCTION = process.env.DEPLOY_ENV === 'production';
interface JwtPayload {
userId: string;
@ -153,7 +153,7 @@ function importCaddyfileRoutes(userId?: string): void {
}
async function pushCaddyConfig(): Promise<void> {
if (!IS_CADDY_MANAGER) return;
if (!IS_PRODUCTION) return;
if (getSetting('caddy_enabled') !== 'true') return;
const adminUrl = getSetting('caddy_admin_url') || 'http://localhost:2019';
const body = buildCaddyfile();
@ -180,7 +180,7 @@ async function startServer() {
console.log('[Init] Default admin user created — login: admin@ghostgrid.local / admin');
}
if (IS_CADDY_MANAGER && getSetting('caddy_enabled') === 'true' && getCaddyRoutes().length === 0) {
if (IS_PRODUCTION && getSetting('caddy_enabled') === 'true' && getCaddyRoutes().length === 0) {
importCaddyfileRoutes();
}
@ -277,7 +277,7 @@ async function startServer() {
effectiveRedirectUri,
checkmkEnabled: getSetting('checkmk_enabled') === 'true',
checkmkBaseUrl: cmkApiUrl.replace(/\/api\/.*$/, ''),
caddyManaged: IS_CADDY_MANAGER,
isProduction: IS_PRODUCTION,
});
});
@ -1205,7 +1205,7 @@ async function startServer() {
app.post('/api/caddy/routes', requireAuth, async (req, res) => {
try {
if (!IS_CADDY_MANAGER) return res.status(403).json({ error: 'Caddy is managed by another instance.' });
if (!IS_PRODUCTION) return res.status(403).json({ error: 'Caddy is managed by another instance.' });
const { hostname, upstream, tls, compress, redirectPath } = req.body as {
hostname?: string; upstream?: string; tls?: boolean; compress?: boolean; redirectPath?: string;
};
@ -1225,7 +1225,7 @@ async function startServer() {
app.put('/api/caddy/routes/:id', requireAuth, async (req, res) => {
try {
if (!IS_CADDY_MANAGER) return res.status(403).json({ error: 'Caddy is managed by another instance.' });
if (!IS_PRODUCTION) return res.status(403).json({ error: 'Caddy is managed by another instance.' });
const id = Number(req.params.id);
if (!id) return res.status(400).json({ error: 'Invalid route id.' });
const { hostname, upstream, tls, compress, redirectPath } = req.body as {
@ -1245,7 +1245,7 @@ async function startServer() {
app.delete('/api/caddy/routes/:id', requireAuth, (req, res) => {
try {
if (!IS_CADDY_MANAGER) return res.status(403).json({ error: 'Caddy is managed by another instance.' });
if (!IS_PRODUCTION) return res.status(403).json({ error: 'Caddy is managed by another instance.' });
const id = Number(req.params.id);
if (!id) return res.status(400).json({ error: 'Invalid route id.' });
const existing = getCaddyRouteById(id);