31 Commits

Author SHA1 Message Date
Adyrem c707a7cceb Add Anki route and fix Jellyfin cache disk exhaustion
Ansible Lint / lint (push) Successful in 6s
- Add adyrem.duckdns.org/Anki → claude-code VM:4002 via Traefik
- Add anki.homelab DNS entry to Pi-hole
- Mount Jellyfin transcode cache to /media/transcodes (HDD) instead of
  anonymous Docker volume on root disk; prevents root disk from filling up

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 15:02:21 +02:00
Adyrem bc838dfcb6 Add jellyfin media stack with full service automation
Ansible Lint / lint (push) Successful in 7s
- New jellyfin Ansible role: Docker stack (Jellyfin, qBittorrent, Radarr,
  Sonarr, Prowlarr, FlareSolverr, Jellyseerr) with NVIDIA GPU passthrough
- configure_services.yml automates download clients, root folders, Prowlarr
  indexers/apps, qBittorrent credential pre-seeding, and Jellyseerr setup
- Fix TPB Cardigann season search: apibay.org returns 0 for season-level
  queries (e.g. the.boys.s05); patch strips season/ep from tv-search params
  and locks the definition file read-only to survive Prowlarr refreshes
- Fix Pi-hole update_hosts.py regex: use re.DOTALL + count=1 to handle
  multi-line arrays without overwriting the DHCP section
- Add phone WireGuard peer, devbox Traefik/DNS entries, TLS cert plumbing,
  firewall rule for Traefik IP to Proxmox web UI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 18:31:30 +02:00
Adyrem f4c1651d17 Fix NVIDIA installer task; remove jellyfin-tools reference file
Ansible Lint / lint (push) Successful in 4s
- Switch NVIDIA installer copy to get_url so the LXC downloads it
  directly from NVIDIA's servers rather than expecting the file on the
  Ansible controller
- Remove jellyfin-tools (original compose reference from upstream);
  the Ansible template is now the authoritative compose file

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 13:41:44 +02:00
Adyrem 3bb01ddaaf Add Jellyfin media stack (LXC 106, NVIDIA GTX 1060)
- New Ansible role: jellyfin — Docker + nvidia-container-toolkit setup,
  full *arr stack compose file (Jellyfin, qBittorrent, Radarr, Sonarr,
  Prowlarr, FlareSolverr, Jellyseerr), systemd service for auto-start
- New playbook: jellyfin.yml targeting media_servers group
- Inventory: add media LXC (10.10.1.50) to media_servers group
- Traefik: add internal .homelab routes for all 6 media services
- Pi-hole: add DNS records for all 6 media services → Traefik
- network-interfaces: DNAT port 6881 TCP+UDP → 10.10.1.50 for BitTorrent
- homelab-requirements.md: document full Jellyfin stack requirements
  including NVIDIA GTX 1060 passthrough approach and driver notes
- jellyfin-tools: original reference compose file from upstream

Infrastructure created on Proxmox:
- LXC 106 (media, Debian 12, privileged, nesting=1, 10.10.1.50/vmbr1)
- ZFS dataset hdd/media mounted at /media in LXC
- NVIDIA device passthrough entries in /etc/pve/lxc/106.conf

Note: NVIDIA driver 580.159.03 (.run) installed on Proxmox host — not
managed by apt (Debian only has 550.x which doesn't support kernel 7.x
or GTX 1060 Pascal as of this driver version).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 13:28:37 +02:00
Adyrem 30b2d42fcc Enforce firewall=0 on vmbr2 VMs to prevent NAT breakage
Ansible Lint / lint (push) Successful in 5s
When a VM has firewall=1, Proxmox inserts a fwbr bridge and iptables
POSTROUTING fires at the bridge step (OUT=fwbrNNNi0) rather than at
vmbr0. The MASQUERADE rule (-o vmbr0) never matches, so the VM loses
internet access.

Adds an idempotent task to the proxmox_host role that strips
firewall=1 from VM 105's net0 config whenever the playbook runs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:39:08 +02:00
Adyrem 80a3250cf6 Add /mtg route to Traefik and Pi-hole DNS
Routes adyrem.duckdns.org/mtg → http://10.10.2.10:3000 (claude-code-vm)
with StripPrefix so the app sees requests at /.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 10:24:33 +02:00
Adyrem 1d878a8d45 Scope port 80/443 DNAT to Proxmox public IP only
Without -d 192.168.1.10, the DNAT rule intercepted all TCP port 80/443
traffic arriving on vmbr0 — including LAN or routed traffic destined for
internal VMs like Pi-hole (10.10.1.2), redirecting it to Traefik instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 13:46:48 +02:00
Adyrem b9cb93b691 Remove scheduled_tasks.lock from tracking and gitignore it
Ansible Lint / lint (push) Successful in 4s
Runtime lock file created by the Claude Code harness — should never be committed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 04:55:11 +02:00
Adyrem f2321e1f45 Persist static IPs via Ansible to survive VM reboots
common role: add nmcli task that ensures the static IP, gateway, and DNS
are configured in NetworkManager — idempotent, only applies when
network_gateway is defined. Fixes Gitea going offline after reboot
because the NM connection had reverted to DHCP.

