Three files existed only as second copies of things group_vars/all already
auto-loads, and 34 playbooks named them in vars_files: - which outranks
group_vars, so the copies won. The day someone edited one and not the other,
those plays would silently keep the stale value. infra_vars.yml was already
drifting: group_vars/all/main.yml had grown age_backup_recipient and
backup_pull_public_key that it lacked.
infra_vars.yml - a strict subset of group_vars/all/main.yml
infra_secrets.yml - decrypts byte-identical to group_vars/all/vault.yml
infra_secrets.yml.example - documented Uptime Kuma credentials as the reason
the file exists, which stopped being true
Deleted, along with 62 vars_files entries across 34 playbooks (12 of which
named ../../group_vars/all/main.yml directly - same defect, a vars_files entry
duplicating an auto-loaded file at higher precedence than the file itself).
Checked before touching anything: infra_secrets.yml was listed LAST in 10 plays,
after services_config.yml, so removal would flip precedence if the two shared a
key. They share none, and neither does services_config.yml with
group_vars/all/main.yml, so the removal is provably inert.
services_config.yml was the last one standing. It held four unrelated things:
caddy_sites_dir - an identical copy of roles/caddy_site/defaults/.
Deleted; the role default is now the only one.
*.tailscale_hostname (x3) - a THIRD copy of each box's identity, which
inventory.ini already holds as ansible_host.
Deleted. Edge plays now read
hostvars['<host>'].ansible_host - verified an
edge play resolves that with nothing loaded and
the other host in no play. Three copies of one
name is how bitcoin_rpc_host ended up labelled
"knots_box" while pointing at fulcrum-box.
subdomains, ntfy topic, - genuinely global: their readers span managed,
headscale namespace monitoring, vpn_control and edge, so no single
group covers them. Moved to group_vars/all/main.yml
where they auto-load. The ntfy_topic and
headscale_namespace indirection through
service_settings collapses to the global name.
the four cross-host ports - the only entries with a real justification.
Left in place; they move in the next commit.
Also dead, all Uptime Kuma residue or duplication:
phoenixd_monitor_name, forgejo_runner healthcheck_timeout_seconds/retries,
fulcrum_tailscale_hostname, and bitcoin_knots_version - the last being a
v-prefixed copy of bitcoin_knots_version_short that nothing read, two
hand-maintained copies of one version string.
Corrected a false comment: services_config.yml claimed the uptime_kuma subdomain
"no longer resolves to anything". It resolves to 164.92.239.72 and answers HTTP
302, and 11 playbooks still template it. Same wrong premise as PLAN_3.
Verification: all 37 playbooks' --list-tasks output is byte-identical before and
after. A probe resolving all 22 values services_config.yml used to supply returns
21 identical and one intended deletion (caddy_sites_dir, now role-only - confirmed
the role still resolves it: "Ensure Caddy sites-enabled directory exists" comes
back ok against the real path). memos check-diff identical before and after.
Syntax passes on every playbook.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
148 lines
5.3 KiB
YAML
148 lines
5.3 KiB
YAML
- name: Join machine to headscale mesh network
|
|
hosts: managed
|
|
become: yes
|
|
vars_files:
|
|
- ../services_config.yml
|
|
vars:
|
|
headscale_host_name: "spacey"
|
|
headscale_subdomain: "{{ subdomains.headscale }}"
|
|
headscale_domain: "https://{{ headscale_subdomain }}.{{ root_domain }}"
|
|
|
|
tasks:
|
|
- name: Set facts for headscale server connection
|
|
set_fact:
|
|
headscale_host: "{{ hostvars.get(headscale_host_name, {}).get('ansible_host', headscale_host_name) }}"
|
|
headscale_user: "{{ hostvars.get(headscale_host_name, {}).get('ansible_user', 'counterweight') }}"
|
|
headscale_key: "{{ hostvars.get(headscale_host_name, {}).get('ansible_ssh_private_key_file', '') }}"
|
|
headscale_port: "{{ hostvars.get(headscale_host_name, {}).get('ansible_port', 22) }}"
|
|
|
|
- name: Get user ID for namespace from headscale server via lapy
|
|
delegate_to: "{{ groups['lapy'][0] }}"
|
|
become: no
|
|
vars:
|
|
ssh_args: "{{ ('-i ' + headscale_key + ' ' if headscale_key else '') + '-p ' + headscale_port|string }}"
|
|
shell: >
|
|
ssh {{ ssh_args }}
|
|
{{ headscale_user }}@{{ headscale_host }}
|
|
"sudo headscale users list -o json"
|
|
register: users_list_result
|
|
changed_when: false
|
|
failed_when: users_list_result.rc != 0
|
|
|
|
- name: Extract user ID from users list
|
|
set_fact:
|
|
headscale_user_id: "{{ (users_list_result.stdout | from_json) | selectattr('name', 'equalto', headscale_namespace) | map(attribute='id') | first }}"
|
|
failed_when: headscale_user_id is not defined or headscale_user_id == ''
|
|
|
|
- name: Generate pre-auth key from headscale server via lapy
|
|
delegate_to: "{{ groups['lapy'][0] }}"
|
|
become: no
|
|
vars:
|
|
ssh_args: "{{ ('-i ' + headscale_key + ' ' if headscale_key else '') + '-p ' + headscale_port|string }}"
|
|
shell: >
|
|
ssh {{ ssh_args }}
|
|
{{ headscale_user }}@{{ headscale_host }}
|
|
"sudo headscale preauthkeys create --user {{ headscale_user_id }} --expiration 10m --output json"
|
|
register: preauth_key_result
|
|
changed_when: true
|
|
failed_when: preauth_key_result.rc != 0
|
|
|
|
- name: Extract auth key from preauth result
|
|
set_fact:
|
|
auth_key: "{{ (preauth_key_result.stdout | from_json).key }}"
|
|
failed_when: auth_key is not defined or auth_key == ''
|
|
|
|
- name: Install required packages for Tailscale
|
|
apt:
|
|
name:
|
|
- curl
|
|
- ca-certificates
|
|
- gnupg
|
|
state: present
|
|
update_cache: yes
|
|
|
|
- name: Create directory for GPG keyrings
|
|
file:
|
|
path: /etc/apt/keyrings
|
|
state: directory
|
|
mode: '0755'
|
|
|
|
- name: Download Tailscale GPG key
|
|
get_url:
|
|
url: https://pkgs.tailscale.com/stable/debian/bookworm.gpg
|
|
dest: /etc/apt/keyrings/tailscale.gpg
|
|
mode: '0644'
|
|
|
|
- name: Add Tailscale repository
|
|
apt_repository:
|
|
repo: "deb [signed-by=/etc/apt/keyrings/tailscale.gpg] https://pkgs.tailscale.com/stable/debian {{ ansible_distribution_release }} main"
|
|
state: present
|
|
update_cache: yes
|
|
|
|
- name: Install Tailscale
|
|
apt:
|
|
name: tailscale
|
|
state: present
|
|
update_cache: yes
|
|
|
|
- name: Enable and start Tailscale service
|
|
systemd:
|
|
name: tailscaled
|
|
enabled: yes
|
|
state: started
|
|
|
|
- name: Configure Tailscale to use headscale server
|
|
command: >
|
|
tailscale up
|
|
--login-server {{ headscale_domain }}
|
|
--authkey {{ auth_key }}
|
|
--accept-dns=true
|
|
--hostname={{ ansible_hostname }}
|
|
--reset
|
|
register: tailscale_up_result
|
|
changed_when: "'already authenticated' not in tailscale_up_result.stdout"
|
|
failed_when: tailscale_up_result.rc != 0 and 'already authenticated' not in tailscale_up_result.stdout
|
|
|
|
- name: Wait for Tailscale to be fully connected
|
|
pause:
|
|
seconds: 2
|
|
|
|
- name: Get node ID from headscale server
|
|
delegate_to: "{{ groups['lapy'][0] }}"
|
|
become: no
|
|
vars:
|
|
ssh_args: "{{ ('-i ' + headscale_key + ' ' if headscale_key else '') + '-p ' + headscale_port|string }}"
|
|
shell: >
|
|
ssh {{ ssh_args }}
|
|
{{ headscale_user }}@{{ headscale_host }}
|
|
"sudo headscale nodes list -o json"
|
|
register: nodes_list_result
|
|
changed_when: false
|
|
failed_when: nodes_list_result.rc != 0
|
|
|
|
- name: Extract node ID for this host
|
|
set_fact:
|
|
headscale_node_id: "{{ (nodes_list_result.stdout | from_json) | selectattr('given_name', 'equalto', ansible_hostname) | map(attribute='id') | first }}"
|
|
failed_when: headscale_node_id is not defined or headscale_node_id == ''
|
|
|
|
- name: Tag node with its hostname
|
|
delegate_to: "{{ groups['lapy'][0] }}"
|
|
become: no
|
|
vars:
|
|
ssh_args: "{{ ('-i ' + headscale_key + ' ' if headscale_key else '') + '-p ' + headscale_port|string }}"
|
|
shell: >
|
|
ssh {{ ssh_args }}
|
|
{{ headscale_user }}@{{ headscale_host }}
|
|
"sudo headscale nodes tag --tags tag:{{ ansible_hostname }} -i {{ headscale_node_id }}"
|
|
register: tag_result
|
|
changed_when: true
|
|
failed_when: tag_result.rc != 0
|
|
|
|
- name: Display Tailscale status
|
|
command: tailscale status
|
|
register: tailscale_status
|
|
changed_when: false
|
|
|
|
- name: Show Tailscale connection status
|
|
debug:
|
|
msg: "{{ tailscale_status.stdout_lines }}"
|