4 Commits

Author SHA1 Message Date
  Astro 1eeb24a2e2 nixos-module/container/dnscache: use all the dn42 ns 1 month ago
  Astro 911cd33a1c nixos-module/container/dnscache: extend domain-insecure, remove all local-zone 1 month ago
  Astro a45fb3c484 nixos-module/container/dnscache: use data from config.site.dns.localZones 1 month ago
  Astro b800691dad nixos-module/container/dns: factor zones out into config.site.dns.localZones 1 month ago
2 changed files with 245 additions and 209 deletions
  1. 209
    163
      nix/nixos-module/container/dns.nix
  2. 36
    46
      nix/nixos-module/container/dnscache.nix

+ 209
- 163
nix/nixos-module/container/dns.nix View File

@@ -1,180 +1,182 @@
1 1
 { hostName, config, lib, pkgs, self, ... }:
2 2
 
3
-lib.mkIf config.site.hosts.${hostName}.services.dns.enable {
4
-  services.bind =
3
+let
4
+  fqdn = "${hostName}.serv.zentralwerk.org";
5
+  # public servers (slaves)
6
+  publicNS = [ "ns.c3d2.de" "ns.spaceboyz.net" ];
7
+in
8
+{
9
+  options =
10
+    with lib;
5 11
     let
6
-      fqdn = "${hostName}.serv.zentralwerk.org";
7
-      # public servers (slaves)
8
-      publicNS = [ "ns.c3d2.de" "ns.spaceboyz.net" ];
9
-      # allowed for zone-transfer
10
-      slaves = [
11
-        # ns.c3d2.de
12
-        "217.197.84.53" "2001:67c:1400:2240::a"
13
-        # ns.spaceboyz.net
14
-        "172.22.24.4" "2a01:4f9:4b:39ec::4"
15
-      ];
16
-      # ip6.arpa aggregation size in CIDR bits
17
-      reverseZone6Size = 60;
18
-
19
-      serial =
20
-        let
21
-          timestamp = toString self.lastModified;
22
-          datePkg = pkgs.runCommandLocal "date-${timestamp}" {} ''
23
-            date -d @${timestamp} +%Y%m%d%H > $out
24
-          '';
25
-        in
26
-          toString (import datePkg);
27
-
28
-      staticZone = { name, ns, records }: {
29
-        inherit name;
30
-        master = true;
31
-        file = builtins.toFile "${name}.zone" ''
32
-          $ORIGIN ${name}.
33
-          $TTL 1h
12
+      recordOpts = {
13
+        name = mkOption {
14
+          description = "DNS label";
15
+          type = types.str;
16
+        };
17
+        type = mkOption {
18
+          type = types.enum [ "A" "AAAA" "PTR" ];
19
+        };
20
+        data = mkOption {
21
+          type = types.str;
22
+        };
23
+      };
34 24
 
35
-          @ IN SOA ${fqdn}. astro.spaceboyz.net. (
36
-              ${serial} ; serial
37
-              1h ; refresh
38
-              1m ; retry
39
-              2h ; expire
40
-              1m ; minimum
41
-            )
42
-          ${lib.concatMapStrings (ns: "  IN NS ${ns}.\n") ns}
25
+      zoneOpts = {
26
+        name = mkOption {
27
+          description = "DNS FQDN w/o trailing dot";
28
+          type = types.str;
29
+        };
30
+        ns = mkOption {
31
+          type = with types; listOf str;
32
+        };
33
+        records = mkOption {
34
+          type = with types; listOf (submodule {
35
+            options = recordOpts;
36
+          });
37
+        };
38
+      };
43 39
 
44
-          ${lib.concatMapStrings ({ name, type, data }:
45
-            "${name} IN ${type} ${data}\n"
46
-          ) records}
47
-        '';
40
+    in {
41
+      site.dns.localZones = mkOption {
42
+        type = with types; listOf (submodule {
43
+          options = zoneOpts;
44
+        });
48 45
       };
46
+    };
49 47
 
50
-      hosts4Records = hosts4:
51
-        builtins.attrValues (
52
-          builtins.mapAttrs (name: addr: {
53
-            inherit name;
54
-            type = "A";
55
-            data = addr;
56
-          }) hosts4
57
-        );
58
-      hosts6Records = hosts6:
59
-        builtins.attrValues (
60
-          builtins.mapAttrs (name: addr: {
61
-            inherit name;
62
-            type = "AAAA";
63
-            data = addr;
64
-          }) hosts6
65
-        );
48
+  config = {
49
+    site.dns.localZones =
50
+      let
51
+        # ip6.arpa aggregation size in CIDR bits
52
+        reverseZone6Size = 60;
66 53
 
67
-      # generate zones only for nets with hosts
68
-      namedNets = lib.filterAttrs (_: { hosts4, hosts6, dynamicDomain, ... }:
69
-        (hosts4 != [] && hosts6 != []) ||
70
-        dynamicDomain
71
-      ) config.site.net;
54
+        hosts4Records = hosts4:
55
+          builtins.attrValues (
56
+            builtins.mapAttrs (name: addr: {
57
+              inherit name;
58
+              type = "A";
59
+              data = addr;
60
+            }) hosts4
61
+          );
62
+        hosts6Records = hosts6:
63
+          builtins.attrValues (
64
+            builtins.mapAttrs (name: addr: {
65
+              inherit name;
66
+              type = "AAAA";
67
+              data = addr;
68
+            }) hosts6
69
+          );
72 70
 
73
-      # converts an IPv4 address to its reverse DNS form
74
-      ipv4ToReverse = ipv4:
75
-        builtins.concatStringsSep "." (
76
-          lib.reverseList (
77
-            builtins.filter builtins.isString (
78
-              builtins.split "\\." ipv4
71
+        # generate zones only for nets with hosts
72
+        namedNets = lib.filterAttrs (_: { hosts4, hosts6, dynamicDomain, ... }:
73
+          (hosts4 != [] && hosts6 != []) ||
74
+          dynamicDomain
75
+        ) config.site.net;
76
+
77
+        # converts an IPv4 address to its reverse DNS form
78
+        ipv4ToReverse = ipv4:
79
+          builtins.concatStringsSep "." (
80
+            lib.reverseList (
81
+              builtins.filter builtins.isString (
82
+                builtins.split "\\." ipv4
83
+              )
79 84
             )
80
-          )
81
-        ) + ".in-addr.arpa";
85
+          ) + ".in-addr.arpa";
82 86
 
83
-      # `{ "1,0.0.127.in-addr.arpa" = "lo.core.zentralwerk.dn42"; }`
84
-      reverseHosts4 = builtins.foldl' (result: { hosts4, domainName, ... }:
85
-        builtins.foldl' (result: host: result // {
86
-          "${ipv4ToReverse hosts4.${host}}" = "${host}.${domainName}";
87
-        }) result (builtins.attrNames hosts4)
88
-      ) {} (builtins.attrValues namedNets);
87
+        # `{ "1,0.0.127.in-addr.arpa" = "lo.core.zentralwerk.dn42"; }`
88
+        reverseHosts4 = builtins.foldl' (result: { hosts4, domainName, ... }:
89
+          builtins.foldl' (result: host: result // {
90
+            "${ipv4ToReverse hosts4.${host}}" = "${host}.${domainName}";
91
+          }) result (builtins.attrNames hosts4)
92
+        ) {} (builtins.attrValues namedNets);
89 93
 
