- name: Create group
  ansible.builtin.group:
    name: blocky

- name: Create user
  ansible.builtin.user:
    name: blocky
    group: blocky
    create_home: false
    system: true

- name: Ensure install, conf and certs dirs exist
  with_items: [
    "{{ blocky_install_path }}",
    "{{ blocky_config_path }}",
  ]
  ansible.builtin.file:
    path: "{{ item }}"
    mode: "0755"
    group: blocky
    owner: blocky
    state: directory
    recurse: true

- name: Download Lego binary
  ansible.builtin.get_url:
    url: "https://github.com/go-acme/lego/releases/download/v4.23.1/lego_v4.23.1_linux_amd64.tar.gz"
    dest: "/tmp/lego.tar.gz"
    mode: "0644"
  args:
    checksum: "sha256:1fd60b1fd59c239bed22719a5de402cb745d1f933540cb1ec196e2c03e6e8882"

- name: Unarchive Lego binary
  ansible.builtin.unarchive:
    src: "/tmp/lego.tar.gz"
    dest: "/usr/local/bin"
    remote_src: true
    creates: "/usr/local/bin/lego"
    owner: blocky
    group: blocky
    mode: "0755"

- name: Obtain Let's Encrypt certificate with Lego (Cloudflare DNS challenge)
  environment:
    CLOUDFLARE_DNS_API_TOKEN: "{{ cloudflare_dns_api_token }}"
  ansible.builtin.command:
    cmd: "lego --domains '{{ blocky_domain }}' --email {{ blocky_letsencrypt_email }} --dns cloudflare --accept-tos --path {{ blocky_config_path }} run"
    creates: "{{ blocky_cert_file }}"
  args:
    chdir: "/tmp"
  when: not ansible_check_mode
  register: blocky_lego_run_result
  changed_when: blocky_lego_run_result.rc == 0

- name: Set permissions and ownership for Blocky certificate and key
  ansible.builtin.file:
    path: "{{ item }}"
    owner: root
    group: blocky
    mode: "0640"
  loop: ["{{ blocky_cert_file }}", "{{ blocky_key_file }}"]
  notify: Restart blocky

- name: Check if blocky has been installed already
  register: blocky_install_status
  ansible.builtin.stat:
    path: "{{ blocky_install_path }}/blocky"

- name: Fetch latest Blocky release information from GitHub API
  become: false
  delegate_to: localhost
  register: blocky_release_info
  ansible.builtin.uri:
    url: "{{ blocky_github_api_latest_release_url }}"
    method: GET
    return_content: true

- name: Set facts for Blocky version, download URL, and checksum URL
  when: blocky_release_info.json is defined
  delegate_to: localhost
  ansible.builtin.set_fact:
    blocky_version: "{{ blocky_release_info.json.tag_name }}"
    blocky_download_url: >-
      {{ (blocky_release_info.json.assets |
          selectattr('name', 'search', blocky_target_arch) |
          selectattr('name', 'search', blocky_binary_file_extension | regex_escape + '$') |
          map(attribute='browser_download_url') | first | default(omit))
          if blocky_release_info.json.assets | length > 0 else omit }}
    blocky_checksums_file_url: >-
      {{ (blocky_release_info.json.assets |
          selectattr('name', 'eq', 'blocky_checksums.txt') |
          map(attribute='browser_download_url') | first | default(omit))
          if blocky_release_info.json.assets | length > 0 else omit }}

- name: Fetch checksums.txt content (if available)
  when: blocky_checksums_file_url is defined and blocky_checksums_file_url != ''
  become: false
  register: blocky_checksum_file_content
  delegate_to: localhost
  ansible.builtin.uri:
    url: "{{ blocky_checksums_file_url }}"
    method: GET
    return_content: true

- name: Extract SHA256 checksum for the Blocky binary from checksums.txt
  ansible.builtin.set_fact:
    blocky_checksum_sha256: >-
      {{ (blocky_checksum_file_content.content.splitlines()
          | select('contains', 'blocky_' + blocky_version + '_' + blocky_target_arch + '.' + blocky_binary_file_extension)
          | first | default('')) | split() | first }}
  delegate_to: localhost
  when:
    - blocky_checksum_file_content is defined
    - blocky_checksum_file_content.content is defined
    - blocky_checksum_file_content.content | length > 0

