about summary refs log tree commit diff
path: root/nixpkgs/nixos/modules/services/web-servers/keter/default.nix
blob: 9adbe65de69fb803299fd95e0553835e732304c5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
{ config, pkgs, lib, ... }:
let
  cfg = config.services.keter;
in
{
  meta = {
    maintainers = with lib.maintainers; [ jappie ];
  };

  options.services.keter = {
    enable = lib.mkEnableOption (lib.mdDoc ''keter, a web app deployment manager.
Note that this module only support loading of webapps:
Keep an old app running and swap the ports when the new one is booted.
'');

    keterRoot = lib.mkOption {
      type = lib.types.str;
      default = "/var/lib/keter";
      description = lib.mdDoc "Mutable state folder for keter";
    };

    keterPackage = lib.mkOption {
      type = lib.types.package;
      default = pkgs.haskellPackages.keter;
      defaultText = lib.literalExpression "pkgs.haskellPackages.keter";
      description = lib.mdDoc "The keter package to be used";
    };

    globalKeterConfig = lib.mkOption {
      type = lib.types.attrs;
      default = {
        ip-from-header = true;
        listeners = [{
          host = "*4";
          port = 6981;
        }];
      };
      # You want that ip-from-header in the nginx setup case
      # so it's not set to 127.0.0.1.
      # using a port above 1024 allows you to avoid needing CAP_NET_BIND_SERVICE
      defaultText = lib.literalExpression ''
        {
          ip-from-header = true;
          listeners = [{
            host = "*4";
            port = 6981;
          }];
        }
      '';
      description = lib.mdDoc "Global config for keter";
    };

    bundle = {
      appName = lib.mkOption {
        type = lib.types.str;
        default = "myapp";
        description = lib.mdDoc "The name keter assigns to this bundle";
      };

      executable = lib.mkOption {
        type = lib.types.path;
        description = lib.mdDoc "The executable to be run";
      };

      domain = lib.mkOption {
        type = lib.types.str;
        default = "example.com";
        description = lib.mdDoc "The domain keter will bind to";
      };

      publicScript = lib.mkOption {
        type = lib.types.str;
        default = "";
        description = lib.mdDoc ''
          Allows loading of public environment variables,
          these are emitted to the log so it shouldn't contain secrets.
        '';
        example = "ADMIN_EMAIL=hi@example.com";
      };

      secretScript = lib.mkOption {
        type = lib.types.str;
        default = "";
        description = lib.mdDoc "Allows loading of private environment variables";
        example = "MY_AWS_KEY=$(cat /run/keys/AWS_ACCESS_KEY_ID)";
      };
    };

  };

  config = lib.mkIf cfg.enable (
    let
      incoming = "${cfg.keterRoot}/incoming";


      globalKeterConfigFile = pkgs.writeTextFile {
        name = "keter-config.yml";
        text = (lib.generators.toYAML { } (cfg.globalKeterConfig // { root = cfg.keterRoot; }));
      };

      # If things are expected to change often, put it in the bundle!
      bundle = pkgs.callPackage ./bundle.nix
        (cfg.bundle // { keterExecutable = executable; keterDomain = cfg.bundle.domain; });

      # This indirection is required to ensure the nix path
      # gets copied over to the target machine in remote deployments.
      # Furthermore, it's important that we use exec to
      # run the binary otherwise we get process leakage due to this
      # being executed on every change.
      executable = pkgs.writeShellScript "bundle-wrapper" ''
        set -e
        ${cfg.bundle.secretScript}
        set -xe
        ${cfg.bundle.publicScript}
        exec ${cfg.bundle.executable}
      '';

    in
    {
      systemd.services.keter = {
        description = "keter app loader";
        script = ''
          set -xe
          mkdir -p ${incoming}
          { tail -F ${cfg.keterRoot}/log/keter/current.log -n 0 & ${cfg.keterPackage}/bin/keter ${globalKeterConfigFile}; }
        '';
        wantedBy = [ "multi-user.target" "nginx.service" ];

        serviceConfig = {
          Restart = "always";
          RestartSec = "10s";
        };

        after = [
          "network.target"
          "local-fs.target"
          "postgresql.service"
        ];
      };

      # On deploy this will load our app, by moving it into the incoming dir
      # If the bundle content changes, this will run again.
      # Because the bundle content contains the nix path to the executable,
      # we inherit nix based cache busting.
      systemd.services.load-keter-bundle = {
        description = "load keter bundle into incoming folder";
        after = [ "keter.service" ];
        wantedBy = [ "multi-user.target" ];
        # we can't override keter bundles because it'll stop the previous app
        # https://github.com/snoyberg/keter#deploying
        script = ''
          set -xe
          cp ${bundle}/bundle.tar.gz.keter ${incoming}/${cfg.bundle.appName}.keter
        '';
        path = [
          executable
          cfg.bundle.executable
        ]; # this is a hack to get the executable copied over to the machine.
      };
    }
  );
}