about summary refs log tree commit diff
path: root/modules/incus.nix
blob: 3d6443275e809628ee6d77566b9cb6646407c687 (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
{
  config,
  lib,
  pkgs,
  ...
}:

let
  # qemu 9.1.2 no longer supports strings being passed instead of some
  # integer parameters. incus already has the fix, but it hasn't made it
  # into a release yet.
  # see: https://github.com/lxc/incus/issues/1522
  incusPatch = pkgs.fetchpatch {
    name = "1531.patch";
    url = "https://github.com/lxc/incus/pull/1531.patch";
    sha256 = "sha256-tM/+JRH0OwR3bM8gk3yNo9SSAEMqpS2HP+OzooV3DJY=";
  };
  incus = pkgs.incus.overrideAttrs (attrs: {
    patches = (attrs.patches or [ ]) ++ [ incusPatch ];
  });

  toYAML = lib.generators.toYAML { };

  cloudInitConfiguration = {
    users = [
      {
        name = "admin";
        groups = "users";
        sudo = "ALL=(ALL) NOPASSWD:ALL";
        plain_text_passwd = "example";
        lock_passwd = false;
      }
    ];

    # ssh configuration
    allow_public_ssh_keys = true;
    disable_root = true;
    packages = [ "openssh-server" ];
    runcmd = [
      [
        "systemctl"
        "enable"
        "ssh.service"
      ]
    ];
  };
in
{
  networking.firewall = {
    # needed so that the nixos firewall does not block
    # DHCP+DNS requests from incus, and to prevent conflicts
    # between the two firewalls.
    trustedInterfaces = [ "incusbr0" ];
    allowedTCPPorts = [ 23 ];
  };
  # needed so inscus instances can connect to the proxy.
  boot.kernelModules = [ "br_netfilter" ];
  virtualisation.incus = {
    enable = true;
    package = incus;
    preseed = {
      networks = [
        {
          config = {
            "ipv4.address" = "10.0.100.1/24";
            "ipv4.nat" = "true";
          };
          name = "incusbr0";
          type = "bridge";
        }
      ];
      profiles = [
        # this default profile gets applied to all
        # new instances without an explicitly set profile.
        {
          name = "default";
          config = { };
          devices = {
            eth0 = {
              name = "eth0";
              network = "incusbr0";
              type = "nic";
            };
            root = {
              path = "/";
              pool = "default";
              size = "35GiB";
              type = "disk";
            };
          };
        }
        # this profile is the one we want to apply to an ubuntu example vm.
        # it is provisioned with a static ipv4 (for nat-ted proxy)
        # and cloud-init configuration
        {
          name = "vm-1";
          # config applied to new instances,
          # this is how we can best control
          # vm provisioning semi-declaratively.
          # for options, see: https://linuxcontainers.org/incus/docs/main/reference/instance_options/
          config = {
            # `vendor` is usually for defaults, but it doesn't actually matter here.
            # NOTE: cloud-init requires either the incus-agent to be running,
            # or that the image is a special cloud image. i.e. `images:ubuntu/22.04/cloud`.
            "cloud-init.vendor-data" = ''
              #cloud-config
              ${toYAML cloudInitConfiguration}
            '';
          };
          devices = {
            eth0 = {
              name = "eth0";
              network = "incusbr0";
              type = "nic";

              # this is necessary for our nat proxy configuration.
              # see: https://linuxcontainers.org/incus/docs/main/reference/devices_proxy/#nat-mode
              "ipv4.address" = "10.0.100.123";
            };
            proxy = {
              type = "proxy";
              listen = "tcp:127.0.0.1:2222";
              connect = "tcp:0.0.0.0:22";
              nat = true;
            };
            root = {
              path = "/";
              pool = "default";
              size = "35GiB";
              type = "disk";
            };
          };
        }
      ];
      storage_pools = [
        {
          config = {
            source = "/var/lib/incus/storage-pools/default";
          };
          driver = "dir";
          name = "default";
        }
      ];
    };
  };

  # `incus-admin` essentially gives you root access anyway,
  # let users in `wheel` use it freely.
  users.groups."incus-admin".members = config.users.groups."wheel".members;
}