Ansible Setup — PVE02
Overview
Ansible is used to manage the configuration of all hosts on pve02 (and eventually pve01). It handles baseline OS configuration, SSH hardening, user management, security, monitoring, and service deployment across all LXC containers and Proxmox hypervisor nodes.
Ansible runs directly on pve02 from /root/ansible/. The repository is hosted on Codeberg and synced via git pull.
Repository Structure
/root/ansible/
├── ansible.cfg # Ansible configuration
├── requirements.yml # Ansible Galaxy collections
├── .gitignore
├── inventory/
│ ├── hosts.yml # Host definitions grouped by VLAN/zone
│ ├── group_vars/
│ │ ├── all/
│ │ │ ├── vars.yml # Global non-secret variables
│ │ │ └── vault.yml # Ansible Vault encrypted secrets (SSH keys)
│ │ ├── proxmox_nodes.yml # Variables for Proxmox hypervisors
│ │ ├── mgmt.yml # Variables for VLAN 10 (MGMT) hosts
│ │ ├── servers.yml # Variables for VLAN 20 (SERVERS) hosts
│ │ ├── dmz.yml # Variables for VLAN 70 (DMZ) hosts
│ │ ├── lxc.yml # Variables for all LXC containers
│ │ └── vms.yml # Variables for VMs
│ └── host_vars/
│ ├── unifi.yml # Unifi-specific overrides
│ ├── authentik/
│ │ ├── vars.yml # Authentik config + Terraform LXC spec
│ │ └── vault.yml # Authentik secrets (encrypted)
│ ├── pulse/
│ │ ├── vars.yml # Pulse config + Terraform LXC spec
│ │ └── vault.yml # Pulse API tokens (encrypted)
│ ├── hookshot/
│ │ ├── vars.yml # Hookshot config + Terraform LXC spec
│ │ └── vault.yml # Hookshot secrets (encrypted)
│ ├── uptime-kuma/
│ │ └── vars.yml # Terraform LXC spec
│ ├── onedev/
│ │ └── vars.yml # OneDev config + Terraform LXC spec
│ ├── mumble/
│ │ └── vars.yml # Terraform LXC spec
│ ├── synapse/
│ │ └── vars.yml # Terraform LXC spec
│ └── minecraft/
│ └── vars.yml # Terraform LXC spec
├── playbooks/
│ ├── site.yml # Main playbook — full configuration + service deployment
│ ├── bootstrap_pve.yml # Onboard a new Proxmox node
│ ├── bootstrap.yml # Onboard a new LXC/VM host
│ ├── update.yml # Rolling system updates
│ ├── authentik.yml # Deploy Authentik
│ ├── pulse.yml # Deploy Pulse
│ ├── hookshot.yml # Deploy Matrix Hookshot
│ ├── uptime-kuma.yml # Deploy Uptime Kuma
│ └── onedev.yml # Deploy OneDev
├── roles/
│ ├── proxmox_node/ # Proxmox hypervisor configuration
│ ├── common/ # Base OS configuration
│ ├── ssh/ # SSH hardening
│ ├── users/ # User and sudo management
│ ├── packages/ # Package management + auto-updates
│ ├── security/ # fail2ban + service hardening
│ ├── monitoring/ # node_exporter (Prometheus metrics)
│ ├── docker/ # Docker installation
│ ├── podman/ # Podman + quadlet setup
│ ├── authentik/ # Authentik identity provider
│ ├── pulse/ # Pulse Proxmox monitoring dashboard
│ ├── hookshot/ # Matrix Hookshot appservice bridge
│ ├── uptime-kuma/ # Uptime Kuma service monitoring
│ └── onedev/ # OneDev Git server with CI/CD
└── terraform/
└── proxmox/ # Terraform IaC for Proxmox resources
Inventory — Host Groups
Hosts are grouped by VLAN zone, matching the VyOS firewall zone names. The VMID naming scheme encodes VLAN + IP (e.g. VMID 720 = VLAN 70, IP .20).
Groups
| Group | VLAN | Zone | Subnet | Hosts |
|---|---|---|---|---|
proxmox_nodes | 10 | MGMT | 10.69.10.x | pve02, pve01 (pending) |
mgmt | 10 | MGMT | 10.69.10.x | pbs01, unifi |
servers | 20 | SERVERS | 10.69.20.x | podman, copyparty, paperless, authentik, pulse, uptime-kuma, onedev |
dmz | 70 | DMZ | 10.69.70.x | hookshot, mumble, minecraft, synapse |
lxc | — | — | — | all of mgmt + servers + dmz |
managed | — | — | — | proxmox_nodes + lxc |
vyos | 40 | TRUSTED | 10.69.40.1 | gateway (router/DNS — not in managed) |
Note:
haos(VM 101, VLAN 30 / HOMELAB) is not Ansible-managed — Home Assistant OS does not provide SSH/Python access.
Host Inventory
| Host | VMID | IP | VLAN | Notes |
|---|---|---|---|---|
| pve02 | — | 10.69.10.20 | 10 | Proxmox hypervisor, Ansible control node |
| pve01 | — | TBD | 10 | Second Proxmox node, pending bootstrap |
| pbs01 | 200 | 10.69.10.25 | 10 | Proxmox Backup Server |
| unifi | 115 | 10.69.10.15 | 10 | Unifi Network Controller (DHCP) |
| podman | 100 | 10.69.20.10 | 20 | Podman host — Traefik, media stack |
| copyparty | 220 | 10.69.20.20 | 20 | File sharing (Copyparty) |
| paperless | 272 | 10.69.20.72 | 20 | Document management (Paperless-ngx) |
| authentik | 268 | 10.69.20.68 | 20 | Identity provider (Authentik) |
| pulse | 260 | 10.69.20.60 | 20 | Proxmox monitoring dashboard |
| uptime-kuma | 275 | 10.69.20.75 | 20 | Service uptime monitoring |
| onedev | 276 | 10.69.20.76 | 20 | Git server with CI/CD (OneDev) |
| hookshot | 740 | 10.69.70.40 | 70 | Matrix Hookshot appservice bridge |
| mumble | 710 | 10.69.70.10 | 70 | Voice server |
| minecraft | 720 | 10.69.70.20 | 70 | Minecraft game server |
| synapse | 730 | 10.69.70.30 | 70 | Matrix Synapse homeserver |
host_vars and Terraform Integration
host_vars/{host}/vars.yml serves as the single source of truth for both Ansible and Terraform. Hosts with lxc_cores defined in their vars.yml are automatically picked up by Terraform's for_each resource — no separate Terraform resource block needed.
# Example: inventory/host_vars/onedev/vars.yml
# Ansible vars
onedev_version: "latest"
onedev_http_port: 6610
# Terraform LXC config — presence of lxc_cores enables Terraform management
lxc_description: "OneDev — self-hosted Git server with CI/CD"
lxc_tags: ["servers", "dev"]
lxc_cores: 2
lxc_memory: 2048
lxc_swap: 512
lxc_disk: 20
See terraform-setup.md for full details.
Secrets Management
Secrets (SSH authorized keys, passwords, API tokens) are stored in inventory/group_vars/all/vault.yml or inventory/host_vars/{host}/vault.yml, encrypted with Ansible Vault.
The vault password lives in ~/.ansible_vault_pass on pve02 (not committed to git).
# Edit global secrets
ansible-vault edit ~/ansible/inventory/group_vars/all/vault.yml
# Edit host-specific secrets
ansible-vault edit ~/ansible/inventory/host_vars/authentik/vault.yml
Variables in vault.yml are prefixed with vault_ and referenced from vars.yml:
# vars.yml
ssh_authorized_keys: "{{ vault_ssh_authorized_keys }}"
Roles
proxmox_node
Applies to: proxmox_nodes
- Disables Proxmox enterprise repo, enables no-subscription community repo
- Removes web UI subscription nag
- Deploys Ansible SSH key to root
- Verifies
vmbr0is VLAN-aware - Runs
pveupdate
common
Applies to: all managed hosts
- Sets timezone (
Europe/Berlin) - Sets hostname and
/etc/hosts - Updates apt cache (with release info change support for hosts like unifi)
- Installs common packages:
vim,htop,curl,wget,git - Generates
en_US.UTF-8locale - Shell UX (works on Debian + Rocky Linux):
- Custom PS1 via
/etc/profile.d/bash-prompt.sh— showsuser@host HH:MM:SS path$with colors (root user in red, regular user in green). Set viaPROMPT_COMMANDso user~/.bashrccannot override it. - Login banner via
/etc/profile.d/login-info.sh— displays hostname, IP, OS, kernel, uptime, load (red if 1-min load > nproc), memory, disk, and logged-in user count on every interactive login. Guarded by tty + interactive bash so scp/rsync are unaffected. - Default
/etc/motdis cleared so the Debian welcome blurb does not clutter the banner. /usr/local/{bin,sbin}placed onPATHfor RHEL-family hosts via/etc/profile.d/usr-local-path.sh.
- Custom PS1 via
ssh
Applies to: all managed hosts
- Deploys hardened
sshd_config:- Password authentication disabled
- Root login:
prohibit-password - Max auth tries: 3
- X11 forwarding disabled
- GSSAPI/Kerberos disabled
- Deploys SSH authorized keys for root
users
Applies to: all managed hosts
- Creates admin user
markowith sudo group - Deploys SSH authorized keys for
marko - Configures passwordless sudo
packages
Applies to: lxc
- Installs
unattended-upgradesfor automatic security updates
security
Applies to: lxc
- Installs and configures fail2ban (SSH jail, 5 retries, 1h ban)
- Disables unnecessary services:
rpcbind,avahi-daemon
No host firewalls. Firewalling is centralized on the VyOS router (per-VLAN zones). Hosts run no local firewall (
ufw/firewalldnot installed/managed).
monitoring
Applies to: all lxc hosts (when enable_monitoring: true)
- Creates
node_exportersystem user - Downloads and installs node_exporter binary to
/usr/local/bin - Creates and enables systemd service
- Exposes Prometheus metrics on port 9100
podman
Applies to: any host running containerized services
- Installs Podman and supporting packages
- Enables quadlet systemd generator (containers defined as
.containerunit files in/etc/containers/systemd/) - Enables
podman-auto-update.timerfor automatic image updates
uptime-kuma
Applies to: uptime-kuma
- Deploys Uptime Kuma as a Podman quadlet container
- Image:
docker.io/louislam/uptime-kuma:1 - Data:
/srv/uptime-kuma - Port:
3001 - Auto-update via
podman auto-update
onedev
Applies to: onedev
- Deploys OneDev as a Podman quadlet container
- Image:
docker.io/1dev/server:latest - Data:
/srv/onedev→/opt/onedev(container path) - Ports:
6610(HTTP),6611(SSH for git) - Auto-update via
podman auto-update
pulse
Applies to: pulse
- Deploys Pulse Proxmox monitoring dashboard as a Podman quadlet container
authentik
Applies to: authentik
- Deploys Authentik identity provider (Docker Compose via Podman)
hookshot
Applies to: hookshot
- Deploys Matrix Hookshot appservice bridge as a Podman quadlet container
Playbooks
site.yml — Base configuration
ansible-playbook playbooks/site.yml
Applies base configuration only (common, ssh, users, packages, security, monitoring,
logging, neovim) to all managed hosts. It does not deploy services — each service has its
own playbook (playbooks/<service>.yml), giving one source of truth per service. Safe to
re-run (idempotent).
bootstrap_pve.yml — Add a new Proxmox node
Used to onboard pve01 (or future nodes). Run with -k on first run to use password auth:
ansible-playbook playbooks/bootstrap_pve.yml -l pve01 -k
bootstrap.yml — Onboard a new LXC host
ansible-playbook playbooks/bootstrap.yml -l onedev -k
update.yml — Rolling system updates
ansible-playbook playbooks/update.yml
Runs apt safe-upgrade across all hosts in rolling batches (30% at a time).
Service-specific playbooks
ansible-playbook playbooks/authentik.yml
ansible-playbook playbooks/pulse.yml
ansible-playbook playbooks/hookshot.yml
ansible-playbook playbooks/uptime-kuma.yml
ansible-playbook playbooks/onedev.yml
VyOS DNS sync
The vyos_dns role pushes inventory-derived static-host-mappings to the VyOS router so every managed host gets a short DNS name (e.g. ssh podman resolves to 10.69.20.10) without editing VyOS by hand.
Targets: vyos group (gateway @ 10.69.40.1, only reachable from VLAN 40 TRUSTED)
Connection: ansible.netcommon.network_cli + vyos.vyos.vyos (collections in requirements.yml)
Auth: dedicated ansible user on VyOS, key ~/.ssh/id_ed25519_ansible on pve02
What it pushes
For every host in proxmox_nodes + mgmt + servers + dmz:
set system static-host-mapping host-name <inventory_hostname> inet <ansible_host>
If a host_vars file declares traefik_subdomains, those FQDNs additionally point at Traefik:
# inventory/host_vars/authentik/vars.yml
traefik_subdomains:
- auth
→ generates set system static-host-mapping host-name auth.home.helix9.org inet 10.69.20.40
The role is additive — it does NOT purge unknown entries, so manually-curated mappings (the *.home.helix9.org → traefik service URLs, vyos-edge, etc.) survive untouched.
Run it
cd ~/ansible
ansible-galaxy collection install -r requirements.yml # one-time
ansible-playbook playbooks/vyos_dns.yml --check --diff # preview
ansible-playbook playbooks/vyos_dns.yml # apply
Prereqs (one-time, on VyOS itself)
configure
set system login user ansible authentication public-keys ansible@pve02 key '<base64-only>'
set system login user ansible authentication public-keys ansible@pve02 type 'ssh-ed25519'
commit
save
VyOS 1.4+ grants login users sudo + vyattacfg automatically — no level admin needed (the option was removed).
On pve02, python3-paramiko must be installed (apt) for the network_cli connection to work; pylibssh isn't packaged on Debian 13 yet.
Common Operations
Test connectivity
cd ~/ansible && ansible all -m ping
Dry run (check what would change)
ansible-playbook playbooks/site.yml --check --diff
Run against a single host or group
ansible-playbook playbooks/site.yml -l onedev
ansible-playbook playbooks/site.yml -l servers
ansible-playbook playbooks/site.yml -l dmz
Check inventory groups
ansible-inventory --list --yaml
ansible-inventory --graph
Sync from git and apply
cd ~/ansible && git pull && ansible-playbook playbooks/site.yml
Git Workflow
The repo is hosted at ssh://git@codeberg.org/Happiest7/Ansible-home.git.
- Local machine (
/home/marko/code/ansible/) — edit here, push to Codeberg - pve02 (
/root/ansible/) — pull from Codeberg, run playbooks here
pve02 authenticates to Codeberg using the deploy key at ~/.ssh/codeberg_ansible.
# On local machine — after making changes
git add .
git commit -m "describe change"
git push
# On pve02 — apply changes
cd ~/ansible && git pull && ansible-playbook playbooks/site.yml
Firewall
No host-level firewalls. All firewalling is done centrally on the VyOS router via per-VLAN
zone policies (see the network docs). Hosts do not run ufw or firewalld.
Monitoring
node_exporter runs on all managed hosts on port 9100.
# Check metrics on any host
curl http://10.69.20.75:9100/metrics # uptime-kuma
curl http://10.69.70.20:9100/metrics # minecraft
Adding a New Host
- Create the LXC — add
lxc_*keys toinventory/host_vars/<hostname>/vars.ymland run Terraform (seeterraform-setup.md) - Add to inventory — entry already exists if added via Terraform workflow; otherwise add to
inventory/hosts.ymlunder the correct zone group - Bootstrap:
ansible-playbook playbooks/bootstrap.yml -l <hostname> -k - Apply full config:
ansible-playbook playbooks/site.yml -l <hostname> - Deploy service (if applicable):
ansible-playbook playbooks/<service>.yml