diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e9ca2fc --- /dev/null +++ b/.env.example @@ -0,0 +1,53 @@ +# Homelab Infrastructure Configuration +# Copy this file to .env and fill in your values +# The .env file is gitignored for security + +# ============================================================================= +# Docker Compose user mapping (for both Ansible and OpenTofu containers) +# These values are auto-populated by 'make init' (run from ansible/ directory) +# ============================================================================= +UID=1000 +GID=1000 + +# ============================================================================= +# OpenBao Secrets Management +# Required for Ansible/OpenTofu to retrieve secrets from OpenBao +# ============================================================================= + +# OpenBao server address +BAO_ADDR=https://bao.lan.quietlife.net:8200 + +# OpenBao token (create with appropriate policy - see docs/openbao-secrets.md) +# This token should have read-only access to secrets needed by automation +BAO_TOKEN= + +# Skip TLS certificate verification +# Only needed for: +# - Initial bootstrap (before wildcard cert is deployed to OpenBao) +# - Recovery when wildcard cert has expired +# Once wildcard cert is deployed, remove this or set to false +# See docs/openbao.md "TLS Certificate Management" for details +# BAO_SKIP_VERIFY=true + +# ============================================================================= +# Proxmox API Configuration (for OpenTofu VM provisioning) +# ============================================================================= + +# Proxmox API URL (use hostname for TLS verification with wildcard cert) +PM_API_URL=https://pve1.lan.quietlife.net:8006/api2/json + +# Proxmox API Token ID (format: user@realm!tokenid) +# Example: root@pam!tofu-token +PM_API_TOKEN_ID= + +# Proxmox API Token Secret +PM_API_TOKEN_SECRET= + +# Proxmox node name (usually "pve", not the inventory hostname) +PM_NODE_NAME=pve + +# Datastore for downloaded cloud images (defaults to 'local') +PM_IMAGE_DATASTORE_ID=local + +# Datastore for VM disks (defaults to 'local-lvm') +PM_VM_DATASTORE_ID=local-lvm diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..c7cac66 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,25 @@ +version: 2 +updates: + # OpenTofu provider updates + - package-ecosystem: "terraform" + directory: "/tofu" + schedule: + interval: "weekly" + + # Development/build Dockerfiles + # Note: Dependabot's docker ecosystem only works with Dockerfile files, + # not docker-compose.yml. Directories with only compose files are excluded. + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "monthly" + + - package-ecosystem: "docker" + directory: "/tofu" + schedule: + interval: "monthly" + + - package-ecosystem: "docker" + directory: "/ansible" + schedule: + interval: "monthly" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f9ca3c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# Root-level secrets +.env +.env.* +!.env.example diff --git a/.trufflehog-exclude.txt b/.trufflehog-exclude.txt index 10a7998..1c147fe 100644 --- a/.trufflehog-exclude.txt +++ b/.trufflehog-exclude.txt @@ -9,3 +9,5 @@ ^/repo/tofu/\.env$ ^/repo/tofu/\.env\..* ^/repo/tofu/secrets/ +# Exclude generated Let's Encrypt certificates (lego) +^/repo/lego/certs/ diff --git a/AGENTS.md b/AGENTS.md index 543ce6c..b646b54 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,9 +1,24 @@ # Homelab monorepo agent guide +## BEFORE MAKING ANY CODE CHANGES +- [ ] Check current branch with `git branch --show-current` +- [ ] If on `master`, STOP and alert the user to create/switch to a feature branch first +- [ ] Only proceed with code changes once on a non-master branch + +--- + - **Layout**: `ansible/` (host config, formerly homelab-ansible), `tofu/` (OpenTofu VM provisioning, formerly homelab-tofu), `docs/` (design notes like `docs/dns-plan.md`). - **Make wrappers**: From repo root use `make ansible-` and `make tofu-` to call component Makefiles (see `make ansible-help`, `make tofu-help`). Avoid running `ansible-playbook` or `tofu` directly; prefer the Dockerized targets. - **Containerized workflows**: Both stacks expect Docker/Compose; use provided Make targets for build/plan/apply/lint. Keep secrets in local `.env` files (gitignored) and never commit keys or state. - **DNS direction**: We standardized on NSD as the authoritative server (`lan.quietlife.net`), with Unbound on the firewall as recursive + stub to NSD. DHCP/DNS should be driven from a shared data model; include an `dhcp-.lan.quietlife.net` pool for ephemeral clients. See `docs/dns-plan.md`. - **Host data**: Inventories and templates live under `ansible/` (e.g., `inventories/hosts.yml`, `roles/openbsd_firewall`). Keep DHCP reservations, DNS records, and VM definitions consistent by editing shared host metadata when adding nodes. +- **Networking**: Proxmox hosts only expose a single useful bridge (`vmbr0`). Assume VMs (even ones with multiple NICs) attach to that same bridge unless the user explicitly requests a different network. - **Testing/validation**: Run relevant Dockerized checks before changes: `make ansible-trufflehog`, `make tofu-validate/plan`, `make ansible-ping`/`ansible-firewall` dry runs as needed. Keep changes scoped; avoid large “deploy everything” unless required. -- **No destructive ops**: Don’t use sudo; avoid wiping state or force pushes. If you must propose a destructive command, surface it to the user instead of running it. +- **No destructive ops**: Don't use sudo; avoid wiping state or force pushes. If you must propose a destructive command, surface it to the user instead of running it. +- **No service restarts without approval**: NEVER restart containers, services, or VMs without explicit user approval. Always ask first - even if a restart seems like the obvious fix. Diagnose the problem, explain the fix, and let the user decide when to restart. +- **Deployment workflow**: Do NOT automatically deploy changes (ansible playbooks, tofu apply, etc.) without explicit user approval. After making code changes, ask the user if they want to deploy rather than assuming. The user prefers to review and deploy manually so they can observe and learn. Only run `make ansible-*` or `make tofu-apply` when the user explicitly requests it or approves it. +- **Let the user drive**: When suggesting commands to run (dry-runs, tests, deploys), prefer telling the user the command rather than running it automatically. The user wants to build muscle memory and learn by doing. Exception: simple read-only diagnostics like `git status` or `ping` are fine to run. +- **Git workflow**: The user will handle git commits and PR creation. Do NOT run `git commit`, `git push`, or `gh pr create` unless explicitly asked. Focus on making code changes; the user will review and commit them. **Important**: Before making code changes, verify you're not on `master` branch. If on master, alert the user so they can create/switch to an appropriate feature branch first. +- **SSH key errors**: If you encounter SSH permission denied errors (e.g., `git@github.com: Permission denied (publickey)`), simply ask the user to add their SSH key (it's passphrase-protected) and then retry the command. Don't attempt to run ssh-agent or ssh-add yourself. +- **IP address allocation**: Before assigning a static IP to a new VM, always verify it's not already in use by checking `ansible/roles/openbsd_firewall/templates/dhcpd.conf.j2` for existing reservations and running `ping -c1 ` to confirm no active host. Prefer IPs in the 10.10.15.10-99 range for infrastructure VMs (the .100-.254 range is the DHCP pool). +- **Documentation**: When adding new Makefile targets, always update the root `README.md` with examples and descriptions of the new targets. Keep the documentation in sync with available commands. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/Dockerfile.trufflehog b/Dockerfile.trufflehog index cfb8ef6..0b21459 100644 --- a/Dockerfile.trufflehog +++ b/Dockerfile.trufflehog @@ -1,4 +1,4 @@ -FROM ghcr.io/trufflesecurity/trufflehog:3.79.0 +FROM ghcr.io/trufflesecurity/trufflehog:3.92.5 WORKDIR /repo ENTRYPOINT ["trufflehog"] diff --git a/Makefile b/Makefile index 1273907..65f03cd 100644 --- a/Makefile +++ b/Makefile @@ -2,14 +2,16 @@ SHELL := /bin/bash ANSIBLE_DIR := ansible TOFU_DIR := tofu +LEGO_DIR := lego -ANSIBLE_TARGETS := help init build galaxy version ping access_check users users-check firewall firewall-check felix felix-check all check-all run adhoc sh build-tinyfugue trufflehog +ANSIBLE_TARGETS := help init build galaxy version ping access_check proxmox proxmox-check firewall firewall-check felix felix-check all check-all run adhoc sh build-tinyfugue trufflehog TOFU_TARGETS := help build shell init plan apply destroy fmt validate trufflehog clean +LEGO_TARGETS := help run renew renew-staging renew-force list show fetch-creds store retrieve TRUFFLEHOG_ARGS ?= filesystem /repo --fail --no-update --exclude-paths /repo/.trufflehog-exclude.txt .DEFAULT_GOAL := help -.PHONY: help ansible tofu ansible-% tofu-% trufflehog install-precommit-hook +.PHONY: help ansible tofu lego ansible-% tofu-% lego-% trufflehog grype grype-compose grype-pull install-precommit-hook help: @echo "homelab monorepo" @@ -17,11 +19,16 @@ help: @echo "Use namespaced targets to drive component Makefiles:" @echo " make ansible- (targets: $(ANSIBLE_TARGETS))" @echo " make tofu- (targets: $(TOFU_TARGETS))" + @echo " make lego- (targets: $(LEGO_TARGETS))" @echo " make trufflehog (root) scan entire repo for secrets" + @echo " make grype IMAGE= (root) scan a container image for CVEs" + @echo " make grype-compose (root) scan all images in production compose" + @echo " make grype-pull (root) pull the grype scanner image" @echo "" @echo "Shortcuts:" @echo " make ansible # same as: (cd ansible && make) -> opens component help/defaults" @echo " make tofu # same as: (cd tofu && make) -> opens component help/defaults" + @echo " make lego # same as: (cd lego && make) -> opens component help/defaults" @echo " make install-precommit-hook # install root pre-commit hook (trufflehog)" ansible-%: @@ -36,8 +43,30 @@ ansible: tofu: @$(MAKE) -C $(TOFU_DIR) +lego-%: + @$(MAKE) -C $(LEGO_DIR) $* + +lego: + @$(MAKE) -C $(LEGO_DIR) + trufflehog: docker compose -f docker-compose.trufflehog.yml run --rm trufflehog $(TRUFFLEHOG_ARGS) +# Grype vulnerability scanning +GRYPE_ARGS ?= +IMAGE ?= + +grype-pull: + docker compose -f docker-compose.grype.yml pull + +grype: +ifndef IMAGE + $(error IMAGE is required. Usage: make grype IMAGE=nginx:latest) +endif + docker compose -f docker-compose.grype.yml run --rm grype $(IMAGE) $(GRYPE_ARGS) + +grype-compose: + ./scripts/grype-compose.sh ansible/files/stacks/docker-compose.yml $(GRYPE_ARGS) + install-precommit-hook: @./scripts/install-precommit-hook.sh diff --git a/README.md b/README.md index b35239c..f0a79be 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ Examples: make ansible-firewall # apply firewall config via Ansible container make ansible-firewall-check # dry-run firewall (check+diff) make ansible-felix-check # dry-run felix VPS playbook +make ansible-gaming # provision gaming servers (all active profiles) +make ansible-gaming-check # dry-run gaming server config +make ansible-gaming PROFILE=vs-buttopia # provision specific profile only +make ansible-gaming-configs # deploy configs/mods only (no deps/lgsm install) make ansible-run PLAY=playbooks/firewall.yml LIMIT=openbsd_firewalls OPTS="--check --diff" # dry-run limited group make ansible-all # run users + firewall + felix (apply) make ansible-check-all # dry-run users + firewall + felix @@ -24,6 +28,9 @@ make ansible-trufflehog # run secrets scan for Ansible tree make tofu-trufflehog # run secrets scan for Tofu tree make trufflehog # scan entire repo for secrets (root-level) make install-precommit-hook # install root pre-commit hook (trufflehog) +make grype IMAGE=nginx:latest # scan a container image for CVEs +make grype-compose # scan all images in production docker-compose +make grype-pull # pull the grype scanner image ``` ### Common scenarios @@ -34,11 +41,80 @@ make install-precommit-hook # install root pre-commit hook (trufflehog) - **Apply to all standard playbooks**: `make ansible-all` (users, firewall, felix) — use sparingly - **Dry-run all standard playbooks**: `make ansible-check-all` +### Gaming servers +Game servers on Linode VPS. Each game profile gets its own Linux user with isolated installation. + +#### Quick reference +| Command | Description | +|---------|-------------| +| `make ansible-gaming` | Full provision (all active profiles) | +| `make ansible-gaming PROFILE=name` | Full provision (single profile) | +| `make ansible-gaming-check` | Dry-run with --check/--diff | +| `make ansible-gaming-configs` | Deploy configs/mods only | +| `make ansible-gaming-archive PROFILE=name` | Archive and purge a profile | + +#### New profile setup + +1. Add profile to `ansible/inventories/group_vars/gaming_servers.yml` +2. Run `make ansible-gaming PROFILE=` to provision user and dependencies +3. Install the game server (see game-specific instructions below) +4. Start the server and verify it works +5. (Optional) Add configs/mods to `ansible/roles/gaming_server/files/profiles//` +6. Deploy configs with `make ansible-gaming-configs PROFILE=` + +#### Game-specific setup + +**Vintage Story / Valheim (LinuxGSM)** + +After provisioning, install the game via LGSM: +```bash +ssh gaming.quietlife.net +sudo -iu +./