Setup Manual · Docker / Google Workspace / VPS Monitoring

Hermes Agent VPS セットアップガイド

DOC / HERMES-VPS モード1(本体コンテナ化)+ bAI 風ホストコレクタのハイブリッド構成 / Ubuntu 24.04 · 26.04 LTS
構成: モード1(本体コンテナ化)+ bAI 風ホストコレクタのハイブリッド
対象 VPS: Xserver VPS(Ubuntu 24.04 LTS / 26.04 LTS 両対応)
目的: Google Workspace 連携 + VPS 状態監視を 24/7 自律運用
バージョン: v4.3(Phase 3 動作確認のユーザー切り替えを整理)
最終更新: 2026-05-24

§ 00このドキュメントについて

「Hermes 本体は Docker コンテナで完全に隔離し、ホスト OS の状態(fail2ban / journalctl 等)はホスト側の root cron が JSON 化したものを read-only マウントで読ませる」という構成のセットアップ手順をまとめたものです。

設計思想は bAI(Bridge AI)と同じで、LLM レイヤーは生データに直接到達できない、事前整形された情報のみを扱うという構造を VPS 運用に持ち込んでいます。

表記規約

各セクションの冒頭に作業ユーザーを明示しています:

  • 作業ユーザー: 管理者 — 普段の sudo 持ちユーザー(例: <管理者ユーザー>)で実行
  • 作業ユーザー: hermessudo -iu hermes で hermes に切り替えてから実行
  • 作業ユーザー: rootsudo を都度つけて実行(管理者ユーザーから)

ユーザーを跨ぐセクションでは、各コードブロックの先頭に切り替えコマンドを書いています。プロンプトが想定通りか不安な場合は whoami で確認してください。

SSH クライアントによる差異

ssh 接続クライアントによって OAuth フローの挙動が変わります:

  • Cursor / VSCode Remote-SSH: ループバックポートの自動転送機能があるため、OAuth コールバックが手元 PC のブラウザから VPS のコンテナまで自動で届く。追加設定不要
  • 素の OpenSSH / PuTTY / Termius 等: ループバック自動転送は無いため、setup wizard 実行時は ssh -L で手動トンネル、または --network host モードで起動する必要がある。

このガイドでは両方をカバーします。

§ 01アーキテクチャ概要

構成図

shell
┌──────────────── Xserver VPS (Ubuntu 24.04 / 26.04 LTS) ────┐
│                                                              │
│  ┌─ root 権限レイヤー ─────────────────────────────────┐    │
│  │                                                       │    │
│  │  cron (root)                                          │    │
│  │   └─ /usr/local/bin/hermes-health-collect.sh         │    │
│  │       ├─ fail2ban-client status                      │    │
│  │       ├─ journalctl _COMM=sshd                       │    │
│  │       ├─ systemctl is-active                         │    │
│  │       ├─ df / free / uptime                          │    │
│  │       └─ JSON 化 → /var/lib/hermes-health/           │    │
│  │                                                       │    │
│  └───────────────────────────────────────────────────────┘    │
│                              │                                │
│                              ▼ :ro mount                      │
│  ┌─ hermes system user レイヤー (sudo なし) ────────────┐    │
│  │                                                       │    │
│  │  Docker container: hermes                             │    │
│  │   ├─ entrypoint が HERMES_UID/HERMES_GID で          │    │
│  │   │  内部ユーザーを gosu リマップ                     │    │
│  │   ├─ /opt/hermes/.venv/bin/hermes  ← CLI バイナリ    │    │
│  │   ├─ /opt/data ← bind: ~/.hermes (rw)                │    │
│  │   │   ├─ config.yaml, .env                           │    │
│  │   │   ├─ memory/, skills/, sessions/                 │    │
│  │   │   ├─ workspace/    (gateway 作業ディレクトリ)     │    │
│  │   │   ├─ credentials/  (OAuth tokens)                │    │
│  │   │   └─ google/                                     │    │
│  │   ├─ /opt/data/health ← bind: /var/lib/hermes-health │    │
│  │   │                                          (ro)    │    │
│  │   └─ HERMES_DASHBOARD=1 で dashboard を同居起動       │    │
│  │     (gateway:8642 と dashboard:9119)                  │    │
│  │                                                       │    │
│  └───────────────────────────────────────────────────────┘    │
│                              │                                │
└──────────────────────────────┼────────────────────────────────┘
                               ▼
                  外部: OpenRouter / xAI / Anthropic /
                       Google API / Discord

設計原則

  1. 権限の最小化: hermes ユーザーは sudo を持たない。docker グループのみ所属。SSH 直接ログインも禁止。
  2. データの局所化: Hermes 関連の全データは ~/.hermes/ に集約。コンテナは窓に過ぎない(bind mount)。
  3. 生データへの到達禁止: Hermes は collector が出した JSON のみを読む。fail2ban-client 等を直接叩けない。
  4. 再現性: VPS 再構築は docker-compose.yml + collector スクリプト + ~/.hermes のバックアップだけで完了する。
  5. バージョン固定: :latest を使わず明示的なバージョンタグで運用。
  6. 公式 entrypoint への信任: UID/GID リマップ・dashboard 起動・gateway 初期化は全て公式 entrypoint に任せる。compose 側で user: を上書きしない。

§ 02全体の流れ(Big Picture)

セットアップは8フェーズに分かれる。

Phase内容主な作業ユーザー所要
1VPS 初期化(ユーザー、SSH、ufw、fail2ban)管理者30分
2Docker Engine インストール管理者15分
3hermes system user の作成管理者10分
4Hermes コンテナ起動、初期セットアップ、永続化検証hermes(一部管理者)60分
5Google Workspace 接続管理者 → hermes30分
6ホスト側 collector 実装管理者30分
7ヘルスチェックスキル投入hermes30分
8バックアップとロールバック手順の確立管理者30分

合計 4 時間程度。Phase 5 と Phase 6 は順序入れ替え可能。

§ 03Phase 1: VPS 初期化

作業ユーザー管理者

既に Xserver VPS で fail2ban や ufw を運用中の状態を前提とするが、念のためチェックリストを置く。

OS バージョン確認

bash
lsb_release -a
# 期待: Ubuntu 24.04 LTS (Noble Numbat) または 26.04 LTS (Resolute Raccoon)

cat /etc/os-release | grep -E '^(VERSION_ID|UBUNTU_CODENAME|VERSION_CODENAME)'
# UBUNTU_CODENAME が Phase 2 の Docker リポジトリ登録で使われる
# 例: noble (24.04) または resolute (26.04)
Ubuntu 26.04 LTS について: 2026年4月23日リリースの最新 LTS。Docker 公式リポジトリは resolute を提供しているが、リリース直後で運用情報が少ないため、何か詰まったら Docker フォーラムや Issue を確認すること。

サービス確認

bash
# fail2ban 動作確認
sudo systemctl status fail2ban

# ufw 動作確認
sudo ufw status verbose

# SSH ポート(公開鍵認証のみ、パスワード認証無効)
sudo sshd -T | grep -iE "passwordauthentication|permitrootlogin|pubkey"

期待値:

  • passwordauthentication no
  • permitrootlogin no または permitrootlogin prohibit-password
  • pubkeyauthentication yes

管理者ユーザーの存在確認

Hermes 用ではない通常の管理者ユーザー(sudo 持ち)で SSH ログインできること。以降の手順は基本的にこの管理者ユーザーから実行する。

bash
sudo -v

時刻同期確認

bash
timedatectl status
# 「System clock synchronized: yes」になっていること
# 「Time zone: Asia/Tokyo (JST, +0900)」が望ましい

SSH 接続クライアントの種類を確認

OAuth ベース Provider(Grok、Anthropic OAuth)を使う場合、Phase 4 の setup 手順が SSH クライアントによって変わる。今接続に使っているクライアントを確認しておく:

クライアントループバック自動転送Phase 4 で必要な追加作業
Cursor (Remote-SSH)ありなし
VSCode (Remote-SSH)ありなし
素の OpenSSH (ターミナル)なしssh -L でポートフォワード
PuTTY / RLoginなし(or 手動設定)ssh -L 相当の設定
Termius設定により異なる確認が必要

API キーベース Provider(OpenRouter 等)のみを使う予定なら、SSH クライアントの種類は関係ない。

§ 04Phase 2: Docker Engine インストール

作業ユーザー管理者

Ubuntu 標準の apt install docker.io ではなく、Docker 公式リポジトリから入れる(バージョンが新しく、Compose V2 が同梱)。

競合する旧パッケージの削除

bash
sudo apt remove -y \
  docker.io \
  docker-doc \
  docker-compose \
  docker-compose-v2 \
  podman-docker \
  containerd \
  runc \
  2>/dev/null || true

