376 lines
11 KiB
Bash
Executable File
376 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Per-bot X11 screens inside one Team computer.
|
|
# Slot 0 is DISPLAY :1 / VNC 5900 / view 6080 (primary).
|
|
# Slot N is DISPLAY :(N+1) / VNC 5900+N / view 6080+N.
|
|
set -euo pipefail
|
|
|
|
export HOME="${HOME:-/home/lazyboy}"
|
|
export SHELL=/bin/zsh
|
|
export TERM="${TERM:-xterm-256color}"
|
|
export LANG="${LANG:-zh_TW.UTF-8}"
|
|
export LC_ALL="${LC_ALL:-zh_TW.UTF-8}"
|
|
export LANGUAGE="${LANGUAGE:-zh_TW:zh:en}"
|
|
export GTK_MODULES="${GTK_MODULES:-atk-bridge}"
|
|
export GTK_A11Y="${GTK_A11Y:-atspi}"
|
|
export GNOME_ACCESSIBILITY="${GNOME_ACCESSIBILITY:-1}"
|
|
export NO_AT_BRIDGE="${NO_AT_BRIDGE:-0}"
|
|
export PATH="$HOME/.local/bin:/usr/local/bin:$PATH"
|
|
|
|
ROOT=/tmp/lazyboy
|
|
LIMIT=8
|
|
cmd="${1:-}"
|
|
shift || true
|
|
|
|
mkdir -p "$ROOT/screens" /tmp/.X11-unix "$HOME/.browser-profiles"
|
|
|
|
hydrate_home() {
|
|
mkdir -p "$HOME/.config/xfce4/terminal" "$HOME/.cache" "$HOME/.local/bin"
|
|
if [[ ! -f "$HOME/.zshrc" && -f /usr/share/lazyboy/skel/zshrc ]]; then
|
|
cp /usr/share/lazyboy/skel/zshrc "$HOME/.zshrc"
|
|
fi
|
|
if [[ ! -f "$HOME/.p10k.zsh" && -f /usr/share/lazyboy/skel/p10k.zsh ]]; then
|
|
cp /usr/share/lazyboy/skel/p10k.zsh "$HOME/.p10k.zsh"
|
|
fi
|
|
if [[ ! -f "$HOME/.config/xfce4/terminal/terminalrc" && -f /usr/share/lazyboy/skel/terminalrc ]]; then
|
|
cp /usr/share/lazyboy/skel/terminalrc "$HOME/.config/xfce4/terminal/terminalrc"
|
|
fi
|
|
}
|
|
|
|
hydrate_xfce() {
|
|
local xfce_home="$1"
|
|
mkdir -p \
|
|
"$xfce_home/.config/xfce4/xfconf/xfce-perchannel-xml" \
|
|
"$xfce_home/.config/xfce4/panel" \
|
|
"$xfce_home/.cache" \
|
|
"$xfce_home/.local/share" \
|
|
"$xfce_home/runtime"
|
|
chmod 700 "$xfce_home/runtime" 2>/dev/null || true
|
|
local xml="$xfce_home/.config/xfce4/xfconf/xfce-perchannel-xml"
|
|
local skel_xml=/usr/share/lazyboy/xfce-skel/xfce4/xfconf/xfce-perchannel-xml
|
|
local f
|
|
for f in xfce4-panel.xml xfwm4.xml xfce4-desktop.xml thunar.xml; do
|
|
if [[ -r "$skel_xml/$f" ]]; then
|
|
cp -f "$skel_xml/$f" "$xml/$f" || true
|
|
fi
|
|
done
|
|
if [[ -r /etc/xdg/xfce4/helpers.rc ]]; then
|
|
cp -f /etc/xdg/xfce4/helpers.rc "$xfce_home/.config/xfce4/helpers.rc" || true
|
|
fi
|
|
}
|
|
|
|
wait_display() {
|
|
local display="$1"
|
|
local n
|
|
for n in $(seq 1 100); do
|
|
if xdpyinfo -display "$display" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
return 1
|
|
}
|
|
|
|
port_open() {
|
|
local port="$1"
|
|
python3 - "$port" <<'PY'
|
|
import socket, sys
|
|
port = int(sys.argv[1])
|
|
try:
|
|
s = socket.create_connection(("127.0.0.1", port), 0.2)
|
|
s.close()
|
|
except OSError:
|
|
sys.exit(1)
|
|
PY
|
|
}
|
|
|
|
wait_port() {
|
|
local port="$1"
|
|
local n
|
|
for n in $(seq 1 50); do
|
|
if port_open "$port"; then
|
|
return 0
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
return 1
|
|
}
|
|
|
|
start_xvfb() {
|
|
local display="$1"
|
|
local number="$2"
|
|
local log="$3"
|
|
if xdpyinfo -display "$display" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
rm -f "/tmp/.X${number}-lock" "/tmp/.X11-unix/X${number}"
|
|
Xvfb "$display" -screen 0 1280x800x24 -ac +extension RANDR +render -noreset >>"${log}-xvfb.log" 2>&1 &
|
|
echo $! > "${log}-xvfb.pid"
|
|
wait_display "$display"
|
|
}
|
|
|
|
alive_pidfile() {
|
|
local file="$1"
|
|
[[ -f "$file" ]] || return 1
|
|
local pid
|
|
pid="$(cat "$file" 2>/dev/null || true)"
|
|
[[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null
|
|
}
|
|
|
|
start_atspi() {
|
|
local display="$1"
|
|
local log="$2"
|
|
local number="${display#:}"
|
|
if [[ -n "${DBUS_SESSION_BUS_ADDRESS:-}" ]]; then
|
|
printf '%s\n' "$DBUS_SESSION_BUS_ADDRESS" >"$ROOT/screen-${number}.dbus"
|
|
fi
|
|
if [[ -n "${XDG_RUNTIME_DIR:-}" ]]; then
|
|
printf '%s\n' "$XDG_RUNTIME_DIR" >"$ROOT/screen-${number}.runtime"
|
|
fi
|
|
local launcher="" registry=""
|
|
local c
|
|
for c in /usr/libexec/at-spi-bus-launcher /usr/lib/at-spi2-core/at-spi-bus-launcher at-spi-bus-launcher; do
|
|
if [[ -x "$c" ]] || command -v "$c" >/dev/null 2>&1; then
|
|
launcher="$c"
|
|
break
|
|
fi
|
|
done
|
|
for c in /usr/libexec/at-spi2-registryd /usr/lib/at-spi2-core/at-spi2-registryd at-spi2-registryd; do
|
|
if [[ -x "$c" ]] || command -v "$c" >/dev/null 2>&1; then
|
|
registry="$c"
|
|
break
|
|
fi
|
|
done
|
|
if [[ -n "$launcher" ]]; then
|
|
DISPLAY="$display" "$launcher" --launch-immediately >>"${log}-atspi-bus.log" 2>&1 &
|
|
echo $! >"${log}-atspi-bus.pid"
|
|
fi
|
|
if [[ -n "$registry" ]]; then
|
|
DISPLAY="$display" "$registry" >>"${log}-atspi-registry.log" 2>&1 &
|
|
echo $! >"${log}-atspi-registry.pid"
|
|
fi
|
|
sleep 0.2
|
|
}
|
|
|
|
start_desktop() {
|
|
local display="$1"
|
|
local xfce_home="$2"
|
|
local log="$3"
|
|
if alive_pidfile "${log}-panel.pid"; then
|
|
return 0
|
|
fi
|
|
hydrate_xfce "$xfce_home"
|
|
DISPLAY="$display" xsetroot -solid "#0f172a" >/dev/null 2>&1 || true
|
|
export DISPLAY="$display"
|
|
export XDG_CONFIG_HOME="$xfce_home/.config"
|
|
export XDG_CACHE_HOME="$xfce_home/.cache"
|
|
export XDG_DATA_HOME="$xfce_home/.local/share"
|
|
export XDG_RUNTIME_DIR="$xfce_home/runtime"
|
|
export XDG_CURRENT_DESKTOP=XFCE
|
|
if command -v dbus-launch >/dev/null 2>&1; then
|
|
eval "$(dbus-launch --sh-syntax)"
|
|
echo "${DBUS_SESSION_BUS_PID:-}" >"${log}-dbus.pid"
|
|
fi
|
|
start_atspi "$display" "$log"
|
|
if command -v xfconfd >/dev/null 2>&1; then
|
|
xfconfd --daemon >/dev/null 2>&1 || true
|
|
fi
|
|
if command -v xfwm4 >/dev/null 2>&1; then
|
|
xfwm4 --compositor=off --display="$display" --sm-client-disable >>"${log}-wm.log" 2>&1 &
|
|
echo $! >"${log}-wm.pid"
|
|
sleep 0.3
|
|
xfdesktop --disable-wm-check --sm-client-disable >>"${log}-desktop.log" 2>&1 &
|
|
echo $! >"${log}-desktop.pid"
|
|
xfce4-panel --disable-wm-check --sm-client-disable >>"${log}-panel.log" 2>&1 &
|
|
echo $! >"${log}-panel.pid"
|
|
else
|
|
echo "XFCE is missing" >&2
|
|
return 1
|
|
fi
|
|
sleep 0.4
|
|
}
|
|
|
|
start_vnc() {
|
|
local display="$1"
|
|
local vnc_port="$2"
|
|
local view_port="$3"
|
|
local log="$4"
|
|
if ! port_open "$vnc_port"; then
|
|
x11vnc -display "$display" -forever -shared -nopw -listen 127.0.0.1 -rfbport "$vnc_port" \
|
|
-xkb -repeat -cursor arrow -noxdamage -ncache 0 >>"${log}-x11vnc.log" 2>&1 &
|
|
fi
|
|
if ! port_open "$view_port"; then
|
|
local novnc=/usr/share/novnc
|
|
if [[ ! -d "$novnc" ]]; then
|
|
echo "noVNC is missing" >&2
|
|
return 1
|
|
fi
|
|
websockify --heartbeat=30 --web="$novnc" "0.0.0.0:${view_port}" "127.0.0.1:${vnc_port}" \
|
|
>>"${log}-novnc.log" 2>&1 &
|
|
fi
|
|
wait_port "$vnc_port"
|
|
wait_port "$view_port"
|
|
}
|
|
|
|
start_cua_driver() {
|
|
local display="$1"
|
|
local log="$2"
|
|
local number="${display#:}"
|
|
[[ "${LAZYBOY_COMPUTER_DRIVER:-legacy}" == "cua" ]] || return 0
|
|
if ! command -v cua-driver >/dev/null 2>&1; then
|
|
echo "Cua backend selected but cua-driver is not installed" >&2
|
|
return 1
|
|
fi
|
|
local sock="$ROOT/cua-${number}.sock"
|
|
if [[ "$number" == "1" ]]; then sock="$ROOT/cua.sock"; fi
|
|
if alive_pidfile "${log}-cua.pid"; then
|
|
if [[ -S "$sock" ]]; then return 0; fi
|
|
echo "Cua process exists but its display socket is missing" >&2
|
|
return 1
|
|
fi
|
|
export CUA_DRIVER_RS_HOME="$ROOT/cua-home-${number}"
|
|
if [[ -s "$ROOT/screen-${number}.dbus" ]]; then
|
|
export DBUS_SESSION_BUS_ADDRESS="$(cat "$ROOT/screen-${number}.dbus")"
|
|
fi
|
|
if [[ -s "$ROOT/screen-${number}.runtime" ]]; then
|
|
export XDG_RUNTIME_DIR="$(cat "$ROOT/screen-${number}.runtime")"
|
|
fi
|
|
mkdir -p "$CUA_DRIVER_RS_HOME"
|
|
cua-driver telemetry disable >>"${log}-cua.log" 2>&1 || true
|
|
rm -f "$sock"
|
|
DISPLAY="$display" cua-driver serve \
|
|
--grant existing-profile \
|
|
--socket "$sock" \
|
|
--pid-file "${log}-cua.pid" \
|
|
--no-overlay \
|
|
>>"${log}-cua.log" 2>&1 &
|
|
local bg=$!
|
|
local n
|
|
for n in $(seq 1 50); do
|
|
if [[ -S "$sock" ]]; then
|
|
return 0
|
|
fi
|
|
if ! kill -0 "$bg" 2>/dev/null; then
|
|
echo "cua-driver serve exited" >&2
|
|
cat "${log}-cua.log" >&2 || true
|
|
return 1
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
echo "cua-driver socket was not ready" >&2
|
|
cat "${log}-cua.log" >&2 || true
|
|
return 1
|
|
}
|
|
|
|
start_xterm() {
|
|
local display="$1"
|
|
local log="$2"
|
|
if alive_pidfile "${log}-xterm.pid"; then
|
|
return 0
|
|
fi
|
|
DISPLAY="$display" SHELL=/bin/zsh lazyboy-terminal >>"${log}-xterm.log" 2>&1 &
|
|
echo $! > "${log}-xterm.pid"
|
|
}
|
|
|
|
start_browser() {
|
|
local display="$1"
|
|
local profile="$2"
|
|
local log="$3"
|
|
local number="${display#:}"
|
|
mkdir -p "$profile" "$ROOT"
|
|
if [[ -n "$profile" ]]; then
|
|
printf '%s\n' "$profile" >"$ROOT/screen-${number}.profile"
|
|
fi
|
|
if pgrep -f "chromium.*--user-data-dir=${profile}" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
rm -f "$profile/SingletonLock" "$profile/SingletonCookie" "$profile/SingletonSocket"
|
|
DISPLAY="$display" HOME="$HOME" LAZYBOY_BROWSER_PROFILE="$profile" \
|
|
lazyboy-browser https://duckduckgo.com >>"${log}-browser.log" 2>&1 &
|
|
echo $! > "${log}-browser.pid"
|
|
}
|
|
|
|
ensure_slot() {
|
|
local slot="$1"
|
|
local profile="${2:-}"
|
|
if ! [[ "$slot" =~ ^[0-9]+$ ]] || (( slot < 0 || slot >= LIMIT )); then
|
|
echo "invalid screen slot" >&2
|
|
return 1
|
|
fi
|
|
local number=$((slot + 1))
|
|
local display=":${number}"
|
|
local vnc_port=$((5900 + slot))
|
|
local view_port=$((6080 + slot))
|
|
local log="$ROOT/screen-${number}"
|
|
local xfce_home="/tmp/xfce-home"
|
|
if (( slot != 0 )); then
|
|
xfce_home="/tmp/xfce-home-${number}"
|
|
fi
|
|
mkdir -p "$ROOT"
|
|
if [[ -n "$profile" ]]; then
|
|
printf '%s\n' "$profile" >"$ROOT/screen-${number}.profile"
|
|
fi
|
|
if xdpyinfo -display "$display" >/dev/null 2>&1 && port_open "$vnc_port" && port_open "$view_port"; then
|
|
start_cua_driver "$display" "$log" || return 1
|
|
if [[ -n "$profile" ]]; then
|
|
start_browser "$display" "$profile" "$log"
|
|
fi
|
|
printf '{"ok":true,"slot":%s,"display":"%s","viewPort":%s,"vncPort":%s}\n' \
|
|
"$slot" "$display" "$view_port" "$vnc_port"
|
|
return 0
|
|
fi
|
|
(
|
|
flock -w 25 9 || {
|
|
echo "screen ${slot} is busy starting" >&2
|
|
exit 1
|
|
}
|
|
if xdpyinfo -display "$display" >/dev/null 2>&1 && port_open "$vnc_port" && port_open "$view_port"; then
|
|
start_cua_driver "$display" "$log" || exit 1
|
|
if [[ -n "$profile" ]]; then
|
|
start_browser "$display" "$profile" "$log"
|
|
fi
|
|
printf '{"ok":true,"slot":%s,"display":"%s","viewPort":%s,"vncPort":%s}\n' \
|
|
"$slot" "$display" "$view_port" "$vnc_port"
|
|
exit 0
|
|
fi
|
|
start_xvfb "$display" "$number" "$log" || {
|
|
echo "Xvfb failed on ${display}" >&2
|
|
cat "${log}-xvfb.log" >&2 || true
|
|
exit 1
|
|
}
|
|
start_desktop "$display" "$xfce_home" "$log"
|
|
start_cua_driver "$display" "$log" || exit 1
|
|
start_xterm "$display" "$log"
|
|
start_vnc "$display" "$vnc_port" "$view_port" "$log" || exit 1
|
|
if [[ -n "$profile" ]]; then
|
|
start_browser "$display" "$profile" "$log"
|
|
fi
|
|
if (( slot == 0 )) && [[ -f "${log}-xvfb.pid" ]]; then
|
|
cp "${log}-xvfb.pid" "$ROOT/xvfb-1.pid"
|
|
fi
|
|
printf '{"ok":true,"slot":%s,"display":"%s","viewPort":%s,"vncPort":%s}\n' \
|
|
"$slot" "$display" "$view_port" "$vnc_port"
|
|
) 9>"$ROOT/screen-${slot}.lock"
|
|
}
|
|
|
|
boot_primary() {
|
|
mkdir -p "$HOME" "$HOME/.local/bin" "$HOME/.config" "$HOME/.browser-profiles" "$ROOT"
|
|
cd "$HOME"
|
|
hydrate_home
|
|
printf '這是 Docker 裡的 Debian 桌面。家目錄會保存在 /home/lazyboy。\n' > "$HOME/README.txt"
|
|
mkdir -p "$HOME/shared" "$HOME/bots"
|
|
ensure_slot 0 ""
|
|
}
|
|
|
|
case "$cmd" in
|
|
boot-primary)
|
|
boot_primary
|
|
;;
|
|
ensure)
|
|
ensure_slot "${1:-0}" "${2:-}"
|
|
;;
|
|
*)
|
|
echo "usage: lazyboy-screen boot-primary|ensure <slot> [profile]" >&2
|
|
exit 2
|
|
;;
|
|
esac
|