feat: add JMRI headless server role and main-street-station host

- New ansible/roles/jmri role: installs OpenJDK 21 headless, creates
  jmri service user, downloads JMRI 5.10, deploys JmriFaceless systemd unit
- Handles dialout group membership for serial device access
- Config restore task for post-reinstall recovery from GitHub backup
- host_vars/main-street-station: profile_id and serial device (TODO: fill in)
- Inventory: jmri_server group with main-street-station at 192.168.10.45
- Playbook: day1_deploy_jmri.yml (linux-baseline + jmri)
This commit is contained in:
Hermes Agent service account
2026-07-18 19:35:01 -05:00
parent bc34a1f915
commit 490c483924
8 changed files with 216 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
---
# Host-specific vars for main-street-station (JMRI headless server)
# LCRR - Lake Country Railroad, Milwaukee Road Oct 1956, HO scale
# JMRI profile ID — find with: ls ~/.jmri/profiles/ on the old box
# Format: <name>.<8-char-hex> e.g. LCRR.3d3f1dfc
# TODO: fill in after restoring config from GitHub backup
jmri_profile_id: ""
# USB serial device for NCE command station
# Verify after install: ls -la /dev/ttyUSB* /dev/ttyACM*
jmri_serial_device: /dev/ttyUSB0
# Path to JMRI config backup for restore task (leave empty to skip)
# Point at a local checkout of the LCRR GitHub repo
jmri_config_src: ""

View File

@@ -80,6 +80,13 @@ honcho_server:
ansible_user: wed ansible_user: wed
ansible_become: true ansible_become: true
jmri_server:
hosts:
main-street-station:
ansible_host: 192.168.10.45
ansible_user: wed
ansible_become: true
papermc_server: papermc_server:
# ansible-galaxy role install engonzal.papermc # ansible-galaxy role install engonzal.papermc
hosts: hosts:

View File

@@ -0,0 +1,24 @@
---
# ============================================================================
# day1_deploy_jmri.yml
# ----------------------------------------------------------------------------
# Deploys JMRI JmriFaceless headless server on main-street-station.
# Applies linux-baseline first, then the jmri role.
#
# Usage:
# ansible-playbook playbooks/day1_deploy_jmri.yml
# ansible-playbook playbooks/day1_deploy_jmri.yml -e target=main-street-station
#
# Prerequisites:
# 1. Host is in inventory under jmri_server group
# 2. jmri_profile_id is set in host_vars/main-street-station.yml
# 3. SSH access as 'wed' with sudo
# ============================================================================
- name: Deploy JMRI headless server
hosts: "{{ target | default('jmri_server') }}"
become: true
gather_facts: true
roles:
- linux-baseline
- jmri

View File

@@ -0,0 +1,25 @@
---
# JMRI version to install
jmri_version: "5.10"
jmri_install_dir: /opt/JMRI
jmri_download_url: "https://github.com/JMRI/JMRI/releases/download/v{{ jmri_version }}/JMRI.{{ jmri_version }}+Rca461bd266.tgz"
# Service user
jmri_user: jmri
jmri_group: jmri
jmri_home: /home/jmri
# Profile — override per-host in inventory
# Find on the old box: ls ~/.jmri/profiles/
jmri_profile_id: ""
# Config restore source (path to the backed-up .jmri directory)
jmri_config_src: ""
# Ports (for documentation / firewall rules)
jmri_json_port: 12080
jmri_withrottle_port: 12090
# USB serial device for NCE (set per-host in inventory)
# e.g. /dev/ttyUSB0 or /dev/ttyACM0
jmri_serial_device: /dev/ttyUSB0

View File

@@ -0,0 +1,9 @@
---
- name: Reload systemd
ansible.builtin.systemd:
daemon_reload: true
- name: Restart jmri
ansible.builtin.systemd:
name: jmri
state: restarted

View File

@@ -0,0 +1,13 @@
---
galaxy_info:
role_name: jmri
author: JARVIS
description: Deploy JMRI JmriFaceless headless server as a systemd service
license: MIT
min_ansible_version: "2.12"
platforms:
- name: Ubuntu
versions:
- jammy
- noble
dependencies: []

View File

@@ -0,0 +1,87 @@
---
- name: Install Java runtime (headless)
ansible.builtin.apt:
name: openjdk-21-jre-headless
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: /usr/sbin/nologin
system: true
create_home: true
state: present
- name: Add JMRI user to dialout group (serial device access)
ansible.builtin.user:
name: "{{ jmri_user }}"
groups: dialout
append: true
- name: Check if JMRI is already installed at correct version
ansible.builtin.stat:
path: "{{ jmri_install_dir }}/JmriFaceless"
register: jmri_binary
- name: Download JMRI release archive
ansible.builtin.get_url:
url: "{{ jmri_download_url }}"
dest: /tmp/JMRI-{{ jmri_version }}.tgz
mode: '0644'
when: not jmri_binary.stat.exists
- 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: not jmri_binary.stat.exists
- 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 and start JMRI service
ansible.builtin.systemd:
name: jmri
enabled: true
state: started
daemon_reload: true

View File

@@ -0,0 +1,35 @@
[Unit]
Description=JMRI Server (JmriFaceless)
Documentation=https://www.jmri.org/
After=network.target
# Give USB devices time to enumerate after boot
After=dev-{{ jmri_serial_device | basename }}.device
Wants=dev-{{ jmri_serial_device | basename }}.device
[Service]
Type=simple
User={{ jmri_user }}
Group={{ jmri_group }}
WorkingDirectory={{ jmri_install_dir }}
# JmriFaceless requires a profile ID
# Find yours: ls ~/.jmri/profiles/
ExecStart={{ jmri_install_dir }}/JmriFaceless --profile={{ jmri_profile_id }}
# Restart on failure, not on clean exit (e.g. intentional shutdown from Jython)
Restart=on-failure
RestartSec=10
# Give hardware time to settle before retry
TimeoutStartSec=30
# Logging — view with: journalctl -u jmri -f
StandardOutput=journal
StandardError=journal
SyslogIdentifier=jmri
# Serial device access
SupplementaryGroups=dialout
[Install]
WantedBy=multi-user.target