Initial commit - NUC server configuration and docs
- CLAUDE.md: Server instructions and service reference - docs/: Persistent documentation (architecture, guides) - .artifacts/: Session-generated notes - playwriter-browser/: Remote browser container config Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
94
playwriter-browser/Dockerfile
Normal file
94
playwriter-browser/Dockerfile
Normal file
@@ -0,0 +1,94 @@
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
# Basic utilities
|
||||
wget \
|
||||
gnupg \
|
||||
curl \
|
||||
ca-certificates \
|
||||
# Xvfb for virtual display
|
||||
xvfb \
|
||||
# VNC server and noVNC
|
||||
x11vnc \
|
||||
novnc \
|
||||
websockify \
|
||||
# Window manager (lightweight)
|
||||
openbox \
|
||||
# For clicking extension icon automatically
|
||||
xdotool \
|
||||
# Credential storage (gnome-keyring for password persistence)
|
||||
gnome-keyring \
|
||||
libsecret-1-0 \
|
||||
libsecret-1-dev \
|
||||
dbus-x11 \
|
||||
# Chrome dependencies
|
||||
fonts-liberation \
|
||||
libasound2 \
|
||||
libatk-bridge2.0-0 \
|
||||
libatk1.0-0 \
|
||||
libatspi2.0-0 \
|
||||
libcups2 \
|
||||
libdbus-1-3 \
|
||||
libdrm2 \
|
||||
libgbm1 \
|
||||
libgtk-3-0 \
|
||||
libnspr4 \
|
||||
libnss3 \
|
||||
libwayland-client0 \
|
||||
libxcomposite1 \
|
||||
libxdamage1 \
|
||||
libxfixes3 \
|
||||
libxkbcommon0 \
|
||||
libxrandr2 \
|
||||
xdg-utils \
|
||||
libu2f-udev \
|
||||
libvulkan1 \
|
||||
# Node.js for Playwriter relay server
|
||||
nodejs \
|
||||
npm \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Google Chrome (better extension support than Chromium)
|
||||
RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg \
|
||||
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y google-chrome-stable \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Playwriter CLI globally
|
||||
RUN npm install -g playwriter
|
||||
|
||||
# Create directories for Chrome profile and keyrings
|
||||
RUN mkdir -p /app \
|
||||
/root/.config/google-chrome/Default/Extensions \
|
||||
/root/.local/share/keyrings \
|
||||
/run/dbus \
|
||||
&& chmod 700 /root/.local/share/keyrings
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy startup scripts
|
||||
COPY start.sh /app/start.sh
|
||||
COPY auto-activate.sh /app/auto-activate.sh
|
||||
RUN chmod +x /app/start.sh /app/auto-activate.sh
|
||||
|
||||
# Run as root (required for dbus/keyring; Chrome uses --no-sandbox in containers)
|
||||
USER root
|
||||
|
||||
# Environment variables
|
||||
ENV DISPLAY=:99
|
||||
ENV HOME=/root
|
||||
ENV GNOME_KEYRING_CONTROL=/tmp/keyring
|
||||
|
||||
# Expose ports
|
||||
# 5900 - VNC
|
||||
# 6080 - noVNC web interface
|
||||
# 19988 - Playwriter WebSocket relay
|
||||
EXPOSE 5900 6080 19988
|
||||
|
||||
# Health check - verify Chrome and relay are running
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||
CMD pgrep -x chrome > /dev/null && curl -s http://localhost:19988 || exit 1
|
||||
|
||||
CMD ["/app/start.sh"]
|
||||
219
playwriter-browser/README.md
Normal file
219
playwriter-browser/README.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# Playwriter Browser Container
|
||||
|
||||
A persistent browser container running on the NUC for AI-driven browser automation via Playwriter MCP.
|
||||
|
||||
## Purpose
|
||||
|
||||
Provides an always-available browser for Claude/AI agents to:
|
||||
- Navigate websites and perform web tasks
|
||||
- Fill forms, click buttons, extract data
|
||||
- Debug web applications
|
||||
- Access services that require browser interaction
|
||||
- Eliminate need for local browser resources
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ NUC Server │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ │ playwriter-browser container │ │
|
||||
│ │ │ │
|
||||
│ │ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │ │
|
||||
│ │ │ Xvfb │ │ Chrome │ │ Playwriter │ │ │
|
||||
│ │ │ :99 │ │ +ext │ │ Relay │ │ │
|
||||
│ │ └──────────┘ └──────────┘ └───────────────┘ │ │
|
||||
│ │ │ │ │ │ │
|
||||
│ │ ┌──────────┐ │ ws://19988 │ │
|
||||
│ │ │ x11vnc │ │ │ │
|
||||
│ │ │ :5901 │ CDP://9222 │ │
|
||||
│ │ └──────────┘ │ │
|
||||
│ │ │ │ │
|
||||
│ │ ┌──────────┐ │ │
|
||||
│ │ │ noVNC │ │ │
|
||||
│ │ │ :6081 │ │ │
|
||||
│ │ └──────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
Web Browser DevTools MCP Playwriter MCP
|
||||
(manual) (AI agent) (AI agent)
|
||||
```
|
||||
|
||||
## Ports
|
||||
|
||||
| Port | Service | Purpose |
|
||||
|------|---------|---------|
|
||||
| 5901 | VNC | Direct VNC client access |
|
||||
| 6081 | noVNC | Web-based browser view |
|
||||
| 19988 | Playwriter | MCP WebSocket relay |
|
||||
| 9222 | CDP | Chrome DevTools Protocol |
|
||||
|
||||
## Deployment to NUC
|
||||
|
||||
### Option 1: Via Coolify
|
||||
|
||||
1. Create a new Docker Compose service in Coolify
|
||||
2. Paste the contents of `docker-compose.yml`
|
||||
3. Deploy
|
||||
|
||||
### Option 2: Direct Docker Compose
|
||||
|
||||
```bash
|
||||
# Copy files to NUC
|
||||
scp -r playwriter-browser nuc:/opt/
|
||||
|
||||
# SSH to NUC and deploy
|
||||
ssh nuc
|
||||
cd /opt/playwriter-browser
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
1. Access noVNC at `http://192.168.1.3:6081/vnc.html`
|
||||
2. Install Playwriter extension from Chrome Web Store:
|
||||
https://chromewebstore.google.com/detail/playwriter-mcp/jfeammnjpkecdekppnclgkkffahnhfhe
|
||||
3. Navigate to any website (e.g., google.com)
|
||||
4. Click the Playwriter extension icon to activate (turns green)
|
||||
5. The extension stays active until container restart
|
||||
|
||||
## Auto-Activation Attempt
|
||||
|
||||
The container includes an auto-activation script that tries to click the extension icon using xdotool. However, due to security design, this may not always work. Manual activation via noVNC is the reliable method.
|
||||
|
||||
```bash
|
||||
# Run auto-activation manually
|
||||
docker exec playwriter-browser /app/auto-activate.sh
|
||||
```
|
||||
|
||||
## MCP Configuration
|
||||
|
||||
### Remote Connection (Recommended)
|
||||
|
||||
Configure Claude Code to use NUC's browser remotely:
|
||||
|
||||
```json
|
||||
{
|
||||
"playwriter": {
|
||||
"command": "npx",
|
||||
"args": ["playwriter", "--host", "ws://192.168.1.3:19988", "--token", "nuc-browser-token"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This connects to the NUC browser container, eliminating local resource usage.
|
||||
|
||||
### Keep Local Browser Option
|
||||
|
||||
If you also want local browser control:
|
||||
|
||||
```json
|
||||
{
|
||||
"playwriter-local": {
|
||||
"command": "npx",
|
||||
"args": ["playwriter"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Chrome DevTools MCP (Alternative)
|
||||
|
||||
For performance analysis and debugging:
|
||||
|
||||
```json
|
||||
{
|
||||
"chrome-devtools": {
|
||||
"command": "npx",
|
||||
"args": ["chrome-devtools-mcp@latest"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `PLAYWRITER_TOKEN` | `nuc-browser-token` | Auth token for remote connections |
|
||||
|
||||
## Session Persistence
|
||||
|
||||
The container persists browser state across restarts using Docker volumes:
|
||||
|
||||
| Volume | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| `playwriter-chrome-profile` | `/root/.config/google-chrome` | Full Chrome profile (cookies, localStorage, passwords, bookmarks) |
|
||||
| `playwriter-browser-keyring` | `/root/.local/share/keyrings` | Encrypted credentials via gnome-keyring |
|
||||
| `playwriter-browser-sessions` | `/root/.config/google-chrome/Default/Sessions` | Browser session tabs |
|
||||
|
||||
### What's Persisted
|
||||
- **Login sessions**: Stay logged into websites (Google, GitHub, etc.)
|
||||
- **Cookies**: Session cookies, preferences
|
||||
- **Passwords**: Chrome password manager entries (encrypted via gnome-keyring)
|
||||
- **localStorage/IndexedDB**: Web app data
|
||||
- **Extensions**: Installed extensions including Playwriter
|
||||
- **Bookmarks**: Any saved bookmarks
|
||||
|
||||
### First Login via noVNC
|
||||
1. Access noVNC: `http://192.168.1.3:6081/vnc.html`
|
||||
2. Navigate to website and log in normally
|
||||
3. Allow Chrome to save password when prompted
|
||||
4. Sessions persist across container restarts
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Via Playwriter MCP
|
||||
|
||||
```javascript
|
||||
// Navigate to a page
|
||||
await page.goto('https://example.com');
|
||||
|
||||
// Get page state
|
||||
console.log(await accessibilitySnapshot({ page }));
|
||||
|
||||
// Interact with elements
|
||||
await page.locator('aria-ref=e5').click();
|
||||
|
||||
// Take screenshot
|
||||
await page.screenshot({ path: '/tmp/shot.png', scale: 'css' });
|
||||
```
|
||||
|
||||
### Via Chrome DevTools MCP
|
||||
|
||||
```
|
||||
"Check the LCP of https://example.com"
|
||||
"Analyze network requests on this page"
|
||||
"Find console errors"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Extension not activated
|
||||
Access noVNC and click the Playwriter extension icon manually.
|
||||
|
||||
### Container unhealthy
|
||||
```bash
|
||||
docker logs playwriter-browser
|
||||
docker exec playwriter-browser pgrep -la chrome
|
||||
```
|
||||
|
||||
### Reset Chrome profile (lose all logins)
|
||||
```bash
|
||||
docker compose down
|
||||
docker volume rm playwriter-chrome-profile playwriter-browser-keyring playwriter-browser-sessions
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Backup browser profile
|
||||
```bash
|
||||
# Create backup of logged-in sessions
|
||||
docker run --rm -v playwriter-chrome-profile:/data -v $(pwd):/backup alpine tar czf /backup/chrome-profile-backup.tar.gz -C /data .
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Playwriter requires explicit activation per tab (security by design)
|
||||
- Only tabs where extension is activated can be controlled
|
||||
- WebSocket relay only accepts localhost connections
|
||||
- VNC has no password (internal network only)
|
||||
64
playwriter-browser/auto-activate.sh
Normal file
64
playwriter-browser/auto-activate.sh
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
# Auto-activate Playwriter extension by clicking its icon
|
||||
# This script attempts to find and click the Playwriter extension icon
|
||||
|
||||
export DISPLAY=:99
|
||||
|
||||
echo "Attempting to auto-activate Playwriter extension..."
|
||||
|
||||
# Wait for Chrome to fully load
|
||||
sleep 5
|
||||
|
||||
# Get Chrome window ID
|
||||
CHROME_WINDOW=$(xdotool search --name "Chrome" | head -1)
|
||||
|
||||
if [ -z "$CHROME_WINDOW" ]; then
|
||||
echo "Chrome window not found. Trying again..."
|
||||
sleep 5
|
||||
CHROME_WINDOW=$(xdotool search --name "Chrome" | head -1)
|
||||
fi
|
||||
|
||||
if [ -n "$CHROME_WINDOW" ]; then
|
||||
echo "Found Chrome window: $CHROME_WINDOW"
|
||||
|
||||
# Focus the Chrome window
|
||||
xdotool windowactivate --sync $CHROME_WINDOW
|
||||
sleep 1
|
||||
|
||||
# Navigate to a page first (extension won't activate on chrome:// pages)
|
||||
xdotool key ctrl+l
|
||||
sleep 0.5
|
||||
xdotool type "https://www.google.com"
|
||||
xdotool key Return
|
||||
sleep 3
|
||||
|
||||
# The extension icon is typically in the top-right corner
|
||||
# Get window geometry
|
||||
GEOMETRY=$(xdotool getwindowgeometry $CHROME_WINDOW)
|
||||
WIDTH=$(echo "$GEOMETRY" | grep "Geometry" | sed 's/.*Geometry: \([0-9]*\)x.*/\1/')
|
||||
|
||||
# Extension icons are usually ~100-200px from the right edge, ~50px from top
|
||||
# Try multiple positions since exact location varies
|
||||
echo "Attempting to click extension icon area..."
|
||||
|
||||
# Click positions to try (relative to window, near extension area)
|
||||
for OFFSET in 100 130 160 190 220; do
|
||||
X_POS=$((WIDTH - OFFSET))
|
||||
Y_POS=50
|
||||
|
||||
echo "Trying position: $X_POS, $Y_POS"
|
||||
xdotool mousemove --window $CHROME_WINDOW $X_POS $Y_POS
|
||||
sleep 0.3
|
||||
xdotool click 1
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "Auto-activation attempts complete."
|
||||
echo "If extension didn't activate, please click it manually via noVNC at:"
|
||||
echo "http://localhost:6080/vnc.html"
|
||||
else
|
||||
echo "Could not find Chrome window. Please activate extension manually."
|
||||
fi
|
||||
|
||||
# Alternative: Use Chrome DevTools Protocol to check extension status
|
||||
# This would require more sophisticated scripting with CDP
|
||||
48
playwriter-browser/docker-compose.yml
Normal file
48
playwriter-browser/docker-compose.yml
Normal file
@@ -0,0 +1,48 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
playwriter-browser:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: playwriter-browser
|
||||
hostname: playwriter-browser
|
||||
ports:
|
||||
- "5901:5900" # VNC (use 5901 to avoid conflicts)
|
||||
- "6081:6080" # noVNC web interface
|
||||
- "19988:19988" # Playwriter WebSocket relay
|
||||
- "9222:9222" # Chrome DevTools Protocol
|
||||
volumes:
|
||||
# Persist full Chrome profile (login sessions, cookies, localStorage, bookmarks, passwords)
|
||||
- chrome-profile:/root/.config/google-chrome
|
||||
# Persist keyrings for encrypted credentials (gnome-keyring)
|
||||
- browser-keyring:/root/.local/share/keyrings
|
||||
# Persist session data for quick restore
|
||||
- browser-sessions:/root/.config/google-chrome/Default/Sessions
|
||||
environment:
|
||||
- DISPLAY=:99
|
||||
- PLAYWRITER_TOKEN=${PLAYWRITER_TOKEN:-nuc-browser-token}
|
||||
# Enable password saving in Chrome
|
||||
- CHROME_ENABLE_PASSWORDS=true
|
||||
# Chrome requires shared memory for stability
|
||||
shm_size: 2gb
|
||||
# Chrome capabilities
|
||||
cap_add:
|
||||
- SYS_ADMIN
|
||||
security_opt:
|
||||
- seccomp:unconfined
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pgrep -x chrome > /dev/null || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
start_period: 60s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
chrome-profile:
|
||||
name: playwriter-chrome-profile
|
||||
browser-keyring:
|
||||
name: playwriter-browser-keyring
|
||||
browser-sessions:
|
||||
name: playwriter-browser-sessions
|
||||
90
playwriter-browser/start.sh
Normal file
90
playwriter-browser/start.sh
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=== Starting Playwriter Browser Container ==="
|
||||
|
||||
# Start Xvfb (virtual display)
|
||||
echo "Starting Xvfb..."
|
||||
Xvfb :99 -screen 0 1920x1080x24 -ac +extension GLX +render -noreset &
|
||||
export DISPLAY=:99
|
||||
sleep 2
|
||||
|
||||
# Initialize D-Bus for gnome-keyring
|
||||
echo "Starting D-Bus session..."
|
||||
mkdir -p /run/dbus
|
||||
if [ ! -f /run/dbus/pid ]; then
|
||||
dbus-daemon --system --fork 2>/dev/null || true
|
||||
fi
|
||||
export $(dbus-launch)
|
||||
echo "D-Bus started: $DBUS_SESSION_BUS_ADDRESS"
|
||||
|
||||
# Initialize gnome-keyring (enables Chrome password saving)
|
||||
echo "Starting gnome-keyring daemon..."
|
||||
mkdir -p /root/.local/share/keyrings
|
||||
# Create default keyring with empty password for headless operation
|
||||
eval $(echo '' | gnome-keyring-daemon --unlock --start --components=secrets,pkcs11 2>/dev/null || true)
|
||||
export GNOME_KEYRING_CONTROL
|
||||
export SSH_AUTH_SOCK
|
||||
echo "Keyring initialized at: $GNOME_KEYRING_CONTROL"
|
||||
|
||||
# Start window manager (needed for proper window handling)
|
||||
echo "Starting Openbox window manager..."
|
||||
openbox &
|
||||
sleep 1
|
||||
|
||||
# Start VNC server
|
||||
echo "Starting VNC server on port 5900..."
|
||||
x11vnc -display :99 -forever -shared -rfbport 5900 -nopw -bg
|
||||
sleep 1
|
||||
|
||||
# Start noVNC web interface
|
||||
echo "Starting noVNC web interface on port 6080..."
|
||||
websockify --web=/usr/share/novnc/ 6080 localhost:5900 &
|
||||
sleep 1
|
||||
|
||||
# Start Playwriter relay server in background (binds to all interfaces for remote access)
|
||||
echo "Starting Playwriter relay server on port 19988..."
|
||||
playwriter serve --host 0.0.0.0 --token "${PLAYWRITER_TOKEN:-nuc-browser-token}" &
|
||||
RELAY_PID=$!
|
||||
sleep 2
|
||||
|
||||
# Start Chrome with remote debugging enabled
|
||||
# The extension needs to be installed from Chrome Web Store on first run
|
||||
echo "Starting Google Chrome..."
|
||||
google-chrome-stable \
|
||||
--no-sandbox \
|
||||
--disable-gpu \
|
||||
--disable-dev-shm-usage \
|
||||
--remote-debugging-port=9222 \
|
||||
--remote-debugging-address=0.0.0.0 \
|
||||
--user-data-dir=/root/.config/google-chrome \
|
||||
--start-maximized \
|
||||
--no-first-run \
|
||||
--disable-default-apps \
|
||||
--disable-background-networking \
|
||||
--disable-sync \
|
||||
--disable-translate \
|
||||
--password-store=gnome \
|
||||
--enable-features=PasswordImport \
|
||||
"chrome://extensions" &
|
||||
CHROME_PID=$!
|
||||
|
||||
echo ""
|
||||
echo "=== Playwriter Browser Ready ==="
|
||||
echo "VNC: vnc://localhost:5900"
|
||||
echo "noVNC Web: http://localhost:6080/vnc.html"
|
||||
echo "Playwriter Relay: ws://localhost:19988"
|
||||
echo "Chrome DevTools: http://localhost:9222"
|
||||
echo ""
|
||||
echo "IMPORTANT: On first run, install Playwriter extension from Chrome Web Store:"
|
||||
echo "https://chromewebstore.google.com/detail/playwriter-mcp/jfeammnjpkecdekppnclgkkffahnhfhe"
|
||||
echo ""
|
||||
echo "Then run: /app/auto-activate.sh to try auto-activating the extension"
|
||||
echo ""
|
||||
|
||||
# Wait for auto-activation script if extension is installed
|
||||
sleep 10
|
||||
/app/auto-activate.sh &
|
||||
|
||||
# Keep container running
|
||||
wait $CHROME_PID
|
||||
Reference in New Issue
Block a user