diff --git a/docs/infrastructure-inventory.md b/docs/infrastructure-inventory.md index 89eb0ea..d09e16d 100644 --- a/docs/infrastructure-inventory.md +++ b/docs/infrastructure-inventory.md @@ -50,6 +50,12 @@ Compose files define intended service runtime composition, networking, labels, a Dynu write operations are intentionally blocked in this repository stage. +### 7) Terraform Dynu DNS layer + +`infrastructure/terraform/dynu/` is the brownfield Terraform DNS mirror/reconciliation root for Dynu domain/record inventory outputs. + +At this stage it is primarily documentation-oriented catalog output, ready for one-object-at-a-time imports. + ## Output shaping expectations When adding Terraform outputs for documentation/tooling: diff --git a/docs/source-of-truth.md b/docs/source-of-truth.md index f38d54b..3ac8125 100644 --- a/docs/source-of-truth.md +++ b/docs/source-of-truth.md @@ -30,6 +30,10 @@ Use Terraform when documenting/reconciling existing: Do **not** treat Terraform as a full replacement for Compose operations in this repo. +- Dynu public DNS records remain authoritative at Dynu. +- Terraform Dynu configuration mirrors/reconciles Dynu DNS state for documentation and controlled drift management. +- Imported Dynu Terraform state reflects actual provider-side DNS state at import time. + ### Ansible bootstrap decisions diff --git a/docs/terraform-workflows.md b/docs/terraform-workflows.md index 37d216a..91ca6e2 100644 --- a/docs/terraform-workflows.md +++ b/docs/terraform-workflows.md @@ -43,6 +43,21 @@ Use for existing Proxmox VMs and metadata reconciliation. 5. Keep lifecycle ignore rules narrow and explicit. 6. Iterate per VM until plan stabilizes. + +## Dynu DNS workflow + +Directory: `infrastructure/terraform/dynu/` + +Use for existing Dynu domains and DNS records. + +1. Add or confirm the documentation catalog entry for one hostname. +2. Confirm the provider resource type and import ID format. +3. Import one existing domain or DNS record at a time. +4. Inspect state with `terraform state show`. +5. Reconcile only stable, meaningful attributes into hand-maintained `.tf`. +6. Keep record IDs, dynamic DNS targets, and provider-computed values out unless intentionally required. +7. Re-run plan until intended scope is clean. + ## Physical host metadata workflow Physical host metadata currently lives in Proxmox Terraform locals/outputs and is used as documentation inventory context. diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md index 8622aff..5bd185b 100644 --- a/infrastructure/terraform/README.md +++ b/infrastructure/terraform/README.md @@ -10,17 +10,21 @@ It does **not** replace Docker Compose as runtime deployment authority. - Physical host metadata represented in Terraform locals/outputs. - Select Docker container mirror resources for documentation-oriented tracking. - Outputs that can support documentation and later downstream tooling. +- Dynu DNS domain/record import and documentation inventory. ## What Terraform is not used for (today) - Replacing `services-up.sh` / Compose for day-to-day app runtime orchestration. - Broad, immediate greenfield provisioning of the whole stack. - Casual `apply` operations across all infrastructure. +- Replacing Dynu as DNS authority. +- Blindly recreating production DNS records without import/reconciliation. ## Directory map - `proxmox/` — imported/reconciled VM resources and host metadata outputs. - `docker/` — selective Docker container import/mirror resources. +- `dynu/` — Dynu DNS brownfield import/reconciliation and DNS documentation outputs. - `bootstrap/` — backend/provider bootstrap scaffolding. - `modules/` — placeholder module directories for future stable abstractions. - `scripts/reconcile_from_plan.sh` — helper to convert generated plan config into reviewable draft files. diff --git a/infrastructure/terraform/dynu/README.md b/infrastructure/terraform/dynu/README.md new file mode 100644 index 0000000..2bd99f9 --- /dev/null +++ b/infrastructure/terraform/dynu/README.md @@ -0,0 +1,67 @@ +# Dynu Terraform Layer (Brownfield DNS Reconciliation) + +This Terraform root is for **Dynu DNS brownfield import/reconciliation** and documentation outputs. + +Dynu remains the authoritative DNS provider for existing records. Terraform here is used to mirror and reconcile existing DNS state incrementally, not to casually recreate production DNS from scratch. + +## Provider + +- Source: `beatz174-bit/dynu` +- Version constraint: `>= 0.1.0` + +Authentication is local-only and must not be committed. + +## Credentials and auth + +Use local `terraform.tfvars` (or environment variables if supported by the provider release you use). + +Variables included: + +- `dynu_api_key` (sensitive) +- `dynu_username` (optional, sensitive) +- `dynu_password` (optional, sensitive) + +> Keep real values out of git and out of shared logs. + +## Safety + +- Do not commit `terraform.tfvars`, `.tfstate*`, or `.terraform/`. +- Import/reconcile one domain or record at a time. +- Treat generated config as draft input, not final truth. + +## Safe validation commands + +```bash +cd infrastructure/terraform/dynu + +terraform fmt -check -recursive +terraform init -backend=false -input=false +terraform validate +``` + +## Local workflow + +```bash +cp terraform.tfvars.example terraform.tfvars +$EDITOR terraform.tfvars +terraform init +terraform plan +``` + +## Import workflow (placeholder examples) + +```bash +terraform import dynu_dns_domain.lan_ddnsgeek_com '' +terraform state show dynu_dns_domain.lan_ddnsgeek_com +terraform plan +``` + +Or with import blocks: + +```bash +cp imports.tf.example imports.tf +$EDITOR imports.tf +terraform plan -generate-config-out=generated-dynu.tf +``` + +Confirm exact resource types and import ID formats from the provider docs before running imports. diff --git a/infrastructure/terraform/dynu/domains.tf b/infrastructure/terraform/dynu/domains.tf new file mode 100644 index 0000000..28fa409 --- /dev/null +++ b/infrastructure/terraform/dynu/domains.tf @@ -0,0 +1,3 @@ +locals { + dynu_domain = "lan.ddnsgeek.com" +} diff --git a/infrastructure/terraform/dynu/imports.tf.example b/infrastructure/terraform/dynu/imports.tf.example new file mode 100644 index 0000000..d693c07 --- /dev/null +++ b/infrastructure/terraform/dynu/imports.tf.example @@ -0,0 +1,13 @@ +# Copy this file to imports.tf and adjust values after confirming the +# published provider docs for resource type names and import ID formats. + +# Example placeholder shape only: +# import { +# to = dynu_dns_domain.lan_ddnsgeek_com +# id = "REPLACE_WITH_DYNU_DOMAIN_IMPORT_ID" +# } +# +# import { +# to = dynu_dns_record.grafana_lan_ddnsgeek_com +# id = "REPLACE_WITH_DYNU_RECORD_IMPORT_ID" +# } diff --git a/infrastructure/terraform/dynu/outputs.tf b/infrastructure/terraform/dynu/outputs.tf new file mode 100644 index 0000000..641808e --- /dev/null +++ b/infrastructure/terraform/dynu/outputs.tf @@ -0,0 +1,19 @@ +output "dynu_domain" { + description = "Primary Dynu domain represented by this Terraform root." + value = local.dynu_domain +} + +output "dynu_dns_records_catalog" { + description = "Documentation catalog of expected Dynu DNS records discovered from repo service exposure." + value = local.dynu_dns_records_catalog +} + +output "dynu_dns_inventory" { + description = "Documentation-friendly Dynu DNS inventory for export and merge into broader infrastructure docs." + value = { + provider = "dynu" + domain = local.dynu_domain + record_count = length(local.dynu_dns_records_catalog) + records = local.dynu_dns_records_catalog + } +} diff --git a/infrastructure/terraform/dynu/provider.tf b/infrastructure/terraform/dynu/provider.tf new file mode 100644 index 0000000..8a12311 --- /dev/null +++ b/infrastructure/terraform/dynu/provider.tf @@ -0,0 +1,5 @@ +provider "dynu" { + # Keep auth local-only; do not commit credentials. + # Provider schema must be confirmed against registry docs before changing fields. + api_key = var.dynu_api_key +} diff --git a/infrastructure/terraform/dynu/records.tf b/infrastructure/terraform/dynu/records.tf new file mode 100644 index 0000000..42e48e3 --- /dev/null +++ b/infrastructure/terraform/dynu/records.tf @@ -0,0 +1,147 @@ +locals { + dynu_dns_records_catalog = { + auth = { + fqdn = "auth.lan.ddnsgeek.com" + hostname = "auth" + service = "authelia" + source = "core/authelia/docker-compose.yml" + purpose = "Authentication portal" + record_type = null + ttl = null + target = null + proxied = null + } + gitea = { + fqdn = "gitea.lan.ddnsgeek.com" + hostname = "gitea" + service = "gitea" + source = "apps/gitea/docker-compose.yml" + purpose = "Gitea service endpoint" + record_type = null + ttl = null + target = null + proxied = null + } + gotify = { + fqdn = "gotify.lan.ddnsgeek.com" + hostname = "gotify" + service = "gotify" + source = "monitoring/gotify/docker-compose.yml" + purpose = "Gotify notifications" + record_type = null + ttl = null + target = null + proxied = null + } + grafana = { + fqdn = "grafana.lan.ddnsgeek.com" + hostname = "grafana" + service = "grafana" + source = "monitoring/grafana/docker-compose.yml" + purpose = "Grafana monitoring UI" + record_type = null + ttl = null + target = null + proxied = null + } + familytree = { + fqdn = "familytree.lan.ddnsgeek.com" + hostname = "familytree" + service = "gramps" + source = "apps/gramps/docker-compose.yml" + purpose = "Family tree application" + record_type = null + ttl = null + target = null + proxied = null + } + influxdb = { + fqdn = "influxdb.lan.ddnsgeek.com" + hostname = "influxdb" + service = "influxdb" + source = "monitoring/influxdb/docker-compose.yml" + purpose = "InfluxDB metrics endpoint" + record_type = null + ttl = null + target = null + proxied = null + } + monitor_kuma = { + fqdn = "monitor-kuma.lan.ddnsgeek.com" + hostname = "monitor-kuma" + service = "uptime-kuma" + source = "monitoring/uptime-kuma/docker-compose.yml" + purpose = "Uptime Kuma monitoring UI" + record_type = null + ttl = null + target = null + proxied = null + } + mtls_bridge = { + fqdn = "mtls-bridge.lan.ddnsgeek.com" + hostname = "mtls-bridge" + service = "mtls-bridge" + source = "monitoring/mtls-bridge/docker-compose.yml" + purpose = "mTLS bridge API" + record_type = null + ttl = null + target = null + proxied = null + } + nextcloud = { + fqdn = "nextcloud.lan.ddnsgeek.com" + hostname = "nextcloud" + service = "nextcloud-webapp" + source = "apps/nextcloud/docker-compose.yml" + purpose = "Nextcloud service endpoint" + record_type = null + ttl = null + target = null + proxied = null + } + node_red = { + fqdn = "node-red.lan.ddnsgeek.com" + hostname = "node-red" + service = "node-red" + source = "monitoring/node-red/docker-compose.yml" + purpose = "Node-RED automation UI/API" + record_type = null + ttl = null + target = null + proxied = null + } + passbolt = { + fqdn = "passbolt.lan.ddnsgeek.com" + hostname = "passbolt" + service = "passbolt-webapp" + source = "apps/passbolt/docker-compose.yml" + purpose = "Passbolt password management" + record_type = null + ttl = null + target = null + proxied = null + } + portainer = { + fqdn = "portainer.lan.ddnsgeek.com" + hostname = "portainer" + service = "portainer" + source = "monitoring/portainer/docker-compose.yml" + purpose = "Portainer admin endpoint" + record_type = null + ttl = null + target = null + proxied = null + } + prometheus = { + fqdn = "prometheus.lan.ddnsgeek.com" + hostname = "prometheus" + service = "prometheus" + source = "monitoring/prometheus/docker-compose.yml" + purpose = "Prometheus metrics endpoint" + record_type = null + ttl = null + target = null + proxied = null + } + } +} diff --git a/infrastructure/terraform/dynu/terraform.tfvars.example b/infrastructure/terraform/dynu/terraform.tfvars.example new file mode 100644 index 0000000..05632f8 --- /dev/null +++ b/infrastructure/terraform/dynu/terraform.tfvars.example @@ -0,0 +1,4 @@ +# Local-only credentials. Do not commit real values. +dynu_api_key = "replace-with-dynu-api-key" +dynu_username = null +dynu_password = null diff --git a/infrastructure/terraform/dynu/variables.tf b/infrastructure/terraform/dynu/variables.tf new file mode 100644 index 0000000..3614969 --- /dev/null +++ b/infrastructure/terraform/dynu/variables.tf @@ -0,0 +1,20 @@ +variable "dynu_api_key" { + description = "Dynu API key/token used by the Dynu Terraform provider." + type = string + sensitive = true + default = null +} + +variable "dynu_username" { + description = "Optional Dynu username, only if required by the provider." + type = string + sensitive = true + default = null +} + +variable "dynu_password" { + description = "Optional Dynu password, only if required by the provider." + type = string + sensitive = true + default = null +} diff --git a/infrastructure/terraform/dynu/versions.tf b/infrastructure/terraform/dynu/versions.tf new file mode 100644 index 0000000..32b6064 --- /dev/null +++ b/infrastructure/terraform/dynu/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.6.0" + + required_providers { + dynu = { + source = "beatz174-bit/dynu" + version = ">= 0.1.0" + } + } +}