# Installing third-party tools on the read-only VPS

The Hostinger VPS mounts `/root` read-only **except `/root/.hermes`** (writable). Anything that installs outside `.hermes` fails with `Errno 30 Read-only file system`. This file collects the failure modes and fixes observed 2026-07-28 while installing SkillClaw (AMAP-ML/SkillClaw) — they generalize to any pip/venv-based tool on this box.

## Failure mode 1: `git clone` into `/root` fails

`git clone <url>` run from `cwd=/root` dies with `fatal: could not create work tree dir '<name>': Read-only file system`.

**Fix:** clone into `/root/.hermes/<name>` instead. Do not bother retrying `/root/opt`, `/root/src`, etc. — only `.hermes` is writable.

## Failure mode 2: pip wheel-build cache on RO filesystem

`pip install` fails building wheels for pyproject-based packages with:

```
WARNING: Building wheel for <pkg> failed: [Errno 30] Read-only file system: '/root/.cache/pip/wheels'
error: failed-wheel-build-for-install
```

Important: when any wheel in the transaction fails, **pip aborts the whole transaction** — even the packages that built fine (including the editable install of the tool itself) are NOT installed. `pip list` will show nothing. Don't assume partial success.

**Fix:** point the cache into writable space before installing:

```bash
export PIP_CACHE_DIR=/root/.hermes/<tool>/.pip-cache
<venv>/bin/python -m pip install -e ".[extras]"
```

Same class of fix applies to any tool that writes to `~/.cache` (uv, npm cache, HF hub): redirect the cache under `/root/.hermes/`.

## Failure mode 3: tool hardcodes `Path.home()` for its config/state dir

Some tools compute their config dir as `Path.home() / ".<toolname>"` with **no env override** (SkillClaw: `~/.skillclaw`, checked `config_store.py` — no `SKILLCLAW_HOME` or similar). On this box that directory cannot be created, and even a symlink fails because creating the symlink *entry* needs write on `/root`:

```
ln -s /root/.hermes/skillclaw /root/.skillclaw
# ln: failed to create symbolic link '/root/.skillclaw': Read-only file system
```

**Fix:** `Path.home()` follows the `HOME` env var. Run the tool with a redirected HOME:

```bash
export HOME=/root/.hermes/<tool>-home
mkdir -p "$HOME"
# every invocation of the tool's CLI needs this HOME set
HOME=/root/.hermes/<tool>-home <venv>/bin/<tool> status
```

Consequences to flag for the user:
- Every CLI invocation needs the HOME prefix — easy to forget; consider a wrapper script in `/root/.hermes/bin/` if usage becomes routine.
- A daemon started this way is **not** tied to systemd and won't survive reboot. If the tool graduates to permanent use, write a unit with `Environment=HOME=/root/.hermes/<tool>-home`.
- The tool's "home" now lives at `/root/.hermes/<tool>-home/.<toolname>/` (double nesting) — remember where the config/logs actually are.

## Hermes credential pattern: `env:VAR_NAME` references

`~/.hermes/config.yaml` stores secrets as **env references**, not literals:

```yaml
model:
  api_key: env:NOUS_API_KEY   # NOT the actual key
```

Any third-party tool that you point at "the same provider Hermes uses" by copying `api_key` out of `config.yaml` will send the literal string `env:NOUS_API_KEY` as its Bearer token and get **401 Unauthorized** from upstream. The real values live in `/root/.hermes/.env` (`NOUS_API_KEY=...`, etc.).

**Fix:** resolve the reference before writing it into the other tool's config:

```bash
grep "^NOUS_API_KEY=" /root/.hermes/.env   # then inject the value
```

Diagnosis shortcut: a 401 from a known-good upstream right after wiring a new proxy/tool = check for unresolved `env:` reference first, before suspecting the key itself.

## Worked example: SkillClaw (2026-07-28)

- Repo: `/root/.hermes/SkillClaw`, venv at `.venv/`, install extras `[evolve,sharing,server]`.
- HOME redirect: `HOME=/root/.hermes/skillclaw-home`; config at `$HOME/.skillclaw/config.yaml` (chmod 600).
- Non-interactive config (skip `skillclaw setup` wizard): write the YAML directly or use `skillclaw config <dotted.key> <value>`. Dotted-key schema lives in `skillclaw/config_store.py::_DEFAULTS` (llm.*, proxy.*, prm.*, sharing.*, validation.*, dashboard.*).
- Key settings for a safe single-user eval on the live gateway box: `claw_type: none` + `configure_openclaw: false` (otherwise `skillclaw start` **rewrites `~/.hermes/config.yaml`** to route Hermes through its proxy — do not let it touch the live gateway until the tool has earned trust), `prm.enabled: false`, `sharing.enabled: false`.
- Verify: `skillclaw start --daemon`, `skillclaw status`, `curl http://127.0.0.1:30000/healthz` → `{"ok":true}`, then a real `/v1/chat/completions` round-trip through the proxy.

## Wiring Hermes to a local OpenAI-compatible proxy WITHOUT touching the gateway

To test Hermes against a local proxy (SkillClaw or any other) while leaving the live gateway path untouched, register a **custom provider entry** in `~/.hermes/config.yaml` — do not run the tool's own integration (`claw_type`), which rewrites the main `model:` block:

```bash
hermes config set providers.<name>.base_url http://127.0.0.1:<port>/v1
hermes config set providers.<name>.api_key dummy   # proxy has no client auth
hermes chat -Q --provider <name> -m <proxy-served-model> -q "..."
```

Two Hermes-specific facts this relies on:
- `hermes chat` has **no `--base-url` flag** — the custom-providers mechanism is the only clean way to retarget a one-shot chat.
- The agent's `patch`/`write_file` tools **refuse to edit `~/.hermes/config.yaml`** ("security-sensitive configuration") — use the `hermes config set` CLI instead; it handles nested provider maps fine.

## Graduating a HOME-redirected daemon to systemd

When the tool earns permanent status, stop the manual daemon (`<tool> stop`) and install a unit so it survives reboot. Run the tool in the **foreground** under systemd (no `--daemon` flag) so systemd is the supervisor; model the unit on `/etc/systemd/system/hermes-gateway.service` (Restart=always, RestartSec=5, KillMode=mixed, journal logging) and add the HOME redirect as an Environment line:

```ini
ExecStart=/root/.hermes/<tool>/.venv/bin/<tool> start
Environment="HOME=/root/.hermes/<tool>-home"
```

Note: `write_file`/`patch` also refuse `/etc/systemd/system/*.service` paths — write the unit via a `terminal` heredoc (surfaces the Security-scan approval prompt), then `systemctl daemon-reload && systemctl enable --now <tool>.service`. Verify with `systemctl is-active` + `is-enabled` + a healthz curl, then re-run the end-to-end test through the systemd-managed instance (the old daemon is dead; the port now belongs to the unit).
