// 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 . package containers import ( "context" "fmt" "strings" "git.dcntrlized.org/Dcntrlized/Maestro/lib/docker" "git.dcntrlized.org/Dcntrlized/Maestro/lib/logger" libregistry "git.dcntrlized.org/Dcntrlized/Maestro/lib/registry" "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" // AuthServiceName is the shared auth service (image, container and DNS name). AuthServiceName = "dcntrlized-auth" ) // 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. On a // fresh host — when the database is not installed yet — it resolves the // database from the registry and installs it automatically before starting it. // The database is infrastructure, so this must never be invoked for the // database service itself (StartContainer guards that). func EnsureDatabaseRunning(ctx context.Context) error { return ensureInfraRunning(ctx, DatabaseServiceName, "shared database") } // EnsureAuthRunning ensures the shared auth service container is running. The // auth service is hidden infrastructure: every consumer proxies /auth/* to it // and validates action tokens with its key, so it must be up before services // that depend on it start. Like the database, it is installed automatically // from the registry when it is missing. func EnsureAuthRunning(ctx context.Context) error { return ensureInfraRunning(ctx, AuthServiceName, "shared auth service") } // ensureInfraRunning makes sure a hidden infrastructure container (shared // database, shared auth service) is running. When the service is not installed // yet it resolves the registry item and installs it first, so a fresh host // boots with its infrastructure without manual UI steps. Failures are returned // to the caller, which treats them as non-fatal warnings. func ensureInfraRunning(ctx context.Context, serviceName, label string) 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 infraRunning(ctx, dc, serviceName) { return nil } if !IsInstalled(serviceName) { logger.Info("%s %s not installed; installing from the registry...", label, serviceName) item, err := libregistry.ItemByName(ctx, serviceName) if err != nil { return fmt.Errorf("failed to resolve %s %s from the registry: %w", label, serviceName, err) } if err := InstallContainer(ctx, *item); err != nil { return fmt.Errorf("failed to install %s %s: %w", label, serviceName, err) } } logger.Info("Starting %s %s...", label, serviceName) if err := StartContainer(ctx, types.RegistryContainer{Name: serviceName}); err != nil { return fmt.Errorf("failed to start %s %s: %w", label, serviceName, err) } return nil } // infraRunning reports whether the named infrastructure container is currently up. func infraRunning(ctx context.Context, dc *docker.DockerClient, serviceName string) bool { filter := filters.NewArgs() filter.Add("label", "org.dcntrlized.service="+serviceName) containers, err := dc.ContainerList(ctx, container.ListOptions{All: true, Filters: filter}) if err != nil { logger.Warn("Failed to list %s containers: %v", serviceName, err) return false } for _, c := range containers { if c.State != "running" && c.State != "restarting" { continue } // Only the canonical container (e.g. "dcntrlized-auth") counts as the // shared infrastructure being up. Dev lookalikes (auth-debug) share the // role label but must never satisfy the check. for _, n := range c.Names { if strings.TrimPrefix(n, "/") == serviceName { return true } } } return false }