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