-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·254 lines (223 loc) · 10.4 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·254 lines (223 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
#!/usr/bin/env bash
# Install Browser Agent: project deps, background server, native host, extension setup.
#
# Usage:
# ./install.sh # Step-by-step (prompts for extension ID)
# ./install.sh <extension-id> # Non-interactive
#
# What it does:
# 1. Installs Python dependencies (uv sync)
# 2. Installs Playwright browser binaries
# 3. Registers macOS LaunchAgent (or Linux systemd user unit) so the
# API server auto-starts on login
# 4. Starts the API server immediately
# 5. Registers the native messaging host for OS keychain access
# 6. Registers the Brave policy plist (macOS + BRAVE_INSTALL_POLICY=1)
# 7. Prints instructions to load the unpacked extension in Chrome/Brave
set -euo pipefail
# ── Repo root ──────────────────────────────────────────────────────────
REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
EXTENSION_DIR="${REPO_DIR}/extension"
NATIVE_HOST_DIR="${EXTENSION_DIR}/native_host"
# ── Colors ─────────────────────────────────────────────────────────────
BOLD='\033[1m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
info() { echo -e "${GREEN}==>${NC} ${BOLD}$*${NC}"; }
warn() { echo -e "${YELLOW}==>${NC} ${BOLD}$*${NC}"; }
err() { echo -e "${RED}==>${NC} ${BOLD}$*${NC}" >&2; }
# ── Pre-flight checks ──────────────────────────────────────────────────
if ! command -v uv &>/dev/null; then
err "uv is not installed. Install it first:"
err " curl -LsSf https://astral.sh/uv/install.sh | sh"
exit 1
fi
if ! command -v node &>/dev/null; then
warn "node is not in PATH — Playwright browser install may fail."
warn "Install Node.js from https://nodejs.org/"
fi
# ── 1. Install project deps ────────────────────────────────────────────
info "Installing Python dependencies …"
cd "${REPO_DIR}"
uv sync
# ── 2. Install Playwright browsers ────────────────────────────────────
info "Installing Playwright browsers …"
uv run playwright install --with-deps chromium 2>/dev/null || true
# ── 3. Register background service ─────────────────────────────────────
PLIST_LABEL="com.browseragent.server"
PLIST_DEST=""
SERVICE_STARTED=false
case "$(uname -s)" in
Darwin)
PLIST_DEST="${HOME}/Library/LaunchAgents/${PLIST_LABEL}.plist"
info "Installing LaunchAgent at ${PLIST_DEST} …"
mkdir -p "${HOME}/Library/LaunchAgents"
cat > "${PLIST_DEST}" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>${PLIST_LABEL}</string>
<key>ProgramArguments</key>
<array>
<string>${REPO_DIR}/.venv/bin/uv</string>
<string>run</string>
<string>browser-agent-ui</string>
</array>
<key>WorkingDirectory</key>
<string>${REPO_DIR}</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>${HOME}/Library/Logs/browser-agent-ui.log</string>
<key>StandardErrorPath</key>
<string>${HOME}/Library/Logs/browser-agent-ui.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>${REPO_DIR}/.venv/bin:/usr/local/bin:/usr/bin:/bin</string>
</dict>
</dict>
</plist>
PLIST
# Unload any previous version, then load the new one.
launchctl bootout "gui/$(id -u)/${PLIST_LABEL}" 2>/dev/null || true
launchctl bootstrap "gui/$(id -u)" "${PLIST_DEST}" 2>/dev/null || true
SERVICE_STARTED=true
info "LaunchAgent registered. The server will auto-start on login."
;;
Linux)
UNIT_DEST="${HOME}/.config/systemd/user/${PLIST_LABEL}.service"
info "Installing systemd user unit at ${UNIT_DEST} …"
mkdir -p "${HOME}/.config/systemd/user"
cat > "${UNIT_DEST}" <<UNIT
[Unit]
Description=Browser Agent API Server
After=network.target
[Service]
ExecStart=${REPO_DIR}/.venv/bin/uv run browser-agent-ui
WorkingDirectory=${REPO_DIR}
Restart=on-failure
RestartSec=3
Environment=PATH=${REPO_DIR}/.venv/bin:/usr/local/bin:/usr/bin:/bin
[Install]
WantedBy=default.target
UNIT
systemctl --user daemon-reload 2>/dev/null || true
systemctl --user enable "${PLIST_LABEL}.service" 2>/dev/null || true
systemctl --user start "${PLIST_LABEL}.service" 2>/dev/null || true
SERVICE_STARTED=true
info "Systemd unit registered. The server will auto-start on login."
;;
*)
warn "Unsupported platform: $(uname -s). Skipping background service."
;;
esac
# ── 4. Start the server immediately ────────────────────────────────────
if [ "${SERVICE_STARTED}" = false ]; then
info "Starting API server in background …"
cd "${REPO_DIR}"
nohup uv run browser-agent-ui > /tmp/browser-agent-ui.log 2>&1 &
disown
# Give it a moment to start.
sleep 2
info "Server started."
else
info "Background service started by launchctl/systemd."
fi
# ── 5. Register native messaging host ──────────────────────────────────
cd "${REPO_DIR}"
EXTENSION_ID="${1:-}"
if [ -z "${EXTENSION_ID}" ]; then
warn "No extension ID supplied — native host will NOT be installed."
info "An unpinned native host (allowed_origins: any extension) would"
info "let any installed extension read your OS keychain. Skipping."
info ""
info "After loading the extension in chrome://extensions, re-run:"
info " ${0} <extension-id>"
info "to register and pin the host to your specific extension."
else
bash "${NATIVE_HOST_DIR}/install.sh" "${EXTENSION_ID}"
fi
# ── 6. (macOS) Brave policy ──────────────────────────────────────────
if [[ "$(uname -s)" == "Darwin" && -n "${EXTENSION_ID}" && "${BRAVE_INSTALL_POLICY:-0}" == "1" ]]; then
info "Writing Brave policy plist …"
BRAVE_INSTALL_POLICY=1 bash "${NATIVE_HOST_DIR}/install.sh" "${EXTENSION_ID}" 2>/dev/null || true
fi
# ── 7. Instructions ───────────────────────────────────────────────────
echo ""
echo -e "${GREEN}${BOLD}✓ Browser Agent installed.${NC}"
echo ""
echo " ${BOLD}Load the extension:${NC}"
echo " 1. Open Chrome/Brave and go to chrome://extensions"
echo " 2. Enable 'Developer mode' (top-right toggle)"
echo " 3. Click 'Load unpacked' and select:"
echo " ${EXTENSION_DIR}"
echo " 4. Copy the 32-character extension ID from the tile"
echo " 5. Re-run: ${0} <extension-id>"
echo ""
echo " ${BOLD}Start a task:${NC}"
echo " Click the extension icon → side panel opens."
echo " Set your API key and model in the Settings tab."
echo " Type a task in the Chat tab (e.g. 'go to example.com')."
echo ""
echo " ${BOLD}Use your own Chrome (CDP connection):${NC}"
echo " By default the agent spawns its own headless browser. To make it"
echo " use the Chrome you already have open (so it can see and interact"
echo " with your real tabs):"
echo " 1. Quit Chrome completely"
echo " 2. Re-launch with the debug port BOUND TO LOOPBACK ONLY:"
echo " macOS: /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome \\"
echo " --remote-debugging-port=9222 \\"
echo " --remote-debugging-address=127.0.0.1"
echo " Linux: google-chrome --remote-debugging-port=9222 \\"
echo " --remote-debugging-address=127.0.0.1"
echo ""
echo " SECURITY: the --remote-debugging-address=127.0.0.1 flag is"
echo " REQUIRED. Without it, Chrome binds 0.0.0.0 and ANY host on"
echo " your network (or a browser visiting a malicious site while"
echo " on the same LAN) can connect to the debug port, dump every"
echo " tab, read every cookie, and execute JS in any origin. CDP"
echo " gives the holder FULL control of the browser — treat the"
echo " debug port like an open root shell."
echo ""
echo " 3. In the extension Settings tab, set 'Connect to my Chrome (CDP URL)'"
echo " to: http://127.0.0.1:9222 (NOT localhost, NOT 192.168.x.x)"
echo " 4. Save. The agent now attaches to your existing Chrome session."
echo ""
echo " The server will reject any non-loopback CDP URL with a 400,"
echo " even if you typed one in by hand — see config.py's cdp_url"
echo " validator for details."
echo ""
echo " ${BOLD}Security: DNS rebinding${NC}"
echo " The API server binds to 127.0.0.1:8000 (loopback only). If you"
echo " ever need to access it from another machine, do NOT change the"
echo " bind address to 0.0.0.0 — a DNS rebinding attack would let a"
echo " malicious website (e.g. evil.com resolving to 127.0.0.1) bypass"
echo " the same-origin policy and call the API as if it were local."
echo " Keep the server on loopback; use a reverse proxy (nginx, Caddy)"
echo " with TLS + auth if remote access is required."
echo ""
echo " ${BOLD}Security: SSE over HTTP${NC}"
echo " The /api/task/{id}/stream endpoint uses Server-Sent Events over"
echo " plain HTTP (http://127.0.0.1:8000). This is safe on loopback"
echo " (no network hop), but if you proxy the server behind a reverse"
echo " proxy, ensure TLS is terminated at the proxy. Without HTTPS, any"
echo " network observer on the same LAN could read task results, injected"
echo " credentials, or session cookies streamed over the SSE channel."
echo ""
echo " ${BOLD}Server status:${NC}"
echo " The API server runs on http://127.0.0.1:8000"
if [[ "$(uname -s)" == "Darwin" ]]; then
echo " Manage the service: launchctl kickstart gui/$(id -u)/${PLIST_LABEL}"
echo " Logs: ~/Library/Logs/browser-agent-ui.log"
elif [[ "$(uname -s)" == "Linux" ]]; then
echo " Manage the service: systemctl --user restart ${PLIST_LABEL}"
fi
echo ""