feat(setup): persist ci-runner X grant for ci vmware GUI

`ci vmware` runs as ci-runner and relies on host-based X auth (xhost),
but the grant is per-session and wiped on logout / X restart, so the GUI
launch fails with "cannot open X display" after every desktop relogin.

Add scripts/install-vmware-gui-access.sh: installs an XDG autostart entry
in the desktop user's home that re-applies `xhost +SI:localuser:ci-runner`
at each graphical login, and applies it immediately when a display is
reachable. Desktop user resolves from --desktop-user / $CI_DESKTOP_USER /
$SUDO_USER (never root); service user from $CI_RUN_USER. --uninstall
removes the autostart entry and revokes the live grant.

Wire it into setup-host-linux.sh as Step 8, with --desktop-user (default
$SUDO_USER) and --skip-gui-access for headless hosts.
This commit is contained in:
2026-06-08 21:11:07 +02:00
parent de5348c36c
commit 3f43b1ca12
2 changed files with 137 additions and 10 deletions
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env bash
# install-vmware-gui-access.sh — authorise the ci-runner service user on the
# desktop operator's X session so `ci vmware` can open the VMware Workstation
# GUI (the VM files are owned by ci-runner, mode 750, so the GUI must run as
# that user — see commands/vm.py:vmware).
#
# `ci vmware` runs as ci-runner and relies on host-based X auth (xhost). An
# xhost grant is runtime + per-session: it is wiped on logout / X restart. This
# script makes it persistent by installing a per-user XDG autostart entry in
# the *desktop* user's home that re-runs the grant at every graphical login. It
# also applies the grant immediately when a display is reachable, so no logout
# is needed the first time.
#
# Usage:
# ./scripts/install-vmware-gui-access.sh # desktop user = $SUDO_USER or $USER
# ./scripts/install-vmware-gui-access.sh --desktop-user simone
# ./scripts/install-vmware-gui-access.sh --uninstall
#
# Env:
# CI_RUN_USER service user to authorise (default: ci-runner)
# CI_DESKTOP_USER desktop user whose X session is granted (overridden by --desktop-user)
#
# Idempotent: re-running overwrites the autostart entry in place.
#
# Security note: `xhost +SI:localuser:<user>` grants that local user full
# access to the desktop X session (input injection, screen capture). Acceptable
# on a single-operator CI workstation. `SI:localuser:` is same-machine only,
# never network. Remove with --uninstall when no longer needed.
set -euo pipefail
RUN_USER="${CI_RUN_USER:-ci-runner}"
DESKTOP_USER="${CI_DESKTOP_USER:-}"
ACTION="install"
log() { printf '[install-vmware-gui-access] %s\n' "$*"; }
die() { printf '[install-vmware-gui-access] ERROR: %s\n' "$*" >&2; exit 1; }
while [ $# -gt 0 ]; do
case "$1" in
--desktop-user) DESKTOP_USER="${2:-}"; shift 2 ;;
--uninstall) ACTION="uninstall"; shift ;;
-h|--help) sed -n '2,30p' "$0"; exit 0 ;;
*) die "unknown argument: $1 (use --desktop-user <name> or --uninstall)" ;;
esac
done
# Resolve the desktop user: explicit flag/env, else the user behind sudo, else
# the current user. Never default to root — root has no graphical session.
if [ -z "$DESKTOP_USER" ]; then
DESKTOP_USER="${SUDO_USER:-$USER}"
fi
[ -z "$DESKTOP_USER" ] && die "could not determine desktop user (pass --desktop-user <name>)"
[ "$DESKTOP_USER" = "root" ] && die "desktop user resolved to root; pass --desktop-user <name>"
id "$DESKTOP_USER" &>/dev/null || die "desktop user '$DESKTOP_USER' does not exist"
id "$RUN_USER" &>/dev/null || log "WARNING: service user '$RUN_USER' does not exist yet — grant installed anyway."
HOME_DIR="$(getent passwd "$DESKTOP_USER" | cut -d: -f6)"
[ -n "$HOME_DIR" ] && [ -d "$HOME_DIR" ] || die "home directory for '$DESKTOP_USER' not found"
AUTOSTART_DIR="$HOME_DIR/.config/autostart"
DESKTOP_FILE="$AUTOSTART_DIR/ci-runner-xhost.desktop"
GRANT="SI:localuser:$RUN_USER"
# Run a command as the desktop user, preserving ownership of created files.
as_desktop() { sudo -u "$DESKTOP_USER" "$@"; }
if [ "$ACTION" = "uninstall" ]; then
as_desktop rm -f "$DESKTOP_FILE" && log "removed $DESKTOP_FILE"
# Best-effort: revoke the live grant too (ignore failure when no display).
as_desktop xhost "-$GRANT" >/dev/null 2>&1 && log "revoked live grant ($GRANT)" || true
log "done."
exit 0
fi
# Install the autostart entry, owned by the desktop user.
as_desktop mkdir -p "$AUTOSTART_DIR"
as_desktop tee "$DESKTOP_FILE" >/dev/null <<EOF
[Desktop Entry]
Type=Application
Name=Authorise $RUN_USER on X (CI vmware GUI)
Comment=Grant the $RUN_USER service user access to this X session so 'ci vmware' can open the VMware Workstation GUI
Exec=xhost +$GRANT
X-GNOME-Autostart-enabled=true
NoDisplay=true
EOF
as_desktop chmod 0644 "$DESKTOP_FILE"
log "installed $DESKTOP_FILE (re-applies at each graphical login)"
# Apply immediately if a display is reachable now, so no logout is needed.
DESKTOP_DISPLAY="${DISPLAY:-:0}"
if as_desktop env DISPLAY="$DESKTOP_DISPLAY" xhost "+$GRANT" >/dev/null 2>&1; then
log "applied now on display $DESKTOP_DISPLAY ($GRANT)"
else
log "could not apply now (no reachable display) — takes effect at next login of '$DESKTOP_USER'"
fi
log "done. Test with: ci vmware"
+39 -10
View File
@@ -14,6 +14,8 @@
# 5. (Opzionale) Clona il repo e installa il package nel venv
# 6. (Opt-in, --with-pwsh) Installa PowerShell Core
# 7. Installa l'alias shell `ci` a livello di sistema (/etc/profile.d/)
# 8. Autorizza ci-runner sulla sessione X del desktop user (xhost persistente)
# così `ci vmware` può aprire la GUI di VMware Workstation.
# L'host Linux NON ha più bisogno di pwsh per il normale funzionamento
# (orchestratore Python). Installalo solo se ti serve per script legacy.
@@ -29,6 +31,8 @@ REPO_URL="https://gitea.emulab.it/Simone/local-ci-cd-system.git"
SKIP_PYTHON=false
WITH_PWSH=false
SKIP_CLONE=false
DESKTOP_USER="${SUDO_USER:-}"
SKIP_GUI_ACCESS=false
# ── Colori ───────────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
@@ -53,11 +57,17 @@ Opzionali:
L'host Linux non ha più bisogno di pwsh per il normale
funzionamento — usa questo flag solo per script legacy.
--skip-pwsh DEPRECATO: no-op. pwsh non è più installato di default.
--desktop-user <nome>
Desktop user da autorizzare sulla sessione X per la GUI di
VMware (default: \$SUDO_USER). Serve per 'ci vmware'.
--skip-gui-access
Salta l'autorizzazione X (host headless / nessun desktop).
Esempi:
sudo bash setup-host-linux.sh -d /dev/sdf1
sudo bash setup-host-linux.sh -d /dev/sdf1 --with-pwsh
sudo bash setup-host-linux.sh -d /dev/sdf1 --skip-clone
sudo bash setup-host-linux.sh -d /dev/sdf1 --desktop-user simone
EOF
exit 1
}
@@ -75,6 +85,8 @@ while [[ $# -gt 0 ]]; do
echo "WARN: --skip-pwsh è deprecato e non ha più effetto: pwsh non è installato di default (usa --with-pwsh per installarlo)." >&2
shift ;;
--skip-clone) SKIP_CLONE=true; shift ;;
--desktop-user) DESKTOP_USER="$2"; shift 2 ;;
--skip-gui-access) SKIP_GUI_ACCESS=true; shift ;;
-h|--help) usage ;;
*) die "Parametro sconosciuto: $1"; ;;
esac
@@ -94,7 +106,7 @@ echo " Utente : $CI_USER"
echo ""
# ── Step 1: utente di servizio ────────────────────────────────────────────────
info "[1/7] Utente di servizio $CI_USER"
info "[1/8] Utente di servizio $CI_USER"
if id "$CI_USER" &>/dev/null; then
warn "Utente $CI_USER esiste già — skip"
else
@@ -103,7 +115,7 @@ else
fi
# ── Step 2: mount partizione CI ───────────────────────────────────────────────
info "[2/7] Mount $CI_DISK → $CI_ROOT"
info "[2/8] Mount $CI_DISK → $CI_ROOT"
if mountpoint -q "$CI_ROOT" 2>/dev/null; then
warn "$CI_ROOT già montato — skip riorganizzazione e fstab"
@@ -161,7 +173,7 @@ else
fi
# ── Step 3: layout directory e permessi ───────────────────────────────────────
info "[3/7] Layout directory e permessi"
info "[3/8] Layout directory e permessi"
# Installa acl se mancante
if ! command -v setfacl &>/dev/null; then
@@ -187,7 +199,7 @@ echo " Layout OK — ACL default su build-vms applicata"
# ── Step 4: Python 3.11+ e venv ──────────────────────────────────────────────
if [[ "$SKIP_PYTHON" == false ]]; then
info "[4/7] Python 3.11+ e venv produzione"
info "[4/8] Python 3.11+ e venv produzione"
# Cerca la prima versione di Python >= 3.11 già installata
PYTHON_BIN=""
@@ -218,12 +230,12 @@ if [[ "$SKIP_PYTHON" == false ]]; then
echo " venv creato in $OPT_CI/venv ($($PYTHON_BIN --version))"
fi
else
info "[4/7] Python/venv — SKIPPED"
info "[4/8] Python/venv — SKIPPED"
fi
# ── Step 5: Clone repo e pip install ─────────────────────────────────────────
if [[ "$SKIP_CLONE" == false ]]; then
info "[5/7] Clone repo e pip install"
info "[5/8] Clone repo e pip install"
REPO_DIR="$OPT_CI/local-ci-cd-system"
if [[ -d "$REPO_DIR/.git" ]]; then
warn "Repo già presente in $REPO_DIR — skip clone (eseguire git pull manualmente)"
@@ -237,12 +249,12 @@ if [[ "$SKIP_CLONE" == false ]]; then
|| die "ci_orchestrator non importabile"
echo " Verifica OK: ${verify_out%%$'\n'*}"
else
info "[5/7] Clone repo — SKIPPED"
info "[5/8] Clone repo — SKIPPED"
fi
# ── Step 6: PowerShell Core (opt-in via --with-pwsh) ──────────────────────────
if [[ "$WITH_PWSH" == true ]]; then
info "[6/7] PowerShell Core"
info "[6/8] PowerShell Core"
if command -v pwsh &>/dev/null; then
warn "pwsh già installato: $(pwsh --version) — skip"
else
@@ -264,11 +276,11 @@ if [[ "$WITH_PWSH" == true ]]; then
echo " Installato: $(pwsh --version)"
fi
else
info "[6/7] PowerShell Core — SKIPPED (opt-in con --with-pwsh)"
info "[6/8] PowerShell Core — SKIPPED (opt-in con --with-pwsh)"
fi
# ── Step 7: alias shell `ci` (system-wide) ────────────────────────────────────
info "[7/7] Alias shell 'ci'"
info "[7/8] Alias shell 'ci'"
ALIAS_SCRIPT="$OPT_CI/local-ci-cd-system/scripts/install-ci-alias.sh"
if [[ -x "$ALIAS_SCRIPT" ]]; then
# Installa /etc/profile.d/ci-alias.sh per tutti gli utenti login.
@@ -280,6 +292,23 @@ else
warn "install-ci-alias.sh non trovato in $ALIAS_SCRIPT — skip (clona il repo, Step 5)"
fi
# ── Step 8: autorizzazione X per la GUI di VMware (xhost persistente) ──────────
info "[8/8] Accesso GUI VMware (xhost ci-runner)"
GUI_SCRIPT="$OPT_CI/local-ci-cd-system/scripts/install-vmware-gui-access.sh"
if [[ "$SKIP_GUI_ACCESS" == true ]]; then
info " SKIPPED (--skip-gui-access)"
elif [[ -z "$DESKTOP_USER" ]]; then
warn "Nessun desktop user (SUDO_USER vuoto) — skip. Riesegui con --desktop-user <nome>."
elif [[ -x "$GUI_SCRIPT" ]]; then
# Autorizza ci-runner sulla sessione X del desktop user così `ci vmware`
# può aprire la GUI. Grant reso persistente via autostart XDG.
CI_RUN_USER="$CI_USER" \
bash "$GUI_SCRIPT" --desktop-user "$DESKTOP_USER"
echo " Accesso GUI configurato per '$DESKTOP_USER' — prova: ci vmware"
else
warn "install-vmware-gui-access.sh non trovato in $GUI_SCRIPT — skip (clona il repo, Step 5)"
fi
# ── Riepilogo ─────────────────────────────────────────────────────────────────
echo ""
echo "╔══════════════════════════════════════════════════════╗"