jmri: replace TigerVNC with Xpra for JMRI GUI display
VNC had window management issues and some dialogs wouldn't open correctly. Xpra runs in rootless mode — each JMRI window appears natively on the client without a VNC client or XQuartz required. Changes: - Remove tigervnc-standalone-server, jmri-vnc.service, .vnc/ directory - Install xpra, deploy jmri-xpra.service.j2 (systemd unit) - Update jmri-gui wrapper: DISPLAY=:100, attach instructions printed on launch - Update defaults: drop VNC vars, add jmri_xpra_display=100 - Rename handler: Restart jmri-vnc -> Restart jmri-xpra Connect from macOS/Linux: xpra attach ssh://jmri@main-street-station/100
This commit is contained in:
@@ -34,15 +34,12 @@ jmri_json_port: 12080
|
|||||||
jmri_withrottle_port: 12090
|
jmri_withrottle_port: 12090
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Phase 4 — VNC server (TigerVNC)
|
# Phase 4 — Xpra virtual display
|
||||||
# Replaces X11 forwarding with a persistent virtual display.
|
# Replaces TigerVNC with rootless Xpra — proper window management,
|
||||||
# Connect with any VNC client to <host>:5901
|
# persistent sessions, SSH-native attach (no VNC client needed).
|
||||||
|
# Connect from macOS/Linux: xpra attach ssh://jmri@main-street-station/100
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
jmri_vnc_display: "1"
|
jmri_xpra_display: "100"
|
||||||
jmri_vnc_port: 5901
|
|
||||||
jmri_vnc_geometry: "1280x800"
|
|
||||||
jmri_vnc_depth: "24"
|
|
||||||
jmri_vnc_password: "" # set via group_vars (vault-backed)
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Phase 2 — Layout power monitor (Leviton Decora Smart Wi-Fi)
|
# Phase 2 — Layout power monitor (Leviton Decora Smart Wi-Fi)
|
||||||
|
|||||||
@@ -17,9 +17,9 @@
|
|||||||
name: jmri
|
name: jmri
|
||||||
state: restarted
|
state: restarted
|
||||||
|
|
||||||
- name: Restart jmri-vnc
|
- name: Restart jmri-xpra
|
||||||
ansible.builtin.systemd:
|
ansible.builtin.systemd:
|
||||||
name: jmri-vnc
|
name: jmri-xpra
|
||||||
state: restarted
|
state: restarted
|
||||||
|
|
||||||
- name: Restart sshd
|
- name: Restart sshd
|
||||||
|
|||||||
@@ -272,51 +272,55 @@
|
|||||||
notify: Restart sshd
|
notify: Restart sshd
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Phase 4 — VNC server (TigerVNC)
|
# Phase 4 — Xpra virtual display
|
||||||
# Persistent virtual display on :1 (port 5901). Replaces X11 forwarding.
|
# 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: Install TigerVNC server
|
- name: Remove TigerVNC (replaced by Xpra)
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
name: tigervnc-standalone-server
|
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
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: xpra
|
||||||
state: present
|
state: present
|
||||||
update_cache: false
|
update_cache: false
|
||||||
|
|
||||||
- name: Create .vnc directory for jmri user
|
- name: Deploy jmri-xpra systemd unit
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ jmri_home }}/.vnc"
|
|
||||||
state: directory
|
|
||||||
owner: "{{ jmri_user }}"
|
|
||||||
group: "{{ jmri_group }}"
|
|
||||||
mode: '0700'
|
|
||||||
|
|
||||||
- name: Set VNC password for jmri user
|
|
||||||
ansible.builtin.shell:
|
|
||||||
cmd: "printf '{{ jmri_vnc_password }}' | vncpasswd -f > {{ jmri_home }}/.vnc/passwd"
|
|
||||||
become_user: "{{ jmri_user }}"
|
|
||||||
no_log: true
|
|
||||||
changed_when: true
|
|
||||||
|
|
||||||
- name: Set correct permissions on VNC password file
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ jmri_home }}/.vnc/passwd"
|
|
||||||
owner: "{{ jmri_user }}"
|
|
||||||
group: "{{ jmri_group }}"
|
|
||||||
mode: '0600'
|
|
||||||
|
|
||||||
- name: Deploy jmri-vnc systemd unit
|
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: jmri-vnc.service.j2
|
src: jmri-xpra.service.j2
|
||||||
dest: /etc/systemd/system/jmri-vnc.service
|
dest: /etc/systemd/system/jmri-xpra.service
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: '0644'
|
mode: '0644'
|
||||||
notify:
|
notify:
|
||||||
- Reload systemd
|
- Reload systemd
|
||||||
- Restart jmri-vnc
|
- Restart jmri-xpra
|
||||||
|
|
||||||
- name: Enable and start jmri-vnc service
|
- name: Enable and start jmri-xpra service
|
||||||
ansible.builtin.systemd:
|
ansible.builtin.systemd:
|
||||||
name: jmri-vnc
|
name: jmri-xpra
|
||||||
enabled: true
|
enabled: true
|
||||||
state: started
|
state: started
|
||||||
daemon_reload: true
|
daemon_reload: true
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# jmri-gui — launch JMRI GUI on VNC virtual display
|
# jmri-gui — launch JMRI GUI on Xpra virtual display
|
||||||
# Managed by Ansible — do not edit manually.
|
# Managed by Ansible — do not edit manually.
|
||||||
#
|
#
|
||||||
# Usage (connect as jmri user):
|
# Usage (connect as jmri user):
|
||||||
# ssh jmri@main-street-station jmri-gui panelpro
|
# jmri-gui panelpro Launch PanelPro on Xpra display
|
||||||
# ssh jmri@main-street-station jmri-gui decoderpro
|
# jmri-gui decoderpro Launch DecoderPro on Xpra display
|
||||||
# ssh jmri@main-street-station jmri-gui status
|
# jmri-gui status Show service status and attach command
|
||||||
#
|
#
|
||||||
# Then connect a VNC client to main-street-station.local.mk-labs.cloud:5901
|
# Then attach from macOS/Linux:
|
||||||
# VNC password is in the Ansible vault (vault_jmri_vnc_password)
|
# xpra attach ssh://jmri@main-street-station/{{ jmri_xpra_display }}
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
JMRI_DIR="{{ jmri_install_dir }}"
|
JMRI_DIR="{{ jmri_install_dir }}"
|
||||||
JMRI_SERVICE="jmri.service"
|
JMRI_SERVICE="jmri.service"
|
||||||
MONITOR_SERVICE="jmri-monitor.service"
|
MONITOR_SERVICE="jmri-monitor.service"
|
||||||
VNC_SERVICE="jmri-vnc.service"
|
XPRA_SERVICE="jmri-xpra.service"
|
||||||
DISPLAY=":{{ jmri_vnc_display }}"
|
DISPLAY=":{{ jmri_xpra_display }}"
|
||||||
export DISPLAY
|
export DISPLAY
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
@@ -28,13 +28,13 @@ status() {
|
|||||||
echo "=== JMRI daemon ==="
|
echo "=== JMRI daemon ==="
|
||||||
systemctl status "$JMRI_SERVICE" --no-pager -l 2>&1 | head -8
|
systemctl status "$JMRI_SERVICE" --no-pager -l 2>&1 | head -8
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== VNC server ==="
|
echo "=== Xpra display ==="
|
||||||
systemctl status "$VNC_SERVICE" --no-pager -l 2>&1 | head -5
|
systemctl status "$XPRA_SERVICE" --no-pager -l 2>&1 | head -5
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Layout monitor ==="
|
echo "=== Layout monitor ==="
|
||||||
systemctl status "$MONITOR_SERVICE" --no-pager -l 2>&1 | head -5
|
systemctl status "$MONITOR_SERVICE" --no-pager -l 2>&1 | head -5
|
||||||
echo ""
|
echo ""
|
||||||
echo "Connect VNC client to: $(hostname -f):{{ jmri_vnc_port }}"
|
echo "To attach: xpra attach ssh://jmri@$(hostname -f)/{{ jmri_xpra_display }}"
|
||||||
}
|
}
|
||||||
|
|
||||||
launch() {
|
launch() {
|
||||||
@@ -47,32 +47,31 @@ launch() {
|
|||||||
*) usage ;;
|
*) usage ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Ensure VNC server is running
|
# Ensure Xpra display is running
|
||||||
if ! systemctl is-active --quiet "$VNC_SERVICE" 2>/dev/null; then
|
if ! systemctl is-active --quiet "$XPRA_SERVICE" 2>/dev/null; then
|
||||||
echo "Starting VNC server..."
|
echo "Starting Xpra display..."
|
||||||
sudo systemctl start "$VNC_SERVICE"
|
sudo systemctl start "$XPRA_SERVICE"
|
||||||
sleep 2
|
sleep 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Stop the JMRI daemon if running
|
# Stop the JMRI daemon if running (we're taking over the hardware connections)
|
||||||
if systemctl is-active --quiet "$JMRI_SERVICE" 2>/dev/null; then
|
if systemctl is-active --quiet "$JMRI_SERVICE" 2>/dev/null; then
|
||||||
echo "Stopping JMRI daemon..."
|
echo "Stopping JMRI daemon..."
|
||||||
sudo systemctl stop "$JMRI_SERVICE"
|
sudo systemctl stop "$JMRI_SERVICE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Launching $binary on VNC display $DISPLAY..."
|
|
||||||
echo "Connect your VNC client to: $(hostname -f):{{ jmri_vnc_port }}"
|
|
||||||
echo "VNC password: see Ansible vault (vault_jmri_vnc_password)"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Force AWT out of headless mode
|
# Force AWT out of headless mode
|
||||||
export JMRI_OPTIONS="-Djava.awt.headless=false"
|
export JMRI_OPTIONS="-Djava.awt.headless=false"
|
||||||
|
|
||||||
# Launch on the VNC display — no --profile so JMRI uses last-session preference
|
echo "Launching $binary on Xpra display $DISPLAY..."
|
||||||
|
echo ""
|
||||||
|
echo "Attach from your workstation:"
|
||||||
|
echo " xpra attach ssh://jmri@$(hostname -f)/{{ jmri_xpra_display }}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
"$JMRI_DIR/$binary" &
|
"$JMRI_DIR/$binary" &
|
||||||
|
|
||||||
echo "$binary launched in background on VNC display."
|
echo "$binary launched. Attach with xpra to see windows."
|
||||||
echo "Close it from the VNC client, then run 'jmri-gui status' to check state."
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Restart daemon if layout switch is still on
|
# Restart daemon if layout switch is still on
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=JMRI VNC virtual display (:{{ jmri_vnc_display }})
|
|
||||||
After=network.target
|
|
||||||
# Start before jmri.service so the display is ready when JMRI launches
|
|
||||||
Before=jmri.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User={{ jmri_user }}
|
|
||||||
Group={{ jmri_group }}
|
|
||||||
ExecStart=/usr/bin/Xtigervnc \
|
|
||||||
:{{ jmri_vnc_display }} \
|
|
||||||
-rfbport {{ jmri_vnc_port }} \
|
|
||||||
-geometry {{ jmri_vnc_geometry }} \
|
|
||||||
-depth {{ jmri_vnc_depth }} \
|
|
||||||
-SecurityTypes VncAuth \
|
|
||||||
-PasswordFile {{ jmri_home }}/.vnc/passwd \
|
|
||||||
-localhost no \
|
|
||||||
-AlwaysShared \
|
|
||||||
-NeverShared no \
|
|
||||||
-AcceptKeyEvents \
|
|
||||||
-AcceptPointerEvents \
|
|
||||||
-desktop "JMRI"
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=5
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
26
ansible/roles/jmri/templates/jmri-xpra.service.j2
Normal file
26
ansible/roles/jmri/templates/jmri-xpra.service.j2
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Xpra virtual display for JMRI (:{{ jmri_xpra_display }})
|
||||||
|
After=network.target
|
||||||
|
# Start before jmri.service so the display is ready when JMRI launches
|
||||||
|
Before=jmri.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User={{ jmri_user }}
|
||||||
|
Group={{ jmri_group }}
|
||||||
|
ExecStart=/usr/bin/xpra start \
|
||||||
|
:{{ jmri_xpra_display }} \
|
||||||
|
--daemon=no \
|
||||||
|
--mdns=no \
|
||||||
|
--notifications=no \
|
||||||
|
--systemd-run=no \
|
||||||
|
--pulseaudio=no \
|
||||||
|
--speaker=off \
|
||||||
|
--microphone=off \
|
||||||
|
--video-encoders=none \
|
||||||
|
--start-via-proxy=no
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Reference in New Issue
Block a user