mirror of
https://github.com/domfelipe/hermes-client-onboarding.git
synced 2026-08-07 06:16:47 +00:00
Multi-tenant shared VPS: one client = one Hermes profile
Require --client SLUG on the one-liner; add hermes-client-provision for isolated HERMES_HOME + hermes-gateway-<slug>. Document profile isolation over per-VM/Docker for DomHubs fleet on a single host.
This commit is contained in:
parent
4ea1708cdd
commit
ff725338f3
5 changed files with 422 additions and 145 deletions
49
README.md
49
README.md
|
|
@ -7,49 +7,58 @@ One-liner + skill conversacional para deixar o **Hermes Agent** pronto no client
|
||||||
- Personalidade em `SOUL.md`
|
- Personalidade em `SOUL.md`
|
||||||
- Setup guiado por LLM (Codex ou Hermes)
|
- Setup guiado por LLM (Codex ou Hermes)
|
||||||
|
|
||||||
## One-liner (produção) — entrar na VPS + instalar
|
## One-liner (produção) — VPS compartilhada multi-tenant
|
||||||
|
|
||||||
O bootstrap roda **dentro da VPS Linux**, não no Mac do operador.
|
**Uma VPS DomHubs, vários clientes.** Isolamento = **Hermes profile** por cliente (não VM nova, não Docker por padrão).
|
||||||
|
|
||||||
|
| Cliente | Home | Gateway unit |
|
||||||
|
|---------|------|----------------|
|
||||||
|
| `flavia` | `~/.hermes/profiles/flavia/` | `hermes-gateway-flavia` |
|
||||||
|
| `acme` | `~/.hermes/profiles/acme/` | `hermes-gateway-acme` |
|
||||||
|
|
||||||
|
Cada um: bot token, allowlist, SOUL, `state.db` próprios. **Nunca** reutilizar token entre profiles.
|
||||||
|
|
||||||
### 1) Entrar na VPS
|
### 1) Entrar na VPS
|
||||||
|
|
||||||
```bash
|
|
||||||
ssh root@IP_DA_VPS
|
|
||||||
```
|
|
||||||
|
|
||||||
DomHubs ops (alias no Mac, após chave `~/.ssh/domhubs_vps`):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh domhubs-vps
|
ssh domhubs-vps
|
||||||
# → root@169.58.116.28
|
# → root@169.58.116.28
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2) One-liner (já dentro da VPS)
|
### 2) One-liner **com slug do cliente** (obrigatório)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://setup.domhubs.com.br/hermes | bash
|
curl -fsSL https://setup.domhubs.com.br/hermes | bash -s -- --client flavia
|
||||||
```
|
```
|
||||||
|
|
||||||
Instala/atualiza Hermes + skill + launcher e **abre o onboarding** (condutor Hermes por padrão).
|
Isso: instala/atualiza Hermes + skill → provisiona profile isolado → abre onboarding **só nesse profile**.
|
||||||
|
|
||||||
Variantes (ainda na VPS):
|
Variantes:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# só instalar, sem abrir agente
|
# provisionar sem abrir o agente
|
||||||
curl -fsSL https://setup.domhubs.com.br/hermes | bash -s -- --no-launch
|
curl -fsSL https://setup.domhubs.com.br/hermes | bash -s -- --client acme --no-launch
|
||||||
|
|
||||||
# perguntar Codex vs Hermes
|
# reabrir onboarding de um cliente
|
||||||
curl -fsSL https://setup.domhubs.com.br/hermes | bash -s -- --ask-conductor
|
hermes-client-onboarding --client flavia
|
||||||
|
|
||||||
|
# só criar/atualizar a instância
|
||||||
|
hermes-client-provision --client acme
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3) Voltar depois
|
### 3) Ops diário
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh root@IP_DA_VPS # ou: ssh domhubs-vps
|
ssh domhubs-vps
|
||||||
hermes gateway status
|
hermes profile list
|
||||||
hermes-client-onboarding # se precisar retomar o onboarding
|
hermes --profile flavia gateway status
|
||||||
|
journalctl --user -u hermes-gateway-flavia -n 50 --no-pager
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Por que não container (ainda)
|
||||||
|
|
||||||
|
Profiles + `HERMES_HOME` + multi-gateway já isolam secrets/histórico/polling do Telegram com ~150 MB RAM/cliente. Docker entra depois se precisar sandbox de shell/tools por cliente.
|
||||||
|
|
||||||
Espelho GitHub (fallback):
|
Espelho GitHub (fallback):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
100
install.sh
100
install.sh
|
|
@ -7,20 +7,23 @@
|
||||||
# ssh root@IP_DA_VPS
|
# ssh root@IP_DA_VPS
|
||||||
# # DomHubs ops alias: ssh domhubs-vps (169.58.116.28, key ~/.ssh/domhubs_vps)
|
# # DomHubs ops alias: ssh domhubs-vps (169.58.116.28, key ~/.ssh/domhubs_vps)
|
||||||
#
|
#
|
||||||
# Then one-liner (inside the VPS):
|
# Then one-liner (inside the VPS) — SHARED host multi-tenant:
|
||||||
# curl -fsSL https://setup.domhubs.com.br/hermes | bash
|
# curl -fsSL https://setup.domhubs.com.br/hermes | bash -s -- --client flavia
|
||||||
#
|
#
|
||||||
# Local checkout: ./install.sh [--conductor codex|hermes|skip] [--no-launch]
|
# Each --client creates an isolated Hermes profile + gateway unit.
|
||||||
|
# Local checkout: ./install.sh --client SLUG [--conductor hermes|skip] [--no-launch]
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SKILL_NAME="hermes-client-onboarding"
|
SKILL_NAME="hermes-client-onboarding"
|
||||||
DEFAULT_BASE="${HERMES_ONBOARD_BASE:-https://setup.domhubs.com.br/hermes}"
|
DEFAULT_BASE="${HERMES_ONBOARD_BASE:-https://setup.domhubs.com.br/hermes}"
|
||||||
HERMES_INSTALL_URL="${HERMES_INSTALL_URL:-https://hermes-agent.nousresearch.com/install.sh}"
|
HERMES_INSTALL_URL="${HERMES_INSTALL_URL:-https://hermes-agent.nousresearch.com/install.sh}"
|
||||||
KICKOFF_MSG="${HERMES_ONBOARD_KICKOFF:-Inicie o onboarding agora. Skill hermes-client-onboarding. Pre-flight silencioso e Phase 1 (voce fala primeiro).}"
|
KICKOFF_MSG="${HERMES_ONBOARD_KICKOFF:-}"
|
||||||
|
|
||||||
CONDUCTOR="${HERMES_ONBOARD_CONDUCTOR:-hermes}"
|
CONDUCTOR="${HERMES_ONBOARD_CONDUCTOR:-hermes}"
|
||||||
|
CLIENT_SLUG="${HERMES_CLIENT_SLUG:-}"
|
||||||
NO_LAUNCH=0
|
NO_LAUNCH=0
|
||||||
NONINTERACTIVE=0
|
NONINTERACTIVE=0
|
||||||
|
REQUIRE_CLIENT="${HERMES_REQUIRE_CLIENT:-1}"
|
||||||
|
|
||||||
log() { printf '==> %s\n' "$*"; }
|
log() { printf '==> %s\n' "$*"; }
|
||||||
warn() { printf 'warn: %s\n' "$*" >&2; }
|
warn() { printf 'warn: %s\n' "$*" >&2; }
|
||||||
|
|
@ -30,40 +33,44 @@ usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Usage: install.sh [options]
|
Usage: install.sh [options]
|
||||||
|
|
||||||
Run this ON the Linux VPS after SSH:
|
Shared DomHubs VPS — one host, many isolated Hermes clients (profiles):
|
||||||
ssh root@IP_DA_VPS
|
|
||||||
curl -fsSL https://setup.domhubs.com.br/hermes | bash
|
|
||||||
|
|
||||||
DomHubs ops (Mac alias):
|
|
||||||
ssh domhubs-vps
|
ssh domhubs-vps
|
||||||
|
curl -fsSL https://setup.domhubs.com.br/hermes | bash -s -- --client flavia
|
||||||
|
|
||||||
--conductor codex|hermes|skip Who runs onboarding (default: hermes; use skip for install-only)
|
--client SLUG Required. Isolated profile name (a-z0-9-)
|
||||||
--no-launch Install only; do not start the conductor
|
--conductor codex|hermes|skip Who runs onboarding (default: hermes)
|
||||||
|
--no-launch Install + provision only; do not start conductor
|
||||||
--base URL Asset base for skill files (or HERMES_ONBOARD_BASE)
|
--base URL Asset base for skill files (or HERMES_ONBOARD_BASE)
|
||||||
--non-interactive No prompts
|
--non-interactive No prompts
|
||||||
--ask-conductor Prompt for conductor even when default is hermes
|
--ask-conductor Prompt for conductor even when default is hermes
|
||||||
|
--allow-default Allow missing --client (single-tenant / laptop only)
|
||||||
-h, --help Show help
|
-h, --help Show help
|
||||||
|
|
||||||
Env:
|
Env:
|
||||||
HERMES_ONBOARD_BASE, HERMES_ONBOARD_CONDUCTOR, HERMES_ONBOARD_KICKOFF
|
HERMES_CLIENT_SLUG, HERMES_ONBOARD_BASE, HERMES_ONBOARD_CONDUCTOR
|
||||||
HERMES_INSTALL_URL, HERMES_ONBOARD_NO_LAUNCH=1
|
HERMES_ONBOARD_KICKOFF, HERMES_INSTALL_URL, HERMES_ONBOARD_NO_LAUNCH=1
|
||||||
|
HERMES_REQUIRE_CLIENT=0 Same as --allow-default
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
ASK_CONDUCTOR=0
|
ASK_CONDUCTOR=0
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
--client) CLIENT_SLUG="${2:-}"; shift 2 ;;
|
||||||
--conductor) CONDUCTOR="${2:-}"; shift 2 ;;
|
--conductor) CONDUCTOR="${2:-}"; shift 2 ;;
|
||||||
--no-launch) NO_LAUNCH=1; shift ;;
|
--no-launch) NO_LAUNCH=1; shift ;;
|
||||||
--base) DEFAULT_BASE="${2:-}"; shift 2 ;;
|
--base) DEFAULT_BASE="${2:-}"; shift 2 ;;
|
||||||
--non-interactive) NONINTERACTIVE=1; shift ;;
|
--non-interactive) NONINTERACTIVE=1; shift ;;
|
||||||
--ask-conductor) ASK_CONDUCTOR=1; shift ;;
|
--ask-conductor) ASK_CONDUCTOR=1; shift ;;
|
||||||
|
--allow-default) REQUIRE_CLIENT=0; shift ;;
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
*) die "unknown arg: $1" ;;
|
*) die "unknown arg: $1" ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
[[ "${HERMES_ONBOARD_NO_LAUNCH:-0}" == "1" ]] && NO_LAUNCH=1
|
[[ "${HERMES_ONBOARD_NO_LAUNCH:-0}" == "1" ]] && NO_LAUNCH=1
|
||||||
|
[[ "${HERMES_REQUIRE_CLIENT:-1}" == "0" ]] && REQUIRE_CLIENT=0
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Resolve skill source: local checkout vs remote base URL
|
# Resolve skill source: local checkout vs remote base URL
|
||||||
|
|
@ -123,6 +130,9 @@ copy_tree() {
|
||||||
if [[ -f "$dest/scripts/apply-core-config.sh" ]]; then
|
if [[ -f "$dest/scripts/apply-core-config.sh" ]]; then
|
||||||
chmod +x "$dest/scripts/apply-core-config.sh"
|
chmod +x "$dest/scripts/apply-core-config.sh"
|
||||||
fi
|
fi
|
||||||
|
if [[ -f "$dest/scripts/provision-client-instance.sh" ]]; then
|
||||||
|
chmod +x "$dest/scripts/provision-client-instance.sh"
|
||||||
|
fi
|
||||||
if [[ -f "$dest/scripts/start-onboarding.sh" ]]; then
|
if [[ -f "$dest/scripts/start-onboarding.sh" ]]; then
|
||||||
chmod +x "$dest/scripts/start-onboarding.sh"
|
chmod +x "$dest/scripts/start-onboarding.sh"
|
||||||
fi
|
fi
|
||||||
|
|
@ -140,9 +150,11 @@ fetch_skill_to() {
|
||||||
curl -fsSL "${base}/skill/${SKILL_NAME}/SKILL.md" -o "$dest/SKILL.md"
|
curl -fsSL "${base}/skill/${SKILL_NAME}/SKILL.md" -o "$dest/SKILL.md"
|
||||||
curl -fsSL "${base}/skill/${SKILL_NAME}/references/troubleshooting.md" -o "$dest/references/troubleshooting.md"
|
curl -fsSL "${base}/skill/${SKILL_NAME}/references/troubleshooting.md" -o "$dest/references/troubleshooting.md"
|
||||||
curl -fsSL "${base}/skill/${SKILL_NAME}/scripts/apply-core-config.sh" -o "$dest/scripts/apply-core-config.sh"
|
curl -fsSL "${base}/skill/${SKILL_NAME}/scripts/apply-core-config.sh" -o "$dest/scripts/apply-core-config.sh"
|
||||||
|
curl -fsSL "${base}/skill/${SKILL_NAME}/scripts/provision-client-instance.sh" -o "$dest/scripts/provision-client-instance.sh" || true
|
||||||
curl -fsSL "${base}/skill/${SKILL_NAME}/scripts/start-onboarding.sh" -o "$dest/scripts/start-onboarding.sh" || true
|
curl -fsSL "${base}/skill/${SKILL_NAME}/scripts/start-onboarding.sh" -o "$dest/scripts/start-onboarding.sh" || true
|
||||||
curl -fsSL "${base}/skill/${SKILL_NAME}/scripts/auto_kickoff_cli.py" -o "$dest/scripts/auto_kickoff_cli.py" || true
|
curl -fsSL "${base}/skill/${SKILL_NAME}/scripts/auto_kickoff_cli.py" -o "$dest/scripts/auto_kickoff_cli.py" || true
|
||||||
chmod +x "$dest/scripts/apply-core-config.sh"
|
chmod +x "$dest/scripts/apply-core-config.sh"
|
||||||
|
[[ -f "$dest/scripts/provision-client-instance.sh" ]] && chmod +x "$dest/scripts/provision-client-instance.sh"
|
||||||
[[ -f "$dest/scripts/start-onboarding.sh" ]] && chmod +x "$dest/scripts/start-onboarding.sh"
|
[[ -f "$dest/scripts/start-onboarding.sh" ]] && chmod +x "$dest/scripts/start-onboarding.sh"
|
||||||
[[ -f "$dest/scripts/auto_kickoff_cli.py" ]] && chmod +x "$dest/scripts/auto_kickoff_cli.py"
|
[[ -f "$dest/scripts/auto_kickoff_cli.py" ]] && chmod +x "$dest/scripts/auto_kickoff_cli.py"
|
||||||
[[ -s "$dest/SKILL.md" ]] || die "failed to download SKILL.md from $base"
|
[[ -s "$dest/SKILL.md" ]] || die "failed to download SKILL.md from $base"
|
||||||
|
|
@ -166,12 +178,17 @@ install_skill() {
|
||||||
copy_tree "$staging" "$hermes_dest"
|
copy_tree "$staging" "$hermes_dest"
|
||||||
log "Skill installed for Hermes → $hermes_dest"
|
log "Skill installed for Hermes → $hermes_dest"
|
||||||
|
|
||||||
# Launcher: auto-starts Phase 1 (agent speaks first via CLI PTY inject)
|
# Launcher + provisioner
|
||||||
mkdir -p "${HOME}/.local/bin" "${HOME}/.local/share/hermes-client-onboarding"
|
mkdir -p "${HOME}/.local/bin" "${HOME}/.local/share/hermes-client-onboarding"
|
||||||
if [[ -f "$staging/scripts/start-onboarding.sh" ]]; then
|
if [[ -f "$staging/scripts/start-onboarding.sh" ]]; then
|
||||||
cp -f "$staging/scripts/start-onboarding.sh" "${HOME}/.local/bin/hermes-client-onboarding"
|
cp -f "$staging/scripts/start-onboarding.sh" "${HOME}/.local/bin/hermes-client-onboarding"
|
||||||
chmod +x "${HOME}/.local/bin/hermes-client-onboarding"
|
chmod +x "${HOME}/.local/bin/hermes-client-onboarding"
|
||||||
log "Launcher → ~/.local/bin/hermes-client-onboarding (agent fala primeiro)"
|
log "Launcher → ~/.local/bin/hermes-client-onboarding [--client SLUG]"
|
||||||
|
fi
|
||||||
|
if [[ -f "$staging/scripts/provision-client-instance.sh" ]]; then
|
||||||
|
cp -f "$staging/scripts/provision-client-instance.sh" "${HOME}/.local/bin/hermes-client-provision"
|
||||||
|
chmod +x "${HOME}/.local/bin/hermes-client-provision"
|
||||||
|
log "Provisioner → ~/.local/bin/hermes-client-provision --client SLUG"
|
||||||
fi
|
fi
|
||||||
if [[ -f "$staging/scripts/auto_kickoff_cli.py" ]]; then
|
if [[ -f "$staging/scripts/auto_kickoff_cli.py" ]]; then
|
||||||
cp -f "$staging/scripts/auto_kickoff_cli.py" "${HOME}/.local/share/hermes-client-onboarding/auto_kickoff_cli.py"
|
cp -f "$staging/scripts/auto_kickoff_cli.py" "${HOME}/.local/share/hermes-client-onboarding/auto_kickoff_cli.py"
|
||||||
|
|
@ -239,15 +256,26 @@ pick_conductor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
launch_hermes_onboarding() {
|
launch_hermes_onboarding() {
|
||||||
|
if [[ -n "$CLIENT_SLUG" ]]; then
|
||||||
|
export HERMES_CLIENT_SLUG="$CLIENT_SLUG"
|
||||||
|
export HERMES_HOME="${HOME}/.hermes/profiles/${CLIENT_SLUG}"
|
||||||
|
if [[ -z "$KICKOFF_MSG" ]]; then
|
||||||
|
KICKOFF_MSG="Inicie onboarding do cliente ${CLIENT_SLUG}. Skill hermes-client-onboarding. Multi-tenant profile ${CLIENT_SLUG} only. Pre-flight e Phase 1."
|
||||||
|
fi
|
||||||
|
elif [[ -z "$KICKOFF_MSG" ]]; then
|
||||||
|
KICKOFF_MSG="Inicie o onboarding agora. Skill hermes-client-onboarding. Pre-flight silencioso e Phase 1 (voce fala primeiro)."
|
||||||
|
fi
|
||||||
export HERMES_ONBOARD_KICKOFF="$KICKOFF_MSG"
|
export HERMES_ONBOARD_KICKOFF="$KICKOFF_MSG"
|
||||||
export HERMES_ONBOARD_SKILL="$SKILL_NAME"
|
export HERMES_ONBOARD_SKILL="$SKILL_NAME"
|
||||||
export HERMES_TUI_QUERY="$KICKOFF_MSG"
|
export HERMES_TUI_QUERY="$KICKOFF_MSG"
|
||||||
export HERMES_TUI_SKILLS="$SKILL_NAME"
|
export HERMES_TUI_SKILLS="$SKILL_NAME"
|
||||||
if [[ -x "${HOME}/.local/bin/hermes-client-onboarding" ]]; then
|
if [[ -x "${HOME}/.local/bin/hermes-client-onboarding" ]]; then
|
||||||
|
local launch_args=()
|
||||||
|
[[ -n "$CLIENT_SLUG" ]] && launch_args=(--client "$CLIENT_SLUG")
|
||||||
if [[ -r /dev/tty ]]; then
|
if [[ -r /dev/tty ]]; then
|
||||||
exec "${HOME}/.local/bin/hermes-client-onboarding" </dev/tty >/dev/tty 2>/dev/tty
|
exec "${HOME}/.local/bin/hermes-client-onboarding" "${launch_args[@]}" </dev/tty >/dev/tty 2>/dev/tty
|
||||||
else
|
else
|
||||||
exec "${HOME}/.local/bin/hermes-client-onboarding"
|
exec "${HOME}/.local/bin/hermes-client-onboarding" "${launch_args[@]}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
# Fallback: classic CLI + PTY inject script if present
|
# Fallback: classic CLI + PTY inject script if present
|
||||||
|
|
@ -258,7 +286,7 @@ launch_hermes_onboarding() {
|
||||||
fi
|
fi
|
||||||
exec python3 "$auto_py"
|
exec python3 "$auto_py"
|
||||||
fi
|
fi
|
||||||
die "launcher missing — re-run install or: hermes chat --cli -s ${SKILL_NAME}"
|
die "launcher missing — re-run install or: hermes --profile ${CLIENT_SLUG:-default} chat --cli -s ${SKILL_NAME}"
|
||||||
}
|
}
|
||||||
|
|
||||||
launch_conductor() {
|
launch_conductor() {
|
||||||
|
|
@ -292,22 +320,44 @@ launch_conductor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
provision_client_if_needed() {
|
||||||
|
if [[ -z "$CLIENT_SLUG" ]]; then
|
||||||
|
if [[ "$REQUIRE_CLIENT" == "1" ]]; then
|
||||||
|
die "missing --client SLUG (shared VPS multi-tenant). Example: bash -s -- --client flavia | or HERMES_REQUIRE_CLIENT=0 for laptop"
|
||||||
|
fi
|
||||||
|
warn "No --client: using default ~/.hermes (not multi-tenant safe on shared host)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
# normalize slug early
|
||||||
|
CLIENT_SLUG="$(printf '%s' "$CLIENT_SLUG" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9-' '-' | sed -E 's/-+/-/g; s/^-|-$//g')"
|
||||||
|
export HERMES_CLIENT_SLUG="$CLIENT_SLUG"
|
||||||
|
local prov="${HOME}/.local/bin/hermes-client-provision"
|
||||||
|
[[ -x "$prov" ]] || prov="${HOME}/.hermes/skills/${SKILL_NAME}/scripts/provision-client-instance.sh"
|
||||||
|
[[ -x "$prov" ]] || die "provision script missing after skill install"
|
||||||
|
log "Provisioning isolated instance: ${CLIENT_SLUG}"
|
||||||
|
"$prov" --client "$CLIENT_SLUG" --no-start
|
||||||
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
log "DomHubs Hermes Client Onboarding"
|
log "DomHubs Hermes Client Onboarding (shared VPS multi-tenant)"
|
||||||
log "Target: this machine (run via SSH on the client VPS)"
|
log "Target: this machine (run via SSH on the DomHubs VPS)"
|
||||||
if [[ "$(uname -s 2>/dev/null || true)" == "Darwin" ]]; then
|
if [[ "$(uname -s 2>/dev/null || true)" == "Darwin" ]]; then
|
||||||
warn "You appear to be on macOS. Production onboarding expects Ubuntu/Debian VPS:"
|
warn "You appear to be on macOS. Production onboarding expects the DomHubs VPS:"
|
||||||
warn " ssh root@IP_DA_VPS"
|
warn " ssh domhubs-vps"
|
||||||
warn " curl -fsSL https://setup.domhubs.com.br/hermes | bash"
|
warn " curl -fsSL https://setup.domhubs.com.br/hermes | bash -s -- --client SLUG"
|
||||||
fi
|
fi
|
||||||
ensure_hermes
|
ensure_hermes
|
||||||
install_skill
|
install_skill
|
||||||
|
provision_client_if_needed
|
||||||
|
|
||||||
if [[ "$NO_LAUNCH" -eq 1 ]]; then
|
if [[ "$NO_LAUNCH" -eq 1 ]]; then
|
||||||
log "Done (--no-launch). Re-enter VPS later with: ssh root@IP_DA_VPS (or ssh domhubs-vps)"
|
log "Done (--no-launch). Re-enter: ssh domhubs-vps"
|
||||||
log "Start with (agent speaks first):"
|
if [[ -n "$CLIENT_SLUG" ]]; then
|
||||||
|
echo " hermes-client-onboarding --client ${CLIENT_SLUG}"
|
||||||
|
echo " hermes --profile ${CLIENT_SLUG} gateway status"
|
||||||
|
else
|
||||||
echo " hermes-client-onboarding"
|
echo " hermes-client-onboarding"
|
||||||
echo " # ou: hermes chat --tui -s ${SKILL_NAME} -q \"…\""
|
fi
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
---
|
---
|
||||||
name: hermes-client-onboarding
|
name: hermes-client-onboarding
|
||||||
description: Use when setting up Hermes for a client, install Hermes + Telegram + DeepSeek, run a demo setup, or launch client onboarding. Conducts guided conversational onboarding on a clean Linux VM (deepseek-v4-flash, Telegram gateway, systemd, SOUL.md).
|
description: Use when setting up Hermes for a client, install Hermes + Telegram + DeepSeek, run a demo setup, or launch client onboarding. Conducts guided conversational onboarding on a clean Linux VM (deepseek-v4-flash, Telegram gateway, systemd, SOUL.md).
|
||||||
version: 1.3.0
|
version: 1.4.0
|
||||||
author: DomHubs
|
author: DomHubs
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
metadata:
|
metadata:
|
||||||
hermes:
|
hermes:
|
||||||
tags: [onboarding, client, telegram, deepseek, gateway, demo, vps]
|
tags: [onboarding, client, telegram, deepseek, gateway, demo, vps, multi-tenant]
|
||||||
related_skills: []
|
related_skills: []
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -15,9 +15,15 @@ metadata:
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
You are conducting a professional, step-by-step onboarding of Hermes Agent on a clean Ubuntu/Debian **VPS** so a client can start using it immediately (primarily via Telegram). The goal is a working agent in minutes, with **native DeepSeek** (`DEEPSEEK_API_KEY`, provider `deepseek`) and model **`deepseek-v4-flash`** (V4 Flash 0731 family) as the default, Telegram as the primary channel, and the gateway running as a persistent service.
|
You are conducting a professional, step-by-step onboarding of Hermes Agent on the **shared DomHubs Ubuntu/Debian VPS** so a client can start using it immediately (primarily via Telegram). DomHubs does **not** give each client a new VM — many clients share one host.
|
||||||
|
|
||||||
This skill is designed for live demos in front of the client and for commercial handoff. Be clear, structured, and efficient. Always confirm critical values before applying them.
|
**Isolation model (required):** one client = one Hermes **profile** (`~/.hermes/profiles/<slug>/`) + its own gateway unit (`hermes-gateway-<slug>.service`) + its own bot token, allowlist, SOUL, and `state.db`. Never put a client bot on the default `~/.hermes` home.
|
||||||
|
|
||||||
|
Containers are optional later (OS sandbox for untrusted tools). Profiles already prevent secret/history/gateway conflicts for Telegram bots.
|
||||||
|
|
||||||
|
Goal: working agent in minutes, native DeepSeek (`DEEPSEEK_API_KEY`, provider `deepseek`), model **`deepseek-v4-flash`**, Telegram primary, gateway as systemd user service **for that profile only**.
|
||||||
|
|
||||||
|
Be clear, structured, and efficient. Always confirm critical values before applying them.
|
||||||
|
|
||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
|
|
@ -32,63 +38,73 @@ Don't use for: day-to-day Hermes coding tasks after onboarding is done; multi-te
|
||||||
|
|
||||||
The onboarding is complete only when all of the following are true:
|
The onboarding is complete only when all of the following are true:
|
||||||
|
|
||||||
- Operator can **SSH into the VPS** and re-enter when needed
|
- Operator can **SSH into the shared DomHubs VPS**
|
||||||
- Hermes is installed and `hermes` command works
|
- Client has a dedicated **profile slug** (not default `~/.hermes`)
|
||||||
- Model is set to `deepseek-v4-flash` with provider `deepseek`
|
- Hermes is installed; `hermes --profile <slug> …` works
|
||||||
- `DEEPSEEK_API_KEY` is configured
|
- Model is set to `deepseek-v4-flash` with provider `deepseek` **inside that profile**
|
||||||
- Telegram bot token and at least one allowed user ID are set
|
- `DEEPSEEK_API_KEY` is configured **in the profile `.env`**
|
||||||
- Gateway is installed as a systemd service and is running
|
- Telegram bot token (unique to this client) and allowlist are set **in the profile**
|
||||||
- A test message sent to the Telegram bot receives a coherent reply
|
- Gateway unit `hermes-gateway-<slug>` is installed and running
|
||||||
- `hermes doctor` reports no critical errors
|
- No other profile uses the same Telegram bot token
|
||||||
- SOUL.md has been personalized (or the user explicitly skipped it)
|
- A test message to the bot receives a coherent reply
|
||||||
|
- `hermes --profile <slug> doctor` has no critical errors
|
||||||
|
- SOUL.md personalized under the profile home (or user skipped)
|
||||||
|
|
||||||
## Phase 0 — Entrar na VPS (antes de tudo)
|
## Phase 0 — Entrar na VPS + escolher instância (antes de tudo)
|
||||||
|
|
||||||
Onboarding runs **inside** the Linux VPS, not on the operator’s laptop. First step is always SSH.
|
Onboarding runs **inside** the shared DomHubs VPS. First step is always SSH; second is a **client slug**.
|
||||||
|
|
||||||
**Generic (any client VM):**
|
**DomHubs ops (operator Mac):**
|
||||||
|
|
||||||
```bash
|
|
||||||
ssh root@IP_DA_VPS
|
|
||||||
# or: ssh USER@IP_DA_VPS
|
|
||||||
```
|
|
||||||
|
|
||||||
**DomHubs ops alias (operator Mac, after key is set):**
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh domhubs-vps
|
ssh domhubs-vps
|
||||||
# Host: 169.58.116.28 User: root Key: ~/.ssh/domhubs_vps
|
# Host: 169.58.116.28 User: root Key: ~/.ssh/domhubs_vps
|
||||||
```
|
```
|
||||||
|
|
||||||
**Then, already on the VPS, bootstrap + this skill (one-liner):**
|
**On the VPS — provision + onboarding for ONE client (required):**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://setup.domhubs.com.br/hermes | bash
|
# slug = stable id (flavia, acme, joao-silva). Never reuse across clients.
|
||||||
|
curl -fsSL https://setup.domhubs.com.br/hermes | bash -s -- --client SLUG
|
||||||
```
|
```
|
||||||
|
|
||||||
Variants on the VPS:
|
Examples:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# install only (no auto-launch)
|
curl -fsSL https://setup.domhubs.com.br/hermes | bash -s -- --client flavia
|
||||||
curl -fsSL https://setup.domhubs.com.br/hermes | bash -s -- --no-launch
|
curl -fsSL https://setup.domhubs.com.br/hermes | bash -s -- --client acme --no-launch
|
||||||
|
|
||||||
# re-open onboarding later
|
|
||||||
hermes-client-onboarding
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If the session drops mid-onboarding, reconnect with the same `ssh` and reattach tmux if used:
|
This creates/reuses:
|
||||||
|
|
||||||
|
| Piece | Path / unit |
|
||||||
|
|-------|-------------|
|
||||||
|
| Home | `~/.hermes/profiles/<slug>/` |
|
||||||
|
| Secrets | `…/profiles/<slug>/.env` |
|
||||||
|
| Soul | `…/profiles/<slug>/SOUL.md` |
|
||||||
|
| History | `…/profiles/<slug>/state.db` |
|
||||||
|
| Gateway | `hermes-gateway-<slug>.service` |
|
||||||
|
|
||||||
|
**Re-enter later:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh root@IP_DA_VPS # or: ssh domhubs-vps
|
ssh domhubs-vps
|
||||||
tmux ls
|
hermes --profile SLUG gateway status
|
||||||
tmux attach -t hermes-onboard-<pid> # if the launcher created one
|
hermes-client-onboarding --client SLUG
|
||||||
# or restart onboarding:
|
# tmux: tmux ls && tmux attach -t hermes-onboard-SLUG-…
|
||||||
hermes-client-onboarding
|
|
||||||
```
|
```
|
||||||
|
|
||||||
In Phase 6 handover, **always** leave the client/operator with the exact SSH command for *their* IP (do not invent IPs).
|
**Hard isolation rules (never break these):**
|
||||||
|
|
||||||
**Done when:** shell is on the target Linux VPS (hostname/IP known) and you can run commands as the deploy user (usually `root`).
|
1. One Telegram bot token → exactly one profile/gateway.
|
||||||
|
2. All `hermes config set` / doctor / gateway for a client use `--profile SLUG` (or `HERMES_HOME=~/.hermes/profiles/SLUG`).
|
||||||
|
3. Do not start client bots on the default `hermes-gateway.service` (host default is for tooling/setup only).
|
||||||
|
4. Do not enable heavy shared MCP servers on client profiles (leaks tokens + RAM).
|
||||||
|
5. `kanban.dispatch_in_gateway: false` on client profiles (shared host).
|
||||||
|
|
||||||
|
If the kickoff mentions a profile slug, **stay inside that profile for the entire onboarding**.
|
||||||
|
|
||||||
|
**Done when:** shell is on DomHubs VPS, client slug known, profile home exists (or will be created immediately).
|
||||||
|
|
||||||
## Pre-flight Checks (do these first)
|
## Pre-flight Checks (do these first)
|
||||||
|
|
||||||
|
|
@ -227,67 +243,74 @@ Write the final content to `~/.hermes/SOUL.md`. Confirm before overwriting if th
|
||||||
|
|
||||||
**Done when:** SOUL.md written or user explicitly skipped personalization.
|
**Done when:** SOUL.md written or user explicitly skipped personalization.
|
||||||
|
|
||||||
### Phase 5 — Gateway & Persistence
|
### Phase 5 — Gateway & Persistence (per profile)
|
||||||
|
|
||||||
1. Install the gateway as a system service:
|
Assume client slug is `$SLUG` (from Phase 0 / kickoff).
|
||||||
|
|
||||||
|
1. Prefer the provisioner if the unit is missing:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hermes gateway install
|
hermes-client-provision --client "$SLUG"
|
||||||
|
# or: hermes --profile "$SLUG" gateway install
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Start / restart it:
|
2. Start / restart **only this profile’s** gateway:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hermes gateway start
|
hermes --profile "$SLUG" gateway start
|
||||||
# or
|
# or
|
||||||
hermes gateway restart
|
hermes --profile "$SLUG" gateway restart
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Check status:
|
3. Check status:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hermes gateway status
|
hermes --profile "$SLUG" gateway status
|
||||||
|
systemctl --user is-active "hermes-gateway-${SLUG}.service"
|
||||||
```
|
```
|
||||||
|
|
||||||
4. If the service fails, inspect logs (`hermes gateway logs` or `journalctl -u hermes* -n 50` / `launchctl` on macOS) and fix common issues (PATH, missing env, permissions). See `references/troubleshooting.md`.
|
4. If the service fails, inspect logs:
|
||||||
|
|
||||||
**Done when:** gateway status shows running and service is installed for reboot persistence.
|
```bash
|
||||||
|
journalctl --user -u "hermes-gateway-${SLUG}" -n 50 --no-pager
|
||||||
|
```
|
||||||
|
|
||||||
|
Never restart the default `hermes-gateway.service` for a client bot unless you intentionally want the default home (you should not).
|
||||||
|
|
||||||
|
**Done when:** `hermes-gateway-<slug>` is active and Telegram connected for that profile only.
|
||||||
|
|
||||||
### Phase 6 — Validation & Handover
|
### Phase 6 — Validation & Handover
|
||||||
|
|
||||||
Run the full validation sequence:
|
Run the full validation sequence (replace `$SLUG`):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hermes doctor
|
hermes --profile "$SLUG" doctor
|
||||||
hermes gateway status
|
hermes --profile "$SLUG" gateway status
|
||||||
```
|
```
|
||||||
|
|
||||||
Then instruct the user to send a test message to the Telegram bot (“oi” ou “teste”). Confirm that a coherent reply arrives.
|
Then instruct the user to send a test message to the Telegram bot (“oi” ou “teste”). Confirm that a coherent reply arrives.
|
||||||
|
|
||||||
Final checklist to present to the user:
|
Final checklist:
|
||||||
|
|
||||||
- [ ] Hermes installed and in PATH
|
- [ ] Profile `~/.hermes/profiles/<slug>/` isolated
|
||||||
- [ ] Model = deepseek-v4-flash via provider deepseek (native API)
|
- [ ] Model = deepseek-v4-flash via provider deepseek (in profile)
|
||||||
- [ ] Telegram bot responding
|
- [ ] Unique Telegram bot responding
|
||||||
- [ ] Gateway running as service (survives reboot)
|
- [ ] Unit `hermes-gateway-<slug>` running (survives reboot + linger)
|
||||||
- [ ] SOUL.md personalized
|
- [ ] SOUL.md personalized under profile
|
||||||
- [ ] `hermes doctor` clean
|
- [ ] `hermes --profile <slug> doctor` clean
|
||||||
|
- [ ] No token collision with other profiles
|
||||||
|
|
||||||
Give the user the useful commands for later:
|
Ops commands for later:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Re-enter the VPS (fill real IP / use DomHubs alias)
|
ssh domhubs-vps
|
||||||
ssh root@IP_DA_VPS
|
hermes --profile SLUG gateway status
|
||||||
# ssh domhubs-vps
|
journalctl --user -u hermes-gateway-SLUG -n 50 --no-pager
|
||||||
|
hermes --profile SLUG doctor
|
||||||
hermes gateway status
|
hermes profile list
|
||||||
hermes gateway logs
|
|
||||||
hermes doctor
|
|
||||||
hermes config get model.default
|
|
||||||
hermes update
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Done when:** checklist walked, test Telegram reply confirmed, SSH re-entry command + useful commands delivered.
|
**Done when:** checklist walked, test Telegram reply confirmed, slug + SSH re-entry delivered.
|
||||||
|
|
||||||
## Error Handling Guidelines
|
## Error Handling Guidelines
|
||||||
|
|
||||||
|
|
@ -321,35 +344,34 @@ hermes update
|
||||||
## Reference Commands (quick lookup)
|
## Reference Commands (quick lookup)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Enter VPS first
|
# Enter shared VPS
|
||||||
ssh root@IP_DA_VPS
|
ssh domhubs-vps
|
||||||
# DomHubs ops: ssh domhubs-vps
|
|
||||||
|
|
||||||
# DomHubs one-liner (on the VPS)
|
# New / resume client instance (on VPS)
|
||||||
curl -fsSL https://setup.domhubs.com.br/hermes | bash
|
curl -fsSL https://setup.domhubs.com.br/hermes | bash -s -- --client SLUG
|
||||||
|
hermes-client-provision --client SLUG
|
||||||
|
hermes-client-onboarding --client SLUG
|
||||||
|
|
||||||
# Install Hermes only
|
# Config inside profile (HERMES_HOME or --profile)
|
||||||
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash -s -- --skip-browser
|
export HERMES_HOME=~/.hermes/profiles/SLUG
|
||||||
|
# or: hermes --profile SLUG config set …
|
||||||
|
hermes --profile SLUG config set DEEPSEEK_API_KEY "sk-..."
|
||||||
|
hermes --profile SLUG config set model.provider deepseek
|
||||||
|
hermes --profile SLUG config set model.default deepseek-v4-flash
|
||||||
|
hermes --profile SLUG config set model.base_url "https://api.deepseek.com/v1"
|
||||||
|
hermes --profile SLUG config set TELEGRAM_BOT_TOKEN "..."
|
||||||
|
hermes --profile SLUG config set TELEGRAM_ALLOWED_USERS "123456789"
|
||||||
|
grep -E '^TELEGRAM_ALLOWED_USERS=' ~/.hermes/profiles/SLUG/.env
|
||||||
|
|
||||||
# Core config (or use the helper script)
|
# Gateway for this client only
|
||||||
hermes config set DEEPSEEK_API_KEY "sk-..."
|
hermes --profile SLUG gateway install
|
||||||
hermes config set model.provider deepseek
|
hermes --profile SLUG gateway restart
|
||||||
hermes config set model.default deepseek-v4-flash
|
hermes --profile SLUG gateway status
|
||||||
hermes config set model.base_url "https://api.deepseek.com/v1"
|
journalctl --user -u hermes-gateway-SLUG -n 50 --no-pager
|
||||||
hermes config set TELEGRAM_BOT_TOKEN "..."
|
|
||||||
hermes config set TELEGRAM_ALLOWED_USERS "123456789"
|
|
||||||
grep -E '^TELEGRAM_ALLOWED_USERS=' ~/.hermes/.env # must match IDs above
|
|
||||||
|
|
||||||
# Gateway
|
# Fleet
|
||||||
hermes gateway install
|
hermes profile list
|
||||||
hermes gateway restart
|
systemctl --user list-units 'hermes-gateway*' --all
|
||||||
hermes gateway status
|
|
||||||
# Linux: journalctl --user -u hermes-gateway -n 50
|
|
||||||
# macOS: tail -f ~/.hermes/logs/gateway.log
|
|
||||||
|
|
||||||
# Validation
|
|
||||||
hermes doctor
|
|
||||||
hermes --version
|
|
||||||
```
|
```
|
||||||
|
|
||||||
When the user says the onboarding is finished or the bot is responding correctly, summarize what was configured and congratulate them. Offer to make any final adjustments.
|
When the user says the onboarding is finished or the bot is responding correctly, summarize what was configured and congratulate them. Offer to make any final adjustments.
|
||||||
|
|
|
||||||
171
skill/hermes-client-onboarding/scripts/provision-client-instance.sh
Executable file
171
skill/hermes-client-onboarding/scripts/provision-client-instance.sh
Executable file
|
|
@ -0,0 +1,171 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# DomHubs — provision an isolated Hermes client instance on a SHARED VPS.
|
||||||
|
#
|
||||||
|
# Model: one Linux host, many clients = many Hermes *profiles*
|
||||||
|
# ~/.hermes/profiles/<slug>/ → own .env, SOUL, state.db, gateway unit
|
||||||
|
# systemd: hermes-gateway-<slug>.service
|
||||||
|
# Telegram: one bot token per profile (never share tokens)
|
||||||
|
#
|
||||||
|
# Why not Docker first: Hermes already isolates via HERMES_HOME + multi-gateway.
|
||||||
|
# Containers later only if you need OS-level sandbox for untrusted tool use.
|
||||||
|
#
|
||||||
|
# Usage (on the VPS as root/deploy user):
|
||||||
|
# provision-client-instance.sh --client flavia
|
||||||
|
# provision-client-instance.sh --client acme --description "Acme assistant"
|
||||||
|
# provision-client-instance.sh --client flavia --clone-from edwiges
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SLUG=""
|
||||||
|
DESCRIPTION=""
|
||||||
|
CLONE_FROM=""
|
||||||
|
START_GATEWAY=1
|
||||||
|
INSTALL_GATEWAY=1
|
||||||
|
LEAN_MCP=1
|
||||||
|
|
||||||
|
log() { printf '==> %s\n' "$*"; }
|
||||||
|
warn() { printf 'warn: %s\n' "$*" >&2; }
|
||||||
|
die() { printf 'error: %s\n' "$*" >&2; exit 1; }
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage: provision-client-instance.sh --client SLUG [options]
|
||||||
|
|
||||||
|
--client SLUG Required. lowercase [a-z0-9-] (e.g. flavia, acme-corp)
|
||||||
|
--description TEXT Stored on the profile
|
||||||
|
--clone-from PROFILE Clone config skeleton from another profile (not secrets by default uses --clone)
|
||||||
|
--no-gateway-install Create profile only
|
||||||
|
--no-start Install unit but do not start
|
||||||
|
--keep-mcp Do not force mcp_servers: {}
|
||||||
|
-h, --help
|
||||||
|
|
||||||
|
Env:
|
||||||
|
HERMES_CLIENT_SLUG Same as --client
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--client) SLUG="${2:-}"; shift 2 ;;
|
||||||
|
--description) DESCRIPTION="${2:-}"; shift 2 ;;
|
||||||
|
--clone-from) CLONE_FROM="${2:-}"; shift 2 ;;
|
||||||
|
--no-gateway-install) INSTALL_GATEWAY=0; shift ;;
|
||||||
|
--no-start) START_GATEWAY=0; shift ;;
|
||||||
|
--keep-mcp) LEAN_MCP=0; shift ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) die "unknown arg: $1" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
SLUG="${SLUG:-${HERMES_CLIENT_SLUG:-}}"
|
||||||
|
[[ -n "$SLUG" ]] || die "--client SLUG is required (shared VPS multi-tenant)"
|
||||||
|
|
||||||
|
# normalize: lowercase, allow a-z0-9-
|
||||||
|
SLUG="$(printf '%s' "$SLUG" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9-' '-')"
|
||||||
|
SLUG="$(printf '%s' "$SLUG" | sed -E 's/-+/-/g; s/^-|-$//g')"
|
||||||
|
[[ "$SLUG" =~ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ ]] || die "invalid slug after normalize: $SLUG"
|
||||||
|
[[ "$SLUG" != "default" && "$SLUG" != "root" && "$SLUG" != "main" ]] || die "reserved slug: $SLUG"
|
||||||
|
|
||||||
|
command -v hermes >/dev/null 2>&1 || die "hermes not on PATH"
|
||||||
|
export PATH="${HOME}/.local/bin:/usr/local/bin:${PATH}"
|
||||||
|
|
||||||
|
PROFILE_HOME="${HERMES_ROOT:-${HOME}/.hermes}/profiles/${SLUG}"
|
||||||
|
DESCRIPTION="${DESCRIPTION:-DomHubs client instance: ${SLUG}}"
|
||||||
|
|
||||||
|
log "Provisioning isolated client instance: ${SLUG}"
|
||||||
|
log "Profile home: ${PROFILE_HOME}"
|
||||||
|
|
||||||
|
if [[ -d "$PROFILE_HOME" && -f "$PROFILE_HOME/config.yaml" ]]; then
|
||||||
|
log "Profile already exists — reusing ${SLUG}"
|
||||||
|
else
|
||||||
|
create_args=(profile create "$SLUG" --description "$DESCRIPTION" --no-skills)
|
||||||
|
if [[ -n "$CLONE_FROM" ]]; then
|
||||||
|
create_args+=(--clone-from "$CLONE_FROM")
|
||||||
|
fi
|
||||||
|
hermes "${create_args[@]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -d "$PROFILE_HOME" ]] || die "profile home missing after create: $PROFILE_HOME"
|
||||||
|
chmod 700 "$PROFILE_HOME" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Lean production defaults for multi-tenant bots
|
||||||
|
python3 - "$PROFILE_HOME" "$LEAN_MCP" <<'PY'
|
||||||
|
import sys, re
|
||||||
|
from pathlib import Path
|
||||||
|
home = Path(sys.argv[1])
|
||||||
|
lean = sys.argv[2] == "1"
|
||||||
|
cfg = home / "config.yaml"
|
||||||
|
if not cfg.exists():
|
||||||
|
sys.exit(0)
|
||||||
|
text = cfg.read_text()
|
||||||
|
# kanban: only default should dispatch on shared host
|
||||||
|
if re.search(r"^kanban:\s*$", text, re.M):
|
||||||
|
if "dispatch_in_gateway" not in text:
|
||||||
|
text = re.sub(r"^kanban:\s*$", "kanban:\n dispatch_in_gateway: false", text, count=1, flags=re.M)
|
||||||
|
elif "dispatch_in_gateway" not in text:
|
||||||
|
text = text.rstrip() + "\n\nkanban:\n dispatch_in_gateway: false\n"
|
||||||
|
if lean:
|
||||||
|
if re.search(r"^mcp_servers:\s*$", text, re.M):
|
||||||
|
text = re.sub(r"^mcp_servers:\n(?: .*\n)*", "mcp_servers: {}\n", text, count=1, flags=re.M)
|
||||||
|
elif re.search(r"^mcp_servers:\s*\{\s*\}\s*$", text, re.M):
|
||||||
|
pass
|
||||||
|
elif re.search(r"^mcp_servers:", text, re.M):
|
||||||
|
text = re.sub(r"^mcp_servers:\n(?: .*\n)*", "mcp_servers: {}\n", text, count=1, flags=re.M)
|
||||||
|
else:
|
||||||
|
text = text.rstrip() + "\nmcp_servers: {}\n"
|
||||||
|
cfg.write_text(text)
|
||||||
|
print("config hardened (kanban dispatch off, lean mcp)" if lean else "config hardened (kanban dispatch off)")
|
||||||
|
PY
|
||||||
|
|
||||||
|
# Ensure empty secrets file exists with safe perms
|
||||||
|
touch "${PROFILE_HOME}/.env"
|
||||||
|
chmod 600 "${PROFILE_HOME}/.env"
|
||||||
|
|
||||||
|
if [[ "$INSTALL_GATEWAY" -eq 1 ]]; then
|
||||||
|
log "Installing systemd user unit: hermes-gateway-${SLUG}"
|
||||||
|
hermes --profile "$SLUG" gateway install
|
||||||
|
if [[ "$START_GATEWAY" -eq 1 ]]; then
|
||||||
|
# Do not start if no bot token yet — gateway can still run but wasteful
|
||||||
|
if grep -qE '^TELEGRAM_BOT_TOKEN=.+' "${PROFILE_HOME}/.env" 2>/dev/null; then
|
||||||
|
hermes --profile "$SLUG" gateway restart || hermes --profile "$SLUG" gateway start
|
||||||
|
log "Gateway started for ${SLUG}"
|
||||||
|
else
|
||||||
|
warn "No TELEGRAM_BOT_TOKEN in profile .env yet — unit installed, not started"
|
||||||
|
warn "After onboarding secrets: hermes --profile ${SLUG} gateway start"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Mark instance metadata (no secrets)
|
||||||
|
mkdir -p "${PROFILE_HOME}/domhubs"
|
||||||
|
cat > "${PROFILE_HOME}/domhubs/instance.json" <<EOF
|
||||||
|
{
|
||||||
|
"slug": "${SLUG}",
|
||||||
|
"profile_home": "${PROFILE_HOME}",
|
||||||
|
"gateway_unit": "hermes-gateway-${SLUG}.service",
|
||||||
|
"created_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||||
|
"isolation": "hermes-profile"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
chmod 644 "${PROFILE_HOME}/domhubs/instance.json"
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
✓ Instance ready: ${SLUG}
|
||||||
|
|
||||||
|
Home: ${PROFILE_HOME}
|
||||||
|
Unit: hermes-gateway-${SLUG}.service
|
||||||
|
Commands:
|
||||||
|
hermes --profile ${SLUG} doctor
|
||||||
|
hermes --profile ${SLUG} gateway status
|
||||||
|
hermes --profile ${SLUG} chat --cli -s hermes-client-onboarding
|
||||||
|
journalctl --user -u hermes-gateway-${SLUG} -n 50 --no-pager
|
||||||
|
|
||||||
|
Isolation rules:
|
||||||
|
• One Telegram bot token per profile (never reuse)
|
||||||
|
• TELEGRAM_ALLOWED_USERS only for that client
|
||||||
|
• Do not run gateway on default ~/.hermes for client bots
|
||||||
|
• Default host profile: setup/tooling only
|
||||||
|
|
||||||
|
Next: run onboarding against this profile (apply secrets, SOUL, start gateway).
|
||||||
|
EOF
|
||||||
|
|
@ -9,20 +9,44 @@ set -euo pipefail
|
||||||
export PATH="${HOME}/.local/bin:/usr/local/bin:${PATH}"
|
export PATH="${HOME}/.local/bin:/usr/local/bin:${PATH}"
|
||||||
|
|
||||||
SKILL_NAME="${HERMES_ONBOARD_SKILL:-hermes-client-onboarding}"
|
SKILL_NAME="${HERMES_ONBOARD_SKILL:-hermes-client-onboarding}"
|
||||||
|
# Shared VPS multi-tenant: isolate into Hermes profile
|
||||||
|
CLIENT_SLUG="${HERMES_CLIENT_SLUG:-${HERMES_PROFILE:-}}"
|
||||||
|
if [[ -n "${1:-}" && "$1" == "--client" && -n "${2:-}" ]]; then
|
||||||
|
CLIENT_SLUG="$2"
|
||||||
|
shift 2
|
||||||
|
fi
|
||||||
|
if [[ -n "${1:-}" && "$1" == "--profile" && -n "${2:-}" ]]; then
|
||||||
|
CLIENT_SLUG="$2"
|
||||||
|
shift 2
|
||||||
|
fi
|
||||||
|
|
||||||
# Short: long text trips Hermes paste-collapse
|
# Short: long text trips Hermes paste-collapse
|
||||||
KICKOFF="${HERMES_ONBOARD_KICKOFF:-Inicie o onboarding agora. Skill hermes-client-onboarding. Pre-flight silencioso e Phase 1 (voce fala primeiro).}"
|
if [[ -n "$CLIENT_SLUG" ]]; then
|
||||||
|
KICKOFF_DEFAULT="Inicie onboarding do cliente ${CLIENT_SLUG}. Skill hermes-client-onboarding. Multi-tenant profile ${CLIENT_SLUG} only. Pre-flight e Phase 1."
|
||||||
|
else
|
||||||
|
KICKOFF_DEFAULT="Inicie o onboarding agora. Skill hermes-client-onboarding. Pre-flight silencioso e Phase 1 (voce fala primeiro)."
|
||||||
|
fi
|
||||||
|
KICKOFF="${HERMES_ONBOARD_KICKOFF:-$KICKOFF_DEFAULT}"
|
||||||
KICKOFF="$(printf '%s' "$KICKOFF" | tr '\n' ' ' | sed 's/ */ /g')"
|
KICKOFF="$(printf '%s' "$KICKOFF" | tr '\n' ' ' | sed 's/ */ /g')"
|
||||||
|
|
||||||
export HERMES_ONBOARD_SKILL="$SKILL_NAME"
|
export HERMES_ONBOARD_SKILL="$SKILL_NAME"
|
||||||
export HERMES_ONBOARD_KICKOFF="$KICKOFF"
|
export HERMES_ONBOARD_KICKOFF="$KICKOFF"
|
||||||
export HERMES_TUI_SKILLS="$SKILL_NAME"
|
export HERMES_TUI_SKILLS="$SKILL_NAME"
|
||||||
export HERMES_TUI_QUERY="$KICKOFF"
|
export HERMES_TUI_QUERY="$KICKOFF"
|
||||||
|
export HERMES_CLIENT_SLUG="${CLIENT_SLUG:-}"
|
||||||
|
|
||||||
if ! command -v hermes >/dev/null 2>&1; then
|
if ! command -v hermes >/dev/null 2>&1; then
|
||||||
echo "error: hermes not on PATH" >&2
|
echo "error: hermes not on PATH" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
HERMES_BIN=(hermes)
|
||||||
|
if [[ -n "$CLIENT_SLUG" ]]; then
|
||||||
|
HERMES_BIN=(hermes --profile "$CLIENT_SLUG")
|
||||||
|
export HERMES_HOME="${HOME}/.hermes/profiles/${CLIENT_SLUG}"
|
||||||
|
echo "==> Profile/client: ${CLIENT_SLUG} (HERMES_HOME=${HERMES_HOME})"
|
||||||
|
fi
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
AUTO_PY=""
|
AUTO_PY=""
|
||||||
for candidate in \
|
for candidate in \
|
||||||
|
|
@ -36,19 +60,20 @@ done
|
||||||
use_tui="${HERMES_ONBOARD_USE_TUI:-0}"
|
use_tui="${HERMES_ONBOARD_USE_TUI:-0}"
|
||||||
if [[ "$use_tui" == "1" ]]; then
|
if [[ "$use_tui" == "1" ]]; then
|
||||||
if [[ -r /dev/tty ]]; then
|
if [[ -r /dev/tty ]]; then
|
||||||
exec hermes chat --tui -s "$SKILL_NAME" --query "$KICKOFF" </dev/tty
|
exec "${HERMES_BIN[@]}" chat --tui -s "$SKILL_NAME" --query "$KICKOFF" </dev/tty
|
||||||
fi
|
fi
|
||||||
exec hermes chat --tui -s "$SKILL_NAME" --query "$KICKOFF"
|
exec "${HERMES_BIN[@]}" chat --tui -s "$SKILL_NAME" --query "$KICKOFF"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Preferred: tmux (no frozen PTY wrapper) ---
|
# --- Preferred: tmux (no frozen PTY wrapper) ---
|
||||||
if command -v tmux >/dev/null 2>&1 && [[ -t 0 && -t 1 ]]; then
|
if command -v tmux >/dev/null 2>&1 && [[ -t 0 && -t 1 ]]; then
|
||||||
SESSION="hermes-onboard-$$"
|
SESSION="hermes-onboard-${CLIENT_SLUG:-default}-$$"
|
||||||
# Kill leftover same-name (shouldn't happen with $$)
|
# Kill leftover same-name (shouldn't happen with $$)
|
||||||
tmux has-session -t "$SESSION" 2>/dev/null && tmux kill-session -t "$SESSION" 2>/dev/null || true
|
tmux has-session -t "$SESSION" 2>/dev/null && tmux kill-session -t "$SESSION" 2>/dev/null || true
|
||||||
|
|
||||||
|
HERMES_CMD="${HERMES_BIN[*]} chat --cli -s ${SKILL_NAME}"
|
||||||
tmux new-session -d -s "$SESSION" -x "$(tput cols 2>/dev/null || echo 120)" -y "$(tput lines 2>/dev/null || echo 40)" \
|
tmux new-session -d -s "$SESSION" -x "$(tput cols 2>/dev/null || echo 120)" -y "$(tput lines 2>/dev/null || echo 40)" \
|
||||||
"export PATH=\"${PATH}\"; hermes chat --cli -s ${SKILL_NAME}; exec bash"
|
"export PATH=\"${PATH}\"; export HERMES_CLIENT_SLUG=\"${CLIENT_SLUG:-}\"; export HERMES_HOME=\"${HERMES_HOME:-}\"; ${HERMES_CMD}; exec bash"
|
||||||
|
|
||||||
# Wait until Hermes is up, then type kickoff + Enter
|
# Wait until Hermes is up, then type kickoff + Enter
|
||||||
for i in $(seq 1 40); do
|
for i in $(seq 1 40); do
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue