diff options
Diffstat (limited to '')
| -rw-r--r-- | flake.nix | 1 | ||||
| -rw-r--r-- | flake/devshells.nix | 2 | ||||
| -rwxr-xr-x | infra/tf/do-vm/extra-files-script.sh | 8 | ||||
| -rw-r--r-- | infra/tf/do-vm/main.tf | 82 | ||||
| -rw-r--r-- | infra/tf/do-vm/outputs.tf | 9 | ||||
| -rw-r--r-- | infra/tf/do-vm/terraform.tfvars | 2 | ||||
| -rw-r--r-- | infra/tf/flake-module.nix | 31 | ||||
| -rw-r--r-- | infra/tf/scripts.nix | 51 | ||||
| -rw-r--r-- | machines/nixos/x86_64-linux/digitalocean.nix | 110 | ||||
| -rw-r--r-- | machines/nixos/x86_64-linux/vm-synology.nix | 19 | ||||
| -rw-r--r-- | profiles/admin-user/user.nix | 1 | ||||
| -rw-r--r-- | profiles/default.nix | 11 | ||||
| -rw-r--r-- | profiles/server.nix | 19 | ||||
| -rw-r--r-- | scripts/default.nix | 4 | ||||
| -rw-r--r-- | scripts/infra.nix | 79 | ||||
| -rw-r--r-- | secrets/do/host-ed25519-key.age | bin | 0 -> 611 bytes | |||
| -rw-r--r-- | secrets/do/wireguard.age | 7 | ||||
| -rw-r--r-- | secrets/secrets.nix | 10 | ||||
| -rw-r--r-- | users/profiles/nixos.nix | 24 |
19 files changed, 321 insertions, 149 deletions
@@ -63,6 +63,7 @@ ./flake/hosts.nix ./flake/overlays.nix ./flake/packages.nix + ./infra/tf/flake-module.nix ]; }; } diff --git a/flake/devshells.nix b/flake/devshells.nix index d28c4b3..15c571b 100644 --- a/flake/devshells.nix +++ b/flake/devshells.nix @@ -8,6 +8,7 @@ programs = { nixfmt.enable = true; deadnix.enable = true; + terraform.enable = true; }; }; @@ -38,7 +39,6 @@ scripts = import "${self}/scripts" { inherit pkgs - self system inputs ; diff --git a/infra/tf/do-vm/extra-files-script.sh b/infra/tf/do-vm/extra-files-script.sh new file mode 100755 index 0000000..fa47762 --- /dev/null +++ b/infra/tf/do-vm/extra-files-script.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +mkdir -p etc/ssh/ + +if [ -n "$DO_SSH_HOSTKEY" ]; then + echo "$DO_SSH_HOSTKEY" | base64 -d > etc/ssh/ssh_host_ed25519_key + chmod 0600 etc/ssh/ssh_host_ed25519_key +fi diff --git a/infra/tf/do-vm/main.tf b/infra/tf/do-vm/main.tf new file mode 100644 index 0000000..4cff3e9 --- /dev/null +++ b/infra/tf/do-vm/main.tf @@ -0,0 +1,82 @@ +terraform { + required_providers { + digitalocean = { + source = "digitalocean/digitalocean" + version = "~> 2.0" + } + random = { + source = "hashicorp/random" + version = "~> 3.1" + } + } + + backend "gcs" { + bucket = "fcuny-infra-tofu-state" + prefix = "do-vm" + } +} + +provider "digitalocean" { + # Token will be read from DIGITALOCEAN_TOKEN environment variable +} + +variable "ssh_public_key" { + description = "SSH public key content" + type = string + default = "" +} + +variable "region" { + description = "DigitalOcean region" + type = string + default = "" +} + +# Random string for unique naming +resource "random_string" "host" { + length = 6 + special = false + upper = false +} + +# Locals +locals { + server_size = "s-2vcpu-2gb" + labels = { + environment = "nixos" + managed_by = "terraform" + } +} + +resource "digitalocean_ssh_key" "default" { + name = "nixos-anywhere-${random_string.host.result}" + public_key = var.ssh_public_key +} + +resource "digitalocean_droplet" "nixos" { + name = "nixos-${random_string.host.result}" + image = "ubuntu-24-04-x64" # Just to get the server started + size = local.server_size + region = var.region + ssh_keys = [digitalocean_ssh_key.default.id] + tags = ["nixos", "infrastructure"] +} + +module "nixos-system-build" { + source = "github.com/nix-community/nixos-anywhere//terraform/nix-build" + attribute = ".#nixosConfigurations.digitalocean.config.system.build.toplevel" +} + +module "nixos-disko" { + source = "github.com/nix-community/nixos-anywhere//terraform/nix-build" + attribute = ".#nixosConfigurations.digitalocean.config.system.build.diskoScript" +} + +module "nixos-install" { + source = "github.com/nix-community/nixos-anywhere//terraform/install" + nixos_system = module.nixos-system-build.result.out + nixos_partitioner = module.nixos-disko.result.out + target_host = digitalocean_droplet.nixos.ipv4_address + build_on_remote = true + extra_files_script = "./extra-files-script.sh" +} diff --git a/infra/tf/do-vm/outputs.tf b/infra/tf/do-vm/outputs.tf new file mode 100644 index 0000000..2b9f5c2 --- /dev/null +++ b/infra/tf/do-vm/outputs.tf @@ -0,0 +1,9 @@ +output "nixos_ip" { + description = "IP address of the NixOS server" + value = digitalocean_droplet.nixos.ipv4_address +} + +output "droplet_id" { + description = "DigitalOcean droplet ID" + value = digitalocean_droplet.nixos.id +} diff --git a/infra/tf/do-vm/terraform.tfvars b/infra/tf/do-vm/terraform.tfvars new file mode 100644 index 0000000..99af205 --- /dev/null +++ b/infra/tf/do-vm/terraform.tfvars @@ -0,0 +1,2 @@ +ssh_public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINBkozy+X96u5ciX766bJ/AyQ3xm1tXZTIr5+4PVFZFi" +region = "sfo3" diff --git a/infra/tf/flake-module.nix b/infra/tf/flake-module.nix index a386177..f5a01dd 100644 --- a/infra/tf/flake-module.nix +++ b/infra/tf/flake-module.nix @@ -1,17 +1,30 @@ { perSystem = { pkgs, ... }: + let + scripts = import ./scripts.nix { + inherit pkgs; + }; + in { devShells.terraform = pkgs.mkShellNoCC { - packages = with pkgs; [ - google-cloud-sdk - (pkgs.opentofu.withPlugins (p: [ - p.google - p.cloudflare - p.external - p.null - ])) - ]; + packages = + with pkgs; + [ + google-cloud-sdk + age + (pkgs.opentofu.withPlugins (p: [ + p.cloudflare + p.digitalocean + p.external + p.google + p.null + p.random + ])) + ] + ++ [ + scripts + ]; }; }; } diff --git a/infra/tf/scripts.nix b/infra/tf/scripts.nix new file mode 100644 index 0000000..373b77d --- /dev/null +++ b/infra/tf/scripts.nix @@ -0,0 +1,51 @@ +{ pkgs }: +let + tofuSetup = '' + tofu_setup() { + # Ensure bucket exists + ${pkgs.google-cloud-sdk}/bin/gcloud storage buckets describe \ + gs://fcuny-infra-tofu-state \ + --project=fcuny-infra \ + --quiet || \ + + ${pkgs.google-cloud-sdk}/bin/gcloud storage buckets create \ + gs://fcuny-infra-tofu-state \ + --project=fcuny-infra \ + --uniform-bucket-level-access \ + --public-access-prevention \ + --location=us-west1 \ + --default-storage-class=STANDARD \ + --quiet + } + ''; +in +[ + (pkgs.writeShellScriptBin "gcloud-auth" '' + set -xeuo pipefail + ${pkgs.google-cloud-sdk}/bin/gcloud auth print-identity-token > /dev/null 2>&1 || \ + ${pkgs.google-cloud-sdk}/bin/gcloud auth login --quiet + ${pkgs.google-cloud-sdk}/bin/gcloud auth application-default print-access-token > /dev/null 2>&1 || \ + ${pkgs.google-cloud-sdk}/bin/gcloud auth application-default login --quiet + '') + + (pkgs.writeShellScriptBin "tf-plan-dns" '' + set -xeuo pipefail + ${tofuSetup} + tofu_setup + ${pkgs.opentofu}/bin/tofu -chdir="cloudflare-dns" plan + '') + + (pkgs.writeShellScriptBin "tf-plan-backups" '' + set -xeuo pipefail + ${tofuSetup} + tofu_setup + ${pkgs.opentofu}/bin/tofu -chdir="backups-bucket" plan + '') + + (pkgs.writeShellScriptBin "tf-plan-do" '' + set -xeuo pipefail + ${tofuSetup} + tofu_setup + ${pkgs.opentofu}/bin/tofu -chdir="do-vm" plan + '') +] diff --git a/machines/nixos/x86_64-linux/digitalocean.nix b/machines/nixos/x86_64-linux/digitalocean.nix new file mode 100644 index 0000000..5e6f069 --- /dev/null +++ b/machines/nixos/x86_64-linux/digitalocean.nix @@ -0,0 +1,110 @@ +{ + adminUser, + config, + lib, + modulesPath, + self, + ... +}: +{ + age = { + secrets = { + wireguard = { + file = "${self}/secrets/do/wireguard.age"; + }; + }; + }; + + imports = [ + (modulesPath + "/profiles/qemu-guest.nix") + (modulesPath + "/virtualisation/digital-ocean-config.nix") + "${self}/profiles/home-manager.nix" + "${self}/profiles/admin-user/user.nix" + "${self}/profiles/admin-user/home-manager.nix" + "${self}/profiles/disk/vm.nix" + "${self}/profiles/server.nix" + ]; + + disko.devices.disk.disk1.device = "/dev/vda"; + + # do not use DHCP, as DigitalOcean provisions IPs using cloud-init + networking.useDHCP = lib.mkForce false; + + networking.hostName = "do-jump"; + + boot.loader.grub = { + efiSupport = true; + efiInstallAsRemovable = true; + }; + + home-manager.users.${adminUser.name} = { + imports = [ + "${self}/users/profiles/minimal.nix" + ]; + }; + + # this one seems to always be broken + systemd.services.growpart.enable = false; + + # in order to get networking setup we need to enable it in cloud-init + # Disables all modules that do not work with NixOS + # Based on https://github.com/nix-community/nixos-anywhere-examples/blob/7f945ff0ae676c0eb77360b892add91328dd1f17/digitalocean.nix + services.cloud-init = { + enable = true; + network.enable = true; + settings = { + datasource_list = [ + "ConfigDrive" + "Digitalocean" + ]; + datasource.ConfigDrive = { }; + datasource.Digitalocean = { }; + # Based on https://github.com/canonical/cloud-init/blob/main/config/cloud.cfg.tmpl + cloud_init_modules = [ + "seed_random" + "bootcmd" + "write_files" + "growpart" + "resizefs" + "set_hostname" + "update_hostname" + "set_password" + ]; + cloud_config_modules = [ + "ssh-import-id" + "keyboard" + "runcmd" + "disable_ec2_metadata" + ]; + cloud_final_modules = [ + "write_files_deferred" + "puppet" + "chef" + "ansible" + "mcollective" + "salt_minion" + "reset_rmc" + "scripts_per_once" + "scripts_per_boot" + "scripts_user" + "ssh_authkey_fingerprints" + "keys_to_console" + "install_hotplug" + "phone_home" + "final_message" + ]; + }; + }; + + networking.wireguard = { + enable = true; + interfaces.wg0 = { + ips = [ "10.100.0.50/32" ]; + listenPort = 51871; + privateKeyFile = config.age.secrets.wireguard.path; + }; + }; + + networking.firewall.allowedUDPPorts = [ 51871 ]; + system.stateVersion = "25.05"; # Did you read the comment? +} diff --git a/machines/nixos/x86_64-linux/vm-synology.nix b/machines/nixos/x86_64-linux/vm-synology.nix index 309c3a6..468d0dd 100644 --- a/machines/nixos/x86_64-linux/vm-synology.nix +++ b/machines/nixos/x86_64-linux/vm-synology.nix @@ -1,4 +1,5 @@ { + lib, adminUser, config, self, @@ -37,7 +38,11 @@ # Use the systemd-boot EFI boot loader. boot.loader.efi.canTouchEfiVariables = true; + boot.loader.systemd-boot.enable = true; + networking.hostName = "vm-synology"; + networking.useDHCP = lib.mkDefault true; + systemd.network.wait-online.anyInterface = lib.mkDefault config.networking.useDHCP; home-manager.users.${adminUser.name} = { imports = [ @@ -45,20 +50,6 @@ ]; }; - services.cloudflared = { - enable = true; - certificateFile = config.age.secrets.cloudflared-cert.path; - tunnels = { - "cragmont" = { - credentialsFile = config.age.secrets.cloudflared-tunnel.path; - default = "http_status:404"; - ingress = { - "git.fcuny.net".service = "ssh://127.0.0.1:22"; - }; - }; - }; - }; - my.modules.nas-client = { enable = true; volumes = { diff --git a/profiles/admin-user/user.nix b/profiles/admin-user/user.nix index e05ae6d..1a6b1dc 100644 --- a/profiles/admin-user/user.nix +++ b/profiles/admin-user/user.nix @@ -10,6 +10,7 @@ inherit (adminUser) uid; shell = pkgs.fish; isNormalUser = true; + hashedPassword = "$6$Llw8m62nKMLLN9mm$3.a4CKUFlqwkG8vjBryLlBNwTwgH63vpg2nhYwRoQzG76Q91vTXnlYDujS4G5yGrWoatkKZx5epCx4/NAvRh2/"; openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINBkozy+X96u5ciX766bJ/AyQ3xm1tXZTIr5+4PVFZFi" ]; diff --git a/profiles/default.nix b/profiles/default.nix index 58c22eb..13b8759 100644 --- a/profiles/default.nix +++ b/profiles/default.nix @@ -40,16 +40,6 @@ ## only allow declarative user management users.mutableUsers = false; - services.openssh.enable = true; - services.openssh.settings.PasswordAuthentication = false; - services.openssh.settings.PermitRootLogin = "no"; - - users.users.root.openssh.authorizedKeys.keys = [ - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINBkozy+X96u5ciX766bJ/AyQ3xm1tXZTIr5+4PVFZFi" - ]; - - networking.firewall.allowedTCPPorts = [ 22 ]; - programs.fish.enable = true; security.sudo.wheelNeedsPassword = false; @@ -67,6 +57,7 @@ tcpdump traceroute vim + wireguard-tools ]; ## disable that slow "building man-cache" step diff --git a/profiles/server.nix b/profiles/server.nix index ae46bce..49a0bc7 100644 --- a/profiles/server.nix +++ b/profiles/server.nix @@ -1,5 +1,4 @@ { - config, lib, pkgs, self, @@ -12,10 +11,9 @@ "${self}/modules/motd.nix" ]; - # Virtualization settings + #Virtualization settings virtualisation.docker.enable = true; - boot.loader.systemd-boot.enable = true; boot.kernelPackages = pkgs.linuxPackages_latest; boot.kernel.sysctl = { @@ -32,11 +30,6 @@ useNetworkd = lib.mkDefault true; }; - networking.useDHCP = lib.mkDefault true; - - # Default to systemd-networkd usage. - systemd.network.wait-online.anyInterface = lib.mkDefault config.networking.useDHCP; - # Use systemd-resolved for DoT support. services.resolved = { enable = true; @@ -51,4 +44,14 @@ "8.8.8.8#dns.google" "1.0.0.1#cloudflare-dns.com" ]; + + services.openssh.enable = true; + services.openssh.settings.PasswordAuthentication = false; + services.openssh.settings.PermitRootLogin = "no"; + + users.users.root.openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINBkozy+X96u5ciX766bJ/AyQ3xm1tXZTIr5+4PVFZFi" + ]; + + networking.firewall.allowedTCPPorts = [ 22 ]; } diff --git a/scripts/default.nix b/scripts/default.nix index bf91760..90851df 100644 --- a/scripts/default.nix +++ b/scripts/default.nix @@ -2,23 +2,19 @@ pkgs, system, inputs, - self, }: let common = import ./common.nix { inherit pkgs; }; - infra = import ./infra.nix { inherit self pkgs; }; darwin = import ./darwin.nix { inherit pkgs system inputs; }; linux = import ./linux.nix { inherit pkgs system inputs; }; in { common = common; - infra = infra; darwin = if pkgs.lib.hasSuffix "darwin" system then darwin else [ ]; linux = if pkgs.lib.hasSuffix "linux" system then linux else [ ]; all = common - ++ infra ++ (if pkgs.lib.hasSuffix "darwin" system then darwin else [ ]) ++ (if pkgs.lib.hasSuffix "linux" system then linux else [ ]); } diff --git a/scripts/infra.nix b/scripts/infra.nix deleted file mode 100644 index aa9b0d6..0000000 --- a/scripts/infra.nix +++ /dev/null @@ -1,79 +0,0 @@ -{ self, pkgs }: -let - tofuSetup = '' - tofu_setup() { - # Ensure bucket exists - ${pkgs.google-cloud-sdk}/bin/gcloud storage buckets describe \ - gs://fcuny-infra-tofu-state \ - --project=fcuny-infra \ - --quiet || \ - ${pkgs.google-cloud-sdk}/bin/gcloud storage buckets create \ - gs://fcuny-infra-tofu-state \ - --project=fcuny-infra \ - --uniform-bucket-level-access \ - --public-access-prevention \ - --location=us-west1 \ - --default-storage-class=STANDARD \ - --quiet - - # Setup temp directory - TMPDIR=$(mktemp -d) - trap 'rm -rf "$TMPDIR"' EXIT - - # Install terraform configs - ${pkgs.coreutils}/bin/install -Dm 0644 ${ - import "${self}/infra/tf/backups.nix" { - inherit pkgs; - } - } "$TMPDIR/backups/backups.tf.json" - - ${pkgs.coreutils}/bin/install -Dm 0644 ${ - import "${self}/infra/tf/dns.nix" { - inherit pkgs; - } - } "$TMPDIR/cloudflare/cloudflare-dns.tf.json" - - # Initialize both workspaces - ${pkgs.opentofu}/bin/tofu -chdir="$TMPDIR/backups" init - ${pkgs.opentofu}/bin/tofu -chdir="$TMPDIR/cloudflare" init - - # Fetch Cloudflare API token - CLOUDFLARE_API_TOKEN=$(${pkgs._1password-cli}/bin/op --account my.1password.com read "op://Private/mcwt3evuidhalk3dfz4tqpzdpa/credential") - } - ''; -in -[ - (pkgs.writeShellScriptBin "gcloud-auth" '' - set -xeuo pipefail - ${pkgs.google-cloud-sdk}/bin/gcloud auth print-identity-token > /dev/null 2>&1 || \ - ${pkgs.google-cloud-sdk}/bin/gcloud auth login --quiet - ${pkgs.google-cloud-sdk}/bin/gcloud auth application-default print-access-token > /dev/null 2>&1 || \ - ${pkgs.google-cloud-sdk}/bin/gcloud auth application-default login --quiet - '') - - (pkgs.writeShellScriptBin "tf-plan" '' - set -xeuo pipefail - - ${tofuSetup} - tofu_setup - - echo "=== Planning backups ===" - ${pkgs.opentofu}/bin/tofu -chdir="$TMPDIR/backups" plan - - echo "=== Planning cloudflare ===" - CLOUDFLARE_API_TOKEN="$CLOUDFLARE_API_TOKEN" ${pkgs.opentofu}/bin/tofu -chdir="$TMPDIR/cloudflare" plan - '') - - (pkgs.writeShellScriptBin "tf-apply" '' - set -xeuo pipefail - - ${tofuSetup} - tofu_setup - - echo "=== Applying backups ===" - ${pkgs.opentofu}/bin/tofu -chdir="$TMPDIR/backups" apply -auto-approve - - echo "=== Applying cloudflare ===" - CLOUDFLARE_API_TOKEN="$CLOUDFLARE_API_TOKEN" ${pkgs.opentofu}/bin/tofu -chdir="$TMPDIR/cloudflare" apply -auto-approve - '') -] diff --git a/secrets/do/host-ed25519-key.age b/secrets/do/host-ed25519-key.age Binary files differnew file mode 100644 index 0000000..d73ed26 --- /dev/null +++ b/secrets/do/host-ed25519-key.age diff --git a/secrets/do/wireguard.age b/secrets/do/wireguard.age new file mode 100644 index 0000000..62c7d99 --- /dev/null +++ b/secrets/do/wireguard.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 pFjJaA PZwR2gnJbrjUz0ym7cSy5Fp7uJ2FYtuXdwpOvNMkbC4 +2hglFicM8rIy0fZOs99Om3+Q9fD8uNgiuda3QG++kIE +-> ssh-ed25519 8Nmf6A 5SNPolSGlqSH9MFjY2zlqsp8tHTm2t8Sdw2UPphJKlU +vpJ/24lPuaqnN4SQvDOK8buu9w7MQXyFZKU+VuXkj30 +--- 0R9ApzzbQu97K4PuPVW3Zmq0w/ppKAhwlKJu+mh0CvI +ٔjJU30#Ge[dũ#SSB7%#>
b8-IG`dUL^
\ No newline at end of file diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 52f2311..3ef9cd2 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -2,6 +2,7 @@ let hosts = { vm-synology = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHKZAKlqOU6bSuMaaZAsYJdZnmNASWuIbbrrOjB6yGb8 root@vm-synology"; mba = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDLQTIPZraE+jpMqGkh8yUhNFzRJbMarX5Mky3nETw6c root@mba-m2"; + do = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID6qsTQwvo6lUACTZKb4T+Je89bW3/BY4DB4aCTqfApz"; }; users = { fcuny = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKdyJepi/NyO6d9eP8m48Ga/gdjB5ENHRXYM1ZqFZR8t"; @@ -41,4 +42,13 @@ in hosts.vm-synology hosts.mba ]; + # this is the SSH key for the digital ocean droplet + # the public key is ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID6qsTQwvo6lUACTZKb4T+Je89bW3/BY4DB4aCTqfApz + "do/host-ed25519-key.age".publicKeys = [ + users.fcuny + ]; + "do/wireguard.age".publicKeys = [ + users.fcuny + hosts.do + ]; } diff --git a/users/profiles/nixos.nix b/users/profiles/nixos.nix deleted file mode 100644 index a6c302f..0000000 --- a/users/profiles/nixos.nix +++ /dev/null @@ -1,24 +0,0 @@ -{ pkgs, ... }: -{ - # add ~/.local/bin to PATH - environment.localBinInPath = true; - - # we're using fish as our shell - programs.fish.enable = true; - - users.users.fcuny = { - isNormalUser = true; - home = "/home/fcuny"; - extraGroups = [ - "docker" - "wheel" - "podman" - ]; - shell = pkgs.fish; - hashedPassword = "$6$U4GoqhuHgdr.h0JP$C/BKslQfOpPJ5lUzrTeQh6i859R/jEKYSF9MaRhWYo5VG6aCDKsvb5xKSifH4nQt6okJixG9ceFh..Mnt93Jt/"; - openssh.authorizedKeys.keys = [ - # key `nixos` in 1password - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINBkozy+X96u5ciX766bJ/AyQ3xm1tXZTIr5+4PVFZFi" - ]; - }; -} |
