feat(proxmox): add hosts config

Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
This commit is contained in:
Tuan-Dat Tran
2026-02-28 11:30:58 +01:00
parent bf7c7c9562
commit 5a8c7f0248
10 changed files with 1280 additions and 66 deletions

View File

@@ -0,0 +1,715 @@
# Edge VPS Ansible Role Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Create a modular Ansible role for deploying edge VPS infrastructure components (WireGuard, Traefik, Pangolin, Elastic Agent).
**Architecture:** Modular task-based role following existing patterns in the repository. Each component has its own numbered task file. Configs are templated with secrets from ansible-vault encrypted group_vars.
**Tech Stack:** Ansible, Jinja2 templates, Docker Compose, WireGuard, Traefik, Pangolin, Elastic Fleet Agent
---
### Task 1: Create Role Directory Structure
**Files:**
- Create: `roles/edge_vps/tasks/main.yaml`
- Create: `roles/edge_vps/handlers/main.yaml`
- Create: `roles/edge_vps/defaults/main.yaml`
- Create: `roles/edge_vps/templates/` directory structure
**Step 1: Create directory structure**
Run:
```bash
mkdir -p tasks handlers defaults templates/wireguard templates/traefik templates/pangolin templates/elastic-agent
```
**Step 2: Create defaults/main.yaml**
```yaml
---
edge_vps_config_base: /root/config
edge_vps_wireguard_config_dir: /etc/wireguard
edge_vps_wireguard_interface: wg0
edge_vps_wireguard_address: "10.133.7.1/24"
edge_vps_wireguard_port: 61975
edge_vps_traefik_config_dir: "{{ edge_vps_config_base }}/traefik"
edge_vps_traefik_logs_dir: "{{ edge_vps_traefik_config_dir }}/logs"
edge_vps_pangolin_config_dir: "{{ edge_vps_config_base }}/pangolin"
edge_vps_elastic_config_dir: "{{ edge_vps_config_base }}/elastic-agent"
edge_vps_elastic_state_dir: /var/lib/elastic-agent/elastic-system/elastic-agent/state
```
**Step 3: Create handlers/main.yaml**
```yaml
---
- name: Restart wireguard
ansible.builtin.systemd:
name: "wg-quick@{{ edge_vps_wireguard_interface }}"
state: restarted
listen: restart wireguard
- name: Restart traefik
ansible.builtin.command:
cmd: docker compose restart
chdir: "{{ edge_vps_traefik_config_dir }}"
listen: restart traefik
```
**Step 4: Commit**
```bash
git add defaults/main.yaml handlers/main.yaml
git commit -m "feat(edge_vps): add role structure and handlers"
```
---
### Task 2: Create Directory Setup Task
**Files:**
- Create: `roles/edge_vps/tasks/10_directories.yaml`
**Step 1: Create 10_directories.yaml**
```yaml
---
- name: Create config base directory
ansible.builtin.file:
path: "{{ edge_vps_config_base }}"
state: directory
mode: "0755"
- name: Create Traefik directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: "0755"
loop:
- "{{ edge_vps_traefik_config_dir }}"
- "{{ edge_vps_traefik_logs_dir }}"
- name: Create Pangolin config directory
ansible.builtin.file:
path: "{{ edge_vps_pangolin_config_dir }}"
state: directory
mode: "0755"
- name: Create Elastic Agent directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: "0755"
loop:
- "{{ edge_vps_elastic_config_dir }}"
- "{{ edge_vps_elastic_state_dir }}"
```
**Step 2: Commit**
```bash
git add tasks/10_directories.yaml
git commit -m "feat(edge_vps): add directory setup task"
```
---
### Task 3: Create WireGuard Task and Template
**Files:**
- Create: `roles/edge_vps/tasks/20_wireguard.yaml`
- Create: `roles/edge_vps/templates/wireguard/wg0.conf.j2`
**Step 1: Create templates/wireguard/wg0.conf.j2**
```jinja2
[Interface]
Address = {{ edge_vps_wireguard_address }}
ListenPort = {{ edge_vps_wireguard_port }}
PrivateKey = {{ vault_edge_vps.wireguard.private_key }}
PostUp = sysctl -w net.ipv4.ip_forward=1
PostUp = iptables -A FORWARD -i {{ edge_vps_wireguard_interface }} -j ACCEPT
PostUp = iptables -A FORWARD -o {{ edge_vps_wireguard_interface }} -j ACCEPT
{% for route in edge_vps_wireguard_routes | default([]) %}
PostUp = ip route add {{ route }} via {{ route.gateway }} dev {{ edge_vps_wireguard_interface }}
{% endfor %}
PostDown = iptables -D FORWARD -i {{ edge_vps_wireguard_interface }} -j ACCEPT
PostDown = iptables -D FORWARD -o {{ edge_vps_wireguard_interface }} -j ACCEPT
{% for route in edge_vps_wireguard_routes | default([]) %}
PostDown = ip route del {{ route }} via {{ route.gateway }} dev {{ edge_vps_wireguard_interface }}
{% endfor %}
{% for peer in vault_edge_vps.wireguard.peers %}
[Peer]
# {{ peer.name }}
PublicKey = {{ peer.public_key }}
PresharedKey = {{ peer.preshared_key }}
AllowedIPs = {{ peer.allowed_ips }}
{% endfor %}
```
**Step 2: Create tasks/20_wireguard.yaml**
```yaml
---
- name: Install WireGuard
ansible.builtin.apt:
name: wireguard
state: present
update_cache: true
- name: Deploy WireGuard config
ansible.builtin.template:
src: wireguard/wg0.conf.j2
dest: "{{ edge_vps_wireguard_config_dir }}/{{ edge_vps_wireguard_interface }}.conf"
mode: "0600"
notify: restart wireguard
- name: Enable WireGuard
ansible.builtin.systemd:
name: "wg-quick@{{ edge_vps_wireguard_interface }}"
enabled: true
state: started
```
**Step 3: Commit**
```bash
git add tasks/20_wireguard.yaml templates/wireguard/wg0.conf.j2
git commit -m "feat(edge_vps): add WireGuard setup task and template"
```
---
### Task 4: Create Traefik Task and Template
**Files:**
- Create: `roles/edge_vps/tasks/30_traefik.yaml`
- Create: `roles/edge_vps/templates/traefik/traefik_config.yml.j2`
**Step 1: Create templates/traefik/traefik_config.yml.j2**
```jinja2
api:
insecure: true
dashboard: true
providers:
http:
endpoint: "http://pangolin:3001/api/v1/traefik-config"
pollInterval: "5s"
file:
filename: "/etc/traefik/dynamic_config.yml"
experimental:
plugins:
badger:
moduleName: "github.com/fosrl/badger"
version: "v1.2.1"
log:
level: "INFO"
format: "common"
maxSize: 100
maxBackups: 3
maxAge: 3
compress: true
certificatesResolvers:
letsencrypt:
acme:
dnsChallenge:
provider: "cloudflare"
email: "{{ edge_vps_acme_email }}"
storage: "/letsencrypt/acme.json"
caServer: "https://acme-v02.api.letsencrypt.org/directory"
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
transport:
respondingTimeouts:
readTimeout: "30m"
http:
tls:
certResolver: "letsencrypt"
tcp-6443:
address: ":6443/tcp"
serversTransport:
insecureSkipVerify: true
ping:
entryPoint: "web"
accessLog:
filePath: "/var/log/traefik/access.log"
format: common
```
**Step 2: Create tasks/30_traefik.yaml**
```yaml
---
- name: Deploy Traefik config
ansible.builtin.template:
src: traefik/traefik_config.yml.j2
dest: "{{ edge_vps_traefik_config_dir }}/traefik_config.yml"
mode: "0644"
notify: restart traefik
- name: Deploy Cloudflare credentials for ACME
ansible.builtin.copy:
content: |
CF_DNS_API_TOKEN={{ vault_edge_vps.traefik.cloudflare_api_token }}
dest: "{{ edge_vps_traefik_config_dir }}/cloudflare.env"
mode: "0600"
no_log: true
```
**Step 3: Commit**
```bash
git add tasks/30_traefik.yaml templates/traefik/traefik_config.yml.j2
git commit -m "feat(edge_vps): add Traefik setup task and template"
```
---
### Task 5: Create Pangolin Task and Templates
**Files:**
- Create: `roles/edge_vps/tasks/40_pangolin.yaml`
- Create: `roles/edge_vps/templates/pangolin/config.yml.j2`
- Create: `roles/edge_vps/templates/pangolin/docker-compose.yml.j2`
**Step 1: Create templates/pangolin/config.yml.j2**
```jinja2
gerbil:
start_port: 51820
base_endpoint: "{{ edge_vps_pangolin_base_endpoint }}"
app:
dashboard_url: "{{ edge_vps_pangolin_dashboard_url }}"
log_level: "info"
telemetry:
anonymous_usage: true
domains:
domain1:
base_domain: "{{ edge_vps_pangolin_base_domain }}"
server:
secret: "{{ vault_edge_vps.pangolin.server_secret }}"
cors:
origins: ["{{ edge_vps_pangolin_dashboard_url }}"]
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
allowed_headers: ["X-CSRF-Token", "Content-Type"]
credentials: false
maxmind_db_path: "./config/GeoLite2-Country.mmdb"
flags:
require_email_verification: false
disable_signup_without_invite: true
disable_user_create_org: false
allow_raw_resources: true
```
**Step 2: Create templates/pangolin/docker-compose.yml.j2**
```yaml
services:
pangolin:
image: fosrl/pangolin:latest
container_name: pangolin
restart: unless-stopped
ports:
- "3001:3001"
- "443:443"
- "80:80"
volumes:
- ./config.yml:/app/config/config.yml:ro
- ./letsencrypt:/letsencrypt
depends_on:
- gerbil
gerbil:
image: fosrl/gerbil:latest
container_name: gerbil
restart: unless-stopped
network_mode: host
cap_add:
- NET_ADMIN
- SYS_MODULE
volumes:
- /lib/modules:/lib/modules
```
**Step 3: Create tasks/40_pangolin.yaml**
```yaml
---
- name: Deploy Pangolin config
ansible.builtin.template:
src: pangolin/config.yml.j2
dest: "{{ edge_vps_pangolin_config_dir }}/config.yml"
mode: "0644"
notify: restart pangolin
- name: Deploy Pangolin docker-compose
ansible.builtin.template:
src: pangolin/docker-compose.yml.j2
dest: "{{ edge_vps_pangolin_config_dir }}/docker-compose.yml"
mode: "0644"
- name: Create letsencrypt directory for Pangolin
ansible.builtin.file:
path: "{{ edge_vps_pangolin_config_dir }}/letsencrypt"
state: directory
mode: "0755"
- name: Start Pangolin
community.docker.docker_compose_v2:
project_src: "{{ edge_vps_pangolin_config_dir }}"
state: present
```
**Step 4: Commit**
```bash
git add tasks/40_pangolin.yaml templates/pangolin/
git commit -m "feat(edge_vps): add Pangolin setup task and templates"
```
---
### Task 6: Create Elastic Agent Task and Templates
**Files:**
- Create: `roles/edge_vps/tasks/50_elastic_agent.yaml`
- Create: `roles/edge_vps/templates/elastic-agent/docker-compose.yml.j2`
- Create: `roles/edge_vps/templates/elastic-agent/elastic-agent.yml.j2`
**Step 1: Create templates/elastic-agent/elastic-agent.yml.j2**
```yaml
fleet:
enabled: true
```
**Step 2: Create templates/elastic-agent/docker-compose.yml.j2**
```yaml
services:
elastic-agent:
image: docker.elastic.co/elastic-agent/elastic-agent:{{ edge_vps_elastic_version }}
container_name: elastic-agent
restart: always
network_mode: host
dns:
- {{ edge_vps_elastic_dns_server }}
dns_search:
- elastic-system.svc.cluster.local
- svc.cluster.local
- cluster.local
user: "0:0"
privileged: true
entrypoint: ["/usr/bin/env", "bash", "-c"]
command:
- |
set -e
if [[ -f /mnt/elastic-internal/elasticsearch-association/elastic-system/elasticsearch/certs/ca.crt ]]; then
if [[ -f /usr/bin/update-ca-trust ]]; then
cp /mnt/elastic-internal/elasticsearch-association/elastic-system/elasticsearch/certs/ca.crt /etc/pki/ca-trust/source/anchors/
/usr/bin/update-ca-trust
elif [[ -f /usr/sbin/update-ca-certificates ]]; then
cp /mnt/elastic-internal/elasticsearch-association/elastic-system/elasticsearch/certs/ca.crt /usr/local/share/ca-certificates/
/usr/sbin/update-ca-certificates
fi
fi
exec /usr/bin/tini -- /usr/local/bin/docker-entrypoint -e -c /etc/agent/elastic-agent.yml
environment:
- FLEET_CA=/mnt/elastic-internal/fleetserver-association/elastic-system/fleet-server/certs/ca.crt
- FLEET_ENROLL=true
- FLEET_ENROLLMENT_TOKEN={{ vault_edge_vps.elastic.fleet_enrollment_token }}
- FLEET_URL={{ edge_vps_elastic_fleet_url }}
- STATE_PATH=/usr/share/elastic-agent/state
- CONFIG_PATH=/usr/share/elastic-agent/state
- NODE_NAME={{ inventory_hostname }}
volumes:
- {{ edge_vps_elastic_state_dir }}:/usr/share/elastic-agent/state
- ./elastic-agent.yml:/etc/agent/elastic-agent.yml:ro
- ./elasticsearch-ca.crt:/mnt/elastic-internal/elasticsearch-association/elastic-system/elasticsearch/certs/ca.crt:ro
- ./fleet-ca.crt:/mnt/elastic-internal/fleetserver-association/elastic-system/fleet-server/certs/ca.crt:ro
- {{ edge_vps_traefik_logs_dir }}:/var/log/traefik:ro
```
**Step 3: Create tasks/50_elastic_agent.yaml**
```yaml
---
- name: Deploy Elastic Agent config
ansible.builtin.template:
src: elastic-agent/elastic-agent.yml.j2
dest: "{{ edge_vps_elastic_config_dir }}/elastic-agent.yml"
mode: "0644"
- name: Deploy Elastic Agent docker-compose
ansible.builtin.template:
src: elastic-agent/docker-compose.yml.j2
dest: "{{ edge_vps_elastic_config_dir }}/docker-compose.yml"
mode: "0644"
- name: Deploy Elasticsearch CA certificate
ansible.builtin.copy:
src: elastic-agent/elasticsearch-ca.crt
dest: "{{ edge_vps_elastic_config_dir }}/elasticsearch-ca.crt"
mode: "0644"
- name: Deploy Fleet CA certificate
ansible.builtin.copy:
src: elastic-agent/fleet-ca.crt
dest: "{{ edge_vps_elastic_config_dir }}/fleet-ca.crt"
mode: "0644"
- name: Start Elastic Agent
community.docker.docker_compose_v2:
project_src: "{{ edge_vps_elastic_config_dir }}"
state: present
```
**Step 4: Commit**
```bash
git add tasks/50_elastic_agent.yaml templates/elastic-agent/
git commit -m "feat(edge_vps): add Elastic Agent setup task and templates"
```
---
### Task 7: Create Main Task Orchestrator
**Files:**
- Create: `roles/edge_vps/tasks/main.yaml`
**Step 1: Create tasks/main.yaml**
```yaml
---
- name: Setup directories
ansible.builtin.include_tasks: 10_directories.yaml
- name: Setup WireGuard
ansible.builtin.include_tasks: 20_wireguard.yaml
- name: Setup Traefik
ansible.builtin.include_tasks: 30_traefik.yaml
- name: Setup Pangolin
ansible.builtin.include_tasks: 40_pangolin.yaml
- name: Setup Elastic Agent
ansible.builtin.include_tasks: 50_elastic_agent.yaml
```
**Step 2: Commit**
```bash
git add tasks/main.yaml
git commit -m "feat(edge_vps): add main task orchestrator"
```
---
### Task 8: Create Inventory Variables
**Files:**
- Create: `vars/group_vars/vps/vars.yaml`
- Create: `vars/group_vars/vps/secrets.yaml`
**Step 1: Create vars/group_vars/vps/vars.yaml**
```yaml
edge_vps_wireguard_address: "10.133.7.1/24"
edge_vps_wireguard_port: 61975
edge_vps_wireguard_routes:
- network: "10.43.0.0/16"
gateway: "10.133.7.4"
edge_vps_pangolin_dashboard_url: "https://pangolin.seyshiro.de"
edge_vps_pangolin_base_endpoint: "pangolin.seyshiro.de"
edge_vps_pangolin_base_domain: "seyshiro.de"
edge_vps_acme_email: "me+acme@tudattr.dev"
edge_vps_elastic_version: "9.2.2"
edge_vps_elastic_dns_server: "10.43.0.10"
edge_vps_elastic_fleet_url: "https://fleet-server-agent-http.elastic-system.svc:8220"
```
**Step 2: Create vars/group_vars/vps/secrets.yaml (template)**
```yaml
vault_edge_vps:
wireguard:
private_key: "YOUR_WIREGUARD_PRIVATE_KEY"
peers:
- name: lilcrow
public_key: "PEER_PUBLIC_KEY"
preshared_key: "PEER_PRESHARED_KEY"
allowed_ips: "10.133.7.2/32"
- name: homelab
public_key: "PEER_PUBLIC_KEY"
preshared_key: "PEER_PRESHARED_KEY"
allowed_ips: "10.133.7.3/32"
- name: k3s
public_key: "PEER_PUBLIC_KEY"
preshared_key: "PEER_PRESHARED_KEY"
allowed_ips: "10.133.7.4/32, 10.43.0.0/16"
pangolin:
server_secret: "YOUR_PANGOLIN_SERVER_SECRET"
traefik:
cloudflare_api_token: "YOUR_CLOUDFLARE_API_TOKEN"
elastic:
fleet_enrollment_token: "YOUR_FLEET_ENROLLMENT_TOKEN"
```
**Step 3: Encrypt secrets file**
Run:
```bash
ansible-vault encrypt vars/group_vars/vps/secrets.yaml
```
**Step 4: Commit**
```bash
git add vars/group_vars/vps/
git commit -m "feat(edge_vps): add inventory variables for VPS group"
```
---
### Task 9: Update README
**Files:**
- Modify: `roles/edge_vps/README.md`
**Step 1: Update README.md**
```markdown
# Edge VPS
Configures edge VPS instances with WireGuard VPN, Traefik reverse proxy, Pangolin, and Elastic Fleet Agent.
## Requirements
- Docker and Docker Compose installed
- Ansible community.docker collection
## Role Variables
### WireGuard
| Variable | Default | Description |
|----------|---------|-------------|
| `edge_vps_wireguard_address` | `10.133.7.1/24` | WireGuard interface address |
| `edge_vps_wireguard_port` | `61975` | WireGuard listen port |
| `edge_vps_wireguard_interface` | `wg0` | WireGuard interface name |
| `edge_vps_wireguard_routes` | `[]` | List of routes to add (network, gateway) |
### Traefik
| Variable | Default | Description |
|----------|---------|-------------|
| `edge_vps_traefik_config_dir` | `/root/config/traefik` | Traefik config directory |
| `edge_vps_acme_email` | - | Email for Let's Encrypt |
### Pangolin
| Variable | Default | Description |
|----------|---------|-------------|
| `edge_vps_pangolin_dashboard_url` | - | Pangolin dashboard URL |
| `edge_vps_pangolin_base_endpoint` | - | Pangolin base endpoint |
| `edge_vps_pangolin_base_domain` | - | Base domain for Pangolin |
### Elastic Agent
| Variable | Default | Description |
|----------|---------|-------------|
| `edge_vps_elastic_version` | `9.2.2` | Elastic Agent version |
| `edge_vps_elastic_fleet_url` | - | Fleet server URL |
| `edge_vps_elastic_dns_server` | `10.43.0.10` | DNS server for agent |
## Secrets
Store secrets in `vars/group_vars/vps/secrets.yaml` (ansible-vault encrypted):
```yaml
vault_edge_vps:
wireguard:
private_key: "..."
peers: [...]
pangolin:
server_secret: "..."
traefik:
cloudflare_api_token: "..."
elastic:
fleet_enrollment_token: "..."
```
## Dependencies
None.
## Example Playbook
```yaml
- hosts: vps
roles:
- role: edge_vps
```
## License
MIT
```
**Step 2: Commit**
```bash
git add README.md
git commit -m "docs(edge_vps): update README with role documentation"
```
---
### Task 10: Move Certificate Files
**Files:**
- Move: `files/agent/agent/elasticsearch-ca.crt``files/elastic-agent/`
- Move: `files/agent/agent/fleet-ca.crt``files/elastic-agent/`
**Step 1: Move certificate files**
Run:
```bash
mkdir -p files/elastic-agent
mv files/agent/agent/elasticsearch-ca.crt files/elastic-agent/
mv files/agent/agent/fleet-ca.crt files/elastic-agent/
rm -rf files/agent
```
**Step 2: Commit**
```bash
git add files/
git commit -m "refactor(edge_vps): reorganize certificate files"
```

