1 { config, pkgs, lib, ... }:
3 cfg = config.services.keter;
4 yaml = pkgs.formats.yaml { };
8 maintainers = with lib.maintainers; [ jappie ];
12 (lib.mkRenamedOptionModule [ "services" "keter" "keterRoot" ] [ "services" "keter" "root" ])
13 (lib.mkRenamedOptionModule [ "services" "keter" "keterPackage" ] [ "services" "keter" "package" ])
16 options.services.keter = {
17 enable = lib.mkEnableOption (lib.mdDoc ''keter, a web app deployment manager.
18 Note that this module only support loading of webapps:
19 Keep an old app running and swap the ports when the new one is booted
24 default = "/var/lib/keter";
25 description = lib.mdDoc "Mutable state folder for keter";
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";
36 globalKeterConfig = lib.mkOption {
37 type = lib.types.submodule {
38 freeformType = yaml.type;
40 ip-from-header = lib.mkOption {
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)";
45 listeners = lib.mkOption {
46 default = [{ host = "*"; port = 6981; }];
47 type = lib.types.listOf (lib.types.submodule {
51 description = lib.mdDoc "host";
54 type = lib.types.port;
55 description = lib.mdDoc "port";
59 description = lib.mdDoc ''
60 You want that ip-from-header in
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.'';
67 rotate-logs = lib.mkOption {
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)'';
79 description = lib.mdDoc "Global config for keter, see <https://github.com/snoyberg/keter/blob/master/etc/keter-config.yaml> for reference";
83 appName = lib.mkOption {
86 description = lib.mdDoc "The name keter assigns to this bundle";
89 executable = lib.mkOption {
90 type = lib.types.path;
91 description = lib.mdDoc "The executable to be run";
94 domain = lib.mkOption {
96 default = "example.com";
97 description = lib.mdDoc "The domain keter will bind to";
100 publicScript = lib.mkOption {
101 type = lib.types.str;
103 description = lib.mdDoc ''
104 Allows loading of public environment variables,
105 these are emitted to the log so it shouldn't contain secrets.
107 example = "ADMIN_EMAIL=hi@example.com";
110 secretScript = lib.mkOption {
111 type = lib.types.str;
113 description = lib.mdDoc "Allows loading of private environment variables";
114 example = "MY_AWS_KEY=$(cat /run/keys/AWS_ACCESS_KEY_ID)";
120 config = lib.mkIf cfg.enable (
122 incoming = "${cfg.root}/incoming";
125 globalKeterConfigFile = pkgs.writeTextFile {
126 name = "keter-config.yml";
127 text = (lib.generators.toYAML { } (cfg.globalKeterConfig // { root = cfg.root; }));
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; });
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" ''
141 ${cfg.bundle.secretScript}
143 ${cfg.bundle.publicScript}
144 exec ${cfg.bundle.executable}
149 systemd.services.keter = {
150 description = "keter app loader";
154 ${lib.getExe cfg.package} ${globalKeterConfigFile};
156 wantedBy = [ "multi-user.target" "nginx.service" ];
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
182 cp ${bundle}/bundle.tar.gz.keter ${incoming}/${cfg.bundle.appName}.keter
186 cfg.bundle.executable
187 ]; # this is a hack to get the executable copied over to the machine.