Repository navigation
Expand file tree
/
Copy pathsetup.sh
More file actions
executable file
·314 lines (293 loc) · 14.3 KB
/
Copy pathsetup.sh
File metadata and controls
executable file
·314 lines (293 loc) · 14.3 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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
#!/usr/bin/env bash
# autoresearcherUI — one-command installer for a fresh GPU node.
#
# git clone <repo> autoresearcherui
# cd autoresearcherui
# ./setup.sh
#
# What it does:
# 1. installs system deps (python3, tmux, curl)
# 2. installs uv + the autoresearcherUI package (and its python deps)
# 3. starts the backend in tmux session 'arui'
# 4. opens a cloudflared quick-tunnel in tmux 'arui-cf' and prints the URL
# so you can hit the dashboard from anywhere — no Tailscale needed
#
# Flags:
# --no-tunnel skip cloudflared (localhost only)
# --yes non-interactive
#
# Re-running setup.sh is safe; it restarts everything.
set -e
cd "$(dirname "$0")"
ROOT="$(pwd)"
YES=0; NO_TUNNEL=0
for a in "$@"; do
case "$a" in
--yes) YES=1 ;;
--no-tunnel) NO_TUNNEL=1 ;;
esac
done
bold() { printf "\033[1m%s\033[0m\n" "$*"; }
step() { printf " \033[1;36m→\033[0m %s\n" "$*"; }
ok() { printf " \033[1;32m✓\033[0m %s\n" "$*"; }
warn() { printf " \033[1;33m!\033[0m %s\n" "$*"; }
SUDO=""
[ "$(id -u)" -ne 0 ] && command -v sudo >/dev/null 2>&1 && SUDO="sudo"
bold "┌────────────────────────────────────────────┐"
bold "│ autoresearcherUI — fresh node setup │"
bold "└────────────────────────────────────────────┘"
# ── 1. preflight ────────────────────────────────────────────────────────
if command -v nvidia-smi >/dev/null 2>&1; then
GPUS=$(nvidia-smi --query-gpu=name --format=csv,noheader | wc -l | tr -d ' ')
ok "detected $GPUS GPU(s)"
else
warn "no NVIDIA GPU detected — UI will still work, agent will run on CPU"
fi
# ── 2. system packages (cross-distro + macOS) ───────────────────────────
# Detect an available package manager so a fresh macOS laptop (brew) works the
# same as a Linux GPU box (apt/dnf/yum/apk/pacman).
PKG=""
if command -v apt-get >/dev/null 2>&1; then PKG=apt
elif command -v dnf >/dev/null 2>&1; then PKG=dnf
elif command -v yum >/dev/null 2>&1; then PKG=yum
elif command -v apk >/dev/null 2>&1; then PKG=apk
elif command -v pacman >/dev/null 2>&1; then PKG=pacman
elif command -v brew >/dev/null 2>&1; then PKG=brew
fi
[ -n "$PKG" ] && ok "package manager: $PKG" \
|| warn "no supported package manager (apt/dnf/yum/apk/pacman/brew) found"
pkg_install() { # pkg_install <pkg> [pkg...] - best-effort, never aborts
case "$PKG" in
apt) $SUDO apt-get install -y -qq "$@" >/dev/null 2>&1 ;;
dnf) $SUDO dnf install -y -q "$@" >/dev/null 2>&1 ;;
yum) $SUDO yum install -y -q "$@" >/dev/null 2>&1 ;;
apk) $SUDO apk add --quiet "$@" >/dev/null 2>&1 ;;
pacman) $SUDO pacman -Sy --noconfirm "$@" >/dev/null 2>&1 ;;
brew) brew install "$@" >/dev/null 2>&1 ;; # brew: no sudo
*) return 1 ;;
esac
}
step "installing system packages (python3, tmux, curl, TeX Live)…"
[ "$PKG" = "apt" ] && { $SUDO apt-get update -qq >/dev/null 2>&1 || true; }
# tmux is REQUIRED - both agents run inside tmux sessions. Try to install it via
# whatever package manager exists, then hard-fail with clear guidance if it is
# still missing (the app cannot run without it).
if ! command -v tmux >/dev/null 2>&1; then
step "installing tmux (required - agents run in tmux sessions)…"
pkg_install tmux || true
fi
if ! command -v tmux >/dev/null 2>&1; then
warn "tmux could not be installed automatically."
{
echo " autoresearcherUI runs its agents inside tmux and cannot continue"
echo " without it. Install tmux, then re-run: bash setup.sh"
echo " macOS: brew install tmux"
echo " Debian/Ubuntu: sudo apt-get install tmux"
echo " Fedora/RHEL: sudo dnf install tmux"
echo " Alpine: sudo apk add tmux"
echo " Arch: sudo pacman -S tmux"
} >&2
exit 1
fi
ok "tmux $(tmux -V 2>/dev/null | awk '{print $2}') present"
if [ -n "$PKG" ]; then
# python3 + curl are needed too; best-effort across managers (already present
# on most systems). On apt we also want the venv/pip extras.
if [ "$PKG" = "apt" ]; then
$SUDO apt-get install -y -qq python3 python3-venv python3-pip curl \
ca-certificates >/dev/null 2>&1 || warn "some apt packages may have failed"
else
command -v python3 >/dev/null 2>&1 || pkg_install python3 || true
command -v curl >/dev/null 2>&1 || pkg_install curl || true
fi
# TeX Live for Paper Mode's pdflatex build. Without this, every paper
# render shows "TeX Live not installed" and the PDF iframe stays
# empty — Francois hit this on the live pod 2026-06-06. The
# -recommended bundles are ~700 MB instead of 4 GB for texlive-full;
# they cover everything our LaTeX template uses (article + amsmath +
# graphicx + hyperref + booktabs + geometry). We swallow failures
# because TeX Live isn't required for research-mode — the warning
# surfaces in the UI instead.
if [ "$PKG" = "apt" ] && ! command -v pdflatex >/dev/null 2>&1; then
step "installing TeX Live (pdflatex — needed by Paper Mode)…"
$SUDO apt-get install -y -qq --no-install-recommends \
texlive-latex-recommended texlive-fonts-recommended \
texlive-latex-extra >/dev/null 2>&1 \
&& ok "pdflatex installed" \
|| warn "TeX Live install failed — Paper Mode PDF builds will warn"
elif ! command -v pdflatex >/dev/null 2>&1; then
warn "pdflatex not found — install a TeX distribution for Paper Mode PDFs" \
"(macOS: brew install --cask mactex-no-gui)"
fi
fi
# ── 3. python deps via uv (fast) with pip fallback ──────────────────────
if ! command -v uv >/dev/null 2>&1; then
step "installing uv (fast python package manager)…"
curl -LsSf https://astral.sh/uv/install.sh | sh >/dev/null 2>&1 || true
export PATH="$HOME/.local/bin:$PATH"
fi
step "installing autoresearcherUI python deps…"
if command -v uv >/dev/null 2>&1; then
uv venv .venv >/dev/null 2>&1 || true
uv pip install --python .venv -e . -q
else
python3 -m venv .venv
.venv/bin/pip install -q --upgrade pip
.venv/bin/pip install -q -e .
fi
ok "deps installed"
# ── 4a. Node.js (needed by Claude Code) ─────────────────────────────────
if ! command -v node >/dev/null 2>&1; then
step "installing Node.js (Claude Code's runtime)…"
if command -v apt-get >/dev/null 2>&1; then
# nvm-free path: use NodeSource's prebuilt apt repo.
curl -fsSL https://deb.nodesource.com/setup_lts.x | $SUDO bash - \
>/dev/null 2>&1 || warn "could not add NodeSource repo"
$SUDO apt-get install -y -qq nodejs >/dev/null 2>&1 \
|| warn "apt-get install nodejs failed"
fi
if command -v node >/dev/null 2>&1; then
ok "node $(node --version) installed"
else
warn "Node install didn't finish — install it manually then re-run setup.sh"
fi
fi
# ── 4b. Claude Code (the research + author agent runtimes) ──────────────
if ! command -v claude >/dev/null 2>&1; then
step "installing Claude Code (the autonomous agent)…"
if command -v npm >/dev/null 2>&1; then
# --location=global is the future-proof flag; fall back to -g for older
# npm versions. Either way the binary lands in /usr/local/bin/claude.
$SUDO npm install -g --location=global @anthropic-ai/claude-code \
>/dev/null 2>&1 \
|| $SUDO npm install -g @anthropic-ai/claude-code >/dev/null 2>&1 \
|| warn "npm install -g @anthropic-ai/claude-code failed"
fi
if command -v claude >/dev/null 2>&1; then
ok "claude $(claude --version 2>/dev/null | head -1) installed"
else
warn "Claude Code missing — research agent won't spawn." \
"Install with: npm install -g @anthropic-ai/claude-code"
fi
fi
# ── 4c. cloudflared (for the public URL) ────────────────────────────────
if [ "$NO_TUNNEL" -eq 0 ] && ! command -v cloudflared >/dev/null 2>&1; then
step "installing cloudflared (gives you a public https URL)…"
ARCH=$(uname -m)
case "$ARCH" in
x86_64) CF=cloudflared-linux-amd64 ;;
aarch64) CF=cloudflared-linux-arm64 ;;
*) CF="" ;;
esac
if [ -n "$CF" ]; then
$SUDO curl -fsSL -o /usr/local/bin/cloudflared \
"https://github.com/cloudflare/cloudflared/releases/latest/download/$CF" \
&& $SUDO chmod +x /usr/local/bin/cloudflared && ok "cloudflared installed" \
|| warn "cloudflared install failed — will fall back to localhost"
else
warn "unknown arch $ARCH — skipping cloudflared"
fi
fi
# ── 5. start backend in tmux 'arui' (with supervisor loop) ──────────────
step "starting backend in tmux session 'arui'…"
PORT="${PORT:-8000}"
tmux kill-session -t arui 2>/dev/null || true
LOG="$ROOT/data/arui.log"
mkdir -p "$ROOT/data"
: > "$LOG"
# SUPERVISE the backend the same way we supervise cloudflared. Without
# this, if a user (or operator, hi Francois) ever sends Ctrl-C into
# the 'arui' pane, the backend stays dead until someone SSHes back in
# and relaunches it manually. The while-loop respawns on any exit
# (including OOM, Python tracebacks, accidental Ctrl-C in the pane),
# and ARUI_PORT is pinned so a respawn can't accidentally land on
# a different port and orphan cloudflared.
tmux new-session -d -s arui \
"cd $ROOT && while true; do \
ARUI_PORT=$PORT .venv/bin/python -m backend.main 2>&1 | tee -a $LOG; \
echo \"[arui] backend exited at \$(date -u +%FT%TZ); respawning in 2s\" >>$LOG; \
sleep 2; \
done"
# wait for /healthz
for i in $(seq 1 40); do
if curl -fsS "http://127.0.0.1:$PORT/healthz" >/dev/null 2>&1; then
ok "backend is up on http://127.0.0.1:$PORT"
break
fi
sleep 0.5
done
# ── 6. cloudflared tunnel ───────────────────────────────────────────────
URL="http://localhost:$PORT"
if [ "$NO_TUNNEL" -eq 0 ] && command -v cloudflared >/dev/null 2>&1; then
step "opening cloudflared tunnel in tmux 'arui-cf'…"
CFLOG="$ROOT/data/cloudflared.log"
: > "$CFLOG"
tmux kill-session -t arui-cf 2>/dev/null || true
# SUPERVISE the tunnel — wrap cloudflared in a while-loop so the
# tunnel auto-respawns when it dies. Without this, ANY backend
# restart (or a network blip) kills cloudflared permanently and the
# bookmarked URL goes to NXDOMAIN. trycloudflare assigns a fresh
# random hostname on each respawn, so the user grabs it via the
# `/api/url` endpoint (or by tailing data/cloudflared.log) — the
# important thing is the tunnel never just dies and stays dead.
tmux new-session -d -s arui-cf \
"while true; do cloudflared tunnel --url http://localhost:$PORT 2>&1 | tee -a $CFLOG; echo '[arui-cf] cloudflared exited; respawning in 2s' >>$CFLOG; sleep 2; done"
# parse the public URL out of cloudflared's log
for i in $(seq 1 40); do
PUB=$(grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' "$CFLOG" \
| head -1 || true)
[ -n "$PUB" ] && { URL="$PUB"; break; }
sleep 0.5
done
[ -n "$PUB" ] && ok "tunnel up: $URL" \
|| warn "tunnel didn't print a URL — check tmux attach -t arui-cf"
fi
# ── 6b. cron watchdog (resurrects the backend if its tmux session dies) ──
# PR 10's in-session supervisor only survives a process crash *inside* the
# tmux session. If the whole session/server dies (tmux-server crash, an
# operator `tmux kill-server`, the session getting unlinked, or — the case
# that stranded the pod on 2026-06-06 — it simply vanishing), nothing brings
# it back. These pods are containers with docker-init as PID 1 (no systemd),
# so cron is the right resurrection layer: it's a separate subsystem that
# survives a tmux-server death. bin/arui_watchdog.sh is idempotent and only
# (re)creates the infra sessions; it never touches the agent/author REPLs.
# Install the `arun` run-launcher onto PATH so the research agent can stream
# every run live to the Sessions tab (instead of redirecting to a log file,
# which leaves the Sessions terminal blank).
step "installing the arun run-launcher onto PATH…"
chmod +x "$ROOT/bin/arun" 2>/dev/null || true
$SUDO ln -sf "$ROOT/bin/arun" /usr/local/bin/arun 2>/dev/null \
|| cp -f "$ROOT/bin/arun" /usr/local/bin/arun 2>/dev/null || true
step "installing the backend watchdog (cron → bin/arui_watchdog.sh)…"
chmod +x "$ROOT/bin/arui_watchdog.sh" 2>/dev/null || true
if command -v apt-get >/dev/null 2>&1 && ! command -v crontab >/dev/null 2>&1; then
$SUDO apt-get install -y -qq cron >/dev/null 2>&1 || warn "cron install failed"
fi
if command -v crontab >/dev/null 2>&1; then
# Start cron without systemd (containers): service script, then the
# bare binary as a fallback. Harmless if already running.
$SUDO service cron start >/dev/null 2>&1 || $SUDO cron >/dev/null 2>&1 || true
CRON_LINE="* * * * * ARUI_ROOT=$ROOT ARUI_PORT=$PORT $ROOT/bin/arui_watchdog.sh >/dev/null 2>&1"
# Idempotent install: drop any prior arui_watchdog line, re-add one.
( crontab -l 2>/dev/null | grep -v 'arui_watchdog.sh' ; echo "$CRON_LINE" ) \
| crontab - 2>/dev/null \
&& ok "watchdog registered (runs every minute)" \
|| warn "could not register watchdog crontab — backend won't self-resurrect"
else
warn "cron unavailable — backend won't auto-resurrect if its tmux session dies"
fi
# NOTE: cron itself does not auto-start on a *container* restart. For full
# resilience across pod restarts, set the pod's On-Start / entrypoint to:
# cd $ROOT && ./setup.sh --yes
echo " Tip: set the pod's On-Start command to: cd $ROOT && ./setup.sh --yes"
# ── 7. done ─────────────────────────────────────────────────────────────
echo
bold "✅ autoresearcherUI is running."
echo " Dashboard: $URL"
echo " Backend logs: tmux attach -t arui (Ctrl-b d to detach)"
[ "$NO_TUNNEL" -eq 0 ] && \
echo " Tunnel logs: tmux attach -t arui-cf"
echo
echo " Next: open $URL in your browser and complete onboarding."
echo " (paste a Claude API key + your purpose, and the agent starts.)"
echo