各パッケージの意図:

  • docker.io: Ubuntu リポジトリ版(バージョンが古い)
  • docker-doc: ドキュメント(不要)
  • docker-compose: Python 製 V1(非推奨)
  • docker-compose-v2: Ubuntu 同梱の V2 シム
  • podman-docker: Docker 互換シム
  • containerd / runc: Ubuntu リポジトリ版(公式版 containerd.io と競合)

依存パッケージのインストール

bash
sudo apt update
sudo apt install -y ca-certificates curl

Docker 公式 GPG キーの追加(.asc 形式)

重要: このステップを飛ばすと次の apt updateNO_PUBKEY エラーになる。必ず実行すること。
bash
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# 確認: ファイルが存在し、サイズが約 1.7KB あること
ls -la /etc/apt/keyrings/docker.asc
sudo head -1 /etc/apt/keyrings/docker.asc
# "-----BEGIN PGP PUBLIC KEY BLOCK-----" が表示されること

リポジトリの登録(DEB822 形式)

bash
echo \
  "Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc" | \
  sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null

# 確認: Suites が noble (24.04) または resolute (26.04) になっていること
cat /etc/apt/sources.list.d/docker.sources

Docker と Compose プラグインのインストール

bash
sudo apt update
sudo apt install -y \
  docker-ce \
  docker-ce-cli \
  containerd.io \
  docker-buildx-plugin \
  docker-compose-plugin
パッケージ役割
docker-ceDocker Engine 本体
docker-ce-clidocker コマンド
containerd.ioコンテナランタイム
docker-buildx-plugindocker buildx サブコマンド
docker-compose-plugindocker compose サブコマンド(V2)

起動と自動起動の有効化

bash
sudo systemctl enable --now docker
sudo systemctl enable containerd

動作確認

bash
sudo systemctl is-active docker
# → active

docker --version
docker compose version

sudo docker run --rm hello-world
# "Hello from Docker!" メッセージが出ればOK

docker グループの確認

bash
getent group docker
# 例: docker:x:988:
#                ↑GID(環境により異なる)

詰まったときの切り分け

NO_PUBKEY エラーで apt update が失敗する: GPG キー取得ステップを飛ばしているか、docker.asc が空になっている。

bash
ls -la /etc/apt/keyrings/docker.asc
# サイズが 0 や数バイトしか無ければ取得失敗

# 再取得
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo apt update

Suites: の行が空になる: /etc/os-releaseUBUNTU_CODENAMEVERSION_CODENAME も無い特殊な環境。手動でコードネームを書き込む:

bash
sudo sed -i 's/^Suites:.*/Suites: resolute/' /etc/apt/sources.list.d/docker.sources
# noble の場合は resolute → noble

§ 05Phase 3: hermes system user の作成

作業ユーザー管理者

useradd による作成

bash
sudo useradd \
  --system \
  --create-home \
  --home-dir /home/hermes \
  --shell /bin/bash \
  --user-group \
  --comment "Hermes Agent service account" \
  hermes

各フラグの意味:

  • --system: UID を system 範囲(100〜999)から割り当て、aging を無効化
  • --create-home: ホームディレクトリを作成(Hermes のデータ集約先なので必須)
  • --home-dir /home/hermes: 場所を明示
  • --shell /bin/bash: 管理時に sudo -iu hermes で入るためシェルを与える
  • --user-group: 同名のグループを作って一次グループに設定

パスワードロック状態の確認

system user は作成直後からパスワードがロックされている。意図的にこのまま放置する

bash
sudo passwd -S hermes
# 例: hermes L 2026-05-24 0 99999 7 -1
#            ↑
#            L = Locked

L になっていれば:

  • パスワード認証でログインできない
  • 他ユーザーから su - hermes 不可(root と sudo 持ちユーザーは例外)
  • 管理者から sudo -iu hermes でのみ入れる

UID/GID の確認(重要)

bash
id hermes
# 例(Xserver VPS): uid=999(hermes) gid=986(hermes) groups=986(hermes)
# 例(別 VPS): uid=998(hermes) gid=998(hermes) groups=998(hermes)

UID は環境によって 998 / 999 / その他になる。Xserver VPS で観測されたのは UID=999, GID=986 のようなパターン。重要なのは:

  • ホストの hermes ユーザーの UID を Phase 4 の ~/hermes-stack/.env に正しく書く($(id -u) で動的取得するので失敗しない)
  • bind mount でホストとコンテナ内のファイル所有権を一致させる

docker グループに追加

bash
sudo usermod -aG docker hermes

SSH 直接ログイン禁止

/etc/ssh/sshd_configDenyUsers を追加し、SSH 経由での hermes ログインを明示的に禁止する。

bash
echo "DenyUsers hermes" | sudo tee -a /etc/ssh/sshd_config
sudo systemctl reload sshd

これで仮に /home/hermes/.ssh/authorized_keys に鍵が置かれても外部から hermes としてログイン不可になる。多層防御。

AllowUsers 運用の場合: 既に AllowUsers で許可ユーザーを明示している環境では、hermes を含めないだけで自動的に拒否される。両方設定する必要はない。

動作確認

重要: 以下は前半が管理者ユーザー、最後の sudo -iu hermes 以降だけが hermes ユーザーでの操作。hermes は sudo を持たないので、sudo を含む確認(sudo lssudo grep など)は必ず管理者プロンプトで実行すること。hermes プロンプト(hermes@...$)で sudo を叩くと sudo: ... I'm afraid I can't do that と拒否される(=エラーではなく仕様)。
bash
# ===== ここから管理者ユーザーで実行 =====

# UID が system 範囲(< 1000)か
id hermes

# パスワードロック確認
sudo passwd -S hermes | awk '{print $2}'
# → L

# ホームディレクトリ(HOME_MODE 0700 なので sudo 必須)
sudo ls -la /home/hermes
# 所有者が hermes:hermes になっていること
# ※ 所有者・モードの確認だけなら sudo 不要の `ls -ld /home/hermes` でも可

# docker グループ所属
groups hermes | grep -q docker && echo OK
# → OK

# SSH 直接ログイン不可(sudo を使うので必ず管理者ユーザーで)
sudo grep -E "^DenyUsers.*hermes" /etc/ssh/sshd_config && echo OK
# → OK

# ===== 最後に hermes に切り替えて確認(このブロックの末尾に配置)=====
# 注意: 以降は hermes ユーザー。sudo は一切使えない。
sudo -iu hermes
whoami
# → hermes
docker ps
# → 空のテーブル(CONTAINER ID, IMAGE, ...)
exit
# → exit で管理者プロンプトに戻る

hermes ユーザーへの入り方(今後の標準フロー)

system user は SSH 直接入れないので、管理は管理者ユーザー経由で行う。現時点では ~/hermes-stack/ などのディレクトリはまだ存在しない(Phase 4 で作成する)。今できることは hermes に入って docker ps を確認するくらいまで。

bash
# 管理者ユーザーから hermes に切り替え
sudo -iu hermes

# プロンプトが hermes になることを確認
whoami
# → hermes

# Phase 3 時点で動くコマンド
docker ps    # 空のテーブル
docker version

# 管理者に戻る
exit

Phase 4 以降は ~/hermes-stack/ ディレクトリを作って、そこで docker compose を使う運用に入っていく。

§ 06Phase 4: Hermes コンテナの起動と初期セットアップ

このフェーズは hermes ユーザーと管理者ユーザーを行き来する。各セクション冒頭の「作業ユーザー」を確認しながら進めること。プロンプトが想定通りか不安な場合は whoami で確認する。

Phase 4 の作業フロー(全体像)

詰まりやすいので、最初に全体の順序を示しておく:

  1. ディレクトリ準備(hermes)
  2. .envdocker-compose.yml を先に作る(hermes)← v4.1 までは setup wizard の後だったが、v4.2 では先に
  3. /var/lib/hermes-health/ を作る(管理者)
  4. setup wizard を実行(hermes、OAuth Provider は --network host 必須)
  5. docker compose up -d で常駐起動(hermes)
  6. 動作確認と永続化検証(hermes)

「先に compose ファイルを作る理由」: setup wizard で OAuth が時間切れになっても、docker compose up -d まで戻れる状態にしておくため。Phase 4 で時間がかかるのは OAuth フローなので、その前に全部の準備を済ませる。

ディレクトリ構造(hermes ユーザー側、完了後イメージ)

shell
/home/hermes/
├── .hermes/               # ← bind mount to /opt/data
│   ├── config.yaml
│   ├── .env               # setup wizard が書き込む(API キー or OAuth ref)
│   ├── credentials/       # OAuth tokens (Grok / Anthropic OAuth)
│   ├── memory/
│   ├── skills/
│   ├── sessions/
│   ├── workspace/         # gateway の作業ディレクトリ(自律作業の場)
│   └── google/            # Google OAuth tokens (Phase 5 で作られる)
└── hermes-stack/          # docker-compose 配置
    ├── docker-compose.yml
    └── .env               # compose 用変数(バージョンと UID/GID のみ)

