Phase 1: Add ESLint/Prettier config + baseline auto-fixes

This commit is contained in:
Krystie
2026-03-22 11:00:25 +01:00
parent 41a0cdee7e
commit e2c67a8fe8
90 changed files with 4008 additions and 3066 deletions

View File

@@ -62,7 +62,7 @@ module.exports = function(ctx, helpers) {
ctx.log.info('deploy', 'DashCA: Using existing index.html');
}
ctx.log.info('deploy', 'DashCA: For full features, copy certificate files to ' + destPath);
ctx.log.info('deploy', `DashCA: For full features, copy certificate files to ${ destPath}`);
ctx.log.info('deploy', 'DashCA: Static site deployment completed successfully');
} catch (error) {
ctx.log.error('deploy', 'DashCA deployment error', { error: error.message });
@@ -121,14 +121,14 @@ module.exports = function(ctx, helpers) {
PortBindings: {},
Binds: translatedVolumes,
RestartPolicy: { Name: 'unless-stopped' },
LogConfig: DOCKER.LOG_CONFIG
LogConfig: DOCKER.LOG_CONFIG,
},
Env: Object.entries(processedTemplate.docker.environment || {}).map(([k, v]) => `${k}=${v}`),
Labels: {
'sami.managed': 'true', 'sami.app': appId,
'sami.subdomain': userConfig.subdomain,
'sami.deployed': new Date().toISOString()
}
'sami.deployed': new Date().toISOString(),
},
};
processedTemplate.docker.ports.forEach(portMapping => {
@@ -164,7 +164,7 @@ module.exports = function(ctx, helpers) {
try {
const pruneResult = await ctx.docker.client.pruneImages({ filters: { dangling: { true: true } } });
if (pruneResult.SpaceReclaimed > 0) {
ctx.log.info('docker', 'Pruned dangling images after deploy', { spaceReclaimed: Math.round(pruneResult.SpaceReclaimed / 1024 / 1024) + 'MB' });
ctx.log.info('docker', 'Pruned dangling images after deploy', { spaceReclaimed: `${Math.round(pruneResult.SpaceReclaimed / 1024 / 1024) }MB` });
}
} catch (pruneErr) {
ctx.log.debug('docker', 'Image prune after deploy failed', { error: pruneErr.message });
@@ -324,7 +324,7 @@ module.exports = function(ctx, helpers) {
tailscaleOnly: config.tailscaleOnly || false,
allowedIPs: config.allowedIPs || [],
customVolumes: config.customVolumes || undefined,
useExisting: false
useExisting: false,
},
container: template.isStaticSite ? null : {
image: processedTemplate.docker.image,
@@ -340,14 +340,14 @@ module.exports = function(ctx, helpers) {
}
return env;
})(),
capabilities: processedTemplate.docker.capabilities || undefined
capabilities: processedTemplate.docker.capabilities || undefined,
},
caddy: {
tailscaleOnly: config.tailscaleOnly || false,
allowedIPs: config.allowedIPs || [],
subpathSupport: template.subpathSupport || 'strip',
routingMode: ctx.siteConfig.routingMode
}
routingMode: ctx.siteConfig.routingMode,
},
};
await ctx.addServiceToConfig({
@@ -358,7 +358,7 @@ module.exports = function(ctx, helpers) {
tailscaleOnly: config.tailscaleOnly || false,
routingMode: ctx.siteConfig.routingMode,
deployedAt: new Date().toISOString(),
deploymentManifest
deploymentManifest,
});
ctx.log.info('deploy', 'Service added to dashboard', { subdomain: config.subdomain });
@@ -366,7 +366,7 @@ module.exports = function(ctx, helpers) {
success: true, containerId, usedExisting,
url: serviceUrl,
message: usedExisting ? `${template.name} configured using existing container!` : `${template.name} deployed successfully!`,
setupInstructions: template.setupInstructions || []
setupInstructions: template.setupInstructions || [],
};
if (dnsWarning) response.warning = dnsWarning;

View File

@@ -38,16 +38,16 @@ module.exports = function(ctx) {
const templateImage = template.docker.image.split(':')[0];
for (const container of containers) {
const containerImage = container.Image.split(':')[0];
if (containerImage === templateImage || containerImage.endsWith('/' + templateImage)) {
if (containerImage === templateImage || containerImage.endsWith(`/${ templateImage}`)) {
const ports = container.Ports.filter(p => p.PublicPort).map(p => ({
hostPort: p.PublicPort, containerPort: p.PrivatePort, protocol: p.Type
hostPort: p.PublicPort, containerPort: p.PrivatePort, protocol: p.Type,
}));
return {
id: container.Id, shortId: container.Id.slice(0, 12),
name: container.Names[0]?.replace(/^\//, '') || 'unknown',
image: container.Image, status: container.Status, state: container.State,
ports, primaryPort: ports.length > 0 ? ports[0].hostPort : null,
labels: container.Labels || {}
labels: container.Labels || {},
};
}
}
@@ -72,7 +72,7 @@ module.exports = function(ctx) {
'{{PORT}}': config.port || template.defaultPort,
'{{MEDIA_PATH}}': mediaPaths[0] || '/media',
'{{TIMEZONE}}': ctx.siteConfig.timezone || 'UTC',
'{{GENERATED_SECRET}}': crypto.randomBytes(32).toString('hex')
'{{GENERATED_SECRET}}': crypto.randomBytes(32).toString('hex'),
};
function replaceInObject(obj) {
@@ -117,7 +117,7 @@ module.exports = function(ctx) {
const basePath = `/${config.subdomain}`;
// Some apps need the full URL, not just the path
if (['GF_SERVER_ROOT_URL', 'GITEA__server__ROOT_URL'].includes(template.urlBaseEnv)) {
processed.docker.environment[template.urlBaseEnv] = ctx.buildServiceUrl(config.subdomain) + '/';
processed.docker.environment[template.urlBaseEnv] = `${ctx.buildServiceUrl(config.subdomain) }/`;
} else {
processed.docker.environment[template.urlBaseEnv] = basePath;
}
@@ -137,7 +137,7 @@ module.exports = function(ctx) {
config.mediaPath.split(',').map(p => p.trim()).filter(Boolean).forEach(p => allowedRoots.push(path.resolve(p)));
}
const isAllowed = allowedRoots.some(root =>
normalizedHost === root || normalizedHost.startsWith(root + path.sep)
normalizedHost === root || normalizedHost.startsWith(root + path.sep),
);
if (!isAllowed) {
ctx.log.warn('deploy', 'Custom volume host path rejected', { hostPath: override.hostPath, allowed: allowedRoots });
@@ -162,76 +162,76 @@ module.exports = function(ctx) {
c += ` root * ${sitePath}\n\n`;
if (tailscaleOnly) {
c += ` @blocked not remote_ip 100.64.0.0/10\n`;
c += ` respond @blocked "Access denied. Tailscale connection required." 403\n\n`;
c += ' @blocked not remote_ip 100.64.0.0/10\n';
c += ' respond @blocked "Access denied. Tailscale connection required." 403\n\n';
}
if (apiProxy) {
c += ` handle /api/* {\n`;
c += ' handle /api/* {\n';
c += ` reverse_proxy ${apiProxy}\n`;
c += ` }\n\n`;
c += ' }\n\n';
}
c += ` @crt path *.crt\n`;
c += ` handle @crt {\n`;
c += ` header Content-Type application/x-x509-ca-cert\n`;
c += ` header Content-Disposition "attachment; filename=\\"{file}\\""\n`;
c += ` header Cache-Control "public, max-age=86400"\n`;
c += ` file_server\n`;
c += ` }\n\n`;
c += ` @der path *.der\n`;
c += ` handle @der {\n`;
c += ` header Content-Type application/x-x509-ca-cert\n`;
c += ` header Content-Disposition "attachment; filename=\\"{file}\\""\n`;
c += ` header Cache-Control "public, max-age=86400"\n`;
c += ` file_server\n`;
c += ` }\n\n`;
c += ` @mobileconfig path *.mobileconfig\n`;
c += ` handle @mobileconfig {\n`;
c += ` header Content-Type application/x-apple-aspen-config\n`;
c += ` header Content-Disposition "attachment; filename=\\"{file}\\""\n`;
c += ` header Cache-Control "public, max-age=86400"\n`;
c += ` file_server\n`;
c += ` }\n\n`;
c += ` @ps1 path *.ps1\n`;
c += ` handle @ps1 {\n`;
c += ` header Content-Type text/plain\n`;
c += ` header Content-Disposition "attachment; filename=\\"{file}\\""\n`;
c += ` file_server\n`;
c += ` }\n\n`;
c += ` @sh path *.sh\n`;
c += ` handle @sh {\n`;
c += ` header Content-Type text/x-shellscript\n`;
c += ` header Content-Disposition "attachment; filename=\\"{file}\\""\n`;
c += ` file_server\n`;
c += ` }\n\n`;
c += ` # Static site with SPA fallback\n`;
c += ` handle {\n`;
c += ` @notFile not file {path}\n`;
c += ` rewrite @notFile /index.html\n`;
c += ` file_server\n`;
c += ` }\n\n`;
c += ` # No cache for HTML\n`;
c += ` @htmlfiles {\n`;
c += ` path *.html\n`;
c += ` path /\n`;
c += ` }\n`;
c += ` header @htmlfiles Cache-Control "no-store"\n`;
c += ' @crt path *.crt\n';
c += ' handle @crt {\n';
c += ' header Content-Type application/x-x509-ca-cert\n';
c += ' header Content-Disposition "attachment; filename=\\"{file}\\""\n';
c += ' header Cache-Control "public, max-age=86400"\n';
c += ' file_server\n';
c += ' }\n\n';
c += ' @der path *.der\n';
c += ' handle @der {\n';
c += ' header Content-Type application/x-x509-ca-cert\n';
c += ' header Content-Disposition "attachment; filename=\\"{file}\\""\n';
c += ' header Cache-Control "public, max-age=86400"\n';
c += ' file_server\n';
c += ' }\n\n';
c += ' @mobileconfig path *.mobileconfig\n';
c += ' handle @mobileconfig {\n';
c += ' header Content-Type application/x-apple-aspen-config\n';
c += ' header Content-Disposition "attachment; filename=\\"{file}\\""\n';
c += ' header Cache-Control "public, max-age=86400"\n';
c += ' file_server\n';
c += ' }\n\n';
c += ' @ps1 path *.ps1\n';
c += ' handle @ps1 {\n';
c += ' header Content-Type text/plain\n';
c += ' header Content-Disposition "attachment; filename=\\"{file}\\""\n';
c += ' file_server\n';
c += ' }\n\n';
c += ' @sh path *.sh\n';
c += ' handle @sh {\n';
c += ' header Content-Type text/x-shellscript\n';
c += ' header Content-Disposition "attachment; filename=\\"{file}\\""\n';
c += ' file_server\n';
c += ' }\n\n';
c += ' # Static site with SPA fallback\n';
c += ' handle {\n';
c += ' @notFile not file {path}\n';
c += ' rewrite @notFile /index.html\n';
c += ' file_server\n';
c += ' }\n\n';
c += ' # No cache for HTML\n';
c += ' @htmlfiles {\n';
c += ' path *.html\n';
c += ' path /\n';
c += ' }\n';
c += ' header @htmlfiles Cache-Control "no-store"\n';
return c;
}
// HTTPS block
let config = `${domain} {\n`;
config += ` tls internal\n\n`;
config += ' tls internal\n\n';
config += siteBlockContent();
config += `}`;
config += '}';
// HTTP companion block for devices that haven't trusted the CA yet
if (httpAccess) {
config += `\n\n# HTTP access for first-time certificate installation\n`;
config += '\n\n# HTTP access for first-time certificate installation\n';
config += `http://${domain} {\n`;
config += siteBlockContent();
config += `}`;
config += '}';
}
return config;
@@ -254,7 +254,7 @@ module.exports = function(ctx) {
} else if (healthPath && port && httpCheckFailed < 5) {
try {
const response = await ctx.fetchT(`http://localhost:${port}${healthPath}`, {
signal: AbortSignal.timeout(3000), redirect: 'manual'
signal: AbortSignal.timeout(3000), redirect: 'manual',
});
if (response.ok || (response.status >= 300 && response.status < 400)) {
ctx.log.info('docker', 'Health check passed', { containerId, status: response.status });
@@ -290,7 +290,7 @@ module.exports = function(ctx) {
await ctx.caddy.reload(existing);
return;
}
const result = await ctx.caddy.modify(c => c + `\n${config}\n`);
const result = await ctx.caddy.modify(c => `${c }\n${config}\n`);
if (!result.success) throw new Error(`[DC-303] Failed to add Caddy config for ${domain}: ${result.error}`);
await ctx.caddy.verifySite(domain);
}
@@ -405,6 +405,6 @@ module.exports = function(ctx) {
removeSubpathConfig,
ensureMainDomainBlock,
RESERVED_SUBPATHS,
generateStaticSiteConfig
generateStaticSiteConfig,
};
};

View File

@@ -26,7 +26,7 @@ module.exports = function(ctx, helpers) {
try {
const pruneResult = await ctx.docker.client.pruneImages({ filters: { dangling: { true: true } } });
if (pruneResult.SpaceReclaimed > 0) {
ctx.log.info('docker', 'Pruned dangling images after removal', { spaceReclaimed: Math.round(pruneResult.SpaceReclaimed / 1024 / 1024) + 'MB' });
ctx.log.info('docker', 'Pruned dangling images after removal', { spaceReclaimed: `${Math.round(pruneResult.SpaceReclaimed / 1024 / 1024) }MB` });
}
} catch (pruneErr) {
ctx.log.debug('docker', 'Image prune after removal failed', { error: pruneErr.message });
@@ -42,7 +42,7 @@ module.exports = function(ctx, helpers) {
try {
const domain = ctx.buildDomain(subdomain);
const getResult = await ctx.dns.call(ctx.siteConfig.dnsServerIp, '/api/zones/records/get', {
token: ctx.dns.getToken(), domain, zone: ctx.siteConfig.tld.replace(/^\./, ''), listZone: 'true'
token: ctx.dns.getToken(), domain, zone: ctx.siteConfig.tld.replace(/^\./, ''), listZone: 'true',
});
let recordIp = ip || 'localhost';
if (getResult.status === 'ok' && getResult.response?.records) {
@@ -50,7 +50,7 @@ module.exports = function(ctx, helpers) {
if (aRecord && aRecord.rData?.ipAddress) recordIp = aRecord.rData.ipAddress;
}
const dnsResult = await ctx.dns.call(ctx.siteConfig.dnsServerIp, '/api/zones/records/delete', {
token: ctx.dns.getToken(), domain, type: 'A', ipAddress: recordIp
token: ctx.dns.getToken(), domain, type: 'A', ipAddress: recordIp,
});
results.dns = dnsResult.status === 'ok' ? 'deleted' : (dnsResult.errorMessage || 'failed');
ctx.log.info('dns', 'DNS record removal', { result: results.dns });

View File

@@ -37,7 +37,7 @@ module.exports = function(ctx, helpers) {
return res.json({
success: true,
message: 'No services have deployment manifests to restore',
results: []
results: [],
});
}
@@ -51,7 +51,7 @@ module.exports = function(ctx, helpers) {
id: service.id,
name: service.name,
status: 'failed',
error: error.message
error: error.message,
});
}
}
@@ -63,7 +63,7 @@ module.exports = function(ctx, helpers) {
res.json({
success: true,
message: `Restore complete: ${succeeded} restored, ${skipped} skipped, ${failed} failed`,
results
results,
});
}, 'apps-restore-all'));
@@ -81,7 +81,7 @@ module.exports = function(ctx, helpers) {
hasManifest: !!service.deploymentManifest,
templateId: service.deploymentManifest?.templateId || service.appTemplate || null,
deployedAt: service.deployedAt || null,
containerRunning: false
containerRunning: false,
};
// Check if container is currently running
@@ -125,7 +125,7 @@ module.exports = function(ctx, helpers) {
name: service.name,
status: 'restored',
type: 'static',
message: `Static site "${service.name}" config preserved`
message: `Static site "${service.name}" config preserved`,
};
}
@@ -140,7 +140,7 @@ module.exports = function(ctx, helpers) {
id: service.id,
name: service.name,
status: 'skipped',
message: 'Container already running'
message: 'Container already running',
};
}
} catch (e) {
@@ -164,7 +164,7 @@ module.exports = function(ctx, helpers) {
id: service.id,
name: service.name,
status: 'skipped',
message: 'Container already running (found by name)'
message: 'Container already running (found by name)',
};
}
// Exists but not running — remove stale container
@@ -178,7 +178,7 @@ module.exports = function(ctx, helpers) {
id: service.id,
name: service.name,
status: 'failed',
error: 'No container configuration in manifest'
error: 'No container configuration in manifest',
};
}
@@ -189,7 +189,7 @@ module.exports = function(ctx, helpers) {
} catch (e) {
// Check if image exists locally
const images = await ctx.docker.client.listImages({
filters: { reference: [manifest.container.image] }
filters: { reference: [manifest.container.image] },
});
if (images.length === 0) {
throw new Error(`Failed to pull image ${manifest.container.image}: ${e.message}`);
@@ -206,7 +206,7 @@ module.exports = function(ctx, helpers) {
PortBindings: {},
Binds: manifest.container.volumes || [],
RestartPolicy: { Name: 'unless-stopped' },
LogConfig: DOCKER.LOG_CONFIG
LogConfig: DOCKER.LOG_CONFIG,
},
Env: Object.entries(manifest.container.environment || {}).map(([k, v]) => `${k}=${v}`),
Labels: {
@@ -214,8 +214,8 @@ module.exports = function(ctx, helpers) {
'sami.app': manifest.templateId,
'sami.subdomain': manifest.config.subdomain,
'sami.deployed': new Date().toISOString(),
'sami.restored': 'true'
}
'sami.restored': 'true',
},
};
// Set up port bindings
@@ -287,7 +287,7 @@ module.exports = function(ctx, helpers) {
status: 'restored',
type: 'container',
containerId: container.id,
message: `${service.name} restored successfully`
message: `${service.name} restored successfully`,
};
}

View File

@@ -11,7 +11,7 @@ module.exports = function(ctx, helpers) {
success: true,
templates: ctx.APP_TEMPLATES,
categories: ctx.TEMPLATE_CATEGORIES,
difficultyLevels: ctx.DIFFICULTY_LEVELS
difficultyLevels: ctx.DIFFICULTY_LEVELS,
});
}, 'apps-templates'));
@@ -71,7 +71,7 @@ module.exports = function(ctx, helpers) {
try {
const oldDomain = oldSubdomain.includes('.') ? oldSubdomain : ctx.buildDomain(oldSubdomain);
const result = await ctx.dns.call(ctx.siteConfig.dnsServerIp, '/api/zones/records/delete', {
token: ctx.dns.getToken(), domain: oldDomain, type: 'A', ipAddress: ip || 'localhost'
token: ctx.dns.getToken(), domain: oldDomain, type: 'A', ipAddress: ip || 'localhost',
});
results.oldDns = result.status === 'ok' ? 'deleted' : result.errorMessage;
ctx.log.info('dns', 'Old DNS record deleted', { domain: oldDomain });
@@ -139,7 +139,7 @@ module.exports = function(ctx, helpers) {
success: true,
message: `Subdomain updated: ${oldSubdomain} -> ${newSubdomain}`,
newUrl: `https://${ctx.buildDomain(newSubdomain)}`,
results
results,
});
}, 'update-subdomain'));