feat(nixos): add control center host with hypervisor gitops service
This commit is contained in:
parent
89cec4e0f3
commit
dc9b7a30d9
10 changed files with 259 additions and 42 deletions
|
|
@ -70,7 +70,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
hosts = {
|
hosts = {
|
||||||
# Hosts will be populated here as they are migrated to the v2 branch.
|
izanagi.modules = [ ./hosts/izanagi ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
52
nixos/hosts/izanagi/default.nix
Normal file
52
nixos/hosts/izanagi/default.nix
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
networking.hostName = "izanagi";
|
||||||
|
|
||||||
|
homelab = {
|
||||||
|
# Enable the standard guest VM configuration
|
||||||
|
virtualisation.guest.enable = true;
|
||||||
|
|
||||||
|
# Enable standard GitOps for the Control Center itself
|
||||||
|
common.gitops.enable = true;
|
||||||
|
# We might pull from our Forgejo instance eventually, but for bootstrap
|
||||||
|
# it might need to pull from Github or the local Gitea if it's up.
|
||||||
|
# common.gitops.repoUrl = "https://git.depeuter.dev/Bos55/nix-config.git";
|
||||||
|
|
||||||
|
services = {
|
||||||
|
openssh.enable = true;
|
||||||
|
|
||||||
|
# Enable the Hypervisor GitOps service to provision OTHER VMs
|
||||||
|
hypervisor-gitops = {
|
||||||
|
enable = true;
|
||||||
|
repoUrl = "https://git.depeuter.dev/Bos55/nix-config.git";
|
||||||
|
pollInterval = "hourly";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sops = {
|
||||||
|
defaultSopsFile = ./secrets.yaml;
|
||||||
|
defaultSopsFormat = "yaml";
|
||||||
|
age.keyFile = "/var/lib/sops-age/keys.txt"; # Injected via Cloud-Init during Phase 4
|
||||||
|
|
||||||
|
secrets = {
|
||||||
|
proxmox_api_token.owner = "root";
|
||||||
|
forgejo_token.owner = "root";
|
||||||
|
};
|
||||||
|
|
||||||
|
templates."hypervisor-gitops.env".content = ''
|
||||||
|
PROXMOX_VE_API_TOKEN=${config.sops.placeholder.proxmox_api_token}
|
||||||
|
FORGEJO_TOKEN=${config.sops.placeholder.forgejo_token}
|
||||||
|
PROXMOX_VE_ENDPOINT=https://mikoshi:8006/
|
||||||
|
PROXMOX_VE_INSECURE=true
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Make the secrets available to the hypervisor-gitops service via EnvironmentFile
|
||||||
|
systemd.services.hypervisor-gitops.serviceConfig.EnvironmentFile = [
|
||||||
|
config.sops.templates."hypervisor-gitops.env".path
|
||||||
|
];
|
||||||
|
|
||||||
|
system.stateVersion = "24.05";
|
||||||
|
}
|
||||||
5
nixos/hosts/izanagi/secrets.yaml
Normal file
5
nixos/hosts/izanagi/secrets.yaml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# This is a placeholder SOPS file.
|
||||||
|
# You must encrypt it with sops using your age key before deploying.
|
||||||
|
# sops -e -i secrets.yaml
|
||||||
|
proxmox_api_token: "PLACEHOLDER_TOKEN"
|
||||||
|
forgejo_token: "PLACEHOLDER_TOKEN"
|
||||||
|
|
@ -6,42 +6,7 @@ let
|
||||||
updateScript = pkgs.writeShellApplication {
|
updateScript = pkgs.writeShellApplication {
|
||||||
name = "homelab-gitops-update";
|
name = "homelab-gitops-update";
|
||||||
runtimeInputs = [ pkgs.git pkgs.nixos-rebuild pkgs.jq pkgs.coreutils ];
|
runtimeInputs = [ pkgs.git pkgs.nixos-rebuild pkgs.jq pkgs.coreutils ];
|
||||||
text = ''
|
text = builtins.readFile ../../../../scripts/nixos-sync.sh;
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
REMOTE_URL="${cfg.repoUrl}"
|
|
||||||
BRANCH="${cfg.branch}"
|
|
||||||
|
|
||||||
echo "Checking remote hash for $REMOTE_URL branch $BRANCH..."
|
|
||||||
|
|
||||||
# Fetch remote hash, fallback to unknown if it fails
|
|
||||||
REMOTE_HASH=$(git ls-remote "$REMOTE_URL" "refs/heads/$BRANCH" | awk '{print $1}' || true)
|
|
||||||
|
|
||||||
if [ -z "$REMOTE_HASH" ]; then
|
|
||||||
echo "WARNING: Could not fetch remote hash. Forcing rebuild to be safe."
|
|
||||||
REMOTE_HASH="unknown_remote"
|
|
||||||
fi
|
|
||||||
|
|
||||||
LOCAL_HASH="unknown_local"
|
|
||||||
if [ -f /run/current-system/configurationRevision ]; then
|
|
||||||
LOCAL_HASH=$(cat /run/current-system/configurationRevision)
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Remote hash: $REMOTE_HASH"
|
|
||||||
echo "Local hash: $LOCAL_HASH"
|
|
||||||
|
|
||||||
if [ "$REMOTE_HASH" = "$LOCAL_HASH" ] && [ "$REMOTE_HASH" != "unknown_remote" ] && [ "$LOCAL_HASH" != "unknown" ]; then
|
|
||||||
echo "Hashes match. No update needed."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Hashes differ or unknown. Triggering nixos-rebuild..."
|
|
||||||
|
|
||||||
# Trigger the build and switch
|
|
||||||
nixos-rebuild switch --flake "git+$REMOTE_URL?dir=nixos&ref=$BRANCH"
|
|
||||||
|
|
||||||
echo "Update successful."
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
in {
|
in {
|
||||||
|
|
@ -68,14 +33,13 @@ in {
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
# 1. Systemd Service and Timer for polling
|
|
||||||
systemd.services.homelab-gitops = {
|
systemd.services.homelab-gitops = {
|
||||||
description = "Homelab GitOps Update Service";
|
description = "Homelab GitOps Update Service";
|
||||||
wants = [ "network-online.target" ];
|
wants = [ "network-online.target" ];
|
||||||
after = [ "network-online.target" ];
|
after = [ "network-online.target" ];
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
ExecStart = "${updateScript}/bin/homelab-gitops-update";
|
ExecStart = "${updateScript}/bin/homelab-gitops-update ${cfg.repoUrl} ${cfg.branch}";
|
||||||
# Must run as root to rebuild the system
|
# Must run as root to rebuild the system
|
||||||
User = "root";
|
User = "root";
|
||||||
};
|
};
|
||||||
|
|
@ -91,7 +55,6 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# 2. Webhook listener for instant trigger
|
|
||||||
sops.secrets."webhook-secret" = {};
|
sops.secrets."webhook-secret" = {};
|
||||||
|
|
||||||
services.webhook = {
|
services.webhook = {
|
||||||
|
|
@ -121,7 +84,6 @@ in {
|
||||||
# Inject the secret as an environment variable into the webhook service
|
# Inject the secret as an environment variable into the webhook service
|
||||||
systemd.services.webhook.serviceConfig.EnvironmentFile = config.sops.secrets."webhook-secret".path;
|
systemd.services.webhook.serviceConfig.EnvironmentFile = config.sops.secrets."webhook-secret".path;
|
||||||
|
|
||||||
# 3. Builder Configuration
|
|
||||||
sops.secrets."builder-ssh-key" = lib.mkIf cfg.useBuilder {};
|
sops.secrets."builder-ssh-key" = lib.mkIf cfg.useBuilder {};
|
||||||
|
|
||||||
nix.buildMachines = lib.mkIf cfg.useBuilder [
|
nix.buildMachines = lib.mkIf cfg.useBuilder [
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,6 @@
|
||||||
imports = [
|
imports = [
|
||||||
./actions
|
./actions
|
||||||
./openssh
|
./openssh
|
||||||
|
./hypervisor-gitops
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
64
nixos/modules/services/hypervisor-gitops/default.nix
Normal file
64
nixos/modules/services/hypervisor-gitops/default.nix
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.homelab.services.hypervisor-gitops;
|
||||||
|
|
||||||
|
hypervisorSyncScript = pkgs.writeShellApplication {
|
||||||
|
name = "hypervisor-sync";
|
||||||
|
runtimeInputs = with pkgs; [ git opentofu coreutils ];
|
||||||
|
text = builtins.readFile ../../../../scripts/hypervisor-sync.sh;
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
options.homelab.services.hypervisor-gitops = {
|
||||||
|
enable = mkEnableOption "Hypervisor GitOps Service";
|
||||||
|
|
||||||
|
repoUrl = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "The URL of the git repository to pull";
|
||||||
|
};
|
||||||
|
|
||||||
|
# TODO Replace with webhooks
|
||||||
|
pollInterval = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "hourly";
|
||||||
|
description = "Systemd calendar event for polling interval";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
git
|
||||||
|
opentofu
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd.services.hypervisor-gitops = {
|
||||||
|
description = "Hypervisor GitOps Polling Service";
|
||||||
|
|
||||||
|
# We need network access to reach Forgejo and Proxmox API
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
User = "root"; # Needs root to read SOPS secrets potentially
|
||||||
|
|
||||||
|
# We will create a state directory for the repo
|
||||||
|
StateDirectory = "hypervisor-gitops";
|
||||||
|
WorkingDirectory = "/var/lib/hypervisor-gitops";
|
||||||
|
|
||||||
|
ExecStart = "${hypervisorSyncScript}/bin/hypervisor-sync ${cfg.repoUrl} opentofu/nodes/mikoshi";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.hypervisor-gitops = {
|
||||||
|
description = "Timer for Hypervisor GitOps Service";
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = cfg.pollInterval;
|
||||||
|
Persistent = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ in {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.listOf lib.types.str;
|
||||||
default = [
|
default = [
|
||||||
# HomeLab > NixOS > admin > ssh
|
# HomeLab > NixOS > admin > ssh
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGWIOOEqTy8cWKpENVbzD4p7bsQgQb/Dgpzk8i0dZ00T"
|
"sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIHOoTp+e6qWGn4Sco5CZ6G0zrX5NAQBpLlDVirncJ/HqAAAABHNzaDo="
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -28,3 +28,57 @@ output "control_center_api_token" {
|
||||||
sensitive = true
|
sensitive = true
|
||||||
description = "The secret API token for control-center@pve."
|
description = "The secret API token for control-center@pve."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "proxmox_virtual_environment_vm" "control_center" {
|
||||||
|
depends_on = [module.proxmox_node]
|
||||||
|
|
||||||
|
name = "izanagi"
|
||||||
|
description = "Managed by OpenTofu - GitOps Control Center"
|
||||||
|
tags = ["infrastructure", "gitops"]
|
||||||
|
node_name = "mikoshi"
|
||||||
|
vm_id = 100001000
|
||||||
|
|
||||||
|
on_boot = true
|
||||||
|
|
||||||
|
pool_id = "core"
|
||||||
|
|
||||||
|
cpu {
|
||||||
|
cores = 2
|
||||||
|
type = "x86-64-v2-AES"
|
||||||
|
}
|
||||||
|
|
||||||
|
memory {
|
||||||
|
dedicated = 2048
|
||||||
|
}
|
||||||
|
|
||||||
|
agent {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
network_device {
|
||||||
|
bridge = "vmbr0"
|
||||||
|
}
|
||||||
|
|
||||||
|
disk {
|
||||||
|
datastore_id = "data"
|
||||||
|
file_id = "local:iso/nixos-minimal.iso" # TODO Replace with actual ISO or use Clone
|
||||||
|
interface = "scsi0"
|
||||||
|
size = 20
|
||||||
|
file_format = "raw"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cloud-Init for initial SSH access and SOPS age key injection
|
||||||
|
initialization {
|
||||||
|
ip_config {
|
||||||
|
ipv4 {
|
||||||
|
address = "dhcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
user_account {
|
||||||
|
username = "gh0st"
|
||||||
|
keys = [
|
||||||
|
"sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIHOoTp+e6qWGn4Sco5CZ6G0zrX5NAQBpLlDVirncJ/HqAAAABHNzaDo="
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
39
scripts/hypervisor-sync.sh
Normal file
39
scripts/hypervisor-sync.sh
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# This script pulls the latest changes from the Git repository
|
||||||
|
# and runs OpenTofu to provision the hypervisor state.
|
||||||
|
# Usage: ./hypervisor-sync.sh <REPO_URL> <OPENTOFU_DIR>
|
||||||
|
|
||||||
|
REPO_URL=${1:-"https://git.depeuter.dev/Bos55/nix-config.git"}
|
||||||
|
OPENTOFU_DIR=${2:-"opentofu/nodes/mikoshi"}
|
||||||
|
|
||||||
|
echo "Starting Hypervisor GitOps sync..."
|
||||||
|
|
||||||
|
if [ ! -d "nix-config" ]; then
|
||||||
|
echo "Cloning repository from $REPO_URL..."
|
||||||
|
git clone "$REPO_URL" nix-config
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd nix-config || exit
|
||||||
|
|
||||||
|
git fetch origin main
|
||||||
|
|
||||||
|
LOCAL=$(git rev-parse HEAD)
|
||||||
|
REMOTE=$(git rev-parse origin/main)
|
||||||
|
|
||||||
|
if [ "$LOCAL" = "$REMOTE" ]; then
|
||||||
|
echo "Already up to date. Nothing to do."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Changes detected. Updating from $LOCAL to $REMOTE..."
|
||||||
|
git reset --hard origin/main
|
||||||
|
|
||||||
|
echo "Applying OpenTofu changes in $OPENTOFU_DIR..."
|
||||||
|
cd "$OPENTOFU_DIR" || exit
|
||||||
|
|
||||||
|
tofu init -upgrade
|
||||||
|
|
||||||
|
tofu apply -auto-approve
|
||||||
|
|
||||||
|
echo "Hypervisor GitOps sync completed successfully."
|
||||||
40
scripts/nixos-sync.sh
Normal file
40
scripts/nixos-sync.sh
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# This script checks the remote Git repository for changes
|
||||||
|
# and triggers a nixos-rebuild if a new commit is found.
|
||||||
|
# Usage: ./nixos-sync.sh <REPO_URL> <BRANCH>
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_URL=${1:-"https://git.depeuter.dev/Bos55/nix-config.git"}
|
||||||
|
BRANCH=${2:-"main"}
|
||||||
|
|
||||||
|
echo "Checking remote hash for $REPO_URL branch $BRANCH..."
|
||||||
|
|
||||||
|
# Fetch remote hash, fallback to unknown if it fails
|
||||||
|
REMOTE_HASH=$(git ls-remote "$REPO_URL" "refs/heads/$BRANCH" | awk '{print $1}' || true)
|
||||||
|
|
||||||
|
if [ -z "$REMOTE_HASH" ]; then
|
||||||
|
echo "WARNING: Could not fetch remote hash. Forcing rebuild to be safe."
|
||||||
|
REMOTE_HASH="unknown_remote"
|
||||||
|
fi
|
||||||
|
|
||||||
|
LOCAL_HASH="unknown_local"
|
||||||
|
if [ -f /run/current-system/configurationRevision ]; then
|
||||||
|
LOCAL_HASH=$(cat /run/current-system/configurationRevision)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Remote hash: $REMOTE_HASH"
|
||||||
|
echo "Local hash: $LOCAL_HASH"
|
||||||
|
|
||||||
|
if [ "$REMOTE_HASH" = "$LOCAL_HASH" ] && [ "$REMOTE_HASH" != "unknown_remote" ] && [ "$LOCAL_HASH" != "unknown" ]; then
|
||||||
|
echo "Hashes match. No update needed."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Hashes differ or unknown. Triggering nixos-rebuild..."
|
||||||
|
|
||||||
|
# Trigger the build and switch
|
||||||
|
nixos-rebuild switch --flake "git+$REPO_URL?dir=nixos&ref=$BRANCH"
|
||||||
|
|
||||||
|
echo "Update successful."
|
||||||
Loading…
Add table
Add a link
Reference in a new issue