File size: 11,522 Bytes
43c6693 7308820 8e01a03 43c6693 8e01a03 43c6693 7308820 43c6693 8e01a03 43c6693 96dae5d 43c6693 7308820 43c6693 7308820 675dd28 7308820 43c6693 7308820 43c6693 7308820 43c6693 8e01a03 43c6693 8e01a03 43c6693 dcaca40 43c6693 8e01a03 43c6693 8e01a03 43c6693 164e59e 43c6693 164e59e 43c6693 9f020c3 8e01a03 9f020c3 8e01a03 9f020c3 8e01a03 164e59e 8e01a03 43c6693 8e01a03 43c6693 8e01a03 43c6693 9f020c3 164e59e 43c6693 8e01a03 43c6693 9f020c3 43c6693 | 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 | #!/usr/bin/env bash
# ============================================================================
# Hermes Agent on a Bucket β one-shot bootstrap
# ============================================================================
# Creates your own HF bucket from the merve/hermes-agent template, mounts it,
# installs Hermes, sets up Telegram, and launches the agent.
#
# Layout:
# ~/hermes-bucket mount of YOUR bucket (config, SOUL, skills, memories, ...)
# ~/.hermes-home HERMES_HOME (LOCAL). Secrets live here, everything else
# is symlinked to ~/hermes-bucket so Hermes data syncs.
# ~/.hermes Hermes Python install (code + venv)
#
# Why the shadow dir: Hermes hardcodes its secrets file at $HERMES_HOME/.env
# and atomically rewrites it on `hermes setup` / sanitization. If HERMES_HOME
# were the mount, those writes would land in a public bucket. The shadow dir
# keeps .env local while every other Hermes write still passes through to the
# bucket via symlink.
#
# bash <(curl -fsSL https://huggingface.co/merve/hermes-agent-bootstrap/resolve/main/bootstrap.sh)
#
# Idempotent: re-runs are safe.
# ============================================================================
set -euo pipefail
TEMPLATE="merve/hermes-agent"
MOUNT="$HOME/hermes-bucket"
HOME_DIR="$HOME/.hermes-home"
LEGACY_SECRETS="$HOME/.hermes-secrets.env"
SECRETS="$HOME_DIR/.env"
G=$'\033[0;32m'; Y=$'\033[0;33m'; C=$'\033[0;36m'; R=$'\033[0;31m'; N=$'\033[0m'
say() { printf "${C}β${N} %s\n" "$*"; }
ok() { printf "${G}β${N} %s\n" "$*"; }
warn() { printf "${Y}!${N} %s\n" "$*"; }
die() { printf "${R}β${N} %s\n" "$*" >&2; exit 1; }
ask() { local __var=$1; shift; local __prompt="$*"; read -rp " $__prompt" "$__var" </dev/tty; }
export PATH="$HOME/.local/bin:$PATH"
printf "\n${C}β Hermes Agent β bucket bootstrap${N}\n\n"
# ----------------------------------------------------------------------------
# 1. hf CLI
# ----------------------------------------------------------------------------
if ! command -v hf >/dev/null 2>&1; then
say "Installing hf CLI..."
curl -LsSf https://hf.co/cli/install.sh | bash
export PATH="$HOME/.local/bin:$PATH"
fi
ok "hf CLI: $(command -v hf)"
# ----------------------------------------------------------------------------
# 2. Hugging Face login
# ----------------------------------------------------------------------------
if ! hf auth whoami >/dev/null 2>&1; then
warn "Not logged in to Hugging Face."
echo " Run this in another terminal, then re-run the bootstrap:"
echo " hf auth login"
exit 1
fi
HF_USER="$(hf auth whoami --format json 2>/dev/null | python3 -c 'import json,sys; print(json.load(sys.stdin).get("user",""))')"
[ -n "$HF_USER" ] || die "Couldn't determine your HF username from 'hf auth whoami'"
HF_TOKEN_VAL="$(python3 -c 'from huggingface_hub import get_token; print(get_token() or "")' 2>/dev/null || true)"
[ -n "$HF_TOKEN_VAL" ] || die "Couldn't read HF token from local cache"
ok "Hugging Face: logged in as $HF_USER"
BUCKET="$HF_USER/hermes-agent"
# ----------------------------------------------------------------------------
# 3. Your bucket β create if missing, copy template if empty
# ----------------------------------------------------------------------------
if hf buckets info "$BUCKET" >/dev/null 2>&1; then
ok "Bucket $BUCKET already exists"
else
say "Creating $BUCKET (private)..."
hf buckets create "$BUCKET" --private --exist-ok >/dev/null
ok "Bucket created (private): https://huggingface.co/buckets/$BUCKET"
fi
FILE_COUNT="$(hf buckets list "$BUCKET" --recursive --quiet 2>/dev/null | wc -l | tr -d ' ')"
if [ "${FILE_COUNT:-0}" -lt 3 ]; then
say "Seeding $BUCKET from template $TEMPLATE (server-side copy, ~1s)..."
python3 - <<PY
from huggingface_hub import HfApi
HfApi().copy_files(
source="hf://buckets/$TEMPLATE/",
destination="hf://buckets/$BUCKET/",
)
PY
ok "Template copied into $BUCKET"
else
ok "$BUCKET already populated ($FILE_COUNT files) β skipping template copy"
fi
# ----------------------------------------------------------------------------
# 4. hf-mount
# ----------------------------------------------------------------------------
if ! command -v hf-mount >/dev/null 2>&1; then
say "Installing hf-mount..."
curl -fsSL https://raw.githubusercontent.com/huggingface/hf-mount/main/install.sh | sh
fi
ok "hf-mount: $(command -v hf-mount)"
# ----------------------------------------------------------------------------
# 5. Mount your bucket
# ----------------------------------------------------------------------------
mkdir -p "$MOUNT"
if hf-mount status 2>/dev/null | grep -q "$MOUNT"; then
ok "Bucket already mounted at $MOUNT"
else
say "Mounting $BUCKET at $MOUNT..."
hf-mount start --hf-token "$HF_TOKEN_VAL" bucket "$BUCKET" "$MOUNT" >/dev/null
ok "Mounted $BUCKET β $MOUNT"
fi
# ----------------------------------------------------------------------------
# 6. Hermes Agent install
# ----------------------------------------------------------------------------
if ! command -v hermes >/dev/null 2>&1; then
say "Installing Hermes Agent (this takes a minute)..."
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash -s -- --skip-setup --skip-browser >/dev/null
export PATH="$HOME/.local/bin:$PATH"
fi
ok "Hermes: $(command -v hermes)"
# ----------------------------------------------------------------------------
# 7. Shadow HERMES_HOME β keeps .env local, symlinks everything else to bucket
# ----------------------------------------------------------------------------
say "Setting up shadow HERMES_HOME at $HOME_DIR..."
mkdir -p "$HOME_DIR" "$HOME_DIR/audio_cache" "$HOME_DIR/image_cache" "$HOME_DIR/logs"
# Make sure bucket subdirs that Hermes writes to exist (so symlinks resolve)
for d in memories cron hooks pairing sessions; do
mkdir -p "$MOUNT/$d"
done
# Files mirrored from the bucket (read mostly; if Hermes ever rewrites one
# atomically the symlink severs and the local copy takes over β safe but no
# longer syncing for that file)
for f in config.yaml SOUL.md README.md .env.example; do
ln -sfn "$MOUNT/$f" "$HOME_DIR/$f"
done
# Directory trees that Hermes appends to: writes inside flow through to bucket
for d in skills memories cron hooks pairing sessions; do
ln -sfn "$MOUNT/$d" "$HOME_DIR/$d"
done
ok "Shadow dir wired: $HOME_DIR (.env stays local, data symlinks β $MOUNT)"
# Migrate legacy secrets file from a previous bootstrap, if present
if [ -f "$LEGACY_SECRETS" ] && [ ! -f "$SECRETS" ]; then
cp "$LEGACY_SECRETS" "$SECRETS"
chmod 600 "$SECRETS"
warn "Migrated legacy $LEGACY_SECRETS β $SECRETS"
warn " You can delete $LEGACY_SECRETS once you confirm things work."
fi
# ----------------------------------------------------------------------------
# 8. Telegram (required step)
# ----------------------------------------------------------------------------
printf "\n${C}Telegram setup${N}\n"
EXISTING_TG=""
if [ -f "$SECRETS" ] && grep -q "^TELEGRAM_BOT_TOKEN=" "$SECRETS"; then
EXISTING_TG="$(grep "^TELEGRAM_BOT_TOKEN=" "$SECRETS" | head -1 | cut -d= -f2-)"
fi
if [ -n "$EXISTING_TG" ]; then
ok "Telegram bot token already on disk ($SECRETS)"
TG_TOKEN="$EXISTING_TG"
USER_ID="$(grep "^TELEGRAM_ALLOWED_USERS=" "$SECRETS" 2>/dev/null | head -1 | cut -d= -f2- || true)"
else
echo " 1. Open Telegram, message @BotFather, send /newbot, follow the prompts."
echo " 2. Paste the bot token below (looks like 123456:ABC-DEF...)"
echo ""
ask TG_TOKEN "Telegram bot token: "
[ -n "$TG_TOKEN" ] || die "Telegram bot token required"
echo ""
echo " 3. Now message @userinfobot on Telegram. It replies with your numeric"
echo " user id (looks like 123456789). Paste it below β this goes into"
echo " TELEGRAM_ALLOWED_USERS so only you can DM the bot."
echo ""
ask USER_ID "Your Telegram numeric user id: "
[ -n "$USER_ID" ] || die "Telegram user id required"
case "$USER_ID" in
*[!0-9]*) die "User id must be numeric (got: $USER_ID)" ;;
esac
ok "Telegram user id: $USER_ID"
fi
# ----------------------------------------------------------------------------
# 9. Write secrets file (LOCAL, never reaches the bucket)
# ----------------------------------------------------------------------------
umask 077
cat > "$SECRETS" <<EOF
# Hermes secrets β local-only ($HOME_DIR is HERMES_HOME, not a mount)
HF_TOKEN=$HF_TOKEN_VAL
TELEGRAM_BOT_TOKEN=$TG_TOKEN
TELEGRAM_ALLOWED_USERS=$USER_ID
TELEGRAM_HOME_CHANNEL=$USER_ID
EOF
chmod 600 "$SECRETS"
ok "Secrets saved to $SECRETS (mode 600)"
# ----------------------------------------------------------------------------
# 10. Patch the hermes entrypoint so HERMES_HOME defaults to the shadow dir
# ----------------------------------------------------------------------------
# Shell-rc exports require users to source/relaunch. Patching the launcher at
# ~/.local/bin/hermes makes plain `hermes` and `hermes gateway` Just Work in
# *any* shell, immediately, no sourcing. If a user explicitly sets HERMES_HOME
# (e.g. for a profile), that wins via :=.
HERMES_BIN="$HOME/.local/bin/hermes"
if [ -f "$HERMES_BIN" ] && ! grep -q "HERMES_HOME:=" "$HERMES_BIN"; then
python3 - "$HERMES_BIN" "$HOME_DIR" <<'PY'
import sys, pathlib
bin_path, home_dir = sys.argv[1], sys.argv[2]
p = pathlib.Path(bin_path)
lines = p.read_text().splitlines(keepends=True)
# Find the exec line and inject defaults right before it
out = []
injected = False
for line in lines:
if not injected and line.lstrip().startswith("exec "):
out.append(f': "${{HERMES_HOME:={home_dir}}}"\n')
out.append("export HERMES_HOME\n")
injected = True
out.append(line)
p.write_text("".join(out))
PY
ok "Patched $HERMES_BIN so 'hermes' defaults to HERMES_HOME=$HOME_DIR"
else
ok "$HERMES_BIN already patched (or not found)"
fi
# Clean up any prior shell-rc block from older bootstrap versions
SHELL_NAME="${SHELL##*/}"
case "$SHELL_NAME" in
zsh) SHELL_RC="$HOME/.zshrc" ;;
bash) SHELL_RC="$HOME/.bashrc" ;;
*) SHELL_RC="$HOME/.profile" ;;
esac
if [ -f "$SHELL_RC" ] && grep -q "# hermes-bucket" "$SHELL_RC"; then
python3 - "$SHELL_RC" <<'PY'
import sys, pathlib, re
p = pathlib.Path(sys.argv[1])
text = p.read_text()
new = re.sub(r"\n*# hermes-bucket[^\n]*\n(?:[^\n]*\n)*?(?=\n|\Z)", "\n", text)
p.write_text(new)
PY
ok "Removed legacy hermes-bucket block from $SHELL_RC"
fi
# ----------------------------------------------------------------------------
# 11. Summary + launch
# ----------------------------------------------------------------------------
cat <<EOF
${G}β Ready.${N}
Your bucket: https://huggingface.co/buckets/$BUCKET
Bucket mount: $MOUNT
HERMES_HOME: $HOME_DIR (secrets local; everything else symlinks to mount)
Model: Qwen/Qwen3.6-35B-A3B (HF Inference Providers β deepinfra)
${C}hermes${N} β chat in your terminal
${C}hermes gateway run${N} β start Telegram bot in the foreground (Ctrl-C to stop)
${C}hermes gateway install${N} && ${C}hermes gateway start${N} β run gateway as a background service
These work in any shell β no sourcing, no aliases. The hermes launcher
itself now defaults HERMES_HOME to $HOME_DIR.
EOF
if [ -t 0 ] && [ -t 1 ]; then
say "Launching Hermes now..."
sleep 1
export HERMES_HOME="$HOME_DIR"
exec hermes
else
say "Run ${C}source $SHELL_RC${N} then ${C}hermes${N} to start chatting."
fi
|