wip: app-deploy dependency tracking (incomplete — needs endpoint wiring)

Half-finished feature for declaring and resolving app dependencies
when deploying. Preserved here for later finishing.

What's done:
- app-templates.js: dependsOn declarations on 7 templates
  (sonarr, radarr, lidarr, readarr, bazarr, overseerr, tautulli).
- routes/apps/deploy.js: helper functions checkDependencies(),
  topologicalSortTemplates(), buildDefaultDepConfig().
- routes/recipes/deploy.js: wait-for-health between recipe components
  via appsHelpers.waitForHealthCheck() (verify export exists).
- status/js/app-selector.js: dependency-warning modal injected into
  app-selector flow, with a "deploy with deps" checkbox.

What's missing (blockers for merge):
- POST /api/v1/apps/check-dependencies endpoint — frontend calls it
  (app-selector.js around line 395) but the route is never registered.
  Helper functions exist; just need to expose them. Frontend currently
  404s and falls back to plain deploy (line 401), so the dep-aware
  flow is non-functional.
- Auto-deploy-with-dependencies handler in the modal — checkbox
  exists but nothing wires the "yes deploy them" choice into actually
  deploying the listed dependencies before the target app.
- No tests around topological sort behaviour (circular deps,
  diamond deps, missing deps).

Lifted out of wip/cloud-backups-and-history when the cloud-backups +
resource-history features were merged to main (commit d81d118).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-06 19:15:42 -07:00
parent d81d1183db
commit 0f2cce362f
4 changed files with 349 additions and 5 deletions

View File

@@ -70,6 +70,9 @@ module.exports = function({ docker, credentialManager: _credentialManager, servi
const deployedComponents = [];
const errors = [];
// Lazy-load apps helpers once for health waits between components
const appsHelpers = require('../apps/helpers')(ctx);
try {
for (const component of componentsToDeploy) {
try {
@@ -84,6 +87,18 @@ module.exports = function({ docker, credentialManager: _credentialManager, servi
log.info('recipe', `Component deployed: ${component.id}`, {
containerId: result.containerId?.substring(0, 12)
});
// Wait for the component to become healthy before deploying the next one
// (so dependent components — Sonarr after Prowlarr — see a working dep)
if (result.containerId && result.healthPort) {
try {
await appsHelpers.waitForHealthCheck(result.containerId, result.healthPath, result.healthPort);
log.info('recipe', `Component healthy: ${component.id}`);
} catch (healthErr) {
log.warn('recipe', `Component health wait failed: ${component.id}`, { error: healthErr.message });
// Don't abort the recipe — continue with the next component
}
}
} catch (componentError) {
log.error('recipe', `Component failed: ${component.id}`, {
error: componentError.message
@@ -349,6 +364,17 @@ module.exports = function({ docker, credentialManager: _credentialManager, servi
}
}
// Resolve health check path + port for the waiter
let healthPath = null;
let healthPort = null;
if (component.templateRef) {
const tpl = ctx.APP_TEMPLATES[component.templateRef];
healthPath = tpl?.healthCheck || null;
healthPort = port || tpl?.defaultPort || null;
} else if (dockerConfig.ports?.length > 0) {
healthPort = port || dockerConfig.ports[0].split(/[:/]/)[0];
}
return {
id: component.id,
role: component.role,
@@ -358,7 +384,9 @@ module.exports = function({ docker, credentialManager: _credentialManager, servi
internal: component.internal || false,
templateRef: component.templateRef,
logo,
url
url,
healthPath,
healthPort
};
}