setup: Ubuntu GNOME-on-Xorg desktop + setup.sh hardening

setup-desktop.sh now installs ubuntu-desktop + gdm3 + x11vnc and forces the
Xorg session (WaylandEnable=false) with auto-login — x11vnc can only mirror an
Xorg :0, not Wayland. vnc-manager.ts resolves the X authority from the GDM
per-session path (/run/user/<uid>/gdm/Xauthority) with a ~/.Xauthority fallback.

setup.sh fixes:
- desktop step gates on `dpkg -s ubuntu-desktop` (was the decommissioned
  officer-vnc service, which never matched so setup-desktop re-ran every time)
- remove Pi (install, --list-models validation, verification check)
- export GOPATH before the cliamp build so `go install` lands where it's checked
  even when Go was already present this run
- write PUBLIC_BUILD_ENV=production and quote all .env values
- guard the interactive .env block behind a TTY check so non-interactive runs
  skip cleanly instead of aborting on read EOF under set -e
- restart systemd-logind only when a key actually changed
- sed prefix-strip instead of `tr -d` (which deletes characters, not a prefix)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 15:32:28 +00:00
co-authored by Claude Opus 4.8
parent 5d077a4a54
commit 1e789b4c44
4 changed files with 113 additions and 266 deletions
+42 -37
View File
@@ -299,8 +299,13 @@ else
fi
# Configure logind to ignore idle — patch individual keys, don't overwrite the file
LOGIND_CHANGED=0
set_logind_key() {
local key="$1" val="$2" file="/etc/systemd/logind.conf"
# Already set (uncommented) to the desired value → nothing to do.
if grep -qE "^${key}=${val}$" "$file" 2>/dev/null; then
return
fi
if grep -qE "^${key}=" "$file" 2>/dev/null; then
sudo sed -i "s|^${key}=.*|${key}=${val}|" "$file"
elif grep -qE "^#${key}=" "$file" 2>/dev/null; then
@@ -308,6 +313,7 @@ set_logind_key() {
else
echo "${key}=${val}" | sudo tee -a "$file" > /dev/null
fi
LOGIND_CHANGED=1
}
set_logind_key HandleLidSwitch ignore
@@ -316,8 +322,13 @@ set_logind_key HandlePowerKey ignore
set_logind_key IdleAction none
set_logind_key RuntimeDirectorySize 10%
sudo systemctl restart systemd-logind
ok "Configured logind to disable auto-suspend"
# Only restart logind when something actually changed — a needless restart can disrupt live sessions.
if [ "$LOGIND_CHANGED" = "1" ]; then
sudo systemctl restart systemd-logind
ok "Configured logind to disable auto-suspend"
else
skip "logind auto-suspend settings"
fi
# ─── 5. Node.js 22 (system-wide) ─────────────────────────────────────────────
echo ""
@@ -337,7 +348,7 @@ case $PM in
apt)
NEED_INSTALL=0
if [ -f "$SYSTEM_NODE" ]; then
SYS_MAJOR=$("$SYSTEM_NODE" -v 2>/dev/null | cut -d. -f1 | tr -d 'v')
SYS_MAJOR=$("$SYSTEM_NODE" -v 2>/dev/null | cut -d. -f1 | sed 's/^v//')
if [ "$SYS_MAJOR" = "22" ]; then
skip "node v$("$SYSTEM_NODE" -v) (system-wide at $SYSTEM_NODE)"
else
@@ -354,7 +365,7 @@ case $PM in
fi
if [ "$NEED_INSTALL" = "1" ]; then
install_node22_apt
if [ -f "$SYSTEM_NODE" ] && [ "$("$SYSTEM_NODE" -v 2>/dev/null | cut -d. -f1 | tr -d 'v')" = "22" ]; then
if [ -f "$SYSTEM_NODE" ] && [ "$("$SYSTEM_NODE" -v 2>/dev/null | cut -d. -f1 | sed 's/^v//')" = "22" ]; then
ok "node v$("$SYSTEM_NODE" -v) installed at $SYSTEM_NODE"
if has node && [ "$(command -v node)" != "$SYSTEM_NODE" ]; then
warn "Shell resolves 'node' to $(command -v node) — system node is at $SYSTEM_NODE"
@@ -367,7 +378,7 @@ case $PM in
fi
;;
pacman)
if has node && [ "$(node -v 2>/dev/null | cut -d. -f1 | tr -d 'v')" = "22" ]; then
if has node && [ "$(node -v 2>/dev/null | cut -d. -f1 | sed 's/^v//')" = "22" ]; then
skip "node v$(node -v)"
else
install_pkg nodejs npm
@@ -375,7 +386,7 @@ case $PM in
fi
;;
brew)
if has node && [ "$(node -v 2>/dev/null | cut -d. -f1 | tr -d 'v')" = "22" ]; then
if has node && [ "$(node -v 2>/dev/null | cut -d. -f1 | sed 's/^v//')" = "22" ]; then
skip "node v$(node -v)"
else
install_pkg node
@@ -412,7 +423,7 @@ echo ""
echo "── Go ──"
echo " Fetching latest Go version..."
GOLANG_VERSION=$(curl -fsSL "https://go.dev/dl/?mode=json" | jq -r '.[0].version' | tr -d 'go')
GOLANG_VERSION=$(curl -fsSL "https://go.dev/dl/?mode=json" | jq -r '.[0].version' | sed 's/^go//')
if [ -z "$GOLANG_VERSION" ]; then
warn "Could not fetch latest Go version — falling back to 1.23.6"
GOLANG_VERSION=1.23.6
@@ -445,7 +456,7 @@ install_go() {
}
if has go; then
INSTALLED_GO=$(go version 2>/dev/null | awk '{print $3}' | tr -d 'go')
INSTALLED_GO=$(go version 2>/dev/null | awk '{print $3}' | sed 's/^go//')
if [ "$INSTALLED_GO" = "$GOLANG_VERSION" ]; then
skip "go $INSTALLED_GO"
else
@@ -528,7 +539,11 @@ fi
echo ""
echo "── cliamp ──"
GOPATH_BIN="${GOPATH:-$HOME/.local/go-path}/bin"
# Export GOPATH (not just PATH) so `go install` lands in GOPATH_BIN — even when Go was already present
# this run and install_go (which sets GOPATH) never ran. Otherwise go uses its default ~/go/bin and the
# check below wrongly reports a build failure.
export GOPATH="${GOPATH:-$HOME/.local/go-path}"
GOPATH_BIN="$GOPATH/bin"
export PATH="$GOPATH_BIN:$PATH"
if has cliamp; then
@@ -619,7 +634,7 @@ else
case $PM in
apt)
echo " Fetching latest eza version..."
EZA_VERSION=$(curl -fsSL "https://api.github.com/repos/eza-community/eza/releases/latest" | jq -r '.tag_name' | tr -d 'v')
EZA_VERSION=$(curl -fsSL "https://api.github.com/repos/eza-community/eza/releases/latest" | jq -r '.tag_name' | sed 's/^v//')
if [ -z "$EZA_VERSION" ]; then warn "Could not fetch eza version — skipping"; else
ARCH=$(uname -m)
case $ARCH in
@@ -647,7 +662,7 @@ else
case $PM in
apt)
echo " Fetching latest lazygit version..."
LAZYGIT_VERSION=$(curl -fsSL "https://api.github.com/repos/jesseduffield/lazygit/releases/latest" | jq -r '.tag_name' | tr -d 'v')
LAZYGIT_VERSION=$(curl -fsSL "https://api.github.com/repos/jesseduffield/lazygit/releases/latest" | jq -r '.tag_name' | sed 's/^v//')
if [ -z "$LAZYGIT_VERSION" ]; then warn "Could not fetch lazygit version — skipping"; else
ARCH=$(uname -m)
case $ARCH in
@@ -710,24 +725,6 @@ else
npm config set prefix "$HOME/.local"
ok "npm prefix set to $HOME/.local"
# Pi (coding agent)
if has pi; then
skip "pi (@mariozechner/pi-coding-agent)"
else
echo " Installing pi..."
npm install -g @mariozechner/pi-coding-agent
if has pi; then ok "pi installed"; else warn "pi install failed"; fi
fi
# Validate Pi works
if has pi; then
if pi --list-models > /dev/null 2>&1; then
ok "pi validated (--list-models works)"
else
warn "pi installed but --list-models failed — check API keys in ~/.pi/agent/auth.json"
fi
fi
# Claude Code (uses Anthropic's own installer for auto-update support)
if has claude; then
skip "claude (claude-code)"
@@ -765,7 +762,15 @@ echo "── Environment (.env) ──"
GENERATE_ENV=true
if [ -f "$PROJECT_DIR/.env" ]; then
if [ ! -t 0 ]; then
# Non-interactive shell: the prompts below would hit EOF and abort the whole script under `set -e`.
GENERATE_ENV=false
if [ -f "$PROJECT_DIR/.env" ]; then
skip ".env (kept existing — non-interactive shell)"
else
warn "No .env and not a terminal — re-run setup.sh interactively to generate it"
fi
elif [ -f "$PROJECT_DIR/.env" ]; then
echo -n " .env already exists. Regenerate? (y/n) [n]: "
read -r REGEN
if [[ "$REGEN" != "y" && "$REGEN" != "Y" ]]; then
@@ -820,12 +825,13 @@ if [ "$GENERATE_ENV" = true ]; then
# Write .env
cat > "$PROJECT_DIR/.env" <<ENVFILE
PORT=$ENV_PORT
PORT="$ENV_PORT"
JWT_SECRET="$JWT_SECRET"
MAIL_TRANSPORT="$ENV_MAIL_TRANSPORT"
PUBLIC_URL=$ENV_PUBLIC_URL
DATA_PATH=$ENV_DATA_PATH
HOME_DIR=$HOME
PUBLIC_URL="$ENV_PUBLIC_URL"
PUBLIC_BUILD_ENV="production"
DATA_PATH="$ENV_DATA_PATH"
HOME_DIR="$HOME"
POSTGRES_URL="$POSTGRES_URL"
DISCORD_BUG_REPORT_WEBHOOK="$ENV_DISCORD_WEBHOOK"
ENVFILE
@@ -837,8 +843,8 @@ fi
echo ""
echo "── Remote Desktop (XFCE + VNC) ──"
if systemctl is-active --quiet officer-vnc 2>/dev/null; then
skip "remote desktop (officer-vnc service already running)"
if dpkg -s ubuntu-desktop &>/dev/null 2>&1; then
skip "remote desktop (ubuntu-desktop already installed)"
else
case $PM in
apt)
@@ -901,7 +907,6 @@ check cliamp
echo ""
echo "AI agents:"
check pi
check claude
echo ""