diff --git a/README.md b/README.md index d634bbf..9dcc82b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,76 @@ # dotmesh-monitor +Ansible playbooks for deploying MeshCore monitoring nodes (Raspberry Pi Zero W / Zero 2 W). + +## Hosts + +| Host | Hardware | Group | +|---|---|---| +| dm-baldock | Pi Zero W (armv6) | zero_w | +| dm-ashwell | Pi Zero 2 W (armv7) | zero2_w | +| dm-edworth | Pi Zero 2 W (armv7) | zero2_w | + +## Prerequisites + +**Local machine:** +```bash +pip install ansible +# or: sudo apt install ansible +``` + +**New Pi node checklist:** +1. Flash Raspberry Pi OS Lite (Bookworm), connect to WiFi +2. Install Tailscale and join the network +3. Ensure `david` user exists with sudo access +4. Connect the MeshCore device via USB, then find its serial ID: + ```bash + ls /dev/serial/by-id/ + ``` +5. Set `serial_port` in `ansible/host_vars/.yml` + +SSH key auth is required. From this machine: +```bash +ssh-copy-id david@.tail740bb.ts.net +``` + +## Usage + +**Deploy to a single host (recommended for first run / testing):** +```bash +cd ansible +ansible-playbook -i inventory.yml site.yml --limit dm-edworth +``` + +**Deploy to all nodes:** +```bash +ansible-playbook -i inventory.yml site.yml +``` + +**Dry run:** +```bash +ansible-playbook -i inventory.yml site.yml --limit dm-edworth --check +``` + +If sudo requires a password, add `--ask-become-pass`. + +You'll be prompted for a Tailscale auth key — leave blank if the node is already authenticated. + +## What it does + +1. **base** — apt upgrade, installs screen/pipx/vnstat/git, sets MOTD, installs and authenticates Tailscale +2. **meshcore_cli** — installs `meshcore-cli` via pipx +3. **meshcore_capture** — runs the agessaman/meshcore-packet-capture install script, writes `.env.local` config, enables `meshcore-capture.service`, deploys update/log helper scripts +4. **scripts** — deploys `voltage.sh` and `bandwidth.sh` + +## Config + +Shared MQTT config lives in `group_vars/meshcore.yml`. Per-host serial port is in `host_vars/.yml`. + +Running the playbook again re-applies `.env.local` and restarts the service if it changed — safe to run on already-deployed nodes. + +## Credentials note + +`group_vars/meshcore.yml` contains MQTT credentials in plaintext. Consider encrypting with Ansible Vault if this repo is shared: +```bash +ansible-vault encrypt_string 'yourpassword' --name mqtt_ukmesh_password +``` diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml new file mode 100644 index 0000000..28e6473 --- /dev/null +++ b/ansible/group_vars/all.yml @@ -0,0 +1,5 @@ +remote_user: david +ansible_user: "{{ remote_user }}" +ansible_become: true + +meshcore_description: "meshcore management" diff --git a/ansible/group_vars/meshcore.yml b/ansible/group_vars/meshcore.yml new file mode 100644 index 0000000..f91e2b8 --- /dev/null +++ b/ansible/group_vars/meshcore.yml @@ -0,0 +1,13 @@ +packetcapture_update_repo: agessaman/meshcore-packet-capture +packetcapture_update_branch: main + +packetcapture_iata: LTN +packetcapture_advert_interval_hours: 11 +packetcapture_log_level: INFO +packetcapture_owner_public_key: 6C6E8B1B0EE79816941D85928D6ABB0002F46D4ED15AD8D8D2A8B56A3022D13B +packetcapture_owner_email: david@datajack.org + +# MQTT broker credentials +mqtt_ukmesh_username: dotdavid +mqtt_ukmesh_password: ApNPXjRj4xtRoluW2X22 +mqtt_meshrank_token: 908E8CB151EE04E37A4B6529CD0263C3 diff --git a/ansible/host_vars/dm-ashwell.yml b/ansible/host_vars/dm-ashwell.yml new file mode 100644 index 0000000..50a53fa --- /dev/null +++ b/ansible/host_vars/dm-ashwell.yml @@ -0,0 +1 @@ +serial_port: /dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_1C:DB:D4:5A:AA:B0-if00 diff --git a/ansible/host_vars/dm-baldock.yml b/ansible/host_vars/dm-baldock.yml new file mode 100644 index 0000000..343df89 --- /dev/null +++ b/ansible/host_vars/dm-baldock.yml @@ -0,0 +1 @@ +serial_port: /dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_98:3D:AE:61:74:60-if00 diff --git a/ansible/host_vars/dm-edworth.yml b/ansible/host_vars/dm-edworth.yml new file mode 100644 index 0000000..b75e72c --- /dev/null +++ b/ansible/host_vars/dm-edworth.yml @@ -0,0 +1,2 @@ +# Find this by connecting the MeshCore device then running: ls /dev/serial/by-id/ +serial_port: FILL_IN_SERIAL_PORT diff --git a/ansible/inventory.yml b/ansible/inventory.yml new file mode 100644 index 0000000..3d1d957 --- /dev/null +++ b/ansible/inventory.yml @@ -0,0 +1,14 @@ +all: + children: + meshcore: + children: + zero_w: + hosts: + dm-baldock: + ansible_host: dm-baldock + zero2_w: + hosts: + dm-ashwell: + ansible_host: dm-ashwell + dm-edworth: + ansible_host: dm-edworth diff --git a/ansible/roles/base/tasks/main.yml b/ansible/roles/base/tasks/main.yml new file mode 100644 index 0000000..1053239 --- /dev/null +++ b/ansible/roles/base/tasks/main.yml @@ -0,0 +1,47 @@ +--- +- name: Install base packages + apt: + name: + - screen + - pipx + - vnstat + - git + state: present + become: true + +- name: Set MOTD + template: + src: motd.j2 + dest: /etc/motd + owner: root + group: root + mode: "0644" + become: true + +- name: Add Tailscale apt signing key + get_url: + url: https://pkgs.tailscale.com/stable/debian/bookworm.nosetup.sh + dest: /tmp/tailscale-setup.sh + mode: "0755" + when: tailscale_auth_key != "" + +- name: Run Tailscale install script + shell: sh /tmp/tailscale-setup.sh + args: + creates: /usr/bin/tailscale + become: true + when: tailscale_auth_key != "" + +- name: Enable and start tailscaled + systemd: + name: tailscaled + enabled: true + state: started + become: true + when: tailscale_auth_key != "" + +- name: Authenticate Tailscale + shell: tailscale up --authkey {{ tailscale_auth_key }} + become: true + when: tailscale_auth_key != "" + no_log: true diff --git a/ansible/roles/base/templates/motd.j2 b/ansible/roles/base/templates/motd.j2 new file mode 100644 index 0000000..e238284 --- /dev/null +++ b/ansible/roles/base/templates/motd.j2 @@ -0,0 +1,8 @@ + __ __ __ + ____/ /___ / /_____ ___ ___ _____/ /_ + / __ / __ \/ __/ __ `__ \/ _ \/ ___/ __ \ +/ /_/ / /_/ / /_/ / / / / / __(__ ) / / / +\__,_/\____/\__/_/ /_/ /_/\___/____/_/ /_/ + +{{ inventory_hostname }} :: {{ meshcore_description }} + diff --git a/ansible/roles/meshcore_capture/handlers/main.yml b/ansible/roles/meshcore_capture/handlers/main.yml new file mode 100644 index 0000000..2d449b5 --- /dev/null +++ b/ansible/roles/meshcore_capture/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: restart meshcore-capture + systemd: + name: meshcore-capture + state: restarted + become: true diff --git a/ansible/roles/meshcore_capture/tasks/main.yml b/ansible/roles/meshcore_capture/tasks/main.yml new file mode 100644 index 0000000..f390c5c --- /dev/null +++ b/ansible/roles/meshcore_capture/tasks/main.yml @@ -0,0 +1,38 @@ +--- +- name: Run meshcore-packet-capture install script + shell: | + bash <(curl -fsSL https://raw.githubusercontent.com/agessaman/meshcore-packet-capture/main/install.sh) + args: + executable: /bin/bash + creates: /etc/systemd/system/meshcore-capture.service + become: false + +- name: Write .env.local config + template: + src: env.local.j2 + dest: "{{ ansible_env.HOME }}/.meshcore-packet-capture/.env.local" + mode: "0640" + become: false + notify: restart meshcore-capture + +- name: Enable and start meshcore-capture service + systemd: + name: meshcore-capture + enabled: true + state: started + daemon_reload: true + become: true + +- name: Deploy meshcore-capture-update script + copy: + content: "#!/usr/bin/env bash\nbash <(curl -fsSL https://raw.githubusercontent.com/agessaman/meshcore-packet-capture/main/install.sh)\n" + dest: "{{ ansible_env.HOME }}/meshcore-capture-update.sh" + mode: "0755" + become: false + +- name: Deploy meshcore-capture-logs script + copy: + content: "#!/usr/bin/env bash\nsudo journalctl -u meshcore-capture -f\n" + dest: "{{ ansible_env.HOME }}/meshcore-capture-logs.sh" + mode: "0755" + become: false diff --git a/ansible/roles/meshcore_capture/templates/env.local.j2 b/ansible/roles/meshcore_capture/templates/env.local.j2 new file mode 100644 index 0000000..b89546c --- /dev/null +++ b/ansible/roles/meshcore_capture/templates/env.local.j2 @@ -0,0 +1,77 @@ +# MeshCore Packet Capture Configuration +# Managed by Ansible - local changes will be overwritten on next playbook run + +# Update source +PACKETCAPTURE_UPDATE_REPO={{ packetcapture_update_repo }} +PACKETCAPTURE_UPDATE_BRANCH={{ packetcapture_update_branch }} + +# Connection Configuration +PACKETCAPTURE_CONNECTION_TYPE=serial +PACKETCAPTURE_SERIAL_PORTS={{ serial_port }} + +# Location Code +PACKETCAPTURE_IATA={{ packetcapture_iata }} + +# Advert Settings +PACKETCAPTURE_ADVERT_INTERVAL_HOURS={{ packetcapture_advert_interval_hours }} + +# Logging Settings +PACKETCAPTURE_LOG_LEVEL={{ packetcapture_log_level }} +PACKETCAPTURE_OWNER_PUBLIC_KEY={{ packetcapture_owner_public_key }} +PACKETCAPTURE_OWNER_EMAIL={{ packetcapture_owner_email }} + +# MQTT Broker 1 - LetsMesh.net Packet Analyzer (US) +PACKETCAPTURE_MQTT1_ENABLED=true +PACKETCAPTURE_MQTT1_SERVER=mqtt-us-v1.letsmesh.net +PACKETCAPTURE_MQTT1_PORT=443 +PACKETCAPTURE_MQTT1_TRANSPORT=websockets +PACKETCAPTURE_MQTT1_USE_TLS=true +PACKETCAPTURE_MQTT1_USE_AUTH_TOKEN=true +PACKETCAPTURE_MQTT1_TOKEN_AUDIENCE=mqtt-us-v1.letsmesh.net +PACKETCAPTURE_MQTT1_KEEPALIVE=120 +PACKETCAPTURE_MQTT1_TOPIC_STATUS=meshcore/{IATA}/{PUBLIC_KEY}/status +PACKETCAPTURE_MQTT1_TOPIC_PACKETS=meshcore/{IATA}/{PUBLIC_KEY}/packets + +# MQTT Broker 2 - LetsMesh.net Packet Analyzer (EU) +PACKETCAPTURE_MQTT2_ENABLED=true +PACKETCAPTURE_MQTT2_SERVER=mqtt-eu-v1.letsmesh.net +PACKETCAPTURE_MQTT2_PORT=443 +PACKETCAPTURE_MQTT2_TRANSPORT=websockets +PACKETCAPTURE_MQTT2_USE_TLS=true +PACKETCAPTURE_MQTT2_USE_AUTH_TOKEN=true +PACKETCAPTURE_MQTT2_TOKEN_AUDIENCE=mqtt-eu-v1.letsmesh.net +PACKETCAPTURE_MQTT2_KEEPALIVE=120 +PACKETCAPTURE_MQTT2_TOPIC_STATUS=meshcore/{IATA}/{PUBLIC_KEY}/status +PACKETCAPTURE_MQTT2_TOPIC_PACKETS=meshcore/{IATA}/{PUBLIC_KEY}/packets + +# MQTT Broker 3 - MeshRank +PACKETCAPTURE_MQTT3_ENABLED=true +PACKETCAPTURE_MQTT3_SERVER=meshrank.net +PACKETCAPTURE_MQTT3_PORT=8883 +PACKETCAPTURE_MQTT3_TRANSPORT=tcp +PACKETCAPTURE_MQTT3_USE_TLS=true +PACKETCAPTURE_MQTT3_TLS_VERIFY=true +PACKETCAPTURE_MQTT3_TOPIC_PACKETS=meshrank/uplink/{{ mqtt_meshrank_token }}/{PUBLIC_KEY}/packets +PACKETCAPTURE_MQTT3_TOPIC_STATUS=meshrank/uplink/{{ mqtt_meshrank_token }}/{PUBLIC_KEY}/status + +# MQTT Broker 4 - UKMesh +PACKETCAPTURE_MQTT4_ENABLED=true +PACKETCAPTURE_MQTT4_SERVER=mqtt.ukmesh.com +PACKETCAPTURE_MQTT4_PORT=443 +PACKETCAPTURE_MQTT4_TRANSPORT=websockets +PACKETCAPTURE_MQTT4_USE_TLS=true +PACKETCAPTURE_MQTT4_USERNAME={{ mqtt_ukmesh_username }} +PACKETCAPTURE_MQTT4_PASSWORD={{ mqtt_ukmesh_password }} +PACKETCAPTURE_MQTT4_TOPIC_STATUS=meshcore/{IATA}/{PUBLIC_KEY}/status +PACKETCAPTURE_MQTT4_TOPIC_PACKETS=meshcore/{IATA}/{PUBLIC_KEY}/packets + +# MQTT Broker 5 - MeshMapper +PACKETCAPTURE_MQTT5_ENABLED=true +PACKETCAPTURE_MQTT5_SERVER=mqtt.meshmapper.cc +PACKETCAPTURE_MQTT5_PORT=443 +PACKETCAPTURE_MQTT5_TRANSPORT=websockets +PACKETCAPTURE_MQTT5_USE_TLS=true +PACKETCAPTURE_MQTT5_USE_AUTH_TOKEN=true +PACKETCAPTURE_MQTT5_TOKEN_AUDIENCE=mqtt.meshmapper.cc +PACKETCAPTURE_MQTT5_TOPIC_STATUS=meshcore/{IATA}/{PUBLIC_KEY}/status +PACKETCAPTURE_MQTT5_TOPIC_PACKETS=meshcore/{IATA}/{PUBLIC_KEY}/packets diff --git a/ansible/roles/meshcore_cli/tasks/main.yml b/ansible/roles/meshcore_cli/tasks/main.yml new file mode 100644 index 0000000..3e42cfe --- /dev/null +++ b/ansible/roles/meshcore_cli/tasks/main.yml @@ -0,0 +1,15 @@ +--- +- name: Ensure pipx path is configured + shell: pipx ensurepath + args: + executable: /bin/bash + become: false + register: pipx_ensurepath + changed_when: "'already' not in pipx_ensurepath.stdout" + +- name: Install meshcore-cli via pipx + shell: pipx install meshcore-cli + args: + executable: /bin/bash + creates: "{{ ansible_env.HOME }}/.local/bin/meshcore-cli" + become: false diff --git a/ansible/roles/scripts/files/bandwidth.sh b/ansible/roles/scripts/files/bandwidth.sh new file mode 100644 index 0000000..f11f75c --- /dev/null +++ b/ansible/roles/scripts/files/bandwidth.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +vnstat diff --git a/ansible/roles/scripts/files/voltage.sh b/ansible/roles/scripts/files/voltage.sh new file mode 100644 index 0000000..46a6ae8 --- /dev/null +++ b/ansible/roles/scripts/files/voltage.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +SCRIPT=$(basename "$0") + +# fetch status +STATUS=$(vcgencmd get_throttled | cut -d "=" -f 2) + +# decode - https://www.raspberrypi.com/documentation/computers/os.html#get_throttled +echo "vcgencmd get_throttled ($STATUS)" +IFS="," +for BITMAP in \ + 00,"currently under-voltage" \ + 01,"ARM frequency currently capped" \ + 02,"currently throttled" \ + 03,"soft temperature limit reached" \ + 16,"under-voltage has occurred since last reboot" \ + 17,"ARM frequency capping has occurred since last reboot" \ + 18,"throttling has occurred since last reboot" \ + 19,"soft temperature reached since last reboot" +do set -- $BITMAP + if [ $(($STATUS & 1 << $1)) -ne 0 ] ; then echo " $2" ; fi +done + +echo "vcgencmd measure_volts:" +for S in core sdram_c sdram_i sdram_p ; do printf '%9s %s\n' "$S" "$(vcgencmd measure_volts $S)" ; done + +echo "Temperature: $(vcgencmd measure_temp)" diff --git a/ansible/roles/scripts/tasks/main.yml b/ansible/roles/scripts/tasks/main.yml new file mode 100644 index 0000000..ae6b639 --- /dev/null +++ b/ansible/roles/scripts/tasks/main.yml @@ -0,0 +1,14 @@ +--- +- name: Deploy voltage.sh + copy: + src: voltage.sh + dest: "{{ ansible_env.HOME }}/voltage.sh" + mode: "0755" + become: false + +- name: Deploy bandwidth.sh + copy: + src: bandwidth.sh + dest: "{{ ansible_env.HOME }}/bandwidth.sh" + mode: "0755" + become: false diff --git a/ansible/site.yml b/ansible/site.yml new file mode 100644 index 0000000..1fc1203 --- /dev/null +++ b/ansible/site.yml @@ -0,0 +1,22 @@ +--- +- name: Deploy MeshCore monitoring nodes + hosts: meshcore + vars_prompt: + - name: tailscale_auth_key + prompt: "Tailscale auth key (leave blank to skip)" + private: true + default: "" + + pre_tasks: + - name: Update apt cache and upgrade packages + apt: + update_cache: true + upgrade: dist + autoremove: true + become: true + + roles: + - base + - meshcore_cli + - meshcore_capture + - scripts