Imported from MichaelHeaton/platform-bootstrap (
AGENTS.md). Install upstream withnpx skills add MichaelHeaton/platform-bootstrap. Copyright stays with the author.
AGENTS.md — platform-bootstrap
Guidance for AI agents and humans working in this repository.
Purpose
platform-bootstrap is the platform factory for personal infrastructure. It provisions foundational AWS identity, GitHub repositories, and HCP Terraform workspaces for domain spokes (Cloudflare, Azure, homelab, services) so they can run independently.
It does not own most domain resources — no Azure Entra apps or Proxmox VMs live here.
Exception (2026-09-16, HomeLab 2.0 / #1135): Cloudflare Tunnel substrate for
narrow public hostnames (today: kb-mcp.specterrealm.com → *.cfargotunnel.com
CNAME and remote tunnel ingress → in-cluster service) and the SM token wiring
for that path live in this repo (terraform/cloudflare-tunnel.tf). Broader personal
DNS (mail, etc.) remains in homelab-infra/terraform/cloudflare/. The k3s
cloudflared connector is GitOps in homelab-infra.
Credential strategy
Prefer credentials that expire in minutes and renew themselves. Use AWS Secrets Manager only for what still has to be a stored secret overnight.
| Tier | Pattern | Examples in this repo |
|---|---|---|
| Ephemeral | OIDC / federated identity — no stored secret | AWS deploy roles for service repos (ADR-002) |
| Short-lived | GitHub App installation tokens (~1 hour, auto-minted) | Terraform integrations/github providers |
| Stored | AWS Secrets Manager (scoped IAM per consumer) | platform-bootstrap/github-app-pem; platform-bootstrap/tfe-api-token (HCP org API; also Vault homelab/hcp/tfe-api-token for CI migrate); personal/cloudflare-api-token (DNS, 5 zones); workstation: personal/linear-api-token, personal/notion-api-token; comms: personal/slack-bot-token, personal/discord-bot-token, personal/discord-webhook-url — see runbooks 08–11 |
Do not introduce user PATs with manual expiry for Terraform automation. Use a GitHub App (see runbook 07). Store long-lived material in AWS Secrets Manager (see runbook 08).
Quick reference
| Task | Command |
|---|---|
| Run Python tests | pytest scripts/tests/ -v |
| Structural compliance check | python3 scripts/compliance_check.py --structural-only |
| OpenTofu format check | tofu -chdir=terraform fmt -check -recursive |
| OpenTofu validate (no backend) | tofu -chdir=terraform init -backend=false && tofu -chdir=terraform validate |
| Plan (steady state) | GitHub OpenTofu Plan on self-hosted runner (runner-lxc-01) — PostgreSQL homelab_platform (#97). Not HCP remote. |
| Apply (gated) | Start in GitHub Actions (OpenTofu Apply (gated)); job runs on self-hosted sibling runner. confirm_apply=yes + Environment opentofu-apply. See runbook 11. |
| All Makefile targets | make help |
Non-obvious caveats
- GitHub repo creation is managed here — never imperatively: when asked to create or change a GitHub repository, add or update an entry in
terraform/managed.auto.tfvars. Usemanaged_repositoriesfor repos under the personalMichaelHeatonaccount; usespecterrealm_repositoriesfor repos under theSpecterRealmorg. Do not create repositories withgh repo create, the GitHub REST API, MCP tool calls (mcp__github__create_repository), or any other imperative method. CI/CD will run Terraform plan/apply after the PR is merged. This rule is absolute — no exceptions. - New repo requests have an issue form: prefer using
.github/ISSUE_TEMPLATE/new-repository.ymlas the source of truth for repository name, visibility, default branch, license, Pages, Discussions, and service-account needs. Public repos should include a supportedlicenseblock when added to the relevant repositories list. - Terraform init requires
-backend=falsefor local validation. The S3 backend needs real AWS credentials and bucket config, so always useterraform init -backend=falsewhen runningvalidateorfmtlocally without AWS access. - Python path for pytest: pytest resolves imports via
sys.pathmanipulation in the test files themselves, so runningpytest scripts/tests/ -vfrom the repo root works without extraPYTHONPATHsetup. - No
requirements.txt: Python dependencies (pytest) are installed directly viapip3 install pytest. The compliance script's heavy dependencies (boto3,requests) are optional and only needed for full (non-structural) checks that require AWS/GitHub credentials. - Terraform >= 1.10.0 is required (for S3 native state locking). The update script installs Terraform 1.15.4 to
/usr/local/bin/terraform. To upgrade, change the version in the update script. - The
.terraform/directory created byterraform initis gitignored and ephemeral; re-run init after a fresh clone. - Two GitHub providers, one GitHub App:
provider "github"(default, owner =MichaelHeaton) andprovider "github" { alias = "specterrealm" }(owner =SpecterRealm). Both authenticate via the same GitHub App (github_app_id+ PEM from SM) with different installation IDs per account/org. HCP workspace variables (terraform category):github_app_id,github_app_installation_id,specterrealm_github_app_installation_id,mccleaton_github_app_installation_id. App setup:docs/runbooks/07-github-app-auth.md. PEM in SM:platform-bootstrap/github-app-pem—docs/runbooks/08-aws-secrets-manager.md. - Domain spokes read SM at plan time: platform infra repos live under
McCleaton(mccleaton_repositories), not SpecterRealm. Example:McCleaton/cloudflarereadspersonal/cloudflare-api-tokenvia SM. Setgithub_orgon the pipeline entry for OIDC trust — see runbook 09. - HCP workspaces are factory-managed: each
pipelinesentry withtfe_workspace_enabled(default true) creates an HCP workspace (tfe-workspacesmodule), TFE dynamic-credentials IAM role (tfe-rolesmodule), andTF_TOKEN_app_terraform_ioon the spoke repo. Requires HCP variabletfe_vcs_oauth_token_idon the platform-bootstrap workspace; org API token in SMplatform-bootstrap/tfe-api-token—docs/runbooks/08-aws-secrets-manager.md. - New spoke repo + HCP workspace: two PRs, not one: PR 1 adds only the
*_repositoriesentry and merges so apply creates the GitHub repo. PR 2 adds thepipelinesentry so apply can link the HCP workspace VCS to an existing repo. Combining both in one PR often fails withRepository doesn't exist or isn't accessibleon the first apply (a second apply usually succeeds, but split PRs avoid the failure). For a new GitHub org, also grant the HCP VCS OAuth provider access to that org before PR 2 — seedocs/runbooks/09-cloudflare-terraform-repo.md§8. - platform-bootstrap excludes itself from GitHub Terraform management (ADR-004). It is the foundation — if broken, repair via HCP UI and local Terraform, not via itself.
Homelab factory (MichaelHeaton/homelab-infra)
This repo is the platform factory for homelab ops. Target rename: homelab-platform
(homelab-infra #100).
Operator model (2026-06-22): Two repos for AI + humans — homelab-platform + homelab-infra.
Operational Terraform lives only in homelab-infra (one HCP workspace per terraform/<stack>/).
| Action | Issue | Effect on factory |
|---|---|---|
Move homelab-infra to personal account |
platform-bootstrap #92 | Branch protection + single GitHub App path; drops specterrealm-homelab org overhead |
Merge homelab-vault, homelab-identity, homelab-observability into homelab-infra |
#102 | Fewer github_repository resources → lower McCleaton-Bootstrap RUM |
Wire homelab-unifi through factory |
#103 | Move workspace from legacy SpecterRealm-HomeLab org |
| Gitea on NAS01 | #90–#93 | Unblocks LAN-local CI before OpenTofu state migration |
HCP RUM: The 500-resource cap is per HCP Terraform organization, not summed across orgs.
McCleaton-Bootstrap (~395 RUM) and SpecterRealm-HomeLab (~106 RUM) are both under cap today;
repo consolidation still reduces headroom pressure on the factory monolith.
Spoke docs: homelab-infra/docs/iac-modernization.md · full audit AUDIT-REPORT.md.
When updating homelab pipelines after #102: keep tfe_workspace_name unchanged; update repo_name
to homelab-infra and terraform_working_directory to e.g. terraform/vault.
Cursor Cloud specific instructions
This is an infrastructure-as-code (Terraform + Python) repository with no application services to run. Development work involves Terraform configuration and a Python compliance script.