Deployment and lifecycle management for Nix
0
fork

Configure Feed

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

nixos: add agent capability

+167 -142
+2 -1
apps/sower_agent/lib/sower_agent/application.ex
··· 12 12 {SowerAgent.Client, []}, 13 13 {SowerAgent.Storage, []}, 14 14 {Task.Supervisor, name: SowerAgent.TaskSupervisor}, 15 - SowerAgent.Scheduler 15 + SowerAgent.Scheduler, 16 + :systemd.ready() 16 17 ] 17 18 18 19 # See https://hexdocs.pm/elixir/Supervisor.html
+3 -1
apps/sower_agent/mix.exs
··· 35 35 {:slipstream, "~> 1.0"}, 36 36 # load typedstruct before typed_struct_ecto_changeset 37 37 {:typedstruct, "~> 0.5", runtime: false}, 38 - {:sower_client, in_umbrella: true} 38 + {:sower_client, in_umbrella: true}, 39 + {:systemd, 40 + github: "hauleth/erlang-systemd", ref: "62723b2a99afca491cc5c8f15c7f72d108e84f4b"} 39 41 ] 40 42 end 41 43 end
+38 -5
apps/sower_client/lib/sower_client/config.ex
··· 156 156 def xdg_config_path(app_name, filename) do 157 157 case System.get_env("USER") do 158 158 user when user != "root" -> 159 - System.get_env("XDG_CONFIG_HOME", Path.join(System.fetch_env!("HOME"), ".config")) 160 - |> Path.join(app_name) 161 - |> Path.join(filename) 159 + xdg_path_file(:config, app_name, filename) 162 160 163 161 _ -> 164 162 Path.join(["/etc", app_name, filename]) ··· 168 166 def xdg_state_path(app_name) do 169 167 case System.get_env("USER") do 170 168 user when user != "root" -> 171 - System.get_env("XDG_STATE_HOME", Path.join(System.fetch_env!("HOME"), ".local/state")) 172 - |> Path.join(app_name) 169 + case System.get_env("STATE_DIRECTORY") do 170 + nil -> 171 + xdg_path_file(:state, app_name, nil) 172 + 173 + state -> 174 + state 175 + end 173 176 174 177 _ -> 175 178 Path.join("/var/lib", app_name) 179 + end 180 + end 181 + 182 + defp xdg_path_file(:config, app_name, filename) do 183 + case System.get_env("XDG_CONFIG_HOME", home_default(".config")) do 184 + nil -> 185 + nil 186 + 187 + path -> 188 + path 189 + |> Path.join(app_name) 190 + |> Path.join(filename) 191 + end 192 + end 193 + 194 + defp xdg_path_file(:state, app_name, _filename) do 195 + case System.get_env("XDG_STATE_HOME", home_default(".local/state")) do 196 + nil -> 197 + nil 198 + 199 + path -> 200 + path 201 + |> Path.join(app_name) 202 + end 203 + end 204 + 205 + defp home_default(subdir) do 206 + case System.get_env("HOME") do 207 + nil -> nil 208 + home -> Path.join(home, subdir) 176 209 end 177 210 end 178 211
+109
nix/nixos/agent.nix
··· 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + ... 6 + }: 7 + let 8 + cfg = config.services.sower.agent; 9 + json = pkgs.formats.json { }; 10 + jsonType = json.type; 11 + jsonConfig = json.generate "sower-client.json" ( 12 + cfg.settings // (lib.optionalAttrs cfg.autoreboot { reboot = true; }) 13 + ); 14 + 15 + # TODO re-enable services support 16 + manageServices = false; 17 + in 18 + { 19 + options = { 20 + services.sower.agent = { 21 + enable = lib.mkEnableOption "Sower agent"; 22 + 23 + autoreboot = lib.mkEnableOption "automatic rebooting"; 24 + 25 + credentials = lib.mkOption { 26 + type = lib.types.listOf lib.types.str; 27 + description = "systemd credentials"; 28 + default = [ ]; 29 + }; 30 + 31 + onCalendar = lib.mkOption { 32 + type = lib.types.str; 33 + description = "OnCalendar for systemd timer on linux. See https://www.freedesktop.org/software/systemd/man/latest/systemd.time.html#Calendar%20Events"; 34 + default = "daily"; 35 + }; 36 + 37 + package = lib.mkOption { type = lib.types.package; }; 38 + 39 + settings = lib.mkOption { 40 + type = lib.types.submodule { 41 + freeformType = jsonType; 42 + 43 + options = { 44 + }; 45 + }; 46 + description = "Sower client (agent and cli) configuration file"; 47 + default = null; 48 + }; 49 + }; 50 + }; 51 + 52 + config = lib.mkIf cfg.enable { 53 + boot.extraSystemdUnitPaths = lib.optionals manageServices [ 54 + "/etc/sower/systemd/system" 55 + ]; 56 + 57 + environment.etc."sower/client.json".source = lib.mkIf (cfg.settings != null) jsonConfig; 58 + 59 + environment.systemPackages = [ cfg.package ]; 60 + 61 + systemd.services.sower-agent = { 62 + wantedBy = [ "multi-user.target" ]; 63 + after = [ 64 + "network-online.target" 65 + ] 66 + ++ lib.optionals config.services.sower.server.enable [ "sower.service" ]; 67 + requires = [ 68 + "network-online.target" 69 + ] 70 + ++ lib.optionals config.services.sower.server.enable [ "sower.service" ]; 71 + path = [ config.nix.package ]; 72 + 73 + environment = { 74 + RELEASE_COOKIE = "%S/release-cookie"; 75 + SHELL = lib.getExe pkgs.bash; 76 + SOWER_CONFIG_FILE = "/etc/sower/client.json"; 77 + }; 78 + 79 + # avoid restarting mid-switch 80 + restartIfChanged = false; 81 + 82 + serviceConfig = { 83 + Type = "notify"; 84 + WatchdogSec = "10s"; 85 + Restart = lib.mkDefault "on-failure"; 86 + 87 + LoadCredential = cfg.credentials; 88 + 89 + DynamicUser = true; 90 + StateDirectory = "sower-agent"; 91 + WorkingDirectory = "%S/sower-agent"; 92 + 93 + ExecStartPre = pkgs.writeShellScript "sower-agentinit-secrets" '' 94 + if [ ! -e release-cookie ]; then 95 + echo "Generating release cookie" 96 + ${lib.getExe pkgs.openssl} rand -hex 48 > release-cookie 97 + fi 98 + ''; 99 + ExecStart = "${lib.getExe cfg.package} start"; 100 + ExecStop = "${lib.getExe cfg.package} stop"; 101 + }; 102 + }; 103 + 104 + systemd.tmpfiles.rules = lib.optionals manageServices [ 105 + "d /etc/sower 0755 root root" 106 + "L /etc/sower/systemd - - - - /nix/var/nix/profiles/sower/services-units/systemd" 107 + ]; 108 + }; 109 + }
-119
nix/nixos/client.nix
··· 1 - { 2 - config, 3 - lib, 4 - pkgs, 5 - ... 6 - }: 7 - let 8 - cfg = config.services.sower.client; 9 - json = pkgs.formats.json { }; 10 - jsonType = json.type; 11 - jsonConfig = json.generate "sower-client.json" ( 12 - cfg.settings // (lib.optionalAttrs cfg.autoreboot { reboot = true; }) 13 - ); 14 - in 15 - { 16 - options = { 17 - services.sower.client = { 18 - enable = lib.mkEnableOption "Sower client"; 19 - 20 - autoreboot = lib.mkEnableOption "automatic rebooting"; 21 - 22 - credentials = lib.mkOption { 23 - type = lib.types.listOf lib.types.str; 24 - description = "systemd credentials"; 25 - default = [ ]; 26 - }; 27 - 28 - onCalendar = lib.mkOption { 29 - type = lib.types.str; 30 - description = "OnCalendar for systemd timer on linux. See https://www.freedesktop.org/software/systemd/man/latest/systemd.time.html#Calendar%20Events"; 31 - default = "daily"; 32 - }; 33 - 34 - package = lib.mkOption { type = lib.types.package; }; 35 - 36 - settings = lib.mkOption { 37 - type = lib.types.submodule { 38 - freeformType = jsonType; 39 - 40 - options = { 41 - seed = { 42 - name = lib.mkOption { 43 - type = lib.types.str; 44 - description = "seed name"; 45 - default = config.networking.hostName; 46 - }; 47 - 48 - type = lib.mkOption { 49 - type = lib.types.enum [ 50 - "home-manager" 51 - "nix-darwin" 52 - "nixos" 53 - ]; 54 - default = "nixos"; 55 - }; 56 - }; 57 - 58 - services.services = lib.mkOption { 59 - type = lib.types.listOf lib.types.str; 60 - description = "services to be managed by client"; 61 - default = [ ]; 62 - }; 63 - }; 64 - }; 65 - description = "Sower configuration file"; 66 - default = null; 67 - }; 68 - }; 69 - }; 70 - 71 - config = lib.mkIf cfg.enable { 72 - boot.extraSystemdUnitPaths = lib.optionals (cfg.settings.services.services != [ ]) [ 73 - "/etc/sower/systemd/system" 74 - ]; 75 - 76 - environment.etc."sower/client.json".source = lib.mkIf (cfg.settings != null) jsonConfig; 77 - 78 - environment.systemPackages = [ cfg.package ]; 79 - 80 - systemd.services.sower-client = { 81 - after = [ "network-online.target" ]; 82 - requires = [ "network-online.target" ]; 83 - path = [ config.nix.package ]; 84 - 85 - environment = { 86 - SOWER_CONFIG_FILE = "/etc/sower/client.json"; 87 - }; 88 - 89 - # avoid restarting mid-switch 90 - restartIfChanged = false; 91 - 92 - serviceConfig = { 93 - ExecStart = 94 - [ 95 - "${lib.getExe cfg.package} seed upgrade ${lib.optionalString cfg.autoreboot "--yes"}" 96 - ] 97 - ++ lib.optionals (cfg.settings.services.services != [ ]) [ 98 - "${lib.getExe cfg.package} services upgrade" 99 - ]; 100 - Type = "oneshot"; 101 - LoadCredential = cfg.credentials; 102 - }; 103 - }; 104 - 105 - systemd.timers.sower-client = { 106 - wantedBy = [ "timers.target" ]; 107 - 108 - timerConfig = { 109 - OnCalendar = cfg.onCalendar; 110 - Persistent = true; 111 - }; 112 - }; 113 - 114 - systemd.tmpfiles.rules = lib.optionals (cfg.settings.services.services != [ ]) [ 115 - "d /etc/sower 0755 root root" 116 - "L /etc/sower/systemd - - - - /nix/var/nix/profiles/sower/services-units/systemd" 117 - ]; 118 - }; 119 - }
+1 -1
nix/nixos/module.nix
··· 1 1 { 2 2 imports = [ 3 - # ./client.nix 3 + ./agent.nix 4 4 ./server.nix 5 5 ]; 6 6 }
+2 -1
nix/nixos/server.nix
··· 53 53 type = lib.types.bool; 54 54 default = true; 55 55 description = '' 56 - Whether to initialise non-existent secrets with random values. 56 + Whether to initialise release cookie and secret key base with random values on first start. 57 57 ''; 58 58 }; 59 59 ··· 63 63 64 64 config = lib.mkIf cfg.enable { 65 65 assertions = [ 66 + # TODO assertions for setting secrets that initSecrets sets 66 67 { 67 68 assertion = builtins.hasAttr "RELEASE_COOKIE_FILE" cfg.environment; 68 69 message = ''
+12 -14
nix/tests/e2e.nix
··· 50 50 connect-timeout = 1; 51 51 }; 52 52 53 - # services.sower.client = { 54 - # enable = true; 55 - # package = flake.packages.${pkgs.stdenv.hostPlatform.system}.cli; 56 - # 57 - # settings = { 58 - # api-token-file = "/run/sower/test_token"; 59 - # debug = true; 60 - # endpoint = "http://localhost:4000"; 61 - # 62 - # services.services = [ 63 - # "simple-service" 64 - # ]; 65 - # }; 66 - # }; 53 + services.sower.agent = { 54 + enable = true; 55 + package = flake.packages.${pkgs.stdenv.hostPlatform.system}.agent; 56 + 57 + settings = { 58 + access_token_file = "/run/sower/test_token"; 59 + endpoint = "http://localhost:4000"; 60 + }; 61 + }; 62 + # if agent fails to start, fail immediately 63 + systemd.services.sower-agent.serviceConfig.Restart = "no"; 67 64 68 65 services.sower.server = { 69 66 enable = true; ··· 139 136 start_all() 140 137 server.wait_for_unit("postgresql.service") 141 138 server.wait_for_unit("sower.service") 139 + server.wait_for_unit("sower-agent.service") 142 140 server.wait_for_open_port(4000) 143 141 144 142 # with subtest("basic submission"):