View File

@@ -0,0 +1,15 @@
---
- name: Configure /etc/hosts with Proxmox cluster nodes
ansible.builtin.blockinfile:
path: /etc/hosts
block: |
# Proxmox Cluster Nodes
192.168.20.12 aya01.seyshiro.de aya01
192.168.20.14 lulu.seyshiro.de lulu
192.168.20.28 inko01.seyshiro.de inko01
192.168.20.10 naruto01.seyshiro.de naruto01
192.168.20.9 mii01.seyshiro.de mii01
marker: "# {mark} ANSIBLE MANAGED BLOCK - PROXMOX CLUSTER NODES"
create: true
mode: "644"
when: is_proxmox_node | bool

View File

@@ -6,5 +6,8 @@
state: present
loop: "{{ proxmox_node_dependencies }}"
- name: Configure hosts file for cluster nodes
ansible.builtin.include_tasks: 04_configure_hosts.yaml
- name: Ensure Harware Acceleration on node
ansible.builtin.include_tasks: 06_hardware_acceleration.yaml

View File

@@ -10,6 +10,7 @@
dest: "{{ proxmox_dirs.isos }}/{{ distro.name }}"
mode: "0644"
when: not image_stat.stat.exists
register: download_result
- name: Set raw image file name fact
ansible.builtin.set_fact:
@@ -24,5 +25,5 @@
ansible.builtin.command:
cmd: "qemu-img convert -O raw {{ proxmox_dirs.isos }}/{{ distro.name }} {{ proxmox_dirs.isos }}/{{ raw_image_name }}"
when:
- download_result is changed or not raw_image_stat.stat.exists
- (download_result is defined and download_result is changed) or not raw_image_stat.stat.exists
- image_stat.stat.exists