WIP: nixos-anywhere for virtual machines

This commit is contained in:
Pim Kunis 2023-11-25 21:00:21 +01:00
parent cc809942ef
commit 0bf113fa25
16 changed files with 282 additions and 7 deletions

2
.gitignore vendored
View file

@ -1 +1,3 @@
.direnv
.terraform.lock.hcl
.terraform

View file

@ -20,7 +20,7 @@ Additionally, it deploys an age identity, which is later used for decrypting sec
1. Make sure your have a [Secret service](https://www.gnu.org/software/emacs/manual/html_node/auth/Secret-Service-API.html) running (such as Keepassxc) that provides the age identity.
2. Ensure you have root SSH access to the server.
3. Run nixos-anywhere: `./bootstrap.sh <servername>`
3. Run nixos-anywhere: `./bootstrap.sh <servername> <hostname>`
## Deployment

View file

@ -4,9 +4,11 @@ IFS=$'\n\t'
servername="${1-}"
if [ -z "$servername" ]
hostname="${2-}"
if [ -z "$servername" ] || [ -z "$hostname" ]
then
echo "Usage: $0 SERVERNAME"
echo "Usage: $0 SERVERNAME HOSTNAME"
exit 1
fi
@ -40,4 +42,4 @@ secret-tool lookup age-identity "$servername" > "$temp/root/age_ed25519"
chmod 600 "$temp/root/age_ed25519"
# Install NixOS to the host system with our age identity
nix run github:numtide/nixos-anywhere -- --extra-files "$temp" --flake ".#${servername}" "root@${servername}.hyp"
nix run github:numtide/nixos-anywhere -- --extra-files "$temp" --flake ".#${servername}" "root@${hostname}"

View file

@ -1,7 +1,7 @@
{ pkgs, config, lib, modulesPath, ... }: {
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
./modules/disk-config.nix
# ./modules/disk-config.nix
./modules/custom
./modules/uptimed.nix
];

View file

@ -41,6 +41,8 @@
pkgs-unstable.deploy-rs
pkgs.openssl
pkgs.postgresql_15
pkgs-unstable.opentofu
pkgs.cdrtools
];
};

View file

