From 3f43b1ca12c6ba662bd52b4d35128ca199319ec8 Mon Sep 17 00:00:00 2001 From: Simone Date: Mon, 8 Jun 2026 21:11:07 +0200 Subject: [PATCH] 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. --- scripts/install-vmware-gui-access.sh | 98 ++++++++++++++++++++++++++++ setup-host-linux.sh | 49 +++++++++++--- 2 files changed, 137 insertions(+), 10 deletions(-) create mode 100755 scripts/install-vmware-gui-access.sh diff --git a/scripts/install-vmware-gui-access.sh b/scripts/install-vmware-gui-access.sh new file mode 100755 index 0000000..748e603 --- /dev/null +++ b/scripts/install-vmware-gui-access.sh @@ -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:` 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 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 )" +[ "$DESKTOP_USER" = "root" ] && die "desktop user resolved to root; pass --desktop-user " + +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 </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" diff --git a/setup-host-linux.sh b/setup-host-linux.sh index ff04e0e..1f478c4 100755 --- a/setup-host-linux.sh +++ b/setup-host-linux.sh @@ -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 + 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 ." +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 "╔══════════════════════════════════════════════════════╗"