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
+97
View File
@@ -0,0 +1,97 @@
// Copyright (C) 2026 Dcntrlized
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package containers
import (
"context"
"fmt"
"git.dcntrlized.org/Dcntrlized/Maestro/lib/docker"
"git.dcntrlized.org/Dcntrlized/Maestro/lib/logger"
"git.dcntrlized.org/Dcntrlized/Maestro/lib/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
)
const (
// DCNTRLIZEDNetwork is the external Docker network every dcntrlized service joins.
DCNTRLIZEDNetwork = "dcntrlized_net"
// DatabaseServiceName is the shared database service (image, container and DNS name).
DatabaseServiceName = "dcntrlized-db"
)
// EnsureDCNTRLIZEDNetwork creates the external dcntrlized_net network when it is
// missing. Every service compose file declares it as external, so it must exist
// before any compose project is started.
func EnsureDCNTRLIZEDNetwork(ctx context.Context, dc *docker.DockerClient) error {
if _, err := dc.NetworkInspect(ctx, DCNTRLIZEDNetwork, network.InspectOptions{}); err == nil {
return nil
}
if _, err := dc.NetworkCreate(ctx, DCNTRLIZEDNetwork, network.CreateOptions{}); err != nil {
return fmt.Errorf("failed to create network %s: %w", DCNTRLIZEDNetwork, err)
}
logger.Info("Created external Docker network %s", DCNTRLIZEDNetwork)
return nil
}
// EnsureDatabaseRunning ensures the shared database container is running. It
// starts the database only when it is already installed; otherwise it logs and
// leaves installation to the UI. The database is infrastructure, so this must
// never be invoked for the database service itself (StartContainer guards that).
func EnsureDatabaseRunning(ctx context.Context) error {
dc, err := docker.NewDockerClient()
if err != nil {
return fmt.Errorf("failed to initialize docker client: %w", err)
}
defer dc.Close()
if err := EnsureDCNTRLIZEDNetwork(ctx, dc); err != nil {
return err
}
if databaseRunning(ctx, dc) {
return nil
}
if !IsInstalled(DatabaseServiceName) {
logger.Info("Shared database not installed; install %s from the registry to enable database-backed services", DatabaseServiceName)
return nil
}
logger.Info("Starting shared database %s...", DatabaseServiceName)
if err := StartContainer(ctx, types.RegistryContainer{Name: DatabaseServiceName}); err != nil {
return fmt.Errorf("failed to start shared database %s: %w", DatabaseServiceName, err)
}
return nil
}
// databaseRunning reports whether the shared database container is currently up.
func databaseRunning(ctx context.Context, dc *docker.DockerClient) bool {
filter := filters.NewArgs()
filter.Add("label", "org.dcntrlized.service="+DatabaseServiceName)
containers, err := dc.ContainerList(ctx, container.ListOptions{All: true, Filters: filter})
if err != nil {
logger.Warn("Failed to list database containers: %v", err)
return false
}
for _, c := range containers {
if c.State == "running" || c.State == "restarting" {
return true
}
}
return false
}