feat(infra): introduce Terranix

This requires the support for monorepo-terraform-state.s3.dgnum.eu being
available.

`.credentials/` is age-encrypted using only my key for now until we
figure out the right mechanism.

Signed-off-by: Ryan Lahfa <ryan@dgnum.eu>
This commit is contained in:
Ryan Lahfa 2024-10-10 12:27:40 +02:00
parent f20353b727
commit 4d68bfda2a
9 changed files with 135 additions and 0 deletions

Binary file not shown.

6
.credentials/secrets.nix Normal file
View file

@ -0,0 +1,6 @@
let
keys = import ../keys;
in
{
"admin-environment.age".publicKeys = keys.rootKeys;
}

6
.gitignore vendored
View file

@ -9,3 +9,9 @@ result-*
*.qcow2 *.qcow2
.gcroots .gcroots
.pre-commit-config.yaml .pre-commit-config.yaml
# Ignore Terraform configuration file
config.tf.json
# Ignore Terraform stuff
.terraform

38
.terraform.lock.hcl Normal file
View file

@ -0,0 +1,38 @@
# This file is maintained automatically by "tofu init".
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/numtide/secret" {
version = "1.2.1"
constraints = "~> 1.2.1"
hashes = [
"h1:t2z3CjxVsXjKb3g59WGkLtvDIR4NzLU7UFEcyAgF2C0=",
"zh:17cbc7f3b90ee2b3ae5adfc3bd9cb70166a5ffbd8e642e64afa7cb0e32a34bae",
"zh:5d66ce2aea25fc3c12cec6fc569b8ff314df6d773b9c3449983a4e9cde8347c7",
"zh:67d02e96bf0d07f2fcf16ce9427a7a26f53e695676405d0c2b815808f950411d",
"zh:77c3c05681ce199e6b0e2e5a2dfe418f61ae8863d527e7a7d47a9699d912683b",
"zh:7f37e633b4f94ba9f347cfe68d44f80fe066188feb954b13ee0f621caae4121d",
"zh:ea16bbe494c6ddd0af7bbea9554474c387517db4e7f0d15513bb29ff893871bc",
]
}
provider "registry.opentofu.org/raitobezarius/garage" {
version = "1.0.3"
constraints = "~> 1.0.3"
hashes = [
"h1:QKbZcU7u9OG1t/h4S3+pXS3sOUfVMmfLTiYh5L5j1rE=",
"zh:04f220a2baf4bd1bae07888a1c311cacd6076c209de83adbe573525fc50f2ea4",
"zh:078938d5fa07e024d779c664823427af28935bbeb77e0ff940bac3e7bc41f1e8",
"zh:2dd58a2d82094a1b07ff1b6de57e4a0d96e1f20abecd4f70a6469079b46b76d9",
"zh:325da7a74b1c84f934b38134d7c419253292aeed6f6836a2fb37f42d13a8ff67",
"zh:3ca9230ef87e70691b24fd83d40bb5b6a08f0b91ab26cbb2e692f92155b6d179",
"zh:45ef683a18a5053c93c691d08f3903fd4918467dfa056b1c274207de8a6aeb74",
"zh:4c9ee6c34b07c209c5daf1e9ff182f828667e54a90a683bc11cdcea86e4f8ef7",
"zh:5f0bb6524b2fffa606e0e3585af93dfc31b611c7abf55e4371ae5fc36e85972c",
"zh:7a3495dc211164c7d4042769c20d7111c767d0fd5908742e0766281c70d7d184",
"zh:7ce79867cdd4b1f7028da811cd5cb271a46820c79c0328a1221dd3bb6215c631",
"zh:93278861ee6bcb64e23bd1268f79b02035fba4fca0a98607a98f46abf8dfdf83",
"zh:937e681beea8b0dd899557f2a194c8128bd8810417ff04954bc9958ff826e980",
"zh:cae6e1598dd32f23f3900c41e50a6ece7d9456dbd033d855bb238ac21539d67b",
"zh:f6f7556ba7d5578604290170a709e00140be6d7f8a510a20bce49a9a23d75e5f",
]
}

View file

