diff --git a/.gitignore b/.gitignore index 2efca37..5b60bc2 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ instance/ # Generated test scripts (these are sample outputs, not source) vnc_cnbugs_gnome.sh +vnc_cnbug_gnome.sh vnc11.sh vnc12.sh vnc13.sh @@ -23,3 +24,6 @@ vnc15.sh vnc16.sh vncs13.sh /tmp/*.sh + +# Other projects' assets that may live alongside this repo +deliverables/ diff --git a/generators/middleware.py b/generators/middleware.py index 2d1c212..d6ebcf9 100644 --- a/generators/middleware.py +++ b/generators/middleware.py @@ -51,6 +51,12 @@ class VNCSrv(Generator): Field("depth", "颜色深度", "select", default="24", options=["16", "24", "32"]), Field("localhost", "仅本地监听", "checkbox", default="no", 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", help="Headless 服务器必须启用 — VNC 通过 Xvfb 渲染,无需物理显卡。"), ] @@ -84,9 +90,18 @@ class VNCSrv(Generator): "gnome": ( "unset SESSION_MANAGER\n" "unset DBUS_SESSION_BUS_ADDRESS\n" - # GNOME requires XDG_CURRENT_DESKTOP and a dbus session - "export XDG_CURRENT_DESKTOP=GNOME\n" + # Ubuntu-flavored GNOME. The colon-separated form + # `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" + # 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" ), "kde-plasma": ( @@ -116,6 +131,8 @@ class VNCSrv(Generator): geometry = p.get("geometry", "1920x1080") depth = p.get("depth", "24") 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)) if de not in self.DE_PACKAGES: @@ -131,18 +148,40 @@ class VNCSrv(Generator): out.append('apt-get update') out.append(f'log "Installing {de} desktop + TigerVNC..."') # 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 = ( 'DEBIAN_FRONTEND=noninteractive apt-get install -y \\\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: install += ' || \\\nDEBIAN_FRONTEND=noninteractive apt-get install -y \\\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"): - # Some compositing DEs need a fake display backend - install += ' \\\n xserver-xorg-video-dummy xvfb' + # Some compositing DEs need a fake display backend driver + 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('log "Preparing VNC directory..."') @@ -216,14 +255,13 @@ class VNCSrv(Generator): # to the User='s NSS-resolved home) can be inconsistent after a # usermod -d in the same session. We write the literal absolute # path that was just resolved and verified to work above. - out.append('HOME_ABS="' + '$USER_HOME' + '"') 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..."') - # Write to a .in template, then sed-replace $USER_HOME with the - # resolved absolute path so the final .service has a literal path. + # Use vncserver -fg directly. TigerVNC 1.12+ supports -fg + # (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' '[Unit]\n' 'Description=TigerVNC server on display :%i (' + de + ')\n' @@ -232,31 +270,34 @@ class VNCSrv(Generator): 'Type=simple\n' f'User={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' - # Best-effort cleanup. Main script also does this as root - # (where rm -f actually works on root-owned files). The - # leading "-" tells systemd to ignore non-zero exit. - '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' - f'ExecStart=/usr/bin/vncserver -fg -localhost {localhost} :%i \\\n' - f' -geometry {geometry} -depth {depth}\n' + 'ExecStartPre=-/usr/bin/vncserver -kill :%i\n' + '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' + 'ExecStart=/usr/bin/vncserver -fg :%i' + ' -geometry ' + geometry + ' -depth ' + depth + + localhost_flag + '\n' 'ExecStop=/usr/bin/vncserver -kill :%i\n\n' '[Install]\n' 'WantedBy=multi-user.target\n' 'UNIT_EOF') - # Replace the placeholder with the literal absolute home path. - out.append('# Substitute the placeholder with the actual home path we resolved above') + out.append('# Substitute the placeholder with the actual home path') 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 " 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. - # systemd runs ExecStartPre under User=, which means cnbugs can't - # rm -f files owned by root. Do it here from the main script (running - # as root) so the cleanup actually takes effect. + # systemd runs ExecStartPre under User=, which means the user + # can't rm files owned by root. Do it here from the main script. 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('pkill -f "Xvnc.*:' + display + ' " 2>/dev/null || true')