@ -8,12 +8,79 @@
dataDisk.enable = true;
ssh = {
useCertificates = true;
hostCert = builtins.readFile ./jefke_host_ed25519-cert.pub;
userCert = builtins.readFile ./jefke_user_ed25519-cert.pub;
};
terraformDatabase.enable = true;
};
disko.devices = {
disk = {
vdb = {
device = "/dev/nvme0n1";
type = "disk";
content = {
type = "gpt";
partitions = {
ESP = {
type = "EF00";
size = "500M";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
};
};
root = {
end = "-4G";
content = {
type = "filesystem";
format = "btrfs";
mountpoint = "/";
};
};
swap = { size = "100%"; };
};
};
};
};
};
};
};
bancomart = {
name = "bancomart";
hostname = "bancomart.dmz";
specificConfig = {
disko.devices = {
disk = {
vda = {
device = "/dev/vda";
type = "disk";
content = {
type = "gpt";
partitions = {
boot = {
size = "1M";
type = "EF02"; # for grub MBR
};
root = {
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
};
};
};
};
}

View file

@ -7,6 +7,14 @@ in {
options = {
custom = {
ssh = {
useCertificates = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to use certificates at all.
'';
};
hostCert = lib.mkOption {
type = lib.types.str;
description = ''
@ -42,7 +50,7 @@ in {
};
};
config = {
config = lib.mkIf cfg.useCertificates {
services.openssh = {
extraConfig = ''
HostCertificate ${hostCert}

View file

@ -31,4 +31,3 @@
};
};
}

36
terraform/main.tf Normal file
View file

@ -0,0 +1,36 @@
terraform {
backend "pg" {
schema_name = "testje"
conn_str = "postgresql://terraform@jefke.hyp/terraformstates"
}
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.7.1" # https://github.com/dmacvicar/terraform-provider-libvirt/issues/1040
}
}
}
# https://libvirt.org/uri.html#libssh-and-libssh2-transport
provider "libvirt" {
# alias = "jefke"
uri = "qemu+ssh://root@jefke.hyp/system?known_hosts=/etc/ssh/ssh_known_hosts"
}
module "setup_jefke" {
source = "./modules/setup"
# providers = {
# libvirt = libvirt.jefke
# }
}
module "bancomart" {
source = "./modules/debian"
name = "bancomart"
ram = 2048
storage = 10
# providers = {
# libvirt = libvirt.jefke
# }
}

View file

@ -0,0 +1,7 @@
# tf-modules
Terraform modules we use for the virtual machines in our home network.
These are all personalized and probably of little use outside our network.
The modules are currently:
- `debian`: Personalized Debian VM using Terraform's `libvirt` provider
- `invariants`: Invariants for our home network we use in multiple places.

View file

@ -0,0 +1,15 @@
#cloud-config
hostname: "${hostname}"
manage_etc_hosts: true
disable_root: false
ssh_authorized_keys:
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOodpLr+FDRyKyHjucHizNLVFHZ5AQmE9GmxMnOsSoaw pimkunis@thinkpadpim"
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINUZp4BCxf7uLa1QWonx/Crf8tYZ5MKIZ+EuaBa82LrV user@user-laptop"
ssh_pwauth: false
# TODO: Do we need this?
runcmd:
- dhclient -r
- dhclient

View file

@ -0,0 +1,17 @@
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
eval "$(jq -r '@sh "PUBKEY=\(.pubkey) HOST=\(.host) CAHOST=\(.cahost) CASCRIPT=\(.cascript) CAKEY=\(.cakey)"')"
# TODO: Can this be done more eye-pleasingly?
set +e
CERT=$(ssh -o ConnectTimeout=3 -o ConnectionAttempts=1 root@$CAHOST '"'"$CASCRIPT"'" host "'"$CAKEY"'" "'"$PUBKEY"'" "'"$HOST"'".dmz')
retval=$?
set -e
if [ retval -neq 0 ]; then
CERT=""
fi
jq -n --arg cert "$CERT" '{"cert":$cert}'

View file

@ -0,0 +1,9 @@
version: 2
ethernets:
ens:
match:
name: ens*
dhcp4: true
routes:
- to: 0.0.0.0/0
via: 192.168.30.1

View file

@ -0,0 +1,54 @@
terraform {
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
}
}
}
resource "libvirt_volume" "os" {
name = "${var.name}.qcow2"
pool = "disks"
size = 1024 * 1024 * 1024 * var.storage
base_volume_name = "debian-bookworm.qcow2"
base_volume_pool = "images"
lifecycle {
replace_triggered_by = [
libvirt_cloudinit_disk.main.id
]
}
}
resource "libvirt_cloudinit_disk" "main" {
name = "${var.name}.iso"
pool = "cloudinit"
user_data = templatefile("${path.module}/files/cloud_init.cfg.tftpl", {
hostname = var.name
})
network_config = file("${path.module}/files/network_config.cfg")
}
resource "libvirt_domain" "main" {
name = var.name
memory = var.ram
vcpu = 4
autostart = true
disk {
volume_id = libvirt_volume.os.id
}
network_interface {
bridge = "bridgedmz"
hostname = var.name
}
cloudinit = libvirt_cloudinit_disk.main.id
lifecycle {
replace_triggered_by = [
libvirt_cloudinit_disk.main.id
]
}
}

View file

@ -0,0 +1,13 @@
variable "name" {
type = string
}
variable "ram" {
type = number
description = "In MiB"
}
variable "storage" {
type = number
description = "In GiB"
}

View file

@ -0,0 +1,44 @@
terraform {
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
}
}
}
resource "libvirt_pool" "images" {
name = "images"
type = "dir"
path = "/var/lib/libvirt/pools/images"
}
resource "libvirt_pool" "cloudinit" {
name = "cloudinit"
type = "dir"
path = "/var/lib/libvirt/pools/cloudinit"
}
resource "libvirt_pool" "disks" {
name = "disks"
type = "dir"
path = "/var/lib/libvirt/pools/disks"
}
resource "libvirt_volume" "debian_bookworm" {
name = "debian-bookworm.qcow2"
pool = libvirt_pool.images.name
source = "https://cloud.debian.org/images/cloud/bookworm/daily/latest/debian-12-generic-amd64-daily.qcow2"
}
resource "libvirt_network" "bridgedmz" {
name = "bridgedmz"
mode = "bridge"
bridge = "bridgedmz"
dhcp {
enabled = false
}
dns {
enabled = false
}
autostart = true
}