Add preflight-homarr (read-only reachability gate) + update-homarr-checked (preflight -> update -> verify back up)
This commit is contained in:
parent
cb3b4e78eb
commit
5625ff89a1
139
playbooks/preflight-homarr.yml
Normal file
139
playbooks/preflight-homarr.yml
Normal file
@ -0,0 +1,139 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# preflight-homarr.yml — reachability pre-flight for Homarr (NO host changes)
|
||||
# ============================================================================
|
||||
# Read-only gate. Confirms, in order:
|
||||
# 1. Infisical auth + deploy SSH key retrievable (runner).
|
||||
# 2. Target host reachable over SSH as the `ansible` user.
|
||||
# 3. Rootless docker is usable (docker version).
|
||||
# 4. The `homarr` container exists and is running.
|
||||
# 5. Homarr answers HTTP on its port.
|
||||
# Fails with a clear reason at the first broken link. Changes nothing on the host.
|
||||
#
|
||||
# Run this before update-homarr-checked.yml, or any time to confirm Homarr health.
|
||||
#
|
||||
# Connects AS the `ansible` user via the Infisical-injected deploy key (same
|
||||
# pattern as deploy-homarr.yml / update-homarr.yml).
|
||||
#
|
||||
# Usage: playbook playbooks/preflight-homarr.yml, extra vars: target=racknerd1
|
||||
# ============================================================================
|
||||
|
||||
- name: "PRECHECK — fetch deploy SSH key from Infisical"
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
vars:
|
||||
infisical_url: "{{ lookup('env','INFISICAL_URL') | default('http://192.168.122.41:8080', true) }}"
|
||||
infisical_env_slug: "{{ lookup('env','INFISICAL_ENV_SLUG') | default('dev', true) }}"
|
||||
tasks:
|
||||
- name: "[precheck] Infisical auth vars present?"
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- lookup('env','INFISICAL_CLIENT_ID') | length > 0
|
||||
- lookup('env','INFISICAL_CLIENT_SECRET') | length > 0
|
||||
- lookup('env','INFISICAL_PROJECT_ID') | length > 0
|
||||
fail_msg: "Missing INFISICAL_CLIENT_ID / _SECRET / _PROJECT_ID in Semaphore Environment."
|
||||
|
||||
- name: "[precheck] Login to Infisical"
|
||||
infisical.vault.login:
|
||||
url: "{{ infisical_url }}"
|
||||
auth_method: universal_auth
|
||||
universal_auth_client_id: "{{ lookup('env','INFISICAL_CLIENT_ID') }}"
|
||||
universal_auth_client_secret: "{{ lookup('env','INFISICAL_CLIENT_SECRET') }}"
|
||||
register: infisical_login
|
||||
|
||||
- name: "[precheck] Read secrets"
|
||||
infisical.vault.read_secrets:
|
||||
login_data: "{{ infisical_login.login_data }}"
|
||||
project_id: "{{ lookup('env','INFISICAL_PROJECT_ID') }}"
|
||||
env_slug: "{{ infisical_env_slug }}"
|
||||
path: "/"
|
||||
as_dict: true
|
||||
register: infisical_secrets
|
||||
|
||||
- name: "[precheck] stash SSH key for preflight play"
|
||||
ansible.builtin.set_fact:
|
||||
pc_ssh_privkey: "{{ infisical_secrets.secrets.SSH_PRIVATE_KEY }}"
|
||||
no_log: true
|
||||
|
||||
- name: "[precheck] write deploy private key to runner temp (0600)"
|
||||
ansible.builtin.copy:
|
||||
dest: "/tmp/homarr_deploy_key"
|
||||
content: "{{ pc_ssh_privkey }}\n"
|
||||
mode: "0600"
|
||||
no_log: true
|
||||
|
||||
- name: "PRE-FLIGHT — reachability + Homarr health (read-only)"
|
||||
hosts: "{{ target | default('racknerd1') }}"
|
||||
gather_facts: false
|
||||
vars:
|
||||
deploy_user: ansible
|
||||
homarr_dir: "/home/{{ deploy_user }}/docker/homarr"
|
||||
homarr_port: 7575
|
||||
ansible_user: ansible
|
||||
ansible_ssh_private_key_file: "/tmp/homarr_deploy_key"
|
||||
ansible_ssh_common_args: "-o StrictHostKeyChecking=accept-new -o IdentitiesOnly=yes"
|
||||
tasks:
|
||||
- name: "[1/5] SSH reachable as ansible user?"
|
||||
ansible.builtin.ping:
|
||||
|
||||
- name: Reset connection so docker group membership is active in this session
|
||||
ansible.builtin.meta: reset_connection
|
||||
|
||||
- name: "[2/5] Rootless docker usable?"
|
||||
ansible.builtin.command: docker version
|
||||
register: dv
|
||||
changed_when: false
|
||||
failed_when: dv.rc != 0
|
||||
|
||||
- name: "[3/5] Homarr container present + running?"
|
||||
ansible.builtin.command:
|
||||
cmd: docker inspect --format '{{ '{{' }}.State.Running{{ '}}' }}' homarr
|
||||
register: homarr_state
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: "Assert homarr container is running"
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- homarr_state.rc == 0
|
||||
- homarr_state.stdout | trim == 'true'
|
||||
fail_msg: >-
|
||||
Homarr container is not running on {{ inventory_hostname }}
|
||||
(docker inspect rc={{ homarr_state.rc }}, state='{{ homarr_state.stdout | trim }}').
|
||||
Run deploy-homarr.yml.
|
||||
|
||||
- name: "[4/5] Compose stack file present?"
|
||||
ansible.builtin.stat:
|
||||
path: "{{ homarr_dir }}/docker-compose.yml"
|
||||
register: compose_file
|
||||
|
||||
- name: "Warn if compose file missing (container running without repo compose)"
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
NOTE: no docker-compose.yml at {{ homarr_dir }} — container is running but
|
||||
update-homarr-checked.yml expects the compose stack. Deploy first if updating.
|
||||
when: not compose_file.stat.exists
|
||||
|
||||
- name: "[5/5] Homarr answers HTTP?"
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1:{{ homarr_port }}/"
|
||||
status_code: [200, 302, 307]
|
||||
register: health
|
||||
retries: 6
|
||||
delay: 5
|
||||
until: health.status is defined and health.status in [200, 302, 307]
|
||||
|
||||
- name: "PRE-FLIGHT PASSED"
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
Homarr reachable on {{ inventory_hostname }}:{{ homarr_port }}
|
||||
(HTTP {{ health.status }}) — safe to update.
|
||||
|
||||
- name: "Cleanup runner temp key"
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: "remove temp deploy key from runner"
|
||||
ansible.builtin.file:
|
||||
path: /tmp/homarr_deploy_key
|
||||
state: absent
|
||||
180
playbooks/update-homarr-checked.yml
Normal file
180
playbooks/update-homarr-checked.yml
Normal file
@ -0,0 +1,180 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# update-homarr-checked.yml — gated update: pre-flight → update → verify back up
|
||||
# ============================================================================
|
||||
# End-to-end safe updater:
|
||||
# PLAY 1 (runner) fetch deploy SSH key from Infisical.
|
||||
# PLAY 2 (target) PRE-FLIGHT — SSH + docker + homarr running + HTTP OK.
|
||||
# Aborts before any change if the stack is already broken.
|
||||
# PLAY 3 (target) UPDATE — pull latest image, recreate, verify HTTP back up,
|
||||
# prune the old dangling image.
|
||||
# PLAY 4 (runner) cleanup temp key.
|
||||
#
|
||||
# Differs from update-homarr.yml by adding an explicit reachability GATE before
|
||||
# the pull (won't touch a host that's already down) and a firm post-update
|
||||
# health confirmation with the before/after image digest reported.
|
||||
#
|
||||
# Prereq: host deployed via deploy-homarr.yml (compose stack exists).
|
||||
# Connects AS the `ansible` user via the Infisical-injected deploy key.
|
||||
#
|
||||
# Usage: playbook playbooks/update-homarr-checked.yml, extra vars: target=racknerd1
|
||||
# ============================================================================
|
||||
|
||||
- name: "PRECHECK — fetch deploy SSH key from Infisical"
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
vars:
|
||||
infisical_url: "{{ lookup('env','INFISICAL_URL') | default('http://192.168.122.41:8080', true) }}"
|
||||
infisical_env_slug: "{{ lookup('env','INFISICAL_ENV_SLUG') | default('dev', true) }}"
|
||||
tasks:
|
||||
- name: "[precheck] Infisical auth vars present?"
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- lookup('env','INFISICAL_CLIENT_ID') | length > 0
|
||||
- lookup('env','INFISICAL_CLIENT_SECRET') | length > 0
|
||||
- lookup('env','INFISICAL_PROJECT_ID') | length > 0
|
||||
fail_msg: "Missing INFISICAL_CLIENT_ID / _SECRET / _PROJECT_ID in Semaphore Environment."
|
||||
|
||||
- name: "[precheck] Login to Infisical"
|
||||
infisical.vault.login:
|
||||
url: "{{ infisical_url }}"
|
||||
auth_method: universal_auth
|
||||
universal_auth_client_id: "{{ lookup('env','INFISICAL_CLIENT_ID') }}"
|
||||
universal_auth_client_secret: "{{ lookup('env','INFISICAL_CLIENT_SECRET') }}"
|
||||
register: infisical_login
|
||||
|
||||
- name: "[precheck] Read secrets"
|
||||
infisical.vault.read_secrets:
|
||||
login_data: "{{ infisical_login.login_data }}"
|
||||
project_id: "{{ lookup('env','INFISICAL_PROJECT_ID') }}"
|
||||
env_slug: "{{ infisical_env_slug }}"
|
||||
path: "/"
|
||||
as_dict: true
|
||||
register: infisical_secrets
|
||||
|
||||
- name: "[precheck] stash SSH key"
|
||||
ansible.builtin.set_fact:
|
||||
pc_ssh_privkey: "{{ infisical_secrets.secrets.SSH_PRIVATE_KEY }}"
|
||||
no_log: true
|
||||
|
||||
- name: "[precheck] write deploy private key to runner temp (0600)"
|
||||
ansible.builtin.copy:
|
||||
dest: "/tmp/homarr_deploy_key"
|
||||
content: "{{ pc_ssh_privkey }}\n"
|
||||
mode: "0600"
|
||||
no_log: true
|
||||
|
||||
- name: "PRE-FLIGHT — verify reachable + healthy BEFORE touching anything"
|
||||
hosts: "{{ target | default('racknerd1') }}"
|
||||
gather_facts: false
|
||||
vars:
|
||||
deploy_user: ansible
|
||||
homarr_dir: "/home/{{ deploy_user }}/docker/homarr"
|
||||
homarr_port: 7575
|
||||
ansible_user: ansible
|
||||
ansible_ssh_private_key_file: "/tmp/homarr_deploy_key"
|
||||
ansible_ssh_common_args: "-o StrictHostKeyChecking=accept-new -o IdentitiesOnly=yes"
|
||||
tasks:
|
||||
- name: "[pre] SSH reachable as ansible user?"
|
||||
ansible.builtin.ping:
|
||||
|
||||
- name: Reset connection so docker group membership is active in this session
|
||||
ansible.builtin.meta: reset_connection
|
||||
|
||||
- name: "[pre] Rootless docker usable?"
|
||||
ansible.builtin.command: docker version
|
||||
register: dv
|
||||
changed_when: false
|
||||
failed_when: dv.rc != 0
|
||||
|
||||
- name: "[pre] Compose stack present?"
|
||||
ansible.builtin.stat:
|
||||
path: "{{ homarr_dir }}/docker-compose.yml"
|
||||
register: compose_file
|
||||
|
||||
- name: "[pre] Fail early if stack not deployed"
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- compose_file.stat.exists
|
||||
fail_msg: >-
|
||||
No docker-compose.yml at {{ homarr_dir }} — run deploy-homarr.yml first.
|
||||
|
||||
- name: "[pre] Homarr answering HTTP before update?"
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1:{{ homarr_port }}/"
|
||||
status_code: [200, 302, 307]
|
||||
register: pre_health
|
||||
retries: 6
|
||||
delay: 5
|
||||
until: pre_health.status is defined and pre_health.status in [200, 302, 307]
|
||||
|
||||
- name: "[pre] PRE-FLIGHT PASSED"
|
||||
ansible.builtin.debug:
|
||||
msg: "Reachable + healthy (HTTP {{ pre_health.status }}). Proceeding to update."
|
||||
|
||||
- name: "UPDATE — pull latest image + recreate, then confirm back up"
|
||||
hosts: "{{ target | default('racknerd1') }}"
|
||||
gather_facts: false
|
||||
vars:
|
||||
deploy_user: ansible
|
||||
homarr_dir: "/home/{{ deploy_user }}/docker/homarr"
|
||||
homarr_port: 7575
|
||||
ansible_user: ansible
|
||||
ansible_ssh_private_key_file: "/tmp/homarr_deploy_key"
|
||||
ansible_ssh_common_args: "-o StrictHostKeyChecking=accept-new -o IdentitiesOnly=yes"
|
||||
tasks:
|
||||
- name: Reset connection so docker group membership is active in this session
|
||||
ansible.builtin.meta: reset_connection
|
||||
|
||||
- name: Gather facts (after connection reset)
|
||||
ansible.builtin.setup:
|
||||
|
||||
- name: Record image digest before update
|
||||
ansible.builtin.command:
|
||||
cmd: docker inspect --format '{{ '{{' }}.Image{{ '}}' }}' homarr
|
||||
register: img_before
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Pull latest image + recreate (compose preserved as-is)
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ homarr_dir }}"
|
||||
pull: always
|
||||
state: present
|
||||
recreate: auto
|
||||
|
||||
- name: Record image digest after update
|
||||
ansible.builtin.command:
|
||||
cmd: docker inspect --format '{{ '{{' }}.Image{{ '}}' }}' homarr
|
||||
register: img_after
|
||||
changed_when: false
|
||||
|
||||
- name: "POST-CHECK — Homarr back up + reachable?"
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1:{{ homarr_port }}/"
|
||||
status_code: [200, 302, 307]
|
||||
register: post_health
|
||||
retries: 18
|
||||
delay: 5
|
||||
until: post_health.status is defined and post_health.status in [200, 302, 307]
|
||||
|
||||
- name: Prune the now-dangling old image (best effort)
|
||||
ansible.builtin.command: docker image prune -f
|
||||
register: prune
|
||||
changed_when: "'Total reclaimed space: 0B' not in prune.stdout"
|
||||
failed_when: false
|
||||
|
||||
- name: Result
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
Homarr on {{ inventory_hostname }}:{{ homarr_port }} back up — HTTP {{ post_health.status }}.
|
||||
{{ 'Image updated.' if img_before.stdout != img_after.stdout else 'Already up to date (no image change).' }}
|
||||
|
||||
- name: "Cleanup runner temp key"
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: "remove temp deploy key from runner"
|
||||
ansible.builtin.file:
|
||||
path: /tmp/homarr_deploy_key
|
||||
state: absent
|
||||
Loading…
Reference in New Issue
Block a user