--- # ============================================================================ # Road-warrior OpenVPN профиль для телефона (Android). # # Отдельный point-to-point static-key инстанс на ru-vps (порт 9444/tcp, tun2, # сеть 10.80.0.0/30), параллельный тоннелю site-to-site (tun0, homelab-openvpn) # и профилю ноутбука (tun1, openvpn-laptop.yml). Клиент подключается один за # раз — static key не мультиплексируется. # # КЛИЕНТ # Приложение «OpenVPN for Android» (Arne Schwabe, F-Droid/Play). Официальный # OpenVPN Connect static-key конфиги НЕ поддерживает — на Android нужен # именно этот клиент. # # ЧТО ДЕЛАЕТ # Ставит openvpn на ru-vps, генерит static key (один раз), открывает # 9444/tcp в UFW, поднимает systemd-юнит homelab-openvpn-phone, NAT'ит # 10.80.0.0/30 -> LAN через tun0, кладёт готовый профиль в # ansible/generated/ada-phone.ovpn (mode 0600, содержит секрет). # # ЗАПУСК # make openvpn-phone # ============================================================================ - name: Configure OpenVPN phone access through ru-vps hosts: ru-vps gather_facts: true become: true vars: phone_profile_name: ada-phone phone_openvpn_dir: /etc/openvpn/homelab-phone phone_openvpn_config: /etc/openvpn/homelab-phone/server.conf phone_openvpn_key: /etc/openvpn/homelab-phone/static.key phone_openvpn_service: homelab-openvpn-phone phone_openvpn_port: 9444 phone_openvpn_interface: tun2 phone_openvpn_site_interface: tun0 phone_openvpn_network_cidr: 10.80.0.0/30 phone_openvpn_server_ip: 10.80.0.1 phone_openvpn_client_ip: 10.80.0.2 phone_openvpn_remote: 157.22.231.198 phone_openvpn_client_config_local: "{{ playbook_dir }}/../generated/{{ phone_profile_name }}.ovpn" phone_openvpn_routes: "{{ homelab_vpn_client_routes }}" tasks: - name: Install OpenVPN packages ansible.builtin.apt: name: - openvpn - iptables state: present update_cache: true - name: Ensure phone OpenVPN config directory exists ansible.builtin.file: path: "{{ phone_openvpn_dir }}" state: directory owner: root group: root mode: "0700" - name: Generate phone static key if missing ansible.builtin.command: "openvpn --genkey secret {{ phone_openvpn_key }}" args: creates: "{{ phone_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 phone OpenVPN TCP in UFW ansible.builtin.command: "ufw allow {{ phone_openvpn_port }}/tcp" register: ufw_allow_phone_openvpn changed_when: "'Rule added' in ufw_allow_phone_openvpn.stdout or 'Rules updated' in ufw_allow_phone_openvpn.stdout" failed_when: false - name: Render phone OpenVPN server config ansible.builtin.copy: dest: "{{ phone_openvpn_config }}" owner: root group: root mode: "0600" content: | dev {{ phone_openvpn_interface }} ifconfig {{ phone_openvpn_server_ip }} {{ phone_openvpn_client_ip }} secret {{ phone_openvpn_key }} cipher AES-256-CBC auth SHA256 proto tcp-server port {{ phone_openvpn_port }} persist-key persist-tun verb 3 keepalive 10 60 notify: restart phone openvpn - name: Render phone OpenVPN up script ansible.builtin.copy: dest: "{{ phone_openvpn_dir }}/up.sh" owner: root group: root mode: "0700" content: | #!/bin/sh iptables -t nat -C POSTROUTING -s {{ phone_openvpn_network_cidr }} -d {{ homelab_lan_cidr }} -o {{ phone_openvpn_site_interface }} -j MASQUERADE 2>/dev/null || \ iptables -t nat -A POSTROUTING -s {{ phone_openvpn_network_cidr }} -d {{ homelab_lan_cidr }} -o {{ phone_openvpn_site_interface }} -j MASQUERADE notify: restart phone openvpn - name: Render phone OpenVPN down script ansible.builtin.copy: dest: "{{ phone_openvpn_dir }}/down.sh" owner: root group: root mode: "0700" content: | #!/bin/sh iptables -t nat -D POSTROUTING -s {{ phone_openvpn_network_cidr }} -d {{ homelab_lan_cidr }} -o {{ phone_openvpn_site_interface }} -j MASQUERADE 2>/dev/null || true notify: restart phone openvpn - name: Render phone OpenVPN systemd service ansible.builtin.copy: dest: "/etc/systemd/system/{{ phone_openvpn_service }}.service" owner: root group: root mode: "0644" content: | [Unit] Description=HomeLab OpenVPN phone access After=network-online.target homelab-openvpn.service Wants=network-online.target Requires=homelab-openvpn.service [Service] Type=simple ExecStartPre={{ phone_openvpn_dir }}/up.sh ExecStart=/usr/sbin/openvpn --config {{ phone_openvpn_config }} ExecStopPost={{ phone_openvpn_dir }}/down.sh Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target notify: - reload systemd - restart phone openvpn - name: Flush handlers before starting phone OpenVPN ansible.builtin.meta: flush_handlers - name: Enable and start phone OpenVPN ansible.builtin.systemd: name: "{{ phone_openvpn_service }}" state: started enabled: true - name: Read phone static key for client profile ansible.builtin.slurp: src: "{{ phone_openvpn_key }}" register: phone_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 phone OpenVPN profile ansible.builtin.copy: dest: "{{ phone_openvpn_client_config_local }}" mode: "0600" content: | client dev tun proto tcp-client remote {{ phone_openvpn_remote }} {{ phone_openvpn_port }} ifconfig {{ phone_openvpn_client_ip }} {{ phone_openvpn_server_ip }} cipher AES-256-CBC auth SHA256 persist-key persist-tun resolv-retry infinite nobind verb 3 {% for route in phone_openvpn_routes %} route {{ route.network }} {{ route.netmask }} {% endfor %} {{ phone_openvpn_static_key_raw.content | b64decode }} 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 phone openvpn ansible.builtin.systemd: name: "{{ phone_openvpn_service }}" state: restarted