-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathinstall
More file actions
executable file
·361 lines (323 loc) · 13.3 KB
/
install
File metadata and controls
executable file
·361 lines (323 loc) · 13.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
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
#!/usr/bin/env bash
# hum installer — v0.3 (Rust daemon, ensemble mesh, openai-server door).
#
# What this does, in order:
# 1. Ensure prerequisites: claude CLI (≥ MIN_CLAUDE), cargo, pnpm (for nestlings).
# 2. Build the Rust humd from source — `cargo install --path humd --root $HUM_BIN_ROOT`.
# 3. Mint Ed25519 identity at $HUM_STATE/humd.key if missing.
# 4. Seed an empty peers.json at $HUM_CONFIG/peers.json if missing.
# 5. Seed a minimal hum.json at $HUM_CONFIG/hum.json if missing.
# 6. Install systemd --user unit pointing at the Rust binary.
# 7. Build and install the openai-server nestling (the 0.3 front door).
# 8. Start the daemon.
#
# 0.2 → 0.3 is a config-shape change; there is no migration script. Read
# MIGRATING.md for the old-key → new-home table. Re-run this installer
# after editing $HUM_CONFIG/hum.json into the new shape.
#
# Idempotent — re-running upgrades in place.
#
# Usage:
# ./install # from a cloned repo
# ./install status # daemon health
# ./install logs # journalctl tail
# ./install uninstall # remove binary + unit (keeps state)
# ./install purge # remove everything INCLUDING state
#
set -euo pipefail
# ─── XDG dirs ────────────────────────────────────────────────────────────────
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
HUM_CONFIG="$XDG_CONFIG_HOME/hum"
HUM_DATA="$XDG_DATA_HOME/hum"
HUM_STATE="$XDG_STATE_HOME/hum"
HUM_BIN_ROOT="$HOME/.local"
HUM_BIN="$HUM_BIN_ROOT/bin/humd"
if [ -n "${XDG_RUNTIME_DIR:-}" ]; then
HUM_RUNTIME="$XDG_RUNTIME_DIR/hum"
else
HUM_RUNTIME="/tmp/hum-$(id -u)"
fi
# Canonical per WIRE.md. All clients (thrum-core / thrum-clients/{ts,python,go})
# resolve here. Override at runtime via HUM_THRUM_SOCK env if you must.
THRUM_SOCK="$HUM_RUNTIME/thrum.sock"
OPENCODE_CONFIG="$XDG_CONFIG_HOME/opencode/opencode.json"
HUM_REPO_URL="${HUM_REPO_URL:-https://github.com/adiled/hum.git}"
HUM_SRC="$HUM_DATA/src"
MIN_CLAUDE="2.1.86"
CMD="${1:-install}"
OS="$(uname -s)"
# ─── helpers ─────────────────────────────────────────────────────────────────
log() { printf '\033[1m[hum]\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[hum]\033[0m %s\n' "$*" >&2; }
fail() { printf '\033[1;31m[hum]\033[0m %s\n' "$*" >&2; exit 1; }
version_gte() { printf '%s\n%s\n' "$2" "$1" | sort -V -C; }
read_version() {
"$1" --version 2>/dev/null | sed -n 's/.*\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' | head -1
}
ensure_min_version() {
local NAME="$1" BIN="$2" MIN="$3"
if ! command -v "$BIN" >/dev/null 2>&1; then
fail "$NAME not found on PATH. Install it and re-run."
fi
local CUR; CUR="$(read_version "$BIN" || true)"
if [ -z "$CUR" ]; then fail "$NAME found but couldn't read version."; fi
if version_gte "$CUR" "$MIN"; then
log "$NAME ${CUR} ≥ ${MIN} ✓"
else
fail "$NAME ${CUR} < ${MIN} — upgrade and re-run."
fi
}
# ─── prerequisite checks ─────────────────────────────────────────────────────
ensure_prereqs() {
ensure_min_version "claude CLI" "claude" "$MIN_CLAUDE"
if ! command -v cargo >/dev/null 2>&1; then
# Dev paradigm: if a pre-built humd is already at $HUM_BIN (e.g.
# placed by ./dev/deploy from a root user), allow proceeding so the
# installer can still write the systemd unit + hum.json. Otherwise
# building from source is required.
if [ -x "$HUM_BIN" ]; then
warn "cargo not found; using pre-built humd at $HUM_BIN"
SKIP_BUILD=1
else
fail "cargo (rustup) not found. Install rustup: https://rustup.rs"
fi
fi
}
# ─── 0.2 detection — guide, don't migrate ───────────────────────────────────
flag_0_2_install() {
local LANDMARK_TS="$HUM_DATA/src/humd/humd.ts"
local LANDMARK_UNIT
LANDMARK_UNIT="$(systemctl --user cat hum 2>/dev/null | grep -E '^ExecStart=' | head -1 || true)"
local IS_OLD=false
[ -f "$LANDMARK_TS" ] && IS_OLD=true
case "$LANDMARK_UNIT" in *humd.ts*|*tsx*) IS_OLD=true ;; esac
$IS_OLD || return 0
warn "v0.2 install detected (TS daemon landmarks present)."
warn ""
warn " 0.3 has no migration script — config shape changed too much."
warn " Read MIGRATING.md for the old-key → new-home table."
warn ""
warn " Quick path: stop the old service, remove $HUM_DATA/src, edit"
warn " $HUM_CONFIG/hum.json into the namespaced 0.3 shape, re-run this script."
warn ""
warn " Continuing the install will overwrite the systemd unit and"
warn " binary; your hum.json stays untouched."
}
# ─── identity ────────────────────────────────────────────────────────────────
ensure_identity() {
mkdir -p "$HUM_STATE"
local KEY="$HUM_STATE/humd.key"
if [ -s "$KEY" ]; then
log "humd identity: $KEY ✓"
return 0
fi
log "minting humd identity at $KEY (Ed25519, 32 bytes)"
if command -v openssl >/dev/null 2>&1; then
openssl genpkey -algorithm Ed25519 -outform DER -out "$KEY.tmp" 2>/dev/null
tail -c 32 "$KEY.tmp" > "$KEY"
rm -f "$KEY.tmp"
else
dd if=/dev/urandom of="$KEY" bs=32 count=1 2>/dev/null
fi
chmod 600 "$KEY"
}
# ─── peers.json ──────────────────────────────────────────────────────────────
ensure_peers_config() {
mkdir -p "$HUM_CONFIG"
local PEERS="$HUM_CONFIG/peers.json"
if [ -s "$PEERS" ]; then
log "peers.json: $PEERS ✓"
return 0
fi
echo "[]" > "$PEERS"
log "wrote empty peers.json at $PEERS"
}
# ─── hum.json (0.3 namespaced) ──────────────────────────────────────────────
ensure_hum_config() {
mkdir -p "$HUM_CONFIG"
local CFG="$HUM_CONFIG/hum.json"
if [ -s "$CFG" ]; then
log "hum.json: $CFG ✓"
return 0
fi
cat > "$CFG" <<JSON
{
"\$schema": "https://adiled.github.io/hum/hum.schema.json",
"humd": {
"permissionDuskMs": 60000,
"driftRetentionDays": 30
},
"fs": {
"roots": [
{ "path": "~/code", "mode": "rw" },
{ "path": "/tmp", "mode": "rw" }
],
"denied": ["~/.ssh", "~/.aws", "~/.gnupg", "~/.config/hum"]
},
"nest": {
"maxProcs": 4,
"idleThresholdMs": 300000,
"default": "claude-cli"
},
"hives": {
"claude-cli": { "cliPath": "claude", "defaultModel": "claude-sonnet-4-5" },
"claude-repl": { "cliPath": "claude", "defaultModel": "claude-sonnet-4-5" }
}
}
JSON
log "wrote default hum.json at $CFG"
log " edit fs.roots to match where you actually code."
}
# ─── humd binary ─────────────────────────────────────────────────────────────
HUM_CLI_BIN="$HUM_BIN_ROOT/bin/hum"
build_humd() {
local SRC
if [ -f "$(dirname "$0")/Cargo.toml" ] && grep -q '"humd"' "$(dirname "$0")/Cargo.toml"; then
SRC="$(dirname "$0")"
else
if [ ! -d "$HUM_SRC/.git" ]; then
log "cloning $HUM_REPO_URL into $HUM_SRC"
mkdir -p "$HUM_DATA"
git clone --depth 1 "$HUM_REPO_URL" "$HUM_SRC"
else
log "updating $HUM_SRC"
(cd "$HUM_SRC" && git pull --ff-only --quiet)
fi
SRC="$HUM_SRC"
fi
log "building Rust binaries via cargo install (may take a few minutes)"
cargo install --quiet --locked --path "$SRC/humd" --root "$HUM_BIN_ROOT" --force
cargo install --quiet --locked --path "$SRC/hum" --root "$HUM_BIN_ROOT" --force
[ -x "$HUM_BIN" ] || fail "humd binary not at $HUM_BIN after build"
[ -x "$HUM_CLI_BIN" ] || fail "hum binary not at $HUM_CLI_BIN after build"
log "humd installed at $HUM_BIN"
log "hum installed at $HUM_CLI_BIN"
}
# ─── service unit (Linux systemd / macOS launchd via scripts/svc.sh) ───────
# Resolve and source the cross-platform svc helper.
SVC_HELPER="$(dirname "$0")/scripts/svc.sh"
if [ -f "$SVC_HELPER" ]; then
# shellcheck source=/dev/null
. "$SVC_HELPER"
elif [ -f "$HUM_DATA/src/scripts/svc.sh" ]; then
. "$HUM_DATA/src/scripts/svc.sh"
else
warn "scripts/svc.sh not found; service management will be skipped"
fi
install_service_unit() {
if ! command -v svc_install >/dev/null 2>&1; then
warn "svc helper unavailable — run '$HUM_BIN' manually"
return 0
fi
case "$SVC_OS" in
Linux|Darwin) ;;
*) warn "service install not supported on $SVC_OS — run '$HUM_BIN' manually"; return 0 ;;
esac
svc_install hum "$HUM_BIN" \
--env "HUM_LOG_LEVEL=info" \
--env "XDG_CONFIG_HOME=$XDG_CONFIG_HOME" \
--env "XDG_DATA_HOME=$XDG_DATA_HOME" \
--env "XDG_STATE_HOME=$XDG_STATE_HOME" \
--env "XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" \
--env "HUM_THRUM_SOCK=$THRUM_SOCK" \
--env "PATH=$HUM_BIN_ROOT/bin:/usr/local/bin:/usr/bin:/bin"
log "service unit installed for hum ($SVC_OS)"
# humd does its own daily auto-update check in-process — no separate
# timer / cron / launchd interval to maintain.
# Hives are NOT installed here — each hive ships its own installer
# under hives/<kind>/install. humd just routes; whichever bee you
# bring up registers itself via thrum bee:["worker"] (or "forager").
}
start_daemon() {
if ! command -v svc_restart >/dev/null 2>&1; then
warn "svc helper unavailable — start '$HUM_BIN' manually"
return 0
fi
svc_restart hum 2>/dev/null || svc_start hum 2>/dev/null || true
sleep 1
if svc_is_active hum; then
log "hum service running ✓"
else
warn "hum service did not start — \`./install logs\` to inspect."
fi
}
# ─── next steps ──────────────────────────────────────────────────────────────
# humd has no opinion on which nestlings you run. Each nestling is a
# separate process with its own installer under hives/<kind>/install.
# Pick at least one to give humd an outside-world surface.
print_next_steps() {
log ""
log "humd is up. Pick a recipe to give it an outside-world surface:"
log ""
log " hum + opencode (chat from the opencode TUI/CLI):"
log " ./recipes/opencode/install"
log ""
log " Other nestlings — each is a standalone install:"
log " hives/openai-server/install (OpenAI-shape HTTP)"
log " hives/anthropic-server/install (Anthropic-shape HTTP)"
log " hives/ollama-server/install (Ollama-shape HTTP)"
log ""
log "Health checks:"
log " ./install status (is humd alive)"
log " ./install logs (recent humd journal)"
}
# ─── status / logs / uninstall ───────────────────────────────────────────────
show_status() {
echo "humd binary: $HUM_BIN"
echo " version: $($HUM_BIN --version 2>&1 || echo 'not installed')"
echo "identity: $HUM_STATE/humd.key $([ -s "$HUM_STATE/humd.key" ] && echo ✓ || echo MISSING)"
echo "peers.json: $HUM_CONFIG/peers.json $([ -s "$HUM_CONFIG/peers.json" ] && echo ✓ || echo MISSING)"
echo "hum.json: $HUM_CONFIG/hum.json $([ -s "$HUM_CONFIG/hum.json" ] && echo ✓ || echo MISSING)"
echo "thrum socket: $THRUM_SOCK $([ -S "$THRUM_SOCK" ] && echo ✓ || echo absent)"
if command -v svc_status >/dev/null 2>&1; then
echo "service ($SVC_OS):"
svc_status hum 2>&1 | head -6 || true
fi
}
show_logs() {
case "$SVC_OS" in
Linux) journalctl --user -u hum --no-pager -n 200 ;;
Darwin) tail -n 200 "$HOME/Library/Logs/sh.hum.hum.out.log" "$HOME/Library/Logs/sh.hum.hum.err.log" 2>/dev/null ;;
*) warn "logs command unavailable on $SVC_OS" ;;
esac
}
uninstall() {
if command -v svc_uninstall >/dev/null 2>&1; then
svc_uninstall hum-update || true
svc_uninstall hum || true
fi
rm -f "$HUM_BIN" "$HUM_CLI_BIN"
log "uninstalled. State preserved in $HUM_STATE + $HUM_CONFIG."
log " run \`./install purge\` to remove state too."
}
purge() {
uninstall
rm -rf "$HUM_STATE" "$HUM_CONFIG" "$HUM_DATA"
log "purged all hum state."
}
# ─── dispatch ────────────────────────────────────────────────────────────────
do_install() {
log "hum installer — v0.3"
flag_0_2_install
ensure_prereqs
if [ "${SKIP_BUILD:-0}" != "1" ]; then build_humd; fi
ensure_identity
ensure_peers_config
ensure_hum_config
install_service_unit
start_daemon
log ""
log "humd installed. Try: ./install status / ./install logs"
log ""
print_next_steps
}
case "$CMD" in
install|"") do_install ;;
status) show_status ;;
logs) show_logs ;;
uninstall) uninstall ;;
purge) purge ;;
*) fail "unknown command: $CMD (try: install, status, logs, uninstall, purge)" ;;
esac