90
-      # `[ "0.0.127.in-addr.arpa" ]`
91
-      reverseZones4 = builtins.attrNames (
92
-        builtins.foldl' (result: rname:
93
-          let
94
-            zone = builtins.head (
95
-              builtins.match "[[:digit:]]+\\.(.+)" rname
96
-            );
97
-          in result // {
98
-            "${zone}" = true;
99
-          }
100
-        ) {} (builtins.attrNames reverseHosts4)
101
-      );
94
+        # `[ "0.0.127.in-addr.arpa" ]`
95
+        reverseZones4 = builtins.attrNames (
96
+          builtins.foldl' (result: rname:
97
+            let
98
+              zone = builtins.head (
99
+                builtins.match "[[:digit:]]+\\.(.+)" rname
100
+              );
101
+            in result // {
102
+              "${zone}" = true;
103
+            }
104
+          ) {} (builtins.attrNames reverseHosts4)
105
+        );
102 106
 
103
-      # turns `::` into `0000:0000:0000:0000:0000:0000:0000:0000`
104
-      expandIpv6 = ipv6:
105
-        if lib.hasPrefix "::" ipv6
106
-        then expandIpv6 "0${ipv6}"
107
+        # turns `::` into `0000:0000:0000:0000:0000:0000:0000:0000`
108
+        expandIpv6 = ipv6:
109
+          if lib.hasPrefix "::" ipv6
110
+          then expandIpv6 "0${ipv6}"
107 111
 
