From 2aa85bbd9d61e664680eb295c5a32f65d1bc0f86 Mon Sep 17 00:00:00 2001 From: Assil Ksiksi Date: Sun, 5 Nov 2023 08:41:17 -0500 Subject: [PATCH] cleanup container name logic --- cmd/nixose/main.go | 2 +- compose.go | 63 +++--- nixose.go | 28 ++- nixose_test.go | 54 +++++ templates/container.nix.tmpl | 5 +- templates/network.nix.tmpl | 7 +- testdata/TestDocker_WithProject_out.nix | 256 ++++++++++++++++++++++++ testdata/TestDocker_out.nix | 3 + testdata/TestPodman_WithProject_out.nix | 234 ++++++++++++++++++++++ testdata/TestPodman_out.nix | 3 + testdata/docker-compose.yml | 4 +- 11 files changed, 608 insertions(+), 51 deletions(-) create mode 100644 testdata/TestDocker_WithProject_out.nix create mode 100644 testdata/TestPodman_WithProject_out.nix diff --git a/cmd/nixose/main.go b/cmd/nixose/main.go index e63fc51..3e3f313 100644 --- a/cmd/nixose/main.go +++ b/cmd/nixose/main.go @@ -44,7 +44,7 @@ func main() { start := time.Now() g := nixose.Generator{ - Project: nixose.NewProject(*project, *projectSeparator), + Project: nixose.NewProjectWithSeparator(*project, *projectSeparator), Runtime: containerRuntime, Paths: paths, EnvFiles: envFiles, diff --git a/compose.go b/compose.go index b483b77..004af84 100644 --- a/compose.go +++ b/compose.go @@ -181,36 +181,37 @@ func (g *Generator) Run(ctx context.Context) (*NixContainerConfig, error) { }, nil } -func (g *Generator) postProcessContainers(containers []NixContainer) { +func (g *Generator) postProcessContainers(containers []*NixContainer) { serviceToContainer := make(map[string]*NixContainer) for _, c := range containers { - serviceToContainer[c.service.Name] = &c + serviceToContainer[c.service.Name] = c } + for _, c := range containers { if networkMode := c.service.NetworkMode; strings.HasPrefix(networkMode, "service:") { targetService := strings.Split(networkMode, ":")[1] targetContainerName := serviceToContainer[targetService].Name c.ExtraOptions = append(c.ExtraOptions, "--network=container:"+targetContainerName) } - } -} -func (g *Generator) buildNixContainer(service types.ServiceConfig) NixContainer { - dependsOn := service.GetDependencies() - if g.Project != nil { - for i := range dependsOn { - dependsOn[i] = g.Project.With(dependsOn[i]) + dependsOn := c.service.GetDependencies() + for i, service := range dependsOn { + dependsOn[i] = serviceToContainer[service].Name } + c.DependsOn = dependsOn + + // Drop the reference to the service at the end of post-processing. This allows GC to + // kick in and free the service allocation. + c.service = nil } +} +func (g *Generator) buildNixContainer(service types.ServiceConfig) *NixContainer { var name string if service.ContainerName != "" { name = service.ContainerName } else { - // TODO(aksiksi): We should try to use the same convention as Docker Compose - // when container_name is not set. - // See: https://github.com/docker/compose/issues/6316 - name = service.Name + name = g.Project.With(service.Name) } systemdConfig, err := parseRestartPolicyAndSystemdLabels(&service) @@ -219,8 +220,7 @@ func (g *Generator) buildNixContainer(service types.ServiceConfig) NixContainer panic(err) } - c := NixContainer{ - Project: g.Project, + c := &NixContainer{ Runtime: g.Runtime, Name: name, Image: service.Image, @@ -230,7 +230,6 @@ func (g *Generator) buildNixContainer(service types.ServiceConfig) NixContainer Volumes: make(map[string]string), Networks: maps.Keys(service.Networks), SystemdConfig: systemdConfig, - DependsOn: dependsOn, AutoStart: g.AutoStart, service: &service, } @@ -245,7 +244,7 @@ func (g *Generator) buildNixContainer(service types.ServiceConfig) NixContainer for _, name := range c.Networks { networkName := g.Project.With(name) c.ExtraOptions = append(c.ExtraOptions, fmt.Sprintf("--network=%s", networkName)) - // Allow other containers to use bare container name as an alias even when a project is set. + // Allow other containers to use service name as an alias when a project is set. c.ExtraOptions = append(c.ExtraOptions, fmt.Sprintf("--network-alias=%s", service.Name)) } @@ -298,45 +297,43 @@ func (g *Generator) buildNixContainer(service types.ServiceConfig) NixContainer return c } -func (g *Generator) buildNixContainers() []NixContainer { - var containers []NixContainer +func (g *Generator) buildNixContainers() []*NixContainer { + var containers []*NixContainer for _, s := range g.composeProject.Services { containers = append(containers, g.buildNixContainer(s)) } - slices.SortFunc(containers, func(c1, c2 NixContainer) int { + slices.SortFunc(containers, func(c1, c2 *NixContainer) int { return cmp.Compare(c1.Name, c2.Name) }) return containers } -func (g *Generator) buildNixNetworks(containers []NixContainer) []NixNetwork { - var networks []NixNetwork +func (g *Generator) buildNixNetworks(containers []*NixContainer) []*NixNetwork { + var networks []*NixNetwork for name, network := range g.composeProject.Networks { - n := NixNetwork{ - Project: g.Project, + n := &NixNetwork{ Runtime: g.Runtime, - Name: name, + Name: g.Project.With(name), Labels: network.Labels, } // Keep track of all containers that are in this network. for _, c := range containers { if slices.Contains(c.Networks, name) { - n.Containers = append(n.Containers, fmt.Sprintf("%s-%s", g.Runtime, g.Project.With(c.Name))) + n.Containers = append(n.Containers, fmt.Sprintf("%s-%s", g.Runtime, c.Name)) } } networks = append(networks, n) } - slices.SortFunc(networks, func(n1, n2 NixNetwork) int { + slices.SortFunc(networks, func(n1, n2 *NixNetwork) int { return cmp.Compare(n1.Name, n2.Name) }) return networks } -func (g *Generator) buildNixVolumes(containers []NixContainer) []NixVolume { - var volumes []NixVolume +func (g *Generator) buildNixVolumes(containers []*NixContainer) []*NixVolume { + var volumes []*NixVolume for name, volume := range g.composeProject.Volumes { - v := NixVolume{ - Project: g.Project, + v := &NixVolume{ Runtime: g.Runtime, Name: name, Driver: volume.Driver, @@ -364,12 +361,12 @@ func (g *Generator) buildNixVolumes(containers []NixContainer) []NixVolume { // Keep track of all containers that use this named volume. for _, c := range containers { if _, ok := c.Volumes[name]; ok { - v.Containers = append(v.Containers, fmt.Sprintf("%s-%s", g.Runtime, g.Project.With(c.Name))) + v.Containers = append(v.Containers, fmt.Sprintf("%s-%s", g.Runtime, c.Name)) } } volumes = append(volumes, v) } - slices.SortFunc(volumes, func(n1, n2 NixVolume) int { + slices.SortFunc(volumes, func(n1, n2 *NixVolume) int { return cmp.Compare(n1.Name, n2.Name) }) return volumes diff --git a/nixose.go b/nixose.go index 6cfe080..71b7031 100644 --- a/nixose.go +++ b/nixose.go @@ -8,7 +8,7 @@ import ( "github.com/compose-spec/compose-go/types" ) -const DefaultProjectSeparator = "-" +const DefaultProjectSeparator = "_" type ContainerRuntime int @@ -36,7 +36,14 @@ type Project struct { separator string } -func NewProject(name, separator string) *Project { +func NewProject(name string) *Project { + if name == "" { + return nil + } + return &Project{Name: name, separator: DefaultProjectSeparator} +} + +func NewProjectWithSeparator(name, separator string) *Project { if name == "" { return nil } @@ -53,8 +60,14 @@ func (p *Project) With(name string) string { return fmt.Sprintf("%s%s%s", p.Name, p.separator, name) } +func (p *Project) WithSeparator(name, separator string) string { + if p == nil { + return name + } + return fmt.Sprintf("%s%s%s", p.Name, separator, name) +} + type NixNetwork struct { - Project *Project Runtime ContainerRuntime Name string Labels map[string]string @@ -62,7 +75,6 @@ type NixNetwork struct { } type NixVolume struct { - Project *Project Runtime ContainerRuntime Name string Driver string @@ -88,7 +100,6 @@ type NixContainerSystemdConfig struct { // https://search.nixos.org/options?channel=unstable&from=0&size=50&sort=relevance&type=packages&query=oci-container type NixContainer struct { - Project *Project Runtime ContainerRuntime Name string Image string @@ -105,15 +116,16 @@ type NixContainer struct { AutoStart bool // Original Docker Compose service. + // Used for post-processing containers and reset to nil right afterwards. service *types.ServiceConfig } type NixContainerConfig struct { Project *Project Runtime ContainerRuntime - Containers []NixContainer - Networks []NixNetwork - Volumes []NixVolume + Containers []*NixContainer + Networks []*NixNetwork + Volumes []*NixVolume } func (c NixContainerConfig) String() string { diff --git a/nixose_test.go b/nixose_test.go index 2837674..b1a488c 100644 --- a/nixose_test.go +++ b/nixose_test.go @@ -47,6 +47,33 @@ func TestDocker(t *testing.T) { } } +func TestDocker_WithProject(t *testing.T) { + ctx := context.Background() + composePath, envFilePath, outFilePath := getPaths(t) + g := Generator{ + Project: NewProject("myproject"), + Runtime: ContainerRuntimeDocker, + Paths: []string{composePath}, + EnvFiles: []string{envFilePath}, + } + c, err := g.Run(ctx) + if err != nil { + t.Fatal(err) + } + wantOutput, err := os.ReadFile(outFilePath) + if err != nil { + t.Fatal(err) + } + got, want := c.String(), string(wantOutput) + if *update { + if err := os.WriteFile(outFilePath, []byte(got), 0644); err != nil { + t.Fatal(err) + } + } else if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("output diff: %s\n", diff) + } +} + func TestPodman(t *testing.T) { ctx := context.Background() composePath, envFilePath, outFilePath := getPaths(t) @@ -72,3 +99,30 @@ func TestPodman(t *testing.T) { t.Errorf("output diff: %s\n", diff) } } + +func TestPodman_WithProject(t *testing.T) { + ctx := context.Background() + composePath, envFilePath, outFilePath := getPaths(t) + g := Generator{ + Project: NewProject("myproject"), + Runtime: ContainerRuntimePodman, + Paths: []string{composePath}, + EnvFiles: []string{envFilePath}, + } + c, err := g.Run(ctx) + if err != nil { + t.Fatal(err) + } + wantOutput, err := os.ReadFile(outFilePath) + if err != nil { + t.Fatal(err) + } + got, want := c.String(), string(wantOutput) + if *update { + if err := os.WriteFile(outFilePath, []byte(got), 0644); err != nil { + t.Fatal(err) + } + } else if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("output diff: %s\n", diff) + } +} diff --git a/templates/container.nix.tmpl b/templates/container.nix.tmpl index fa84d3c..87b9011 100644 --- a/templates/container.nix.tmpl +++ b/templates/container.nix.tmpl @@ -1,6 +1,5 @@ -{{- $name := .Project.With .Name -}} {{- $runtime := .Runtime | printf "%s" -}} -virtualisation.oci-containers.containers."{{$name}}" = { +virtualisation.oci-containers.containers."{{.Name}}" = { image = "{{.Image}}"; {{- if .Environment}} @@ -68,7 +67,7 @@ virtualisation.oci-containers.containers."{{$name}}" = { {{- end}} }; {{- if .SystemdConfig}} -systemd.services."{{$runtime}}-{{$name}}" = { +systemd.services."{{$runtime}}-{{.Name}}" = { {{- if .SystemdConfig.Service}} serviceConfig = { {{- range $k, $v := .SystemdConfig.Service}} diff --git a/templates/network.nix.tmpl b/templates/network.nix.tmpl index 9cd2241..0fcc4c7 100644 --- a/templates/network.nix.tmpl +++ b/templates/network.nix.tmpl @@ -1,16 +1,15 @@ -{{- $name := .Project.With .Name -}} {{- $runtime := .Runtime | printf "%s" -}} {{- $labels := mapToRepeatedKeyValFlag "--label" .Labels -}} -systemd.services."create-{{$runtime}}-network-{{$name}}" = { +systemd.services."create-{{$runtime}}-network-{{.Name}}" = { serviceConfig.Type = "oneshot"; path = [ pkgs.{{$runtime}} ]; {{- if eq $runtime "docker"}} script = '' - docker network inspect {{$name}} || docker network create {{$name}}{{ $labels | join " "}} + docker network inspect {{.Name}} || docker network create {{.Name}}{{ $labels | join " "}} ''; {{- else}} script = '' - podman network create {{$name}} --opt isolate=true --ignore{{ $labels | join " "}} + podman network create {{.Name}} --opt isolate=true --ignore{{ $labels | join " "}} ''; {{- end}} {{- if .Containers}} diff --git a/testdata/TestDocker_WithProject_out.nix b/testdata/TestDocker_WithProject_out.nix new file mode 100644 index 0000000..9393f2c --- /dev/null +++ b/testdata/TestDocker_WithProject_out.nix @@ -0,0 +1,256 @@ +{ pkgs, ... }: + +{ + # Runtime + virtualisation.docker = { + enable = true; + autoPrune.enable = true; + }; + virtualisation.oci-containers.backend = "docker"; + + # Containers + virtualisation.oci-containers.containers."jellyseerr" = { + image = "docker.io/fallenbagel/jellyseerr:latest"; + environment = { + PGID = "1000"; + PUID = "1000"; + TZ = "America/New_York"; + }; + volumes = [ + "/var/volumes/jellyseerr:/app/config:rw" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.jellyseerr.middlewares" = "chain-authelia@file"; + "traefik.http.routers.jellyseerr.rule" = "Host(`requests.hello.us`)"; + "traefik.http.routers.jellyseerr.tls.certresolver" = "htpc"; + }; + dependsOn = [ + "myproject_sabnzbd" + ]; + extraOptions = [ + "--network=myproject_default" + "--network-alias=jellyseerr" + "--dns=1.1.1.1" + "--log-driver=json-file" + "--log-opt=compress=true" + "--log-opt=max-file=3" + "--log-opt=max-size=10m" + ]; + autoStart = false; + }; + systemd.services."docker-jellyseerr" = { + serviceConfig = { + Restart = "always"; + }; + }; + virtualisation.oci-containers.containers."myproject_sabnzbd" = { + image = "lscr.io/linuxserver/sabnzbd"; + environment = { + DOCKER_MODS = "ghcr.io/gilbn/theme.park:sabnzbd"; + PGID = "1000"; + PUID = "1000"; + TP_DOMAIN = "hey.hello.us\/themepark"; + TP_HOTIO = "false"; + TP_THEME = "potato"; + TZ = "America/New_York"; + }; + volumes = [ + "/var/volumes/sabnzbd:/config:rw" + "storage:/storage:rw" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.sabnzbd.middlewares" = "chain-authelia@file"; + "traefik.http.routers.sabnzbd.rule" = "Host(`hey.hello.us`) && PathPrefix(`/sabnzbd`)"; + "traefik.http.routers.sabnzbd.tls.certresolver" = "htpc"; + }; + extraOptions = [ + "--network=myproject_default" + "--network-alias=sabnzbd" + "--log-driver=json-file" + "--log-opt=compress=true" + "--log-opt=max-file=3" + "--log-opt=max-size=10m" + ]; + autoStart = false; + }; + systemd.services."docker-myproject_sabnzbd" = { + serviceConfig = { + Restart = "always"; + RuntimeMaxSec = 10; + }; + }; + virtualisation.oci-containers.containers."myproject_transmission" = { + image = "docker.io/haugene/transmission-openvpn"; + environment = { + GLOBAL_APPLY_PERMISSIONS = "false"; + LOCAL_NETWORK = "192.168.0.0/16"; + PGID = "1000"; + PUID = "1000"; + TRANSMISSION_DHT_ENABLED = "false"; + TRANSMISSION_DOWNLOAD_DIR = "/storage/Downloads/transmission"; + TRANSMISSION_HOME = "/config/transmission-home"; + TRANSMISSION_INCOMPLETE_DIR = "/storage/Downloads/transmission/incomplete"; + TRANSMISSION_INCOMPLETE_DIR_ENABLED = "true"; + TRANSMISSION_PEX_ENABLED = "false"; + TRANSMISSION_SCRIPT_TORRENT_DONE_ENABLED = "true"; + TRANSMISSION_SCRIPT_TORRENT_DONE_FILENAME = "/config/transmission-unpack.sh"; + TZ = "America/New_York"; + }; + volumes = [ + "/etc/localtime:/etc/localtime:ro" + "/var/volumes/transmission/config:/config:rw" + "/var/volumes/transmission/scripts:/scripts:rw" + "storage:/storage:rw" + ]; + ports = [ + "9091:9091/tcp" + ]; + labels = { + "autoheal" = "true"; + "traefik.enable" = "true"; + "traefik.http.routers.transmission.middlewares" = "chain-authelia@file"; + "traefik.http.routers.transmission.rule" = "Host(`hey.hello.us`) && PathPrefix(`/transmission`)"; + "traefik.http.routers.transmission.tls.certresolver" = "htpc"; + "traefik.http.services.transmission.loadbalancer.server.port" = "9091"; + }; + extraOptions = [ + "--network=myproject_default" + "--network-alias=transmission" + "--dns=8.8.8.8" + "--dns=8.8.4.4" + "--privileged" + "--cap-add=NET_ADMIN" + "--device=/dev/net/tun:/dev/net/tun" + "--log-driver=json-file" + "--log-opt=compress=true" + "--log-opt=max-file=3" + "--log-opt=max-size=10m" + ]; + autoStart = false; + }; + systemd.services."docker-myproject_transmission" = { + serviceConfig = { + Restart = "on-failure"; + }; + startLimitBurst = 3; + }; + virtualisation.oci-containers.containers."photoprism-mariadb" = { + image = "docker.io/library/mariadb:10.9"; + environment = { + MARIADB_AUTO_UPGRADE = "1"; + MARIADB_DATABASE = "photoprism"; + MARIADB_INITDB_SKIP_TZINFO = "1"; + MARIADB_PASSWORD = "insecure"; + MARIADB_ROOT_PASSWORD = "insecure"; + MARIADB_USER = "photoprism"; + }; + volumes = [ + "/var/volumes/photoprism-mariadb:/var/lib/mysql:rw" + ]; + extraOptions = [ + "--network=myproject_default" + "--network-alias=photoprism-mariadb" + "--log-driver=json-file" + "--log-opt=compress=true" + "--log-opt=max-file=3" + "--log-opt=max-size=10m" + ]; + user = "1000:1000"; + autoStart = false; + }; + systemd.services."docker-photoprism-mariadb" = { + serviceConfig = { + Restart = "always"; + }; + }; + virtualisation.oci-containers.containers."traefik" = { + image = "docker.io/library/traefik"; + environment = { + CLOUDFLARE_API_KEY = "yomama"; + CLOUDFLARE_EMAIL = "aaa@aaa.com"; + }; + volumes = [ + "/var/run/podman/podman.sock:/var/run/docker.sock:ro" + "/var/volumes/traefik:/etc/traefik:rw" + ]; + ports = [ + "80:80/tcp" + "443:443/tcp" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.traefik.entrypoints" = "https"; + "traefik.http.routers.traefik.middlewares" = "chain-authelia@file"; + "traefik.http.routers.traefik.rule" = "Host(`hey.hello.us`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"; + "traefik.http.routers.traefik.service" = "api@internal"; + "traefik.http.routers.traefik.tls.certresolver" = "htpc"; + }; + extraOptions = [ + "--network=myproject_default" + "--network-alias=traefik" + "--log-driver=json-file" + "--log-opt=compress=true" + "--log-opt=max-file=3" + "--log-opt=max-size=10m" + ]; + autoStart = false; + }; + systemd.services."docker-traefik" = { + serviceConfig = { + Restart = "always"; + }; + }; + + # Networks + systemd.services."create-docker-network-myproject_default" = { + serviceConfig.Type = "oneshot"; + path = [ pkgs.docker ]; + script = '' + docker network inspect myproject_default || docker network create myproject_default + ''; + wantedBy = [ + "docker-jellyseerr.service" + "docker-myproject_sabnzbd.service" + "docker-myproject_transmission.service" + "docker-photoprism-mariadb.service" + "docker-traefik.service" + ]; + }; + + # Volumes + systemd.services."create-docker-volume-books" = { + serviceConfig.Type = "oneshot"; + path = [ pkgs.docker ]; + script = '' + docker volume inspect books || docker volume create books --opt device=/mnt/media/Books,o=bind,type=none + ''; + }; + systemd.services."create-docker-volume-photos" = { + serviceConfig.Type = "oneshot"; + path = [ pkgs.docker ]; + script = '' + docker volume inspect photos || docker volume create photos --opt device=/mnt/photos,o=bind,type=none + ''; + }; + systemd.services."create-docker-volume-storage" = { + serviceConfig.Type = "oneshot"; + path = [ pkgs.docker ]; + script = '' + docker volume inspect storage || docker volume create storage --opt device=/mnt/media,o=bind,type=none + ''; + wantedBy = [ + "docker-myproject_sabnzbd.service" + "docker-myproject_transmission.service" + ]; + }; + + # Scripts + up = writeShellScript "compose-myproject_up.sh" '' + echo "TODO: Create resources." + ''; + down = writeShellScript "compose-myproject_down.sh" '' + echo "TODO: Remove resources." + ''; +} diff --git a/testdata/TestDocker_out.nix b/testdata/TestDocker_out.nix index 7828f50..e28784f 100644 --- a/testdata/TestDocker_out.nix +++ b/testdata/TestDocker_out.nix @@ -25,6 +25,9 @@ "traefik.http.routers.jellyseerr.rule" = "Host(`requests.hello.us`)"; "traefik.http.routers.jellyseerr.tls.certresolver" = "htpc"; }; + dependsOn = [ + "sabnzbd" + ]; extraOptions = [ "--network=default" "--network-alias=jellyseerr" diff --git a/testdata/TestPodman_WithProject_out.nix b/testdata/TestPodman_WithProject_out.nix new file mode 100644 index 0000000..a760e9e --- /dev/null +++ b/testdata/TestPodman_WithProject_out.nix @@ -0,0 +1,234 @@ +{ pkgs, ... }: + +{ + # Runtime + virtualisation.podman = { + enable = true; + autoPrune.enable = true; + dockerCompat = true; + defaultNetwork.settings = { + # Required for container networking to be able to use names. + dns_enabled = true; + }; + }; + virtualisation.oci-containers.backend = "podman"; + + # Containers + virtualisation.oci-containers.containers."jellyseerr" = { + image = "docker.io/fallenbagel/jellyseerr:latest"; + environment = { + PGID = "1000"; + PUID = "1000"; + TZ = "America/New_York"; + }; + volumes = [ + "/var/volumes/jellyseerr:/app/config:rw" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.jellyseerr.middlewares" = "chain-authelia@file"; + "traefik.http.routers.jellyseerr.rule" = "Host(`requests.hello.us`)"; + "traefik.http.routers.jellyseerr.tls.certresolver" = "htpc"; + }; + dependsOn = [ + "myproject_sabnzbd" + ]; + extraOptions = [ + "--network=myproject_default" + "--network-alias=jellyseerr" + "--dns=1.1.1.1" + "--log-driver=json-file" + "--log-opt=compress=true" + "--log-opt=max-file=3" + "--log-opt=max-size=10m" + ]; + autoStart = false; + }; + systemd.services."podman-jellyseerr" = { + serviceConfig = { + Restart = "always"; + }; + }; + virtualisation.oci-containers.containers."myproject_sabnzbd" = { + image = "lscr.io/linuxserver/sabnzbd"; + environment = { + DOCKER_MODS = "ghcr.io/gilbn/theme.park:sabnzbd"; + PGID = "1000"; + PUID = "1000"; + TP_DOMAIN = "hey.hello.us\/themepark"; + TP_HOTIO = "false"; + TP_THEME = "potato"; + TZ = "America/New_York"; + }; + volumes = [ + "/var/volumes/sabnzbd:/config:rw" + "/mnt/media:/storage:rw" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.sabnzbd.middlewares" = "chain-authelia@file"; + "traefik.http.routers.sabnzbd.rule" = "Host(`hey.hello.us`) && PathPrefix(`/sabnzbd`)"; + "traefik.http.routers.sabnzbd.tls.certresolver" = "htpc"; + }; + extraOptions = [ + "--network=myproject_default" + "--network-alias=sabnzbd" + "--log-driver=json-file" + "--log-opt=compress=true" + "--log-opt=max-file=3" + "--log-opt=max-size=10m" + ]; + autoStart = false; + }; + systemd.services."podman-myproject_sabnzbd" = { + serviceConfig = { + Restart = "always"; + RuntimeMaxSec = 10; + }; + }; + virtualisation.oci-containers.containers."myproject_transmission" = { + image = "docker.io/haugene/transmission-openvpn"; + environment = { + GLOBAL_APPLY_PERMISSIONS = "false"; + LOCAL_NETWORK = "192.168.0.0/16"; + PGID = "1000"; + PUID = "1000"; + TRANSMISSION_DHT_ENABLED = "false"; + TRANSMISSION_DOWNLOAD_DIR = "/storage/Downloads/transmission"; + TRANSMISSION_HOME = "/config/transmission-home"; + TRANSMISSION_INCOMPLETE_DIR = "/storage/Downloads/transmission/incomplete"; + TRANSMISSION_INCOMPLETE_DIR_ENABLED = "true"; + TRANSMISSION_PEX_ENABLED = "false"; + TRANSMISSION_SCRIPT_TORRENT_DONE_ENABLED = "true"; + TRANSMISSION_SCRIPT_TORRENT_DONE_FILENAME = "/config/transmission-unpack.sh"; + TZ = "America/New_York"; + }; + volumes = [ + "/etc/localtime:/etc/localtime:ro" + "/var/volumes/transmission/config:/config:rw" + "/var/volumes/transmission/scripts:/scripts:rw" + "/mnt/media:/storage:rw" + ]; + ports = [ + "9091:9091/tcp" + ]; + labels = { + "autoheal" = "true"; + "traefik.enable" = "true"; + "traefik.http.routers.transmission.middlewares" = "chain-authelia@file"; + "traefik.http.routers.transmission.rule" = "Host(`hey.hello.us`) && PathPrefix(`/transmission`)"; + "traefik.http.routers.transmission.tls.certresolver" = "htpc"; + "traefik.http.services.transmission.loadbalancer.server.port" = "9091"; + }; + extraOptions = [ + "--network=myproject_default" + "--network-alias=transmission" + "--dns=8.8.8.8" + "--dns=8.8.4.4" + "--privileged" + "--cap-add=NET_ADMIN" + "--device=/dev/net/tun:/dev/net/tun" + "--log-driver=json-file" + "--log-opt=compress=true" + "--log-opt=max-file=3" + "--log-opt=max-size=10m" + ]; + autoStart = false; + }; + systemd.services."podman-myproject_transmission" = { + serviceConfig = { + Restart = "on-failure"; + }; + startLimitBurst = 3; + }; + virtualisation.oci-containers.containers."photoprism-mariadb" = { + image = "docker.io/library/mariadb:10.9"; + environment = { + MARIADB_AUTO_UPGRADE = "1"; + MARIADB_DATABASE = "photoprism"; + MARIADB_INITDB_SKIP_TZINFO = "1"; + MARIADB_PASSWORD = "insecure"; + MARIADB_ROOT_PASSWORD = "insecure"; + MARIADB_USER = "photoprism"; + }; + volumes = [ + "/var/volumes/photoprism-mariadb:/var/lib/mysql:rw" + ]; + extraOptions = [ + "--network=myproject_default" + "--network-alias=photoprism-mariadb" + "--log-driver=json-file" + "--log-opt=compress=true" + "--log-opt=max-file=3" + "--log-opt=max-size=10m" + ]; + user = "1000:1000"; + autoStart = false; + }; + systemd.services."podman-photoprism-mariadb" = { + serviceConfig = { + Restart = "always"; + }; + }; + virtualisation.oci-containers.containers."traefik" = { + image = "docker.io/library/traefik"; + environment = { + CLOUDFLARE_API_KEY = "yomama"; + CLOUDFLARE_EMAIL = "aaa@aaa.com"; + }; + volumes = [ + "/var/run/podman/podman.sock:/var/run/docker.sock:ro" + "/var/volumes/traefik:/etc/traefik:rw" + ]; + ports = [ + "80:80/tcp" + "443:443/tcp" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.traefik.entrypoints" = "https"; + "traefik.http.routers.traefik.middlewares" = "chain-authelia@file"; + "traefik.http.routers.traefik.rule" = "Host(`hey.hello.us`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"; + "traefik.http.routers.traefik.service" = "api@internal"; + "traefik.http.routers.traefik.tls.certresolver" = "htpc"; + }; + extraOptions = [ + "--network=myproject_default" + "--network-alias=traefik" + "--log-driver=json-file" + "--log-opt=compress=true" + "--log-opt=max-file=3" + "--log-opt=max-size=10m" + ]; + autoStart = false; + }; + systemd.services."podman-traefik" = { + serviceConfig = { + Restart = "always"; + }; + }; + + # Networks + systemd.services."create-podman-network-myproject_default" = { + serviceConfig.Type = "oneshot"; + path = [ pkgs.podman ]; + script = '' + podman network create myproject_default --opt isolate=true --ignore + ''; + wantedBy = [ + "podman-jellyseerr.service" + "podman-myproject_sabnzbd.service" + "podman-myproject_transmission.service" + "podman-photoprism-mariadb.service" + "podman-traefik.service" + ]; + }; + + # Scripts + up = writeShellScript "compose-myproject_up.sh" '' + echo "TODO: Create resources." + ''; + down = writeShellScript "compose-myproject_down.sh" '' + echo "TODO: Remove resources." + ''; +} diff --git a/testdata/TestPodman_out.nix b/testdata/TestPodman_out.nix index 32feaaa..44751af 100644 --- a/testdata/TestPodman_out.nix +++ b/testdata/TestPodman_out.nix @@ -30,6 +30,9 @@ "traefik.http.routers.jellyseerr.rule" = "Host(`requests.hello.us`)"; "traefik.http.routers.jellyseerr.tls.certresolver" = "htpc"; }; + dependsOn = [ + "sabnzbd" + ]; extraOptions = [ "--network=default" "--network-alias=jellyseerr" diff --git a/testdata/docker-compose.yml b/testdata/docker-compose.yml index e4eff35..7767bb9 100644 --- a/testdata/docker-compose.yml +++ b/testdata/docker-compose.yml @@ -2,7 +2,6 @@ version: "3.7" services: sabnzbd: image: lscr.io/linuxserver/sabnzbd - container_name: sabnzbd environment: PUID: ${PUID} PGID: ${PGID} @@ -29,7 +28,6 @@ services: restart: unless-stopped transmission: image: docker.io/haugene/transmission-openvpn - container_name: transmission privileged: true cap_add: - NET_ADMIN @@ -103,6 +101,8 @@ services: - "traefik.http.routers.jellyseerr.rule=Host(`requests.${DOMAIN}`)" - "traefik.http.routers.jellyseerr.tls.certresolver=htpc" - "traefik.http.routers.jellyseerr.middlewares=chain-authelia@file" + depends_on: + - sabnzbd logging: driver: "json-file" options: