[grade=B urn:ump:szxhoevzog44pvl3sz6n6qp2edv6wj6dcs3ajpi3kxbpbz7kikqa] DC-137: shipdeck engine branch for App Selector catalog installs

Opt-in (config.engine='shipdeck' + SHIPDECK_BRIDGE_URL configured + template
compatible): catalog installs go through the bridge's validated image-install
pipeline (digest-pinned OCI image, systemd release, Caddy gate, DNS, verify)
instead of Docker. Port semantics: engine is host-networked, so the app LISTEN
port (container side of the mapping, protocol suffix stripped) is gated —
never the Docker host port; no mapping falls back to defaultPort. Volumes:
absolute binds only, :ro preserved, named volumes and placeholders skipped.
Engine installs skip panel DNS + Caddy (pipeline did them), record an
engine=shipdeck manifest with the Shipdeckfile path, and removal runs
shipdeck rm via the registry. Failures return 502 with stage detail and
never fall back to Docker. Bridge unset = Docker path unchanged.

10 new tests (gating, payload mapping, route integration); full suite
138/138 suites 2933/2933 green. Judge: C -> C -> B zero-blockers.
This commit is contained in:
DashCaddy Polish Loop
2026-09-16 04:04:53 -07:00
parent 8ff618ce58
commit 4ca805795b
7 changed files with 636 additions and 11 deletions
+23 -1
View File
@@ -45,7 +45,29 @@ module.exports = function({
try {
log.info('deploy', 'Removing app', { appId, containerId, subdomain, deleteContainer: shouldDeleteContainer });
if (containerId && shouldDeleteContainer) {
// DC-137: engine-installed apps run as shipdeck services (no Docker
// container). Detect via the services registry BEFORE touching Docker.
let engineService = null;
try {
const svcList = await servicesStateManager.read();
const svc = (Array.isArray(svcList) ? svcList : []).find(s => s.id === subdomain);
if (svc && svc.deploymentManifest && svc.deploymentManifest.engine === 'shipdeck') {
engineService = svc.deploymentManifest.shipdeck && svc.deploymentManifest.shipdeck.service;
}
} catch (_) { /* registry read failure falls through to legacy path */ }
if (engineService && shouldDeleteContainer) {
// Engine path: `shipdeck rm` removes unit + releases (Caddy/DNS are
// handled below by the shared removal code, same as Docker apps).
try {
const { call } = require('../../src/shipdeck-bridge-client');
const { status, body } = await call('POST', '/api/rm', { name: engineService }, 120000);
results.container = (status === 200 && body.ok) ? 'removed (shipdeck)' : `shipdeck rm failed: ${body.error || status}`;
log.info('deploy', 'shipdeck service removal', { engineService, result: results.container });
} catch (error) {
results.container = `shipdeck bridge unreachable: ${error.message}`;
}
} else if (containerId && shouldDeleteContainer) {
try {
const container = docker.client.getContainer(containerId);
try { await container.stop(); log.info('docker', 'Container stopped', { containerId }); }