group_vars/infra: add network vars (10.10.1.0/24, gw 10.10.1.1)
group_vars/claude_code_vms: add network vars (10.10.2.0/24, gw 10.10.2.1)
  also fix ProxyJump to use adyrem@:2222 (root login was disabled)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 04:54:01 +02:00
Adyrem 1cf6c81ac5 Add sanoid Ansible role for ZFS snapshots and replication
Replaces manually deployed config files with a fully idempotent role:
installs sanoid, templates sanoid.conf and syncoid service/timer from
variables, enables both systemd timers. Removes static proxmox/sanoid.*
files. README rebuild section updated accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 04:22:05 +02:00
Adyrem 18e05c2f88 Add README with rebuild procedure, VPN setup, and operations guide
Covers: infrastructure overview, SSH aliases, playbook usage, full
bare-metal rebuild sequence, WireGuard client setup (Linux/Windows/macOS),
ZFS snapshot restore, dev VM provisioning, monitoring onboarding,
and secrets management.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 04:16:17 +02:00
Adyrem 7ba749c620 Add Proxmox host role, WireGuard VPN, and public Gitea via Traefik HTTPS
Ansible Lint / lint (push) Successful in 4s
Proxmox host role:
- Admin user setup, SSH hardening (port 2222, no root login)
- claude-code SFTP chroot user
- Unattended upgrades, DuckDNS dynamic DNS updater (vault-encrypted token)
- WireGuard VPN server (10.10.10.0/24) with Fedora client registered
- Proxmox firewall management via pvesh API (idempotent Python script)

Public Gitea exposure:
- Traefik: add HTTPS entrypoint, Let's Encrypt ACME (HTTP-01), StripPrefix
  middleware for /git subpath, HTTP→HTTPS redirect
- Gitea ROOT_URL updated to https://adyrem.duckdns.org/git/
- Pi-hole split DNS: adyrem.duckdns.org → 10.10.1.3 for internal hairpin bypass

SSH config fixes:
- StrictHostKeyChecking no → accept-new across all hosts
- Proxmox jump host: port 2222, user adyrem (root login disabled)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 04:06:56 +02:00
Adyrem e888560175 Add pre-commit hook to detect secrets
Blocks commits containing:
- Private key file headers (RSA/EC/DSA/OPENSSH)
- Plaintext passwords/tokens ≥16 chars
- Gitea/GitHub-style hex tokens
- Files with private key path suffixes (_ed25519, _rsa, etc.)

Allowlisted: Ansible vault variable references ({{ vault_* }}),
encrypted vault blocks ($ANSIBLE_VAULT), and comment lines.

Run scripts/install-hooks.sh after cloning to activate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 20:03:43 +02:00
Adyrem 7ba68de68d CI: trigger re-run to verify vault secret fix
Ansible Lint / lint (push) Successful in 4s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 19:57:39 +02:00
Adyrem d1d38df0e4 Track LXC container configs and fix network-interfaces
- proxmox/lxc/103.conf: Pi-hole LXC (firewall=0 to fix NAT via fwbr)
- proxmox/lxc/104.conf: Traefik LXC
- proxmox/network-interfaces: Proxmox network config with DNAT/MASQUERADE
  rules; fixes tpost-up typo so DNS DNAT persists across reboots

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 19:52:43 +02:00
Adyrem 62e5027d24 Add Proxmox firewall configs and Grafana dashboards
- proxmox/firewall/host.fw: host firewall rules (SSH/Proxmox UI/SPICE
  from LAN, DNS from internal subnets, ICMP)
- proxmox/firewall/cluster.fw: cluster firewall with network aliases
- files/grafana-dashboards/node-exporter-full.json: Node Exporter Full
  (grafana.com/dashboards/1860)
- files/grafana-dashboards/loki-logs.json: Logs/App dashboard for Loki
  (grafana.com/dashboards/13639)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 18:35:36 +02:00