108
-        else if lib.hasSuffix "::" ipv6
109
-        then expandIpv6 "${ipv6}0"
112
+          else if lib.hasSuffix "::" ipv6
113
+          then expandIpv6 "${ipv6}0"
110 114
 
111
-        else let
112
-          words = builtins.filter builtins.isString (
113
-            builtins.split ":" ipv6
114
-          );
115
-          fillWordCount = 8 - builtins.length words;
116
-          fillWords = n:
117
-            if n >= 0
118
-            then [ "0000" ] ++ fillWords (n - 1)
119
-            else [];
120
-          words' = builtins.concatMap (word:
121
-            if word == ""
122
-            then fillWords fillWordCount
123
-            else [ word ]
124
-          ) words;
125
-          leftPad = padding: target: s:
126
-            if builtins.stringLength s < target
127
-            then leftPad padding target "${padding}${s}"
128
-            else s;
129
-          words'' = map (leftPad "0" 4) words';
130
-        in
131
-          builtins.concatStringsSep ":" words'';
115
+          else let
116
+            words = builtins.filter builtins.isString (
117
+              builtins.split ":" ipv6
118
+            );
119
+            fillWordCount = 8 - builtins.length words;
120
+            fillWords = n:
121
+              if n >= 0
122
+              then [ "0000" ] ++ fillWords (n - 1)
123
+              else [];
124
+            words' = builtins.concatMap (word:
125
+              if word == ""
126
+              then fillWords fillWordCount
127
+              else [ word ]
128
+            ) words;
129
+            leftPad = padding: target: s:
130
+              if builtins.stringLength s < target
131
+              then leftPad padding target "${padding}${s}"
132
+              else s;
133
+            words'' = map (leftPad "0" 4) words';
134
+          in
135
+            builtins.concatStringsSep ":" words'';
132 136
 
133
-      # turns `::1` into `1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa`
134
-      ipv6ToReverse = ipv6:
135
-        builtins.concatStringsSep "." (
136
-          lib.reverseList (
137
-            lib.stringToCharacters (
138
-              builtins.replaceStrings [":"] [""] (expandIpv6 ipv6)
137
+        # turns `::1` into `1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa`
138
+        ipv6ToReverse = ipv6:
139
+          builtins.concatStringsSep "." (
140
+            lib.reverseList (
141
+              lib.stringToCharacters (
142
+                builtins.replaceStrings [":"] [""] (expandIpv6 ipv6)
143
+              )
139 144
             )
140
-          )
141
-        ) + ".ip6.arpa";
145
+          ) + ".ip6.arpa";
142 146
 
143
-      # `{ dn42 = { "...ip6.arpa" = "lo.core.zentralwerk.dn42"; }; }`
144
-      reverseHosts6 = builtins.foldl' (result: net: lib.recursiveUpdate result (
145
-        builtins.mapAttrs (ctx: hosts:
146
-          builtins.foldl' (result: host:
147
-            let
148
-              domain =
149
-                if ctx == "dn42"
150
-                then namedNets.${net}.domainName
151
-                else if builtins.match "up.*" ctx != null
152
-                then "${net}.zentralwerk.org"
153
-                else throw "Invalid IPv6 context: ${ctx}";
154
-            in
155
-              lib.recursiveUpdate result {
156
-                "${ipv6ToReverse hosts.${host}}" = "${host}.${domain}";
157
-              }
158
-          ) {} (builtins.attrNames hosts)
159
-        ) namedNets.${net}.hosts6
160
-      )) {} (builtins.attrNames namedNets);
147
+        # `{ dn42 = { "...ip6.arpa" = "lo.core.zentralwerk.dn42"; }; }`
148
+        reverseHosts6 = builtins.foldl' (result: net: lib.recursiveUpdate result (
149
+          builtins.mapAttrs (ctx: hosts:
150
+            builtins.foldl' (result: host:
151
+              let
152
+                domain =
153
+                  if ctx == "dn42"
154
+                  then namedNets.${net}.domainName
155
+                  else if builtins.match "up.*" ctx != null
156
+                  then "${net}.zentralwerk.org"
157
+                  else throw "Invalid IPv6 context: ${ctx}";
158
+              in
159
+                lib.recursiveUpdate result {
160
+                  "${ipv6ToReverse hosts.${host}}" = "${host}.${domain}";
161
+                }
162
+            ) {} (builtins.attrNames hosts)
163
+          ) namedNets.${net}.hosts6
164
+        )) {} (builtins.attrNames namedNets);
161 165
 
