diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2019-10-18 19:43:39 +0200 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2020-04-25 00:04:29 +0200 |
commit | d8a4e41a41664030ffece882e98694883e377b06 (patch) | |
tree | e9bb70980a3fdf9663f4d53edd378450a48b34a5 /overlays | |
parent | 245acb2459476f63db7311472ceb397f709c7671 (diff) | |
download | NUR-d8a4e41a41664030ffece882e98694883e377b06.tar.gz NUR-d8a4e41a41664030ffece882e98694883e377b06.tar.zst NUR-d8a4e41a41664030ffece882e98694883e377b06.zip |
Add new machine to nixops
Diffstat (limited to 'overlays')
-rw-r--r-- | overlays/environments/immae-eu.nix | 2 | ||||
-rw-r--r-- | overlays/nixops/default.nix | 1 | ||||
-rw-r--r-- | overlays/nixops/hetzner_cloud.patch | 480 |
3 files changed, 482 insertions, 1 deletions
diff --git a/overlays/environments/immae-eu.nix b/overlays/environments/immae-eu.nix index db1caa4d..cc2e5c3f 100644 --- a/overlays/environments/immae-eu.nix +++ b/overlays/environments/immae-eu.nix | |||
@@ -63,7 +63,7 @@ let | |||
63 | newsboat irssi | 63 | newsboat irssi |
64 | 64 | ||
65 | # nix | 65 | # nix |
66 | mylibs.yarn2nixPackage.yarn2nix | 66 | mylibs.yarn2nixPackage.yarn2nix nix |
67 | nixops nix-prefetch-scripts nix-generate-from-cpan | 67 | nixops nix-prefetch-scripts nix-generate-from-cpan |
68 | nix-zsh-completions bundix nodePackages.bower2nix | 68 | nix-zsh-completions bundix nodePackages.bower2nix |
69 | nodePackages.node2nix | 69 | nodePackages.node2nix |
diff --git a/overlays/nixops/default.nix b/overlays/nixops/default.nix index eb29ecd0..247d0366 100644 --- a/overlays/nixops/default.nix +++ b/overlays/nixops/default.nix | |||
@@ -1,5 +1,6 @@ | |||
1 | self: super: { | 1 | self: super: { |
2 | nixops = super.nixops.overrideAttrs (old: { | 2 | nixops = super.nixops.overrideAttrs (old: { |
3 | patches = [ ./hetzner_cloud.patch ]; | ||
3 | preConfigure = (old.preConfigure or "") + '' | 4 | preConfigure = (old.preConfigure or "") + '' |
4 | sed -i -e "/'keyFile'/s/'path'/'string'/" nixops/backends/__init__.py | 5 | sed -i -e "/'keyFile'/s/'path'/'string'/" nixops/backends/__init__.py |
5 | ''; | 6 | ''; |
diff --git a/overlays/nixops/hetzner_cloud.patch b/overlays/nixops/hetzner_cloud.patch new file mode 100644 index 00000000..b75c1168 --- /dev/null +++ b/overlays/nixops/hetzner_cloud.patch | |||
@@ -0,0 +1,480 @@ | |||
1 | From 272e50d0b0262e49cdcaad42cdab57aad183d1c2 Mon Sep 17 00:00:00 2001 | ||
2 | From: goodraven | ||
3 | <employee-pseudonym-7f597def-7eeb-47f8-b10a-0724f2ba59a9@google.com> | ||
4 | Date: Thu, 3 May 2018 22:24:58 -0700 | ||
5 | Subject: [PATCH] Initial commit adding support for hetzner cloud | ||
6 | |||
7 | This is based on the digital ocean backend. It also uses nixos-infect. I extended nixos-infect to be generic | ||
8 | for both backends. | ||
9 | |||
10 | Fixes #855 | ||
11 | --- | ||
12 | examples/trivial-hetzner-cloud.nix | 12 ++ | ||
13 | nix/eval-machine-info.nix | 1 + | ||
14 | nix/hetzner-cloud.nix | 56 +++++++ | ||
15 | nix/options.nix | 1 + | ||
16 | nixops/backends/hetzner_cloud.py | 230 +++++++++++++++++++++++++++++ | ||
17 | nixops/data/nixos-infect | 77 +++++++--- | ||
18 | 6 files changed, 354 insertions(+), 23 deletions(-) | ||
19 | create mode 100644 examples/trivial-hetzner-cloud.nix | ||
20 | create mode 100644 nix/hetzner-cloud.nix | ||
21 | create mode 100644 nixops/backends/hetzner_cloud.py | ||
22 | |||
23 | diff --git a/examples/trivial-hetzner-cloud.nix b/examples/trivial-hetzner-cloud.nix | ||
24 | new file mode 100644 | ||
25 | index 000000000..c61add6bb | ||
26 | --- /dev/null | ||
27 | +++ b/examples/trivial-hetzner-cloud.nix | ||
28 | @@ -0,0 +1,12 @@ | ||
29 | +{ | ||
30 | + resources.sshKeyPairs.ssh-key = {}; | ||
31 | + | ||
32 | + machine = { config, pkgs, ... }: { | ||
33 | + services.openssh.enable = true; | ||
34 | + | ||
35 | + deployment.targetEnv = "hetznerCloud"; | ||
36 | + deployment.hetznerCloud.serverType = "cx11"; | ||
37 | + | ||
38 | + networking.firewall.allowedTCPPorts = [ 22 ]; | ||
39 | + }; | ||
40 | +} | ||
41 | diff --git a/nix/eval-machine-info.nix b/nix/eval-machine-info.nix | ||
42 | index 2884b4b47..6a7205786 100644 | ||
43 | --- a/nix/eval-machine-info.nix | ||
44 | +++ b/nix/eval-machine-info.nix | ||
45 | @@ -309,6 +309,7 @@ rec { | ||
46 | digitalOcean = optionalAttrs (v.config.deployment.targetEnv == "digitalOcean") v.config.deployment.digitalOcean; | ||
47 | gce = optionalAttrs (v.config.deployment.targetEnv == "gce") v.config.deployment.gce; | ||
48 | hetzner = optionalAttrs (v.config.deployment.targetEnv == "hetzner") v.config.deployment.hetzner; | ||
49 | + hetznerCloud = optionalAttrs (v.config.deployment.targetEnv == "hetznerCloud") v.config.deployment.hetznerCloud; | ||
50 | container = optionalAttrs (v.config.deployment.targetEnv == "container") v.config.deployment.container; | ||
51 | route53 = v.config.deployment.route53; | ||
52 | virtualbox = | ||
53 | diff --git a/nix/hetzner-cloud.nix b/nix/hetzner-cloud.nix | ||
54 | new file mode 100644 | ||
55 | index 000000000..21d148c1a | ||
56 | --- /dev/null | ||
57 | +++ b/nix/hetzner-cloud.nix | ||
58 | @@ -0,0 +1,56 @@ | ||
59 | +{ config, pkgs, lib, utils, ... }: | ||
60 | + | ||
61 | +with utils; | ||
62 | +with lib; | ||
63 | +with import ./lib.nix lib; | ||
64 | + | ||
65 | +let | ||
66 | + cfg = config.deployment.hetznerCloud; | ||
67 | +in | ||
68 | +{ | ||
69 | + ###### interface | ||
70 | + options = { | ||
71 | + | ||
72 | + deployment.hetznerCloud.authToken = mkOption { | ||
73 | + default = ""; | ||
74 | + example = "8b2f4e96af3997853bfd4cd8998958eab871d9614e35d63fab45a5ddf981c4da"; | ||
75 | + type = types.str; | ||
76 | + description = '' | ||
77 | + The API auth token. We're checking the environment for | ||
78 | + <envar>HETZNER_CLOUD_AUTH_TOKEN</envar> first and if that is | ||
79 | + not set we try this auth token. | ||
80 | + ''; | ||
81 | + }; | ||
82 | + | ||
83 | + deployment.hetznerCloud.datacenter = mkOption { | ||
84 | + example = "fsn1-dc8"; | ||
85 | + default = null; | ||
86 | + type = types.nullOr types.str; | ||
87 | + description = '' | ||
88 | + The datacenter. | ||
89 | + ''; | ||
90 | + }; | ||
91 | + | ||
92 | + deployment.hetznerCloud.location = mkOption { | ||
93 | + example = "fsn1"; | ||
94 | + default = null; | ||
95 | + type = types.nullOr types.str; | ||
96 | + description = '' | ||
97 | + The location. | ||
98 | + ''; | ||
99 | + }; | ||
100 | + | ||
101 | + deployment.hetznerCloud.serverType = mkOption { | ||
102 | + example = "cx11"; | ||
103 | + type = types.str; | ||
104 | + description = '' | ||
105 | + Name or id of server types. | ||
106 | + ''; | ||
107 | + }; | ||
108 | + }; | ||
109 | + | ||
110 | + config = mkIf (config.deployment.targetEnv == "hetznerCloud") { | ||
111 | + nixpkgs.system = mkOverride 900 "x86_64-linux"; | ||
112 | + services.openssh.enable = true; | ||
113 | + }; | ||
114 | +} | ||
115 | diff --git a/nix/options.nix b/nix/options.nix | ||
116 | index 0866c3ab8..db021f74d 100644 | ||
117 | --- a/nix/options.nix | ||
118 | +++ b/nix/options.nix | ||
119 | @@ -22,6 +22,7 @@ in | ||
120 | ./keys.nix | ||
121 | ./gce.nix | ||
122 | ./hetzner.nix | ||
123 | + ./hetzner-cloud.nix | ||
124 | ./container.nix | ||
125 | ./libvirtd.nix | ||
126 | ]; | ||
127 | diff --git a/nixops/backends/hetzner_cloud.py b/nixops/backends/hetzner_cloud.py | ||
128 | new file mode 100644 | ||
129 | index 000000000..a2cb176b9 | ||
130 | --- /dev/null | ||
131 | +++ b/nixops/backends/hetzner_cloud.py | ||
132 | @@ -0,0 +1,230 @@ | ||
133 | +# -*- coding: utf-8 -*- | ||
134 | +""" | ||
135 | +A backend for hetzner cloud. | ||
136 | + | ||
137 | +This backend uses nixos-infect (which uses nixos LUSTRATE) to infect a | ||
138 | +hetzner cloud instance. The setup requires two reboots, one for | ||
139 | +the infect itself, another after we pushed the nixos image. | ||
140 | +""" | ||
141 | +import os | ||
142 | +import os.path | ||
143 | +import time | ||
144 | +import socket | ||
145 | + | ||
146 | +import requests | ||
147 | + | ||
148 | +import nixops.resources | ||
149 | +from nixops.backends import MachineDefinition, MachineState | ||
150 | +from nixops.nix_expr import Function, RawValue | ||
151 | +import nixops.util | ||
152 | +import nixops.known_hosts | ||
153 | + | ||
154 | +infect_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'nixos-infect')) | ||
155 | + | ||
156 | +API_HOST = 'api.hetzner.cloud' | ||
157 | + | ||
158 | +class ApiError(Exception): | ||
159 | + pass | ||
160 | + | ||
161 | +class ApiNotFoundError(ApiError): | ||
162 | + pass | ||
163 | + | ||
164 | +class HetznerCloudDefinition(MachineDefinition): | ||
165 | + @classmethod | ||
166 | + def get_type(cls): | ||
167 | + return "hetznerCloud" | ||
168 | + | ||
169 | + def __init__(self, xml, config): | ||
170 | + MachineDefinition.__init__(self, xml, config) | ||
171 | + self.auth_token = config["hetznerCloud"]["authToken"] | ||
172 | + self.location = config["hetznerCloud"]["location"] | ||
173 | + self.datacenter = config["hetznerCloud"]["datacenter"] | ||
174 | + self.server_type = config["hetznerCloud"]["serverType"] | ||
175 | + | ||
176 | + def show_type(self): | ||
177 | + return "{0} [{1}]".format(self.get_type(), self.location or self.datacenter or 'any location') | ||
178 | + | ||
179 | + | ||
180 | +class HetznerCloudState(MachineState): | ||
181 | + @classmethod | ||
182 | + def get_type(cls): | ||
183 | + return "hetznerCloud" | ||
184 | + | ||
185 | + state = nixops.util.attr_property("state", MachineState.MISSING, int) # override | ||
186 | + public_ipv4 = nixops.util.attr_property("publicIpv4", None) | ||
187 | + public_ipv6 = nixops.util.attr_property("publicIpv6", None) | ||
188 | + location = nixops.util.attr_property("hetznerCloud.location", None) | ||
189 | + datacenter = nixops.util.attr_property("hetznerCloud.datacenter", None) | ||
190 | + server_type = nixops.util.attr_property("hetznerCloud.serverType", None) | ||
191 | + auth_token = nixops.util.attr_property("hetznerCloud.authToken", None) | ||
192 | + server_id = nixops.util.attr_property("hetznerCloud.serverId", None, int) | ||
193 | + | ||
194 | + def __init__(self, depl, name, id): | ||
195 | + MachineState.__init__(self, depl, name, id) | ||
196 | + self.name = name | ||
197 | + | ||
198 | + def get_ssh_name(self): | ||
199 | + return self.public_ipv4 | ||
200 | + | ||
201 | + def get_ssh_flags(self, *args, **kwargs): | ||
202 | + super_flags = super(HetznerCloudState, self).get_ssh_flags(*args, **kwargs) | ||
203 | + return super_flags + [ | ||
204 | + '-o', 'UserKnownHostsFile=/dev/null', | ||
205 | + '-o', 'StrictHostKeyChecking=no', | ||
206 | + '-i', self.get_ssh_private_key_file(), | ||
207 | + ] | ||
208 | + | ||
209 | + def get_physical_spec(self): | ||
210 | + return Function("{ ... }", { | ||
211 | + 'imports': [ RawValue('<nixpkgs/nixos/modules/profiles/qemu-guest.nix>') ], | ||
212 | + ('boot', 'loader', 'grub', 'device'): 'nodev', | ||
213 | + ('fileSystems', '/'): { 'device': '/dev/sda1', 'fsType': 'ext4'}, | ||
214 | + ('users', 'extraUsers', 'root', 'openssh', 'authorizedKeys', 'keys'): [self.depl.active_resources.get('ssh-key').public_key], | ||
215 | + }) | ||
216 | + | ||
217 | + def get_ssh_private_key_file(self): | ||
218 | + return self.write_ssh_private_key(self.depl.active_resources.get('ssh-key').private_key) | ||
219 | + | ||
220 | + def create_after(self, resources, defn): | ||
221 | + # make sure the ssh key exists before we do anything else | ||
222 | + return { | ||
223 | + r for r in resources if | ||
224 | + isinstance(r, nixops.resources.ssh_keypair.SSHKeyPairState) | ||
225 | + } | ||
226 | + | ||
227 | + def get_auth_token(self): | ||
228 | + return os.environ.get('HETZNER_CLOUD_AUTH_TOKEN', self.auth_token) | ||
229 | + | ||
230 | + def _api(self, path, method=None, data=None, json=True): | ||
231 | + """Basic wrapper around requests that handles auth and serialization.""" | ||
232 | + assert path[0] == '/' | ||
233 | + url = 'https://%s%s' % (API_HOST, path) | ||
234 | + token = self.get_auth_token() | ||
235 | + if not token: | ||
236 | + raise Exception('No hetzner cloud auth token set') | ||
237 | + headers = { | ||
238 | + 'Authorization': 'Bearer '+self.get_auth_token(), | ||
239 | + } | ||
240 | + res = requests.request( | ||
241 | + method=method, | ||
242 | + url=url, | ||
243 | + json=data, | ||
244 | + headers=headers) | ||
245 | + | ||
246 | + if res.status_code == 404: | ||
247 | + raise ApiNotFoundError('Not Found: %r' % path) | ||
248 | + elif not res.ok: | ||
249 | + raise ApiError('Response for %s %s has status code %d: %s' % (method, path, res.status_code, res.content)) | ||
250 | + if not json: | ||
251 | + return | ||
252 | + try: | ||
253 | + res_data = res.json() | ||
254 | + except ValueError as e: | ||
255 | + raise ApiError('Response for %s %s has invalid JSON (%s): %r' % (method, path, e, res.content)) | ||
256 | + return res_data | ||
257 | + | ||
258 | + | ||
259 | + def destroy(self, wipe=False): | ||
260 | + if not self.server_id: | ||
261 | + self.log('server {} was never made'.format(self.name)) | ||
262 | + return | ||
263 | + self.log('destroying server {} with id {}'.format(self.name, self.server_id)) | ||
264 | + try: | ||
265 | + res = self._api('/v1/servers/%s' % (self.server_id), method='DELETE') | ||
266 | + except ApiNotFoundError: | ||
267 | + self.log("server not found - assuming it's been destroyed already") | ||
268 | + | ||
269 | + self.public_ipv4 = None | ||
270 | + self.server_id = None | ||
271 | + | ||
272 | + return True | ||
273 | + | ||
274 | + def _create_ssh_key(self, public_key): | ||
275 | + """Create or get an ssh key and return an id.""" | ||
276 | + public_key = public_key.strip() | ||
277 | + res = self._api('/v1/ssh_keys', method='GET') | ||
278 | + name = 'nixops-%s-%s' % (self.depl.uuid, self.name) | ||
279 | + deletes = [] | ||
280 | + for key in res['ssh_keys']: | ||
281 | + if key['public_key'].strip() == public_key: | ||
282 | + return key['id'] | ||
283 | + if key['name'] == name: | ||
284 | + deletes.append(key['id']) | ||
285 | + for d in deletes: | ||
286 | + # This reply is empty, so don't decode json. | ||
287 | + self._api('/v1/ssh_keys/%d' % d, method='DELETE', json=False) | ||
288 | + res = self._api('/v1/ssh_keys', method='POST', data={ | ||
289 | + 'name': name, | ||
290 | + 'public_key': public_key, | ||
291 | + }) | ||
292 | + return res['ssh_key']['id'] | ||
293 | + | ||
294 | + def create(self, defn, check, allow_reboot, allow_recreate): | ||
295 | + ssh_key = self.depl.active_resources.get('ssh-key') | ||
296 | + if ssh_key is None: | ||
297 | + raise Exception('Please specify a ssh-key resource (resources.sshKeyPairs.ssh-key = {}).') | ||
298 | + | ||
299 | + self.set_common_state(defn) | ||
300 | + | ||
301 | + if self.server_id is not None: | ||
302 | + return | ||
303 | + | ||
304 | + ssh_key_id = self._create_ssh_key(ssh_key.public_key) | ||
305 | + | ||
306 | + req = { | ||
307 | + 'name': self.name, | ||
308 | + 'server_type': defn.server_type, | ||
309 | + 'start_after_create': True, | ||
310 | + 'image': 'debian-9', | ||
311 | + 'ssh_keys': [ | ||
312 | + ssh_key_id, | ||
313 | + ], | ||
314 | + } | ||
315 | + | ||
316 | + if defn.datacenter: | ||
317 | + req['datacenter'] = defn.datacenter | ||
318 | + elif defn.location: | ||
319 | + req['location'] = defn.location | ||
320 | + | ||
321 | + self.log_start("creating server ...") | ||
322 | + create_res = self._api('/v1/servers', method='POST', data=req) | ||
323 | + self.server_id = create_res['server']['id'] | ||
324 | + self.public_ipv4 = create_res['server']['public_net']['ipv4']['ip'] | ||
325 | + self.public_ipv6 = create_res['server']['public_net']['ipv6']['ip'] | ||
326 | + self.datacenter = create_res['server']['datacenter']['name'] | ||
327 | + self.location = create_res['server']['datacenter']['location']['name'] | ||
328 | + | ||
329 | + action = create_res['action'] | ||
330 | + action_path = '/v1/servers/%d/actions/%d' % (self.server_id, action['id']) | ||
331 | + | ||
332 | + while action['status'] == 'running': | ||
333 | + time.sleep(1) | ||
334 | + res = self._api(action_path, method='GET') | ||
335 | + action = res['action'] | ||
336 | + | ||
337 | + if action['status'] != 'success': | ||
338 | + raise Exception('unexpected status: %s' % action['status']) | ||
339 | + | ||
340 | + self.log_end("{}".format(self.public_ipv4)) | ||
341 | + | ||
342 | + self.wait_for_ssh() | ||
343 | + self.log_start("running nixos-infect") | ||
344 | + self.run_command('bash </dev/stdin 2>&1', stdin=open(infect_path)) | ||
345 | + self.reboot_sync() | ||
346 | + | ||
347 | + def reboot(self, hard=False): | ||
348 | + if hard: | ||
349 | + self.log("sending hard reset to server...") | ||
350 | + res = self._api('/v1/servers/%d/actions/reset' % self.server_id, method='POST') | ||
351 | + action = res['action'] | ||
352 | + action_path = '/v1/servers/%d/actions/%d' % (self.server_id, action['id']) | ||
353 | + while action['status'] == 'running': | ||
354 | + time.sleep(1) | ||
355 | + res = self._api(action_path, method='GET') | ||
356 | + action = res['action'] | ||
357 | + if action['status'] != 'success': | ||
358 | + raise Exception('unexpected status: %s' % action['status']) | ||
359 | + self.wait_for_ssh() | ||
360 | + self.state = self.STARTING | ||
361 | + else: | ||
362 | + MachineState.reboot(self, hard=hard) | ||
363 | diff --git a/nixops/data/nixos-infect b/nixops/data/nixos-infect | ||
364 | index 66634357b..437a2ec61 100644 | ||
365 | --- a/nixops/data/nixos-infect | ||
366 | +++ b/nixops/data/nixos-infect | ||
367 | @@ -68,26 +68,49 @@ makeConf() { | ||
368 | } | ||
369 | EOF | ||
370 | # (nixos-generate-config will add qemu-user and bind-mounts, so avoid) | ||
371 | + local disk | ||
372 | + if [ -e /dev/sda ]; then | ||
373 | + disk=/dev/sda | ||
374 | + else | ||
375 | + disk=/dev/vda | ||
376 | + fi | ||
377 | cat > /etc/nixos/hardware-configuration.nix << EOF | ||
378 | { ... }: | ||
379 | { | ||
380 | imports = [ <nixpkgs/nixos/modules/profiles/qemu-guest.nix> ]; | ||
381 | - boot.loader.grub.device = "/dev/vda"; | ||
382 | - fileSystems."/" = { device = "/dev/vda1"; fsType = "ext4"; }; | ||
383 | + boot.loader.grub.device = "${disk}"; | ||
384 | + fileSystems."/" = { device = "${disk}1"; fsType = "ext4"; }; | ||
385 | } | ||
386 | EOF | ||
387 | |||
388 | local IFS=$'\n' | ||
389 | - ens3_ip4s=($(ip address show dev eth0 | grep 'inet ' | sed -r 's|.*inet ([0-9.]+)/([0-9]+).*|{ address="\1"; prefixLength=\2; }|')) | ||
390 | - ens3_ip6s=($(ip address show dev eth0 | grep 'inet6 .*global' | sed -r 's|.*inet6 ([0-9a-f:]+)/([0-9]+).*|{ address="\1"; prefixLength=\2; }|')) | ||
391 | - ens4_ip4s=($(ip address show dev eth1 | grep 'inet ' | sed -r 's|.*inet ([0-9.]+)/([0-9]+).*|{ address="\1"; prefixLength=\2; }|')) | ||
392 | - ens4_ip6s=($(ip address show dev eth1 | grep 'inet6 .*global' | sed -r 's|.*inet6 ([0-9a-f:]+)/([0-9]+).*|{ address="\1"; prefixLength=\2; }|')) | ||
393 | - gateway=($(ip route show dev eth0 | grep default | sed -r 's|default via ([0-9.]+).*|\1|')) | ||
394 | - gateway6=($(ip -6 route show dev eth0 | grep default | sed -r 's|default via ([0-9a-f:]+).*|\1|')) | ||
395 | - ether0=($(ip address show dev eth0 | grep link/ether | sed -r 's|.*link/ether ([0-9a-f:]+) .*|\1|')) | ||
396 | - ether1=($(ip address show dev eth1 | grep link/ether | sed -r 's|.*link/ether ([0-9a-f:]+) .*|\1|')) | ||
397 | + gateway=($(ip route show | grep default | sed -r 's|default via ([0-9.]+).*|\1|')) | ||
398 | + gateway6=($(ip -6 route show | grep default | sed -r 's|default via ([0-9a-f:]+).*|\1|')) | ||
399 | + interfaces=($(ip link | awk -F ': ' '/^[0-9]*: / {if ($2 != "lo") {print $2}}')) | ||
400 | nameservers=($(grep ^nameserver /etc/resolv.conf | cut -f2 -d' ')) | ||
401 | |||
402 | + # Predict the predictable name for each interface since that is enabled in | ||
403 | + # the nixos system. | ||
404 | + declare -A predictable_names | ||
405 | + for interface in ${interfaces[@]}; do | ||
406 | + # udevadm prints out the candidate names which will be selected if | ||
407 | + # available in this order. | ||
408 | + local name=$(udevadm info /sys/class/net/$interface | awk -F = ' | ||
409 | + /^E: ID_NET_NAME_FROM_DATABASE=/ {arr[1]=$2} | ||
410 | + /^E: ID_NET_NAME_ONBOARD=/ {arr[2]=$2} | ||
411 | + /^E: ID_NET_NAME_SLOT=/ {arr[3]=$2} | ||
412 | + /^E: ID_NET_NAME_PATH=/ {arr[4]=$2} | ||
413 | + /^E: ID_NET_NAME_MAC=/ {arr[5]=$2} | ||
414 | + END {for (i=1;i<6;i++) {if (length(arr[i]) > 0) { print arr[i]; break}}}') | ||
415 | + if [ -z "$name" ]; then | ||
416 | + echo Could not determine predictable name for interface $interface | ||
417 | + fi | ||
418 | + predictable_names[$interface]=$name | ||
419 | + done | ||
420 | + | ||
421 | + # Take a gamble on the first interface being able to reach the gateway. | ||
422 | + local default_interface=${predictable_names[${interfaces[0]}]} | ||
423 | + | ||
424 | cat > /etc/nixos/networking.nix << EOF | ||
425 | { ... }: { | ||
426 | # This file was populated at runtime with the networking | ||
427 | @@ -96,25 +119,27 @@ EOF | ||
428 | nameservers = [$(for a in ${nameservers[@]}; do echo -n " | ||
429 | \"$a\""; done) | ||
430 | ]; | ||
431 | - defaultGateway = "${gateway}"; | ||
432 | - defaultGateway6 = "${gateway6}"; | ||
433 | + defaultGateway = {address = "${gateway}"; interface = "${default_interface}";}; | ||
434 | + defaultGateway6 = {address = "${gateway6}"; interface = "${default_interface}";}; | ||
435 | interfaces = { | ||
436 | - ens3 = { | ||
437 | - ip4 = [$(for a in ${ens3_ip4s[@]}; do echo -n " | ||
438 | - $a"; done) | ||
439 | - ]; | ||
440 | - ip6 = [$(for a in ${ens3_ip6s[@]}; do echo -n " | ||
441 | - $a"; done) | ||
442 | - ]; | ||
443 | - }; | ||
444 | - ens4 = { | ||
445 | - ip4 = [$(for a in ${ens4_ip4s[@]}; do echo -n " | ||
446 | +EOF | ||
447 | + | ||
448 | + for interface in ${interfaces[@]}; do | ||
449 | + ip4s=($(ip address show dev $interface | grep 'inet ' | sed -r 's|.*inet ([0-9.]+)/([0-9]+).*|{ address="\1"; prefixLength=\2; }|')) | ||
450 | + ip6s=($(ip address show dev $interface | grep 'inet6 .*global' | sed -r 's|.*inet6 ([0-9a-f:]+)/([0-9]+).*|{ address="\1"; prefixLength=\2; }|')) | ||
451 | + cat >> /etc/nixos/networking.nix << EOF | ||
452 | + ${predictable_names[$interface]} = { | ||
453 | + ip4 = [$(for a in ${ip4s[@]}; do echo -n " | ||
454 | $a"; done) | ||
455 | ]; | ||
456 | - ip6 = [$(for a in ${ens4_ip6s[@]}; do echo -n " | ||
457 | + ip6 = [$(for a in ${ip6s[@]}; do echo -n " | ||
458 | $a"; done) | ||
459 | ]; | ||
460 | }; | ||
461 | +EOF | ||
462 | + done | ||
463 | + | ||
464 | + cat >> /etc/nixos/networking.nix << EOF | ||
465 | }; | ||
466 | }; | ||
467 | } | ||
468 | @@ -154,6 +179,12 @@ export HOME="/root" | ||
469 | groupadd -r nixbld -g 30000 | ||
470 | seq 1 10 | xargs -I{} useradd -c "Nix build user {}" -d /var/empty -g nixbld -G nixbld -M -N -r -s `which nologin` nixbld{} | ||
471 | |||
472 | +if ! which curl >/dev/null 2>/dev/null; then | ||
473 | + if which apt-get >/dev/null 2>/dev/null; then | ||
474 | + apt-get update && apt-get install -y curl | ||
475 | + fi | ||
476 | +fi | ||
477 | + | ||
478 | curl https://nixos.org/nix/install | sh | ||
479 | |||
480 | source ~/.nix-profile/etc/profile.d/nix.sh | ||