feat: WMT client versioning, release management and force-update playbook
- api/wmt.py: add GET /api/wmt/client/version and GET /api/wmt/client/download endpoints; rewrite submit_update_request with dedup logic - web/wmt.py: add releases, releases_upload, releases_delete, releases_build routes; build-from-folder excludes hidden/data/venv/pyc files - web/main.py: admin per-device delete route; clear-device-logs route; pass devices list to admin template - templates/wmt/releases.html: new release management page (current release info, upload form, build-from-folder card) - templates/admin.html: replace nuclear clear-devices with clear-logs + per-device delete table - templates/base.html: add Client Releases nav link in WMT sidebar section - templates/ansible/execute.html: add Update WMT Code playbook card - ansible/playbooks/update_wmt_code.yml: rsync WMT_project to clients excluding data/; backs up app.py; restarts wmt service - ansible_service.py: register update_wmt_code description - .gitignore: whitelist update_wmt_code.yml
This commit is contained in:
@@ -0,0 +1,306 @@
|
||||
---
|
||||
# Update WMT client code from the controller's WMT_project folder
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
# Use this for devices that have not yet received the HTTP auto-update,
|
||||
# or whenever you need to force a code push from the server.
|
||||
#
|
||||
# What this playbook does:
|
||||
# 1. Ensure WMT directory exists on the target
|
||||
# 2. Back up the current app.py as app.py.bak.<version>
|
||||
# 3. Copy everything from /home/pi/Desktop/WMT_project/ on the CONTROLLER
|
||||
# → /home/pi/Desktop/WMT/ on the TARGET
|
||||
# The data/ directory on the target is fully preserved (never touched)
|
||||
# 4. Fix file ownership
|
||||
# 5. Restart the wmt systemd service
|
||||
#
|
||||
# The data/ directory (config.txt, idmasa.txt, tag.txt, log.txt, device_info.txt)
|
||||
# is intentionally excluded — device-specific settings stay intact.
|
||||
#
|
||||
# Run via: Ansible > Playbooks > "Update WMT Code"
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Update WMT client code from WMT_project folder
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
become: false
|
||||
|
||||
vars:
|
||||
controller_src: /home/pi/Desktop/WMT_project/
|
||||
wmt_dir: /home/pi/Desktop/WMT
|
||||
|
||||
tasks:
|
||||
|
||||
# ── 1. Ensure WMT directory exists ────────────────────────────────────
|
||||
- name: Ensure WMT directory exists on target
|
||||
file:
|
||||
path: "{{ wmt_dir }}"
|
||||
state: directory
|
||||
owner: pi
|
||||
group: pi
|
||||
mode: '0755'
|
||||
|
||||
# ── 2. Back up current app.py ─────────────────────────────────────────
|
||||
- name: Read first line of current app.py (for backup filename)
|
||||
shell: head -1 {{ wmt_dir }}/app.py 2>/dev/null || echo "unknown"
|
||||
register: local_first_line
|
||||
changed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: Extract local version number
|
||||
set_fact:
|
||||
local_version: >-
|
||||
{{ local_first_line.stdout
|
||||
| regex_search('version\s+([\d.]+)', '\1')
|
||||
| first | default('old') }}
|
||||
|
||||
- name: Back up current app.py
|
||||
copy:
|
||||
src: "{{ wmt_dir }}/app.py"
|
||||
dest: "{{ wmt_dir }}/app.py.bak.{{ local_version }}"
|
||||
remote_src: true
|
||||
owner: pi
|
||||
group: pi
|
||||
mode: preserve
|
||||
ignore_errors: true
|
||||
|
||||
- name: Show backup info
|
||||
debug:
|
||||
msg: "Backed up app.py v{{ local_version }} → app.py.bak.{{ local_version }}"
|
||||
|
||||
# ── 3. Snapshot data/ before copy (audit) ────────────────────────────
|
||||
- name: List current data/ files (audit)
|
||||
shell: ls -1 {{ wmt_dir }}/data/ 2>/dev/null || echo "(empty or missing)"
|
||||
register: data_files_before
|
||||
changed_when: false
|
||||
|
||||
- name: Show data/ files that will be preserved
|
||||
debug:
|
||||
msg: "data/ contents (will NOT be changed): {{ data_files_before.stdout_lines }}"
|
||||
|
||||
# ── 4. Sync WMT_project → WMT on target, excluding data/ ─────────────
|
||||
# synchronize uses rsync under the hood; delegate_to pushes
|
||||
# from the controller to the target.
|
||||
- name: Sync WMT_project to target (exclude data/ and junk files)
|
||||
synchronize:
|
||||
src: "{{ controller_src }}"
|
||||
dest: "{{ wmt_dir }}/"
|
||||
recursive: true
|
||||
delete: false
|
||||
checksum: true
|
||||
rsync_opts:
|
||||
- "--exclude=data/"
|
||||
- "--exclude=.git/"
|
||||
- "--exclude=.gitignore"
|
||||
- "--exclude=__pycache__/"
|
||||
- "--exclude=*.pyc"
|
||||
- "--exclude=*.pyo"
|
||||
- "--exclude=*.log"
|
||||
- "--exclude=*.bak"
|
||||
- "--exclude=venv/"
|
||||
- "--exclude=.venv/"
|
||||
- "--exclude=node_modules/"
|
||||
- "--exclude=.*"
|
||||
register: sync_result
|
||||
|
||||
- name: Show sync summary
|
||||
debug:
|
||||
msg: "Sync completed. Changed: {{ sync_result.changed }}"
|
||||
|
||||
# ── 5. Fix ownership ──────────────────────────────────────────────────
|
||||
- name: Set correct ownership on WMT directory
|
||||
become: true
|
||||
file:
|
||||
path: "{{ wmt_dir }}"
|
||||
owner: pi
|
||||
group: pi
|
||||
recurse: true
|
||||
|
||||
# ── 6. Verify data/ is still intact ──────────────────────────────────
|
||||
- name: List data/ files after update (verification)
|
||||
shell: ls -1 {{ wmt_dir }}/data/ 2>/dev/null || echo "(empty)"
|
||||
register: data_files_after
|
||||
changed_when: false
|
||||
|
||||
- name: Show data/ contents after update (should match before)
|
||||
debug:
|
||||
msg: "{{ data_files_after.stdout_lines }}"
|
||||
|
||||
# ── 7. Restart WMT service ────────────────────────────────────────────
|
||||
- name: Restart WMT systemd service
|
||||
become: true
|
||||
systemd:
|
||||
name: wmt
|
||||
state: restarted
|
||||
enabled: true
|
||||
register: service_result
|
||||
ignore_errors: true
|
||||
|
||||
- name: Show service state
|
||||
debug:
|
||||
msg: "WMT service state: {{ service_result.status.ActiveState | default('unknown') }}"
|
||||
when: service_result is not failed
|
||||
|
||||
- name: Warn if service restart failed
|
||||
debug:
|
||||
msg: "WARNING: wmt service restart failed – the device may need a manual reboot."
|
||||
when: service_result is failed
|
||||
|
||||
vars:
|
||||
wmt_dir: /home/pi/Desktop/WMT
|
||||
tmp_zip: /tmp/wmt_update.zip
|
||||
# Controller address – override on CLI with -e "server_url=http://..."
|
||||
server_url: "http://{{ hostvars[inventory_hostname]['ansible_host'] | default(ansible_host) | regex_replace('\\d+\\.\\d+$', '10.76.157.1') }}"
|
||||
|
||||
tasks:
|
||||
|
||||
# ── 0. Resolve server URL ─────────────────────────────────────────────
|
||||
# The monitoring server address is read from the device's own config.txt
|
||||
# so we don't have to hard-code it here.
|
||||
- name: Read server_host from WMT config.txt
|
||||
shell: |
|
||||
grep -E '^\s*server_host\s*=' {{ wmt_dir }}/data/config.txt 2>/dev/null \
|
||||
| head -1 | awk -F'=' '{print $2}' | tr -d ' \r\n'
|
||||
register: cfg_server_host
|
||||
changed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: Read server_port from WMT config.txt
|
||||
shell: |
|
||||
grep -E '^\s*server_port\s*=' {{ wmt_dir }}/data/config.txt 2>/dev/null \
|
||||
| head -1 | awk -F'=' '{print $2}' | tr -d ' \r\n'
|
||||
register: cfg_server_port
|
||||
changed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: Set monitoring server base URL
|
||||
set_fact:
|
||||
monitoring_base: "http://{{ cfg_server_host.stdout | default('rpi-ansible') }}:{{ cfg_server_port.stdout | default('5000') }}"
|
||||
|
||||
- name: Show resolved server URL
|
||||
debug:
|
||||
msg: "Monitoring server: {{ monitoring_base }}"
|
||||
|
||||
# ── 1. Check latest version on server ────────────────────────────────
|
||||
- name: Query latest WMT version from monitoring server
|
||||
uri:
|
||||
url: "{{ monitoring_base }}/api/wmt/client/version"
|
||||
method: GET
|
||||
return_content: true
|
||||
timeout: 15
|
||||
register: version_response
|
||||
ignore_errors: true
|
||||
|
||||
- name: Show server version info
|
||||
debug:
|
||||
msg: "Server release: v{{ version_response.json.version | default('unknown') }} ({{ version_response.json.filename | default('n/a') }})"
|
||||
when: version_response is not failed
|
||||
|
||||
- name: Fail if server version endpoint unreachable
|
||||
fail:
|
||||
msg: "Cannot reach {{ monitoring_base }}/api/wmt/client/version – is the server running?"
|
||||
when: version_response is failed
|
||||
|
||||
# ── 2. Get current local version ─────────────────────────────────────
|
||||
- name: Read first line of local app.py
|
||||
shell: head -1 {{ wmt_dir }}/app.py 2>/dev/null || echo "unknown"
|
||||
register: local_first_line
|
||||
changed_when: false
|
||||
|
||||
- name: Extract local version number
|
||||
set_fact:
|
||||
local_version: "{{ local_first_line.stdout | regex_search('version\\s+([\\d.]+)', '\\1') | first | default('0') }}"
|
||||
|
||||
- name: Show local version
|
||||
debug:
|
||||
msg: "Local version: {{ local_version }} | Server version: {{ version_response.json.version }}"
|
||||
|
||||
# ── 3. Ensure WMT directory exists ───────────────────────────────────
|
||||
- name: Ensure WMT directory exists
|
||||
file:
|
||||
path: "{{ wmt_dir }}"
|
||||
state: directory
|
||||
owner: pi
|
||||
group: pi
|
||||
mode: '0755'
|
||||
|
||||
# ── 4. Download release zip ───────────────────────────────────────────
|
||||
- name: Download WMT release zip from monitoring server
|
||||
get_url:
|
||||
url: "{{ monitoring_base }}/api/wmt/client/download"
|
||||
dest: "{{ tmp_zip }}"
|
||||
force: true
|
||||
timeout: 120
|
||||
mode: '0644'
|
||||
|
||||
# ── 5. Back up current app.py ─────────────────────────────────────────
|
||||
- name: Back up current app.py
|
||||
copy:
|
||||
src: "{{ wmt_dir }}/app.py"
|
||||
dest: "{{ wmt_dir }}/app.py.bak.{{ local_version }}"
|
||||
remote_src: true
|
||||
owner: pi
|
||||
group: pi
|
||||
mode: preserve
|
||||
ignore_errors: true
|
||||
|
||||
# ── 6. Extract zip – skip data/ directory ─────────────────────────────
|
||||
- name: Extract WMT release zip (preserving data/ directory)
|
||||
shell: |
|
||||
cd {{ wmt_dir }}
|
||||
python3 - <<'EOF'
|
||||
import zipfile, os, sys
|
||||
zip_path = "{{ tmp_zip }}"
|
||||
dest = "{{ wmt_dir }}"
|
||||
skipped = 0
|
||||
extracted = 0
|
||||
with zipfile.ZipFile(zip_path, 'r') as zf:
|
||||
for member in zf.infolist():
|
||||
p = member.filename.replace('\\', '/')
|
||||
if p.startswith('data/') or p == 'data':
|
||||
skipped += 1
|
||||
continue
|
||||
zf.extract(member, dest)
|
||||
extracted += 1
|
||||
print(f"Extracted {extracted} files, skipped {skipped} data/ entries")
|
||||
EOF
|
||||
register: extract_result
|
||||
changed_when: true
|
||||
|
||||
- name: Show extraction result
|
||||
debug:
|
||||
msg: "{{ extract_result.stdout }}"
|
||||
|
||||
# ── 7. Fix ownership ──────────────────────────────────────────────────
|
||||
- name: Set correct ownership on WMT directory
|
||||
become: true
|
||||
file:
|
||||
path: "{{ wmt_dir }}"
|
||||
owner: pi
|
||||
group: pi
|
||||
recurse: true
|
||||
|
||||
# ── 8. Clean up temp zip ──────────────────────────────────────────────
|
||||
- name: Remove temporary zip file
|
||||
file:
|
||||
path: "{{ tmp_zip }}"
|
||||
state: absent
|
||||
|
||||
# ── 9. Restart WMT service ────────────────────────────────────────────
|
||||
- name: Restart WMT systemd service
|
||||
become: true
|
||||
systemd:
|
||||
name: wmt
|
||||
state: restarted
|
||||
enabled: true
|
||||
register: service_result
|
||||
ignore_errors: true
|
||||
|
||||
- name: Show service restart result
|
||||
debug:
|
||||
msg: "Service state: {{ service_result.status.ActiveState | default('unknown') }}"
|
||||
when: service_result is not failed
|
||||
|
||||
- name: Warn if service restart failed
|
||||
debug:
|
||||
msg: "WARNING: wmt service restart failed – the device may need a manual reboot."
|
||||
when: service_result is failed
|
||||
Reference in New Issue
Block a user