compose 用ディレクトリの準備

作業ユーザーhermes
bash
# 管理者ユーザーから hermes に切り替え
sudo -iu hermes
whoami   # → hermes であること

# ディレクトリ作成
mkdir -p ~/hermes-stack ~/.hermes
cd ~/hermes-stack

# 確認
ls -la ~
# hermes-stack/ と .hermes/ が作られていること

compose 用 .env の作成

作業ユーザーhermes(前セクションから継続)
bash
cd ~/hermes-stack

cat > .env <<'EOF'
# Hermes バージョン(明示ピン留め)
HERMES_VERSION=v2026.5.16
EOF

# コンテナ内 hermes ユーザーをリマップするための UID/GID
# (環境ごとに 998 / 999 等で異なるので動的取得)
echo "HERMES_UID=$(id -u)" >> .env
echo "HERMES_GID=$(id -g)" >> .env
chmod 600 .env

# 確認
cat .env
# HERMES_UID, HERMES_GID がホストの id -u / id -g と一致すること
バージョンピン留めの理由: :latest は更新で挙動が変わるリスクがあるため運用では使わない。明示バージョンで固定し、自分が判断したタイミングで上げる。
UID/GID を環境変数で渡す理由: Hermes コンテナは root として起動し、entrypoint が usermod/groupmod + gosu で内部 hermes ユーザーをホスト UID にリマップする設計。compose の user: で起動 UID を強制するとこの初期化がスキップされ、ディレクトリ作成や dashboard side-process の起動が失敗する。

docker-compose.yml の作成

作業ユーザーhermes(前セクションから継続)
bash
cd ~/hermes-stack

cat > docker-compose.yml <<'EOF'
services:
  hermes:
    image: nousresearch/hermes-agent:${HERMES_VERSION}
    container_name: hermes
    restart: unless-stopped
    # user: は指定しないこと
    # entrypoint が HERMES_UID/HERMES_GID を読んで内部ユーザーを gosu リマップする
    volumes:
      # Hermes の全データ(rw)
      - /home/hermes/.hermes:/opt/data
      # ホスト側 collector の出力(ro、Phase 6 で配置)
      - /var/lib/hermes-health:/opt/data/health:ro
    ports:
      # gateway(ローカルバインドのみ)
      - "127.0.0.1:8642:8642"
      # dashboard(同一コンテナ内で side-process として起動)
      - "127.0.0.1:9119:9119"
    environment:
      - HERMES_UID=${HERMES_UID}
      - HERMES_GID=${HERMES_GID}
      - HERMES_DASHBOARD=1
      - TZ=Asia/Tokyo
    command: gateway run
    healthcheck:
      # gateway は /health エンドポイントを提供しないため TCP 接続成立で判定
      # (curl ベースの健康診断は使えない)
      test: ["CMD-SHELL", "bash -c '</dev/tcp/localhost/8642'"]
      interval: 1m
      timeout: 10s
      retries: 3
      start_period: 30s
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "5"
EOF

# 確認
cat docker-compose.yml
ls -la
# .env と docker-compose.yml が両方あること
dashboard の起動方法: HERMES_DASHBOARD=1 を渡すと、Hermes の entrypoint が dashboard を gateway と同一コンテナ内の side-process として起動する。これは公式の標準ルート。dashboard を別コンテナで動かす流儀もあるが、その場合は GATEWAY_HEALTH_URL の明示が必要になり手順が増える。単一 VPS で完結する用途では同居の方が単純で推奨。
healthcheck の仕様: gateway は /health エンドポイントを提供しないため、curl -f http://localhost:8642/health は接続リセットを返し healthcheck が常に失敗する。bash の /dev/tcp 機能で TCP 接続成立のみ確認する方式に切り替えることで、外部ツール不要で gateway の生存確認ができる。
logging 制限: Hermes は 24/7 で gateway ログを吐き続けるため、デフォルト無制限の json-file ドライバだとディスクを食い潰す。10MB × 5世代でローテーションさせる。
API キーを environment に書かない: compose の environment で API キーを上書きすると、setup wizard が書く ~/.hermes/.env と二重定義になる。environment: 経由の値は .env を上書きする優先度を持つため、運用では ~/.hermes/.env に一本化する。

/var/lib/hermes-health/ の事前作成

作業ユーザー管理者(hermes から一旦抜ける)

docker compose up 時に :ro マウント先が存在しないとエラーになるので、ダミーで先に作る:

bash
# hermes から管理者に戻る
exit
whoami   # 管理者ユーザー名(例: <管理者ユーザー>)であること

sudo mkdir -p /var/lib/hermes-health
sudo chown root:hermes /var/lib/hermes-health
sudo chmod 750 /var/lib/hermes-health

# 確認
ls -ld /var/lib/hermes-health
# drwxr-x--- root hermes ... であること

Phase 6 で本物の collector が出力を書く。

Provider 選択方針の決定

setup wizard 実行前に、どの Provider を使うかを決めておく。OAuth 系を選ぶ場合は実行コマンドが変わる。

Provider タイプsetup wizard の起動方法
API キーベースOpenRouter, OpenAI 直接, Anthropic API key通常モード
OAuth ベースxAI Grok OAuth (SuperGrok), Anthropic OAuth (Claude Max)--network host 必須

OAuth ベースを選ぶと、wizard が一時的にローカルポート(例: 127.0.0.1:56121)で OAuth コールバックを listen する。デフォルトの docker run だとこのポートがコンテナの中に閉じてしまい、ブラウザが認証完了後にリダイレクトしてもコールバックを受け取れない。--network host でコンテナにホストのネットワーク名前空間を共有させると、コンテナ内 127.0.0.1 がホストの 127.0.0.1 と一致してコールバックが届く。

setup wizard の実行(API キーベース Provider の場合)

作業ユーザーhermes(再度切り替え)
bash
sudo -iu hermes
whoami   # → hermes

docker run -it --rm \
  -v /home/hermes/.hermes:/opt/data \
  -e HERMES_UID=$(id -u) \
  -e HERMES_GID=$(id -g) \
  nousresearch/hermes-agent:v2026.5.16 \
  setup

ウィザードで設定する項目(API キーベースの場合):

  • Provider: OpenRouter / OpenAI / Anthropic API key 等
  • API Key: 該当キーを貼り付け
  • Model: 用途に応じて
  • SOUL.md: ペルソナ定義(後で編集可)
  • Terminal backend: local のまま(既にコンテナ内なので二重隔離は不要)
  • Gateway working directory: /opt/data/workspace
  • Enable sudo support: N(必須。hermes は sudo を持たない)
  • Messaging apps: スキップ可
  • Approval mode: smart

setup wizard の実行(OAuth ベース Provider の場合)

作業ユーザーhermes

OAuth フローを含むので --network host で起動する:

bash
sudo -iu hermes
whoami   # → hermes

docker run -it --rm \
  -v /home/hermes/.hermes:/opt/data \
  -e HERMES_UID=$(id -u) \
  -e HERMES_GID=$(id -g) \
  --network host \
  nousresearch/hermes-agent:v2026.5.16 \
  setup
--network host は setup の時だけ。通常運用の docker compose up -d127.0.0.1:8642:8642 のローカルバインド構成のままで使う。

Provider 選択で OAuth 系(例: xAI Grok OAuth (SuperGrok Subscription))を選ぶと、以下のような表示が出る:

shell
Open this URL to authorize Hermes with xAI:
https://auth.x.ai/oauth2/authorize?response_type=code&client_id=...&redirect_uri=http%3A%2F%2F127.0.0.1%3A56121%2Fcallback&...

Waiting for callback on http://127.0.0.1:56121/callback
Could not open the browser automatically; use the URL above.
コールバックポート(例の 56121)は毎回ランダム。表示された値をメモする。

OAuth フロー実行(SSH クライアントの種類による分岐)

ケース A: Cursor / VSCode Remote-SSH を使っている場合

ループバック自動転送が効くので、追加作業なしで以下を実行するだけ:

  1. setup wizard が表示した OAuth URL を手元 PC のブラウザで開く(ターミナルの URL をクリック / コピー&貼り付け)
  2. Grok のログイン画面で認証
  3. 認証完了後、ブラウザが自動で http://127.0.0.1:56121/callback?code=... にリダイレクト
  4. Cursor / VSCode の自動転送で VPS のコンテナにコールバックが届く
  5. setup wizard が次のステップに進む(「successful」のような表示)

「接続を確立できませんでした、コードをコピー&ペーストしてください」という画面が出たら、それはコールバックが届かなかった signal。SSH クライアントの自動転送が効いていない可能性があるのでケース B へ。

ケース B: 素の OpenSSH / PuTTY / その他 を使っている場合

手元 PC のブラウザから VPS のループバックに到達する経路が無いので、手動で SSH トンネルを張る。

手順:

  1. setup wizard が OAuth URL を表示してポート番号(例: 56121)が確定したら、手元 PC で新しいターミナルを開く
  2. 新ターミナルから SSH トンネルを張る:
bash
# XXXXX は wizard が表示した実際のポート番号
ssh -L XXXXX:127.0.0.1:XXXXX -N <管理者ユーザー>@<VPSアドレス>
# 例:
# ssh -L 56121:127.0.0.1:56121 -N <管理者ユーザー>@<VPSアドレス>

-N でシェルを開かず、トンネルだけ確立。何も表示されなくて正常(接続維持中の状態)。

  1. 手元 PC のブラウザで OAuth URL を開く
  2. Grok で認証
  3. ブラウザがリダイレクト → SSH トンネル経由で VPS のコンテナに到達 → setup 完了
  4. SSH トンネルのターミナルは Ctrl+C で終了

OAuth で時間切れになった場合

OAuth コードの有効期限は数分なので、ブラウザを開く前に以下の準備をしておくと安全:

  1. SSH トンネルが必要な場合は事前に張っておく
  2. 手元 PC のブラウザを起動しておく
  3. クリップボードを空にしておく

時間切れで wizard が中断したらキャンセル(Ctrl+C)して、もう一度同じコマンドで実行。既存設定があれば wizard がデフォルト値として表示するので、変更が必要な項目だけ再入力する。

「コードを Grok Build にコピー&ペーストしてください」画面の罠: Grok 側はコールバック失敗時にこのフォールバック画面を出すが、Hermes 側はこのコードを受け付ける入力プロンプトを出さない設計(純粋なローカルコールバック方式)。なので画面の指示通りコードをコピーしても貼り付け先がない。コールバックが届く経路を確保する以外に解決策はない。

setup wizard 完了後の確認

作業ユーザーhermes
bash
# config が作られたか
ls -la ~/.hermes/

# config.yaml の中身(Provider 等の確認)
cat ~/.hermes/config.yaml | head -30

# 認証情報の確認
# OAuth の場合は ~/.hermes/credentials/ に保存される(.env ではない)
ls -la ~/.hermes/credentials/ 2>/dev/null
# API キーの場合は ~/.hermes/.env に書かれる
grep -iE 'api_key|token' ~/.hermes/.env 2>/dev/null | sed 's/=.*/=***/'
.env を grep して何も出ない場合: OAuth ベース Provider を選んだ場合、認証情報は .env ではなく credentials/ ディレクトリ配下に保存される。.env が空でも問題ない。

コンテナ起動

作業ユーザーhermes(前セクションから継続)
bash
cd ~/hermes-stack
docker compose pull
docker compose up -d

# ステータス確認(healthcheck の interval が 1m + start_period が 30s なので少し待つ)
sleep 90
docker compose ps
# STATUS が "Up X minutes (healthy)" になればOK
# (health: starting) のままなら、もう1分待ってから再確認

動作確認

作業ユーザーhermes

dashboard / gateway の生存

bash
# dashboard が応答するか(HTML が返る)
curl -fsS http://127.0.0.1:9119 | head

# gateway は /health エンドポイントが無いので curl は失敗するが、
# 8642 で listen していることは docker compose ps の (healthy) で判定済み

コンテナ内ユーザーの UID/GID リマップ確認

bash
# entrypoint が gosu でリマップした結果を確認
# (docker exec のデフォルトユーザーは root なので、-u hermes 指定が重要)
docker exec -u hermes hermes id
# uid=999(hermes) gid=986(hermes) など、ホスト側 (id) と一致すること

docker exec hermes env | grep -E '^HERMES_(UID|GID|DASHBOARD)'
# HERMES_UID, HERMES_GID, HERMES_DASHBOARD=1 が出ること

Hermes との対話(重要: コマンドの絶対パス)

hermes バイナリのコンテナ内パスは /opt/hermes/.venv/bin/hermesdocker exec のデフォルトユーザーは root で PATH が違うため、絶対パスで呼ぶ必要がある。さらにファイル所有権を破壊しないために -u hermes を必須にする:

bash
# 対話を1回試す
docker exec -u hermes -it hermes /opt/hermes/.venv/bin/hermes chat -q "hello"
# Hermes が応答を返せば成功

alias 設定(毎回フルパスを打つのが煩雑なため)

bash
# hermes ユーザーの ~/.bashrc に alias を追加
echo "alias hermes='docker exec -u hermes -it hermes /opt/hermes/.venv/bin/hermes'" >> ~/.bashrc
source ~/.bashrc

# 以降は短く書ける
hermes chat -q "hello"
hermes --help
hermes sessions list
なぜ -u hermes 必須か: docker exec のデフォルトユーザーは Dockerfile の USER 指定または root。Hermes の場合は root として exec されるため、hermes login などを -u hermes なしで実行すると認証ファイルが root 所有で書かれ、リマップ後の hermes uid で動く gateway から読めなくなる(auth.json 所有権バグ)。alias で -u hermes を固定しておくことで予防できる。

永続化の検証(重要)

作業ユーザーhermes

docker pull でアップデートしてもデータが残ることを、運用前に必ず実機検証する。

bash
# 1. コンテナ内に印を書く
docker exec -u hermes hermes touch /opt/data/persistence-test.txt
docker exec -u hermes hermes ls -la /opt/data/persistence-test.txt

# 2. ホスト側でも見えるか
ls -la /home/hermes/.hermes/persistence-test.txt
# → 同じファイルが見えるはず(bind mount が効いている証拠)
# → 所有者が hermes:hermes であること(gosu リマップが効いている証拠)

# 3. mount 状態の inspect
docker inspect hermes | jq '.[0].Mounts'
# 期待値:
# [
#   { "Type": "bind", "Source": "/home/hermes/.hermes",
#     "Destination": "/opt/data", "RW": true, ... },
#   { "Type": "bind", "Source": "/var/lib/hermes-health",
#     "Destination": "/opt/data/health", "RW": false, ... }
# ]
# 重要: "Type": "bind" であること("volume" だと named volume で別管理)

# 4. コンテナを完全削除
cd ~/hermes-stack
docker compose down

# 5. 新しくコンテナを起動
docker compose up -d
sleep 90
docker compose ps
# (healthy) であること

# 6. 印が残っているか
docker exec -u hermes hermes ls -la /opt/data/persistence-test.txt
# → ファイルが残っていればOK
# → 所有者も hermes:hermes のままであること

# 7. dashboard も再度応答するか
curl -fsS http://127.0.0.1:9119 | head

# 8. テストファイルを削除
docker exec -u hermes hermes rm /opt/data/persistence-test.txt

これで「docker compose down → up -d してもデータと dashboard 起動状態が残る」ことが実証できる。この検証を通さずに運用に入らない

トラブル時の対処

healthcheck が (unhealthy) のまま: gateway は /health エンドポイントを提供しないので、healthcheck で curl ベースのテストを使っていると常に失敗する。docker-compose.yml の healthcheck が TCP 接続確認方式になっているか確認:

bash
grep -A2 'healthcheck:' ~/hermes-stack/docker-compose.yml
# test: ["CMD-SHELL", "bash -c '</dev/tcp/localhost/8642'"]  ← この行があること

curl ベースになっていたら、上の docker-compose.yml ブロックで書き直し、docker compose down && docker compose up -d

permission denied で書けない: ~/.hermes/ の所有権を確認

bash
ls -la ~/.hermes/
# 全部 hermes:hermes になっているか

ズレていた場合は管理者に戻って chown:

bash
exit   # hermes から管理者へ
sudo chown -R hermes:hermes /home/hermes/.hermes
sudo -iu hermes

間違って user: を docker-compose.yml に書いて起動してしまった場合: entrypoint の gosu リマップが効かず、初期化が中途半端になる(dashboard が立たない、ファイル所有権が変、など)。対処:

bash
cd ~/hermes-stack
docker compose down

# docker-compose.yml から user: 行を削除して、environment に HERMES_UID/HERMES_GID を入れる
# (上記の docker-compose.yml と一致させる)
vi docker-compose.yml

# 管理者で所有権リセット
exit
sudo chown -R hermes:hermes /home/hermes/.hermes

sudo -iu hermes
cd ~/hermes-stack && docker compose up -d

dashboard が応答しない:

bash
# 環境変数が伝わっているか
docker exec hermes env | grep HERMES_DASHBOARD
# HERMES_DASHBOARD=1 が出ること

# プロセス確認
docker exec hermes ps -ef | grep dashboard
# /opt/hermes/.venv/bin/hermes dashboard ... が走っていること

# entrypoint のログで dashboard 起動を確認
docker logs hermes 2>&1 | grep -iE 'dashboard|9119'

# Hermes の dashboard side-process は supervised ではないため、
# 何らかの理由で落ちた場合はコンテナごと再起動
docker compose restart hermes