Adyrem 3d596b984b Add Claude Code VM (105) with cloud-init provisioning
Arch Linux cloud image, 4 cores/8GB, dev-net (10.10.2.10), autostart.
Cloud-init handles user/SSH key/network on first boot. Ansible role
installs Claude Code CLI, tmux, SSH keys, SSH config for all managed
VMs via ProxyJump, homelab repo clone.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:41:52 +02:00
Adyrem 36c63d1243 Add archinstall config for Claude Code VM (105, 10.10.2.10, dev-net)
Ansible Lint / lint (push) Failing after 7s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:04:30 +02:00
Adyrem afd240de86 Add Pi-hole and Traefik LXC containers
- LXC 103 (pihole): Debian 12, 10.10.1.2, Pi-hole v6 with local DNS for
  gitea/grafana/traefik/pihole.homelab, Proxmox dnsmasq updated to use
  Pi-hole upstream
- LXC 104 (traefik): Debian 12, 10.10.1.3, Traefik v3 routing
  gitea.homelab → 10.10.1.125:3000, grafana.homelab → 10.10.1.137:3000
- DNAT rules on vmbr0 forwarding :80/:443 to Traefik, persisted in
  /etc/network/interfaces
- Ansible roles: pihole, traefik; playbooks: pihole.yml, traefik.yml
- site.yml excludes Debian containers from Arch-specific common role

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 16:52:20 +02:00
Adyrem c626177daa Enable autostart for Gitea and monitoring VMs
Set onboot=1 on VM 100 (gitea) and VM 101 (monitoring) so they start
automatically on Proxmox host boot.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 16:18:49 +02:00
Adyrem d50451fde4 Add Windows VM config and update remote access docs
VM 102: Windows 11 Home N, 8 cores/16GB, OVMF+TPM2, QXL/SPICE display,
vmbr3 (desktop-net). SPICE used instead of RDP — Home N has no RDP server.
Clean-install snapshot taken and synced to HDD.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 16:14:50 +02:00
Adyrem 81a2a4ba54 Add sanoid/syncoid ZFS snapshot and replication config
- sanoid: daily snapshots (7d) + weekly (4w) for rpool/data (VM disks)
  and rpool/ROOT (Proxmox OS); runs every 15 min via systemd timer
- syncoid: daily replication from rpool/data and pve-root to hdd/backups
  at 02:00; first full sync completed successfully
- All VM disks (gitea, monitoring) now backed up to HDD pool

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 14:45:18 +02:00
Adyrem 069ad6afac Add claude-code SFTP user and clarify Claude Code execution model
- Create claude-code user on Proxmox with chroot + ForceCommand internal-sftp
- Add dedicated SSH keypair (public key committed, private key in keys/)
- Update requirements to document that Claude Code runs in a persistent VM,
  connects to other VMs via SSH, and to the Proxmox host via SFTP only
- Session persistence via tmux allows reconnecting from any WireGuard device

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 14:38:51 +02:00
Adyrem 37b5b12478 Rotate Gitea secret_key and internal_token
Ansible Lint / lint (push) Failing after 4s
Old values were exposed in plaintext in the initial commit history.
New values generated via `gitea generate secret`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 18:16:33 +02:00
Adyrem 60856b5b65 Fix authorized_key FQCN and add ansible.posix to CI collections
ansible.posix.authorized_key replaces the bare module name removed
in ansible-core 2.x.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 18:00:11 +02:00
Adyrem 33f42e8ebd Fix CI: add vault password from secret for ansible-lint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 17:56:46 +02:00
Adyrem b633f8b4ed Fix CI: use python3 -m pip and explicit ansible-galaxy collection install
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 17:48:47 +02:00
Adyrem 6e50504923 Add nodejs and python-pip to gitea_runner dependencies
Node.js is required by JS-based actions (e.g. actions/checkout@v4)
on native host runners. python-pip is needed for ansible-lint install.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 17:39:41 +02:00
Adyrem 39be9fafe7 Add Gitea Actions runner, ansible-lint CI, vault setup, and fix group_vars layout
- Move group_vars into inventory/ so playbooks can find them (was only
  visible to ad-hoc commands via CWD)
- Add Ansible Vault for sensitive variables (gitea/grafana passwords,
  tokens, db credentials) with vault_password_file outside the repo
- Enable Gitea Actions and deploy act_runner with Docker backend on
  the gitea VM
- Add .gitea/workflows/lint.yml to run ansible-lint on every push
- Set up Gitea → GitHub push mirror (sync_on_commit)
- Fix ansible.cfg stdout_callback for ansible-core 2.20 compatibility
- Add python-psycopg2 to gitea role (required for postgresql modules)
- Add docker to gitea_runner role (required by act_runner at startup)
- Exclude password hashes and VM images from git

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 17:34:03 +02:00
Adyrem 72d07f49e3 Add .gitignore to exclude keys and secrets 2026-05-13 16:32:35 +02:00
Adyrem c23274c7ab Initial Ansible automation for homelab infrastructure 2026-05-13 16:17:04 +02:00