first commit

This commit is contained in:
ansible user
2025-10-22 14:11:49 +02:00
commit 110301f862
75 changed files with 20802 additions and 0 deletions

View File

@@ -0,0 +1,277 @@
---
- 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