diff options
Diffstat (limited to '')
30 files changed, 397 insertions, 636 deletions
@@ -3,3 +3,5 @@ watch_file nix/ watch_file flake/*.nix watch_file scripts/*.nix + +source_env_if_exists .envrc.local diff --git a/infra/tf/.envrc.local.template b/.envrc.local.template index 82ebf5f..82ebf5f 100644 --- a/infra/tf/.envrc.local.template +++ b/.envrc.local.template @@ -12,5 +12,7 @@ bin/ /.direnv/ .aider* .env - -.terraform* +config.tf.json +.terraform +.terraform.lock.hcl +/.envrc.local diff --git a/docs/dns.org b/docs/dns.org index 53a0873..1972f5f 100644 --- a/docs/dns.org +++ b/docs/dns.org @@ -2,10 +2,9 @@ ** fcuny.net To access the console: https://dash.cloudflare.com/2c659eeaf2ae9a0206c589c706b3748e/fcuny.net -The records are managed with =tofu=, and I use =nix= to manage the records. To apply the changes, first run =tofu-plan= to see the changes, then =tofu-apply= if they look correct. +The records are managed with =tofu=, and I use =nix= to manage the records. To apply the changes, first run =nix run .#tf -- plan= to see the changes, then =nix run .#tf -- apply= if they look correct. To renew API token: https://dash.cloudflare.com/profile/api-tokens The token should: - be able to edit zone DNS - limited to the domain =fcuny.net= -- have a reasonable TTL (~60 days) @@ -73,9 +73,9 @@ ./flake/devshells.nix ./flake/formatter.nix ./flake/hosts.nix + ./flake/terraform.nix ./flake/overlays.nix ./flake/packages.nix - ./infra/tf/flake-module.nix ]; }; } diff --git a/flake/overlays.nix b/flake/overlays.nix index 1eecfcf..2f9100d 100644 --- a/flake/overlays.nix +++ b/flake/overlays.nix @@ -1,4 +1,9 @@ -{ inputs, self, ... }: +{ + inputs, + self, + config, + ... +}: { flake.overlays.default = _final: prev: { @@ -8,6 +13,17 @@ perSystem = { system, ... }: + let + mkTerraformCfg = + modules: + inputs.terranix.lib.terranixConfiguration { + inherit system; + extraArgs = { + inherit (config.flake) nixosConfigurations; + }; + inherit modules; + }; + in { _module.args.pkgs = import inputs.nixpkgs { inherit system; @@ -18,6 +34,11 @@ inputs.nur.overlays.default inputs.my-go-tools.overlays.default self.overlays.default + (_self: _super: { + adminTerraformCfg = mkTerraformCfg [ + "${self}/terraform/admin" + ]; + }) ]; }; }; diff --git a/flake/terraform.nix b/flake/terraform.nix new file mode 100644 index 0000000..23cc6d3 --- /dev/null +++ b/flake/terraform.nix @@ -0,0 +1,40 @@ +{ lib, ... }: +{ + perSystem = + { pkgs, ... }: + let + mkTfWrapper = + { + tfPlugins, + cfg, + }: + let + pkg = pkgs.opentofu.withPlugins tfPlugins; + in + { + type = "app"; + program = toString ( + pkgs.writers.writeBash "tf" '' + set -xeuo pipefail + ln -snf ${cfg} config.tf.json + exec ${lib.getExe pkg} "$@" + '' + ); + }; + in + { + apps = { + tf = mkTfWrapper { + cfg = pkgs.adminTerraformCfg; + tfPlugins = p: [ + p.cloudflare + p.digitalocean + p.external + p.google + p.null + p.random + ]; + }; + }; + }; +} diff --git a/infra/tf/.envrc b/infra/tf/.envrc deleted file mode 100644 index dd49577..0000000 --- a/infra/tf/.envrc +++ /dev/null @@ -1,12 +0,0 @@ -# shellcheck shell=bash -use flake .#terraform - -watch_file flake-module.nix -watch_file scripts.nix - -source_env_if_exists .envrc.local - -if ! use flake . --impure -then - echo "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to devenv.nix and hit enter to try again." >&2 -fi diff --git a/infra/tf/.gitignore b/infra/tf/.gitignore deleted file mode 100644 index cff6f1b..0000000 --- a/infra/tf/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/.envrc.local diff --git a/infra/tf/backups-bucket/main.tf b/infra/tf/backups-bucket/main.tf deleted file mode 100644 index a86e582..0000000 --- a/infra/tf/backups-bucket/main.tf +++ /dev/null @@ -1,43 +0,0 @@ -terraform { - required_version = ">= 1.0" - - required_providers { - google = { - source = "hashicorp/google" - version = ">= 5.0" - } - } - - backend "gcs" { - bucket = "fcuny-infra-tofu-state" - prefix = "backups" - } -} - -provider "google" { - project = var.project_id - region = var.region -} - -resource "google_storage_bucket" "backups" { - name = var.bucket_name - location = var.location - uniform_bucket_level_access = var.uniform_bucket_level_access - force_destroy = var.force_destroy - public_access_prevention = var.public_access_prevention - storage_class = var.storage_class - - # Optional: Add lifecycle rules for cost optimization - dynamic "lifecycle_rule" { - for_each = var.lifecycle_rules - content { - condition { - age = lifecycle_rule.value.age - } - action { - type = lifecycle_rule.value.action - storage_class = lifecycle_rule.value.storage_class - } - } - } -} diff --git a/infra/tf/backups-bucket/outputs.tf b/infra/tf/backups-bucket/outputs.tf deleted file mode 100644 index 3ca60c5..0000000 --- a/infra/tf/backups-bucket/outputs.tf +++ /dev/null @@ -1,24 +0,0 @@ -output "bucket_name" { - description = "Name of the created backup bucket" - value = google_storage_bucket.backups.name -} - -output "bucket_url" { - description = "URL of the backup bucket" - value = google_storage_bucket.backups.url -} - -output "bucket_self_link" { - description = "Self-link of the backup bucket" - value = google_storage_bucket.backups.self_link -} - -output "bucket_location" { - description = "Location of the backup bucket" - value = google_storage_bucket.backups.location -} - -output "bucket_storage_class" { - description = "Storage class of the backup bucket" - value = google_storage_bucket.backups.storage_class -} diff --git a/infra/tf/backups-bucket/terraform.tfvars b/infra/tf/backups-bucket/terraform.tfvars deleted file mode 100644 index 1d4f2c4..0000000 --- a/infra/tf/backups-bucket/terraform.tfvars +++ /dev/null @@ -1,27 +0,0 @@ -# GCP Configuration -project_id = "fcuny-infra" -region = "us-west1" - -# Bucket Configuration -bucket_name = "fcuny-infra-backups" -location = "us-west1" - -# Bucket Settings -uniform_bucket_level_access = true -force_destroy = true -public_access_prevention = "enforced" -storage_class = "NEARLINE" - -# Lifecycle Rules (optional customization) -lifecycle_rules = [ - { - age = 365 # After 1 year - action = "SetStorageClass" - storage_class = "COLDLINE" - }, - { - age = 730 # After 2 years - action = "SetStorageClass" - storage_class = "ARCHIVE" - } -] diff --git a/infra/tf/backups-bucket/variables.tf b/infra/tf/backups-bucket/variables.tf deleted file mode 100644 index ffdc1a6..0000000 --- a/infra/tf/backups-bucket/variables.tf +++ /dev/null @@ -1,73 +0,0 @@ -variable "project_id" { - description = "GCP Project ID" - type = string - default = "fcuny-infra" -} - -variable "region" { - description = "GCP Region" - type = string - default = "us-west1" -} - -variable "bucket_name" { - description = "Name of the backup storage bucket" - type = string - default = "fcuny-infra-backups" -} - -variable "location" { - description = "Location for the storage bucket" - type = string - default = "us-west1" -} - -variable "uniform_bucket_level_access" { - description = "Enable uniform bucket-level access" - type = bool - default = true -} - -variable "force_destroy" { - description = "Allow destruction of bucket even if it contains objects" - type = bool - default = true -} - -variable "public_access_prevention" { - description = "Public access prevention setting" - type = string - default = "enforced" - - validation { - condition = contains(["enforced", "inherited"], var.public_access_prevention) - error_message = "Public access prevention must be either 'enforced' or 'inherited'." - } -} - -variable "storage_class" { - description = "Storage class for the bucket" - type = string - default = "NEARLINE" - - validation { - condition = contains(["STANDARD", "NEARLINE", "COLDLINE", "ARCHIVE"], var.storage_class) - error_message = "Storage class must be one of: STANDARD, NEARLINE, COLDLINE, ARCHIVE." - } -} - -variable "lifecycle_rules" { - description = "List of lifecycle rules for the bucket" - type = list(object({ - age = number - action = string - storage_class = string - })) - default = [ - { - age = 365 - action = "SetStorageClass" - storage_class = "COLDLINE" - } - ] -} diff --git a/infra/tf/cloudflare-dns/main.tf b/infra/tf/cloudflare-dns/main.tf deleted file mode 100644 index 30442e6..0000000 --- a/infra/tf/cloudflare-dns/main.tf +++ /dev/null @@ -1,24 +0,0 @@ -terraform { - required_version = ">= 1.0" - - required_providers { - cloudflare = { - source = "cloudflare/cloudflare" - version = "~> 5" - } - } - - backend "gcs" { - bucket = "fcuny-infra-tofu-state" - prefix = "cloudflare-dns" - } -} - -provider "cloudflare" { - # API token will be provided via CLOUDFLARE_API_TOKEN environment variable -} - -# Use data source for existing zone instead of managing it -data "cloudflare_zone" "main" { - zone_id = var.zone_id -} diff --git a/infra/tf/cloudflare-dns/records.tf b/infra/tf/cloudflare-dns/records.tf deleted file mode 100644 index a282e2f..0000000 --- a/infra/tf/cloudflare-dns/records.tf +++ /dev/null @@ -1,223 +0,0 @@ -resource "cloudflare_dns_record" "cname_root_0" { - content = "185.199.108.153" - name = "fcuny.net" - proxied = false - ttl = 1 - type = "A" - zone_id = var.zone_id -} - -resource "cloudflare_dns_record" "cname_root_1" { - content = "185.199.110.153" - name = "fcuny.net" - proxied = false - ttl = 1 - type = "A" - zone_id = var.zone_id -} - -resource "cloudflare_dns_record" "cname_root_2" { - content = "185.199.109.153" - name = "fcuny.net" - proxied = false - ttl = 1 - type = "A" - zone_id = var.zone_id -} - -resource "cloudflare_dns_record" "cname_root_3" { - content = "185.199.111.153" - name = "fcuny.net" - proxied = false - ttl = 1 - type = "A" - zone_id = var.zone_id -} - -resource "cloudflare_dns_record" "cname_code" { - content = "165.232.158.110" - name = "code.fcuny.net" - proxied = false - ttl = 1 - type = "A" - zone_id = var.zone_id -} - -resource "cloudflare_dns_record" "cname_go" { - content = "165.232.158.110" - name = "go.fcuny.net" - proxied = false - ttl = 1 - type = "A" - zone_id = var.zone_id -} - -resource "cloudflare_dns_record" "cname_id" { - content = "165.232.158.110" - name = "id.fcuny.net" - proxied = false - ttl = 1 - type = "A" - zone_id = var.zone_id -} - -resource "cloudflare_dns_record" "cname_dkim_0" { - content = "fm1.fcuny.net.dkim.fmhosted.com" - name = "fm1._domainkey" - proxied = false - ttl = 60 - type = "CNAME" - zone_id = var.zone_id -} - -resource "cloudflare_dns_record" "cname_dkim_1" { - content = "fm2.fcuny.net.dkim.fmhosted.com" - name = "fm2._domainkey" - proxied = false - ttl = 60 - type = "CNAME" - zone_id = var.zone_id -} - -resource "cloudflare_dns_record" "cname_dkim_2" { - content = "fm3.fcuny.net.dkim.fmhosted.com" - name = "fm3._domainkey" - proxied = false - ttl = 60 - type = "CNAME" - zone_id = var.zone_id -} - -resource "cloudflare_dns_record" "mx_0" { - content = "in1-smtp.messagingengine.com" - name = "fcuny.net" - priority = 10 - proxied = false - ttl = 1 - type = "MX" - zone_id = var.zone_id -} - -resource "cloudflare_dns_record" "mx_1" { - content = "in2-smtp.messagingengine.com" - name = "fcuny.net" - priority = 20 - proxied = false - ttl = 1 - type = "MX" - zone_id = var.zone_id -} - -resource "cloudflare_dns_record" "srv_caldavs" { - name = "_caldavs._tcp" - priority = 0 - proxied = false - ttl = 1 - type = "SRV" - zone_id = var.zone_id - data = { - port = 443 - priority = 0 - target = "caldav.fastmail.com" - weight = 1 - } -} - -resource "cloudflare_dns_record" "srv_caldav" { - name = "_caldav._tcp" - priority = 0 - proxied = false - ttl = 1 - type = "SRV" - zone_id = var.zone_id - data = { - port = 0 - priority = 0 - target = "." - weight = 0 - } -} - -resource "cloudflare_dns_record" "srv_carddavs" { - name = "_carddavs._tcp" - priority = 0 - proxied = false - ttl = 1 - type = "SRV" - zone_id = var.zone_id - data = { - port = 443 - priority = 0 - target = "carddav.fastmail.com" - weight = 1 - } -} - -resource "cloudflare_dns_record" "srv_carddav" { - name = "_carddav._tcp" - priority = 0 - proxied = false - ttl = 1 - type = "SRV" - zone_id = var.zone_id - data = { - port = 0 - priority = 0 - target = "." - weight = 0 - } -} - -resource "cloudflare_dns_record" "srv_imaps" { - name = "_imaps._tcp" - priority = 0 - proxied = false - ttl = 1 - type = "SRV" - zone_id = var.zone_id - data = { - port = 993 - priority = 0 - target = "imap.fastmail.com" - weight = 1 - } -} - -resource "cloudflare_dns_record" "srv_imap" { - name = "_imap._tcp" - priority = 0 - proxied = false - ttl = 1 - type = "SRV" - zone_id = var.zone_id - data = { - port = 0 - priority = 0 - target = "." - weight = 0 - } -} - -resource "cloudflare_dns_record" "srv_smtp" { - name = "_submission._tcp" - priority = 0 - proxied = false - ttl = 1 - type = "SRV" - zone_id = var.zone_id - data = { - port = 587 - priority = 0 - target = "smtp.fastmail.com" - weight = 1 - } -} - -resource "cloudflare_dns_record" "txt_spf" { - content = "\"v=spf1 include:spf.messagingengine.com ?all\"" - name = "fcuny.net" - proxied = false - ttl = 1 - type = "TXT" - zone_id = var.zone_id -} diff --git a/infra/tf/cloudflare-dns/terraform.tfvars b/infra/tf/cloudflare-dns/terraform.tfvars deleted file mode 100644 index 5a88de4..0000000 --- a/infra/tf/cloudflare-dns/terraform.tfvars +++ /dev/null @@ -1,3 +0,0 @@ -# Zone Configuration -zone_id = "6878e48b5cb81c7d789040632153719d" -state_bucket = "fcuny-infra-tofu-state" diff --git a/infra/tf/cloudflare-dns/variables.tf b/infra/tf/cloudflare-dns/variables.tf deleted file mode 100644 index 24a4a35..0000000 --- a/infra/tf/cloudflare-dns/variables.tf +++ /dev/null @@ -1,11 +0,0 @@ -variable "zone_id" { - description = "Cloudflare zone ID" - type = string - default = "6878e48b5cb81c7d789040632153719d" -} - -variable "state_bucket" { - description = "GCS bucket for Terraform state" - type = string - default = "fcuny-infra-tofu-state" -} diff --git a/infra/tf/do-vm/extra-files-script.sh b/infra/tf/do-vm/extra-files-script.sh deleted file mode 100755 index fa47762..0000000 --- a/infra/tf/do-vm/extra-files-script.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/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 deleted file mode 100644 index 4cff3e9..0000000 --- a/infra/tf/do-vm/main.tf +++ /dev/null @@ -1,82 +0,0 @@ -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 deleted file mode 100644 index 2b9f5c2..0000000 --- a/infra/tf/do-vm/outputs.tf +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index 99af205..0000000 --- a/infra/tf/do-vm/terraform.tfvars +++ /dev/null @@ -1,2 +0,0 @@ -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 deleted file mode 100644 index f5a01dd..0000000 --- a/infra/tf/flake-module.nix +++ /dev/null @@ -1,30 +0,0 @@ -{ - perSystem = - { pkgs, ... }: - let - scripts = import ./scripts.nix { - inherit pkgs; - }; - in - { - devShells.terraform = pkgs.mkShellNoCC { - 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 deleted file mode 100644 index 8e20d30..0000000 --- a/infra/tf/scripts.nix +++ /dev/null @@ -1,58 +0,0 @@ -{ 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-apply-dns" '' - set -xeuo pipefail - ${tofuSetup} - tofu_setup - ${pkgs.opentofu}/bin/tofu -chdir="cloudflare-dns" apply -auto-approve - '') - - (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/scripts/common.nix b/scripts/common.nix index 931480c..b8ab82e 100644 --- a/scripts/common.nix +++ b/scripts/common.nix @@ -1,4 +1,29 @@ { pkgs }: [ (pkgs.writeScriptBin "update-deps" "nix flake update --commit-lock-file") + + (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-state-setup" '' + set -xeuo pipefail + ${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 + '') ] diff --git a/terraform/admin/backups.nix b/terraform/admin/backups.nix new file mode 100644 index 0000000..ae021e5 --- /dev/null +++ b/terraform/admin/backups.nix @@ -0,0 +1,28 @@ +{ lib, ... }: +{ + resource.google_storage_bucket.backups = { + name = "fcuny-infra-backups"; + storage_class = "NEARLINE"; + force_destroy = true; + uniform_bucket_level_access = true; + public_access_prevention = "enforced"; + location = lib.tfRef "var.gcp_region"; + + lifecycle_rule = [ + { + condition.age = 365; # After 1 year + action = { + type = "SetStorageClass"; + storage_class = "COLDLINE"; + }; + } + { + condition.age = 730; # After 2 years + action = { + type = "SetStorageClass"; + storage_class = "ARCHIVE"; + }; + } + ]; + }; +} diff --git a/terraform/admin/base.nix b/terraform/admin/base.nix new file mode 100644 index 0000000..7221742 --- /dev/null +++ b/terraform/admin/base.nix @@ -0,0 +1,30 @@ +{ lib, ... }: +{ + provider.google = { + region = lib.tfRef "var.gcp_region"; + project = lib.tfRef "var.gcp_project"; + }; + + terraform = { + backend.gcs = { + bucket = "fcuny-infra-tofu-state"; + prefix = "admin"; + }; + required_providers = { + google = { + source = "hashicorp/google"; + }; + cloudflare = { + source = "cloudflare/cloudflare"; + }; + digitalocean = { + source = "digitalocean/digitalocean"; + version = "~> 2.0"; + }; + random = { + source = "hashicorp/random"; + version = "~> 3.1"; + }; + }; + }; +} diff --git a/terraform/admin/default.nix b/terraform/admin/default.nix new file mode 100644 index 0000000..0cbbe12 --- /dev/null +++ b/terraform/admin/default.nix @@ -0,0 +1,9 @@ +{ + imports = [ + ./backups.nix + ./base.nix + ./dns.nix + ./droplet-proxy.nix + ./variables.nix + ]; +} diff --git a/terraform/admin/dns.nix b/terraform/admin/dns.nix new file mode 100644 index 0000000..eeddfd5 --- /dev/null +++ b/terraform/admin/dns.nix @@ -0,0 +1,117 @@ +{ lib, ... }: +let + zoneId = lib.tfRef "var.cloudflare_zone_id"; + primaryIPv4 = "165.232.158.110"; + domain = "fcuny.net"; + + # GitHub Pages IP addresses for root domain + githubPagesIPs = [ + "185.199.108.153" + "185.199.110.153" + "185.199.109.153" + "185.199.111.153" + ]; + + mkARecord = name: content: ttl: { + inherit name content ttl; + type = "A"; + proxied = false; + zone_id = zoneId; + }; + + mkCNAMERecord = name: content: ttl: { + inherit name content ttl; + type = "CNAME"; + proxied = false; + zone_id = zoneId; + }; + + mkMXRecord = name: content: priority: { + inherit name content priority; + type = "MX"; + proxied = false; + ttl = 1; + zone_id = zoneId; + }; + + mkSRVRecord = name: port: priority: target: weight: { + inherit name priority; + type = "SRV"; + proxied = false; + ttl = 1; + zone_id = zoneId; + data = { + inherit + port + priority + target + weight + ; + }; + }; + + mkTXTRecord = name: content: { + inherit name content; + type = "TXT"; + proxied = false; + ttl = 1; + zone_id = zoneId; + }; + + mkMultipleARecords = + baseName: ips: + lib.listToAttrs ( + lib.imap0 (i: ip: { + name = "${baseName}_${toString i}"; + value = mkARecord domain ip 1; + }) ips + ); + + dkimRecords = lib.listToAttrs ( + lib.imap1 + (i: _: { + name = "cname_dkim_${toString (i - 1)}"; + value = mkCNAMERecord "fm${toString i}._domainkey" "fm${toString i}.${domain}.dkim.fmhosted.com" 60; + }) + [ + 1 + 2 + 3 + ] + ); + + subdomainARecords = { + cname_code = mkARecord "code.${domain}" primaryIPv4 1; + cname_go = mkARecord "go.${domain}" primaryIPv4 1; + cname_id = mkARecord "id.${domain}" primaryIPv4 1; + }; + + mxRecords = { + mx_0 = mkMXRecord domain "in1-smtp.messagingengine.com" 10; + mx_1 = mkMXRecord domain "in2-smtp.messagingengine.com" 20; + }; + + srvRecords = { + srv_caldavs = mkSRVRecord "_caldavs._tcp" 443 0 "caldav.fastmail.com" 1; + srv_caldav = mkSRVRecord "_caldav._tcp" 0 0 "." 0; + srv_carddavs = mkSRVRecord "_carddavs._tcp" 443 0 "carddav.fastmail.com" 1; + srv_carddav = mkSRVRecord "_carddav._tcp" 0 0 "." 0; + srv_imaps = mkSRVRecord "_imaps._tcp" 993 0 "imap.fastmail.com" 1; + srv_imap = mkSRVRecord "_imap._tcp" 0 0 "." 0; + srv_smtp = mkSRVRecord "_submission._tcp" 587 0 "smtp.fastmail.com" 1; + }; + + txtRecords = { + txt_spf = mkTXTRecord domain "\"v=spf1 include:spf.messagingengine.com ?all\""; + }; + +in +{ + resource.cloudflare_dns_record = + (mkMultipleARecords "cname_root" githubPagesIPs) + // subdomainARecords + // dkimRecords + // mxRecords + // srvRecords + // txtRecords; +} diff --git a/terraform/admin/droplet-proxy.nix b/terraform/admin/droplet-proxy.nix new file mode 100644 index 0000000..51ad138 --- /dev/null +++ b/terraform/admin/droplet-proxy.nix @@ -0,0 +1,89 @@ +{ lib, pkgs, ... }: +let + serverSize = "s-2vcpu-2gb"; + + extraFilesScript = pkgs.writeShellScript "extra-files-script" '' + #!/usr/bin/env bash + set -euo pipefail + + mkdir -p etc/ssh/ + + if [ -n "''${DO_SSH_HOSTKEY:-}" ]; then + echo "Setting up SSH host key from environment" + echo "$DO_SSH_HOSTKEY" | base64 -d > etc/ssh/ssh_host_ed25519_key + chmod 0600 etc/ssh/ssh_host_ed25519_key + else + echo "Warning: DO_SSH_HOSTKEY environment variable not set" + fi + ''; + +in +{ + provider.digitalocean = { + # Token will be read from DIGITALOCEAN_TOKEN environment variable + }; + + resource = { + # Random string for unique naming + random_string.host = { + length = 6; + special = false; + upper = false; + }; + + digitalocean_ssh_key.default = { + name = "nixos-anywhere-\${random_string.host.result}"; + public_key = lib.tfRef "var.digitalocean_public_key"; + }; + + digitalocean_droplet.nixos = { + name = "nixos-\${random_string.host.result}"; + image = "ubuntu-24-04-x64"; # Bootstrap image + size = serverSize; + region = lib.tfRef "var.digitalocean_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.do-rproxy.config.system.build.toplevel"; + }; + + nixos-disko = { + source = "github.com/nix-community/nixos-anywhere//terraform/nix-build"; + attribute = ".#nixosConfigurations.do-rproxy.config.system.build.diskoScript"; + }; + + 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 = toString extraFilesScript; + }; + }; + + output = { + server_ip = { + description = "IP address of the NixOS server"; + value = "\${digitalocean_droplet.nixos.ipv4_address}"; + }; + + ssh_command = { + description = "SSH command to connect to the server"; + value = "ssh root@\${digitalocean_droplet.nixos.ipv4_address}"; + }; + + server_name = { + description = "Name of the created server"; + value = "\${digitalocean_droplet.nixos.name}"; + }; + }; +} diff --git a/terraform/admin/variables.nix b/terraform/admin/variables.nix new file mode 100644 index 0000000..0c795dd --- /dev/null +++ b/terraform/admin/variables.nix @@ -0,0 +1,29 @@ +{ + variable = { + gcp_region = { + description = "GCP region"; + type = "string"; + default = "us-west1"; + }; + gcp_project = { + description = "GCP project"; + type = "string"; + default = "fcuny-infra"; + }; + cloudflare_zone_id = { + description = "cloudflare zone ID"; + type = "string"; + default = "6878e48b5cb81c7d789040632153719d"; + }; + digitalocean_region = { + description = "DigitalOcean region"; + type = "string"; + default = "SFO3"; + }; + digitalocean_public_key = { + description = "SSH public key"; + type = "string"; + default = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINBkozy+X96u5ciX766bJ/AyQ3xm1tXZTIr5+4PVFZFi"; + }; + }; +} |
