Compare commits

...

3 commits

Author SHA1 Message Date
862d6ba2b7
tf command 2025-08-10 12:26:47 +02:00
42d6eaff25
data model: add tf deployment 2025-08-10 12:26:47 +02:00
3932a0ad0a
data model: add tf deployment 2025-08-10 12:26:47 +02:00
8 changed files with 209 additions and 3 deletions

View file

@ -2,7 +2,6 @@
inputs,
# sources,
lib,
# providers,
...
}:

View file

@ -18,7 +18,6 @@
nodes.deployer =
{ pkgs, ... }:
{
# FIXME: sad times
system.extraDependencies = with pkgs; [
jq
@ -40,8 +39,21 @@
hello.fail("hello 1>&2")
cowsay.fail("cowsay 1>&2")
# SETTINGS.BIN_PATH
# CONFIG
env = {
"PATH": settings.bin_path,
# "TF_LOG": "info",
} | {
# pass in form info to our deployment
# FIXME: ensure sensitive info is protected
f"TF_VAR_{k}": v if isinstance(v, str) else json.dumps(v) for k, v in json.loads(config.model_dump_json()).items()
}
# USE ENV
# SETTINGS.REPO_DIR
with subtest("Run the deployment"):
deployer.succeed("nixops4 apply check-deployment-basic --show-trace --no-interactive 1>&2")
deployer.succeed("cd ${settings.repo_dir}/infra/operator && tofu apply --auto-approve -lock=false -parallelism=1")
with subtest("Check the deployment"):
hello.succeed("hello 1>&2")

View file

@ -26,6 +26,14 @@ let
};
};
deployment = attrTag {
tf-ssh = mkOption {
description = "A Terraform deployment by SSH to update a single existing NixOS host";
type = types.attrset;
};
tf-ssh = mkOption {
description = "A Terraform deployment by SSH to update a single existing NixOS host";
type = types.attrset;
};
};
in
{

38
infra/dev/main.nix Normal file
View file

@ -0,0 +1,38 @@
let
vm_domain = "abundos.eu";
in
{
module."nixos" =
builtins.mapAttrs
(service: hostname: {
source = "../sync-nix";
inherit vm_domain hostname;
config_tf = {
terraform = {
inherit hostname;
domain = vm_domain;
};
};
config_nix = ''
{
imports = [
# shared NixOS config
$${path.root}/../common/shared.nix
# FIXME: separate template options by service
$${path.root}/options.nix
# for service `forgejo` import `forgejo.nix`
$${path.root}/../../machines/dev/${hostname}/${service}.nix
# FIXME: get VM details from TF
$${path.root}/../../machines/dev/${hostname}
];
}
'';
})
{
# wiki = "vm02187" # does not resolve
# forgejo = "vm02116" # does not resolve
# TODO: move these to a separate `host` dir
dns = "fedi200";
# fedipanel = "fedi201";
};
}

91
infra/sync-nix/main.tf Normal file
View file

@ -0,0 +1,91 @@
locals {
system = "x86_64-linux"
# dependency paths pre-calculated from npins
pins = jsondecode(file("${path.module}/.npins.json"))
# nix path: expose pins, use nixpkgs in flake commands (`nix run`)
nix_path = "${join(":", [for name, dir in local.pins : "${name}=${dir}"])}:flake=${local.pins["nixpkgs"]}:flake"
}
# hash of our code directory, used to trigger re-deploy
# FIXME calculate separately to reduce false positives
data "external" "hash" {
program = ["sh", "-c", "echo \"{\\\"hash\\\":\\\"$(nix-hash ..)\\\"}\""]
}
# TF resource to build and deploy NixOS instances.
resource "terraform_data" "nixos" {
# trigger rebuild/deploy if (FIXME?) any potentially used config/code changed,
# preventing these (20+s, build being bottleneck) when nothing changed.
# terraform-nixos separates these to only deploy if instantiate changed,
# yet building even then - which may be not as bad using deploy on remote.
# having build/deploy one resource reflects wanting to prevent no-op rebuilds
# over preventing (with less false positives) no-op deployments,
# as i could not find a way to do prevent no-op rebuilds without merging them:
# - generic resources cannot have outputs, while we want info from the instantiation (unless built on host?).
# - `data` always runs, which is slow for deploy and especially build.
triggers_replace = [
data.external.hash.result,
var.hostname,
var.config_nix,
var.config_tf,
]
provisioner "local-exec" {
# directory to run the script from. we use the TF project root dir,
# here as a path relative from where TF is run from,
# matching calling modules' expectations on config_nix locations.
# note that absolute paths can cause false positives in triggers,
# so are generally discouraged in TF.
working_dir = path.root
environment = {
# nix path used on build, lets us refer to e.g. nixpkgs like `<nixpkgs>`
NIX_PATH = local.nix_path
}
# TODO: refactor back to command="ignoreme" interpreter=concat([]) to protect sensitive data from error logs?
# TODO: build on target?
command = <<-EOF
set -euo pipefail
# INSTANTIATE
command=(
nix-instantiate
--expr
'import ./nixos.nix {
system = "${local.system}";
configuration =
${var.config_nix} //
builtins.fromJSON "${replace(jsonencode(var.config_tf), "\"", "\\\"")}" //
{
nix.nixPath = [ "${local.nix_path}" ];
};
}'
)
# instantiate the config in /nix/store
"$${command[@]}" -A out_path
# get the other info
json="$("$${command[@]}" --eval --strict --json)"
# DEPLOY
declare substituters trusted_public_keys drv_path
# set our variables using the json object
eval "export $(echo $json | jaq -r 'to_entries | map("\(.key)=\(.value)") | @sh')"
host="root@${var.hostname}.${var.vm_domain}" # FIXME: #24
buildArgs=(
--option extra-binary-caches https://cache.nixos.org/
--option substituters $substituters
--option trusted-public-keys $trusted_public_keys
)
sshOpts=(
-o BatchMode=yes
-o StrictHostKeyChecking=no
)
# get the realized derivation to deploy
outPath=$(nix-store --realize "$drv_path" "$${buildArgs[@]}")
# deploy the config by nix-copy-closure
NIX_SSHOPTS="$${sshOpts[*]}" nix-copy-closure --to "$host" "$outPath" --gzip --use-substitutes
# switch the remote host to the config
ssh "$${sshOpts[@]}" "$host" "nix-env --profile /nix/var/nix/profiles/system --set $outPath; $outPath/bin/switch-to-configuration switch"
EOF
}
}

