Repository: aksiksi/compose2nix (go)

Issue:
Include networks and volumes in UpheldBy
>To restart the container once the mount is back up, you can regenerate your config with -use_upheldby=true.

I'm mostly using single service docker-compose files and I get the impression that `upheldBy` only applies when `dependencies` is configured? Indeed, nothing seems to change in my generated files whether the option is enabled.

Should `upheldBy` reference the mounts themselves too, if my thinking is correct? So if the mount "restarts" in systemd terms, the service restarts.

_Originally posted by @aqxa1 in https://github.com/aksiksi/compose2nix/discussions/75#discussioncomment-12034846_

Pull request:
fix: include networks and volumes in UpheldBy
Closes #78.

Patch:
diff --git a/.github/workflows/flakehub-publish-tagged.yml b/.github/workflows/flakehub-publish-tagged.yml
new file mode 100644
index 0000000..d721453
--- /dev/null
+++ b/.github/workflows/flakehub-publish-tagged.yml
@@ -0,0 +1,29 @@
+name: "Publish tags to FlakeHub"
+on:
+  push:
+    tags:
+      - "v?[0-9]+.[0-9]+.[0-9]+*"
+  workflow_dispatch:
+    inputs:
+      tag:
+        description: "The existing tag to publish to FlakeHub"
+        type: "string"
+        required: true
+
+jobs:
+  flakehub-publish:
+    runs-on: "ubuntu-latest"
+    permissions:
+      id-token: "write"
+      contents: "read"
+    steps:
+      - uses: "actions/checkout@v4"
+        with:
+          ref: "${{ (inputs.tag != null) && format('refs/tags/{0}', inputs.tag) || '' }}"
+      - uses: "DeterminateSystems/nix-installer-action@main"
+      - uses: "DeterminateSystems/flakehub-push@main"
+        with:
+          visibility: "public"
+          name: "aksiksi/compose2nix"
+          tag: "${{ inputs.tag }}"
+          include-output-paths: true
diff --git a/compose.go b/compose.go
index 1388e73..7289752 100644
--- a/compose.go
+++ b/compose.go
@@ -351,6 +351,9 @@ func (g *Generator) buildNixContainer(service types.ServiceConfig, networkMap ma
 				// Add systemd dependencies on volume(s).
 				c.SystemdConfig.Unit.After = append(c.SystemdConfig.Unit.After, g.volumeNameToService(volume.Name))
 				c.SystemdConfig.Unit.Requires = append(c.SystemdConfig.Unit.Requires, g.volumeNameToService(volume.Name))
+				if g.UseUpheldBy {
+					c.SystemdConfig.Unit.UpheldBy = append(c.SystemdConfig.Unit.UpheldBy, g.volumeNameToService(volume.Name))
+				}
 			}
 		} else {
 			// This is a bind mount.
@@ -430,6 +433,7 @@ func (g *Generator) buildNixContainer(service types.ServiceConfig, networkMap ma
 			// project.
 			targetContainerName := strings.TrimSpace(strings.Split(networkMode, ":")[1])
 			c.ExtraOptions = append(c.ExtraOptions, "--network=container:"+targetContainerName)
+			// TODO(aksiksi): Should we even be doing this?
 			if !slices.Contains(c.DependsOn, targetContainerName) {
 				c.DependsOn = append(c.DependsOn, targetContainerName)
 			}
@@ -453,6 +457,9 @@ func (g *Generator) buildNixContainer(service types.ServiceConfig, networkMap ma
 			// Add systemd dependencies on network.
 			c.SystemdConfig.Unit.After = append(c.SystemdConfig.Unit.After, g.networkNameToService(networkName))
 			c.SystemdConfig.Unit.Requires = append(c.SystemdConfig.Unit.Requires, g.networkNameToService(networkName))
+			if g.UseUpheldBy {
+				c.SystemdConfig.Unit.UpheldBy = append(c.SystemdConfig.Unit.UpheldBy, g.networkNameToService(networkName))
+			}
 		}
 
 		// If we don't have any additional config set on this network, stop here.
@@ -704,7 +711,7 @@ func (g *Generator) buildNixContainer(service types.ServiceConfig, networkMap ma
 		//
 		// For further discussion, see: https://github.com/aksiksi/compose2nix/issues/19
 		for _, containerName := range c.DependsOn {
-			c.SystemdConfig.Unit.UpheldBy = append(c.SystemdConfig.Unit.UpheldBy, fmt.Sprintf("%s-%s.service", g.Runtime, containerName))
+			c.SystemdConfig.Unit.UpheldBy = append(c.SystemdConfig.Unit.UpheldBy, g.containerNameToService(containerName))
 		}
 	}
 
@@ -808,6 +815,10 @@ func (g *Generator) buildNixContainers(composeProject *types.Project, networkMap
 	return containers, builds, nil
 }
 
+func (g *Generator) containerNameToService(name string) string {
+	return fmt.Sprintf("%s-%s.service", g.Runtime, name)
+}
+
 func (g *Generator) networkNameToService(name string) string {
 	return fmt.Sprintf("%s-network-%s.service", g.Runtime, name)
 }


Test patch:
diff --git a/nixos-test/docker-compose.nix b/nixos-test/docker-compose.nix
index 2bf71db..1f108f8 100644
--- a/nixos-test/docker-compose.nix
+++ b/nixos-test/docker-compose.nix
@@ -32,6 +32,9 @@
     partOf = [
       "docker-compose-myproject-root.target"
     ];
+    upheldBy = [
+      "docker-network-myproject_default.service"
+    ];
     wantedBy = [
       "docker-compose-myproject-root.target"
     ];
@@ -57,6 +60,9 @@
     partOf = [
       "docker-compose-myproject-root.target"
     ];
+    upheldBy = [
+      "docker-network-myproject_default.service"
+    ];
     wantedBy = [
       "docker-compose-myproject-root.target"
     ];
@@ -109,6 +115,10 @@
     partOf = [
       "docker-compose-myproject-root.target"
     ];
+    upheldBy = [
+      "docker-network-myproject_default.service"
+      "docker-volume-storage.service"
+    ];
     wantedBy = [
       "docker-compose-myproject-root.target"
     ];
@@ -166,6 +176,9 @@
     ];
     upheldBy = [
       "docker-myproject-service-a.service"
+      "docker-network-myproject_something.service"
+      "docker-volume-myproject_books.service"
+      "docker-volume-storage.service"
     ];
     wantedBy = [
       "docker-compose-myproject-root.target"
diff --git a/nixos-test/podman-compose.nix b/nixos-test/podman-compose.nix
index 6e5c170..5dfa149 100644
--- a/nixos-test/podman-compose.nix
+++ b/nixos-test/podman-compose.nix
@@ -10,7 +10,6 @@
   };
 
   # Enable container name DNS for all Podman networks.
-  # See: https://github.com/NixOS/nixpkgs/issues/226365
   networking.firewall.interfaces = let
     matchAll = if !config.networking.nftables.enable then "podman+" else "podman*";
   in {
@@ -42,6 +41,9 @@
     partOf = [
       "podman-compose-myproject-root.target"
     ];
+    upheldBy = [
+      "podman-network-myproject_default.service"
+    ];
     wantedBy = [
       "podman-compose-myproject-root.target"
     ];
@@ -67,6 +69,9 @@
     partOf = [
       "podman-compose-myproject-root.target"
     ];
+    upheldBy = [
+      "podman-network-myproject_default.service"
+    ];
     wantedBy = [
       "podman-compose-myproject-root.target"
     ];
@@ -116,6 +121,10 @@
     partOf = [
       "podman-compose-myproject-root.target"
     ];
+    upheldBy = [
+      "podman-network-myproject_default.service"
+      "podman-volume-storage.service"
+    ];
     wantedBy = [
       "podman-compose-myproject-root.target"
     ];
@@ -173,6 +182,9 @@
     ];
     upheldBy = [
       "podman-myproject-service-a.service"
+      "podman-network-myproject_something.service"
+      "podman-volume-myproject_books.service"
+      "podman-volume-storage.service"
     ];
     wantedBy = [
       "podman-compose-myproject-root.target"
diff --git a/nixos-test/test.nix b/nixos-test/test.nix
index 8f43c1c..fb6bc67 100644
--- a/nixos-test/test.nix
+++ b/nixos-test/test.nix
@@ -81,6 +81,13 @@ in
       # for DNS settings, especially for Podman.
       m.succeed(f"{runtime} exec -it myproject-service-a wget http://no-restart")
 
+      # Verify UpheldBy behavior by stopping the volume service and ensuring
+      # that the container goes down, then comes up after the volume is started.
+      m.systemctl(f"stop {runtime}-volume-storage.service")
+      m.wait_until_fails(f"{runtime}-myproject-service-a.service")
+      m.systemctl(f"start {runtime}-volume-storage.service")
+      m.wait_for_unit(f"{runtime}-myproject-service-a.service")
+
       # Stop the root unit.
       m.systemctl(f"stop {runtime}-compose-myproject-root.target")
   '';
diff --git a/testdata/TestBuildSpec_BuildEnabled.docker.nix b/testdata/TestBuildSpec_BuildEnabled.docker.nix
index 2cf0200..b8eaf76 100644
--- a/testdata/TestBuildSpec_BuildEnabled.docker.nix
+++ b/testdata/TestBuildSpec_BuildEnabled.docker.nix
@@ -47,6 +47,8 @@
     ];
     upheldBy = [
       "docker-build-test-museum.service"
+      "docker-network-test_internal.service"
+      "docker-volume-test_custom-logs.service"
     ];
   };
 
diff --git a/testdata/TestBuildSpec_BuildEnabled.podman.nix b/testdata/TestBuildSpec_BuildEnabled.podman.nix
index 596e279..cc565c8 100644
--- a/testdata/TestBuildSpec_BuildEnabled.podman.nix
+++ b/testdata/TestBuildSpec_BuildEnabled.podman.nix
@@ -56,6 +56,8 @@
     ];
     upheldBy = [
       "podman-build-test-museum.service"
+      "podman-network-test_internal.service"
+      "podman-volume-test_custom-logs.service"
     ];
   };
 
diff --git a/testdata/TestUpheldBy.docker.nix b/testdata/TestUpheldBy.docker.nix
index 5c89d77..0867eaa 100644
--- a/testdata/TestUpheldBy.docker.nix
+++ b/testdata/TestUpheldBy.docker.nix
@@ -60,6 +60,7 @@
     ];
     upheldBy = [
       "docker-myproject-sabnzbd.service"
+      "docker-volume-myproject_books.service"
     ];
   };
   virtualisation.oci-containers.containers."myproject-sabnzbd" = {
@@ -113,6 +114,10 @@
       "docker-network-myproject_default.service"
       "docker-volume-storage.service"
     ];
+    upheldBy = [
+      "docker-network-myproject_default.service"
+      "docker-volume-storage.service"
+    ];
   };
   virtualisation.oci-containers.containers."photoprism-mariadb" = {
     image = "docker.io/library/mariadb:10.9";
@@ -158,6 +163,9 @@
     requires = [
       "docker-volume-photos.service"
     ];
+    upheldBy = [
+      "docker-volume-photos.service"
+    ];
   };
   virtualisation.oci-containers.containers."torrent-client" = {
     image = "docker.io/haugene/transmission-openvpn";
@@ -233,6 +241,8 @@
     ];
     upheldBy = [
       "docker-myproject-sabnzbd.service"
+      "docker-network-myproject_something.service"
+      "docker-volume-storage.service"
     ];
   };
   virtualisation.oci-containers.containers."traefik" = {
diff --git a/testdata/TestUpheldBy.podman.nix b/testdata/TestUpheldBy.podman.nix
index 953839c..da09d56 100644
--- a/testdata/TestUpheldBy.podman.nix
+++ b/testdata/TestUpheldBy.podman.nix
@@ -67,6 +67,7 @@
     ];
     upheldBy = [
       "podman-myproject-sabnzbd.service"
+      "podman-volume-myproject_books.service"
     ];
   };
   virtualisation.oci-containers.containers."myproject-sabnzbd" = {
@@ -117,6 +118,10 @@
       "podman-network-myproject_default.service"
       "podman-volume-storage.service"
     ];
+    upheldBy = [
+      "podman-network-myproject_default.service"
+      "podman-volume-storage.service"
+    ];
   };
   virtualisation.oci-containers.containers."photoprism-mariadb" = {
     image = "docker.io/library/mariadb:10.9";
@@ -160,6 +165,9 @@
     requires = [
       "podman-volume-photos.service"
     ];
+    upheldBy = [
+      "podman-volume-photos.service"
+    ];
   };
   virtualisation.oci-containers.containers."torrent-client" = {
     image = "docker.io/haugene/transmission-openvpn";
@@ -234,6 +242,8 @@
     ];
     upheldBy = [
       "podman-myproject-sabnzbd.service"
+      "podman-network-myproject_something.service"
+      "podman-volume-storage.service"
     ];
   };
   virtualisation.oci-containers.containers."traefik" = {