Deployment and lifecycle management for Nix
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

client(services): clean up services unit env, add profiles for all services

+124 -34
+22 -5
cmd/client/main.go
··· 7 7 "log" 8 8 "log/slog" 9 9 "os" 10 + "path/filepath" 10 11 "strings" 11 12 "time" 12 13 ··· 297 298 os.Exit(1) 298 299 } 299 300 300 - if err := realize(storePath.Path, caches, cfg.Seed.Download.Initrd); err != nil { 301 + if err := realize(storePath.Path, caches, cfg.Seed.Download.Initrd, ""); err != nil { 301 302 slog.Error("Failed realizing seed", "error", err) 302 303 os.Exit(1) 303 304 } ··· 402 403 os.Exit(1) 403 404 } 404 405 405 - if err := realize(storePath.Path, caches, false); err != nil { 406 + if err := realize(storePath.Path, caches, false, ""); err != nil { 406 407 slog.Error("Failed realizing seed", "error", err) 407 408 os.Exit(1) 408 409 } ··· 466 467 467 468 paths := []client.StorePath{} 468 469 470 + if len(cfg.Services.Services) == 0 { 471 + slog.Error("No services configured") 472 + os.Exit(1) 473 + } 474 + 475 + servicesProfileDir := filepath.Join(profileParentDir(), "services") 476 + _, err = os.Stat(servicesProfileDir) 477 + if err != nil { 478 + slog.Debug("Creating profile directory for services", "path", servicesProfileDir) 479 + err = os.MkdirAll(servicesProfileDir, 0755) 480 + if err != nil { 481 + slog.Error("Failed to create profile directory for services", "error", err) 482 + os.Exit(1) 483 + } 484 + } 485 + 469 486 for _, service := range cfg.Services.Services { 470 487 seed, err := seedClient.GetSeed(string(service), string(client.Service)) 471 488 if err != nil { ··· 487 504 os.Exit(1) 488 505 } 489 506 490 - if err := realize(storePath.Path, caches, false); err != nil { 507 + if err := realize(storePath.Path, caches, false, filepath.Join(servicesProfileDir, string(service))); err != nil { 491 508 slog.Error("Failed realizing seed", "error", err) 492 509 os.Exit(1) 493 510 } ··· 497 514 paths = append(paths, *storePath) 498 515 } 499 516 500 - servicesPath, err := buildServicesEnv(paths) 517 + servicesPath, err := buildServicesUnits(paths) 501 518 if err != nil { 502 519 slog.Error("Failed to build services environment", "error", err) 503 520 os.Exit(1) ··· 522 539 case "": 523 540 return "", fmt.Errorf("failed to detect user, not loading default config file") 524 541 default: 525 - return fmt.Sprintf("%s/sower/client.json", xdg.ConfigHome), nil 542 + return filepath.Join(xdg.ConfigHome, "sower", "client.json"), nil 526 543 } 527 544 }
+13 -3
cmd/client/seed.go
··· 42 42 return nil 43 43 } 44 44 45 - func realize(storePath string, caches *[]client.NixCache, initrd bool) error { 45 + func realize(storePath string, caches *[]client.NixCache, initrd bool, profile string) error { 46 46 slog.Debug("Realizing path", "path", storePath) 47 47 48 48 if storePath == "" { 49 49 return fmt.Errorf("cannot download without seed out_path") 50 50 } 51 51 52 - cmd := []string{"--realize", storePath} 52 + _, err := os.Stat(storePath) 53 + if err == nil { 54 + slog.Debug("Already downloaded seed", "path", storePath) 55 + return nil 56 + } 57 + 58 + cmd := []string{"build", storePath} 53 59 54 60 if initrd { 55 61 cmd = append(cmd, "--store", "/sysroot") ··· 67 73 cmd = append(cmd, "--extra-trusted-public-keys", strings.Join(publicKeys, ",")) 68 74 } 69 75 70 - err := commands.SimpleRun(exec.Command("nix-store", cmd...)) 76 + if profile != "" { 77 + cmd = append(cmd, "--profile", profile) 78 + } 79 + 80 + err = commands.SimpleRun(exec.Command("nix", cmd...)) 71 81 72 82 return err 73 83 }
+79 -26
cmd/client/services.go
··· 5 5 "log/slog" 6 6 "os" 7 7 "os/exec" 8 + "path/filepath" 8 9 "text/template" 9 10 10 11 "codeberg.org/adamcstephens/sower/client" 11 12 "codeberg.org/adamcstephens/sower/cmd/client/commands" 13 + "github.com/adrg/xdg" 12 14 ) 13 15 14 16 var nixpkgsref = "refs/heads/nixos-unstable" ··· 19 21 } 20 22 21 23 // https://github.com/NixOS/nixpkgs/archive/refs/heads/master.zip 22 - func buildServicesEnv(paths []client.StorePath) (string, error) { 23 - slog.Debug("Building services environment", "nixpkgs", nixpkgsref) 24 + func buildServicesUnits(paths []client.StorePath) (string, error) { 25 + slog.Debug("Collecting services units", "nixpkgs", nixpkgsref) 24 26 25 - envTemplate := `{ 27 + unitsTemplate := `{ 26 28 pkgs ? 27 - import 28 - (fetchTarball "https://github.com/NixOS/nixpkgs/archive/{{ .Nixpkgsref }}.tar.gz") 29 + import <nixpkgs> 30 + # import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/{{ .Nixpkgsref }}.tar.gz") 29 31 { }, 30 32 }: 31 - pkgs.buildEnv { 32 - name = "sower-services"; 33 - paths = [{{ range .Paths }} 33 + pkgs.runCommand "sower-services" 34 + { 35 + nativeBuildInputs = [{{ range .Paths }} 34 36 {{ .Path }} 35 37 {{ end }} ]; 38 + } 39 + '' 40 + mkdir -p $out/nix-support 41 + 42 + for path in $nativeBuildInputs; do 43 + echo "Copying $path" 44 + cp --recursive --no-clobber $path/.sower $out/ 45 + chmod --recursive +w $out/ 46 + done 36 47 37 - pathsToLink = [ 38 - "/.sower" 39 - ]; 48 + if [ ! -e $out/.sower/systemd ]; then 49 + echo "No services found" 50 + exit 1 51 + fi 40 52 41 - postBuild = '' 42 53 mv $out/.sower/* $out/ 43 54 rmdir $out/.sower 44 - ''; 45 - } 55 + '' 46 56 ` 47 57 48 - envFileNix, err := os.CreateTemp("", "services-env") 58 + unitsFileNix, err := os.CreateTemp("", "services-units-*.nix") 49 59 if err != nil { 50 60 return "", fmt.Errorf("failed to create tempfile: %v", err) 51 61 } 52 - defer os.Remove(envFileNix.Name()) 62 + slog.Debug("Created temp file", "units-file", unitsFileNix.Name()) 63 + // defer os.Remove(unitsFileNix.Name()) 53 64 54 - templateParser, err := template.New("services-env").Parse(envTemplate) 65 + templateParser, err := template.New("services-units").Parse(unitsTemplate) 55 66 if err != nil { 56 67 return "", fmt.Errorf("failed to parse template: %v", err) 57 68 } 58 69 59 - err = templateParser.Execute(envFileNix, &EnvTemplate{Paths: paths, Nixpkgsref: nixpkgsref}) 70 + err = templateParser.Execute(unitsFileNix, &EnvTemplate{Paths: paths, Nixpkgsref: nixpkgsref}) 60 71 if err != nil { 61 72 return "", fmt.Errorf("failed to parse template: %v", err) 62 73 } 63 74 64 - cmd := exec.Command("nix-build", "--no-out-link", envFileNix.Name()) 75 + cmd := exec.Command("nix-build", "--no-out-link", unitsFileNix.Name()) 65 76 stdout, err := commands.Run(cmd) 66 77 if err != nil { 67 - return "", fmt.Errorf("failed to build services env file: %v", err) 78 + return "", fmt.Errorf("failed to build services units file: %v", err) 68 79 } 69 80 70 - slog.Debug("Successfully built services environment", "nixpkgs", nixpkgsref, "path", stdout) 81 + slog.Debug("Successfully built services units output", "nixpkgs", nixpkgsref, "path", stdout) 71 82 72 83 return stdout, nil 73 84 } 74 85 75 86 func activateServices(storePath string) error { 76 - profileDir := "/nix/var/nix/profiles/sower" 77 - _, err := os.Stat(profileDir) 87 + parentDir := profileParentDir() 88 + _, err := os.Stat(parentDir) 78 89 if err != nil { 79 - slog.Debug("Creating profile directory", "dir", profileDir) 80 - err = os.Mkdir(profileDir, 0x0755) 90 + slog.Debug("Creating profile directory", "dir", parentDir) 91 + err = os.MkdirAll(parentDir, 0755) 81 92 if err != nil { 82 93 return fmt.Errorf("failed to create sower profile directory: %v", err) 83 94 } 84 95 } 85 96 86 - err = setProfile(fmt.Sprintf("%s/services", profileDir), storePath) 97 + profile := filepath.Join(parentDir, "services-units") 98 + var oldProfile string 99 + 100 + _, err = os.Stat(profile) 101 + if err != nil { 102 + slog.Warn("No old profile", "profile", profile) 103 + oldProfile, err = os.MkdirTemp("", "fake-old-units") 104 + if err != nil { 105 + return fmt.Errorf("unable to create fake old profile: %v", err) 106 + } 107 + err = os.Mkdir(filepath.Join(oldProfile, "systemd"), 0700) 108 + if err != nil { 109 + return fmt.Errorf("failed to create fake systemd dir: %v", err) 110 + } 111 + } else { 112 + oldProfile, err = filepath.EvalSymlinks(profile) 113 + if err != nil { 114 + return fmt.Errorf("unable to read old profile: %v", err) 115 + } 116 + } 117 + 118 + if oldProfile == storePath { 119 + slog.Debug("Services already activated") 120 + return nil 121 + } 122 + 123 + oldUnits := filepath.Join(oldProfile, "systemd") 124 + newUnits := filepath.Join(storePath, "systemd") 125 + 126 + err = commands.SimpleRun(exec.Command("sd-switch", "--verbose", "--system", "--old-units", oldUnits, "--new-units", newUnits)) 127 + if err != nil { 128 + return fmt.Errorf("failed to run sd-switch: %v", err) 129 + } 130 + 131 + err = setProfile(profile, storePath) 87 132 if err != nil { 88 133 return fmt.Errorf("failed to set services profile: %v", err) 89 134 } 90 135 91 136 return nil 92 137 } 138 + 139 + func profileParentDir() string { 140 + if user, exists := os.LookupEnv("USER"); user != "root" && exists { 141 + return filepath.Join(xdg.StateHome, "nix", "profiles", "sower") 142 + } 143 + 144 + return "/nix/var/nix/profiles/sower" 145 + }
+6
flake.nix
··· 77 77 pkgs.process-compose 78 78 config.process-compose.devServices.services.postgres.postgres1.package 79 79 config.process-compose.devServices.outputs.package 80 + pkgs.sd-switch 80 81 pkgs.watchexec 81 82 ] 82 83 ++ lib.optionals pkgs.stdenv.isLinux [ ··· 126 127 }; 127 128 128 129 sowerServicesHook = pkgs.callPackage ./nix/packages/services-hook.nix { }; 130 + 131 + tests-simple-service = pkgs.callPackage ./nix/tests/simple-service.nix { 132 + inherit sowerServicesHook; 133 + sowerLib = self.lib; 134 + }; 129 135 }; 130 136 131 137 process-compose.devServices =
+4
nix/nixos/client.nix
··· 65 65 }; 66 66 67 67 config = lib.mkIf cfg.enable { 68 + boot.extraSystemdUnitPaths = [ 69 + "/etc/sower/systemd" 70 + ]; 71 + 68 72 environment.etc."sower/client.json".source = lib.mkIf (cfg.settings != null) ( 69 73 json.generate "sower-client.json" ( 70 74 cfg.settings // (lib.optionalAttrs cfg.autoreboot { reboot = true; })