summaryrefslogtreecommitdiffstats
path: root/krebs/3modules/cachecache.nix
blob: 2c2d07ff5ddf54fbf8d180371f88c20aa7d8f39b (plain)
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
163
164
165
166
167
168
169
170
171
{ pkgs, config, lib, ... }:


# fork of https://gist.github.com/rycee/f495fc6cc4130f155e8b670609a1e57b
# related: https://github.com/nh2/nix-binary-cache-proxy

with lib;

let

  cfg = config.krebs.cachecache;

  nginxCfg = config.services.nginx;

  cacheFallbackConfig = {
    proxyPass = "$upstream_endpoint";
    extraConfig = ''
      # Default is HTTP/1, keepalive is only enabled in HTTP/1.1.
      proxy_http_version 1.1;

      # Remove the Connection header if the client sends it, it could
      # be "close" to close a keepalive connection
      proxy_set_header Connection "";

      # Needed for CloudFront.
      proxy_ssl_server_name on;

      proxy_set_header Host $proxy_host;
      proxy_cache nix_cache_cache;
      proxy_cache_valid 200 302 60m;
      proxy_cache_valid 404 1m;

      expires max;
      add_header Cache-Control $nix_cache_cache_header always;
    '';
  };

in

{
  options = {
    krebs.cachecache = {
      enable = mkEnableOption "Nix binary cache cache";

      virtualHost = mkOption {
        type = types.str;
        default = "nix-cache";
        description = ''
          Name of the nginx virtualhost to use and setup. If null, do
          not setup any virtualhost.
        '';
      };
      enableSSL = mkOption {
        type = types.bool;
        default = true;
        description = ''
          enable SSL via letsencrypt. Requires working dns resolution and open
          internet tls port.
        '';
      };

      resolver = mkOption {
        type = types.str;
        description = "Address of DNS resolver.";
        default = "8.8.8.8 ipv6=off";
        example = "127.0.0.1 ipv6=off";
      };

      cacheDir = mkOption {
        type = types.str;
        default = "/var/cache/nix-cache-cache";
        description = ''
          Where nginx should store cached data.
        '';
      };
      indexFile = mkOption {
        type = types.path;
        default = pkgs.writeText "myindex" "<html>hello world</html>";
        description = ''
          Path to index.html file.
        '';
      };

      maxSize = mkOption {
        type = types.str;
        default = "50g";
        description = "Maximum cache size.";
      };
    };
  };

  config = mkIf cfg.enable {
    networking.firewall.allowedTCPPorts = [ 80 443 ];


    systemd.services.nginx.preStart = ''
      mkdir -p ${cfg.cacheDir} /srv/www/nix-cache-cache
      chmod 700 ${cfg.cacheDir} /srv/www/nix-cache-cache
      ln -fs ${cfg.indexFile} /srv/www/nix-cache-cache/index.html
      chown ${nginxCfg.user}:${nginxCfg.group} \
        ${cfg.cacheDir} /srv/www/nix-cache-cache
    '';

    services.nginx = {
      enable = true;

      appendHttpConfig = ''
        proxy_cache_path ${cfg.cacheDir}
          levels=1:2
          keys_zone=nix_cache_cache:100m
          max_size=${cfg.maxSize}
          inactive=365d
          use_temp_path=off;

        # Cache only success status codes; in particular we don't want
        # to cache 404s. See https://serverfault.com/a/690258/128321.
        map $status $nix_cache_cache_header {
          200     "public";
          302     "public";
          default "no-cache";
        }
      '';

      virtualHosts.${cfg.virtualHost} = {
        addSSL = cfg.enableSSL;
        enableACME = cfg.enableSSL;
        extraConfig = ''
          # Using a variable for the upstream endpoint to ensure that it is
          # resolved at runtime as opposed to once when the config file is loaded
          # and then cached forever (we don't want that):
          # see https://tenzer.dk/nginx-with-dynamic-upstreams/
          # This fixes errors like
          #
          #   nginx: [emerg] host not found in upstream "upstream.example.com"
          #
          # when the upstream host is not reachable for a short time when
          # nginx is started.
          resolver ${cfg.resolver} valid=10s;
          set $upstream_endpoint https://cache.nixos.org;
        '';

        locations."/" =
        {
          root = "/srv/www/nix-cache-cache";
          index = "index.html";
          extraConfig = ''
            expires max;
            add_header Cache-Control $nix_cache_cache_header always;

            # Ask the upstream server if a file isn't available
            # locally.
            error_page 404 = @fallback;

            # Don't bother logging the above 404.
            log_not_found off;
          '';
        };

        locations."@fallback" = cacheFallbackConfig;

        # We always want to copy cache.nixos.org's nix-cache-info
        # file, and ignore our own, because `nix-push` by default
        # generates one without `Priority` field, and thus that file
        # by default has priority 50 (compared to cache.nixos.org's
        # `Priority: 40`), which will make download clients prefer
        # `cache.nixos.org` over our binary cache.
        locations."= /nix-cache-info" = cacheFallbackConfig;
      };
    };
  };
}