Files
infra/ansible/playbooks/openvpn-laptop.yml
T
DmitryandClaude Sonnet 5 4b54e44116
lint / yamllint + ansible-lint + syntax-check (push) Canceled after 0s
feat: road-warrior OpenVPN profile for the phone (Android, static key)
playbooks/openvpn-phone.yml (make openvpn-phone) stands up a separate
point-to-point static-key instance on ru-vps: tcp/9444, tun2, 10.80.0.0/30,
homelab-openvpn-phone unit, NAT 10.80.0.0/30 -> LAN via tun0. The client
profile (with the secret) lands in ansible/generated/ada-phone.ovpn
(gitignored). Android client: "OpenVPN for Android" (Arne Schwabe) — the
official OpenVPN Connect does not support static-key configs.

- homelab_vpn_client_routes in group_vars/all/main.yml: shared surgical
  route list for both road-warrior profiles; not the whole /24, since the
  phone's home network is almost certainly 192.168.1.0/24 too
- openvpn-laptop.yml reuses that list instead of its own literal copy
- both playbooks: local profile write moved from `become: false` to
  `vars: {ansible_connection: local, ansible_become: false}` — the keyword
  did not suppress the inherited ansible_become on delegate_to: localhost

Deployed and verified on ru-vps 2026-09-03: service active, tun2 up, ufw
9444/tcp, NAT rule present, make openvpn-phone idempotent (changed=0 on
rerun), 192.168.1.30:8082 reachable from ru-vps. openvpn-laptop.yml is
still not applied on the live host.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbuZrUoevfBgCpf5DCF4DG
2026-09-03 09:33:18 +03:00

191 lines
6.2 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
- 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"
# Общий список с openvpn-phone.yml — см. group_vars/all/main.yml.
laptop_openvpn_home_routes: "{{ homelab_vpn_client_routes }}"
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"
delegate_to: localhost
vars:
ansible_connection: local
ansible_become: false
- 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>
delegate_to: localhost
vars:
ansible_connection: local
ansible_become: false
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