Compare commits
7 Commits
homelab-de
...
deploy-tes
| Author | SHA1 | Date | |
|---|---|---|---|
|
38348c5980
|
|||
|
370cf2b03a
|
|||
|
7bc465b414
|
|||
|
8d7bc50108
|
|||
|
03e70ac094
|
|||
|
3b32c9479f
|
|||
|
b0d35f9a99
|
31
flake.nix
31
flake.nix
@@ -186,15 +186,6 @@
|
|||||||
./hosts/nats1
|
./hosts/nats1
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
testvm01 = nixpkgs.lib.nixosSystem {
|
|
||||||
inherit system;
|
|
||||||
specialArgs = {
|
|
||||||
inherit inputs self sops-nix;
|
|
||||||
};
|
|
||||||
modules = commonModules ++ [
|
|
||||||
./hosts/testvm01
|
|
||||||
];
|
|
||||||
};
|
|
||||||
vault01 = nixpkgs.lib.nixosSystem {
|
vault01 = nixpkgs.lib.nixosSystem {
|
||||||
inherit system;
|
inherit system;
|
||||||
specialArgs = {
|
specialArgs = {
|
||||||
@@ -204,13 +195,31 @@
|
|||||||
./hosts/vault01
|
./hosts/vault01
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
vaulttest01 = nixpkgs.lib.nixosSystem {
|
testvm01 = nixpkgs.lib.nixosSystem {
|
||||||
inherit system;
|
inherit system;
|
||||||
specialArgs = {
|
specialArgs = {
|
||||||
inherit inputs self sops-nix;
|
inherit inputs self sops-nix;
|
||||||
};
|
};
|
||||||
modules = commonModules ++ [
|
modules = commonModules ++ [
|
||||||
./hosts/vaulttest01
|
./hosts/testvm01
|
||||||
|
];
|
||||||
|
};
|
||||||
|
testvm02 = nixpkgs.lib.nixosSystem {
|
||||||
|
inherit system;
|
||||||
|
specialArgs = {
|
||||||
|
inherit inputs self sops-nix;
|
||||||
|
};
|
||||||
|
modules = commonModules ++ [
|
||||||
|
./hosts/testvm02
|
||||||
|
];
|
||||||
|
};
|
||||||
|
testvm03 = nixpkgs.lib.nixosSystem {
|
||||||
|
inherit system;
|
||||||
|
specialArgs = {
|
||||||
|
inherit inputs self sops-nix;
|
||||||
|
};
|
||||||
|
modules = commonModules ++ [
|
||||||
|
./hosts/testvm03
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,14 +13,17 @@
|
|||||||
../../common/vm
|
../../common/vm
|
||||||
];
|
];
|
||||||
|
|
||||||
# Test VM - exclude from DNS zone generation
|
# Host metadata (adjust as needed)
|
||||||
homelab.dns.enable = false;
|
|
||||||
|
|
||||||
homelab.host = {
|
homelab.host = {
|
||||||
tier = "test";
|
tier = "test"; # Start in test tier, move to prod after validation
|
||||||
priority = "low";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Enable Vault integration
|
||||||
|
vault.enable = true;
|
||||||
|
|
||||||
|
# Enable remote deployment via NATS
|
||||||
|
homelab.deploy.enable = true;
|
||||||
|
|
||||||
nixpkgs.config.allowUnfree = true;
|
nixpkgs.config.allowUnfree = true;
|
||||||
boot.loader.grub.enable = true;
|
boot.loader.grub.enable = true;
|
||||||
boot.loader.grub.device = "/dev/vda";
|
boot.loader.grub.device = "/dev/vda";
|
||||||
@@ -29,7 +32,7 @@
|
|||||||
networking.domain = "home.2rjus.net";
|
networking.domain = "home.2rjus.net";
|
||||||
networking.useNetworkd = true;
|
networking.useNetworkd = true;
|
||||||
networking.useDHCP = false;
|
networking.useDHCP = false;
|
||||||
services.resolved.enable = false;
|
services.resolved.enable = true;
|
||||||
networking.nameservers = [
|
networking.nameservers = [
|
||||||
"10.69.13.5"
|
"10.69.13.5"
|
||||||
"10.69.13.6"
|
"10.69.13.6"
|
||||||
@@ -39,7 +42,7 @@
|
|||||||
systemd.network.networks."ens18" = {
|
systemd.network.networks."ens18" = {
|
||||||
matchConfig.Name = "ens18";
|
matchConfig.Name = "ens18";
|
||||||
address = [
|
address = [
|
||||||
"10.69.13.101/24"
|
"10.69.13.20/24"
|
||||||
];
|
];
|
||||||
routes = [
|
routes = [
|
||||||
{ Gateway = "10.69.13.1"; }
|
{ Gateway = "10.69.13.1"; }
|
||||||
|
|||||||
72
hosts/testvm02/configuration.nix
Normal file
72
hosts/testvm02/configuration.nix
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../template2/hardware-configuration.nix
|
||||||
|
|
||||||
|
../../system
|
||||||
|
../../common/vm
|
||||||
|
];
|
||||||
|
|
||||||
|
# Host metadata (adjust as needed)
|
||||||
|
homelab.host = {
|
||||||
|
tier = "test"; # Start in test tier, move to prod after validation
|
||||||
|
};
|
||||||
|
|
||||||
|
# Enable Vault integration
|
||||||
|
vault.enable = true;
|
||||||
|
|
||||||
|
# Enable remote deployment via NATS
|
||||||
|
homelab.deploy.enable = true;
|
||||||
|
|
||||||
|
nixpkgs.config.allowUnfree = true;
|
||||||
|
boot.loader.grub.enable = true;
|
||||||
|
boot.loader.grub.device = "/dev/vda";
|
||||||
|
|
||||||
|
networking.hostName = "testvm02";
|
||||||
|
networking.domain = "home.2rjus.net";
|
||||||
|
networking.useNetworkd = true;
|
||||||
|
networking.useDHCP = false;
|
||||||
|
services.resolved.enable = true;
|
||||||
|
networking.nameservers = [
|
||||||
|
"10.69.13.5"
|
||||||
|
"10.69.13.6"
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd.network.enable = true;
|
||||||
|
systemd.network.networks."ens18" = {
|
||||||
|
matchConfig.Name = "ens18";
|
||||||
|
address = [
|
||||||
|
"10.69.13.21/24"
|
||||||
|
];
|
||||||
|
routes = [
|
||||||
|
{ Gateway = "10.69.13.1"; }
|
||||||
|
];
|
||||||
|
linkConfig.RequiredForOnline = "routable";
|
||||||
|
};
|
||||||
|
time.timeZone = "Europe/Oslo";
|
||||||
|
|
||||||
|
nix.settings.experimental-features = [
|
||||||
|
"nix-command"
|
||||||
|
"flakes"
|
||||||
|
];
|
||||||
|
nix.settings.tarball-ttl = 0;
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
vim
|
||||||
|
wget
|
||||||
|
git
|
||||||
|
];
|
||||||
|
|
||||||
|
# Open ports in the firewall.
|
||||||
|
# networking.firewall.allowedTCPPorts = [ ... ];
|
||||||
|
# networking.firewall.allowedUDPPorts = [ ... ];
|
||||||
|
# Or disable the firewall altogether.
|
||||||
|
networking.firewall.enable = false;
|
||||||
|
|
||||||
|
system.stateVersion = "25.11"; # Did you read the comment?
|
||||||
|
}
|
||||||
72
hosts/testvm03/configuration.nix
Normal file
72
hosts/testvm03/configuration.nix
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../template2/hardware-configuration.nix
|
||||||
|
|
||||||
|
../../system
|
||||||
|
../../common/vm
|
||||||
|
];
|
||||||
|
|
||||||
|
# Host metadata (adjust as needed)
|
||||||
|
homelab.host = {
|
||||||
|
tier = "test"; # Start in test tier, move to prod after validation
|
||||||
|
};
|
||||||
|
|
||||||
|
# Enable Vault integration
|
||||||
|
vault.enable = true;
|
||||||
|
|
||||||
|
# Enable remote deployment via NATS
|
||||||
|
homelab.deploy.enable = true;
|
||||||
|
|
||||||
|
nixpkgs.config.allowUnfree = true;
|
||||||
|
boot.loader.grub.enable = true;
|
||||||
|
boot.loader.grub.device = "/dev/vda";
|
||||||
|
|
||||||
|
networking.hostName = "testvm03";
|
||||||
|
networking.domain = "home.2rjus.net";
|
||||||
|
networking.useNetworkd = true;
|
||||||
|
networking.useDHCP = false;
|
||||||
|
services.resolved.enable = true;
|
||||||
|
networking.nameservers = [
|
||||||
|
"10.69.13.5"
|
||||||
|
"10.69.13.6"
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd.network.enable = true;
|
||||||
|
systemd.network.networks."ens18" = {
|
||||||
|
matchConfig.Name = "ens18";
|
||||||
|
address = [
|
||||||
|
"10.69.13.22/24"
|
||||||
|
];
|
||||||
|
routes = [
|
||||||
|
{ Gateway = "10.69.13.1"; }
|
||||||
|
];
|
||||||
|
linkConfig.RequiredForOnline = "routable";
|
||||||
|
};
|
||||||
|
time.timeZone = "Europe/Oslo";
|
||||||
|
|
||||||
|
nix.settings.experimental-features = [
|
||||||
|
"nix-command"
|
||||||
|
"flakes"
|
||||||
|
];
|
||||||
|
nix.settings.tarball-ttl = 0;
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
vim
|
||||||
|
wget
|
||||||
|
git
|
||||||
|
];
|
||||||
|
|
||||||
|
# Open ports in the firewall.
|
||||||
|
# networking.firewall.allowedTCPPorts = [ ... ];
|
||||||
|
# networking.firewall.allowedUDPPorts = [ ... ];
|
||||||
|
# Or disable the firewall altogether.
|
||||||
|
networking.firewall.enable = false;
|
||||||
|
|
||||||
|
system.stateVersion = "25.11"; # Did you read the comment?
|
||||||
|
}
|
||||||
5
hosts/testvm03/default.nix
Normal file
5
hosts/testvm03/default.nix
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{ ... }: {
|
||||||
|
imports = [
|
||||||
|
./configuration.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
vault-test-script = pkgs.writeShellApplication {
|
|
||||||
name = "vault-test";
|
|
||||||
text = ''
|
|
||||||
echo "=== Vault Secret Test ==="
|
|
||||||
echo "Secret path: hosts/vaulttest01/test-service"
|
|
||||||
|
|
||||||
if [ -f /run/secrets/test-service/password ]; then
|
|
||||||
echo "✓ Password file exists"
|
|
||||||
echo "Password length: $(wc -c < /run/secrets/test-service/password)"
|
|
||||||
else
|
|
||||||
echo "✗ Password file missing!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d /var/lib/vault/cache/test-service ]; then
|
|
||||||
echo "✓ Cache directory exists"
|
|
||||||
else
|
|
||||||
echo "✗ Cache directory missing!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Test successful!"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
../template2/hardware-configuration.nix
|
|
||||||
|
|
||||||
../../system
|
|
||||||
../../common/vm
|
|
||||||
];
|
|
||||||
|
|
||||||
homelab.host = {
|
|
||||||
tier = "test";
|
|
||||||
priority = "low";
|
|
||||||
role = "vault";
|
|
||||||
};
|
|
||||||
|
|
||||||
nixpkgs.config.allowUnfree = true;
|
|
||||||
boot.loader.grub.enable = true;
|
|
||||||
boot.loader.grub.device = "/dev/vda";
|
|
||||||
|
|
||||||
networking.hostName = "vaulttest01";
|
|
||||||
networking.domain = "home.2rjus.net";
|
|
||||||
networking.useNetworkd = true;
|
|
||||||
networking.useDHCP = false;
|
|
||||||
services.resolved.enable = true;
|
|
||||||
networking.nameservers = [
|
|
||||||
"10.69.13.5"
|
|
||||||
"10.69.13.6"
|
|
||||||
];
|
|
||||||
|
|
||||||
systemd.network.enable = true;
|
|
||||||
systemd.network.networks."ens18" = {
|
|
||||||
matchConfig.Name = "ens18";
|
|
||||||
address = [
|
|
||||||
"10.69.13.150/24"
|
|
||||||
];
|
|
||||||
routes = [
|
|
||||||
{ Gateway = "10.69.13.1"; }
|
|
||||||
];
|
|
||||||
linkConfig.RequiredForOnline = "routable";
|
|
||||||
};
|
|
||||||
time.timeZone = "Europe/Oslo";
|
|
||||||
|
|
||||||
nix.settings.experimental-features = [
|
|
||||||
"nix-command"
|
|
||||||
"flakes"
|
|
||||||
];
|
|
||||||
nix.settings.tarball-ttl = 0;
|
|
||||||
environment.systemPackages = with pkgs; [
|
|
||||||
vim
|
|
||||||
wget
|
|
||||||
git
|
|
||||||
htop # test deploy verification
|
|
||||||
];
|
|
||||||
|
|
||||||
# Open ports in the firewall.
|
|
||||||
# networking.firewall.allowedTCPPorts = [ ... ];
|
|
||||||
# networking.firewall.allowedUDPPorts = [ ... ];
|
|
||||||
# Or disable the firewall altogether.
|
|
||||||
networking.firewall.enable = false;
|
|
||||||
|
|
||||||
# Testing config
|
|
||||||
# Enable Vault secrets management
|
|
||||||
vault.enable = true;
|
|
||||||
homelab.deploy.enable = true;
|
|
||||||
|
|
||||||
# Define a test secret
|
|
||||||
vault.secrets.test-service = {
|
|
||||||
secretPath = "hosts/vaulttest01/test-service";
|
|
||||||
restartTrigger = true;
|
|
||||||
restartInterval = "daily";
|
|
||||||
services = [ "vault-test" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
# Create a test service that uses the secret
|
|
||||||
systemd.services.vault-test = {
|
|
||||||
description = "Test Vault secret fetching";
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
after = [ "vault-secret-test-service.service" ];
|
|
||||||
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
RemainAfterExit = true;
|
|
||||||
|
|
||||||
ExecStart = lib.getExe vault-test-script;
|
|
||||||
|
|
||||||
StandardOutput = "journal+console";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Test ACME certificate issuance from OpenBao PKI
|
|
||||||
# Override the global ACME server (from system/acme.nix) to use OpenBao instead of step-ca
|
|
||||||
security.acme.defaults.server = lib.mkForce "https://vault01.home.2rjus.net:8200/v1/pki_int/acme/directory";
|
|
||||||
|
|
||||||
# Request a certificate for this host
|
|
||||||
# Using HTTP-01 challenge with standalone listener on port 80
|
|
||||||
security.acme.certs."vaulttest01.home.2rjus.net" = {
|
|
||||||
listenHTTP = ":80";
|
|
||||||
enableDebugLogs = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
system.stateVersion = "25.11"; # Did you read the comment?
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -18,6 +18,8 @@ from manipulators import (
|
|||||||
remove_from_flake_nix,
|
remove_from_flake_nix,
|
||||||
remove_from_terraform_vms,
|
remove_from_terraform_vms,
|
||||||
remove_from_vault_terraform,
|
remove_from_vault_terraform,
|
||||||
|
remove_from_approle_tf,
|
||||||
|
find_host_secrets,
|
||||||
check_entries_exist,
|
check_entries_exist,
|
||||||
)
|
)
|
||||||
from models import HostConfig
|
from models import HostConfig
|
||||||
@@ -255,7 +257,10 @@ def handle_remove(
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Check what entries exist
|
# Check what entries exist
|
||||||
flake_exists, terraform_exists, vault_exists = check_entries_exist(hostname, repo_root)
|
flake_exists, terraform_exists, vault_exists, approle_exists = check_entries_exist(hostname, repo_root)
|
||||||
|
|
||||||
|
# Check for secrets in secrets.tf
|
||||||
|
host_secrets = find_host_secrets(hostname, repo_root)
|
||||||
|
|
||||||
# Collect all files in the host directory recursively
|
# Collect all files in the host directory recursively
|
||||||
files_in_host_dir = sorted([f for f in host_dir.rglob("*") if f.is_file()])
|
files_in_host_dir = sorted([f for f in host_dir.rglob("*") if f.is_file()])
|
||||||
@@ -294,6 +299,21 @@ def handle_remove(
|
|||||||
else:
|
else:
|
||||||
console.print(f" • terraform/vault/hosts-generated.tf [dim](not found)[/dim]")
|
console.print(f" • terraform/vault/hosts-generated.tf [dim](not found)[/dim]")
|
||||||
|
|
||||||
|
if approle_exists:
|
||||||
|
console.print(f' • terraform/vault/approle.tf (host_policies["{hostname}"])')
|
||||||
|
else:
|
||||||
|
console.print(f" • terraform/vault/approle.tf [dim](not found)[/dim]")
|
||||||
|
|
||||||
|
# Warn about secrets in secrets.tf
|
||||||
|
if host_secrets:
|
||||||
|
console.print(f"\n[yellow]⚠️ Warning: Found {len(host_secrets)} secret(s) in terraform/vault/secrets.tf:[/yellow]")
|
||||||
|
for secret_path in host_secrets:
|
||||||
|
console.print(f' • "{secret_path}"')
|
||||||
|
console.print(f"\n [yellow]These will NOT be removed automatically.[/yellow]")
|
||||||
|
console.print(f" After removal, manually edit secrets.tf and run:")
|
||||||
|
for secret_path in host_secrets:
|
||||||
|
console.print(f" [white]vault kv delete secret/{secret_path}[/white]")
|
||||||
|
|
||||||
# Warn about secrets directory
|
# Warn about secrets directory
|
||||||
if secrets_exist:
|
if secrets_exist:
|
||||||
console.print(f"\n[yellow]⚠️ Warning: secrets/{hostname}/ directory exists and will NOT be deleted[/yellow]")
|
console.print(f"\n[yellow]⚠️ Warning: secrets/{hostname}/ directory exists and will NOT be deleted[/yellow]")
|
||||||
@@ -323,6 +343,13 @@ def handle_remove(
|
|||||||
else:
|
else:
|
||||||
console.print("[yellow]⚠[/yellow] Could not remove from terraform/vault/hosts-generated.tf")
|
console.print("[yellow]⚠[/yellow] Could not remove from terraform/vault/hosts-generated.tf")
|
||||||
|
|
||||||
|
# Remove from terraform/vault/approle.tf
|
||||||
|
if approle_exists:
|
||||||
|
if remove_from_approle_tf(hostname, repo_root):
|
||||||
|
console.print("[green]✓[/green] Removed from terraform/vault/approle.tf")
|
||||||
|
else:
|
||||||
|
console.print("[yellow]⚠[/yellow] Could not remove from terraform/vault/approle.tf")
|
||||||
|
|
||||||
# Remove from terraform/vms.tf
|
# Remove from terraform/vms.tf
|
||||||
if terraform_exists:
|
if terraform_exists:
|
||||||
if remove_from_terraform_vms(hostname, repo_root):
|
if remove_from_terraform_vms(hostname, repo_root):
|
||||||
@@ -345,19 +372,34 @@ def handle_remove(
|
|||||||
console.print(f"\n[bold green]✓ Host {hostname} removed successfully![/bold green]\n")
|
console.print(f"\n[bold green]✓ Host {hostname} removed successfully![/bold green]\n")
|
||||||
|
|
||||||
# Display next steps
|
# Display next steps
|
||||||
display_removal_next_steps(hostname, vault_exists)
|
display_removal_next_steps(hostname, vault_exists, approle_exists, host_secrets)
|
||||||
|
|
||||||
|
|
||||||
def display_removal_next_steps(hostname: str, had_vault: bool) -> None:
|
def display_removal_next_steps(hostname: str, had_vault: bool, had_approle: bool, host_secrets: list) -> None:
|
||||||
"""Display next steps after successful removal."""
|
"""Display next steps after successful removal."""
|
||||||
vault_file = " terraform/vault/hosts-generated.tf" if had_vault else ""
|
vault_files = ""
|
||||||
vault_apply = ""
|
|
||||||
if had_vault:
|
if had_vault:
|
||||||
|
vault_files += " terraform/vault/hosts-generated.tf"
|
||||||
|
if had_approle:
|
||||||
|
vault_files += " terraform/vault/approle.tf"
|
||||||
|
|
||||||
|
vault_apply = ""
|
||||||
|
if had_vault or had_approle:
|
||||||
vault_apply = f"""
|
vault_apply = f"""
|
||||||
3. Apply Vault changes:
|
3. Apply Vault changes:
|
||||||
[white]cd terraform/vault && tofu apply[/white]
|
[white]cd terraform/vault && tofu apply[/white]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
secrets_cleanup = ""
|
||||||
|
if host_secrets:
|
||||||
|
secrets_cleanup = f"""
|
||||||
|
5. Clean up secrets (manual):
|
||||||
|
Edit terraform/vault/secrets.tf to remove entries for {hostname}
|
||||||
|
Then delete from Vault:"""
|
||||||
|
for secret_path in host_secrets:
|
||||||
|
secrets_cleanup += f"\n [white]vault kv delete secret/{secret_path}[/white]"
|
||||||
|
secrets_cleanup += "\n"
|
||||||
|
|
||||||
next_steps = f"""[bold cyan]Next Steps:[/bold cyan]
|
next_steps = f"""[bold cyan]Next Steps:[/bold cyan]
|
||||||
|
|
||||||
1. Review changes:
|
1. Review changes:
|
||||||
@@ -367,9 +409,9 @@ def display_removal_next_steps(hostname: str, had_vault: bool) -> None:
|
|||||||
[white]cd terraform && tofu destroy -target='proxmox_vm_qemu.vm["{hostname}"]'[/white]
|
[white]cd terraform && tofu destroy -target='proxmox_vm_qemu.vm["{hostname}"]'[/white]
|
||||||
{vault_apply}
|
{vault_apply}
|
||||||
4. Commit changes:
|
4. Commit changes:
|
||||||
[white]git add -u hosts/{hostname} flake.nix terraform/vms.tf{vault_file}
|
[white]git add -u hosts/{hostname} flake.nix terraform/vms.tf{vault_files}
|
||||||
git commit -m "hosts: remove {hostname}"[/white]
|
git commit -m "hosts: remove {hostname}"[/white]
|
||||||
"""
|
{secrets_cleanup}"""
|
||||||
console.print(Panel(next_steps, border_style="cyan"))
|
console.print(Panel(next_steps, border_style="cyan"))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ resource "vault_approle_auth_backend_role" "generated_hosts" {
|
|||||||
|
|
||||||
backend = vault_auth_backend.approle.path
|
backend = vault_auth_backend.approle.path
|
||||||
role_name = each.key
|
role_name = each.key
|
||||||
token_policies = ["host-\${each.key}"]
|
token_policies = ["host-\${each.key}", "homelab-deploy"]
|
||||||
secret_id_ttl = 0 # Never expire (wrapped tokens provide time limit)
|
secret_id_ttl = 0 # Never expire (wrapped tokens provide time limit)
|
||||||
token_ttl = 3600
|
token_ttl = 3600
|
||||||
token_max_ttl = 3600
|
token_max_ttl = 3600
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ def remove_from_flake_nix(hostname: str, repo_root: Path) -> bool:
|
|||||||
content = flake_path.read_text()
|
content = flake_path.read_text()
|
||||||
|
|
||||||
# Check if hostname exists
|
# Check if hostname exists
|
||||||
hostname_pattern = rf"^ {re.escape(hostname)} = nixpkgs\.lib\.nixosSystem"
|
hostname_pattern = rf"^ {re.escape(hostname)} = nixpkgs\.lib\.nixosSystem"
|
||||||
if not re.search(hostname_pattern, content, re.MULTILINE):
|
if not re.search(hostname_pattern, content, re.MULTILINE):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Match the entire block from "hostname = " to "};"
|
# Match the entire block from "hostname = " to "};"
|
||||||
replace_pattern = rf"^ {re.escape(hostname)} = nixpkgs\.lib\.nixosSystem \{{.*?^ \}};\n"
|
replace_pattern = rf"^ {re.escape(hostname)} = nixpkgs\.lib\.nixosSystem \{{.*?^ \}};\n"
|
||||||
new_content, count = re.subn(replace_pattern, "", content, flags=re.MULTILINE | re.DOTALL)
|
new_content, count = re.subn(replace_pattern, "", content, flags=re.MULTILINE | re.DOTALL)
|
||||||
|
|
||||||
if count == 0:
|
if count == 0:
|
||||||
@@ -101,7 +101,68 @@ def remove_from_vault_terraform(hostname: str, repo_root: Path) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def check_entries_exist(hostname: str, repo_root: Path) -> Tuple[bool, bool, bool]:
|
def remove_from_approle_tf(hostname: str, repo_root: Path) -> bool:
|
||||||
|
"""
|
||||||
|
Remove host entry from terraform/vault/approle.tf locals.host_policies.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hostname: Hostname to remove
|
||||||
|
repo_root: Path to repository root
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if found and removed, False if not found
|
||||||
|
"""
|
||||||
|
approle_path = repo_root / "terraform" / "vault" / "approle.tf"
|
||||||
|
|
||||||
|
if not approle_path.exists():
|
||||||
|
return False
|
||||||
|
|
||||||
|
content = approle_path.read_text()
|
||||||
|
|
||||||
|
# Check if hostname exists in host_policies
|
||||||
|
hostname_pattern = rf'^\s+"{re.escape(hostname)}" = \{{'
|
||||||
|
if not re.search(hostname_pattern, content, re.MULTILINE):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Match the entire block from "hostname" = { to closing }
|
||||||
|
# The block contains paths = [ ... ] and possibly extra_policies = [...]
|
||||||
|
replace_pattern = rf'\n?\s+"{re.escape(hostname)}" = \{{[^}}]*\}}\n?'
|
||||||
|
new_content, count = re.subn(replace_pattern, "\n", content, flags=re.DOTALL)
|
||||||
|
|
||||||
|
if count == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
approle_path.write_text(new_content)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def find_host_secrets(hostname: str, repo_root: Path) -> list:
|
||||||
|
"""
|
||||||
|
Find secrets in terraform/vault/secrets.tf that belong to a host.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hostname: Hostname to search for
|
||||||
|
repo_root: Path to repository root
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of secret paths found (e.g., ["hosts/hostname/test-service"])
|
||||||
|
"""
|
||||||
|
secrets_path = repo_root / "terraform" / "vault" / "secrets.tf"
|
||||||
|
|
||||||
|
if not secrets_path.exists():
|
||||||
|
return []
|
||||||
|
|
||||||
|
content = secrets_path.read_text()
|
||||||
|
|
||||||
|
# Find all secret paths matching hosts/{hostname}/
|
||||||
|
pattern = rf'"(hosts/{re.escape(hostname)}/[^"]+)"'
|
||||||
|
matches = re.findall(pattern, content)
|
||||||
|
|
||||||
|
# Return unique paths, preserving order
|
||||||
|
return list(dict.fromkeys(matches))
|
||||||
|
|
||||||
|
|
||||||
|
def check_entries_exist(hostname: str, repo_root: Path) -> Tuple[bool, bool, bool, bool]:
|
||||||
"""
|
"""
|
||||||
Check which entries exist for a hostname.
|
Check which entries exist for a hostname.
|
||||||
|
|
||||||
@@ -110,12 +171,12 @@ def check_entries_exist(hostname: str, repo_root: Path) -> Tuple[bool, bool, boo
|
|||||||
repo_root: Path to repository root
|
repo_root: Path to repository root
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple of (flake_exists, terraform_vms_exists, vault_exists)
|
Tuple of (flake_exists, terraform_vms_exists, vault_generated_exists, approle_exists)
|
||||||
"""
|
"""
|
||||||
# Check flake.nix
|
# Check flake.nix
|
||||||
flake_path = repo_root / "flake.nix"
|
flake_path = repo_root / "flake.nix"
|
||||||
flake_content = flake_path.read_text()
|
flake_content = flake_path.read_text()
|
||||||
flake_pattern = rf"^ {re.escape(hostname)} = nixpkgs\.lib\.nixosSystem"
|
flake_pattern = rf"^ {re.escape(hostname)} = nixpkgs\.lib\.nixosSystem"
|
||||||
flake_exists = bool(re.search(flake_pattern, flake_content, re.MULTILINE))
|
flake_exists = bool(re.search(flake_pattern, flake_content, re.MULTILINE))
|
||||||
|
|
||||||
# Check terraform/vms.tf
|
# Check terraform/vms.tf
|
||||||
@@ -131,7 +192,15 @@ def check_entries_exist(hostname: str, repo_root: Path) -> Tuple[bool, bool, boo
|
|||||||
vault_content = vault_tf_path.read_text()
|
vault_content = vault_tf_path.read_text()
|
||||||
vault_exists = f'"{hostname}"' in vault_content
|
vault_exists = f'"{hostname}"' in vault_content
|
||||||
|
|
||||||
return (flake_exists, terraform_exists, vault_exists)
|
# Check terraform/vault/approle.tf
|
||||||
|
approle_path = repo_root / "terraform" / "vault" / "approle.tf"
|
||||||
|
approle_exists = False
|
||||||
|
if approle_path.exists():
|
||||||
|
approle_content = approle_path.read_text()
|
||||||
|
approle_pattern = rf'^\s+"{re.escape(hostname)}" = \{{'
|
||||||
|
approle_exists = bool(re.search(approle_pattern, approle_content, re.MULTILINE))
|
||||||
|
|
||||||
|
return (flake_exists, terraform_exists, vault_exists, approle_exists)
|
||||||
|
|
||||||
|
|
||||||
def update_flake_nix(config: HostConfig, repo_root: Path, force: bool = False) -> None:
|
def update_flake_nix(config: HostConfig, repo_root: Path, force: bool = False) -> None:
|
||||||
@@ -147,32 +216,25 @@ def update_flake_nix(config: HostConfig, repo_root: Path, force: bool = False) -
|
|||||||
content = flake_path.read_text()
|
content = flake_path.read_text()
|
||||||
|
|
||||||
# Create new entry
|
# Create new entry
|
||||||
new_entry = f""" {config.hostname} = nixpkgs.lib.nixosSystem {{
|
new_entry = f""" {config.hostname} = nixpkgs.lib.nixosSystem {{
|
||||||
inherit system;
|
inherit system;
|
||||||
specialArgs = {{
|
specialArgs = {{
|
||||||
inherit inputs self sops-nix;
|
inherit inputs self sops-nix;
|
||||||
|
}};
|
||||||
|
modules = commonModules ++ [
|
||||||
|
./hosts/{config.hostname}
|
||||||
|
];
|
||||||
}};
|
}};
|
||||||
modules = [
|
|
||||||
(
|
|
||||||
{{ config, pkgs, ... }}:
|
|
||||||
{{
|
|
||||||
nixpkgs.overlays = commonOverlays;
|
|
||||||
}}
|
|
||||||
)
|
|
||||||
./hosts/{config.hostname}
|
|
||||||
sops-nix.nixosModules.sops
|
|
||||||
];
|
|
||||||
}};
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Check if hostname already exists
|
# Check if hostname already exists
|
||||||
hostname_pattern = rf"^ {re.escape(config.hostname)} = nixpkgs\.lib\.nixosSystem"
|
hostname_pattern = rf"^ {re.escape(config.hostname)} = nixpkgs\.lib\.nixosSystem"
|
||||||
existing_match = re.search(hostname_pattern, content, re.MULTILINE)
|
existing_match = re.search(hostname_pattern, content, re.MULTILINE)
|
||||||
|
|
||||||
if existing_match and force:
|
if existing_match and force:
|
||||||
# Replace existing entry
|
# Replace existing entry
|
||||||
# Match the entire block from "hostname = " to "};"
|
# Match the entire block from "hostname = " to "};"
|
||||||
replace_pattern = rf"^ {re.escape(config.hostname)} = nixpkgs\.lib\.nixosSystem \{{.*?^ \}};\n"
|
replace_pattern = rf"^ {re.escape(config.hostname)} = nixpkgs\.lib\.nixosSystem \{{.*?^ \}};\n"
|
||||||
new_content, count = re.subn(replace_pattern, new_entry, content, flags=re.MULTILINE | re.DOTALL)
|
new_content, count = re.subn(replace_pattern, new_entry, content, flags=re.MULTILINE | re.DOTALL)
|
||||||
|
|
||||||
if count == 0:
|
if count == 0:
|
||||||
|
|||||||
@@ -18,6 +18,12 @@
|
|||||||
tier = "test"; # Start in test tier, move to prod after validation
|
tier = "test"; # Start in test tier, move to prod after validation
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Enable Vault integration
|
||||||
|
vault.enable = true;
|
||||||
|
|
||||||
|
# Enable remote deployment via NATS
|
||||||
|
homelab.deploy.enable = true;
|
||||||
|
|
||||||
nixpkgs.config.allowUnfree = true;
|
nixpkgs.config.allowUnfree = true;
|
||||||
boot.loader.grub.enable = true;
|
boot.loader.grub.enable = true;
|
||||||
boot.loader.grub.device = "/dev/vda";
|
boot.loader.grub.device = "/dev/vda";
|
||||||
|
|||||||
@@ -101,11 +101,6 @@ locals {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
"vaulttest01" = {
|
|
||||||
paths = [
|
|
||||||
"secret/data/hosts/vaulttest01/*",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,22 @@
|
|||||||
# Each host gets access to its own secrets under hosts/<hostname>/*
|
# Each host gets access to its own secrets under hosts/<hostname>/*
|
||||||
locals {
|
locals {
|
||||||
generated_host_policies = {
|
generated_host_policies = {
|
||||||
"vaulttest01" = {
|
"testvm01" = {
|
||||||
paths = [
|
paths = [
|
||||||
"secret/data/hosts/vaulttest01/*",
|
"secret/data/hosts/testvm01/*",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
"testvm02" = {
|
||||||
|
paths = [
|
||||||
|
"secret/data/hosts/testvm02/*",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"testvm03" = {
|
||||||
|
paths = [
|
||||||
|
"secret/data/hosts/testvm03/*",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Placeholder secrets - user should add actual secrets manually or via tofu
|
# Placeholder secrets - user should add actual secrets manually or via tofu
|
||||||
@@ -40,7 +50,7 @@ resource "vault_approle_auth_backend_role" "generated_hosts" {
|
|||||||
|
|
||||||
backend = vault_auth_backend.approle.path
|
backend = vault_auth_backend.approle.path
|
||||||
role_name = each.key
|
role_name = each.key
|
||||||
token_policies = ["host-${each.key}"]
|
token_policies = ["host-${each.key}", "homelab-deploy"]
|
||||||
secret_id_ttl = 0 # Never expire (wrapped tokens provide time limit)
|
secret_id_ttl = 0 # Never expire (wrapped tokens provide time limit)
|
||||||
token_ttl = 3600
|
token_ttl = 3600
|
||||||
token_max_ttl = 3600
|
token_max_ttl = 3600
|
||||||
|
|||||||
@@ -45,12 +45,6 @@ locals {
|
|||||||
password_length = 24
|
password_length = 24
|
||||||
}
|
}
|
||||||
|
|
||||||
# TODO: Remove after testing
|
|
||||||
"hosts/vaulttest01/test-service" = {
|
|
||||||
auto_generate = true
|
|
||||||
password_length = 32
|
|
||||||
}
|
|
||||||
|
|
||||||
# Shared backup password (auto-generated, add alongside existing restic key)
|
# Shared backup password (auto-generated, add alongside existing restic key)
|
||||||
"shared/backup/password" = {
|
"shared/backup/password" = {
|
||||||
auto_generate = true
|
auto_generate = true
|
||||||
|
|||||||
@@ -31,13 +31,6 @@ locals {
|
|||||||
# Example Minimal VM using all defaults (uncomment to deploy):
|
# Example Minimal VM using all defaults (uncomment to deploy):
|
||||||
# "minimal-vm" = {}
|
# "minimal-vm" = {}
|
||||||
# "bootstrap-verify-test" = {}
|
# "bootstrap-verify-test" = {}
|
||||||
"testvm01" = {
|
|
||||||
ip = "10.69.13.101/24"
|
|
||||||
cpu_cores = 2
|
|
||||||
memory = 2048
|
|
||||||
disk_size = "20G"
|
|
||||||
flake_branch = "pipeline-testing-improvements"
|
|
||||||
}
|
|
||||||
"vault01" = {
|
"vault01" = {
|
||||||
ip = "10.69.13.19/24"
|
ip = "10.69.13.19/24"
|
||||||
cpu_cores = 2
|
cpu_cores = 2
|
||||||
@@ -45,13 +38,29 @@ locals {
|
|||||||
disk_size = "20G"
|
disk_size = "20G"
|
||||||
flake_branch = "vault-setup" # Bootstrap from this branch instead of master
|
flake_branch = "vault-setup" # Bootstrap from this branch instead of master
|
||||||
}
|
}
|
||||||
"vaulttest01" = {
|
"testvm01" = {
|
||||||
ip = "10.69.13.150/24"
|
ip = "10.69.13.20/24"
|
||||||
cpu_cores = 2
|
cpu_cores = 2
|
||||||
memory = 2048
|
memory = 2048
|
||||||
disk_size = "20G"
|
disk_size = "20G"
|
||||||
flake_branch = "pki-migration"
|
flake_branch = "deploy-test-hosts"
|
||||||
vault_wrapped_token = "s.UCpQCOp7cOKDdtGGBvfRWwAt"
|
vault_wrapped_token = "s.YRGRpAZVVtSYEa3wOYOqFmjt"
|
||||||
|
}
|
||||||
|
"testvm02" = {
|
||||||
|
ip = "10.69.13.21/24"
|
||||||
|
cpu_cores = 2
|
||||||
|
memory = 2048
|
||||||
|
disk_size = "20G"
|
||||||
|
flake_branch = "deploy-test-hosts"
|
||||||
|
vault_wrapped_token = "s.tvs8yhJOkLjBs548STs6DBw7"
|
||||||
|
}
|
||||||
|
"testvm03" = {
|
||||||
|
ip = "10.69.13.22/24"
|
||||||
|
cpu_cores = 2
|
||||||
|
memory = 2048
|
||||||
|
disk_size = "20G"
|
||||||
|
flake_branch = "deploy-test-hosts"
|
||||||
|
vault_wrapped_token = "s.sQ80FZGeG3z6jgrsuh74IopC"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user