diff --git a/README.md b/README.md index 7405c99..fe91b2c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ghost 👻 -[![License: MIT](https://img.shields.io/badge/License-MIT-3DDC97?style=flat-square)](LICENSE)  ![Platform: macOS](https://img.shields.io/badge/platform-macOS-lightgrey?style=flat-square)  [![Built on Hermes Agent](https://img.shields.io/badge/built%20on-Hermes%20Agent-7C5CFF?style=flat-square)](https://github.com/NousResearch/hermes-agent)  ![Open-weight only](https://img.shields.io/badge/models-open--weight%20only-FF8A3D?style=flat-square) +[![License: MIT](https://img.shields.io/badge/License-MIT-3DDC97?style=flat-square)](LICENSE)  ![Platform](https://img.shields.io/badge/platform-macOS%20%C2%B7%20Linux%20%C2%B7%20WSL2-lightgrey?style=flat-square)  [![Built on Hermes Agent](https://img.shields.io/badge/built%20on-Hermes%20Agent-7C5CFF?style=flat-square)](https://github.com/NousResearch/hermes-agent)  ![Open-weight only](https://img.shields.io/badge/models-open--weight%20only-FF8A3D?style=flat-square) **A private, unrestricted agentic harness.** A real terminal agent that runs commands, edits files, executes code, and searches the web, with every hosted request routed through OpenGradient's TEE gateway so the model provider never sees your prompts. It answers what you actually ask, drops to a fully-offline local model on demand, and phones home to no one. @@ -12,7 +12,7 @@ Built on the [Hermes Agent](https://github.com/NousResearch/hermes-agent) engine ## Install (30 seconds) -macOS only. One deterministic command, no LLM and nothing agentic, installs **and** updates everything (the engine, the privacy stack, the `ghost` commands). uv provisions an isolated Python 3.11 under the hood, so the only prerequisite is `git`: +One deterministic command, no LLM and nothing agentic, installs **and** updates everything (the engine, the privacy stack, the `ghost` commands) on macOS, Linux, or WSL2. uv provisions an isolated Python 3.11 under the hood, so the only prerequisite is `git`: ```bash curl -fsSL https://raw.githubusercontent.com/OpenGradient/ghost/main/install.sh | bash @@ -29,9 +29,7 @@ Re-run the same command, or `ghost update`, to update. From a local clone it's j ## Why ghost exists -Most agents are either useless or creepy for real work. ghost fixes the two that matter most. - -### #1: The Model Lectures You Instead of Working +### Problem #1: The Model Lectures You Instead of Working > "The Net interprets censorship as damage and routes around it." > @@ -41,7 +39,7 @@ Most agents are either useless or creepy for real work. ghost fixes the two that **The Fix.** ghost only connects **open-weight, unrestricted models** (DeepSeek V4 Pro by default; Hermes 4 405B/70B) and applies a per-model steer, so the default answers in full with no sermon. Closed, refusing models (Claude, GPT, Gemini, Grok) aren't offered, and the gateway rejects anything off the list. It treats you as a competent adult, but it isn't an edgelord either: it won't volunteer illegal or shock content, it just won't refuse you. -### #2: The Provider Reads Everything You Send +### Problem #2: The Provider Reads Everything You Send > "Privacy is the power to selectively reveal oneself to the world." > diff --git a/install.sh b/install.sh index 4b49808..84d6952 100644 --- a/install.sh +++ b/install.sh @@ -25,12 +25,13 @@ # GHOST_CHAT_APP_URL= override the website used for `ghost-login` (default chat.opengradient.ai) set -euo pipefail -# macOS only: the privacy stack runs as launchd LaunchAgents and uses BSD tooling. Fail fast -# with a clear message rather than part-installing on Linux/WSL and erroring confusingly later. -if [ "$(uname -s)" != "Darwin" ]; then - echo "!! ghost's installer currently supports macOS only (it uses launchd). Detected: $(uname -s)." >&2 - exit 1 -fi +# 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 @@ -90,7 +91,9 @@ have uv || { echo "!! ghost needs uv (https://docs.astral.sh/uv/getting-started/ 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; } - pgrep -xq ollama || open -a Ollama 2>/dev/null || true ; sleep 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 @@ -193,25 +196,36 @@ 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 -mkdir -p "$LA" - -# The scrubber runs as a launchd service; og-veil talks to chat-api directly (content is -# still private via OHTTP/TEE). Clean up any rotating-proxy marker from an older install. -BASE_SERVICES="hermes-pii-scrubber" -rm -f "$PRIV/.proxy" - -for svc in $BASE_SERVICES; 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 +# 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 -# og-veil service (port 11435, to avoid colliding with Ollama on 11434). It owns the -# OHTTP/TEE/verification + auth, and talks to chat-api directly. -VEIL_PLIST="$LA/com.advait.hermes-veil.plist" -sed -e "s#__PYTHON__#$PYTHON#g" -e "s#__HOME__#$HOME#g" "$REPO/launchd/com.advait.hermes-veil.plist" > "$VEIL_PLIST" -launchctl unload "$VEIL_PLIST" 2>/dev/null || true -launchctl load -w "$VEIL_PLIST" +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" diff --git a/systemd/ghost-scrubber.service b/systemd/ghost-scrubber.service new file mode 100644 index 0000000..fb6569c --- /dev/null +++ b/systemd/ghost-scrubber.service @@ -0,0 +1,10 @@ +[Unit] +Description=ghost PII/secret scrubbing bridge (localhost :8788) + +[Service] +ExecStart=__PYTHON__ __PRIV__/scrubbing_proxy.py +Restart=always +RestartSec=2 + +[Install] +WantedBy=default.target diff --git a/systemd/ghost-veil.service b/systemd/ghost-veil.service new file mode 100644 index 0000000..84050ae --- /dev/null +++ b/systemd/ghost-veil.service @@ -0,0 +1,11 @@ +[Unit] +Description=ghost og-veil transport (OHTTP relay + TEE verify, localhost :11435) + +[Service] +Environment=OG_VEIL_PORT=11435 +ExecStart=__PYTHON__ -m veil serve --foreground --skip-setup --port 11435 +Restart=always +RestartSec=2 + +[Install] +WantedBy=default.target