Initial commit: DashCaddy v1.0
Full codebase including API server (32 modules + routes), dashboard frontend, DashCA certificate distribution, installer script, and deployment skills.
This commit is contained in:
104
dashcaddy-api/routes/apps/removal.js
Normal file
104
dashcaddy-api/routes/apps/removal.js
Normal file
@@ -0,0 +1,104 @@
|
||||
const express = require('express');
|
||||
const { exists } = require('../../fs-helpers');
|
||||
|
||||
module.exports = function(ctx, helpers) {
|
||||
const router = express.Router();
|
||||
|
||||
// Remove deployed app
|
||||
router.delete('/apps/:appId', ctx.asyncHandler(async (req, res) => {
|
||||
const { appId } = req.params;
|
||||
const { containerId, subdomain, ip, deleteContainer } = req.query;
|
||||
const shouldDeleteContainer = deleteContainer === 'true';
|
||||
const results = { container: null, dns: null, caddy: null, service: null };
|
||||
|
||||
try {
|
||||
ctx.log.info('deploy', 'Removing app', { appId, containerId, subdomain, deleteContainer: shouldDeleteContainer });
|
||||
|
||||
if (containerId && shouldDeleteContainer) {
|
||||
try {
|
||||
const container = ctx.docker.client.getContainer(containerId);
|
||||
try { await container.stop(); ctx.log.info('docker', 'Container stopped', { containerId }); }
|
||||
catch (stopError) { ctx.log.debug('docker', 'Container stop note', { containerId, note: stopError.message }); }
|
||||
await container.remove({ force: true });
|
||||
results.container = 'removed';
|
||||
ctx.log.info('docker', 'Container removed', { containerId });
|
||||
} catch (error) {
|
||||
results.container = error.message.includes('no such container') ? 'already removed' : error.message;
|
||||
}
|
||||
} else if (containerId && !shouldDeleteContainer) {
|
||||
results.container = 'kept (user choice)';
|
||||
}
|
||||
|
||||
if (shouldDeleteContainer && subdomain && ctx.dns.getToken()) {
|
||||
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'
|
||||
});
|
||||
let recordIp = ip || 'localhost';
|
||||
if (getResult.status === 'ok' && getResult.response?.records) {
|
||||
const aRecord = getResult.response.records.find(r => r.type === 'A');
|
||||
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
|
||||
});
|
||||
results.dns = dnsResult.status === 'ok' ? 'deleted' : (dnsResult.errorMessage || 'failed');
|
||||
ctx.log.info('dns', 'DNS record removal', { result: results.dns });
|
||||
} catch (error) {
|
||||
results.dns = error.message;
|
||||
}
|
||||
} else if (!shouldDeleteContainer) {
|
||||
results.dns = 'kept (user choice)';
|
||||
} else {
|
||||
results.dns = 'skipped (no subdomain or token)';
|
||||
}
|
||||
|
||||
if (shouldDeleteContainer && subdomain) {
|
||||
try {
|
||||
const domain = ctx.buildDomain(subdomain);
|
||||
let content = await ctx.caddy.read();
|
||||
const escapedDomain = domain.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const siteBlockRegex = new RegExp(`\\n?${escapedDomain}\\s*\\{[^{}]*(?:\\{[^{}]*(?:\\{[^{}]*\\}[^{}]*)*\\}[^{}]*)*\\}\\s*`, 'g');
|
||||
const originalLength = content.length;
|
||||
content = content.replace(siteBlockRegex, '\n');
|
||||
if (content.length !== originalLength) {
|
||||
content = content.replace(/\n{3,}/g, '\n\n');
|
||||
const caddyResult = await ctx.caddy.modify(() => content);
|
||||
results.caddy = caddyResult.success ? 'removed' : 'removed (reload failed)';
|
||||
} else {
|
||||
results.caddy = 'not found';
|
||||
}
|
||||
ctx.log.info('caddy', 'Caddy config removal', { result: results.caddy });
|
||||
} catch (error) {
|
||||
results.caddy = error.message;
|
||||
}
|
||||
} else if (!shouldDeleteContainer) {
|
||||
results.caddy = 'kept (user choice)';
|
||||
}
|
||||
|
||||
try {
|
||||
if (await exists(ctx.SERVICES_FILE)) {
|
||||
let removed = false;
|
||||
await ctx.servicesStateManager.update(services => {
|
||||
const initialLength = services.length;
|
||||
const filtered = services.filter(s => s.id !== subdomain && s.appTemplate !== appId);
|
||||
removed = filtered.length !== initialLength;
|
||||
return filtered;
|
||||
});
|
||||
results.service = removed ? 'removed' : 'not found';
|
||||
}
|
||||
ctx.log.info('deploy', 'Service config removal', { result: results.service });
|
||||
} catch (error) {
|
||||
results.service = error.message;
|
||||
}
|
||||
|
||||
res.json({ success: true, message: `App ${appId} removal completed`, results });
|
||||
} catch (error) {
|
||||
await ctx.logError('app-removal', error);
|
||||
ctx.errorResponse(res, 500, ctx.safeErrorMessage(error), { results });
|
||||
}
|
||||
}, 'apps-delete'));
|
||||
|
||||
return router;
|
||||
};
|
||||
Reference in New Issue
Block a user