Archive Legacy Setup And Add Ansible Control Plane
This commit is contained in:
@@ -2,3 +2,7 @@
|
||||
*.env
|
||||
!*.env.example
|
||||
passwd
|
||||
.venv/
|
||||
ansible/.venv/
|
||||
ansible/collections/
|
||||
ansible/generated/
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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'
|
||||
@@ -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
@@ -1,6 +1,9 @@
|
||||
[defaults]
|
||||
inventory = inventory/hosts.yml
|
||||
roles_path = roles
|
||||
collections_path = collections
|
||||
retry_files_enabled = false
|
||||
|
||||
[ssh_connection]
|
||||
pipelining = true
|
||||
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
|
||||
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=accept-new
|
||||
|
||||
+82
-10
@@ -1,18 +1,30 @@
|
||||
all:
|
||||
vars:
|
||||
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:
|
||||
servers:
|
||||
homelab:
|
||||
hosts:
|
||||
ru-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:
|
||||
ansible_host: 10.122.62.51
|
||||
# ansible_host: 192.168.1.5
|
||||
ansible_ssh_common_args: '-J vps'
|
||||
ansible_host: 192.168.1.5
|
||||
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.5
|
||||
storage_mounts:
|
||||
- src: UUID=ae278333-4116-4225-9b95-595496fadd26
|
||||
@@ -21,11 +33,71 @@ all:
|
||||
- src: UUID=940a9a4e-10cf-4380-9d22-a71549066561
|
||||
path: /mnt/backup
|
||||
fstype: ext4
|
||||
- src: UUID=89024d30-1ab6-4b2e-b23b-62837455b24a
|
||||
path: /opt/samba
|
||||
fstype: ext4
|
||||
|
||||
mini-pc:
|
||||
ansible_host: 10.122.62.95
|
||||
ansible_ssh_common_args: '-J vps'
|
||||
ansible_host: 192.168.1.10
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
- name: Configure OpenVPN on ru-vps and VPN LXC
|
||||
hosts: vpn_openvpn
|
||||
gather_facts: true
|
||||
become: true
|
||||
roles:
|
||||
- openvpn_gateway
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -0,0 +1,3 @@
|
||||
ansible-core>=2.19
|
||||
proxmoxer>=2.3
|
||||
requests>=2.31
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user