Establish clean homelab infrastructure baseline
lint / yaml (push) Has been cancelled
lint / ansible (push) Has been cancelled
lint / terraform (push) Has been cancelled

Reorganize the brownfield repository, remove retired and generated artifacts, harden ignore rules, and record the GitOps/IaC redesign.
This commit is contained in:
2026-09-09 16:47:20 +00:00
commit 88a02ababa
418 changed files with 50579 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
*.tfstate
*.tfstate.*
.terraform/
terraform.tfvars
crash.log
+22
View File
@@ -0,0 +1,22 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/azuread" {
version = "3.9.0"
constraints = "~> 3.0"
hashes = [
"h1:+ZknnMPMLJ1dIVqxto9ZWoakX4ljsek5cmajhUfEwN4=",
"zh:1c3e89cf19118fc07d7b04257251fc9897e722c16e0a0df7b07fcd261f8c12e7",
"zh:39b11a075e4baa4f6ed5c72a8427013d50f43eecc1a7603b73bccf80f952f758",
"zh:41484c196c943b39411f561e70a308bd2a71da18155bfec7381ba0bd61361d34",
"zh:42068e5da223494beea5f7fcb9057c308cbfa92f96e53c50083e2639216479d8",
"zh:464d7da44682443a4b64bfdaf3d0eb53011c6e1471f244f6354c4d5bca18edce",
"zh:49f597ea3fac39931ff91e55afd5b5cc91e449920a03716f82509d588aaab708",
"zh:6092c376accfc50b555b7a0cd56b76c09abc3d65ac9dd5069063d6f9f1e76d3b",
"zh:65326a9f3ac0783c16e05c16422d191f0a926b8d021fd5303c1fdf8dc42f16e9",
"zh:784214ed809347d74562bb38194c0cef57831eaa621ba3b7cdd3fe7a7a76d844",
"zh:b4233f9bc791adc7d6643507fa5b47360a21125763a072d953586151cacb65f9",
"zh:c4ecdd995ff99b7e362e087c45f080816bcf097da5be257c87b912210e45dd3e",
"zh:f0122771f71cb98248e70cdd6c2ccd3bffb34e79d19897fa28b785c86b2312ed",
]
}
+40
View File
@@ -0,0 +1,40 @@
# Remote state in SeaweedFS S3, on the LAN.
#
# WHY remote at all: local state means the only copy lives on this laptop, which is
# also the k3s node, the NFS server and the libvirt host — i.e. the single point of
# failure. It also cannot be locked, so two concurrent applies silently corrupt it.
#
# WHY s3.ad.ddupan.top and NOT obj.ddupan.top: the public name resolves to
# Cloudflare and hairpins through the WAN. On 2026-07-28 that path was blackholed
# for hours by a dead VPN tunnel. State must be reachable when the WAN is not —
# it is what you need DURING an incident. See ../../seaweedfs/httproute-s3.yaml.
#
# CREDENTIALS are not in this file. Export them before running terraform:
# export AWS_ACCESS_KEY_ID=$(bao kv get -field=... kv/k8s/seaweedfs-s3) # see README
# export AWS_SECRET_ACCESS_KEY=...
# The `terraform` S3 identity is scoped to this bucket only — it deliberately
# cannot create buckets or read anything else in the store.
terraform {
backend "s3" {
bucket = "tfstate"
key = "smtp-relay/terraform.tfstate"
endpoints = {
s3 = "https://s3.ad.ddupan.top"
}
# SeaweedFS is not AWS: it has no regions, no IAM, no metadata service and no
# account IDs, so every AWS-specific validation has to be skipped or the
# provider fails before it ever talks to the endpoint.
region = "us-east-1"
use_path_style = true
skip_credentials_validation = true
skip_metadata_api_check = true
skip_region_validation = true
skip_requesting_account_id = true
# Native S3 locking (Terraform >= 1.10; this repo runs 1.15). Writes a
# .tflock object alongside the state — no DynamoDB table needed.
use_lockfile = true
}
}
+50
View File
@@ -0,0 +1,50 @@
# Entra app registration for the Postfix sasl-xoauth2 relay.
# Delegated Microsoft Graph SMTP.Send + admin consent + a client secret. The relay
# still needs a one-time device-code login to mint the refresh token (see ../README.md).
data "azuread_client_config" "current" {}
# Microsoft Graph well-known IDs, so we don't hardcode the SMTP.Send permission UUID.
data "azuread_application_published_app_ids" "well_known" {}
resource "azuread_service_principal" "msgraph" {
client_id = data.azuread_application_published_app_ids.well_known.result["MicrosoftGraph"]
use_existing = true
}
resource "azuread_application" "smtp_relay" {
display_name = var.app_display_name
sign_in_audience = "AzureADMyOrg"
# Enables "Allow public client flows" so the device-code flow works, while we still
# keep a client secret for confidential refresh.
fallback_public_client_enabled = true
public_client {
redirect_uris = ["https://login.microsoftonline.com/common/oauth2/nativeclient"]
}
required_resource_access {
resource_app_id = data.azuread_application_published_app_ids.well_known.result["MicrosoftGraph"]
resource_access {
id = azuread_service_principal.msgraph.oauth2_permission_scope_ids["SMTP.Send"]
type = "Scope" # delegated
}
}
}
resource "azuread_service_principal" "smtp_relay" {
client_id = azuread_application.smtp_relay.client_id
}
# NOTE: no client secret. This is a PUBLIC client (device-code delegated flow); the
# refresh token is the credential. Presenting a secret makes Entra reject the refresh
# with AADSTS700025 ("Client is public..."). CLIENT_SECRET in the k8s secret is empty.
# Org-wide admin consent for the delegated SMTP.Send scope (no per-user consent prompt).
resource "azuread_service_principal_delegated_permission_grant" "smtp_send" {
service_principal_object_id = azuread_service_principal.smtp_relay.object_id
resource_service_principal_object_id = azuread_service_principal.msgraph.object_id
claim_values = ["SMTP.Send"]
}
+15
View File
@@ -0,0 +1,15 @@
# Feed these into smtp-relay/secret.yaml (CLIENT_ID / CLIENT_SECRET / TENANT_ID).
# terraform output -raw client_id
# terraform output -raw tenant_id
# terraform output -raw client_secret
output "client_id" {
value = azuread_application.smtp_relay.client_id
description = "CLIENT_ID for the relay secret."
}
output "tenant_id" {
value = data.azuread_client_config.current.tenant_id
description = "TENANT_ID for the relay secret."
}
+6
View File
@@ -0,0 +1,6 @@
variable "app_display_name" {
type = string
default = "smtp-relay-sasl-xoauth2"
description = "Display name of the Entra app registration for the SMTP relay."
}
+14
View File
@@ -0,0 +1,14 @@
terraform {
required_version = ">= 1.5"
required_providers {
azuread = {
source = "hashicorp/azuread"
version = "~> 3.0"
}
}
}
# Authenticates via Azure CLI by default: run `az login` as an account that can
# create app registrations AND grant admin consent (Global Admin / Privileged Role
# Admin) before `terraform apply`.
provider "azuread" {}