@ -67,9 +67,18 @@ let
commitizen.enable = true; commitizen.enable = true;
}; };
}; };
terranixConfig = import "${sources.terranix}/core" {
inherit pkgs;
strip_nulls = true;
terranix_config.imports = [ ./terranix ];
};
terranixConfigFile = (pkgs.formats.json { }).generate "config.tf.json" terranixConfig.config;
in in
{ {
inherit terranixConfigFile terranixConfig;
nodes = builtins.mapAttrs ( nodes = builtins.mapAttrs (
host: { site, ... }: "${host}.${site}.infra.dgnum.eu" host: { site, ... }: "${host}.${site}.infra.dgnum.eu"
) (import ./meta/nodes.nix); ) (import ./meta/nodes.nix);
@ -83,11 +92,36 @@ in
name = "dgnum-infra"; name = "dgnum-infra";
packages = [ packages = [
(pkgs.writeShellScriptBin "tf" ''
set -eo pipefail
ln -snf ${terranixConfigFile} config.tf.json
exec ${pkgs.lib.getExe pkgs.opentofu} "$@"
'')
(pkgs.writeShellScriptBin "decryptAndSourceEnvironment" ''
set -eo pipefail
# TODO: don't hardcode me.
SECRET_FILE=".credentials/admin-environment.age"
IDENTITIES=()
for identity in [ "$HOME/.ssh/id_ed25519" "$HOME/.ssh/id_rsa" ]; do
test -r "$identity" || continue
IDENTITIES+=(-i)
IDENTITIES+=("$identity")
done
test "''${#IDENTITIES[@]}" -eq 0 && echo "[agenix-shell] WARNING: no readable identities found!"
test -f "$SECRET_FILE" || echo "[agenix-shell] WARNING: encrypted environment file $SECRET_FILE not found!"
export eval $(${pkgs.lib.getExe pkgs.rage} --decrypt "''${IDENTITIES[@]}" -o - $SECRET_FILE)
echo "[agenix-shell] Repository-wide secrets loaded in the environment."
'')
(pkgs.nixos-generators.overrideAttrs (_: { (pkgs.nixos-generators.overrideAttrs (_: {
version = "1.8.0-unstable"; version = "1.8.0-unstable";
src = builtins.storePath sources.nixos-generators; src = builtins.storePath sources.nixos-generators;
})) }))
pkgs.npins pkgs.npins
pkgs.rage
(pkgs.callPackage ./lib/colmena { inherit (nix-pkgs) colmena; }) (pkgs.callPackage ./lib/colmena { inherit (nix-pkgs) colmena; })
(pkgs.callPackage "${sources.agenix}/pkgs/agenix.nix" { }) (pkgs.callPackage "${sources.agenix}/pkgs/agenix.nix" { })
@ -97,6 +131,8 @@ in
shellHook = '' shellHook = ''
${git-checks.shellHook} ${git-checks.shellHook}
# If we want to export these environments, we need to source it, not call it.
source $(which decryptAndSourceEnvironment)
''; '';
preferLocalBuild = true; preferLocalBuild = true;

View file

@ -300,6 +300,21 @@
"url": null, "url": null,
"hash": "11vvfxw2sznc155x0xlgl00g6n9sr90xa0b1hr14vchg7gkz46r5" "hash": "11vvfxw2sznc155x0xlgl00g6n9sr90xa0b1hr14vchg7gkz46r5"
}, },
"terranix": {
"type": "GitRelease",
"repository": {
"type": "GitHub",
"owner": "terranix",
"repo": "terranix"
},
"pre_releases": false,
"version_upper_bound": null,
"release_prefix": null,
"version": "2.7.0",
"revision": "00710f39f38a0a654a2c4fd96cbb988b4f4cedfa",
"url": "https://api.github.com/repos/terranix/terranix/tarball/2.7.0",
"hash": "1wsyhsdsjw6xlhpkhaqvia3x0na3nx2vamcb2rbcbdmb7ra1y9f6"
},
"wp4nix": { "wp4nix": {
"type": "Git", "type": "Git",
"repository": { "repository": {

7
terranix/common.nix Normal file
View file

@ -0,0 +1,7 @@
{
# Until we get some kind of KMS operational, store secrets in the state file.
terraform.required_providers.secret = {
version = "~> 1.2.1";
source = "numtide/secret";
};
}

6
terranix/default.nix Normal file
View file

@ -0,0 +1,6 @@
{
imports = [
./common.nix
./state.nix
];
}

21
terranix/state.nix Normal file
View file

@ -0,0 +1,21 @@
{
# We use terraform.backend.s3 directly instead of the type-checked Terranix
# backend.s3 options. The latter does not support setting arbitrary s3
# endpoints.
#
# Note: currently requires the user to provide AWS_ACCESS_KEY_ID as well as
# AWS_SECRET_ACCESS_KEY in their environment variables.
terraform.backend.s3 = {
endpoints.s3 = "s3.dgnum.eu";
region = "garage";
bucket = "monorepo-terraform-state";
key = "state";
# It's just a dumb Garage server, don't try to be smart.
skip_credentials_validation = true;
skip_region_validation = true;
skip_requesting_account_id = true;
skip_metadata_api_check = true;
};
}