- name: Download blocky archive
  when:
    - blocky_install_status is defined
    - not blocky_install_status.stat.exists
    - blocky_download_url is defined and blocky_download_url != ''
    - blocky_checksum_sha256 is defined and blocky_checksum_sha256 != ''
  register: blocky_download_status
  ansible.builtin.get_url:
    url: "{{ blocky_download_url }}"
    dest: "/tmp/blocky_{{ blocky_version }}_{{ blocky_target_arch }}.{{ blocky_binary_file_extension }}"
    mode: "0644"
    checksum: "sha256:{{ blocky_checksum_sha256 }}"

- name: Expand blocky archive
  ansible.builtin.unarchive:
    src: "/tmp/blocky_{{ blocky_version }}_{{ blocky_target_arch }}.{{ blocky_binary_file_extension }}"
    dest: "{{ blocky_install_path }}"
    group: blocky
    owner: blocky
    mode: "0755"
    copy: false
  when: blocky_download_status.changed or not blocky_install_status.stat.exists

- name: Ensure owner and mode of blocky binary
  ansible.builtin.file:
    path: "{{ blocky_install_path }}/blocky"
    group: blocky
    owner: blocky
    mode: "0755"
  notify: Restart blocky

- name: Get Tailscale status for DNS binding
  ansible.builtin.command: tailscale status --json
  register: blocky_tailscale_status_raw
  changed_when: false
  failed_when: false

- name: Set Tailscale IPv4 fact for Blocky
  ansible.builtin.set_fact:
    blocky_tailscale_ipv4: "{{ (blocky_tailscale_status_raw.stdout | from_json).Self.TailscaleIPs | select('match', '^[0-9]+\\.') | first }}"
  when:
    - blocky_tailscale_status_raw.rc == 0
    - (blocky_tailscale_status_raw.stdout | from_json).BackendState == "Running"

- name: Configure blocky
  ansible.builtin.template:
    src: config.yaml.j2
    dest: "{{ blocky_config_path }}/config.yaml"
    owner: root
    group: root
    mode: "0644"
  notify: Restart blocky

- name: Configure blocky service
  ansible.builtin.template:
    src: blocky.service.j2
    dest: /etc/systemd/system/blocky.service
    owner: root
    group: root
    mode: "0644"
  notify: Restart blocky

- name: Ensure blocky service is enabled and running
  ansible.builtin.systemd:
    name: blocky
    state: started
    daemon_reload: true
    enabled: true

- name: Wait for Blocky to listen on Tailscale interface
  ansible.builtin.wait_for:
    host: "{{ blocky_tailscale_ipv4 }}"
    port: 53
    timeout: 30
  when: blocky_tailscale_ipv4 is defined and blocky_tailscale_ipv4 | length > 0

- name: Configure Tailscale DNS to use Blocky
  when:
    - tailscale_api_key is defined and tailscale_api_key | length > 0
    - blocky_tailscale_ipv4 is defined and blocky_tailscale_ipv4 | length > 0
  no_log: true
  block:
    - name: Set Tailscale DNS nameservers to Blocky
      ansible.builtin.uri:
        url: https://api.tailscale.com/api/v2/tailnet/-/dns/nameservers
        method: POST
        url_username: "{{ tailscale_api_key }}"
        url_password: ""
        force_basic_auth: true
        body_format: json
        body:
          dns:
            - "{{ blocky_tailscale_ipv4 }}"
        status_code: 200

    - name: Enable DNS override in Tailscale
      ansible.builtin.uri:
        url: https://api.tailscale.com/api/v2/tailnet/-/dns/preferences
        method: POST
        url_username: "{{ tailscale_api_key }}"
        url_password: ""
        force_basic_auth: true
        body_format: json
        body:
          magicDNS: true
        status_code: 200

- name: Allow incoming connections to Blocky DoT port
  community.general.ufw:
    rule: allow
    port: "{{ blocky_dot_port }}"
    proto: tcp
    state: enabled

- name: Deploy Lego renewal script
  ansible.builtin.template:
    src: lego-renew.sh.j2
    dest: "{{ blocky_config_path }}/lego-renew.sh"
    owner: root
    group: root
    mode: "0700"

- name: Deploy Lego renewal systemd service
  ansible.builtin.template:
    src: lego-renew.service.j2
    dest: /etc/systemd/system/lego-renew.service
    owner: root
    group: root
    mode: "0644"
  notify: Reload systemd

- name: Deploy Lego renewal systemd timer
  ansible.builtin.copy:
    src: lego-renew.timer
    dest: /etc/systemd/system/lego-renew.timer
    owner: root
    group: root
    mode: "0644"
  notify: Reload systemd

- name: Enable and start Lego renewal timer
  ansible.builtin.systemd:
    name: lego-renew.timer
    enabled: true
    state: started
  when: not ansible_check_mode
