at 23.11-beta 6.5 kB view raw
1{ config, pkgs, lib, ... }: 2let 3 cfg = config.services.keter; 4 yaml = pkgs.formats.yaml { }; 5in 6{ 7 meta = { 8 maintainers = with lib.maintainers; [ jappie ]; 9 }; 10 11 imports = [ 12 (lib.mkRenamedOptionModule [ "services" "keter" "keterRoot" ] [ "services" "keter" "root" ]) 13 (lib.mkRenamedOptionModule [ "services" "keter" "keterPackage" ] [ "services" "keter" "package" ]) 14 ]; 15 16 options.services.keter = { 17 enable = lib.mkEnableOption (lib.mdDoc ''keter, a web app deployment manager. 18Note that this module only support loading of webapps: 19Keep an old app running and swap the ports when the new one is booted 20''); 21 22 root = lib.mkOption { 23 type = lib.types.str; 24 default = "/var/lib/keter"; 25 description = lib.mdDoc "Mutable state folder for keter"; 26 }; 27 28 package = lib.mkOption { 29 type = lib.types.package; 30 default = pkgs.haskellPackages.keter; 31 defaultText = lib.literalExpression "pkgs.haskellPackages.keter"; 32 description = lib.mdDoc "The keter package to be used"; 33 }; 34 35 36 globalKeterConfig = lib.mkOption { 37 type = lib.types.submodule { 38 freeformType = yaml.type; 39 options = { 40 ip-from-header = lib.mkOption { 41 default = true; 42 type = lib.types.bool; 43 description = lib.mdDoc "You want that ip-from-header in the nginx setup case. It allows nginx setting the original ip address rather then it being localhost (due to reverse proxying)"; 44 }; 45 listeners = lib.mkOption { 46 default = [{ host = "*"; port = 6981; }]; 47 type = lib.types.listOf (lib.types.submodule { 48 options = { 49 host = lib.mkOption { 50 type = lib.types.str; 51 description = lib.mdDoc "host"; 52 }; 53 port = lib.mkOption { 54 type = lib.types.port; 55 description = lib.mdDoc "port"; 56 }; 57 }; 58 }); 59 description = lib.mdDoc '' 60 You want that ip-from-header in 61 the nginx setup case. 62 It allows nginx setting the original ip address rather 63 then it being localhost (due to reverse proxying). 64 However if you configure keter to accept connections 65 directly you may want to set this to false.''; 66 }; 67 rotate-logs = lib.mkOption { 68 default = false; 69 type = lib.types.bool; 70 description = lib.mdDoc '' 71 emits keter logs and it's applications to stderr. 72 which allows journald to capture them. 73 Set to true to let keter put the logs in files 74 (useful on non systemd systems, this is the old approach 75 where keter handled log management)''; 76 }; 77 }; 78 }; 79 description = lib.mdDoc "Global config for keter, see <https://github.com/snoyberg/keter/blob/master/etc/keter-config.yaml> for reference"; 80 }; 81 82 bundle = { 83 appName = lib.mkOption { 84 type = lib.types.str; 85 default = "myapp"; 86 description = lib.mdDoc "The name keter assigns to this bundle"; 87 }; 88 89 executable = lib.mkOption { 90 type = lib.types.path; 91 description = lib.mdDoc "The executable to be run"; 92 }; 93 94 domain = lib.mkOption { 95 type = lib.types.str; 96 default = "example.com"; 97 description = lib.mdDoc "The domain keter will bind to"; 98 }; 99 100 publicScript = lib.mkOption { 101 type = lib.types.str; 102 default = ""; 103 description = lib.mdDoc '' 104 Allows loading of public environment variables, 105 these are emitted to the log so it shouldn't contain secrets. 106 ''; 107 example = "ADMIN_EMAIL=hi@example.com"; 108 }; 109 110 secretScript = lib.mkOption { 111 type = lib.types.str; 112 default = ""; 113 description = lib.mdDoc "Allows loading of private environment variables"; 114 example = "MY_AWS_KEY=$(cat /run/keys/AWS_ACCESS_KEY_ID)"; 115 }; 116 }; 117 118 }; 119 120 config = lib.mkIf cfg.enable ( 121 let 122 incoming = "${cfg.root}/incoming"; 123 124 125 globalKeterConfigFile = pkgs.writeTextFile { 126 name = "keter-config.yml"; 127 text = (lib.generators.toYAML { } (cfg.globalKeterConfig // { root = cfg.root; })); 128 }; 129 130 # If things are expected to change often, put it in the bundle! 131 bundle = pkgs.callPackage ./bundle.nix 132 (cfg.bundle // { keterExecutable = executable; keterDomain = cfg.bundle.domain; }); 133 134 # This indirection is required to ensure the nix path 135 # gets copied over to the target machine in remote deployments. 136 # Furthermore, it's important that we use exec to 137 # run the binary otherwise we get process leakage due to this 138 # being executed on every change. 139 executable = pkgs.writeShellScript "bundle-wrapper" '' 140 set -e 141 ${cfg.bundle.secretScript} 142 set -xe 143 ${cfg.bundle.publicScript} 144 exec ${cfg.bundle.executable} 145 ''; 146 147 in 148 { 149 systemd.services.keter = { 150 description = "keter app loader"; 151 script = '' 152 set -xe 153 mkdir -p ${incoming} 154 ${lib.getExe cfg.package} ${globalKeterConfigFile}; 155 ''; 156 wantedBy = [ "multi-user.target" "nginx.service" ]; 157 158 serviceConfig = { 159 Restart = "always"; 160 RestartSec = "10s"; 161 }; 162 163 after = [ 164 "network.target" 165 "local-fs.target" 166 "postgresql.service" 167 ]; 168 }; 169 170 # On deploy this will load our app, by moving it into the incoming dir 171 # If the bundle content changes, this will run again. 172 # Because the bundle content contains the nix path to the executable, 173 # we inherit nix based cache busting. 174 systemd.services.load-keter-bundle = { 175 description = "load keter bundle into incoming folder"; 176 after = [ "keter.service" ]; 177 wantedBy = [ "multi-user.target" ]; 178 # we can't override keter bundles because it'll stop the previous app 179 # https://github.com/snoyberg/keter#deploying 180 script = '' 181 set -xe 182 cp ${bundle}/bundle.tar.gz.keter ${incoming}/${cfg.bundle.appName}.keter 183 ''; 184 path = [ 185 executable 186 cfg.bundle.executable 187 ]; # this is a hack to get the executable copied over to the machine. 188 }; 189 } 190 ); 191}