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:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user