Add subdirectory routing mode for public domain deployments
Apps can now be served at domain.com/appname/ instead of requiring subdomain DNS records (appname.domain.com). Supports three subpath modes per template: native (URL base env var), strip (handle_path), and none (incompatible warning). Tested on Linux with deploy/removal lifecycle verified. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -196,6 +196,10 @@ module.exports = function(ctx, helpers) {
|
||||
if (!REGEX.SUBDOMAIN.test(config.subdomain)) {
|
||||
return ctx.errorResponse(res, 400, '[DC-301] Invalid subdomain format');
|
||||
}
|
||||
// Block reserved path names in subdirectory mode
|
||||
if (ctx.siteConfig.routingMode === 'subdirectory' && helpers.RESERVED_SUBPATHS.includes(config.subdomain)) {
|
||||
return ctx.errorResponse(res, 400, `[DC-301] "${config.subdomain}" is a reserved path and cannot be used in subdirectory mode`);
|
||||
}
|
||||
}
|
||||
if (config.port && !isValidPort(config.port)) {
|
||||
return ctx.errorResponse(res, 400, 'Invalid port number (must be 1-65535)');
|
||||
@@ -236,8 +240,11 @@ module.exports = function(ctx, helpers) {
|
||||
ctx.log.info('deploy', 'Container is healthy', { containerId });
|
||||
}
|
||||
|
||||
const isSubdirectoryMode = ctx.siteConfig.routingMode === 'subdirectory' && ctx.siteConfig.domain;
|
||||
|
||||
// DNS record creation (skip in subdirectory mode — only one domain needed)
|
||||
let dnsWarning = null;
|
||||
if (config.createDns) {
|
||||
if (config.createDns && !isSubdirectoryMode) {
|
||||
try {
|
||||
await ctx.dns.createRecord(config.subdomain, config.ip);
|
||||
ctx.log.info('deploy', 'DNS record created', { domain: ctx.buildDomain(config.subdomain), ip: config.ip });
|
||||
@@ -248,7 +255,12 @@ module.exports = function(ctx, helpers) {
|
||||
}
|
||||
}
|
||||
|
||||
const caddyOptions = { tailscaleOnly: config.tailscaleOnly || false, allowedIPs: config.allowedIPs || [] };
|
||||
// Caddy config generation
|
||||
const caddyOptions = {
|
||||
tailscaleOnly: config.tailscaleOnly || false,
|
||||
allowedIPs: config.allowedIPs || [],
|
||||
subpathSupport: template.subpathSupport || 'strip',
|
||||
};
|
||||
let caddyConfig;
|
||||
if (template.isStaticSite) {
|
||||
const sitePath = platformPaths.sitePath(config.subdomain);
|
||||
@@ -261,29 +273,40 @@ module.exports = function(ctx, helpers) {
|
||||
caddyConfig = ctx.caddy.generateConfig(config.subdomain, config.ip, config.port || template.defaultPort, caddyOptions);
|
||||
}
|
||||
|
||||
await helpers.addCaddyConfig(config.subdomain, caddyConfig);
|
||||
ctx.log.info('deploy', 'Caddy config added', { domain: ctx.buildDomain(config.subdomain), tailscaleOnly: config.tailscaleOnly || false });
|
||||
// Write Caddy config (subdirectory: inject into main block; subdomain: append as new block)
|
||||
if (isSubdirectoryMode && !template.isStaticSite) {
|
||||
await helpers.ensureMainDomainBlock();
|
||||
await helpers.addSubpathConfig(config.subdomain, caddyConfig);
|
||||
} else {
|
||||
await helpers.addCaddyConfig(config.subdomain, caddyConfig);
|
||||
}
|
||||
ctx.log.info('deploy', 'Caddy config added', { domain: ctx.buildDomain(config.subdomain), routingMode: ctx.siteConfig.routingMode, tailscaleOnly: config.tailscaleOnly || false });
|
||||
|
||||
// Build service URL based on routing mode
|
||||
const serviceUrl = ctx.buildServiceUrl(config.subdomain);
|
||||
|
||||
await ctx.addServiceToConfig({
|
||||
id: config.subdomain, name: template.name,
|
||||
logo: template.logo || `/assets/${appId}.png`,
|
||||
url: serviceUrl,
|
||||
containerId, appTemplate: appId,
|
||||
tailscaleOnly: config.tailscaleOnly || false,
|
||||
routingMode: ctx.siteConfig.routingMode,
|
||||
deployedAt: new Date().toISOString()
|
||||
});
|
||||
ctx.log.info('deploy', 'Service added to dashboard', { subdomain: config.subdomain });
|
||||
|
||||
const response = {
|
||||
success: true, containerId, usedExisting,
|
||||
url: `https://${ctx.buildDomain(config.subdomain)}`,
|
||||
url: serviceUrl,
|
||||
message: usedExisting ? `${template.name} configured using existing container!` : `${template.name} deployed successfully!`,
|
||||
setupInstructions: template.setupInstructions || []
|
||||
};
|
||||
if (dnsWarning) response.warning = dnsWarning;
|
||||
|
||||
const notificationMessage = usedExisting
|
||||
? `**${template.name}** configured using existing container.\nURL: https://${ctx.buildDomain(config.subdomain)}`
|
||||
: `**${template.name}** has been deployed successfully.\nURL: https://${ctx.buildDomain(config.subdomain)}`;
|
||||
? `**${template.name}** configured using existing container.\nURL: ${serviceUrl}`
|
||||
: `**${template.name}** has been deployed successfully.\nURL: ${serviceUrl}`;
|
||||
ctx.notification.send('deploymentSuccess', usedExisting ? 'Configuration Complete' : 'Deployment Successful', notificationMessage, 'success');
|
||||
|
||||
res.json(response);
|
||||
|
||||
Reference in New Issue
Block a user