forked from innotelinc/olympus
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsetup.sh
More file actions
executable file
·473 lines (410 loc) · 20 KB
/
Copy pathsetup.sh
File metadata and controls
executable file
·473 lines (410 loc) · 20 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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
#!/usr/bin/env bash
set -euo pipefail
# olympus - Zero-Cost Coding Agent Platform Setup
# Usage: chmod +x setup.sh && ./setup.sh
# Run-time: Local CPU only - all heavy inference routed to free cloud providers
# - Frontend: Hermes 3 (70B) via OpenRouter Free
# - Backend: Codex via OmniRoute (auto/coding, Responses API)
ROOT_DIR="$(cd "$(dirname "$0")" && pwd)"
CORE_DIR="$ROOT_DIR/core-modules"
FACTORY_ENV="$ROOT_DIR/.factory-env"
ARCHON_CONFIG="$ROOT_DIR/.archon/config.yaml"
# ──────────────────────────────────────────────
# Colors & Helpers
# ──────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
success() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
die() { error "$*"; exit 1; }
header() {
echo ""
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
echo -e "${BOLD} $*${NC}"
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
}
# ──────────────────────────────────────────────
# 1. Validate System Requirements
# ──────────────────────────────────────────────
check_command() {
local cmd="$1"
local install_hint="$2"
if ! command -v "$cmd" >/dev/null 2>&1; then
error "Missing required command: $cmd"
echo -e " Install hint: $install_hint"
return 1
fi
local ver
ver=$("$cmd" --version 2>&1 | head -n1 || echo "unknown")
success "$cmd found — $ver"
return 0
}
validate_requirements() {
header "Validating System Requirements"
local missing=0
check_command "git" "https://git-scm.com/downloads | sudo apt install git | brew install git" || missing=1
check_command "bun" "curl -fsSL https://bun.sh/install | bash && source ~/.bashrc" || missing=1
check_command "python3" "https://www.python.org/downloads | sudo apt install python3 python3-venv | brew install python3" || missing=1
check_command "gh" "https://cli.github.com/ | sudo apt install gh | brew install gh — then run: gh auth login" || missing=1
check_command "npm" "https://nodejs.org/ | sudo apt install nodejs npm | brew install node (npm ships with node)" || missing=1
# Extra version floors
if command -v python3 >/dev/null 2>&1; then
if ! python3 -c "import sys; exit(0 if sys.version_info >= (3,10) else 1)" 2>/dev/null; then
warn "python3 version < 3.10 detected — 3.10+ recommended for factory consumer"
fi
fi
if command -v bun >/dev/null 2>&1; then
local bun_major
bun_major=$(bun --version 2>/dev/null | cut -d. -f1 || echo "0")
if [[ "$bun_major" -lt 1 ]]; then
warn "bun version < 1.0 detected — please upgrade: bun upgrade"
fi
fi
# pip / venv check (non-fatal but warned)
if ! python3 -m pip --version >/dev/null 2>&1; then
warn "pip not available for python3 — factory Python deps may fail. Install with: python3 -m ensurepip --upgrade"
fi
if [[ $missing -ne 0 ]]; then
echo ""
die "One or more required tools are missing. Install them and re-run: chmod +x setup.sh && ./setup.sh"
fi
success "All required tools present"
}
# ──────────────────────────────────────────────
# 2. Create core-modules folder
# ──────────────────────────────────────────────
prepare_core_dir() {
header "Preparing core-modules"
mkdir -p "$CORE_DIR"
success "Created $CORE_DIR"
mkdir -p "$ROOT_DIR/.archon/cache"
mkdir -p "$ROOT_DIR/factory"
# Local dev: build-requests is the trigger, builds is the factory output (both documented, builds/ is gitignored)
mkdir -p "$ROOT_DIR/build-requests"
mkdir -p "$ROOT_DIR/builds"
if [[ ! -f "$ROOT_DIR/build-requests/.gitkeep" && -z "$(ls -A "$ROOT_DIR/build-requests" 2>/dev/null | grep -v README)" ]]; then
touch "$ROOT_DIR/build-requests/.gitkeep" 2>/dev/null || true
fi
}
# ──────────────────────────────────────────────
# 3. Clone vendor tools if they do not exist
# ──────────────────────────────────────────────
clone_if_missing() {
local name="$1"
local repo="$2"
local fallback="$3"
local dest="$CORE_DIR/$name"
if [[ -d "$dest/.git" ]]; then
info "$name already cloned at $dest — pulling latest..."
git -C "$dest" pull --ff-only || warn "git pull failed for $name — continuing with existing checkout"
elif [[ -d "$dest" && -n "$(ls -A "$dest" 2>/dev/null)" ]]; then
warn "$dest exists but is not a git repo — skipping clone for $name"
else
info "Cloning $name from $repo ..."
if git clone --depth 1 "$repo" "$dest" 2>/dev/null; then
success "$name cloned (mirror)"
elif [[ -n "$fallback" ]] && git clone --depth 1 "$fallback" "$dest" 2>/dev/null; then
success "$name cloned (upstream fallback: $fallback)"
else
error "Failed to clone $name from $repo ${fallback:+or $fallback}"
return 1
fi
fi
}
clone_vendor_tools() {
header "Cloning Vendor Tools"
# Vendored mirrors under innotelinc (full-history copies of the upstreams, see
# UPSTREAMS.md) so the factory survives upstream deletion/moves. The original
# upstream is kept as fallback for updates. Override via env vars:
# OMNIROUTE_REPO, ARCHON_REPO, FACTORY_REPO
local OMNIROUTE_REPO="${OMNIROUTE_REPO:-https://github.com/innotelinc/omniroute.git}"
local OMNIROUTE_UPSTREAM="https://github.com/diegosouzapw/OmniRoute.git"
local ARCHON_REPO="${ARCHON_REPO:-https://github.com/innotelinc/Archon.git}"
local ARCHON_UPSTREAM="https://github.com/coleam00/Archon.git"
local FACTORY_REPO="${FACTORY_REPO:-https://github.com/innotelinc/ai-software-factory.git}"
local FACTORY_UPSTREAM="https://github.com/coleam00/ai-software-factory.git"
clone_if_missing "omniroute" "$OMNIROUTE_REPO" "$OMNIROUTE_UPSTREAM"
clone_if_missing "archon" "$ARCHON_REPO" "$ARCHON_UPSTREAM"
clone_if_missing "ai-software-factory" "$FACTORY_REPO" "$FACTORY_UPSTREAM"
success "All vendor tools ready under $CORE_DIR/"
}
# ──────────────────────────────────────────────
# 4. Run bun install inside respective directories
# ──────────────────────────────────────────────
bun_install_dir() {
local dir="$1"
if [[ -f "$dir/package.json" ]]; then
info "Running bun install in $dir ..."
(cd "$dir" && bun install)
success "bun install complete: $dir"
elif [[ -f "$dir/bun.lockb" || -f "$dir/bun.lock" ]]; then
info "Running bun install in $dir (lockfile present) ..."
(cd "$dir" && bun install)
success "bun install complete: $dir"
else
warn "No package.json in $dir — skipping bun install"
fi
}
install_dependencies() {
header "Installing Dependencies (bun)"
bun_install_dir "$CORE_DIR/omniroute"
bun_install_dir "$CORE_DIR/archon"
bun_install_dir "$CORE_DIR/ai-software-factory"
# Root package.json if present (optional monorepo wrapper)
if [[ -f "$ROOT_DIR/package.json" ]]; then
bun_install_dir "$ROOT_DIR"
fi
}
# ──────────────────────────────────────────────
# 5. Initialize Software Factory setup wizard programmatically
# ──────────────────────────────────────────────
init_factory_wizard() {
header "Initializing AI Software Factory"
local factory_dir="$CORE_DIR/ai-software-factory"
# Mirror factory runner into top-level factory/ for the documented path:
# python3 factory/consumer.py tick
if [[ -f "$factory_dir/consumer.py" && ! -f "$ROOT_DIR/factory/consumer.py" ]]; then
cp "$factory_dir/consumer.py" "$ROOT_DIR/factory/consumer.py" 2>/dev/null || true
fi
if [[ -d "$factory_dir/factory" && ! -f "$ROOT_DIR/factory/consumer.py" ]]; then
# Some forks nest under factory/factory
cp "$factory_dir/factory/consumer.py" "$ROOT_DIR/factory/consumer.py" 2>/dev/null || true
fi
# Preferred: factory's own setup wizard
if [[ -f "$factory_dir/setup.py" ]]; then
info "Running factory setup wizard: python3 setup.py --non-interactive"
(cd "$factory_dir" && python3 setup.py --non-interactive 2>/dev/null) \
|| (cd "$factory_dir" && python3 setup.py --yes 2>/dev/null) \
|| (cd "$factory_dir" && python3 -m factory.setup --init 2>/dev/null) \
|| warn "Factory setup wizard exited non-zero — you can re-run manually: cd $factory_dir && python3 setup.py"
success "Factory wizard invoked"
elif [[ -f "$factory_dir/scripts/setup.sh" ]]; then
info "Running factory scripts/setup.sh"
bash "$factory_dir/scripts/setup.sh" --non-interactive 2>/dev/null || bash "$factory_dir/scripts/setup.sh" || warn "factory scripts/setup.sh failed"
elif [[ -f "$factory_dir/install.py" ]]; then
info "Running factory install.py"
(cd "$factory_dir" && python3 install.py --non-interactive 2>/dev/null || python3 install.py)
else
warn "No recognized factory wizard found — creating minimal factory skeleton"
mkdir -p "$ROOT_DIR/factory"
if [[ ! -f "$ROOT_DIR/factory/consumer.py" ]]; then
cat > "$ROOT_DIR/factory/consumer.py" <<'PYEOF'
#!/usr/bin/env python3
"""Olympus factory consumer - placeholder.
This checkout ships the deployment surface only. The SDLC scheduler and the
workflow engine live in the AI Software Factory source repo (see README and
factory/doctor.py), so there is no DAG poller here and nothing that dispatches
to the gateway. This file does not pretend otherwise.
The real consumer is driven by `.factory/loop.sh`, which runs
`python factory/consumer.py tick` until a `.factory/STOP` marker appears.
Usage:
python3 factory/consumer.py status
"""
import json
import os
import sys
def status():
return {
"state": "NOT_INSTALLED",
"reason": "no factory consumer in this checkout - it lives in the source repo",
"gateway": os.environ.get("OMNIROUTE_BASE_URL", "http://10.10.2.1:20128/v1"),
"model": os.environ.get("OMNIROUTE_MODEL", "auto/coding"),
}
if __name__ == "__main__":
print(json.dumps(status(), indent=2))
sys.exit(2)
PYEOF
chmod +x "$ROOT_DIR/factory/consumer.py"
success "Created stub $ROOT_DIR/factory/consumer.py"
fi
fi
# Python deps if factory exposes requirements
for req in "$factory_dir/requirements.txt" "$factory_dir/factory/requirements.txt" "$ROOT_DIR/factory/requirements.txt"; do
if [[ -f "$req" ]]; then
info "Installing Python deps from $req"
python3 -m pip install -r "$req" --quiet || warn "pip install -r $req failed — try: python3 -m pip install -r $req"
fi
done
# Ensure archon binary is executable if present
if [[ -f "$CORE_DIR/archon/bin/archon" ]]; then
chmod +x "$CORE_DIR/archon/bin/archon" || true
fi
success "Factory initialization done"
}
# ──────────────────────────────────────────────
# 6. Securely request Telegram Bot Token -> .factory-env
# ──────────────────────────────────────────────
configure_telegram_token() {
header "Telegram Bot Token Setup"
echo -e "${BOLD}This platform uses Hermes 3 (70B) via OpenRouter Free for the Telegram interface.${NC}"
echo "Create a bot with @BotFather on Telegram to get your token:"
echo " 1. Open Telegram -> search @BotFather -> /newbot"
echo " 2. Follow prompts, copy the HTTP API token (e.g. 123456:ABC-...) "
echo ""
local existing_token=""
if [[ -f "$FACTORY_ENV" ]]; then
existing_token=$(grep -E "^TELEGRAM_BOT_TOKEN=" "$FACTORY_ENV" 2>/dev/null | cut -d= -f2- | tr -d '"' | tr -d "'" || true)
if [[ -n "$existing_token" && "$existing_token" != "ENV_VAR" ]]; then
info "Existing token found in .factory-env (masked: ${existing_token:0:6}...)"
read -r -p "Keep existing token? [Y/n]: " keep
if [[ "$keep" =~ ^[nN] ]]; then
existing_token=""
else
success "Keeping existing Telegram token"
return 0
fi
fi
fi
# Allow non-interactive injection: TELEGRAM_BOT_TOKEN env var
if [[ -n "${TELEGRAM_BOT_TOKEN:-}" && -z "$existing_token" ]]; then
info "Using TELEGRAM_BOT_TOKEN from environment"
existing_token="$TELEGRAM_BOT_TOKEN"
fi
local token="$existing_token"
if [[ -z "$token" ]]; then
# Secure prompt (no echo) with visible fallback
if [[ -t 0 ]]; then
echo -n "Paste your Telegram Bot Token (input hidden) : "
read -r -s token || true
echo ""
if [[ -z "$token" ]]; then
echo -n "Paste your Telegram Bot Token (visible) : "
read -r token || true
fi
else
warn "Non-interactive shell — set TELEGRAM_BOT_TOKEN env var or re-run interactively"
fi
fi
if [[ -z "$token" ]]; then
warn "No token provided — writing placeholder. Edit $FACTORY_ENV later."
token="PASTE_YOUR_TELEGRAM_BOT_TOKEN_HERE"
fi
# Basic format validation (Telegram tokens are <digits>:<35-char>)
if [[ "$token" != "PASTE_YOUR_TELEGRAM_BOT_TOKEN_HERE" && ! "$token" =~ ^[0-9]+:[A-Za-z0-9_-]{30,}$ ]]; then
warn "Token format looks unusual — Telegram tokens are like 123456789:AAH... — continuing anyway"
fi
# Write .factory-env atomically with 0600 perms
local tmp_env
tmp_env=$(mktemp)
{
echo "# Olympus Factory Environment — DO NOT COMMIT"
echo "# Generated by setup.sh on $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "TELEGRAM_BOT_TOKEN=$token"
echo "OPENROUTER_MODEL=nousresearch/hermes-3-llama-3-70b:free"
# The platform's single gateway runs in Group 2 (mesh 10.10.2.1); a stack
# that keeps its own gateway on this host writes the loopback URL instead.
echo "OMNIROUTE_BASE_URL=http://${MESH_GATEWAY_HOST:-10.10.2.1}:20128/v1"
# OMNIROUTE_MODEL is the key the factory and Studio actually read
# (factory/doctor.py, web/studio/lib/omniroute.ts). QWEN_MODEL was written
# here and consumed by nothing, so it is retired.
echo "OMNIROUTE_MODEL=auto/coding"
echo "ARCHON_BINARY=./core-modules/archon/bin/archon"
# Rewrite every managed key, and drop the retired QWEN_MODEL from older files.
if [[ -f "$FACTORY_ENV" ]]; then
grep -v -E "^(TELEGRAM_BOT_TOKEN|OPENROUTER_MODEL|OMNIROUTE_BASE_URL|OMNIROUTE_MODEL|QWEN_MODEL|ARCHON_BINARY)=" "$FACTORY_ENV" 2>/dev/null || true
fi
} > "$tmp_env"
mv "$tmp_env" "$FACTORY_ENV"
chmod 600 "$FACTORY_ENV"
success "Wrote $FACTORY_ENV (0600)"
# Also ensure .archon/config.yaml points to ENV_VAR pattern (token injected at runtime via env)
if [[ -f "$ARCHON_CONFIG" ]]; then
info "Archon config present at $ARCHON_CONFIG — Telegram provider will read TELEGRAM_BOT_TOKEN from environment"
fi
echo ""
echo -e "${YELLOW}Next:${NC} source .factory-env or export \$(cat .factory-env | xargs) before launching the bot"
}
# ──────────────────────────────────────────────
# 7. Final summary
# ──────────────────────────────────────────────
print_summary() {
header "Setup Complete — Olympus Ready"
cat <<EOF
${GREEN}✔ Core modules:${NC} $CORE_DIR/{omniroute,archon,ai-software-factory}
${GREEN}✔ Archon config:${NC} $ARCHON_CONFIG
${GREEN}✔ Factory env:${NC} $FACTORY_ENV (chmod 600)
${BOLD}Quickstart:${NC}
1. Point at the platform gateway (Group 2, http://10.10.2.1:20128/v1).
Running a gateway on THIS host instead — standalone box, no mesh?
cd core-modules/omniroute && npm run start
then set OMNIROUTE_BASE_URL=http://127.0.0.1:20128/v1 in .env and open
${CYAN}http://localhost:20128${NC} to toggle ${BOLD}Free Tier Mode ON${NC}
2. Keep the SDLC loop alive (new terminal, from repo root):
source .factory-env
while [ ! -f .factory/STOP ]; do
python3 factory/consumer.py tick || break
sleep \${FACTORY_INTERVAL_SECONDS:-300}
done
(The factory ships this as .factory/loop.sh. `${BOLD}tick${NC}` submits one
whole workflow; an empty .factory/STOP file halts the loop.)
3. Message your bot on Telegram — Hermes 3 (70B) via OpenRouter Free
will handle the chat, and Codex via OmniRoute (${BOLD}auto/coding${NC}) will
perform codebase edits at ${BOLD}zero local CPU cost${NC}.
${YELLOW}Tip:${NC} Re-run this script any time: chmod +x setup.sh && ./setup.sh
Docs: cat README.md
EOF
}
# ──────────────────────────────────────────────
# 8. Studio's writable directories
# ──────────────────────────────────────────────
# Two directories Studio writes into from inside its container, which runs as
# uid 1001: build-requests/ for "Export to factory", and .factory/build-queue/
# for "Build it". The bind mounts keep host ownership, so a root-owned directory
# answers 503 on every write. Settled here so a fresh install never needs the
# manual chown — and reported rather than fatal when the operator is not root,
# because the rest of setup is still useful to them.
#
# The queue is created here as well as chowned: if it does not exist, the first
# `docker compose up` creates the bind-mount source itself, as root, and no
# amount of documenting that afterwards makes the failure obvious.
prepare_studio_export_dir() {
local script="$ROOT_DIR/scripts/studio-export-dir.sh"
if [[ ! -x "$script" ]]; then
info "scripts/studio-export-dir.sh not found — skipping Studio's writable directories"
return 0
fi
local label target ok=true
for spec in "build-requests:$ROOT_DIR/build-requests" "build-queue:$ROOT_DIR/.factory/build-queue"; do
label="${spec%%:*}"
target="${spec#*:}"
header "Studio Writable Directory — $label"
if STUDIO_DIR_LABEL="$label" "$script" "$target"; then
success "$label is writable by the Studio container"
else
warn "$label is not writable by the Studio container yet — see above;"
warn "writes will answer 503 until it is"
ok=false
fi
done
$ok
}
# ──────────────────────────────────────────────
# Main
# ──────────────────────────────────────────────
main() {
echo -e "${BOLD}"
echo " ___ _ "
echo " / _ \| |_ _ _ __ ___ _ __ _ _ ___ "
echo " | | | | | | | | '_ \` _ \| '_ \| | | / __|"
echo " | |_| | | |_| | | | | | | |_) | |_| \__ \\"
echo " \___/|_|\__, |_| |_| |_| .__/ \__,_|___/"
echo " |___/ |_| "
echo -e "${NC}"
echo -e " Zero-Cost Coding Agent Platform — Local CPU, Free Cloud Brains"
echo ""
validate_requirements
prepare_core_dir
clone_vendor_tools
install_dependencies
init_factory_wizard
configure_telegram_token
prepare_studio_export_dir
print_summary
}
main "$@"