feat: provision shared database as hidden infrastructure

The shared dcntrlized-db Postgres service is now treated as Maestro
infrastructure: it is hidden from the service grid and auto-started
before any app container when already installed.

- Container model gains role/hidden/capabilities; registry items gain
  hidden; installed metadata persists manifest capabilities so stopped
  services still surface them.
- Registry marks the shared database image as hidden (OCI annotation,
  label, or name fallback).
- Compose label rewrite merges existing service labels instead of
  replacing them, so org.dcntrlized.role/hidden survive.
- New infra helpers ensure the external dcntrlized_net network and
  start the database on the prod path and before each StartContainer.
- Watcher skips manifest fetch for db-role containers and persists
  capabilities from the manifest.
- spec.yaml documents the new fields.
This commit is contained in:
2026-08-10 16:27:58 +01:00
parent d2fb10ca44
commit c1ad85b65c
10 changed files with 622 additions and 38 deletions
+40 -19
View File
@@ -89,33 +89,48 @@ func (cache *ContainerCache) WatchContainers(ctx context.Context, dc *docker.Doc
ver, _ := docker.GetContainerVersion(ctx, dc, containerID)
// Fetch Manifest (best effort; fall back to basic info if it fails)
host := containerID[:12]
port := event.Actor.Attributes["org.dcntrlized.port"]
manifest, err := docker.FetchManifestWithRetry(ctx, host, port, 20, 50*time.Millisecond)
if err != nil {
log.Printf("Error fetching manifest for container %s: %v", containerID, err)
// Infrastructure containers (e.g. the shared database) have no HTTP
// manifest endpoint; keep their role marker and skip the fetch.
role := event.Actor.Attributes["org.dcntrlized.role"]
var manifest *types.Manifest
if role != "db" {
host := containerID[:12]
port := event.Actor.Attributes["org.dcntrlized.port"]
var fetchErr error
manifest, fetchErr = docker.FetchManifestWithRetry(ctx, host, port, 20, 50*time.Millisecond)
if fetchErr != nil {
log.Printf("Error fetching manifest for container %s: %v", containerID, fetchErr)
}
}
// Save to Cache
log.Printf("Service started. Adding container %s - %s to cache", host, containerName)
log.Printf("Service started. Adding container %s - %s to cache", containerID[:12], containerName)
existing, _ := cache.services[key]
if existing.Version != "" && existing.Version != "latest" && (ver == "" || ver == "latest") {
ver = existing.Version
}
capabilities := existing.Capabilities
if manifest != nil {
capabilities = manifest.Capabilities
updateInstalledMetadataCapabilities(serviceLabel, manifest.Capabilities)
}
container := types.Container{
ID: containerID,
ServiceName: serviceLabel,
DockerName: containerName,
Image: event.Actor.Attributes["image"],
CreatedAt: fmt.Sprint(event.TimeNano),
Network: containerNetwork,
Status: "running",
Version: ver,
Title: existing.Title,
Vendor: existing.Vendor,
Author: existing.Author,
Description: existing.Description,
Source: existing.Source,
ID: containerID,
ServiceName: serviceLabel,
DockerName: containerName,
Image: event.Actor.Attributes["image"],
CreatedAt: fmt.Sprint(event.TimeNano),
Network: containerNetwork,
Status: "running",
Version: ver,
Role: role,
Hidden: existing.Hidden || event.Actor.Attributes["org.dcntrlized.hidden"] == "true",
Capabilities: capabilities,
Title: existing.Title,
Vendor: existing.Vendor,
Author: existing.Author,
Description: existing.Description,
Source: existing.Source,
}
if manifest != nil {
container.Name = manifest.Name
@@ -220,6 +235,12 @@ func (cache *ContainerCache) ReconcileContainers(ctx context.Context, dc *docker
entry.Status = normalizeDockerState(c.State)
entry.ID = c.ID
entry.DockerName = strings.TrimPrefix(c.Names[0], "/")
if role := c.Labels["org.dcntrlized.role"]; role != "" {
entry.Role = role
}
if c.Labels["org.dcntrlized.hidden"] == "true" {
entry.Hidden = true
}
cache.services[key] = entry
cache.Unlock()
}