Sync CCP templates with production configs for complete instance provisioning

Closes 12 template drift gaps between the Control Panel templates and
production configs. New instances now provision with full monitoring
(alerts fire properly), correct Gitea DB type (postgres not mysql),
social sharing previews (OG meta bot routes), Excalidraw subdomain
routing, docker-socket-proxy for Homepage, and complete Grafana/
Alertmanager/Prometheus config copying.

Key changes:
- Rewrite Prometheus template: add alerting, rule_files, 5 scrape jobs
- Add cAdvisor, node-exporter, redis-exporter, gotify, docker-socket-proxy
- Fix Gitea env from mysql to postgres to match docker-compose
- Add OG bot detection + rewrite routes for campaigns/pages/gallery
- Add Excalidraw nginx server block + Pangolin draw subdomain
- Add embed port to discovery portConfig + emailTestMode to registration
- Copy alerts.yml, alertmanager.yml, Grafana dashboards to templates
- Add Listmonk proxy port and upgrade volume to API service

Bunker Admin
This commit is contained in:
2026-03-05 08:32:49 -07:00
parent da3e43fcf7
commit 5642a24c8f
15 changed files with 1981 additions and 13 deletions

View File

@@ -62,6 +62,7 @@ export const registerInstanceSchema = z.object({
admin: z.coerce.number().int().min(1).max(65535),
postgres: z.coerce.number().int().min(1).max(65535),
nginx: z.coerce.number().int().min(1).max(65535),
embed: z.coerce.number().int().min(1).max(65535).optional(),
}),
adminEmail: z.string().email().optional().default('admin@localhost'),
enableMedia: z.boolean().default(false),
@@ -75,6 +76,7 @@ export const registerInstanceSchema = z.object({
enableSms: z.boolean().default(false),
enableSocial: z.boolean().default(false),
enablePeople: z.boolean().default(false),
emailTestMode: z.boolean().default(true),
notes: z.string().optional(),
});

View File

@@ -19,7 +19,7 @@ export interface DiscoveredInstance {
domain: string;
basePath: string;
composeProject: string;
portConfig: { api: number; admin: number; postgres: number; nginx: number };
portConfig: { api: number; admin: number; postgres: number; nginx: number; embed: number };
adminEmail: string;
enableMedia: boolean;
enableChat: boolean;
@@ -78,11 +78,13 @@ async function parseCmlEnv(envPath: string): Promise<Record<string, string> | nu
}
function extractPortConfig(envVars: Record<string, string>): DiscoveredInstance['portConfig'] {
const nginx = parseInt(envVars.NGINX_HTTP_PORT || '80', 10);
return {
api: parseInt(envVars.API_PORT || '4000', 10),
admin: parseInt(envVars.ADMIN_PORT || '3000', 10),
postgres: parseInt(envVars.V2_POSTGRES_PORT || '5433', 10),
nginx: parseInt(envVars.NGINX_HTTP_PORT || '80', 10),
nginx,
embed: parseInt(envVars.NOCODB_EMBED_PORT || String(nginx + 1), 10),
};
}
@@ -385,6 +387,7 @@ export async function autoDiscoverOnStartup(): Promise<void> {
enableSms: inst.enableSms,
enableSocial: inst.enableSocial,
enablePeople: inst.enablePeople,
emailTestMode: inst.emailTestMode,
},
userId,
'auto-discovery'

View File

@@ -148,7 +148,7 @@ export function buildTemplateContext(
admin: ports.admin,
postgres: ports.postgres,
nginx: ports.nginx,
embed: ports.embed,
embed: ports.embed || (ports.nginx || 80) + 1,
},
secrets: {
postgresPassword: secrets.postgresPassword,
@@ -233,6 +233,7 @@ export async function renderAllTemplates(context: TemplateContext, outputDir: st
{ template: 'nginx/conf.d/services.conf.hbs', output: 'nginx/conf.d/services.conf' },
{ template: 'configs/pangolin/resources.yml.hbs', output: 'configs/pangolin/resources.yml' },
{ template: 'configs/prometheus/prometheus.yml.hbs', output: 'configs/prometheus/prometheus.yml' },
{ template: 'configs/grafana/datasources/datasources.yml.hbs', output: 'configs/grafana/datasources/datasources.yml' },
];
for (const { template, output } of templateFiles) {
@@ -253,8 +254,16 @@ export async function renderAllTemplates(context: TemplateContext, outputDir: st
logger.debug(`Rendered ${template}${outputPath}`);
}
// Copy static files (nginx.conf doesn't need templating)
const staticFiles = ['nginx/nginx.conf'];
// Copy static files (no templating needed)
const staticFiles = [
'nginx/nginx.conf',
'configs/prometheus/alerts.yml',
'configs/alertmanager/alertmanager.yml',
'configs/grafana/dashboards/dashboards.yml',
'configs/grafana/dashboards/application-overview.json',
'configs/grafana/dashboards/api-performance.json',
'configs/grafana/dashboards/system-health.json',
];
for (const file of staticFiles) {
const srcPath = path.join(templatesDir, file);
try {