Files
ansible-worker/files/ansible-playbooks/deploy-scroll24.yml
ansible user 110301f862 first commit
2025-10-22 14:11:49 +02:00

278 lines
11 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
- name: Deploy scroll24 script and cron configuration
hosts: all
gather_facts: no
vars:
ssh_user: "{{ ansible_user | default('root') }}"
ssh_pass: "{{ ansible_password | default(ansible_ssh_pass) }}"
# Version tag for scroll24 deployment (operator-controlled)
scroll24_version: "v1.1"
# Controller-side source paths (same place as wifidebug.sh)
src_script: "files/scroll24.sh"
src_cron_snippet: "files/crond-root-scroll24"
# Remote destinations
remote_script: "/root/scroll24.sh"
remote_crontab: "/etc/crontabs/root"
tmp_cron_snippet: "/tmp/crond-root-scroll24.snippet"
tmp_cron_new: "/tmp/cron.root.new"
crontab_backup_dir: "/etc/crontabs"
# RabbitMQ (same contract/style as your other playbooks)
rmq_host: "{{ lookup('env','RMQ_HOST') | default('10.210.12.2', true) }}"
rmq_port: "{{ lookup('env','RMQ_PORT') | default('15672', true) }}"
rmq_user: "{{ lookup('env','RMQ_USER') | default('admin', true) }}"
rmq_pass: "{{ lookup('env','RMQ_PASS') | default('change_me', true) }}"
rmq_vhost: "{{ lookup('env','RMQ_VHOST') | default('app', true) }}"
rmq_exchange: "{{ lookup('env','RMQ_EXCHANGE') | default('controls', true) }}"
control_queue: "{{ lookup('env','CONTROL_QUEUE') | default('queue_controls', true) }}"
tasks:
##########################################################################
# 0. Controller sanity check for tools
##########################################################################
- name: Verify controller tools (sshpass, sha256sum/busybox)
delegate_to: localhost
shell: |
command -v sshpass >/dev/null 2>&1 \
&& (command -v sha256sum >/dev/null 2>&1 || command -v busybox >/dev/null 2>&1)
args: { executable: /bin/bash }
register: ctrl_tools
changed_when: false
failed_when: ctrl_tools.rc != 0
##########################################################################
# 1. Upload /root/scroll24.sh if changed (sha256 verified)
##########################################################################
- name: Compute local sha256 of scroll24.sh
delegate_to: localhost
command: sha256sum {{ src_script }}
register: sha_local
changed_when: false
- name: Extract local sha256 digest (regex)
delegate_to: localhost
set_fact:
local_hash: "{{ (sha_local.stdout | default('')) | regex_search('([A-Fa-f0-9]{64})') | default('') }}"
- name: Compute remote sha256 of /root/scroll24.sh
raw: "sha256sum {{ remote_script }} 2>/dev/null || busybox sha256sum {{ remote_script }} 2>/dev/null || true"
register: sha_remote
changed_when: false
failed_when: false
- name: Extract remote sha256 digest (regex)
set_fact:
remote_hash: "{{ (sha_remote.stdout | default('')) | regex_search('([A-Fa-f0-9]{64})') | default('') }}"
- name: Decide if scroll24.sh needs upload
set_fact:
script_changed: "{{ (remote_hash | length == 0) or (local_hash != remote_hash) }}"
- name: Upload scroll24.sh via scp (if needed)
when: script_changed | bool
delegate_to: localhost
command: >
sshpass -p {{ ssh_pass | quote }}
scp -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
{{ src_script }}
{{ ssh_user }}@{{ ansible_host }}:{{ remote_script }}
register: scp_scroll24
changed_when: true
- name: Ensure /root/scroll24.sh permissions and ownership
raw: "chown root:root {{ remote_script }} && chmod 0755 {{ remote_script }}"
changed_when: script_changed | bool
- name: Recompute remote sha256 after upload
when: script_changed | bool
raw: "sha256sum {{ remote_script }} || busybox sha256sum {{ remote_script }}"
register: sha_remote_after
changed_when: false
failed_when: false
- name: Extract remote-after sha256 digest (regex)
when: script_changed | bool
set_fact:
remote_hash_after: "{{ (sha_remote_after.stdout | default('')) | regex_search('([A-Fa-f0-9]{64})') | default('') }}"
- name: Fail if scroll24.sh sha256 mismatch after upload
when: script_changed | bool and (remote_hash_after | default('')) != (local_hash | default(''))
fail:
msg: "sha256 mismatch between controller and remote scroll24.sh"
##########################################################################
# 1b. Extract the period (minutes) from the first line of the snippet
##########################################################################
- name: Extract period minutes from first snippet line (*/N …)
delegate_to: localhost
shell: |
awk 'NR==1{
f=$1;
if (f ~ /^\*\/[0-9]+$/) { gsub("^\\*/","",f); print f; exit }
else if (f ~ /^[0-9]+$/) { print f; exit }
else { print ""; exit }
}' {{ src_cron_snippet }}
args: { executable: /bin/bash }
register: cron_period_cmd
changed_when: false
failed_when: false
- name: Set cron_period fact
delegate_to: localhost
set_fact:
cron_period: "{{ (cron_period_cmd.stdout | trim) }}"
##########################################################################
# 2. Upload cron snippet to device /tmp (operator-provided content)
##########################################################################
- name: Push cron snippet to device tmp
delegate_to: localhost
command: >
sshpass -p {{ ssh_pass | quote }}
scp -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
{{ src_cron_snippet }}
{{ ssh_user }}@{{ ansible_host }}:{{ tmp_cron_snippet }}
register: scp_snippet
changed_when: true
##########################################################################
# 3. Crontab subset logic (only scroll24.sh lines)
##########################################################################
- name: Ensure /etc/crontabs/root exists (with perms)
raw: |
if [ ! -f {{ remote_crontab }} ]; then
touch {{ remote_crontab }};
fi
chown root:root {{ remote_crontab }};
chmod 0644 {{ remote_crontab }};
changed_when: false
- name: Extract current scroll24 lines from crontab
raw: "grep -F 'scroll24.sh' {{ remote_crontab }} || true"
register: cron_subset
changed_when: false
failed_when: false
- name: Normalize controller snippet (for compare only)
delegate_to: localhost
shell: |
grep -v '^[[:space:]]*$' {{ src_cron_snippet }} \
| sed 's/[[:space:]]\+/ /g' | sed 's/[[:space:]]*$//' | sort -u
register: norm_snippet
changed_when: false
- name: Normalize device scroll24 subset (for compare only)
delegate_to: localhost
shell: |
printf "%s\n" "{{ cron_subset.stdout | default('') }}" \
| grep -v '^[[:space:]]*$' \
| sed 's/[[:space:]]\+/ /g' | sed 's/[[:space:]]*$//' | sort -u
register: norm_remote_subset
changed_when: false
- name: Decide if crontab needs update (subset compare)
delegate_to: localhost
set_fact:
crontab_changed: "{{ (norm_snippet.stdout | trim) != (norm_remote_subset.stdout | trim) }}"
- name: Backup current crontab (timestamped)
when: crontab_changed | bool
raw: "cp -a {{ remote_crontab }} {{ crontab_backup_dir }}/root.bak.$(date +%Y%m%d%H%M%S)"
changed_when: true
- name: Replace scroll24 lines in crontab (preserve all others; tidy splice)
when: crontab_changed | bool
raw: "grep -v 'scroll24\\.sh' {{ remote_crontab }} > {{ tmp_cron_new }} && awk 'BEGIN{for(i=1;i<=NR;i++)a[i]=$0} {a[NR]=$0} END{e=NR; while(e>0 && a[e] ~ /^[[:space:]]*$/){e--}; for(i=1;i<=e;i++) print a[i]}' {{ tmp_cron_new }} > {{ tmp_cron_new }}.trim && mv {{ tmp_cron_new }}.trim {{ tmp_cron_new }} && cat {{ tmp_cron_snippet }} >> {{ tmp_cron_new }} && printf '\\n' >> {{ tmp_cron_new }} && mv {{ tmp_cron_new }} {{ remote_crontab }} && chown root:root {{ remote_crontab }} && chmod 0644 {{ remote_crontab }}"
changed_when: true
##########################################################################
# 4. Restart crond if script or cron changed (with :51–:59 guard)
##########################################################################
- name: Check if restart required
set_fact:
need_restart: "{{ (script_changed | bool) or (crontab_changed | bool) }}"
- name: Get current seconds
when: need_restart | bool
raw: "date +%S"
register: nowsec
changed_when: false
- name: Sleep 10s if seconds 51-59
when: need_restart | bool and (nowsec.stdout | int >= 51)
pause:
seconds: 10
- name: Restart crond via move/move
when: need_restart | bool
raw: "mv /tmp/launchd/services/crond /root/crond && sleep 1 && mv /root/crond /tmp/launchd/services/crond"
register: crond_restart
changed_when: true
failed_when: false
- name: Verify crond is running
when: need_restart | bool
raw: "pgrep -f '/usr/sbin/crond' || busybox pgrep crond || echo missing"
register: crond_pid
changed_when: false
failed_when: false
##########################################################################
# 5. Final journals to RabbitMQ (controller)
##########################################################################
- name: Publish final scroll24 deployment journal
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost | urlencode }}/{{ rmq_exchange | urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname | default(inventory_hostname)),
'task_name': 'journal_add',
'task_result': (
'scroll24: '
~ (script_changed | ternary('script installed/updated; ', 'script up-to-date; '))
~ (crontab_changed | ternary('cron updated; ', 'cron already matches; '))
~ (need_restart | ternary('crond restarted; ', 'crond unchanged; '))
)
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Set NetBox custom field scroll24 -> "<version>_<period>"
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost | urlencode }}/{{ rmq_exchange | urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname | default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'scroll24',
'task_result': scroll24_version ~ '_' ~ (cron_period | default(''))
} | to_json }}"
payload_encoding: "string"
changed_when: false