setup wizard が時間切れになる: OAuth コードの有効期限は数分。次の準備をしてから再実行:

  1. SSH トンネル(必要なら)を事前に張る
  2. 手元 PC のブラウザを起動済みにする
  3. クリップボード空に
  4. docker run --rm ... setup を再実行(既存設定はそのまま残るので、変更したい項目だけ再入力すれば良い)

OAuth ステップで Enter を押しても進まない: コールバックが届いていない。「コードをコピー&ペーストしてください」画面が出ているなら、それは Grok 側のフォールバック表示で、Hermes 側には貼り付け先が無い。--network host で起動しているか、SSH トンネル張っているか、SSH クライアントが自動転送するかを再確認。

§ 07Phase 5: Google Workspace 接続

GCP プロジェクト作成と API 有効化

作業ユーザー手元のブラウザ(VPS とは無関係)

https://console.cloud.google.com にアクセスし:

  1. 新規プロジェクト作成(例: hermes-personal
  2. API ライブラリで以下を有効化:
    • Gmail API
    • Google Calendar API
    • Google Drive API
    • Google Sheets API
    • Google Docs API
    • People API
  3. 「APIとサービス」→「OAuth 同意画面」
    • ユーザータイプ: 外部
    • テストユーザーに自分の Google アカウントを追加
  4. 「認証情報」→「認証情報を作成」→「OAuth クライアント ID」
    • アプリケーションタイプ: デスクトップ アプリ
    • JSON をダウンロード

JSON をコンテナに渡す

作業ユーザー管理者(hermes に SCP できないので一旦経由)
bash
# ダウンロードした JSON を手元から VPS の管理者ユーザーへ scp 済みの前提
# (例: scp client_secret.json admin@vps:~/)

# 管理者から hermes 配下へコピーして所有権を整える
sudo cp ~/client_secret_xxx.json /home/hermes/.hermes/google_client_secret.json
sudo chown hermes:hermes /home/hermes/.hermes/google_client_secret.json
sudo chmod 600 /home/hermes/.hermes/google_client_secret.json

認証フロー実行

作業ユーザーhermes
bash
sudo -iu hermes
whoami   # → hermes

# alias を設定済みなら短く
hermes
# 設定していなければフルパスで
# docker exec -u hermes -it hermes /opt/hermes/.venv/bin/hermes

Hermes 内のインタラクティブセッションで:

shell
> Google Workspace のセットアップを始めて

Hermes が auth URL を出してくる。Phase 4 の OAuth フローと同様に SSH クライアント次第で扱いが変わる:

  • Cursor / VSCode Remote-SSH: そのままブラウザで開けば自動転送される
  • 素の SSH: ssh -L で当該ポートを転送してからブラウザで開く

承認後 http://localhost:1/?code=... にリダイレクト(接続エラー画面でも問題なし)、その URL 全体をコピーして Hermes に貼り戻す。

動作確認

作業ユーザーhermes(Hermes 内対話を継続)
shell
> 今日のカレンダーの予定を見せて
> 未読のメールを5件サマリーして

auth.json 所有権の確認

作業ユーザーhermes
bash
ls -la /home/hermes/.hermes/google/

全ファイルが hermes:hermes 所有になっていればOK。root:root が混ざっていたら管理者に戻って修正:

bash
exit   # hermes から管理者へ
sudo chown -R hermes:hermes /home/hermes/.hermes/google/

sudo -iu hermes
cd ~/hermes-stack && docker compose restart hermes

§ 08Phase 6: ホスト側コレクタの実装

作業ユーザー管理者(root cron に登録するため終始管理者で作業)

事前確認1: fail2ban の jail 名

ジェイル名は環境によって sshd だったり ssh だったりするので先に確認:

bash
sudo fail2ban-client status
# Jail list: の行に出てくる名前を控える
# 例: Jail list:    sshd  または  ssh

以降のスクリプトでは sshd を仮定する。ssh だった場合は collector スクリプト内の fail2ban-client status sshd の引数を ssh に書き換えること。

事前確認2: SSH journal の単位名

Ubuntu 22.10 以降は SSH が socket activation 化されているため、journalctl -u ssh で認証イベントが拾えるかは環境依存:

bash
# どこに認証イベントが流れているか確認
sudo journalctl -u ssh --since "1 hour ago" --no-pager | tail -20
sudo journalctl -u ssh.socket --since "1 hour ago" --no-pager | tail -20
sudo journalctl _COMM=sshd --since "1 hour ago" --no-pager | tail -20

_COMM=sshd での絞り込みが最も確実(socket activation で短命ユニットに散ったログも拾える)。以下のスクリプトはこれを採用する。

collector スクリプトの配置

bash
sudo tee /usr/local/bin/hermes-health-collect.sh > /dev/null <<'SCRIPT'
#!/bin/bash
# Hermes 用ヘルスチェック情報コレクタ
# root cron から実行され、JSON を /var/lib/hermes-health/ に書き出す

set -euo pipefail

OUT=/var/lib/hermes-health
TS=$(date -Iseconds)
mkdir -p "$OUT"

# 一時ディレクトリを mktemp で確保し、終了時に必ず削除(並行実行でも衝突しない)
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT

# --- fail2ban ---
{
  echo "{"
  echo "  \"timestamp\": \"$TS\","
  if fail2ban-client status sshd > "$TMP/f2b.txt" 2>/dev/null; then
    BANNED=$(grep -oP 'Banned IP list:\s*\K.*' "$TMP/f2b.txt" || echo "")
    TOTAL_BANNED=$(grep -oP 'Total banned:\s*\K\d+' "$TMP/f2b.txt" || echo "0")
    CURRENT_FAILED=$(grep -oP 'Currently failed:\s*\K\d+' "$TMP/f2b.txt" || echo "0")
    echo "  \"sshd\": {"
    echo "    \"banned_ips\": \"$BANNED\","
    echo "    \"total_banned\": $TOTAL_BANNED,"
    echo "    \"currently_failed\": $CURRENT_FAILED"
    echo "  }"
  else
    echo "  \"sshd\": null,"
    echo "  \"error\": \"fail2ban-client not available or sshd jail not running\""
  fi
  echo "}"
} > "$OUT/fail2ban.json.tmp"
mv "$OUT/fail2ban.json.tmp" "$OUT/fail2ban.json"

# --- 直近1時間の SSH 認証イベント(_COMM=sshd で絞り込み) ---
journalctl _COMM=sshd --since "1 hour ago" --no-pager -o json \
  > "$OUT/auth.json.tmp" 2>/dev/null || : > "$OUT/auth.json.tmp"
mv "$OUT/auth.json.tmp" "$OUT/auth.json"
# 注: -o json は NDJSON (各行が独立した JSON)。jq で読む際は jq -s で配列化するか、
# slurp なしで1行ずつ処理する。Hermes スキルでもこれを前提に解釈する。

# --- システムリソース ---
{
  echo "{"
  echo "  \"timestamp\": \"$TS\","
  echo "  \"disk\": {"
  df -B1 / | awk 'NR==2 {printf "    \"total\": %d, \"used\": %d, \"available\": %d, \"percent\": \"%s\"\n", $2, $3, $4, $5}'
  echo "  },"
  echo "  \"memory\": {"
  free -b | awk '/^Mem:/ {printf "    \"total\": %d, \"used\": %d, \"available\": %d\n", $2, $3, $7}'
  echo "  },"
  LOAD=$(uptime | grep -oP 'load average:\s*\K.*')
  echo "  \"load_average\": \"$LOAD\","
  UPTIME=$(uptime -p)
  echo "  \"uptime\": \"$UPTIME\""
  echo "}"
} > "$OUT/system.json.tmp"
mv "$OUT/system.json.tmp" "$OUT/system.json"

# --- 重要サービスの稼働状況 ---
SERVICES=(ssh fail2ban ufw docker)
{
  echo "{"
  echo "  \"timestamp\": \"$TS\","
  echo "  \"services\": {"
  LAST=$((${#SERVICES[@]} - 1))
  for i in "${!SERVICES[@]}"; do
    svc="${SERVICES[$i]}"
    STATUS=$(systemctl is-active "$svc" 2>/dev/null || echo "unknown")
    if [ "$i" -lt "$LAST" ]; then
      printf '    "%s": "%s",\n' "$svc" "$STATUS"
    else
      printf '    "%s": "%s"\n' "$svc" "$STATUS"
    fi
  done
  echo "  }"
  echo "}"
} > "$OUT/services.json.tmp"
mv "$OUT/services.json.tmp" "$OUT/services.json"

# --- 出力ファイルの mode 調整のみ(所有権は初回 chown -R で確定済み) ---
chmod 640 "$OUT"/*.json 2>/dev/null || true
SCRIPT

sudo chmod 755 /usr/local/bin/hermes-health-collect.sh

初回実行と権限の初期設定

bash
# 初回のみ、ディレクトリ全体の所有権を確定
# (Phase 4 でディレクトリ自体は作っているが、念のため再適用)
sudo chown -R root:hermes /var/lib/hermes-health
sudo chmod 750 /var/lib/hermes-health

# collector を1回走らせて生成確認
sudo /usr/local/bin/hermes-health-collect.sh
ls -la /var/lib/hermes-health/

# 各 JSON が valid であることを確認
for f in /var/lib/hermes-health/*.json; do
  if [ "$(basename "$f")" = "auth.json" ]; then
    if [ -s "$f" ]; then
      jq -e -s . "$f" >/dev/null && echo "OK: $f (NDJSON)" || echo "NG: $f"
    else
      echo "OK: $f (empty)"
    fi
  else
    jq -e . "$f" >/dev/null && echo "OK: $f" || echo "NG: $f"
  fi
done

# 中身を眺める
cat /var/lib/hermes-health/system.json | jq .
cat /var/lib/hermes-health/services.json | jq .

cron に登録

bash
sudo tee /etc/cron.d/hermes-health > /dev/null <<'EOF'
# Hermes 用ヘルスチェック collector
*/5 * * * * root /usr/local/bin/hermes-health-collect.sh
EOF

コンテナから見えるか確認

作業ユーザーhermes(一時的に切り替え)
bash
sudo -iu hermes
docker exec -u hermes -it hermes ls -la /opt/data/health/
docker exec -u hermes -it hermes cat /opt/data/health/system.json
exit   # 管理者に戻る

§ 09Phase 7: ヘルスチェックスキルの投入

作業ユーザーhermes
bash
sudo -iu hermes
whoami   # → hermes

スキル定義

~/.hermes/skills/vps-health-check/SKILL.md を作成:

bash
mkdir -p ~/.hermes/skills/vps-health-check
cat > ~/.hermes/skills/vps-health-check/SKILL.md <<'EOF'
---
name: vps-health-check
description: Hermes 自身が動いている VPS のヘルスチェックを実施し、異常があれば Discord で報告する。
trigger: ヘルスチェック要求、または定期実行(1日1回 + 異常検出時)
---

# VPS ヘルスチェック手順

## 入力データ

すべて `/opt/data/health/` 配下の JSON ファイルから読み取る。**シェルコマンドを直接実行してはならない**。生データは root 側の collector が事前整形している。

- `system.json`: disk, memory, load, uptime
- `fail2ban.json`: sshd jail の状態
- `auth.json`: 直近1時間の認証イベント (NDJSON 形式)
- `services.json`: 主要サービスの稼働状況

## チェック項目と閾値

### システムリソース

- ディスク使用率 > 80% → 警告
- ディスク使用率 > 90% → 緊急
- メモリ available < 10% → 警告
- load average 1min > CPU 数 × 2 → 警告

### セキュリティ

- fail2ban の total_banned が前回チェック時より +5 以上 → 警告
- currently_failed > 10 → 攻撃継続中の可能性、警告
- ssh service が active でない → 緊急

### サービス

- docker / ssh / fail2ban のいずれかが active でない → 緊急

## 出力

異常検出時、Discord に以下フォーマットで送信:

```
🚨 [VPS Health Alert] severity={warning|critical}
- 検出時刻: ISO8601
- 該当項目: <chronological list>
- 推奨対応: <Hermes が状況から判断>
- 詳細: <該当 JSON の関連部分>
```

正常時は何もしない(毎日朝9時の定期サマリーを除く)。

## 状態の永続化

前回チェック時の `total_banned` 値を `~/.hermes/memory/vps-health-state.json` に保存し、次回チェック時に差分を計算する。

## 禁止事項

- ホスト側コマンドを実行しようとしない(コンテナの中なので物理的に不可能だが、念のため明示)
- 認証ファイルの内容(auth.json)の生エントリを Discord に流さない(IP 等の情報漏洩防止のため、件数のみ報告)
EOF

定期実行設定

Hermes 内で:

bash
hermes   # alias 経由
shell
> このスキルを毎日朝9時に実行する cron スケジュールを設定して
> また毎時5分に異常検知だけ実行するスケジュールも追加して

Hermes が cron スキルを使って自身のスケジュールに登録する。完了後 exit で Hermes インタラクティブセッションから抜ける。

bash
exit   # hermes から管理者へ

§ 10Phase 8: バックアップとロールバック

作業ユーザー管理者

自動バックアップスクリプト

bash
sudo tee /usr/local/bin/hermes-backup.sh > /dev/null <<'SCRIPT'
#!/bin/bash
set -euo pipefail

BACKUP_DIR=/var/backups/hermes
DATE=$(date +%Y%m%d-%H%M)
RETENTION_DAYS=14

mkdir -p "$BACKUP_DIR"

# DB の一貫性のため、Hermes を一時停止
docker stop hermes >/dev/null

# tarball 作成
tar czf "$BACKUP_DIR/hermes-$DATE.tar.gz" \
  -C /home/hermes \
  --exclude='.hermes/sessions/cache' \
  --exclude='.hermes/logs' \
  .hermes hermes-stack

docker start hermes >/dev/null

# 保持期間超過分を削除
find "$BACKUP_DIR" -name "hermes-*.tar.gz" -mtime +$RETENTION_DAYS -delete

# サイズと整合性チェック
SIZE=$(du -h "$BACKUP_DIR/hermes-$DATE.tar.gz" | cut -f1)
tar tzf "$BACKUP_DIR/hermes-$DATE.tar.gz" >/dev/null

echo "[$(date -Iseconds)] Backup OK: hermes-$DATE.tar.gz ($SIZE)"
SCRIPT

sudo chmod 755 /usr/local/bin/hermes-backup.sh

# cron 登録(毎日 4:00)
sudo tee /etc/cron.d/hermes-backup > /dev/null <<'EOF'
0 4 * * * root /usr/local/bin/hermes-backup.sh >> /var/log/hermes-backup.log 2>&1
EOF

オフサイトバックアップ(推奨)

bash
sudo apt install rclone
sudo rclone config  # gdrive リモート等を作成

# backup スクリプトの末尾に追加(例)
# rclone copy "$BACKUP_DIR/hermes-$DATE.tar.gz" gdrive:hermes-vps-backup/

ログローテーション

/var/log/hermes-backup.log は logrotate で管理する。Docker コンテナログは docker-compose.yml の logging オプションで既にローテーション設定済み:

bash
sudo tee /etc/logrotate.d/hermes > /dev/null <<'EOF'
/var/log/hermes-backup.log {
    weekly
    rotate 8
    compress
    delaycompress
    missingok
    notifempty
    create 644 root root
}
EOF

# 構文チェック(dry run)
sudo logrotate -d /etc/logrotate.d/hermes

将来、collector や Hermes 内部のスキルが追加ログを /var/log/hermes-*.log 等に吐くようになったら、同じ logrotate 定義の対象に追加する。

ロールバック手順

ロールバックは管理者と hermes を行き来する:

bash
# === 作業ユーザー: hermes ===
sudo -iu hermes
cd ~/hermes-stack
docker compose down

# 現状を退避(万一に備えて)
mv ~/.hermes ~/.hermes.broken-$(date +%Y%m%d)

# === 作業ユーザー: 管理者 ===
exit
whoami   # 管理者であること

# バックアップから復元
sudo tar xzf /var/backups/hermes/hermes-20260520-0400.tar.gz \
  -C /home/hermes
sudo chown -R hermes:hermes /home/hermes/.hermes /home/hermes/hermes-stack

# === 作業ユーザー: hermes ===
sudo -iu hermes
cd ~/hermes-stack
docker compose up -d
sleep 90
docker compose ps

# 動作確認
hermes chat -q "hello"   # alias 設定済みなら

§ 11アップデート手順

複数のユーザーを行き来する:

bash
# === 作業ユーザー: hermes ===
sudo -iu hermes
cd ~/hermes-stack

# 1. 最新版のリリースノートを確認
# https://github.com/NousResearch/hermes-agent/releases

# === 作業ユーザー: 管理者(バックアップ取得のため) ===
exit
sudo /usr/local/bin/hermes-backup.sh

# === 作業ユーザー: hermes ===
sudo -iu hermes
cd ~/hermes-stack

# 2. .env の HERMES_VERSION を新バージョンに書き換え
sed -i 's/^HERMES_VERSION=.*/HERMES_VERSION=v2026.6.0/' .env
cat .env   # 確認

# 3. イメージ取得とコンテナ再作成
docker compose pull
docker compose up -d
sleep 90

# 4. ステータス
docker compose ps   # (healthy) になること
docker compose logs --tail 50

# 5. 動作確認
hermes --help
hermes chat -q "hello"

バージョンを戻したい場合

.envHERMES_VERSION を元の値に戻して docker compose up -d で OK。データは触らない(bind mount で永続化済み)。

§ 12注意点と既知の落とし穴

auth.json 所有権バグの対策

hermes logindocker exec -it -u root で実行する(または -u を省略してデフォルト root で動かす)と auth.json が root 所有で書かれ、その後 gosu リマップ後の hermes uid で動いている gateway から読めなくなる。対策:

  • compose で user:指定しないenvironmentHERMES_UID/HERMES_GID を渡し、entrypoint の gosu リマップに任せる
  • docker exec で操作するときは必ず -u hermes を付ける(デフォルトは root)
  • alias を 'docker exec -u hermes -it hermes /opt/hermes/.venv/bin/hermes' で固定しておくと事故防止になる
  • 認証ファイルが root 所有になっていたら sudo chown -R hermes:hermes /home/hermes/.hermes

UID マッピングの正しいやり方

公式 Hermes イメージの内部 hermes ユーザーは固定 UID(10000 がデフォルト)で焼かれているが、起動時に entrypoint が usermod/groupmodHERMES_UID/HERMES_GID の値に書き換え、gosu で権限降下する設計。

つまり:

  • compose の user:指定してはならない(entrypoint の root フェーズが走らずリマップが失敗する)
  • 代わりに environmentHERMES_UID=$(id -u) HERMES_GID=$(id -g) を渡す
  • これで bind mount した ~/.hermes/ の所有権とコンテナ内ユーザーの UID が一致し、permission denied が起きない
  • system user の UID は環境によって 998 / 999 等で異なるので、決め打ちではなく動的取得

もし以前に user: を指定して起動してしまっていた場合、~/.hermes/ 配下や ~/.hermes/google/ の所有権が壊れている可能性があるので、sudo chown -R hermes:hermes /home/hermes/.hermes でリセットしてから compose を直す。

gateway は /health エンドポイントを提供しない

healthcheck で curl http://localhost:8642/health を使うと「接続リセット」となり常に unhealthy になる。docker-compose.yml の healthcheck は bash の /dev/tcp を使った TCP 接続成立確認に切り替える:

yaml
healthcheck:
  test: ["CMD-SHELL", "bash -c '</dev/tcp/localhost/8642'"]

OAuth フローはコールバックが届く経路が必須

Hermes の OAuth フロー(Grok / Anthropic OAuth)は純粋なローカルコールバック方式で、コードコピペの OOB 入力プロンプトを提供しない。setup 時にコールバックポート(毎回ランダム)に手元 PC のブラウザから到達できる経路を確保する必要がある:

  • Cursor / VSCode Remote-SSH: ループバック自動転送が効くので追加作業なし
  • 素の SSH: ssh -L XXXXX:127.0.0.1:XXXXX -N で手動転送
  • --network host: setup wizard のコンテナ起動時に必須

通常運用の docker compose up -d127.0.0.1 ローカルバインドのままで OK。--network host は setup の時だけ。

docker exec は root で実行される

docker exec hermes ... のデフォルトユーザーはコンテナの USER 指定または root。Hermes イメージは root で exec される。そのまま hermes login 系のコマンドを叩くと auth.json が root 所有で書かれてバグの引き金になる。

必ず -u hermes を付ける:

bash
docker exec -u hermes -it hermes /opt/hermes/.venv/bin/hermes ...

alias 化を強く推奨。

hermes コマンドのパス

コンテナ内で hermes バイナリは /opt/hermes/.venv/bin/hermes にある。コンテナ内 root ユーザーの PATH には /opt/data/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin しかなく、/opt/hermes/.venv/bin は含まれない。docker exec hermes hermes ... だと「executable file not found」になる。

正しい呼び方:

bash
# フルパス指定
docker exec -u hermes -it hermes /opt/hermes/.venv/bin/hermes chat -q "..."

# または alias 経由
hermes chat -q "..."

Provider 認証情報の保存場所

API キーベース Provider(OpenRouter 等)の場合は ~/.hermes/.env にキーが書かれる。OAuth ベース Provider(Grok、Anthropic OAuth)の場合は ~/.hermes/credentials/ 配下に保存される.env を grep して見つからなくても認証ができているなら問題ない。

確認:

bash
ls -la ~/.hermes/credentials/ 2>/dev/null
cat ~/.hermes/config.yaml | grep -A2 provider

docker compose down -v は禁止

-v フラグは named volume を削除する。bind mount には影響しないが、誤って named volume 化していた場合に大事故になる。常に docker compose down(フラグなし)を使う。

マルチプロファイル機能は使わない

Hermes には profile 機能(複数の独立した Hermes インスタンスを1インストールから動かす)があるが、公式が「Docker 下では非推奨」と明示している。複数プロファイルが必要になったら、別の docker-compose service として独立した container 化する。

スキルが OS パッケージを要求する場合

ごく稀に、新しいスキルがコンテナ内に存在しない OS パッケージを要求する。対処:

  1. 公式イメージで足りない → Dockerfile を作って自前ビルド
  2. ```dockerfile
  3. FROM nousresearch/hermes-agent:v2026.5.16
  4. USER root
  5. RUN apt update && apt install -y <package>
  6. USER hermes
  7. ```
  8. 一時的に試したい → docker exec -u root -it hermes apt install <package>(次の docker pull で消える)

sudo support は絶対に有効にしない

setup wizard の Enable sudo support? (stores password for apt install, etc.) には 必ず N を回答する。

  • hermes ユーザーは sudo を持たないので機能しない
  • パスワードを Hermes 内部に保存することになり情報漏洩リスク
  • bAI 思想(LLM は事前整形された情報のみ扱う、OS レベル操作させない)に反する

秘匿情報の扱い

~/hermes-stack/.env~/.hermes/.env には API キー、bot token、OAuth secret 等が入る。

  • mode 600 を必ず維持
  • git に commit しない
  • バックアップ tarball には含まれるので、tarball も 600 で保管
  • オフサイトバックアップ先の暗号化を確認

gateway ポートはローカルバインドのみ

docker-compose.yml"127.0.0.1:8642:8642" のように明示的にローカルバインドする。"8642:8642" だとすべてのインタフェースで listen し、攻撃面が広がる。

外部から dashboard を見たい場合は SSH ポートフォワード経由で(または Cursor / VSCode Remote-SSH の自動転送で勝手にやってくれる):

bash
ssh -L 9119:127.0.0.1:9119 admin@your-vps
# ブラウザで http://localhost:9119

Discord bot を gateway として使う場合

bot token は強力(DM 経由でコマンド実行できる)。

  • HERMES_DISCORD_ALLOWED_USERS で自分の Discord user ID のみに制限
  • bot 設定で「Allow direct messages」以外のスコープは付けない
  • token がリークしたら即 regenerate

auth.json の上書き再認証バグ(Issue #4718)

Google OAuth の再認証時に、新しいトークンが古いトークンの権限を狭めて上書きしてしまうケースがある。再認証時は同じスコープセットを指定すること。

docker グループ = 実質 root

docker グループ所属は docker run --privileged -v /:/host などで実質 root 相当の権限を持つ。VPS が Hermes 専用である前提で許容している。本気で隔離したいなら rootless Docker への移行を検討。

Phase をまたぐユーザー切り替えで詰まったら

各セクションの冒頭に「作業ユーザー」を明示しているが、長時間かけて作業すると今どちらにいるか分からなくなる。詰まったら以下を確認:

bash
whoami
# 期待値:
# - 管理者作業中: 自分の通常ユーザー名(例: <管理者ユーザー>)
# - hermes 作業中: hermes

exit で hermes から管理者に戻れる。sudo -iu hermes で管理者から hermes に切り替えられる。hermes から hermes には入れないsudo を持っていないため)。

§ 13検証チェックリスト

セットアップ完了時にこれらをすべて通過していること。

Phase 1: 基盤

  • lsb_release -a で Ubuntu 24.04 LTS または 26.04 LTS と確認
  • systemctl is-active fail2ban → active
  • ufw status で必要なルールがある
  • timedatectl status で時刻同期 OK
  • SSH クライアントの種類(Cursor / VSCode Remote-SSH か否か)を把握済み

Phase 2: Docker

  • sudo systemctl is-active docker → active
  • docker --version で 27.x.x 以上
  • docker compose version で v2.x.x
  • sudo docker run --rm hello-world 成功
  • getent group docker で docker グループ存在
  • /etc/apt/keyrings/docker.asc がサイズ約 1.7KB で存在
  • /etc/apt/sources.list.d/docker.sources の Suites が現在の OS コードネーム

Phase 3: hermes ユーザー

  • id hermes で UID が < 1000(system user)
  • sudo passwd -S hermes の2列目が L(ロック)
  • groups hermes に docker が含まれる
  • /etc/ssh/sshd_configDenyUsers hermes が記載
  • sudo -iu hermesdocker ps 成功
  • ホームディレクトリ /home/hermes/ 所有者が hermes:hermes

Phase 4: コンテナ

  • ~/hermes-stack/docker-compose.yml~/hermes-stack/.env が hermes 所有・mode 600
  • docker-compose.ymluser: 行がないこと
  • docker-compose.ymlenvironmentHERMES_UID/HERMES_GID/HERMES_DASHBOARD=1 がある
  • healthcheck が TCP 接続確認方式(/dev/tcp/localhost/8642
  • docker compose ps で hermes が (healthy)
  • docker exec -u hermes hermes id でホスト hermes と同じ UID/GID
  • docker exec hermes env | grep HERMES_DASHBOARD1
  • docker exec -u hermes -it hermes /opt/hermes/.venv/bin/hermes chat -q "hello" が応答
  • alias hermes.bashrc で設定済み
  • curl -fsS http://127.0.0.1:9119 で dashboard HTML が返る
  • Provider 認証完了確認(hermes chat -q "..." で応答が返る)
  • 永続化検証: コンテナ削除→再作成後にテストファイルが残り、所有者も保持される
  • docker inspect hermes | jq '.[0].Mounts' で Type: bind を確認

Phase 5: Google Workspace

  • カレンダーの予定が取れる
  • Gmail 検索ができる
  • OAuth トークンファイルが hermes 所有
  • スコープに必要な権限が全て含まれている

Phase 6: 監視

  • fail2ban-client status で実際の jail 名を確認済み
  • journalctl _COMM=sshd で認証イベントが拾えることを確認済み
  • */5 * * * * cron が動いている (cat /var/log/syslog | grep CRON)
  • 全 4 種類の JSON が 5 分以内に更新されている
  • 各 JSON が jq で valid(auth.json は NDJSON として jq -s
  • コンテナから /opt/data/health/system.json が読める
  • /var/lib/hermes-health/root:hermes 750

Phase 7: スキル

  • ヘルスチェックスキルが Hermes 内で実行できる
  • 異常検知時の Discord 通知が動く(人為的にディスクを満杯近くにして実証)

Phase 8: バックアップ

  • /usr/local/bin/hermes-backup.sh を手動実行して成功
  • tarball の整合性 OK (tar tzf <file> がエラーなし)
  • cron 登録済み
  • 復元テストを1回実施(別ディレクトリで展開してみる)
  • /etc/logrotate.d/hermes 配置済み、logrotate -d で syntax OK

ネットワークとセキュリティ

  • gateway ポート 8642 が 127.0.0.1 のみで listen (ss -ltn | grep 8642)
  • dashboard ポート 9119 が 127.0.0.1 のみで listen
  • .env ファイル群が mode 600(~/.hermes/.env~/hermes-stack/.env の両方)
  • バックアップディレクトリへの一般ユーザーアクセス不可
  • Discord bot の許可ユーザーが自分のみに制限
  • hermes ユーザーへ SSH 直接ログイン不可

§ 14日常運用のチートシート

bash
# === 作業ユーザー: 管理者 ===

# Hermes に切り替え
sudo -iu hermes

# === 以下は作業ユーザー: hermes ===
# (alias `hermes` が .bashrc 設定済みの前提)

# 状態確認
cd ~/hermes-stack
docker compose ps
docker compose logs --tail 50 hermes

# Hermes と対話
hermes                                # インタラクティブ
hermes chat -q "なにかつぶやいて"       # ワンショット
hermes sessions list                  # 過去セッション
hermes --resume <session_id>          # 再開

# 設定確認
hermes config show
hermes doctor

# 完全再起動
docker compose restart hermes

# 完全リセット(データ保持)
docker compose down
docker compose up -d
sleep 90 && docker compose ps   # healthy 確認

# バージョンアップ
vi .env                                # HERMES_VERSION 書き換え
docker compose pull && docker compose up -d
sleep 90 && docker compose ps
hermes doctor

# === 作業ユーザー: 管理者(戻る場合は exit) ===
exit

# ヘルスチェック手動実行
sudo /usr/local/bin/hermes-health-collect.sh
cat /var/lib/hermes-health/*.json | jq .

# バックアップ手動実行
sudo /usr/local/bin/hermes-backup.sh

# ログ確認
sudo tail -f /var/log/hermes-backup.log
sudo journalctl -u docker -f

§ 15参考リンク

  • Hermes Agent 公式ドキュメント: https://hermes-agent.nousresearch.com/docs/
  • Docker デプロイガイド: https://hermes-agent.nousresearch.com/docs/user-guide/docker
  • Google Workspace スキル: https://hermes-agent.nousresearch.com/docs/user-guide/skills/google-workspace
  • リリースノート: https://github.com/NousResearch/hermes-agent/releases
  • Issues(バグ・機能要望): https://github.com/NousResearch/hermes-agent/issues
  • Docker Hub: https://hub.docker.com/r/nousresearch/hermes-agent
  • Docker Engine 公式インストール: https://docs.docker.com/engine/install/ubuntu/
  • Ubuntu 26.04 LTS リリースノート: https://documentation.ubuntu.com/release-notes/26.04/

§ 16次の拡張ポイント

セットアップが安定したあとに検討する余地のある拡張:

  • Discord 通知の経路: Hermes 内蔵の Discord gateway を使うか、別途 webhook 経由にするか
  • MCP サーバー追加: Obsidian MCP との連携、個人の作業フロー統合
  • collector の拡張: nginx アクセスログ、Docker コンテナ自体のメトリクス、外部 ping 監視
  • second VPS への複製: 同一構成を別 VPS に立てて、相互ヘルスチェック
  • rclone でのオフサイトバックアップ: Google Drive / R2 / B2 等
  • rootless Docker への移行: docker グループ=実質 root の問題を解消
  • Claude Code 経由の Anthropic OAuth: Claude Max の overage credits を Hermes に共有する経路の検討

これらは別途設計が必要だが、現在の構成を素直に拡張できる範囲。

§ 17変更履歴

v1 → v2 の変更点

  • Phase 順序を整理: Docker インストール → hermes ユーザー作成
  • hermes ユーザーを system user として作成
  • SSH 直接ログイン禁止 (DenyUsers) を明示
  • データ永続化の検証ステップを Phase 4 に追加
  • UID マッピング周りの注意を追記

v2 → v3 の変更点

  • Docker インストール手順を公式現行版(DEB822 形式と .asc 鍵)に更新
  • 競合パッケージ削除リストを公式の網羅版に差し替え
  • gpg --dearmor ステップが不要に

v3 → v4 の変更点

  • docker-compose.yml の UID/GID 扱いを修正: user: 指定を削除し、environmentHERMES_UID/HERMES_GID を渡す方式に変更
  • setup wizard を compose 起動より前に実行する手順に変更
  • API キーを compose の environment から削除
  • logging オプションを追加
  • collector スクリプトを改善
  • fail2ban jail 名と journal 単位名の事前確認手順を追加
  • logrotate 設定を追加
  • Phase 4 永続化検証に dashboard 起動確認と UID マッピング検証を追加
  • 12章の auth.json と UID マッピング項を user: 非推奨に書き換え

v4 → v4.1 の変更点

  • Ubuntu 26.04 LTS(Resolute Raccoon)対応を明示
  • Phase 3 末尾の「以降の管理方法」を Phase 3 時点で実行可能な内容に修正
  • 各 Phase に「作業ユーザー」明示を追加
  • 表記規約セクションを冒頭に追加

v4.1 → v4.2 の変更点

実機セットアップで判明した重要な仕様と落とし穴を全反映:

  • OAuth ベース Provider(Grok / Anthropic OAuth)の setup wizard は --network host で起動する必要がある: コールバックを受け取れる経路を確保するため
  • Cursor / VSCode Remote-SSH のループバック自動転送について明記: これがあると --network host だけで OAuth が通る
  • 素の SSH の場合の対処: ssh -L でのポートフォワード手順を追加
  • hermes バイナリの絶対パスを明記: コンテナ内で /opt/hermes/.venv/bin/hermes
  • docker exec のデフォルトユーザー注意: root で実行されるため -u hermes を必須化
  • alias 設定の推奨: 日常運用での煩雑さを軽減
  • healthcheck の修正: gateway は /health エンドポイントを提供しないため、TCP 接続確認方式へ変更
  • system user UID が環境によって 998 / 999 等で異なる点を明記: Xserver VPS は 999
  • Provider 設定が .env に書かれない場合の説明: OAuth は credentials ファイルに保存される
  • setup wizard 中断・時間切れ時の対処手順を追加
  • OAuth の「コードコピペ画面」は Hermes 側に貼り付け先が無い旨を明記: Grok 側のフォールバック表示なので、Hermes 側では使えない
  • ファイル作成手順を Phase 4 で全部 cat > ... <<EOF 形式に統一

v4.2 → v4.3 の変更点

  • Phase 3 動作確認ブロックのユーザー切り替え事故を修正: 1ブロック内で「管理者 → hermes → 管理者」と切り替わる構成だったため、exit 忘れで hermes プロンプトのまま sudo grep を実行→拒否されるケースがあった。管理者で実行する確認を前半にまとめ、hermes への切り替えをブロック末尾に移動。「hermes は sudo 不可」の注記も追加