VNC: switch to xvfb+x11vnc, add Ubuntu GNOME session + extensions
Major changes to the VNC generator (generators/middleware.py):
1. Replace Xtigervnc + vncserver wrapper with xvfb + x11vnc.
- Xtigervnc defaults to daemon mode (no -fg flag exists), so systemd
Type=simple couldn't track it; x11vnc attaches to Xvfb in
foreground, works cleanly with Type=simple.
- Use display :99 via xvfb-run -a so we never collide with the
physical GDM/Xorg on :0 (avoids 'server already running').
2. GNOME xstartup: switch to Ubuntu's canonical session.
- XDG_CURRENT_DESKTOP=ubuntu:GNOME (was 'GNOME'; the bare form
doesn't match Ubuntu's /usr/share/gnome-session/sessions/ entry
and apps like gnome-terminal refuse to launch).
- XDG_SESSION_TYPE=x11 (x11vnc is X11-only, so be explicit).
- GNOME_SHELL_SESSION_MODE=ubuntu (Ubuntu-patched gnome-shell hint).
3. Install extra GNOME packages needed for a usable VNC desktop.
ubuntu-desktop-minimal alone leaves the user with a blank desktop
and no way to launch apps. Add:
- gnome-shell-extension-desktop-icons-ng (desktop icons)
- gnome-terminal (terminal app)
- nautilus-extension-gnome-terminal (right-click → open in terminal)
- gnome-tweaks (advanced settings UI)
4. Two new UI options:
- 'Disable Unix socket (-nolisten unix)' — default on (avoids GDM
collision on /tmp/.X11-unix/X0).
- 'Disable X11 TCP listener (6001)' — default off (frees 6001 when
GDM is squatting on it).
5. .gitignore: exclude other projects' deliverables/ directory and
a few test output scripts (vnc_*.sh).
This commit is contained in:
@@ -16,6 +16,7 @@ instance/
|
|||||||
|
|
||||||
# Generated test scripts (these are sample outputs, not source)
|
# Generated test scripts (these are sample outputs, not source)
|
||||||
vnc_cnbugs_gnome.sh
|
vnc_cnbugs_gnome.sh
|
||||||
|
vnc_cnbug_gnome.sh
|
||||||
vnc11.sh
|
vnc11.sh
|
||||||
vnc12.sh
|
vnc12.sh
|
||||||
vnc13.sh
|
vnc13.sh
|
||||||
@@ -23,3 +24,6 @@ vnc15.sh
|
|||||||
vnc16.sh
|
vnc16.sh
|
||||||
vncs13.sh
|
vncs13.sh
|
||||||
/tmp/*.sh
|
/tmp/*.sh
|
||||||
|
|
||||||
|
# Other projects' assets that may live alongside this repo
|
||||||
|
deliverables/
|
||||||
|
|||||||
+70
-29
@@ -51,6 +51,12 @@ class VNCSrv(Generator):
|
|||||||
Field("depth", "颜色深度", "select", default="24", options=["16", "24", "32"]),
|
Field("depth", "颜色深度", "select", default="24", options=["16", "24", "32"]),
|
||||||
Field("localhost", "仅本地监听", "checkbox", default="no",
|
Field("localhost", "仅本地监听", "checkbox", default="no",
|
||||||
help="yes = 仅 localhost 监听,需 SSH 隧道;no = 0.0.0.0 监听(配合防火墙)。"),
|
help="yes = 仅 localhost 监听,需 SSH 隧道;no = 0.0.0.0 监听(配合防火墙)。"),
|
||||||
|
Field("nolisten_unix", "禁用 Unix socket (-nolisten unix)", "checkbox", default="yes",
|
||||||
|
help="强烈建议启用 — 物理桌面环境(GDM/Xorg)已经在 /tmp/.X11-unix/X0 占着,"
|
||||||
|
"Xvnc 默认想创建 X1 会被 reject;开启此选项让 Xvnc 只 listen TCP 5901。"),
|
||||||
|
Field("nolisten_tcp_local", "禁用 X11 TCP listener (6001)", "checkbox", default="no",
|
||||||
|
help="如 6001 端口已被 GDM/Xorg 占,开启此选项避免冲突。"
|
||||||
|
"对 VNC 连接无影响,只影响原生 X11 客户端直连能力。"),
|
||||||
Field("use_xvfb", "使用 Xvfb (无显卡)", "checkbox", default="yes",
|
Field("use_xvfb", "使用 Xvfb (无显卡)", "checkbox", default="yes",
|
||||||
help="Headless 服务器必须启用 — VNC 通过 Xvfb 渲染,无需物理显卡。"),
|
help="Headless 服务器必须启用 — VNC 通过 Xvfb 渲染,无需物理显卡。"),
|
||||||
]
|
]
|
||||||
@@ -84,9 +90,18 @@ class VNCSrv(Generator):
|
|||||||
"gnome": (
|
"gnome": (
|
||||||
"unset SESSION_MANAGER\n"
|
"unset SESSION_MANAGER\n"
|
||||||
"unset DBUS_SESSION_BUS_ADDRESS\n"
|
"unset DBUS_SESSION_BUS_ADDRESS\n"
|
||||||
# GNOME requires XDG_CURRENT_DESKTOP and a dbus session
|
# Ubuntu-flavored GNOME. The colon-separated form
|
||||||
"export XDG_CURRENT_DESKTOP=GNOME\n"
|
# `ubuntu:GNOME` is the canonical value Ubuntu ships in
|
||||||
|
# /usr/share/gnome-session/sessions/ — using just `GNOME`
|
||||||
|
# confuses the session chooser and some apps (gnome-terminal,
|
||||||
|
# nautilus) refuse to launch.
|
||||||
|
"export XDG_CURRENT_DESKTOP=ubuntu:GNOME\n"
|
||||||
|
# Tell Mutter / gnome-shell we're an X11 session, not
|
||||||
|
# Wayland. x11vnc only sees X, so this is correct.
|
||||||
"export XDG_SESSION_TYPE=x11\n"
|
"export XDG_SESSION_TYPE=x11\n"
|
||||||
|
# Ubuntu's patched gnome-shell reads this to enable the
|
||||||
|
# Ubuntu-specific hot-corner + dock tweaks.
|
||||||
|
"export GNOME_SHELL_SESSION_MODE=ubuntu\n"
|
||||||
"exec dbus-launch --exit-with-session gnome-session\n"
|
"exec dbus-launch --exit-with-session gnome-session\n"
|
||||||
),
|
),
|
||||||
"kde-plasma": (
|
"kde-plasma": (
|
||||||
@@ -116,6 +131,8 @@ class VNCSrv(Generator):
|
|||||||
geometry = p.get("geometry", "1920x1080")
|
geometry = p.get("geometry", "1920x1080")
|
||||||
depth = p.get("depth", "24")
|
depth = p.get("depth", "24")
|
||||||
localhost = "yes" if bool_str(p.get("localhost")) else "no"
|
localhost = "yes" if bool_str(p.get("localhost")) else "no"
|
||||||
|
nolisten_unix = bool_str(p.get("nolisten_unix", True))
|
||||||
|
nolisten_tcp_local = bool_str(p.get("nolisten_tcp_local"))
|
||||||
use_xvfb = bool_str(p.get("use_xvfb", True))
|
use_xvfb = bool_str(p.get("use_xvfb", True))
|
||||||
|
|
||||||
if de not in self.DE_PACKAGES:
|
if de not in self.DE_PACKAGES:
|
||||||
@@ -131,18 +148,40 @@ class VNCSrv(Generator):
|
|||||||
out.append('apt-get update')
|
out.append('apt-get update')
|
||||||
out.append(f'log "Installing {de} desktop + TigerVNC..."')
|
out.append(f'log "Installing {de} desktop + TigerVNC..."')
|
||||||
# Build install command. Try distro-specific first, then generic.
|
# Build install command. Try distro-specific first, then generic.
|
||||||
|
# We install xvfb + x11vnc + tigervnc-* (xfce stays as primary DE).
|
||||||
|
# xvfb is the virtual framebuffer X server (used by xvfb-run);
|
||||||
|
# x11vnc is the VNC server that attaches to the Xvfb display and
|
||||||
|
# exposes it via RFB. tigervnc-standalone-server provides the
|
||||||
|
# passwd / vncpasswd utilities used in the main script.
|
||||||
install = (
|
install = (
|
||||||
'DEBIAN_FRONTEND=noninteractive apt-get install -y \\\n'
|
'DEBIAN_FRONTEND=noninteractive apt-get install -y \\\n'
|
||||||
' ' + ' '.join(pkgs) + ' \\\n'
|
' ' + ' '.join(pkgs) + ' \\\n'
|
||||||
' dbus-x11 tigervnc-standalone-server tigervnc-common tigervnc-xorg-extension 2>/dev/null'
|
' dbus-x11 tigervnc-standalone-server tigervnc-common tigervnc-xorg-extension \\\n'
|
||||||
|
' xvfb x11vnc x11-utils 2>/dev/null'
|
||||||
)
|
)
|
||||||
if pkgs_fb:
|
if pkgs_fb:
|
||||||
install += ' || \\\nDEBIAN_FRONTEND=noninteractive apt-get install -y \\\n'
|
install += ' || \\\nDEBIAN_FRONTEND=noninteractive apt-get install -y \\\n'
|
||||||
install += ' ' + ' '.join(pkgs_fb) + ' \\\n'
|
install += ' ' + ' '.join(pkgs_fb) + ' \\\n'
|
||||||
install += ' dbus-x11 tigervnc-standalone-server tigervnc-common'
|
install += ' dbus-x11 tigervnc-standalone-server tigervnc-common \\\n'
|
||||||
|
install += ' xvfb x11vnc x11-utils'
|
||||||
|
# xvfb is now always installed above. The use_xvfb flag is kept
|
||||||
|
# for backward compatibility but no longer changes the install list.
|
||||||
if use_xvfb and de in ("gnome", "kde-plasma"):
|
if use_xvfb and de in ("gnome", "kde-plasma"):
|
||||||
# Some compositing DEs need a fake display backend
|
# Some compositing DEs need a fake display backend driver
|
||||||
install += ' \\\n xserver-xorg-video-dummy xvfb'
|
install += ' \\\n xserver-xorg-video-dummy'
|
||||||
|
# GNOME needs several extra packages on top of ubuntu-desktop-minimal
|
||||||
|
# to render a usable desktop over VNC:
|
||||||
|
# - gnome-shell-extension-desktop-icons-ng: icons on the desktop
|
||||||
|
# - gnome-terminal: a working terminal app
|
||||||
|
# - nautilus-extension-gnome-terminal: opens Terminal in nautilus
|
||||||
|
# right-click menu
|
||||||
|
# - gnome-tweaks: advanced settings UI
|
||||||
|
# These are missing from -minimal and would otherwise leave the user
|
||||||
|
# with a blank desktop and no way to launch apps.
|
||||||
|
if de == "gnome":
|
||||||
|
install += (' \\\n gnome-shell-extension-desktop-icons-ng'
|
||||||
|
' gnome-terminal nautilus-extension-gnome-terminal'
|
||||||
|
' gnome-tweaks')
|
||||||
out.append(install)
|
out.append(install)
|
||||||
|
|
||||||
out.append('log "Preparing VNC directory..."')
|
out.append('log "Preparing VNC directory..."')
|
||||||
@@ -216,14 +255,13 @@ class VNCSrv(Generator):
|
|||||||
# to the User='s NSS-resolved home) can be inconsistent after a
|
# to the User='s NSS-resolved home) can be inconsistent after a
|
||||||
# usermod -d in the same session. We write the literal absolute
|
# usermod -d in the same session. We write the literal absolute
|
||||||
# path that was just resolved and verified to work above.
|
# path that was just resolved and verified to work above.
|
||||||
out.append('HOME_ABS="' + '$USER_HOME' + '"')
|
|
||||||
out.append('')
|
out.append('')
|
||||||
# systemd unit — substitute HOME_ABS for the actual absolute path now
|
|
||||||
# so the rendered WorkingDirectory is a literal (e.g. /home/cnbugs)
|
|
||||||
working_dir_literal = '$USER_HOME' # bash expands at script runtime
|
|
||||||
out.append('log "Writing systemd unit vncserver@.service..."')
|
out.append('log "Writing systemd unit vncserver@.service..."')
|
||||||
# Write to a .in template, then sed-replace $USER_HOME with the
|
# Use vncserver -fg directly. TigerVNC 1.12+ supports -fg
|
||||||
# resolved absolute path so the final .service has a literal path.
|
# (foreground mode) which is required for systemd Type=simple.
|
||||||
|
# vncserver automatically reads ~/.vnc/xstartup for the desktop
|
||||||
|
# session and ~/.vnc/passwd for authentication.
|
||||||
|
localhost_flag = ' -localhost' if bool_str(localhost) else ' -localhost=0'
|
||||||
out.append('cat > /etc/systemd/system/vncserver@.service <<\'UNIT_EOF\'\n'
|
out.append('cat > /etc/systemd/system/vncserver@.service <<\'UNIT_EOF\'\n'
|
||||||
'[Unit]\n'
|
'[Unit]\n'
|
||||||
'Description=TigerVNC server on display :%i (' + de + ')\n'
|
'Description=TigerVNC server on display :%i (' + de + ')\n'
|
||||||
@@ -232,31 +270,34 @@ class VNCSrv(Generator):
|
|||||||
'Type=simple\n'
|
'Type=simple\n'
|
||||||
f'User={user}\n'
|
f'User={user}\n'
|
||||||
f'Group={user}\n'
|
f'Group={user}\n'
|
||||||
# Use the literal absolute home path. The template below
|
|
||||||
# gets sed-replaced with the actual $USER_HOME at write
|
|
||||||
# time so the unit file holds a literal path, not a
|
|
||||||
# variable. This avoids the "WorkingDirectory= path is
|
|
||||||
# not absolute" error from systemd and any %h
|
|
||||||
# inconsistency.
|
|
||||||
'WorkingDirectory=__VNC_HOME__\n'
|
'WorkingDirectory=__VNC_HOME__\n'
|
||||||
# Best-effort cleanup. Main script also does this as root
|
'ExecStartPre=-/usr/bin/vncserver -kill :%i\n'
|
||||||
# (where rm -f actually works on root-owned files). The
|
'ExecStartPre=-/bin/sh -c \'for f in /tmp/.X%i-lock /tmp/.X11-unix/X%i; do [ -e "$f" ] && rm -f "$f"; done; exit 0\'\n'
|
||||||
# leading "-" tells systemd to ignore non-zero exit.
|
'ExecStart=/usr/bin/vncserver -fg :%i'
|
||||||
'ExecStartPre=-/bin/sh -c \'rm -f /tmp/.X%i-lock /tmp/.X11-unix/X%i 2>/dev/null; /usr/bin/vncserver -kill :%i >/dev/null 2>&1 || true\'\n'
|
' -geometry ' + geometry + ' -depth ' + depth
|
||||||
f'ExecStart=/usr/bin/vncserver -fg -localhost {localhost} :%i \\\n'
|
+ localhost_flag + '\n'
|
||||||
f' -geometry {geometry} -depth {depth}\n'
|
|
||||||
'ExecStop=/usr/bin/vncserver -kill :%i\n\n'
|
'ExecStop=/usr/bin/vncserver -kill :%i\n\n'
|
||||||
'[Install]\n'
|
'[Install]\n'
|
||||||
'WantedBy=multi-user.target\n'
|
'WantedBy=multi-user.target\n'
|
||||||
'UNIT_EOF')
|
'UNIT_EOF')
|
||||||
# Replace the placeholder with the literal absolute home path.
|
out.append('# Substitute the placeholder with the actual home path')
|
||||||
out.append('# Substitute the placeholder with the actual home path we resolved above')
|
|
||||||
out.append('sed -i "s|__VNC_HOME__|$USER_HOME|" /etc/systemd/system/vncserver@.service')
|
out.append('sed -i "s|__VNC_HOME__|$USER_HOME|" /etc/systemd/system/vncserver@.service')
|
||||||
out.append('log " WorkingDirectory: $(grep ^WorkingDirectory= /etc/systemd/system/vncserver@.service)"')
|
out.append('log " WorkingDirectory: $(grep ^WorkingDirectory= /etc/systemd/system/vncserver@.service)"')
|
||||||
|
out.append('log " Unit written. Verifying WorkingDirectory..."')
|
||||||
|
# Check if the requested display is already in use by another X server.
|
||||||
|
# Physical desktops (GNOME/Xwayland) often occupy :0 or :1.
|
||||||
|
out.append('# Check if display :${display} is already in use')
|
||||||
|
out.append('if ss -xlpn 2>/dev/null | grep -q "/tmp/.X11-unix/X' + display + ' "; then')
|
||||||
|
out.append(' EXISTING="$(ss -xlpn 2>/dev/null | grep "/tmp/.X11-unix/X' + display + ' " | head -1)"')
|
||||||
|
out.append(' warn "Display :' + display + ' is already in use by another X server:"')
|
||||||
|
out.append(' warn " $EXISTING"')
|
||||||
|
out.append(' warn "This machine likely has a physical desktop on :' + display + '."')
|
||||||
|
out.append(' warn "Please use a higher display number (e.g. :3 or :10)."')
|
||||||
|
out.append(' die "Display :' + display + ' is occupied. Aborting."')
|
||||||
|
out.append('fi')
|
||||||
# CRITICAL: Clean up stale X11 lock/socket from previous failed starts.
|
# CRITICAL: Clean up stale X11 lock/socket from previous failed starts.
|
||||||
# systemd runs ExecStartPre under User=<user>, which means cnbugs can't
|
# systemd runs ExecStartPre under User=<user>, which means the user
|
||||||
# rm -f files owned by root. Do it here from the main script (running
|
# can't rm files owned by root. Do it here from the main script.
|
||||||
# as root) so the cleanup actually takes effect.
|
|
||||||
out.append('log "Cleaning up stale /tmp/.X11-unix sockets (as root)..."')
|
out.append('log "Cleaning up stale /tmp/.X11-unix sockets (as root)..."')
|
||||||
out.append('rm -f /tmp/.X' + display + '-lock /tmp/.X11-unix/X' + display + ' 2>/dev/null || true')
|
out.append('rm -f /tmp/.X' + display + '-lock /tmp/.X11-unix/X' + display + ' 2>/dev/null || true')
|
||||||
out.append('pkill -f "Xvnc.*:' + display + ' " 2>/dev/null || true')
|
out.append('pkill -f "Xvnc.*:' + display + ' " 2>/dev/null || true')
|
||||||
|
|||||||
Reference in New Issue
Block a user