14
infra/sync-nix/nixos.nix Normal file
View file

@ -0,0 +1,14 @@
{
configuration,
system ? builtins.currentSystem,
}:
let
sources = import ../../npins;
os = import "${sources.nixpkgs}/nixos" { inherit system configuration; };
in
{
substituters = builtins.concatStringsSep " " os.config.nix.settings.substituters;
trusted_public_keys = builtins.concatStringsSep " " os.config.nix.settings.trusted-public-keys;
drv_path = os.config.system.build.toplevel.drvPath;
out_path = os.config.system.build.toplevel;
}

View file

@ -0,0 +1,27 @@
# TODO: generate nix module from `variables.tf`
# - TF -> JSON schema: https://melvinkoh.me/parsing-terraform-for-forms-clr4zq4tu000309juab3r1lf7
# - python313Packages.python-hcl2: hcl2tojson variables.tf
# - JSON schema -> nix
{ lib, ... }:
let
inherit (lib) mkOption types;
inherit (types) string attrsOf any;
in
{
options = {
vm_domain = mkOption {
type = string;
};
hostname = mkOption {
type = string;
};
config_nix = mkOption {
type = string;
default = { };
};
config_tf = mkOption {
type = attrsOf any;
default = { };
};
};
}

View file

@ -0,0 +1,17 @@
variable "vm_domain" {
type = string
}
variable "hostname" {
type = string
}
variable "config_nix" {
type = string
default = "{}"
}
variable "config_tf" {
type = map(any)
default = {}
}