lazyBoy/image/computer/lazyboy-screen

323 lines
9.4 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_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
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
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_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