Files
homelab-optimized/ansible/playbooks/tailscale_mesh_management.yml
Gitea Mirror Bot e7652c8dab
Some checks failed
Documentation / Build Docusaurus (push) Failing after 5m3s
Documentation / Deploy to GitHub Pages (push) Has been skipped
Sanitized mirror from private repository - 2026-04-20 01:32:01 UTC
2026-04-20 01:32:01 +00:00

256 lines
9.2 KiB
YAML

---
# Tailscale Mesh Management
# Validates mesh connectivity, manages keys, and monitors VPN performance
# Run with: ansible-playbook -i hosts.ini playbooks/tailscale_mesh_management.yml
- name: Tailscale Mesh Management
hosts: all
gather_facts: yes
vars:
tailscale_expected_nodes:
- "homelab"
- "atlantis"
- "calypso"
- "setillo"
- "pi-5"
- "pi-5-kevin"
- "vish-concord-nuc"
- "pve"
- "truenas-scale"
- "homeassistant"
performance_test_targets:
- "100.64.0.1" # Tailscale coordinator
- "atlantis"
- "calypso"
tasks:
- name: Check if Tailscale is installed
command: which tailscale
register: tailscale_installed
failed_when: false
changed_when: false
- name: Get Tailscale status
command: tailscale status --json
register: tailscale_status_raw
when: tailscale_installed.rc == 0
become: yes
- name: Parse Tailscale status
set_fact:
tailscale_status: "{{ tailscale_status_raw.stdout | from_json }}"
when: tailscale_installed.rc == 0 and tailscale_status_raw.stdout != ""
- name: Get Tailscale IP
command: tailscale ip -4
register: tailscale_ip
when: tailscale_installed.rc == 0
become: yes
- name: Display Tailscale node info
debug:
msg: |
Tailscale Status for {{ inventory_hostname }}:
- Installed: {{ 'Yes' if tailscale_installed.rc == 0 else 'No' }}
{% if tailscale_installed.rc == 0 %}
- IP Address: {{ tailscale_ip.stdout }}
- Backend State: {{ tailscale_status.BackendState }}
- Version: {{ tailscale_status.Version }}
- Online: {{ tailscale_status.Self.Online }}
- Exit Node: {{ tailscale_status.Self.ExitNode | default('None') }}
{% endif %}
- name: Get peer information
set_fact:
tailscale_peers: "{{ tailscale_status.Peer | dict2items | map(attribute='value') | list }}"
when: tailscale_installed.rc == 0 and tailscale_status.Peer is defined
- name: Analyze mesh connectivity
set_fact:
online_peers: "{{ tailscale_peers | selectattr('Online', 'equalto', true) | list }}"
offline_peers: "{{ tailscale_peers | selectattr('Online', 'equalto', false) | list }}"
expected_missing: "{{ tailscale_expected_nodes | difference(tailscale_peers | map(attribute='HostName') | list + [tailscale_status.Self.HostName]) }}"
when: tailscale_installed.rc == 0 and tailscale_peers is defined
- name: Display mesh analysis
debug:
msg: |
Tailscale Mesh Analysis:
- Total Peers: {{ tailscale_peers | length if tailscale_peers is defined else 0 }}
- Online Peers: {{ online_peers | length if online_peers is defined else 0 }}
- Offline Peers: {{ offline_peers | length if offline_peers is defined else 0 }}
- Expected Nodes: {{ tailscale_expected_nodes | length }}
- Missing Nodes: {{ expected_missing | length if expected_missing is defined else 0 }}
{% if offline_peers is defined and offline_peers | length > 0 %}
Offline Peers:
{% for peer in offline_peers %}
- {{ peer.HostName }} ({{ peer.TailscaleIPs[0] }})
{% endfor %}
{% endif %}
{% if expected_missing is defined and expected_missing | length > 0 %}
Missing Expected Nodes:
{% for node in expected_missing %}
- {{ node }}
{% endfor %}
{% endif %}
when: tailscale_installed.rc == 0
- name: Test connectivity to key nodes
shell: |
echo "=== Connectivity Tests ==="
{% for target in performance_test_targets %}
echo "Testing {{ target }}..."
if ping -c 3 -W 2 {{ target }} >/dev/null 2>&1; then
latency=$(ping -c 3 {{ target }} | tail -1 | awk -F '/' '{print $5}')
echo "✓ {{ target }}: ${latency}ms avg"
else
echo "✗ {{ target }}: Unreachable"
fi
{% endfor %}
register: connectivity_tests
when: tailscale_installed.rc == 0
- name: Check Tailscale service status
systemd:
name: tailscaled
register: tailscale_service
when: tailscale_installed.rc == 0
become: yes
- name: Get Tailscale logs
shell: journalctl -u tailscaled --since "1 hour ago" --no-pager | tail -20
register: tailscale_logs
when: tailscale_installed.rc == 0
become: yes
- name: Check for Tailscale updates
shell: |
current_version=$(tailscale version | head -1 | awk '{print $1}')
echo "Current version: $current_version"
# Check if update is available (this is a simplified check)
if command -v apt >/dev/null 2>&1; then
apt list --upgradable 2>/dev/null | grep tailscale || echo "No updates available via apt"
elif command -v yum >/dev/null 2>&1; then
yum check-update tailscale 2>/dev/null || echo "No updates available via yum"
else
echo "Package manager not supported for update check"
fi
register: update_check
when: tailscale_installed.rc == 0
become: yes
- name: Generate network performance report
shell: |
echo "=== Network Performance Report ==="
echo "Timestamp: $(date)"
echo "Host: {{ inventory_hostname }}"
echo ""
{% if tailscale_installed.rc == 0 %}
echo "=== Tailscale Interface ==="
ip addr show tailscale0 2>/dev/null || echo "Tailscale interface not found"
echo ""
echo "=== Route Table ==="
ip route | grep -E "(tailscale|100\.)" || echo "No Tailscale routes found"
echo ""
echo "=== DNS Configuration ==="
tailscale status --peers=false --self=false 2>/dev/null | grep -E "(DNS|MagicDNS)" || echo "DNS info not available"
{% else %}
echo "Tailscale not installed on this host"
{% endif %}
register: performance_report
when: tailscale_installed.rc == 0
- name: Check exit node configuration
shell: tailscale status --json | jq -r '.ExitNodeStatus // "No exit node configured"'
register: exit_node_status
when: tailscale_installed.rc == 0
become: yes
failed_when: false
- name: Validate Tailscale ACLs (if admin)
uri:
url: "https://api.tailscale.com/api/v2/tailnet/{{ tailscale_tailnet | default('example.com') }}/acl"
method: GET
headers:
Authorization: "Bearer {{ tailscale_api_key }}"
register: acl_check
when:
- tailscale_api_key is defined
- check_acls | default(false) | bool
delegate_to: localhost
run_once: true
failed_when: false
- name: Generate Tailscale mesh report
copy:
content: |
# Tailscale Mesh Report - {{ inventory_hostname }}
Generated: {{ ansible_date_time.iso8601 }}
## Node Status
- Tailscale Installed: {{ 'Yes' if tailscale_installed.rc == 0 else 'No' }}
{% if tailscale_installed.rc == 0 %}
- IP Address: {{ tailscale_ip.stdout }}
- Backend State: {{ tailscale_status.BackendState }}
- Version: {{ tailscale_status.Version }}
- Online: {{ tailscale_status.Self.Online }}
- Service Status: {{ tailscale_service.status.ActiveState }}
{% endif %}
{% if tailscale_peers is defined %}
## Mesh Connectivity
- Total Peers: {{ tailscale_peers | length }}
- Online Peers: {{ online_peers | length }}
- Offline Peers: {{ offline_peers | length }}
### Online Peers
{% for peer in online_peers %}
- {{ peer.HostName }} ({{ peer.TailscaleIPs[0] }}) - Last Seen: {{ peer.LastSeen }}
{% endfor %}
{% if offline_peers | length > 0 %}
### Offline Peers
{% for peer in offline_peers %}
- {{ peer.HostName }} ({{ peer.TailscaleIPs[0] }}) - Last Seen: {{ peer.LastSeen }}
{% endfor %}
{% endif %}
{% endif %}
## Connectivity Tests
```
{{ connectivity_tests.stdout if connectivity_tests is defined else 'Not performed' }}
```
## Performance Report
```
{{ performance_report.stdout if performance_report is defined else 'Not available' }}
```
## Recent Logs
```
{{ tailscale_logs.stdout if tailscale_logs is defined else 'Not available' }}
```
## Update Status
```
{{ update_check.stdout if update_check is defined else 'Not checked' }}
```
dest: "/tmp/tailscale_mesh_{{ inventory_hostname }}_{{ ansible_date_time.epoch }}.md"
delegate_to: localhost
- name: Display mesh summary
debug:
msg: |
Tailscale Mesh Summary for {{ inventory_hostname }}:
- Status: {{ 'Connected' if tailscale_installed.rc == 0 and tailscale_status.BackendState == 'Running' else 'Disconnected' }}
- IP: {{ tailscale_ip.stdout if tailscale_installed.rc == 0 else 'N/A' }}
- Peers: {{ tailscale_peers | length if tailscale_peers is defined else 0 }}
- Report: /tmp/tailscale_mesh_{{ inventory_hostname }}_{{ ansible_date_time.epoch }}.md