-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sh
More file actions
271 lines (253 loc) · 15.9 KB
/
Copy pathinstall.sh
File metadata and controls
271 lines (253 loc) · 15.9 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
#!/usr/bin/env bash
# ghost -- one-command installer for the incognito, uncensored agentic harness.
#
# ./install.sh
#
# Installs EVERYTHING, idempotently (safe to re-run): Ollama, the Hermes engine, the local
# uncensored models, the forked + debranded engine, the privacy stack, and the `ghost` +
# `ghost-login` commands.
#
# Hosted (non-local) models run through ghost's local PII scrubber and then og-veil
# (the opengradient-veil package), which encrypts each request over Oblivious-HTTP to
# the OpenGradient chat-api TEE gateway -- the same enclave path the chat.opengradient.ai
# website uses. After install, run `ghost-login` once to connect your account (a browser
# login that hands a session token to og-veil).
#
# The scrubber + og-veil talk to chat-api directly: content is private (og-veil OHTTP-encrypts
# it and the TEE enclave separates identity), reached over your normal connection.
#
# Optional config via env (all optional -- plain `./install.sh` does the full private setup):
# GHOST_LOCAL=1 also install Ollama + a local model for an offline / true-incognito
# fallback (DEFAULT is hosted-only -- no Ollama, fallback is hosted 70B)
# GHOST_LOCAL_32B=1 pull the stronger 32B local model too (26GB; implies GHOST_LOCAL)
# GHOST_SCRUB=1 opt in to OUTBOUND PII + secret redaction (OFF by default -- ghost is a
# full-fidelity agent; og-veil's OHTTP+TEE provides the privacy regardless)
# GHOST_CHAT_APP_URL= override the website used for `ghost-login` (default chat.opengradient.ai)
set -euo pipefail
# Supported platforms: macOS (privacy services via launchd) and Linux / WSL2 (systemd --user,
# with a plain background-process fallback where systemd --user isn't available).
OS="$(uname -s)"
case "$OS" in
Darwin|Linux) ;;
*) echo "!! ghost supports macOS, Linux, and WSL2. Detected: $OS." >&2; exit 1 ;;
esac
# Resolve where this script lives. When run via `curl ... | bash` there is no checkout, so
# self-bootstrap: clone (or fast-forward) the repo into ~/.ghost-src and re-exec from there. This
# makes ONE deterministic command both INSTALL and UPDATE ghost -- no manual clone, no LLM needed.
REPO="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd || true)"
if [ -z "$REPO" ] || [ ! -f "$REPO/profile/config.yaml" ]; then
command -v git >/dev/null 2>&1 || { echo "!! ghost needs git to fetch itself; install it (xcode-select --install) and re-run." >&2; exit 1; }
SRC="${GHOST_SRC_DIR:-$HOME/.ghost-src}"
if [ -d "$SRC/.git" ]; then
echo "==> Updating ghost source ($SRC)"; git -C "$SRC" pull --ff-only || git -C "$SRC" pull
else
echo "==> Fetching ghost into $SRC"; rm -rf "$SRC"; git clone https://github.com/OpenGradient/ghost.git "$SRC"
fi
exec bash "$SRC/install.sh" "$@"
fi
ENGINE_HOME="${ENGINE_HOME:-$HOME/.hermes}" # where the Hermes engine installs (official installer default)
GHOST_HOME="${GHOST_HOME:-$HOME/.ghost}" # ghost's ISOLATED state (profiles, privacy, auth)
PROFILE="$GHOST_HOME/profiles/uncensored"
PRIV="$GHOST_HOME/privacy"
LA="$HOME/Library/LaunchAgents"
ENG="${GHOST_ENGINE:-$HOME/.ghost-engine}"
PYTHON="${GHOST_PYTHON:-}" # resolved to an isolated uv venv (Python 3.11) in the Dependencies step
SCRUBBER="http://127.0.0.1:8788"
# Local models (Ollama) are OPT-IN. Default = hosted-only: no Ollama, and the fallback +
# auxiliary tasks route to a hosted model (nous/hermes-4-70b) over the same private og-veil
# path. Set GHOST_LOCAL=1 to also install Ollama + a local model for an offline / incognito
# fallback. GHOST_LOCAL_32B implies GHOST_LOCAL. (GHOST_NO_LOCAL is still accepted as a no-op
# since hosted-only is now the default.)
WANT_LOCAL="${GHOST_LOCAL:-}"; [ -n "${GHOST_LOCAL_32B:-}" ] && WANT_LOCAL=1
# Record the source path + the chosen install options so `ghost update` can re-pull and
# re-install the exact same way (see bin/ghost-update).
mkdir -p "$GHOST_HOME"
echo "$REPO" > "$GHOST_HOME/.src"
{
[ -n "${GHOST_LOCAL:-}" ] && echo "GHOST_LOCAL=1"
[ -n "${GHOST_LOCAL_32B:-}" ] && echo "GHOST_LOCAL_32B=1"
[ -n "${GHOST_SCRUB:-}" ] && echo "GHOST_SCRUB=1"
[ -n "${GHOST_CHAT_APP_URL:-}" ] && echo "GHOST_CHAT_APP_URL=$GHOST_CHAT_APP_URL"
:
} > "$GHOST_HOME/.install-env"
say(){ printf '\n\033[1;33m==>\033[0m %s\n' "$*"; }
have(){ command -v "$1" >/dev/null 2>&1; }
# ---------- 0. dependencies (auto-installed) ----------
say "Dependencies"
# uv manages the Python toolchain + an isolated venv for ghost's privacy stack, so we never depend
# on the system python (Apple ships 3.9) -- uv fetches CPython 3.11 itself if the machine lacks it.
if ! have uv; then
echo " installing uv (Astral's Python package/venv manager)"
curl -LsSf https://astral.sh/uv/install.sh | sh >/dev/null 2>&1 || true
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
fi
have uv || { echo "!! ghost needs uv (https://docs.astral.sh/uv/getting-started/installation/); install it and re-run." >&2; exit 1; }
if [ -n "$WANT_LOCAL" ]; then
if ! have ollama && have brew; then echo " installing Ollama (brew --cask)"; brew install --cask ollama || true; fi
have ollama || { echo "!! GHOST_LOCAL set but Ollama is missing -- install it from https://ollama.com (or drop GHOST_LOCAL for hosted-only) then re-run."; exit 1; }
if [ "$OS" = "Darwin" ]; then pgrep -xq ollama || open -a Ollama 2>/dev/null || true
else pgrep -xq ollama || (nohup ollama serve >/dev/null 2>&1 &) || true; fi
sleep 1
fi
if [ ! -d "$ENGINE_HOME/hermes-agent" ] && ! have hermes; then
say "Installing the Hermes Agent engine (official one-liner)"
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash \
|| { echo "!! engine install failed; install manually (https://hermes-agent.nousresearch.com) then re-run."; exit 1; }
fi
SRC="$ENGINE_HOME/hermes-agent"; [ -d "$SRC" ] || SRC="$(cd "$(dirname "$(command -v hermes)")/.." 2>/dev/null && pwd)"
[ -d "$SRC" ] || { echo "!! can't locate the Hermes engine to fork."; exit 1; }
# Privacy stack: og-veil (the opengradient-veil package) owns the whole hosted-inference protocol
# -- on-chain TEE registry discovery, OHTTP/HPKE encryption, response verification, the Supabase
# session. Declared in pyproject.toml + uv.lock, installed into an ISOLATED venv at ~/.ghost/venv
# (Python 3.11, fetched by uv if the system lacks it). Reproducible; never touches system python.
say "Privacy stack (isolated uv venv, Python 3.11)"
export UV_PROJECT_ENVIRONMENT="$GHOST_HOME/venv"
SYNC_EXTRAS=""
# The NER scrubber (Presidio + spaCy) is only needed if you opt into redaction -- skip it otherwise.
[ -n "${GHOST_SCRUB:-}" ] && SYNC_EXTRAS="--extra presidio"
( cd "$REPO" && uv sync --python 3.11 --frozen $SYNC_EXTRAS ) \
|| ( cd "$REPO" && uv sync --python 3.11 $SYNC_EXTRAS ) \
|| { echo "!! failed to provision the privacy venv (uv sync); check network and re-run." >&2; exit 1; }
PYTHON="$UV_PROJECT_ENVIRONMENT/bin/python"
echo " venv: Python $("$PYTHON" -c 'import sys;print(".".join(map(str,sys.version_info[:3])))') · og-veil $("$PYTHON" -c 'import importlib.metadata as m;print(m.version("opengradient-veil"))' 2>/dev/null)"
# ---------- 1. local models (OPT-IN via GHOST_LOCAL; 32B also needs GHOST_LOCAL_32B) ----------
LOCAL_MODEL="ghost-tool:latest"
if [ -z "$WANT_LOCAL" ]; then
say "Hosted-only (default) -- skipping Ollama + local models. Set GHOST_LOCAL=1 for an offline / incognito local fallback."
else
say "Local models (GHOST_LOCAL)"
while IFS=$'\t' read -r src alias opt; do
case "$src" in \#*|"") continue;; esac
if [ "$opt" = "optional" ] && [ -z "${GHOST_LOCAL_32B:-}" ]; then
echo " skipping optional $alias (26GB) -- set GHOST_LOCAL_32B=1 to include the stronger local model"; continue
fi
if ollama show "$alias" >/dev/null 2>&1; then echo " $alias present"; continue; fi
echo " $src -> $alias"; ollama pull "$src"; ollama cp "$src" "$alias"
done < "$REPO/models.txt"
if ollama show uncensored-local >/dev/null 2>&1; then LOCAL_MODEL="uncensored-local:latest"; else LOCAL_MODEL="ghost-tool:latest"; fi
echo " local model = $LOCAL_MODEL"
fi
# ---------- 2. uncensored profile ----------
say "Writing the uncensored profile"
mkdir -p "$PROFILE"
sed -e "s#__HOME__#$HOME#g" -e "s#__LOCAL_MODEL__#$LOCAL_MODEL#g" "$REPO/profile/config.yaml" > "$PROFILE/config.yaml"
cp "$REPO/profile/SOUL.md" "$PROFILE/SOUL.md"
[ -f "$PROFILE/.env" ] || cp "$REPO/profile/.env.example" "$PROFILE/.env"
if [ -z "$WANT_LOCAL" ]; then # hosted-only (default) -> route auxiliary + fallback to a hosted model via og-veil
"$PYTHON" - "$PROFILE/config.yaml" <<'PYEOF'
import sys, re
p = sys.argv[1]; s = open(p).read()
# Handle both key orders (provider-then-model and model-then-provider in the auxiliary blocks).
s = re.sub(r"provider: ollama-local\n(\s*)model: \S+",
r"provider: opengradient\n\1model: nous/hermes-4-70b", s)
s = re.sub(r"model: ghost-tool:latest\n(\s*)provider: ollama-local",
r"model: nous/hermes-4-70b\n\1provider: opengradient", s)
s = s.replace("provider: ollama-local", "provider: opengradient")
# Chat fallback -> the stronger 405B (aux tasks stay on 70B for speed/cost).
s = re.sub(r"(fallback_model:\n provider: opengradient\n model: )nous/hermes-4-70b",
r"\g<1>nous/hermes-4-405b", s, count=1)
open(p, "w").write(s); print(" hosted-only: fallback -> nous/hermes-4-405b, auxiliary -> nous/hermes-4-70b (via og-veil)")
PYEOF
fi
# ---------- 3. privacy stack (PII scrubber + og-veil) ----------
say "Privacy stack (PII/secret scrubber -> og-veil)"
mkdir -p "$PRIV"
cp "$REPO"/privacy/*.py "$PRIV/"
# Stage the NER engine for redaction. Redaction is OFF by default, so this only matters once you
# opt in (GHOST_SCRUB / ghost --scrub). When the presidio extra is installed, fetch the spaCy model
# and mark NER available; otherwise redaction (if turned on later) uses the regex engine.
if "$PYTHON" -c "import presidio_analyzer, presidio_anonymizer" 2>/dev/null; then
"$PYTHON" -c "import en_core_web_md" 2>/dev/null || "$PYTHON" -m spacy download en_core_web_md >/dev/null 2>&1 || true
if "$PYTHON" -c "import en_core_web_md" 2>/dev/null; then
: > "$PRIV/.presidio"; echo " redaction engine ready: Presidio + spaCy NER (used only when redaction is on)"
else
rm -f "$PRIV/.presidio"; echo " redaction engine: regex (spaCy model unavailable)"
fi
else
rm -f "$PRIV/.presidio" # presidio not installed (default) -> regex engine if redaction is turned on
fi
[ -f "$PRIV/pii_denylist.txt" ] || cp "$REPO/profile/pii_denylist.example.txt" "$PRIV/pii_denylist.txt"
cp "$REPO/profile/uncensored_prefill.json" "$PRIV/uncensored_prefill.json"
# Outbound PII + secret redaction is OPT-IN (GHOST_SCRUB=1), OFF by default: ghost is a
# full-fidelity agent and og-veil's OHTTP+TEE already make the hosted path private. The .scrub
# marker drives the bridge; the engine's redact_secrets/redact_pii follow the same default.
if [ -n "${GHOST_SCRUB:-}" ]; then
: > "$PRIV/.scrub"
"$PYTHON" - "$PROFILE/config.yaml" <<'PYEOF'
import sys, re
p = sys.argv[1]; s = open(p).read()
s = re.sub(r"(?m)^ redact_secrets: false$", " redact_secrets: true", s)
s = re.sub(r"(?m)^ redact_pii: false$", " redact_pii: true", s)
open(p, "w").write(s)
PYEOF
say "Outbound PII + secret redaction ON (GHOST_SCRUB)"
else
rm -f "$PRIV/.scrub" "$PRIV/.no_scrub"
say "Full-fidelity mode (default) -- no outbound redaction. Set GHOST_SCRUB=1 to strip your PII/secrets before the gateway."
fi
# Run the privacy services (scrubber :8788 + og-veil :11435) as managed, auto-restarting
# background services -- launchd on macOS, systemd --user on Linux/WSL2 (with a plain
# background-process fallback). Both talk to chat-api directly (content private via OHTTP/TEE).
rm -f "$PRIV/.proxy" # clean any rotating-proxy marker from an older install
if [ "$OS" = "Darwin" ]; then
mkdir -p "$LA"
for svc in hermes-pii-scrubber hermes-veil; do
sed -e "s#__PYTHON__#$PYTHON#g" -e "s#__HOME__#$HOME#g" "$REPO/launchd/com.advait.$svc.plist" > "$LA/com.advait.$svc.plist"
launchctl unload "$LA/com.advait.$svc.plist" 2>/dev/null || true
launchctl load -w "$LA/com.advait.$svc.plist"
done
elif command -v systemctl >/dev/null 2>&1 && systemctl --user show-environment >/dev/null 2>&1; then
UD="$HOME/.config/systemd/user"; mkdir -p "$UD"
for svc in ghost-scrubber ghost-veil; do
sed -e "s#__PYTHON__#$PYTHON#g" -e "s#__PRIV__#$PRIV#g" "$REPO/systemd/$svc.service" > "$UD/$svc.service"
done
systemctl --user daemon-reload
systemctl --user reenable ghost-scrubber.service ghost-veil.service >/dev/null 2>&1 || true
systemctl --user restart ghost-scrubber.service ghost-veil.service
loginctl enable-linger "$USER" >/dev/null 2>&1 || true # keep services up without an active login (reboot persistence)
echo " services started via systemd --user (logs: journalctl --user -u ghost-veil)"
else
echo " systemd --user unavailable -- starting services as background processes"
echo " (no reboot persistence; re-run the installer or 'ghost update' to restart them)"
pkill -f "$PRIV/scrubbing_proxy.py" 2>/dev/null || true
pkill -f "veil serve --foreground --skip-setup --port 11435" 2>/dev/null || true
OG_VEIL_PORT=11435 nohup "$PYTHON" -m veil serve --foreground --skip-setup --port 11435 > "$PRIV/veil.out.log" 2>&1 &
nohup "$PYTHON" "$PRIV/scrubbing_proxy.py" > "$PRIV/scrubber.out.log" 2>&1 &
fi
printf " waiting for the scrubbing bridge"
for _ in $(seq 1 15); do [ "$(curl -s -o /dev/null -w '%{http_code}' --max-time 3 "$SCRUBBER/healthz" 2>/dev/null)" = 200 ] && break; printf "."; sleep 1; done; echo " up"
printf " waiting for og-veil"
for _ in $(seq 1 20); do [ "$(curl -s -o /dev/null -w '%{http_code}' --max-time 3 "http://127.0.0.1:11435/health" 2>/dev/null)" = 200 ] && break; printf "."; sleep 1; done; echo " up (or pending ghost-login)"
# ---------- 4. fork + debrand the engine ----------
say "Forking + debranding the engine -> $ENG"
GHOST_PYTHON="$PYTHON" GHOST_ENGINE="$ENG" HERMES_SRC="$SRC" bash "$REPO/scripts/fork-engine.sh"
# ---------- 5. the ghost + ghost-login + ghost-update commands ----------
say "Installing the ghost + ghost-login + ghost-update commands"
mkdir -p "$HOME/.local/bin"
sed -e "s#__PYTHON__#$PYTHON#g" -e "s#__HOME__#$HOME#g" -e "s#__ENG__#$ENG#g" -e "s#__GHOST_HOME__#$GHOST_HOME#g" "$REPO/bin/ghost" > "$HOME/.local/bin/ghost"
sed -e "s#__PYTHON__#$PYTHON#g" -e "s#__HOME__#$HOME#g" -e "s#__GHOST_HOME__#$GHOST_HOME#g" "$REPO/bin/ghost-login" > "$HOME/.local/bin/ghost-login"
sed -e "s#__HOME__#$HOME#g" -e "s#__GHOST_HOME__#$GHOST_HOME#g" "$REPO/bin/ghost-update" > "$HOME/.local/bin/ghost-update"
chmod +x "$HOME/.local/bin/ghost" "$HOME/.local/bin/ghost-login" "$HOME/.local/bin/ghost-update"
# ---------- 6. connect your account (hosted models) ----------
say "Connect your OpenGradient Chat account (for hosted models)"
GL="$HOME/.local/bin/ghost-login" # thin wrapper over `og-veil login` (installed above)
if "$GL" --status >/dev/null 2>&1; then
echo " already connected: $("$GL" --status)"
else
echo " Hosted models (the default DeepSeek V4 Pro + Hermes 4, all open-weight) need a one-time login."
if [ -t 0 ]; then
printf " Run the browser login now? [Y/n] "; read -r ANS || true
case "${ANS:-Y}" in [Nn]*) echo " Skipped -- run 'ghost-login' anytime.";; *) "$GL" || echo " (login skipped/failed -- run 'ghost-login' anytime)";; esac
else
echo " Non-interactive install -- run 'ghost-login' once you're done."
fi
fi
# ---------- 7. smoke test ----------
say "Smoke test"
"$HOME/.local/bin/ghost" --yolo -z "Reply with one word: hi" 2>&1 | tail -2 || true
say "ghost installed -- run: ghost"
case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) echo " (add ~/.local/bin to your PATH first)";; esac
echo " Hosted default = deepseek/deepseek-v4-pro via og-veil -> the OpenGradient TEE gateway (OHTTP-private)."
echo " Inside ghost, /model switches between hosted models and the local model (true incognito)."
echo " Redaction is OFF by default (full fidelity). Opt in with GHOST_SCRUB=1 (or 'ghost --scrub'), then personalize $PRIV/pii_denylist.txt."
echo " Not connected yet? Run: ghost-login"