Terraform & atlas #16
21 changed files with 306 additions and 7 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1 +1,3 @@
|
||||||
.direnv
|
.direnv
|
||||||
|
.terraform.lock.hcl
|
||||||
|
.terraform
|
||||||
|
|
|
@ -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.
|
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.
|
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
|
## Deployment
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,11 @@ IFS=$'\n\t'
|
||||||
|
|
||||||
servername="${1-}"
|
servername="${1-}"
|
||||||
|
|
||||||
if [ -z "$servername" ]
|
hostname="${2-}"
|
||||||
|
|
||||||
|
if [ -z "$servername" ] || [ -z "$hostname" ]
|
||||||
then
|
then
|
||||||
echo "Usage: $0 SERVERNAME"
|
echo "Usage: $0 SERVERNAME HOSTNAME"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -40,4 +42,4 @@ secret-tool lookup age-identity "$servername" > "$temp/root/age_ed25519"
|
||||||
chmod 600 "$temp/root/age_ed25519"
|
chmod 600 "$temp/root/age_ed25519"
|
||||||
|
|
||||||
# Install NixOS to the host system with our age identity
|
# 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}"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{ pkgs, config, lib, modulesPath, ... }: {
|
{ pkgs, config, lib, modulesPath, ... }: {
|
||||||
imports = [
|
imports = [
|
||||||
(modulesPath + "/installer/scan/not-detected.nix")
|
(modulesPath + "/installer/scan/not-detected.nix")
|
||||||
./modules/disk-config.nix
|
# ./modules/disk-config.nix
|
||||||
./modules/custom
|
./modules/custom
|
||||||
./modules/uptimed.nix
|
./modules/uptimed.nix
|
||||||
];
|
];
|
||||||
|
|
|
@ -41,6 +41,8 @@
|
||||||
pkgs-unstable.deploy-rs
|
pkgs-unstable.deploy-rs
|
||||||
pkgs.openssl
|
pkgs.openssl
|
||||||
pkgs.postgresql_15
|
pkgs.postgresql_15
|
||||||
|
pkgs-unstable.opentofu
|
||||||
|
pkgs.cdrtools
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
1
machines/atlas_host_ed25519-cert.pub
Normal file
1
machines/atlas_host_ed25519-cert.pub
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIH4CQGHwWytKnkn7lYjT6G1NyPzINvfroZgwCLoOLO74AAAAIOMoSSEqM4VUBWUeFweJbqK9z7Ygp7fkX22hyWmgCNg8AAAAAAAAAAAAAAACAAAACWF0bGFzLmh5cAAAAA0AAAAJYXRsYXMuaHlwAAAAAAAAAAD//////////wAAAAAAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgXNGQfd38pUlCi6zBj8Myl6dZsMVU6cjdW63TFHR7W1sAAABTAAAAC3NzaC1lZDI1NTE5AAAAQAYModSEVNG06xvAcRn8XFeCp/iXFeqVcbtfT1NmmMkyIgybkXhJyHjp89BPg0zeAaoScFx8Xpsdd8CsxTeP+QU= root@atlas
|
1
machines/atlas_user_ed25519-cert.pub
Normal file
1
machines/atlas_user_ed25519-cert.pub
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIItpNkjaH8o51VKydwHYbbLxXMtf4euzojFKPxz+XqdwAAAAIG1vJNH1p8l8HlmYMT/vHGTjEnIul7ORQhutNnKiXlgqAAAAAAAAAAAAAAABAAAACWF0bGFzLmh5cAAAABsAAAAJYXRsYXMuaHlwAAAACmh5cGVydmlzb3IAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgdmt4SFL+swd8kHsh6cQR+TfzMKObJx75fYBbHNT83zUAAABTAAAAC3NzaC1lZDI1NTE5AAAAQIW4tC+FJA6bKFUfRVcHLWz1u3ZL/GRTWD2WCW4ApHq7no6ODeMwE10noNt/42mwYjFmjwR+cd9EuMyUErXmaw8= root@atlas
|
|
@ -8,12 +8,92 @@
|
||||||
dataDisk.enable = true;
|
dataDisk.enable = true;
|
||||||
|
|
||||||
ssh = {
|
ssh = {
|
||||||
|
useCertificates = true;
|
||||||
hostCert = builtins.readFile ./jefke_host_ed25519-cert.pub;
|
hostCert = builtins.readFile ./jefke_host_ed25519-cert.pub;
|
||||||
userCert = builtins.readFile ./jefke_user_ed25519-cert.pub;
|
userCert = builtins.readFile ./jefke_user_ed25519-cert.pub;
|
||||||
};
|
};
|
||||||
|
|
||||||
terraformDatabase.enable = true;
|
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%"; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
atlas = {
|
||||||
|
name = "atlas";
|
||||||
|
hostname = "atlas.hyp";
|
||||||
|
|
||||||
|
specificConfig = {
|
||||||
|
custom = {
|
||||||
|
ssh = {
|
||||||
|
useCertificates = true;
|
||||||
|
hostCert = builtins.readFile ./atlas_host_ed25519-cert.pub;
|
||||||
|
userCert = builtins.readFile ./atlas_user_ed25519-cert.pub;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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%"; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,14 @@ in {
|
||||||
options = {
|
options = {
|
||||||
custom = {
|
custom = {
|
||||||
ssh = {
|
ssh = {
|
||||||
|
useCertificates = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Whether to use certificates at all.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
hostCert = lib.mkOption {
|
hostCert = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
description = ''
|
description = ''
|
||||||
|
@ -42,7 +50,7 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = lib.mkIf cfg.useCertificates {
|
||||||
services.openssh = {
|
services.openssh = {
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
HostCertificate ${hostCert}
|
HostCertificate ${hostCert}
|
||||||
|
|
|
@ -31,4 +31,3 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
BIN
secrets/atlas_host_ed25519.age
Normal file
BIN
secrets/atlas_host_ed25519.age
Normal file
Binary file not shown.
BIN
secrets/atlas_user_ed25519.age
Normal file
BIN
secrets/atlas_user_ed25519.age
Normal file
Binary file not shown.
|
@ -12,6 +12,15 @@ let
|
||||||
"postgresql_server.key.age"
|
"postgresql_server.key.age"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
atlas = {
|
||||||
|
publicKeys = [
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKZ1OGe8jLyc+72SFUnW4FOKbpqHs7Mym85ESBN4HWV7 pim@x260"
|
||||||
|
];
|
||||||
|
encryptedFiles = [
|
||||||
|
"atlas_host_ed25519.age"
|
||||||
|
"atlas_user_ed25519.age"
|
||||||
|
];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
in lib.attrsets.mergeAttrsList (builtins.map ({ publicKeys, encryptedFiles }:
|
in lib.attrsets.mergeAttrsList (builtins.map ({ publicKeys, encryptedFiles }:
|
||||||
lib.attrsets.mergeAttrsList (builtins.map
|
lib.attrsets.mergeAttrsList (builtins.map
|
||||||
|
|
36
terraform/main.tf
Normal file
36
terraform/main.tf
Normal 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 = 4096
|
||||||
|
storage = 25
|
||||||
|
# providers = {
|
||||||
|
# libvirt = libvirt.jefke
|
||||||
|
# }
|
||||||
|
}
|
7
terraform/modules/README.md
Normal file
7
terraform/modules/README.md
Normal 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.
|
15
terraform/modules/debian/files/cloud_init.cfg.tftpl
Normal file
15
terraform/modules/debian/files/cloud_init.cfg.tftpl
Normal 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
|
17
terraform/modules/debian/files/get_cert.sh
Executable file
17
terraform/modules/debian/files/get_cert.sh
Executable 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}'
|
9
terraform/modules/debian/files/network_config.cfg
Normal file
9
terraform/modules/debian/files/network_config.cfg
Normal 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
|
54
terraform/modules/debian/main.tf
Normal file
54
terraform/modules/debian/main.tf
Normal 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
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
13
terraform/modules/debian/variables.tf
Normal file
13
terraform/modules/debian/variables.tf
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
variable "name" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "ram" {
|
||||||
|
type = number
|
||||||
|
description = "In MiB"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "storage" {
|
||||||
|
type = number
|
||||||
|
description = "In GiB"
|
||||||
|
}
|
44
terraform/modules/setup/main.tf
Normal file
44
terraform/modules/setup/main.tf
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue