Add a generic installer that asks for the basics and builds offline
The installer ISO no longer needs a prebuilt system: `router-setup` asks for the disk, the WAN and LAN interfaces, DHCP or PPPoE (optionally on a VLAN), host name, LAN address and a password, writes settings.json and the secrets, builds that router offline (the ISO carries a reference router system whose closure has everything the build needs) and installs it with `router-install --system`. The password is used for the web UI and, hashed, as root's console password (routerSettings sets root's hashedPasswordFile; it applies when the account is created at installation). checks.setup answers router-setup on the console and checks the installed box: its settings, the web UI and console login with that password, and a rebuild that reproduces the installed system. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
dns = import ./dns.nix { inherit self pkgs; };
|
||||
installer = import ./installer.nix { inherit self pkgs; };
|
||||
apply = import ./apply.nix { inherit self pkgs; };
|
||||
setup = import ./setup.nix { inherit self pkgs; };
|
||||
image = import ./image.nix { inherit self pkgs; };
|
||||
wireguard = import ./wireguard.nix { inherit self pkgs; };
|
||||
lib = import ./lib.nix { inherit pkgs; };
|
||||
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
# The generic installer: router-setup is answered on its console (DHCP uplink,
|
||||
# LAN address, web UI password), builds the router offline and installs it.
|
||||
# The installed box must come up with those settings, serve the web UI with
|
||||
# that password, and rebuild to exactly itself.
|
||||
{ self, pkgs }:
|
||||
let
|
||||
common = import ./common.nix;
|
||||
|
||||
# Only so the test driver can talk to the installed system.
|
||||
local = pkgs.writeText "local.nix" ''
|
||||
{ lib, modulesPath, ... }:
|
||||
{
|
||||
imports = [ (modulesPath + "/testing/test-instrumentation.nix") ];
|
||||
# The instrumentation empties root's password; keep router-setup's.
|
||||
users.users.root.hashedPasswordFile = lib.mkForce "/var/lib/router/root-password";
|
||||
}
|
||||
'';
|
||||
|
||||
sharedDisk = {
|
||||
virtualisation.diskImage = "./target.qcow2";
|
||||
virtualisation.diskSize = 8192;
|
||||
# Evaluating and building a NixOS system takes memory.
|
||||
virtualisation.memorySize = 3072;
|
||||
};
|
||||
|
||||
answers = pkgs.writeText "answers" ''
|
||||
/dev/vda
|
||||
eth1
|
||||
eth2
|
||||
dhcp
|
||||
gw
|
||||
192.168.10.1
|
||||
hunter2hunter2
|
||||
hunter2hunter2
|
||||
|
||||
/dev/vda
|
||||
'';
|
||||
in
|
||||
pkgs.testers.runNixOSTest {
|
||||
name = "router-setup";
|
||||
|
||||
nodes = {
|
||||
installer =
|
||||
{ lib, ... }:
|
||||
{
|
||||
imports = [
|
||||
sharedDisk
|
||||
(self.lib.setupModule { inherit local; })
|
||||
common.tools
|
||||
];
|
||||
environment.etc."router-test-local.nix".source = local;
|
||||
# The interfaces router-setup offers.
|
||||
virtualisation.vlans = [
|
||||
1
|
||||
2
|
||||
];
|
||||
virtualisation.fileSystems."/".autoFormat = true;
|
||||
virtualisation.emptyDiskImages = [ 1024 ];
|
||||
virtualisation.rootDevice = "/dev/vdb";
|
||||
nix.settings.substituters = lib.mkForce [ ];
|
||||
};
|
||||
|
||||
target = {
|
||||
imports = [ sharedDisk ];
|
||||
virtualisation.vlans = [
|
||||
1
|
||||
2
|
||||
];
|
||||
virtualisation.useBootLoader = true;
|
||||
virtualisation.useEFIBoot = true;
|
||||
virtualisation.useDefaultFilesystems = false;
|
||||
virtualisation.efi.keepVariables = false;
|
||||
virtualisation.fileSystems."/" = {
|
||||
device = "/dev/disk/by-label/this-is-not-real-and-will-never-be-used";
|
||||
fsType = "ext4";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
installer.start()
|
||||
installer.wait_for_unit("multi-user.target")
|
||||
|
||||
with subtest("router-setup refuses to install without the disk name"):
|
||||
installer.fail("printf '/dev/vda\\neth1\\neth2\\ndhcp\\n\\n\\nhunter2hunter2\\nhunter2hunter2\\n\\nno\\n' | router-setup")
|
||||
installer.fail("blkid /dev/vda1")
|
||||
|
||||
with subtest("router-setup builds the answered configuration offline and installs it"):
|
||||
installer.succeed("router-setup --local /etc/router-test-local.nix < ${answers} >&2")
|
||||
installer.shutdown()
|
||||
|
||||
target.state_dir = installer.state_dir
|
||||
target.start()
|
||||
target.wait_for_unit("multi-user.target")
|
||||
|
||||
with subtest("the installed router has the answered settings"):
|
||||
assert target.succeed("hostname").strip() == "gw"
|
||||
target.wait_until_succeeds("ip -4 addr show dev eth2 | grep -q 192.168.10.1/24")
|
||||
target.succeed("grep -q '192.168.10.100 - 192.168.10.200' /etc/kea/dhcp4-server.conf")
|
||||
settings = target.succeed("cat /var/lib/router/config/settings.json")
|
||||
assert '"eth1"' in settings and '"dhcp"' in settings, settings
|
||||
assert target.succeed("stat -c %a /var/lib/router/webui-password").strip() == "600"
|
||||
|
||||
with subtest("the web UI accepts the password given to router-setup"):
|
||||
target.wait_for_unit("router-webui.service")
|
||||
target.wait_until_succeeds("curl -sk -o /dev/null https://127.0.0.1/login")
|
||||
code = target.succeed("curl -sk -o /dev/null -w '%{http_code}' -d password=hunter2hunter2 https://127.0.0.1/login")
|
||||
assert code == "303", code
|
||||
|
||||
with subtest("root logs in on the console with the same password"):
|
||||
target.wait_until_tty_matches("1", "login: ")
|
||||
target.send_chars("root\n")
|
||||
target.wait_until_tty_matches("1", "Password: ")
|
||||
target.send_chars("hunter2hunter2\n")
|
||||
target.wait_until_succeeds("pgrep -u root -t tty1 bash")
|
||||
assert target.succeed("stat -c %a /var/lib/router/root-password").strip() == "600"
|
||||
|
||||
with subtest("the box rebuilds to exactly the installed system"):
|
||||
out = target.succeed("router-apply 2>&1")
|
||||
assert "no changes" in out, out
|
||||
'';
|
||||
}
|
||||
Reference in New Issue
Block a user