Migration from Ubuntu 3.x to upstream 6.x is complete. The explicit removal task was firing changed on every run. state: latest on the install task handles upgrades going forward.
389 lines
11 KiB
YAML
389 lines
11 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: Deploy extra SSH authorized keys for jmri user
|
|
ansible.posix.authorized_key:
|
|
user: "{{ jmri_user }}"
|
|
key: "{{ item }}"
|
|
state: present
|
|
loop: "{{ jmri_ssh_authorized_keys_extra }}"
|
|
|
|
- name: Add JMRI user to dialout group (serial device access)
|
|
ansible.builtin.user:
|
|
name: "{{ jmri_user }}"
|
|
groups: dialout
|
|
append: true
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Phase 1 — Stable USB device symlinks
|
|
# Creates /dev/jmri/loconet and /dev/jmri/nce via udev ID_SERIAL matching.
|
|
# ---------------------------------------------------------------------------
|
|
- name: Deploy udev rules for JMRI USB devices
|
|
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
|
|
- Trigger 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 (retained for fallback; VNC preferred)
|
|
# ---------------------------------------------------------------------------
|
|
- name: Install xauth (required for SSH X11 forwarding fallback)
|
|
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
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Phase 4 — Xpra virtual display
|
|
# Replaces TigerVNC. Rootless mode: each JMRI window appears as a native
|
|
# window on the client. Sessions are persistent across disconnects.
|
|
# Connect: xpra attach ssh://jmri@main-street-station/{{ jmri_xpra_display }}
|
|
# ---------------------------------------------------------------------------
|
|
- name: Remove TigerVNC (replaced by Xpra)
|
|
ansible.builtin.apt:
|
|
name: tigervnc-standalone-server
|
|
state: absent
|
|
notify: Reload systemd
|
|
|
|
- name: Disable and stop jmri-vnc service if present
|
|
ansible.builtin.systemd:
|
|
name: jmri-vnc
|
|
enabled: false
|
|
state: stopped
|
|
failed_when: false
|
|
|
|
- name: Remove jmri-vnc systemd unit if present
|
|
ansible.builtin.file:
|
|
path: /etc/systemd/system/jmri-vnc.service
|
|
state: absent
|
|
notify: Reload systemd
|
|
|
|
- name: Remove jmri VNC password directory if present
|
|
ansible.builtin.file:
|
|
path: "{{ jmri_home }}/.vnc"
|
|
state: absent
|
|
|
|
- name: Install xpra.org apt signing key
|
|
ansible.builtin.get_url:
|
|
url: https://xpra.org/gpg.asc
|
|
dest: /usr/share/keyrings/xpra.asc
|
|
mode: '0644'
|
|
|
|
- name: Add xpra.org upstream apt repository
|
|
ansible.builtin.copy:
|
|
dest: /etc/apt/sources.list.d/xpra.list
|
|
owner: root
|
|
group: root
|
|
mode: '0644'
|
|
content: |
|
|
deb [arch=amd64 signed-by=/usr/share/keyrings/xpra.asc] https://xpra.org/ noble main
|
|
|
|
- name: Install Xpra from upstream repo (v6.x)
|
|
ansible.builtin.apt:
|
|
name: xpra
|
|
state: latest
|
|
update_cache: true
|
|
|
|
- name: Deploy jmri-xpra systemd unit
|
|
ansible.builtin.template:
|
|
src: jmri-xpra.service.j2
|
|
dest: /etc/systemd/system/jmri-xpra.service
|
|
owner: root
|
|
group: root
|
|
mode: '0644'
|
|
notify:
|
|
- Reload systemd
|
|
- Restart jmri-xpra
|
|
|
|
- name: Enable and start jmri-xpra service
|
|
ansible.builtin.systemd:
|
|
name: jmri-xpra
|
|
enabled: true
|
|
state: started
|
|
daemon_reload: true
|
|
|
|
- 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
|