162
-      # `{ dn42 = [ "....ip6.arpa" ]; }`
163
-      reverseZones6 = builtins.mapAttrs (ctx: reverseHosts6ctx:
164
-        builtins.attrNames (
165
-          builtins.foldl' (result: rname: result // {
166
-            "${builtins.substring ((128 - reverseZone6Size) / 2) (72 - ((128 - reverseZone6Size) / 2)) rname}" = true;
167
-          }) {} (builtins.attrNames reverseHosts6ctx)
168
-        )
169
-      ) reverseHosts6;
166
+        # `{ dn42 = [ "....ip6.arpa" ]; }`
167
+        reverseZones6 = builtins.mapAttrs (ctx: reverseHosts6ctx:
168
+          builtins.attrNames (
169
+            builtins.foldl' (result: rname: result // {
170
+              "${builtins.substring ((128 - reverseZone6Size) / 2) (72 - ((128 - reverseZone6Size) / 2)) rname}" = true;
171
+            }) {} (builtins.attrNames reverseHosts6ctx)
172
+          )
173
+        ) reverseHosts6;
170 174
 
171
-    in {
172
-      enable = true;
173
-      zones = [ (staticZone {
175
+      in [ {
174 176
         name = "zentralwerk.org";
175 177
         ns = publicNS;
176 178
         records = [];
177
-      }) (staticZone {
179
+      } {
178 180
         name = "zentralwerk.dn42";
179 181
         ns = [ fqdn ];
180 182
         records = [ {
@@ -182,7 +184,7 @@ lib.mkIf config.site.hosts.${hostName}.services.dns.enable {
182 184
           type = "A";
183 185
           data = config.site.net.serv.hosts4.ipa;
184 186
         } ];
185
-      }) (staticZone {
187
+      } {
186 188
         name = "dyn.zentralwerk.org";
187 189
         ns = publicNS;
188 190
         # TODO: implement dyndns
@@ -195,28 +197,28 @@ lib.mkIf config.site.hosts.${hostName}.services.dns.enable {
195 197
           type = "A";
196 198
           data = "24.134.252.105";
197 199
         } ];
198
-      }) ] ++ builtins.concatLists (
200
+      } ] ++ builtins.concatLists (
199 201
         builtins.attrValues (
200 202
           builtins.mapAttrs (net: { dynamicDomain, hosts4, hosts6, ... }: [
201 203
             (if dynamicDomain
202 204
              then throw "TODO"
203
-             else staticZone {
205
+             else {
204 206
                name = "${net}.zentralwerk.dn42";
205 207
                ns = [ fqdn ];
206 208
                records =
207 209
                  hosts4Records hosts4 ++
208 210
                  lib.optionals (hosts6 ? dn42) (hosts6Records hosts6.dn42);
209 211
              })
210
-            (staticZone {
212
+            {
211 213
               name = "${net}.zentralwerk.org";
212 214
               ns = publicNS;
213 215
               records =
214 216
                 lib.optionals (hosts6 ? up1) (hosts6Records hosts6.up1) ++
215 217
                 lib.optionals (hosts6 ? up2) (hosts6Records hosts6.up2);
216
-            })
218
+            }
217 219
           ]) namedNets
218 220
         )
219
-      ) ++ map (zone: staticZone {
221
+      ) ++ map (zone: {
220 222
         name = zone;
221 223
         ns = [ fqdn ];
222 224
         records =
@@ -232,7 +234,7 @@ lib.mkIf config.site.hosts.${hostName}.services.dns.enable {
232 234
           );
233 235
       }) reverseZones4
234 236
       ++ builtins.concatMap (ctx:
235
-        map (zone: staticZone {
237
+        map (zone: {
236 238
           name = zone;
237 239
           ns =
238 240
             if ctx == "dn42"
@@ -251,7 +253,51 @@ lib.mkIf config.site.hosts.${hostName}.services.dns.enable {
251 253
             );
252 254
         }) reverseZones6.${ctx}
253 255
       ) (builtins.attrNames reverseZones6);
254
-    };
255 256
 
256
-  # TODO: dyn
257
+    services.bind = lib.mkIf config.site.hosts.${hostName}.services.dns.enable (
258
+      let
259
+        serial =
260
+          let
261
+            timestamp = toString self.lastModified;
262
+            datePkg = pkgs.runCommandLocal "date-${timestamp}" {} ''
263
+            date -d @${timestamp} +%Y%m%d%H > $out
264
+          '';
265
+          in
266
+            toString (import datePkg);
267
+
268
+        generateZone = { name, ns, records }: {
269
+          inherit name;
270
+          master = true;
271
+          # allowed for zone-transfer
272
+          slaves = [
273
+            # ns.c3d2.de
274
+            "217.197.84.53" "2001:67c:1400:2240::a"
275
+            # ns.spaceboyz.net
276
+            "172.22.24.4" "2a01:4f9:4b:39ec::4"
277
+          ];
278
+          file = builtins.toFile "${name}.zone" ''
279
+            $ORIGIN ${name}.
280
+            $TTL 1h
281
+
282
+            @ IN SOA ${fqdn}. astro.spaceboyz.net. (
283
+                ${serial} ; serial
284
+                1h ; refresh
285
+                1m ; retry
286
+                2h ; expire
287
+                1m ; minimum
288
+            )
289
+            ${lib.concatMapStrings (ns: "  IN NS ${ns}.\n") ns}
290
+
291
+            ${lib.concatMapStrings ({ name, type, data }:
292
+              "${name} IN ${type} ${data}\n"
293
+            ) records}
294
+          '';
295
+        };
296
+      in {
297
+        enable = true;
298
+        zones = map generateZone config.site.dns.localZones;
299
+      });
300
+
301
+    # TODO: dyn
302
+  };
257 303
 }

+ 36
- 46
nix/nixos-module/container/dnscache.nix View File

@@ -39,23 +39,18 @@ lib.mkIf config.site.hosts.${hostName}.services.dnscache.enable {
39 39
         insecure-lan-zones: yes
40 40
 
41 41
         domain-insecure: "dn42"
42
-        domain-insecure: "20.172.in-addr.arpa"
43
-        domain-insecure: "21.172.in-addr.arpa"
44
-        domain-insecure: "22.172.in-addr.arpa"
45
-        domain-insecure: "99.22.172.in-addr.arpa"
46
-        domain-insecure: "23.172.in-addr.arpa"
42
+        domain-insecure: "10.in-addr.arpa"
43
+      ${lib.concatMapStrings (x:
44
+        "  domain-insecure: ${toString x}.172.in-addr.arpa\n"
45
+      ) [
46
+        16 17 18 19
47
+        20 21 22 23
48
+        24 25 26 27
49
+        28 29 30 31
50
+      ]}
51
+        domain-insecure: "168.192.in-addr.arpa"
47 52
         domain-insecure: "d.f.ip6.arpa"
48 53
         domain-insecure: "ffdd"
49
-        domain-insecure: "200.10.in-addr.arpa"
50
-        domain-insecure: "201.10.in-addr.arpa"
51
-        local-zone: "20.172.in-addr.arpa." nodefault
52
-        local-zone: "21.172.in-addr.arpa." nodefault
53
-        local-zone: "22.172.in-addr.arpa." nodefault
54
-        local-zone: "99.22.172.in-addr.arpa." nodefault
55
-        local-zone: "23.172.in-addr.arpa." nodefault
56
-        local-zone: "d.f.ip6.arpa." nodefault
57
-        local-zone: "200.10.in-addr.arpa." nodefault
58
-        local-zone: "201.10.in-addr.arpa." nodefault
59 54
 
60 55
       forward-zone:
61 56
         name: "."
@@ -73,37 +68,14 @@ lib.mkIf config.site.hosts.${hostName}.services.dnscache.enable {
73 68
 
74 69
       # Local networks
75 70
 
76
-      forward-zone:
77
-        name: "zentralwerk.dn42"
78
-        forward-host: "dns.serv.zentralwerk.org"
79
-
80
-      forward-zone:
81
-        name: "72.20.172.in-addr.arpa"
82
-        forward-host: "dns.serv.zentralwerk.org"
83
-
84
-      forward-zone:
85
-        name: "73.20.172.in-addr.arpa"
86
-        forward-host: "dns.serv.zentralwerk.org"
87
-
88
-      forward-zone:
89
-        name: "74.20.172.in-addr.arpa"
90
-        forward-host: "dns.serv.zentralwerk.org"
91
-
92
-      forward-zone:
93
-        name: "75.20.172.in-addr.arpa"
94
-        forward-host: "dns.serv.zentralwerk.org"
95
-
96
-      forward-zone:
97
-        name: "76.20.172.in-addr.arpa"
98
-        forward-host: "dns.serv.zentralwerk.org"
99
-
100
-      forward-zone:
101
-        name: "77.20.172.in-addr.arpa"
102
-        forward-host: "dns.serv.zentralwerk.org"
103
-
104
-      forward-zone:
105
-        name: "0.0.5.0.2.d.3.c.4.2.0.0.3.2.d.f.ip6.arpa"
106
-        forward-host: "dns.serv.zentralwerk.org"
71
+      ${lib.concatMapStrings ({ name, ... }: ''
72
+        forward-zone:
73
+          name: "${name}"
74
+          forward-host: "${config.site.net.serv.hosts4.dns}"
75
+        ${lib.concatMapStrings (hosts6:
76
+          "  forward-host: ${hosts6.dns}\n"
77
+        ) (builtins.attrValues config.site.net.serv.hosts6)}
78
+      '') config.site.dns.localZones}
107 79
 
108 80
       # C3D2 reverse
109 81
 
@@ -133,32 +105,50 @@ lib.mkIf config.site.hosts.${hostName}.services.dnscache.enable {
133 105
       stub-zone:
134 106
         name: "dn42"
135 107
         stub-prime: yes
108
+        stub-addr: 172.20.0.53
109
+        stub-addr: fd42:d42:d42:54::1
136 110
         stub-addr: 172.23.0.53
111
+        stub-addr: fd42:d42:d42:53::1
137 112
 
138 113
       stub-zone:
139 114
         name: "20.172.in-addr.arpa"
140 115
         stub-prime: yes
116
+        stub-addr: 172.20.0.53
117
+        stub-addr: fd42:d42:d42:54::1
141 118
         stub-addr: 172.23.0.53
119
+        stub-addr: fd42:d42:d42:53::1
142 120
 
143 121
       stub-zone:
144 122
         name: "21.172.in-addr.arpa"
145 123
         stub-prime: yes
124
+        stub-addr: 172.20.0.53
125
+        stub-addr: fd42:d42:d42:54::1
146 126
         stub-addr: 172.23.0.53
127
+        stub-addr: fd42:d42:d42:53::1
147 128
 
148 129
       stub-zone:
149 130
         name: "22.172.in-addr.arpa"
150 131
         stub-prime: yes
132
+        stub-addr: 172.20.0.53
133
+        stub-addr: fd42:d42:d42:54::1
151 134
         stub-addr: 172.23.0.53
135
+        stub-addr: fd42:d42:d42:53::1
152 136
 
153 137
       stub-zone:
154 138
         name: "23.172.in-addr.arpa"
155 139
         stub-prime: yes
140
+        stub-addr: 172.20.0.53
141
+        stub-addr: fd42:d42:d42:54::1
156 142
         stub-addr: 172.23.0.53
143
+        stub-addr: fd42:d42:d42:53::1
157 144
 
158 145
       stub-zone:
159 146
         name: "d.f.ip6.arpa"
160 147
         stub-prime: yes
148
+        stub-addr: 172.20.0.53
149
+        stub-addr: fd42:d42:d42:54::1
161 150
         stub-addr: 172.23.0.53
151
+        stub-addr: fd42:d42:d42:53::1
162 152
     '';
163 153
   };
164 154
 }

Loading…
Cancel
Save