Files
homelab/ansible/roles/jmri/tasks/main.yml
2026-07-29 21:50:46 -05:00

318 lines
9.4 KiB
YAML

---
- name: Install Java runtime (full — required for GUI mode)
ansible.builtin.apt:
name:
- openjdk-21-jre
- openjdk-21-jdk
state: present
update_cache: true
- name: Create JMRI system group
ansible.builtin.group:
name: "{{ jmri_group }}"
state: present
system: true
- name: Create JMRI service user
ansible.builtin.user:
name: "{{ jmri_user }}"
group: "{{ jmri_group }}"
home: "{{ jmri_home }}"
shell: /bin/bash
system: true
create_home: true
state: present
- name: Deploy SSH authorized key for jmri user
ansible.posix.authorized_key:
user: "{{ jmri_user }}"
key: "{{ jmri_ssh_authorized_key }}"
state: present
when: jmri_ssh_authorized_key | length > 0
- name: Add JMRI user to dialout group (serial device access)
ansible.builtin.user:
name: "{{ jmri_user }}"
groups: dialout
append: true
# ---------------------------------------------------------------------------
# Phase 1 — Stable udev device symlinks
# Creates /dev/jmri/nce, /dev/jmri/loconet, /dev/jmri/lcc
# ---------------------------------------------------------------------------
- name: Create /dev/jmri symlink directory (static — survives reboots)
ansible.builtin.file:
path: /etc/jmri
state: directory
owner: root
group: root
mode: '0755'
- name: Deploy udev rules for stable JMRI device symlinks
ansible.builtin.template:
src: 99-jmri-devices.rules.j2
dest: /etc/udev/rules.d/99-jmri-devices.rules
owner: root
group: root
mode: '0644'
notify: Reload udev
# ---------------------------------------------------------------------------
# Phase 2 — LocoNet traffic monitor
# Installs jmri-monitor: watches /dev/jmri/loconet, starts/stops jmri.service
# ---------------------------------------------------------------------------
- name: Install python3-venv (monitor virtualenv support)
ansible.builtin.apt:
name: python3-venv
state: present
- name: Create virtualenv for jmri-monitor
ansible.builtin.command:
cmd: python3 -m venv /opt/jmri-monitor
creates: /opt/jmri-monitor/bin/python3
- name: Install decora_wifi into jmri-monitor virtualenv
ansible.builtin.pip:
name: decora_wifi
state: present
virtualenv: /opt/jmri-monitor
- name: Deploy jmri-monitor script
ansible.builtin.template:
src: jmri-monitor.py.j2
dest: /usr/local/bin/jmri-monitor
owner: root
group: root
mode: '0755'
notify: Restart jmri-monitor
- name: Deploy jmri-monitor systemd unit
ansible.builtin.template:
src: jmri-monitor.service.j2
dest: /etc/systemd/system/jmri-monitor.service
owner: root
group: root
mode: '0644'
notify:
- Reload systemd
- Restart jmri-monitor
- name: Enable jmri-monitor service
ansible.builtin.systemd:
name: jmri-monitor
enabled: true
state: started
daemon_reload: true
# ---------------------------------------------------------------------------
# Phase 2b — LCRR config repo (clone/pull .jmri from Gitea)
# ---------------------------------------------------------------------------
- name: Ensure jmri .ssh directory exists
ansible.builtin.file:
path: /home/jmri/.ssh
state: directory
owner: "{{ jmri_user }}"
group: "{{ jmri_group }}"
mode: '0700'
- name: Deploy jmri SSH config for Gitea
ansible.builtin.copy:
dest: /home/jmri/.ssh/config
owner: "{{ jmri_user }}"
group: "{{ jmri_group }}"
mode: '0600'
content: |
Host gitea.mk-labs.cloud
HostName gitea.mk-labs.cloud
User git
Port 2221
IdentityFile {{ jmri_gitea_key }}
StrictHostKeyChecking accept-new
- name: Clone LCRR config repo if not present
ansible.builtin.git:
repo: "{{ jmri_lcrr_repo }}"
dest: "{{ jmri_home }}/LCRR"
version: "{{ jmri_lcrr_branch }}"
accept_hostkey: true
key_file: "{{ jmri_gitea_key }}"
update: false
become_user: "{{ jmri_user }}"
when: jmri_lcrr_repo | length > 0
notify: Restart jmri
- name: Remove auto-generated .jmri dir if it exists (will be replaced by symlink)
ansible.builtin.file:
path: "{{ jmri_home }}/.jmri"
state: absent
when:
- jmri_lcrr_repo | length > 0
- name: Link .jmri config from LCRR repo
ansible.builtin.file:
src: "{{ jmri_home }}/LCRR/.jmri"
dest: "{{ jmri_home }}/.jmri"
state: link
owner: "{{ jmri_user }}"
group: "{{ jmri_group }}"
force: true
when: jmri_lcrr_repo | length > 0
notify: Restart jmri
# ---------------------------------------------------------------------------
# JMRI install / upgrade — version-marker pattern
# Writes {{ jmri_install_dir }}/.jmri_installed_version after each install.
# On subsequent runs: read the marker, skip everything if it matches
# jmri_version. If it differs (or is absent), stop JMRI cleanly, wipe the
# old install, download the new archive, extract, and write the new marker.
# To upgrade: bump jmri_version + jmri_build_hash in defaults/main.yml (or
# host_vars) and re-run the playbook.
# ---------------------------------------------------------------------------
- name: Read installed JMRI version marker (if present)
ansible.builtin.slurp:
src: "{{ jmri_install_dir }}/.jmri_installed_version"
register: jmri_version_marker
ignore_errors: true
- name: Determine whether JMRI install/upgrade is needed
ansible.builtin.set_fact:
jmri_needs_install: >-
{{
jmri_version_marker is failed or
(jmri_version_marker.content | b64decode | trim) != jmri_version
}}
- name: Stop JMRI service before upgrade (if running)
ansible.builtin.systemd:
name: jmri
state: stopped
failed_when: false # service may not exist yet on first install
when: jmri_needs_install
- name: Remove existing JMRI install directory (upgrade path)
ansible.builtin.file:
path: "{{ jmri_install_dir }}"
state: absent
when: jmri_needs_install
- name: Remove stale JMRI archive from /tmp (if version changed)
ansible.builtin.file:
path: "/tmp/JMRI-{{ jmri_version }}.tgz"
state: absent
when: jmri_needs_install
- name: Download JMRI release archive
ansible.builtin.get_url:
url: "{{ jmri_download_url }}"
dest: /tmp/JMRI-{{ jmri_version }}.tgz
mode: '0644'
when: jmri_needs_install
- name: Create JMRI install directory
ansible.builtin.file:
path: "{{ jmri_install_dir }}"
state: directory
owner: "{{ jmri_user }}"
group: "{{ jmri_group }}"
mode: '0755'
- name: Extract JMRI archive
ansible.builtin.unarchive:
src: /tmp/JMRI-{{ jmri_version }}.tgz
dest: "{{ jmri_install_dir }}"
remote_src: true
owner: "{{ jmri_user }}"
group: "{{ jmri_group }}"
extra_opts: ['--strip-components=1']
when: jmri_needs_install
- name: Write installed version marker
ansible.builtin.copy:
content: "{{ jmri_version }}\n"
dest: "{{ jmri_install_dir }}/.jmri_installed_version"
owner: "{{ jmri_user }}"
group: "{{ jmri_group }}"
mode: '0644'
when: jmri_needs_install
- name: Restore JMRI config from backup
ansible.builtin.copy:
src: "{{ jmri_config_src }}/"
dest: "{{ jmri_home }}/.jmri/"
owner: "{{ jmri_user }}"
group: "{{ jmri_group }}"
mode: '0644'
directory_mode: '0755'
when: jmri_config_src | length > 0
notify: Restart jmri
- name: Deploy JmriFaceless systemd unit
ansible.builtin.template:
src: jmri.service.j2
dest: /etc/systemd/system/jmri.service
owner: root
group: root
mode: '0644'
notify:
- Reload systemd
- Restart jmri
- name: Enable jmri service (monitor manages start/stop — do not start directly)
ansible.builtin.systemd:
name: jmri
enabled: false
daemon_reload: true
# ---------------------------------------------------------------------------
# Phase 3 — X11 remote GUI access
# ---------------------------------------------------------------------------
- name: Install xauth (required for SSH X11 forwarding)
ansible.builtin.apt:
name: xauth
state: present
- name: Remove old jmri X11 sshd drop-in if present (renamed)
ansible.builtin.file:
path: /etc/ssh/sshd_config.d/20-jmri-x11.conf
state: absent
notify: Restart sshd
- name: Deploy sshd drop-in to enable X11 forwarding (must load before hardening)
ansible.builtin.copy:
dest: /etc/ssh/sshd_config.d/09-jmri-x11.conf
owner: root
group: root
mode: '0644'
content: |
# Allow X11 forwarding for JMRI GUI sessions (jmri role)
# Must be numbered below 10-mk-labs-hardening.conf — first match wins.
X11Forwarding yes
X11UseLocalhost yes
notify: Restart sshd
- name: Deploy jmri-gui script
ansible.builtin.template:
src: jmri-gui.j2
dest: /usr/local/bin/jmri-gui
owner: root
group: root
mode: '0755'
- name: Deploy sudoers drop-in for jmri-gui (wed can manage jmri service)
ansible.builtin.copy:
dest: /etc/sudoers.d/jmri-gui
owner: root
group: root
mode: '0440'
validate: 'visudo -cf %s'
content: |
# jmri user can manage its own service (for jmri-gui interactive sessions)
jmri ALL=(root) NOPASSWD: /usr/bin/systemctl start jmri.service
jmri ALL=(root) NOPASSWD: /usr/bin/systemctl stop jmri.service
jmri ALL=(root) NOPASSWD: /opt/jmri-monitor/bin/python3
# wed retains service control for automation/admin use
wed ALL=(root) NOPASSWD: /usr/bin/systemctl start jmri.service
wed ALL=(root) NOPASSWD: /usr/bin/systemctl stop jmri.service
wed ALL=(root) NOPASSWD: /opt/jmri-monitor/bin/python3