Archive Legacy Setup And Add Ansible Control Plane

This commit is contained in:
Dmitry
2026-07-08 12:47:37 +03:00
parent 841d56380b
commit 4bafa7d09e
203 changed files with 1663 additions and 11 deletions
+4
View File
@@ -2,3 +2,7 @@
*.env *.env
!*.env.example !*.env.example
passwd passwd
.venv/
ansible/.venv/
ansible/collections/
ansible/generated/
+26
View File
@@ -0,0 +1,26 @@
# HomeLab Infrastructure
Active HomeLab infrastructure is managed through Ansible.
## Active Files
- `ansible/` — current control plane.
- `ansible/inventory/hosts.yml` — inventory and host facts.
- `ansible/playbooks/check.yml` — safe connectivity/facts check.
## Archive
Historical pre-Proxmox material is kept under:
```text
archive/2026-07-proxmox-migration/
```
It contains old NixOS configs, Docker Compose service definitions, Gitea workflows, deploy scripts and old Ansible bootstrap playbooks.
## Basic Check
```bash
cd ansible
ansible-playbook playbooks/check.yml
```
+8
View File
@@ -0,0 +1,8 @@
export PROXMOX_HOST=192.168.1.10
export PROXMOX_USER='ansible@pve'
export PROXMOX_TOKEN_ID='homelab'
export PROXMOX_TOKEN_SECRET='replace-me'
export PROXMOX_VALIDATE_CERTS=false
# Override if the downloaded template name differs.
export PVE_LXC_OSTEMPLATE='local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst'
+82
View File
@@ -0,0 +1,82 @@
# HomeLab Ansible
Ansible is the control plane for HomeLab infrastructure changes.
## Contract
- The operator keeps passwords, SSH access and network reachability working.
- The agent changes infrastructure only through inventory, roles and playbooks in this directory.
- Manual server changes are allowed only for break-glass recovery; afterwards they must be captured in Ansible.
- Secrets stay outside git. Use `.env`, vault files or local prompt input, not committed variables.
## Layout
- `inventory/hosts.yml` — canonical host list and host-specific facts.
- `playbooks/` — entry points for tasks.
- `roles/` — reusable configuration units.
## Current Groups
- `ru-vps` — public VPS, JumpHost, qdevice, ZeroTier member.
- `pve_nodes` — Proxmox hosts: `cloud-pc`, `mini-pc`.
- `lxc_infra` — infrastructure LXC containers: `pbs`, `zt-cloud`, `zt-mini`.
- `vpn_openvpn` — OpenVPN transport hosts: `ru-vps`, `wg-mini`.
- `shell_hosts` — hosts with unified bash config: `ru-vps`, `cloud-pc`, `mini-pc`.
- `servers` — all managed hosts.
## First Checks
Install control-node dependencies locally:
```bash
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
ansible-galaxy collection install -r requirements.yml -p collections
```
Run from `ansible/`:
```bash
ansible-playbook playbooks/check.yml
```
For Proxmox API playbooks, create ignored `.env` from `.env.example` and load it:
```bash
cp .env.example .env
. ./.env
.venv/bin/ansible-playbook playbooks/pve-wg-mini.yml
```
Or bootstrap the token from `mini-pc` with sudo:
```bash
.venv/bin/ansible-playbook playbooks/bootstrap-pve-api-token.yml -K
```
OpenVPN transport:
```bash
.venv/bin/ansible-playbook playbooks/openvpn-vps-mini.yml -K
.venv/bin/ansible-playbook playbooks/openvpn-check.yml
```
Bootstrap the Ansible service account on shell hosts:
```bash
.venv/bin/ansible-playbook -i inventory/hosts.yml playbooks/bootstrap-ansible-user.yml -K
```
When a task needs privilege escalation:
```bash
ansible-playbook playbooks/<name>.yml -K
```
## Workflow
1. Describe the desired infrastructure change.
2. Add or update a role/playbook.
3. Run the smallest safe check or playbook.
4. Document non-obvious decisions in the project notes.
+4 -1
View File
@@ -1,6 +1,9 @@
[defaults] [defaults]
inventory = inventory/hosts.yml inventory = inventory/hosts.yml
roles_path = roles
collections_path = collections
retry_files_enabled = false
[ssh_connection] [ssh_connection]
pipelining = true pipelining = true
ssh_args = -o ControlMaster=auto -o ControlPersist=60s ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=accept-new
+82 -10
View File
@@ -1,18 +1,30 @@
all: all:
vars: vars:
ansible_python_interpreter: /usr/bin/python3 ansible_python_interpreter: /usr/bin/python3
ansible_user: ada ansible_user: ansible
ansible_ssh_private_key_file: ~/.ssh/id_ed25519_homelab_ansible
ansible_become: true
homelab_lan_cidr: 192.168.1.0/24
homelab_service_range: 192.168.1.5-192.168.1.40
homelab_zerotier_network_id: 743993800fa5a34c
openvpn_network_cidr: 10.78.0.0/30
openvpn_listen_port: 8443
children: children:
servers: homelab:
hosts: hosts:
ru-vps: ru-vps:
ansible_host: vps ansible_host: vps
openvpn_local_ip: 10.78.0.1
openvpn_peer_ip: 10.78.0.2
openvpn_role: server
pve_nodes:
hosts:
cloud-pc: cloud-pc:
ansible_host: 10.122.62.51 ansible_host: 192.168.1.5
# ansible_host: 192.168.1.5 ansible_ssh_common_args: >-
ansible_ssh_common_args: '-J vps' -o ProxyCommand="ssh -i ~/.ssh/id_ed25519_homelab_ansible -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new -p 3422 -W %h:%p ansible@157.22.231.198"
expected_lan_ip: 192.168.1.5 expected_lan_ip: 192.168.1.5
storage_mounts: storage_mounts:
- src: UUID=ae278333-4116-4225-9b95-595496fadd26 - src: UUID=ae278333-4116-4225-9b95-595496fadd26
@@ -21,11 +33,71 @@ all:
- src: UUID=940a9a4e-10cf-4380-9d22-a71549066561 - src: UUID=940a9a4e-10cf-4380-9d22-a71549066561
path: /mnt/backup path: /mnt/backup
fstype: ext4 fstype: ext4
- src: UUID=89024d30-1ab6-4b2e-b23b-62837455b24a
path: /opt/samba
fstype: ext4
mini-pc: mini-pc:
ansible_host: 10.122.62.95 ansible_host: 192.168.1.10
ansible_ssh_common_args: '-J vps' ansible_ssh_common_args: >-
-o ProxyCommand="ssh -i ~/.ssh/id_ed25519_homelab_ansible -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new -p 3422 -W %h:%p ansible@157.22.231.198"
expected_lan_ip: 192.168.1.10 expected_lan_ip: 192.168.1.10
lxc_infra:
hosts:
pbs:
ansible_host: 192.168.1.20
expected_lan_ip: 192.168.1.20
zt-cloud:
ansible_host: 192.168.1.21
expected_lan_ip: 192.168.1.21
zerotier_ip: 10.122.62.206
zerotier_node_id: c606e6d181
zt-mini:
ansible_host: 192.168.1.22
expected_lan_ip: 192.168.1.22
zerotier_ip: 10.122.62.213
zerotier_node_id: 022ac284e1
wg-mini:
ansible_host: 192.168.1.23
ansible_user: root
ansible_ssh_private_key_file: ~/.ssh/id_ed25519_homelab
expected_lan_ip: 192.168.1.23
openvpn_local_ip: 10.78.0.2
openvpn_peer_ip: 10.78.0.1
openvpn_role: gateway
vaultwarden:
ansible_host: 192.168.1.24
ansible_user: root
ansible_become: false
ansible_ssh_private_key_file: ~/.ssh/id_ed25519_homelab
ansible_ssh_common_args: >-
-o ProxyCommand="ssh -i ~/.ssh/id_ed25519_homelab_ansible -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new -p 3422 -W %h:%p ansible@157.22.231.198"
expected_lan_ip: 192.168.1.24
gitea:
ansible_host: 192.168.1.25
ansible_user: root
ansible_become: false
ansible_ssh_private_key_file: ~/.ssh/id_ed25519_homelab
ansible_ssh_common_args: >-
-o ProxyCommand="ssh -i ~/.ssh/id_ed25519_homelab_ansible -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new -p 3422 -W %h:%p ansible@157.22.231.198"
expected_lan_ip: 192.168.1.25
vpn_openvpn:
hosts:
ru-vps:
wg-mini:
shell_hosts:
hosts:
ru-vps:
cloud-pc:
mini-pc:
servers:
children:
homelab:
pve_nodes:
lxc_infra:
+6
View File
@@ -0,0 +1,6 @@
- name: Configure unified bash profile
hosts: shell_hosts
gather_facts: true
become: true
roles:
- bash_config
@@ -0,0 +1,38 @@
- name: Bootstrap Ansible service account
hosts: shell_hosts
gather_facts: true
become: true
vars:
ansible_service_user: ansible
ansible_service_public_key_file: ~/.ssh/id_ed25519_homelab_ansible.pub
ansible_service_sudoers_file: /etc/sudoers.d/99-ansible-service-user
tasks:
- name: Read local Ansible service public key
ansible.builtin.set_fact:
ansible_service_public_key: "{{ lookup('file', ansible_service_public_key_file) }}"
delegate_to: localhost
become: false
- name: Ensure Ansible service user exists
ansible.builtin.user:
name: "{{ ansible_service_user }}"
shell: /bin/bash
create_home: true
state: present
- name: Install Ansible service SSH key
ansible.posix.authorized_key:
user: "{{ ansible_service_user }}"
key: "{{ ansible_service_public_key }}"
state: present
exclusive: true
- name: Allow passwordless sudo for Ansible service user
ansible.builtin.copy:
dest: "{{ ansible_service_sudoers_file }}"
owner: root
group: root
mode: "0440"
content: "{{ ansible_service_user }} ALL=(ALL) NOPASSWD: ALL\n"
validate: /usr/sbin/visudo -cf %s
@@ -0,0 +1,53 @@
- name: Bootstrap Proxmox API token for Ansible
hosts: mini-pc
gather_facts: false
become: true
vars:
pve_api_user: ansible@pve
pve_api_token_id: homelab
pve_api_role: Administrator
tasks:
- name: Ensure Proxmox API user exists
ansible.builtin.command: "pveum user add {{ pve_api_user }} --comment 'Ansible automation user'"
register: pve_user_add
changed_when: pve_user_add.rc == 0
failed_when:
- pve_user_add.rc != 0
- "'already exists' not in (pve_user_add.stderr | lower)"
- name: Grant Proxmox API user permissions
ansible.builtin.command: "pveum aclmod / -user {{ pve_api_user }} -role {{ pve_api_role }}"
changed_when: true
- name: Remove old Ansible token if present
ansible.builtin.command: "pveum user token remove {{ pve_api_user }} {{ pve_api_token_id }}"
register: pve_token_remove
changed_when: pve_token_remove.rc == 0
failed_when: false
no_log: true
- name: Create Proxmox API token
ansible.builtin.command: "pveum user token add {{ pve_api_user }} {{ pve_api_token_id }} --privsep 0 --comment 'HomeLab Ansible token' --output-format json"
register: pve_token_add
changed_when: true
no_log: true
- name: Store token data
ansible.builtin.set_fact:
pve_token_data: "{{ pve_token_add.stdout | from_json }}"
no_log: true
- name: Write local ansible/.env
ansible.builtin.copy:
dest: "{{ playbook_dir }}/../.env"
mode: "0600"
content: |
export PROXMOX_HOST={{ ansible_host }}
export PROXMOX_USER='{{ pve_api_user }}'
export PROXMOX_TOKEN_ID='{{ pve_api_token_id }}'
export PROXMOX_TOKEN_SECRET='{{ pve_token_data.value }}'
export PROXMOX_VALIDATE_CERTS=false
export PVE_LXC_OSTEMPLATE='local:vztmpl/debian-13-standard_13.0-1_amd64.tar.zst'
delegate_to: localhost
become: false
no_log: true
+15
View File
@@ -0,0 +1,15 @@
- name: Check HomeLab Ansible connectivity
hosts: servers
gather_facts: true
become: false
tasks:
- name: Ping host
ansible.builtin.ping:
- name: Check expected LAN IP when defined
ansible.builtin.assert:
that:
- expected_lan_ip in ansible_facts.all_ipv4_addresses
fail_msg: "{{ inventory_hostname }} must have {{ expected_lan_ip }}; found: {{ ansible_facts.all_ipv4_addresses | join(', ') }}"
success_msg: "{{ inventory_hostname }} has {{ expected_lan_ip }}"
when: expected_lan_ip is defined
+19
View File
@@ -0,0 +1,19 @@
- name: Check OpenVPN tunnel from ru-vps
hosts: ru-vps
gather_facts: false
tasks:
- name: Ping VPN LXC OpenVPN address
ansible.builtin.command: ping -c 3 -W 2 10.78.0.2
changed_when: false
- name: Check mini-pc PVE port through OpenVPN gateway
ansible.builtin.command: nc -vz -w 5 192.168.1.10 8006
changed_when: false
- name: Check cloud-pc PVE port through OpenVPN gateway
ansible.builtin.command: nc -vz -w 5 192.168.1.5 8006
changed_when: false
- name: Check PBS port through OpenVPN gateway
ansible.builtin.command: nc -vz -w 5 192.168.1.20 8007
changed_when: false
+197
View File
@@ -0,0 +1,197 @@
- name: Configure OpenVPN laptop access through ru-vps
hosts: ru-vps
gather_facts: true
become: true
vars:
laptop_profile_name: ada-x1
laptop_openvpn_dir: /etc/openvpn/homelab-laptop
laptop_openvpn_config: /etc/openvpn/homelab-laptop/server.conf
laptop_openvpn_key: /etc/openvpn/homelab-laptop/static.key
laptop_openvpn_service: homelab-openvpn-laptop
laptop_openvpn_port: 9443
laptop_openvpn_interface: tun1
laptop_openvpn_site_interface: tun0
laptop_openvpn_network_cidr: 10.79.0.0/30
laptop_openvpn_server_ip: 10.79.0.1
laptop_openvpn_client_ip: 10.79.0.2
laptop_openvpn_remote: 157.22.231.198
laptop_openvpn_client_config_local: "{{ playbook_dir }}/../generated/{{ laptop_profile_name }}.ovpn"
laptop_openvpn_home_routes:
- network: 192.168.1.5
netmask: 255.255.255.255
- network: 192.168.1.6
netmask: 255.255.255.254
- network: 192.168.1.8
netmask: 255.255.255.248
- network: 192.168.1.16
netmask: 255.255.255.240
- network: 192.168.1.32
netmask: 255.255.255.248
- network: 192.168.1.40
netmask: 255.255.255.255
tasks:
- name: Install OpenVPN packages
ansible.builtin.apt:
name:
- openvpn
- iptables
state: present
update_cache: true
- name: Ensure laptop OpenVPN config directory exists
ansible.builtin.file:
path: "{{ laptop_openvpn_dir }}"
state: directory
owner: root
group: root
mode: "0700"
- name: Generate laptop static key if missing
ansible.builtin.command: "openvpn --genkey secret {{ laptop_openvpn_key }}"
args:
creates: "{{ laptop_openvpn_key }}"
no_log: true
- name: Enable IPv4 forwarding on ru-vps
ansible.posix.sysctl:
name: net.ipv4.ip_forward
value: "1"
state: present
reload: true
- name: Allow laptop OpenVPN TCP in UFW
ansible.builtin.command: "ufw allow {{ laptop_openvpn_port }}/tcp"
register: ufw_allow_laptop_openvpn
changed_when: "'Rule added' in ufw_allow_laptop_openvpn.stdout or 'Rules updated' in ufw_allow_laptop_openvpn.stdout"
failed_when: false
- name: Render laptop OpenVPN server config
ansible.builtin.copy:
dest: "{{ laptop_openvpn_config }}"
owner: root
group: root
mode: "0600"
content: |
dev {{ laptop_openvpn_interface }}
ifconfig {{ laptop_openvpn_server_ip }} {{ laptop_openvpn_client_ip }}
secret {{ laptop_openvpn_key }}
cipher AES-256-CBC
auth SHA256
proto tcp-server
port {{ laptop_openvpn_port }}
persist-key
persist-tun
verb 3
keepalive 10 60
notify: restart laptop openvpn
- name: Render laptop OpenVPN up script
ansible.builtin.copy:
dest: "{{ laptop_openvpn_dir }}/up.sh"
owner: root
group: root
mode: "0700"
content: |
#!/bin/sh
iptables -t nat -C POSTROUTING -s {{ laptop_openvpn_network_cidr }} -d {{ homelab_lan_cidr }} -o {{ laptop_openvpn_site_interface }} -j MASQUERADE 2>/dev/null || \
iptables -t nat -A POSTROUTING -s {{ laptop_openvpn_network_cidr }} -d {{ homelab_lan_cidr }} -o {{ laptop_openvpn_site_interface }} -j MASQUERADE
notify: restart laptop openvpn
- name: Render laptop OpenVPN down script
ansible.builtin.copy:
dest: "{{ laptop_openvpn_dir }}/down.sh"
owner: root
group: root
mode: "0700"
content: |
#!/bin/sh
iptables -t nat -D POSTROUTING -s {{ laptop_openvpn_network_cidr }} -d {{ homelab_lan_cidr }} -o {{ laptop_openvpn_site_interface }} -j MASQUERADE 2>/dev/null || true
notify: restart laptop openvpn
- name: Render laptop OpenVPN systemd service
ansible.builtin.copy:
dest: "/etc/systemd/system/{{ laptop_openvpn_service }}.service"
owner: root
group: root
mode: "0644"
content: |
[Unit]
Description=HomeLab OpenVPN laptop access
After=network-online.target homelab-openvpn.service
Wants=network-online.target
Requires=homelab-openvpn.service
[Service]
Type=simple
ExecStartPre={{ laptop_openvpn_dir }}/up.sh
ExecStart=/usr/sbin/openvpn --config {{ laptop_openvpn_config }}
ExecStopPost={{ laptop_openvpn_dir }}/down.sh
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
notify:
- reload systemd
- restart laptop openvpn
- name: Flush handlers before starting laptop OpenVPN
ansible.builtin.meta: flush_handlers
- name: Enable and start laptop OpenVPN
ansible.builtin.systemd:
name: "{{ laptop_openvpn_service }}"
state: started
enabled: true
- name: Read laptop static key for client profile
ansible.builtin.slurp:
src: "{{ laptop_openvpn_key }}"
register: laptop_openvpn_static_key_raw
no_log: true
- name: Ensure local generated directory exists
ansible.builtin.file:
path: "{{ playbook_dir }}/../generated"
state: directory
mode: "0700"
become: false
delegate_to: localhost
- name: Write local laptop OpenVPN profile
ansible.builtin.copy:
dest: "{{ laptop_openvpn_client_config_local }}"
mode: "0600"
content: |
client
dev tun
proto tcp-client
remote {{ laptop_openvpn_remote }} {{ laptop_openvpn_port }}
ifconfig {{ laptop_openvpn_client_ip }} {{ laptop_openvpn_server_ip }}
cipher AES-256-CBC
auth SHA256
persist-key
persist-tun
resolv-retry infinite
nobind
verb 3
{% for route in laptop_openvpn_home_routes %}
route {{ route.network }} {{ route.netmask }}
{% endfor %}
<secret>
{{ laptop_openvpn_static_key_raw.content | b64decode }}
</secret>
become: false
delegate_to: localhost
no_log: true
handlers:
- name: reload systemd
ansible.builtin.systemd:
daemon_reload: true
- name: restart laptop openvpn
ansible.builtin.systemd:
name: "{{ laptop_openvpn_service }}"
state: restarted
+6
View File
@@ -0,0 +1,6 @@
- name: Configure OpenVPN on ru-vps and VPN LXC
hosts: vpn_openvpn
gather_facts: true
become: true
roles:
- openvpn_gateway
+197
View File
@@ -0,0 +1,197 @@
- name: Create Gitea LXC on cloud-pc
hosts: cloud-pc
gather_facts: false
vars:
gitea_vmid: 141
gitea_hostname: gitea
gitea_ip: 192.168.1.25/24
gitea_gateway: 192.168.1.1
gitea_bridge: vmbr0
gitea_rootfs: data:32
gitea_host_data_dir: /opt/data/gitea
gitea_host_data_uid: 101000
gitea_host_data_gid: 101000
gitea_container_data_dir: /opt/gitea/data
gitea_ostemplate: local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst
gitea_pubkey_file: ~/.ssh/id_ed25519_homelab.pub
handlers:
- name: restart gitea lxc
ansible.builtin.shell: "pct stop {{ gitea_vmid }} || true; pct start {{ gitea_vmid }}"
changed_when: true
tasks:
- name: Check if Gitea LXC exists
ansible.builtin.command: "pct config {{ gitea_vmid }}"
register: gitea_pct_config
changed_when: false
failed_when: false
- name: Install Gitea LXC SSH public key on PVE host
ansible.builtin.copy:
dest: /tmp/gitea-lxc.pub
owner: root
group: root
mode: "0600"
content: "{{ lookup('file', gitea_pubkey_file) }}\n"
when: gitea_pct_config.rc != 0
- name: Create Gitea LXC
ansible.builtin.command: >-
pct create {{ gitea_vmid }} {{ gitea_ostemplate }}
--hostname {{ gitea_hostname }}
--rootfs {{ gitea_rootfs }}
--cores 2
--memory 2048
--swap 1024
--net0 name=eth0,bridge={{ gitea_bridge }},gw={{ gitea_gateway }},ip={{ gitea_ip }},firewall=1
--nameserver 1.1.1.1
--unprivileged 1
--features nesting=1,keyctl=1
--onboot 1
--startup order=50
--cmode shell
--ssh-public-keys /tmp/gitea-lxc.pub
when: gitea_pct_config.rc != 0
- name: Start Gitea LXC
ansible.builtin.command: "pct start {{ gitea_vmid }}"
register: gitea_pct_start
changed_when: gitea_pct_start.rc == 0
failed_when: gitea_pct_start.rc not in [0, 255]
- name: Allow FUSE device in Gitea LXC config
ansible.builtin.lineinfile:
path: "/etc/pve/lxc/{{ gitea_vmid }}.conf"
line: "lxc.cgroup2.devices.allow: c 10:229 rwm"
state: present
notify: restart gitea lxc
- name: Bind mount FUSE device in Gitea LXC config
ansible.builtin.lineinfile:
path: "/etc/pve/lxc/{{ gitea_vmid }}.conf"
line: "lxc.mount.entry: /dev/fuse dev/fuse none bind,create=file"
state: present
notify: restart gitea lxc
- name: Bind mount Gitea data directory in LXC config
ansible.builtin.lineinfile:
path: "/etc/pve/lxc/{{ gitea_vmid }}.conf"
line: "mp0: {{ gitea_host_data_dir }},mp={{ gitea_container_data_dir }}"
state: present
notify: restart gitea lxc
- name: Ensure Gitea host data directory ownership matches unprivileged LXC uid map
ansible.builtin.file:
path: "{{ gitea_host_data_dir }}"
state: directory
owner: "{{ gitea_host_data_uid }}"
group: "{{ gitea_host_data_gid }}"
recurse: true
- name: Apply pending LXC config changes
ansible.builtin.meta: flush_handlers
- name: Wait for Gitea SSH through ru-vps
ansible.builtin.wait_for_connection:
timeout: 120
delegate_to: gitea
vars:
ansible_become: false
- name: Configure Docker and Gitea inside LXC
hosts: gitea
gather_facts: true
vars:
ansible_become: false
gitea_data_dir: /opt/gitea/data
gitea_image: gitea/gitea:latest
gitea_container_name: gitea
gitea_http_port: 3000
gitea_ssh_port: 2222
tasks:
- name: Install Docker packages
ansible.builtin.apt:
name:
- docker.io
- fuse-overlayfs
- sqlite3
- rsync
- ca-certificates
- curl
state: present
update_cache: true
- name: Ensure Docker config directory exists
ansible.builtin.file:
path: /etc/docker
state: directory
owner: root
group: root
mode: "0755"
- name: Configure Docker storage driver for unprivileged LXC
ansible.builtin.copy:
dest: /etc/docker/daemon.json
owner: root
group: root
mode: "0644"
content: |
{
"storage-driver": "fuse-overlayfs"
}
register: docker_daemon_config
- name: Ensure Docker service is enabled and running
ansible.builtin.systemd:
name: docker
state: "{{ 'restarted' if docker_daemon_config.changed else 'started' }}"
enabled: true
- name: Ensure Gitea data directory has container ownership
ansible.builtin.file:
path: "{{ gitea_data_dir }}"
state: directory
owner: "1000"
group: "1000"
mode: "0750"
- name: Install Gitea systemd unit
ansible.builtin.copy:
dest: /etc/systemd/system/gitea.service
owner: root
group: root
mode: "0644"
content: |
[Unit]
Description=Gitea container
After=docker.service
Requires=docker.service
[Service]
Restart=always
RestartSec=10
ExecStartPre=-/usr/bin/docker rm -f {{ gitea_container_name }}
ExecStart=/usr/bin/docker run --rm \
--name {{ gitea_container_name }} \
--pull always \
-p {{ gitea_http_port }}:3000 \
-p {{ gitea_ssh_port }}:22 \
-v {{ gitea_data_dir }}:/data \
-e USER_UID=1000 \
-e USER_GID=1000 \
{{ gitea_image }}
ExecStop=/usr/bin/docker stop {{ gitea_container_name }}
[Install]
WantedBy=multi-user.target
register: gitea_unit
- name: Reload systemd when Gitea unit changes
ansible.builtin.systemd:
daemon_reload: true
when: gitea_unit.changed
- name: Enable and start Gitea
ansible.builtin.systemd:
name: gitea
state: started
enabled: true
+174
View File
@@ -0,0 +1,174 @@
- name: Create Vaultwarden LXC on mini-pc
hosts: mini-pc
gather_facts: false
vars:
vaultwarden_vmid: 140
vaultwarden_hostname: vaultwarden
vaultwarden_ip: 192.168.1.24/24
vaultwarden_gateway: 192.168.1.1
vaultwarden_bridge: vmbr0
vaultwarden_rootfs: local-lvm:16
vaultwarden_ostemplate: local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst
vaultwarden_pubkey_file: ~/.ssh/id_ed25519_homelab.pub
handlers:
- name: restart vaultwarden lxc
ansible.builtin.shell: "pct stop {{ vaultwarden_vmid }} || true; pct start {{ vaultwarden_vmid }}"
changed_when: true
tasks:
- name: Check if Vaultwarden LXC exists
ansible.builtin.command: "pct config {{ vaultwarden_vmid }}"
register: vaultwarden_pct_config
changed_when: false
failed_when: false
- name: Install Vaultwarden LXC SSH public key on PVE host
ansible.builtin.copy:
dest: /tmp/vaultwarden-lxc.pub
owner: root
group: root
mode: "0600"
content: "{{ lookup('file', vaultwarden_pubkey_file) }}\n"
when: vaultwarden_pct_config.rc != 0
- name: Create Vaultwarden LXC
ansible.builtin.command: >-
pct create {{ vaultwarden_vmid }} {{ vaultwarden_ostemplate }}
--hostname {{ vaultwarden_hostname }}
--rootfs {{ vaultwarden_rootfs }}
--cores 2
--memory 1024
--swap 512
--net0 name=eth0,bridge={{ vaultwarden_bridge }},gw={{ vaultwarden_gateway }},ip={{ vaultwarden_ip }},firewall=1
--nameserver 1.1.1.1
--unprivileged 1
--features nesting=1,keyctl=1
--onboot 1
--startup order=40
--cmode shell
--ssh-public-keys /tmp/vaultwarden-lxc.pub
when: vaultwarden_pct_config.rc != 0
- name: Start Vaultwarden LXC
ansible.builtin.command: "pct start {{ vaultwarden_vmid }}"
register: vaultwarden_pct_start
changed_when: vaultwarden_pct_start.rc == 0
failed_when: vaultwarden_pct_start.rc not in [0, 255]
- name: Allow FUSE device in Vaultwarden LXC config
ansible.builtin.lineinfile:
path: "/etc/pve/lxc/{{ vaultwarden_vmid }}.conf"
line: "lxc.cgroup2.devices.allow: c 10:229 rwm"
state: present
notify: restart vaultwarden lxc
- name: Bind mount FUSE device in Vaultwarden LXC config
ansible.builtin.lineinfile:
path: "/etc/pve/lxc/{{ vaultwarden_vmid }}.conf"
line: "lxc.mount.entry: /dev/fuse dev/fuse none bind,create=file"
state: present
notify: restart vaultwarden lxc
- name: Apply pending LXC config changes
ansible.builtin.meta: flush_handlers
- name: Wait for Vaultwarden SSH through ru-vps
ansible.builtin.wait_for_connection:
timeout: 120
delegate_to: vaultwarden
vars:
ansible_become: false
- name: Configure Docker and Vaultwarden inside LXC
hosts: vaultwarden
gather_facts: true
vars:
ansible_become: false
vaultwarden_data_dir: /opt/vaultwarden/data
vaultwarden_image: vaultwarden/server:latest
vaultwarden_container_name: vaultwarden
vaultwarden_http_port: 80
tasks:
- name: Install Docker packages
ansible.builtin.apt:
name:
- docker.io
- fuse-overlayfs
- sqlite3
- ca-certificates
- curl
state: present
update_cache: true
- name: Ensure Docker config directory exists
ansible.builtin.file:
path: /etc/docker
state: directory
owner: root
group: root
mode: "0755"
- name: Configure Docker storage driver for unprivileged LXC
ansible.builtin.copy:
dest: /etc/docker/daemon.json
owner: root
group: root
mode: "0644"
content: |
{
"storage-driver": "fuse-overlayfs"
}
register: docker_daemon_config
- name: Ensure Docker service is enabled and running
ansible.builtin.systemd:
name: docker
state: "{{ 'restarted' if docker_daemon_config.changed else 'started' }}"
enabled: true
- name: Ensure Vaultwarden data directory exists
ansible.builtin.file:
path: "{{ vaultwarden_data_dir }}"
state: directory
owner: root
group: root
mode: "0750"
- name: Install Vaultwarden systemd unit
ansible.builtin.copy:
dest: /etc/systemd/system/vaultwarden.service
owner: root
group: root
mode: "0644"
content: |
[Unit]
Description=Vaultwarden container
After=docker.service
Requires=docker.service
[Service]
Restart=always
RestartSec=10
ExecStartPre=-/usr/bin/docker rm -f {{ vaultwarden_container_name }}
ExecStart=/usr/bin/docker run --rm \
--name {{ vaultwarden_container_name }} \
--pull always \
-p {{ vaultwarden_http_port }}:80 \
-v {{ vaultwarden_data_dir }}:/data \
-e WEBSOCKET_ENABLED=true \
{{ vaultwarden_image }}
ExecStop=/usr/bin/docker stop {{ vaultwarden_container_name }}
[Install]
WantedBy=multi-user.target
register: vaultwarden_unit
- name: Reload systemd when Vaultwarden unit changes
ansible.builtin.systemd:
daemon_reload: true
when: vaultwarden_unit.changed
- name: Enable and start Vaultwarden
ansible.builtin.systemd:
name: vaultwarden
state: started
enabled: true
+38
View File
@@ -0,0 +1,38 @@
- name: Create wg-mini LXC on mini-pc
hosts: localhost
connection: local
gather_facts: false
roles:
- role: pve_lxc
vars:
pve_lxc_vmid: 132
pve_lxc_node: mini-pc
pve_lxc_hostname: wg-mini
pve_lxc_ip: 192.168.1.23/24
pve_lxc_gateway: 192.168.1.1
pve_lxc_storage: local-lvm
pve_lxc_disk: local-lvm:8
pve_lxc_ostemplate: "{{ lookup('env', 'PVE_LXC_OSTEMPLATE') | default('local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst', true) }}"
- name: Allow TUN device in wg-mini LXC config
hosts: mini-pc
gather_facts: false
become: true
handlers:
- name: restart wg-mini lxc
ansible.builtin.shell: pct stop 132 || true; pct start 132
changed_when: true
tasks:
- name: Allow /dev/net/tun device
ansible.builtin.lineinfile:
path: /etc/pve/lxc/132.conf
line: "lxc.cgroup2.devices.allow: c 10:200 rwm"
state: present
notify: restart wg-mini lxc
- name: Bind mount /dev/net/tun
ansible.builtin.lineinfile:
path: /etc/pve/lxc/132.conf
line: "lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file"
state: present
notify: restart wg-mini lxc
+65
View File
@@ -0,0 +1,65 @@
- name: Configure Gitea reverse proxy on ru-vps
hosts: ru-vps
gather_facts: false
vars:
caddy_dir: /opt/services/ru-vps/caddy
caddyfile_path: /opt/services/ru-vps/caddy/Caddyfile
gitea_domain: git.ada-dev.ru
gitea_upstream: 192.168.1.25:3000
tasks:
- name: Ensure Caddy service directory exists
ansible.builtin.file:
path: "{{ caddy_dir }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Configure Gitea Caddy site
ansible.builtin.blockinfile:
path: "{{ caddyfile_path }}"
create: true
owner: root
group: root
mode: "0644"
marker: "# {mark} ANSIBLE MANAGED GITEA SITE"
block: |
{{ gitea_domain }} {
reverse_proxy {{ gitea_upstream }}
}
register: gitea_caddy_site
- name: Remove legacy unmanaged Gitea Caddy site
ansible.builtin.replace:
path: "{{ caddyfile_path }}"
regexp: '(?ms)^git\.ada-dev\.ru \{\n\s*reverse_proxy 10\.122\.62\.51:3002\n\}\n+'
replace: ''
register: gitea_legacy_site
- name: Validate host Caddy config
ansible.builtin.command: caddy validate --config {{ caddyfile_path }}
changed_when: false
- name: Check Caddy container mounted config
ansible.builtin.command: docker exec caddy grep -F 'reverse_proxy {{ gitea_upstream }}' /etc/caddy/Caddyfile
register: gitea_container_caddyfile
changed_when: false
failed_when: false
- name: Restart Caddy when config changed or bind mount is stale
ansible.builtin.command: docker restart caddy
when: >-
gitea_caddy_site.changed or
gitea_legacy_site.changed or
gitea_container_caddyfile.rc != 0
- name: Validate Caddy container config after restart
ansible.builtin.command: docker exec caddy caddy validate --config /etc/caddy/Caddyfile
changed_when: false
- name: Check Gitea upstream from ru-vps
ansible.builtin.command: curl -fsS -o /dev/null -w '%{http_code}' http://{{ gitea_upstream }}/
register: gitea_upstream_http
changed_when: false
failed_when: gitea_upstream_http.stdout != '200'
@@ -0,0 +1,65 @@
- name: Configure Vaultwarden reverse proxy on ru-vps
hosts: ru-vps
gather_facts: false
vars:
caddy_dir: /opt/services/ru-vps/caddy
caddyfile_path: /opt/services/ru-vps/caddy/Caddyfile
vaultwarden_domain: pass.ada-dev.ru
vaultwarden_upstream: 192.168.1.24:80
tasks:
- name: Ensure Caddy service directory exists
ansible.builtin.file:
path: "{{ caddy_dir }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Configure Vaultwarden Caddy site
ansible.builtin.blockinfile:
path: "{{ caddyfile_path }}"
create: true
owner: root
group: root
mode: "0644"
marker: "# {mark} ANSIBLE MANAGED VAULTWARDEN SITE"
block: |
{{ vaultwarden_domain }} {
reverse_proxy {{ vaultwarden_upstream }}
}
register: vaultwarden_caddy_site
- name: Remove legacy unmanaged Vaultwarden Caddy site
ansible.builtin.replace:
path: "{{ caddyfile_path }}"
regexp: '(?ms)^pass\.ada-dev\.ru \{\n\s*reverse_proxy 10\.122\.62\.95:10380\n\}\n+'
replace: ''
register: vaultwarden_legacy_site
- name: Validate host Caddy config
ansible.builtin.command: caddy validate --config {{ caddyfile_path }}
changed_when: false
- name: Check Caddy container mounted config
ansible.builtin.command: docker exec caddy grep -F 'reverse_proxy {{ vaultwarden_upstream }}' /etc/caddy/Caddyfile
register: vaultwarden_container_caddyfile
changed_when: false
failed_when: false
- name: Restart Caddy when config changed or bind mount is stale
ansible.builtin.command: docker restart caddy
when: >-
vaultwarden_caddy_site.changed or
vaultwarden_legacy_site.changed or
vaultwarden_container_caddyfile.rc != 0
- name: Validate Caddy container config after restart
ansible.builtin.command: docker exec caddy caddy validate --config /etc/caddy/Caddyfile
changed_when: false
- name: Check Vaultwarden upstream from ru-vps
ansible.builtin.command: curl -fsS -o /dev/null -w '%{http_code}' http://{{ vaultwarden_upstream }}/
register: vaultwarden_upstream_http
changed_when: false
failed_when: vaultwarden_upstream_http.stdout != '200'
+3
View File
@@ -0,0 +1,3 @@
ansible-core>=2.19
proxmoxer>=2.3
requests>=2.31
+6
View File
@@ -0,0 +1,6 @@
---
collections:
- name: ansible.posix
version: ">=2.0.0"
- name: community.proxmox
version: ">=2.0.0"
@@ -0,0 +1,7 @@
---
bash_config_file: /etc/profile.d/homelab.sh
bash_config_bashrc: /etc/bash.bashrc
bash_config_legacy_user: ada
bash_config_legacy_home: "/home/{{ bash_config_legacy_user }}"
bash_config_legacy_profile_files:
- /etc/profile.d/custom_bash.sh
+63
View File
@@ -0,0 +1,63 @@
- name: Install unified HomeLab bash config
ansible.builtin.template:
src: homelab.sh.j2
dest: "{{ bash_config_file }}"
owner: root
group: root
mode: "0644"
- name: Ensure system bashrc sources managed config
ansible.builtin.blockinfile:
path: "{{ bash_config_bashrc }}"
create: true
owner: root
group: root
mode: "0644"
marker: "# {mark} ANSIBLE MANAGED HOMELAB BASH CONFIG"
block: |
if [ -f /etc/profile.d/homelab.sh ]; then
. /etc/profile.d/homelab.sh
fi
- name: List bash users with local home directories
ansible.builtin.shell: >-
getent passwd | awk -F: '$7 ~ /\/bash$/ && $6 ~ /^\// { print $1 ":" $6 }'
register: bash_config_bash_users_raw
changed_when: false
- name: Ensure bash users source managed system config
ansible.builtin.blockinfile:
path: "{{ item.split(':')[1] }}/.bashrc"
create: true
owner: "{{ item.split(':')[0] }}"
group: "{{ item.split(':')[0] }}"
mode: "0644"
marker: "# {mark} ANSIBLE MANAGED HOMELAB SYSTEM BASH CONFIG"
block: |
if [ -f /etc/profile.d/homelab.sh ]; then
. /etc/profile.d/homelab.sh
fi
loop: "{{ bash_config_bash_users_raw.stdout_lines }}"
- name: Check legacy per-user bashrc
ansible.builtin.stat:
path: "{{ bash_config_legacy_home }}/.bashrc"
register: bash_config_legacy_bashrc
- name: Remove legacy per-user bashrc source block
ansible.builtin.blockinfile:
path: "{{ bash_config_legacy_home }}/.bashrc"
marker: "# {mark} ANSIBLE MANAGED HOMELAB BASH CONFIG"
state: absent
when: bash_config_legacy_bashrc.stat.exists
- name: Remove legacy per-user bash config file
ansible.builtin.file:
path: "{{ bash_config_legacy_home }}/.bashrc.d/homelab.sh"
state: absent
- name: Remove legacy profile scripts that conflict with unified config
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop: "{{ bash_config_legacy_profile_files }}"
@@ -0,0 +1,44 @@
# Managed by Ansible: HomeLab unified bash config.
export EDITOR="${EDITOR:-vim}"
export PAGER="${PAGER:-less}"
export LESS="${LESS:--FRSX}"
export HISTCONTROL="ignoreboth:erasedups"
export HISTSIZE=50000
export HISTFILESIZE=100000
shopt -s histappend 2>/dev/null || true
shopt -s checkwinsize 2>/dev/null || true
case ":$PATH:" in
*":$HOME/.local/bin:"*) ;;
*) export PATH="$HOME/.local/bin:$PATH" ;;
esac
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
alias grep='grep --color=auto'
alias ip='ip --color=auto'
alias df='df -h'
alias du='du -h'
alias free='free -h'
alias ..='cd ..'
alias ...='cd ../..'
alias please='sudo $(history -p !!)'
alias gs='git status --short --branch'
alias gd='git diff'
alias gl='git log --oneline --decorate -10'
if command -v systemctl >/dev/null 2>&1; then
alias sctl='systemctl'
alias jctl='journalctl'
fi
if [ -n "$PS1" ]; then
if [ "$(id -u)" -eq 0 ]; then
PS1='\[\e[31m\]\u@\h\[\e[0m\]:\[\e[34m\]\w\[\e[0m\]# '
else
PS1='\[\e[32m\]\u@\h\[\e[0m\]:\[\e[34m\]\w\[\e[0m\]\$ '
fi
fi
@@ -0,0 +1,22 @@
---
openvpn_interface: tun0
openvpn_listen_port: 8443
openvpn_proto_server: tcp-server
openvpn_proto_client: tcp-client
openvpn_config_dir: /etc/openvpn/homelab
openvpn_config_path: /etc/openvpn/homelab/homelab.conf
openvpn_static_key_path: /etc/openvpn/homelab/static.key
openvpn_service_name: homelab-openvpn
openvpn_home_routes:
- network: 192.168.1.5
netmask: 255.255.255.255
- network: 192.168.1.6
netmask: 255.255.255.254
- network: 192.168.1.8
netmask: 255.255.255.248
- network: 192.168.1.16
netmask: 255.255.255.240
- network: 192.168.1.32
netmask: 255.255.255.248
- network: 192.168.1.40
netmask: 255.255.255.255
@@ -0,0 +1,6 @@
---
- name: restart openvpn
ansible.builtin.systemd:
name: "{{ openvpn_service_name }}"
state: restarted
enabled: true
@@ -0,0 +1,105 @@
---
- name: Install OpenVPN packages
ansible.builtin.apt:
name:
- openvpn
- iptables
- iproute2
- netcat-openbsd
state: present
update_cache: true
- name: Ensure OpenVPN config directory exists
ansible.builtin.file:
path: "{{ openvpn_config_dir }}"
state: directory
owner: root
group: root
mode: "0700"
- name: Generate static key on server if missing
ansible.builtin.command: "openvpn --genkey secret {{ openvpn_static_key_path }}"
args:
creates: "{{ openvpn_static_key_path }}"
when: openvpn_role == 'server'
no_log: true
- name: Read static key from server
ansible.builtin.slurp:
src: "{{ openvpn_static_key_path }}"
register: openvpn_static_key_raw
when: openvpn_role == 'server'
no_log: true
- name: Copy static key to all peers
ansible.builtin.copy:
dest: "{{ openvpn_static_key_path }}"
content: "{{ hostvars['ru-vps'].openvpn_static_key_raw.content | b64decode }}"
owner: root
group: root
mode: "0600"
no_log: true
notify: restart openvpn
- name: Enable IPv4 forwarding on gateway
ansible.posix.sysctl:
name: net.ipv4.ip_forward
value: "1"
state: present
reload: true
when: openvpn_role == 'gateway'
- name: Allow OpenVPN TCP in UFW on server
ansible.builtin.command: "ufw allow {{ openvpn_listen_port }}/tcp"
register: ufw_allow_openvpn
changed_when: "'Rule added' in ufw_allow_openvpn.stdout or 'Rules updated' in ufw_allow_openvpn.stdout"
failed_when: false
when: openvpn_role == 'server'
- name: Render OpenVPN config
ansible.builtin.template:
src: homelab.conf.j2
dest: "{{ openvpn_config_path }}"
owner: root
group: root
mode: "0600"
notify: restart openvpn
- name: Render OpenVPN gateway up script
ansible.builtin.template:
src: up.sh.j2
dest: "{{ openvpn_config_dir }}/up.sh"
owner: root
group: root
mode: "0700"
notify: restart openvpn
when: openvpn_role == 'gateway'
- name: Render OpenVPN gateway down script
ansible.builtin.template:
src: down.sh.j2
dest: "{{ openvpn_config_dir }}/down.sh"
owner: root
group: root
mode: "0700"
notify: restart openvpn
when: openvpn_role == 'gateway'
- name: Render OpenVPN systemd service
ansible.builtin.template:
src: homelab-openvpn.service.j2
dest: "/etc/systemd/system/{{ openvpn_service_name }}.service"
owner: root
group: root
mode: "0644"
notify: restart openvpn
- name: Reload systemd
ansible.builtin.systemd:
daemon_reload: true
- name: Enable and start OpenVPN
ansible.builtin.systemd:
name: "{{ openvpn_service_name }}"
state: started
enabled: true
@@ -0,0 +1,2 @@
#!/bin/sh
iptables -t nat -D POSTROUTING -s {{ openvpn_network_cidr }} -d {{ homelab_lan_cidr }} -o eth0 -j MASQUERADE 2>/dev/null || true
@@ -0,0 +1,13 @@
[Unit]
Description=HomeLab OpenVPN {{ openvpn_role }}
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/sbin/openvpn --config {{ openvpn_config_path }}
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,26 @@
dev {{ openvpn_interface }}
ifconfig {{ openvpn_local_ip }} {{ openvpn_peer_ip }}
secret {{ openvpn_static_key_path }}
cipher AES-256-CBC
auth SHA256
persist-key
persist-tun
verb 3
keepalive 10 60
{% if openvpn_role == 'server' %}
proto {{ openvpn_proto_server }}
port {{ openvpn_listen_port }}
{% for route in openvpn_home_routes %}
route {{ route.network }} {{ route.netmask }}
{% endfor %}
{% endif %}
{% if openvpn_role == 'gateway' %}
proto {{ openvpn_proto_client }}
remote 157.22.231.198 {{ openvpn_listen_port }}
resolv-retry infinite
nobind
route-nopull
script-security 2
up /etc/openvpn/homelab/up.sh
down /etc/openvpn/homelab/down.sh
{% endif %}
@@ -0,0 +1,3 @@
#!/bin/sh
iptables -t nat -C POSTROUTING -s {{ openvpn_network_cidr }} -d {{ homelab_lan_cidr }} -o eth0 -j MASQUERADE 2>/dev/null || \
iptables -t nat -A POSTROUTING -s {{ openvpn_network_cidr }} -d {{ homelab_lan_cidr }} -o eth0 -j MASQUERADE
+20
View File
@@ -0,0 +1,20 @@
---
pve_lxc_vmid: 132
pve_lxc_node: mini-pc
pve_lxc_hostname: wg-mini
pve_lxc_ip: 192.168.1.23/24
pve_lxc_gateway: 192.168.1.1
pve_lxc_bridge: vmbr0
pve_lxc_storage: local-lvm
pve_lxc_disk: local-lvm:8
pve_lxc_cores: 1
pve_lxc_memory: 256
pve_lxc_swap: 128
pve_lxc_onboot: true
pve_lxc_startup: order=30
pve_lxc_unprivileged: true
pve_lxc_features:
- nesting=1
pve_lxc_nameserver: 1.1.1.1
pve_lxc_pubkey_file: ~/.ssh/id_ed25519_homelab.pub
pve_lxc_cmode: shell
+46
View File
@@ -0,0 +1,46 @@
---
- name: Validate Proxmox API environment
ansible.builtin.assert:
that:
- lookup('env', 'PROXMOX_HOST') | length > 0
- lookup('env', 'PROXMOX_USER') | length > 0
- lookup('env', 'PROXMOX_TOKEN_ID') | length > 0
- lookup('env', 'PROXMOX_TOKEN_SECRET') | length > 0
fail_msg: Load ansible/.env with PROXMOX_HOST, PROXMOX_USER, PROXMOX_TOKEN_ID and PROXMOX_TOKEN_SECRET.
- name: Create or update LXC container
community.proxmox.proxmox:
api_host: "{{ lookup('env', 'PROXMOX_HOST') }}"
api_user: "{{ lookup('env', 'PROXMOX_USER') }}"
api_token_id: "{{ lookup('env', 'PROXMOX_TOKEN_ID') }}"
api_token_secret: "{{ lookup('env', 'PROXMOX_TOKEN_SECRET') }}"
validate_certs: "{{ lookup('env', 'PROXMOX_VALIDATE_CERTS') | default('false', true) | bool }}"
vmid: "{{ pve_lxc_vmid }}"
node: "{{ pve_lxc_node }}"
hostname: "{{ pve_lxc_hostname }}"
ostemplate: "{{ pve_lxc_ostemplate }}"
disk: "{{ pve_lxc_disk }}"
cores: "{{ pve_lxc_cores }}"
memory: "{{ pve_lxc_memory }}"
swap: "{{ pve_lxc_swap }}"
nameserver: "{{ pve_lxc_nameserver }}"
cmode: "{{ pve_lxc_cmode }}"
netif:
net0: "name=eth0,gw={{ pve_lxc_gateway }},ip={{ pve_lxc_ip }},bridge={{ pve_lxc_bridge }},firewall=1"
pubkey: "{{ lookup('file', pve_lxc_pubkey_file) }}"
features: "{{ pve_lxc_features }}"
unprivileged: "{{ pve_lxc_unprivileged }}"
onboot: "{{ pve_lxc_onboot }}"
startup: "{{ pve_lxc_startup }}"
update: true
state: present
- name: Start LXC container
community.proxmox.proxmox:
api_host: "{{ lookup('env', 'PROXMOX_HOST') }}"
api_user: "{{ lookup('env', 'PROXMOX_USER') }}"
api_token_id: "{{ lookup('env', 'PROXMOX_TOKEN_ID') }}"
api_token_secret: "{{ lookup('env', 'PROXMOX_TOKEN_SECRET') }}"
validate_certs: "{{ lookup('env', 'PROXMOX_VALIDATE_CERTS') | default('false', true) | bool }}"
vmid: "{{ pve_lxc_vmid }}"
state: started
@@ -0,0 +1,13 @@
# Archive: 2026-07 Proxmox Migration
Historical configuration from the pre-Proxmox HomeLab setup.
Moved here:
- `nixos/` — old NixOS host configs and modules.
- `cloud-pc/`, `mini-pc/`, `ru-vps/` — old Docker Compose service trees.
- `.gitea/` — old GitOps workflows.
- `scripts/` — old deploy scripts.
- `legacy-ansible/` — old bootstrap/base/dirs/restic/rclone playbooks and bootstrap role.
Use these files only as reference when implementing new Ansible roles/playbooks.

Some files were not shown because too many files have changed in this diff Show More