Skip to main content

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

GroupVLANZoneSubnetHosts
proxmox_nodes10MGMT10.69.10.xpve02, pve01 (pending)
mgmt10MGMT10.69.10.xpbs01, unifi
servers20SERVERS10.69.20.xpodman, copyparty, paperless, authentik, pulse, uptime-kuma, onedev
dmz70DMZ10.69.70.xhookshot, mumble, minecraft, synapse
lxcall of mgmt + servers + dmz
managedproxmox_nodes + lxc
vyos40TRUSTED10.69.40.1gateway (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

HostVMIDIPVLANNotes
pve0210.69.10.2010Proxmox hypervisor, Ansible control node
pve01TBD10Second Proxmox node, pending bootstrap
pbs0120010.69.10.2510Proxmox Backup Server
unifi11510.69.10.1510Unifi Network Controller (DHCP)
podman10010.69.20.1020Podman host — Traefik, media stack
copyparty22010.69.20.2020File sharing (Copyparty)
paperless27210.69.20.7220Document management (Paperless-ngx)
authentik26810.69.20.6820Identity provider (Authentik)
pulse26010.69.20.6020Proxmox monitoring dashboard
uptime-kuma27510.69.20.7520Service uptime monitoring
onedev27610.69.20.7620Git server with CI/CD (OneDev)
hookshot74010.69.70.4070Matrix Hookshot appservice bridge
mumble71010.69.70.1070Voice server
minecraft72010.69.70.2070Minecraft game server
synapse73010.69.70.3070Matrix 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 vmbr0 is 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-8 locale
  • Shell UX (works on Debian + Rocky Linux):
    • Custom PS1 via /etc/profile.d/bash-prompt.sh — shows user@host HH:MM:SS path$ with colors (root user in red, regular user in green). Set via PROMPT_COMMAND so user ~/.bashrc cannot 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/motd is cleared so the Debian welcome blurb does not clutter the banner.
    • /usr/local/{bin,sbin} placed on PATH for RHEL-family hosts via /etc/profile.d/usr-local-path.sh.

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 marko with sudo group
  • Deploys SSH authorized keys for marko
  • Configures passwordless sudo

packages

Applies to: lxc

  • Installs unattended-upgrades for 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/firewalld not installed/managed).

monitoring

Applies to: all lxc hosts (when enable_monitoring: true)

  • Creates node_exporter system 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 .container unit files in /etc/containers/systemd/)
  • Enables podman-auto-update.timer for 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

  1. Create the LXC — add lxc_* keys to inventory/host_vars/<hostname>/vars.yml and run Terraform (see terraform-setup.md)
  2. Add to inventory — entry already exists if added via Terraform workflow; otherwise add to inventory/hosts.yml under the correct zone group
  3. Bootstrap: ansible-playbook playbooks/bootstrap.yml -l <hostname> -k
  4. Apply full config: ansible-playbook playbooks/site.yml -l <hostname>
  5. Deploy service (if applicable): ansible-playbook playbooks/<service>.yml