diff --git a/docker-compose.debug.yaml b/docker-compose.debug.yaml index ed8bd56..b947040 100644 --- a/docker-compose.debug.yaml +++ b/docker-compose.debug.yaml @@ -16,6 +16,10 @@ services: environment: - DCNTRLIZED_DEV=true - XDG_DATA_HOME=/data + # Host path backing the data root, so Maestro's containerized start path + # can produce bind-mount sources the daemon resolves on the host + # namespace (identity injection for infra/peers). + - DCNTRLIZED_HOST_DATA_ROOT=${XDG_DATA_HOME:-${HOME}}/.dcntrlized - DCNTRLIZED_GITEA_URL=${DCNTRLIZED_GITEA_URL:-http://192.168.122.183:3000} - GITEA_REGISTRY_RO_TOKEN=${GITEA_REGISTRY_RO_TOKEN} - GITEA_RW_PACKAGE_TOKEN=${GITEA_RW_PACKAGE_TOKEN} diff --git a/src/lib/containers/start.go b/src/lib/containers/start.go index ff050a4..1297a47 100644 --- a/src/lib/containers/start.go +++ b/src/lib/containers/start.go @@ -18,6 +18,7 @@ package containers import ( "context" "fmt" + "os" "path/filepath" "strings" @@ -130,9 +131,9 @@ func injectServiceIdentity(ctx context.Context, project *composetypes.Project, s } mounts := []string{ - fmt.Sprintf("%s:%s/ca.crt:ro", pki.CertPath(dataRoot), identityMountDir), - fmt.Sprintf("%s:%s/tls.crt:ro", identity.CertPath, identityMountDir), - fmt.Sprintf("%s:%s/tls.key:ro", identity.KeyPath, identityMountDir), + fmt.Sprintf("%s:%s/ca.crt:ro", hostBindSource(dataRoot, pki.CertPath(dataRoot)), identityMountDir), + fmt.Sprintf("%s:%s/tls.crt:ro", hostBindSource(dataRoot, identity.CertPath), identityMountDir), + fmt.Sprintf("%s:%s/tls.key:ro", hostBindSource(dataRoot, identity.KeyPath), identityMountDir), } env := map[string]string{ "SERVICE_NAME": serviceName, @@ -161,6 +162,22 @@ func injectServiceIdentity(ctx context.Context, project *composetypes.Project, s return nil } +// hostBindSource maps a dataRoot-relative path to a bind-mount source the +// Docker daemon can resolve. Bind sources are resolved on the daemon's host +// namespace, but inside the dev container the data root lives at a +// container-internal path (/data/.dcntrlized via XDG_DATA_HOME=/data) that +// does not exist on the host — the daemon would silently create it as an +// empty directory. DCNTRLIZED_HOST_DATA_ROOT (compose-injected, e.g. +// $HOME/.dcntrlized) carries the host path backing the data root; when set, +// the container-internal prefix is swapped for it. On a host install the path +// is already host-resolvable and returned unchanged. +func hostBindSource(dataRoot, p string) string { + if host := os.Getenv("DCNTRLIZED_HOST_DATA_ROOT"); host != "" && strings.HasPrefix(p, dataRoot) { + return filepath.Join(host, strings.TrimPrefix(p, dataRoot)) + } + return p +} + // pinAuthFingerprint returns the service's pinned auth fingerprint, fetching // and persisting it when the install record does not have one yet (M2 §5). // Best-effort: when the auth service is unreachable, it returns "" and the diff --git a/src/lib/containers/start_test.go b/src/lib/containers/start_test.go index 87830f1..61c0723 100644 --- a/src/lib/containers/start_test.go +++ b/src/lib/containers/start_test.go @@ -14,3 +14,26 @@ // along with this program. If not, see . package containers + +import ( + "testing" +) + +func TestHostBindSource(t *testing.T) { + t.Setenv("DCNTRLIZED_HOST_DATA_ROOT", "") + got := hostBindSource("/data/.dcntrlized", "/data/.dcntrlized/pki/services/auth/tls.crt") + if got != "/data/.dcntrlized/pki/services/auth/tls.crt" { + t.Fatalf("without host root the path must pass through, got %q", got) + } + + t.Setenv("DCNTRLIZED_HOST_DATA_ROOT", "/home/user/.dcntrlized") + got = hostBindSource("/data/.dcntrlized", "/data/.dcntrlized/pki/services/auth/tls.crt") + if want := "/home/user/.dcntrlized/pki/services/auth/tls.crt"; got != want { + t.Fatalf("host root must replace the container-internal prefix, got %q want %q", got, want) + } + + got = hostBindSource("/data/.dcntrlized", "/data/.dcntrlized/pki/ca.crt") + if want := "/home/user/.dcntrlized/pki/ca.crt"; got != want { + t.Fatalf("ca path must be remapped too, got %q want %q", got, want) + } +}