Files
2026-07-13 12:20:32 +08:00

365 lines
11 KiB
Bash
Executable File

#!/bin/bash
#
# Provision a minimal Debian ARM64 VM with:
# - XFCE desktop
# - Chromium with CDP (remote debugging)
# - Python 3 + Node.js 22
# - pi.dev coding agent + custom extensions
#
# Run as root inside the VM.
set -euo pipefail
if [ "$(id -u)" -ne 0 ]; then
echo "This script must be run as root. Try: su -c '/tmp/provision.sh'"
exit 1
fi
export DEBIAN_FRONTEND=noninteractive
export PATH="/usr/sbin:/usr/local/sbin:/sbin:$PATH"
log() { echo "==> $*"; }
# ---------- bootstrap sudo + passwordless agent ----------
REAL_USER="${REAL_USER:-$(logname 2>/dev/null || echo "agent")}"
log "Installing sudo and making system passwordless"
apt-get update
apt-get install -y sudo
usermod -aG sudo "$REAL_USER"
echo "$REAL_USER ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/$REAL_USER"
chmod 440 "/etc/sudoers.d/$REAL_USER"
passwd -d "$REAL_USER"
passwd -d root
mkdir -p /etc/pam.d
sed -i 's/^auth.*pam_unix.so.*/auth sufficient pam_permit.so/' /etc/pam.d/login 2>/dev/null || true
sed -i 's/^auth.*pam_unix.so.*/auth sufficient pam_permit.so/' /etc/pam.d/su 2>/dev/null || true
log "Configuring passwordless SSH"
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
sed -i 's/^#*PermitEmptyPasswords.*/PermitEmptyPasswords yes/' /etc/ssh/sshd_config
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
sed -i 's/pam_unix.so/pam_unix.so nullok/' /etc/pam.d/common-auth 2>/dev/null || true
sed -i 's/pam_unix.so/pam_unix.so nullok/' /etc/pam.d/sshd 2>/dev/null || true
systemctl restart sshd 2>/dev/null || true
# ---------- system update ----------
log "Updating system packages"
apt-get update
apt-get upgrade -y
# ---------- XFCE desktop ----------
log "Installing XFCE desktop (minimal)"
apt-get install -y --no-install-recommends \
xfce4 \
xfce4-terminal \
xfce4-whiskermenu-plugin \
mousepad \
lightdm \
lightdm-gtk-greeter \
dbus-x11 \
xorg \
fonts-dejavu-core
systemctl set-default graphical.target
systemctl enable lightdm
# ---------- Chromium ----------
log "Installing Chromium"
apt-get install -y --no-install-recommends chromium
# ---------- core dev tools ----------
log "Installing core dev tools"
apt-get install -y --no-install-recommends \
build-essential \
git \
curl \
wget \
unzip \
jq \
htop \
tmux \
vim \
openssh-server \
ca-certificates \
gnupg \
lsb-release \
sqlite3
# ---------- Python ----------
log "Installing Python"
apt-get install -y --no-install-recommends python3 python3-pip python3-venv
ln -sf /usr/bin/python3 /usr/local/bin/python
# ---------- Node.js 22 via NodeSource ----------
log "Installing Node.js 22"
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt-get install -y --no-install-recommends nodejs
# ---------- curl wrapper (HTML→Markdown interception) ----------
log "Installing curl wrapper"
cp /tmp/curl-wrapper.sh /usr/local/bin/curl
chmod 755 /usr/local/bin/curl
# ---------- PDF generation ----------
log "Installing PDF tools"
apt-get install -y --no-install-recommends pandoc weasyprint
# ---------- Python pip packages ----------
log "Installing Python packages"
pip3 install --break-system-packages --no-cache-dir requests beautifulsoup4 Pillow openpyxl
# ---------- X11 tools ----------
log "Installing X11 automation tools"
apt-get install -y --no-install-recommends \
wmctrl \
xdotool \
x11-utils \
xclip \
xdg-utils
# ---------- AT-SPI accessibility (desktop app automation) ----------
log "Installing AT-SPI accessibility tools"
apt-get install -y --no-install-recommends \
at-spi2-core \
libatk-adaptor \
python3-pyatspi \
gir1.2-atspi-2.0
# ---------- Flatpak + Flathub ----------
log "Installing Flatpak with Flathub app store"
apt-get install -y --no-install-recommends flatpak gnome-software-plugin-flatpak
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
# ---------- pi.dev coding agent ----------
log "Installing pi.dev coding agent"
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
# ---------- pi-hermes-memory (persistent memory across sessions) ----------
log "Installing pi-hermes-memory extension"
su - "$REAL_USER" -c "pi install npm:pi-hermes-memory"
PI_AGENT_DIR="/home/$REAL_USER/.pi/agent"
mkdir -p "$PI_AGENT_DIR"
cat > "$PI_AGENT_DIR/hermes-memory-config.json" <<'EOF'
{
"memoryMode": "policy-only",
"memoryPolicyStyle": "full",
"reviewEnabled": true,
"nudgeInterval": 10,
"nudgeToolCalls": 15,
"correctionDetection": true,
"autoConsolidate": true,
"flushOnShutdown": true
}
EOF
chown -R "$REAL_USER:$REAL_USER" "$PI_AGENT_DIR"
# NOTE: pi-computer-use and pi-browser-cdp-extension are NOT installed.
# We use custom extensions in /opt/open-computer/extensions/ instead:
# browser-cdp.ts — our own CDP tool that evaluates JS directly in the page context
# open-browser.ts — launches Chromium on the visible desktop
# ask-user.ts — prompts the user for input via the UI
# ---------- Chromium CDP (remote debugging) ----------
CDP_PORT=9222
log "Configuring Chromium to launch with CDP on port $CDP_PORT"
mkdir -p /etc/chromium.d
cat > /etc/chromium.d/cdp-flags <<EOF
export CHROMIUM_FLAGS="\$CHROMIUM_FLAGS --remote-debugging-port=$CDP_PORT --disable-session-crashed-bubble"
EOF
# ---------- timezone ----------
log "Setting timezone to America/Los_Angeles"
ln -sf /usr/share/zoneinfo/America/Los_Angeles /etc/localtime
echo "America/Los_Angeles" > /etc/timezone
# ---------- a11y CLI tools (desktop app automation for pi agent) ----------
log "Installing a11y CLI tools"
install -m 755 /tmp/a11y-harvest.py /usr/local/bin/a11y-harvest
install -m 755 /tmp/a11y-action.py /usr/local/bin/a11y-action
# Enable AT-SPI accessibility bridge globally so all GTK apps expose their a11y tree
log "Enabling AT-SPI accessibility bridge"
for f in "/home/$REAL_USER/.bash_profile" "/home/$REAL_USER/.profile" "/home/$REAL_USER/.xsessionrc"; do
touch "$f"
grep -q "GTK_MODULES.*atk-bridge" "$f" 2>/dev/null || echo "export GTK_MODULES=gail:atk-bridge" >> "$f"
grep -q "NO_AT_BRIDGE" "$f" 2>/dev/null || echo "export NO_AT_BRIDGE=0" >> "$f"
done
chown "$REAL_USER:$REAL_USER" "/home/$REAL_USER/.bash_profile" "/home/$REAL_USER/.profile" "/home/$REAL_USER/.xsessionrc"
# ---------- win10 theme ----------
log "Applying win10 theme"
REAL_USER="$REAL_USER" bash /tmp/win10/apply-noninteractive.sh
# Install wallpaper to a path outside /opt/open-computer so it survives the dev 9p mount.
# Must be world-readable so the agent user (running XFCE) can access it.
if [ -f /tmp/win10/background.png ]; then
cp /tmp/win10/background.png /usr/share/pixmaps/open-computer-background.png
chmod 644 /usr/share/pixmaps/open-computer-background.png
fi
# ---------- auto-login (optional, for kiosk-style use) ----------
log "Configuring LightDM auto-login for $REAL_USER"
mkdir -p /etc/lightdm/lightdm.conf.d
cat > /etc/lightdm/lightdm.conf.d/50-autologin.conf <<EOF
[Seat:*]
autologin-user=$REAL_USER
autologin-user-timeout=0
EOF
# ---------- VNC + noVNC streaming stack ----------
log "Installing x11vnc, noVNC, and websockify"
apt-get install -y x11vnc novnc websockify
log "Creating systemd service: x11vnc"
cat > /etc/systemd/system/x11vnc.service <<'EOF'
[Unit]
Description=x11vnc VNC server
After=lightdm.service
Requires=lightdm.service
[Service]
Type=simple
ExecStartPre=/bin/sh -c 'while [ ! -e /tmp/.X11-unix/X0 ]; do sleep 0.5; done; sleep 2'
ExecStart=/usr/bin/x11vnc -display :0 -forever -shared -nopw -rfbport 5900 -localhost -auth /var/run/lightdm/root/:0
Restart=always
RestartSec=3
[Install]
WantedBy=graphical.target
EOF
log "Creating systemd service: novnc (websockify)"
cat > /etc/systemd/system/novnc.service <<'EOF'
[Unit]
Description=noVNC WebSocket proxy
After=x11vnc.service
Requires=x11vnc.service
[Service]
Type=simple
ExecStart=/usr/bin/websockify --web /usr/share/novnc 6080 localhost:5900
Restart=always
RestartSec=3
[Install]
WantedBy=graphical.target
EOF
log "Creating systemd service: open-computer-mount (9p dev mount, silently skipped in prod)"
cat > /etc/systemd/system/open-computer-mount.service <<'EOF'
[Unit]
Description=Mount 9p open-computer service directory (dev mode)
DefaultDependencies=no
After=local-fs.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c 'mount -t 9p -o trans=virtio,version=9p2000.L,msize=104857600 open-computer_service /opt/open-computer 2>/dev/null; true'
[Install]
WantedBy=multi-user.target
EOF
log "Creating systemd service: open-computer"
cat > /etc/systemd/system/open-computer.service <<'EOF'
[Unit]
Description=open-computer orchestration service
After=novnc.service open-computer-mount.service
Wants=novnc.service open-computer-mount.service
[Service]
Type=simple
User=agent
WorkingDirectory=/opt/open-computer
ExecStart=/opt/open-computer/start-service.sh
Restart=always
RestartSec=3
Environment=HOME=/home/agent
Environment=PORT=8080
[Install]
WantedBy=graphical.target
EOF
log "Creating systemd service: memory-manager"
cat > /etc/systemd/system/memory-manager.service <<'EOF'
[Unit]
Description=Memory Manager Web UI
After=network.target
[Service]
Type=simple
User=agent
WorkingDirectory=/opt/open-computer/memory-manager
ExecStart=/opt/open-computer/memory-manager/start.sh
Restart=always
RestartSec=3
Environment=HOME=/home/agent
Environment=PORT=8090
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable open-computer-mount open-computer memory-manager
# ---------- mount point for service directory ----------
log "Creating /opt/open-computer mount point"
mkdir -p /opt/open-computer
# ---------- nodemon for dev auto-reload (skip in production builds) ----------
log "Installing nodemon globally"
npm install -g nodemon
# ---------- agent home directories ----------
log "Creating agent directories"
su - "$REAL_USER" -c 'mkdir -p ~/deliverables ~/logs'
# ---------- log hygiene (cron + journald cap) ----------
log "Configuring log rotation"
# Cap systemd journal at 50 MB
mkdir -p /etc/systemd/journald.conf.d
cat > /etc/systemd/journald.conf.d/size-limit.conf <<'JEOF'
[Journal]
SystemMaxUse=50M
RuntimeMaxUse=50M
JEOF
# Cron: delete per-session trace logs older than 3 hours, vacuum journal daily
cat > /etc/cron.d/open-computer-log-hygiene <<'CEOF'
# Delete session trace logs older than 3 hours (runs every 15 minutes)
*/15 * * * * root find /home/agent/logs -name "*.jsonl" -mmin +180 -delete 2>/dev/null
# Vacuum journal daily
30 3 * * * root journalctl --vacuum-size=50M >/dev/null 2>&1
# Weekly fstrim so qcow2 can be compacted
0 4 * * 0 root fstrim -a >/dev/null 2>&1
CEOF
chmod 644 /etc/cron.d/open-computer-log-hygiene
# ---------- cleanup ----------
log "Cleaning up"
apt-get autoremove -y
apt-get clean
rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/*
rm -rf /usr/share/doc/* /usr/share/man/* /usr/share/info/*
rm -rf /usr/share/locale/* /usr/share/i18n/locales/*
rm -rf /usr/share/icons/Adwaita 2>/dev/null || true
rm -rf /usr/share/backgrounds/* 2>/dev/null || true
rm -rf /usr/lib/python3*/test /usr/lib/python3*/__pycache__
rm -rf /tmp/* /var/tmp/*
rm -rf /var/log/*.log /var/log/apt/*
pip3 cache purge 2>/dev/null || true
npm cache clean --force 2>/dev/null || true
find /usr/share/chromium -name "*.pak" ! -name "en-US.pak" ! -name "resources.pak" ! -name "chrome_100_percent.pak" ! -name "chrome_200_percent.pak" -delete 2>/dev/null || true
log "Provision complete! Reboot to start the XFCE desktop."
log " Run 'sudo reboot' then reconnect via open-computer up/ssh/vnc."