skills/deploy-server/SKILL.md

```
name: deploy-server
description: Use this skill to provision or update a Welley server on a supported cloud (currently DigitalOcean) when the user asks to deploy a new instance or run a server update. Covers single-instance deployments and SSH-based updates, including interactive collection of missing credentials, SSH keys, domain, region, and sizing.
```

# サーバーのデプロイ

## 概要

このスキルは、DigitalOcean 上に単一インスタンスの Welley スタックを用意し、SSH 経由でアップデートを実行できます。エージェントは、足りない入力を集め、決められた取得元から利用できるインスタンスのスペックを取得し、VM を作成し、ホストを設定し、`wlycli deployment create` でデプロイして、動作を確認します。

コマンド名は CLI と一致させてください。

-   `wlycli deployment create`
-   `wlycli deployment update`
-   `wlycli deployment delete`

## デフォルト (DigitalOcean)

-   リージョン: `nyc3`
-   イメージ: `latest LTS available via API` (`images?type=distribution` で解決します)
-   Droplet 名: `wlsly-<adjective>-<surname>` の形式でランダムに生成します (例: `wlsly-bright-hopper`)。`references/digitalocean.md` にある名前生成のスニペットを使ってください。マネージド DB を作成する場合も、同じ名前を使います。
-   プランの目標: Basic / Regular (共有 CPU)
-   サイズの目標: 2 vCPU / 4 GB RAM / 80 GB SSD / 4 TB 転送量 (月額約 $24)

## デフォルトを変更するときのルール

ユーザーがデフォルトのいずれか (リージョン、イメージ、Droplet 名、プロジェクト、サイズ) を指定した場合、エージェントは必ず次の順で進めます。

1.  指定された内容をそのまま復唱する。
2.  先に進む前に確認を取る。

## 対話で入力を集めるテンプレート

足りないことだけを聞いてください。あいまいさを減らすため、次の質問文をそのまま使います。 質問は何回かに分けて行います。ラウンド 1 は 1 回につき最大 2 問です。ラウンド 2 では「すべてデフォルトで進める」という選択肢をまとめて示してもかまいません。 実行環境が選択肢に対応している場合は、1 問につき 2〜3 個の選択肢 (A/B/C) を示してください。必要な場合を除き、自由記述は避けます。

ラウンド 1 の質問 (認証情報とアクセス):

1.  「使用する DigitalOcean の API トークンはお持ちですか。それとも、ご提供いただくまでお待ちしたほうがよいですか。」
2.  「このサーバーにはどのドメインを使いますか (例: `super-puper.example.com`)。そのドメインの DNS は操作できますか。」
3.  「使用する SSH 公開鍵はすでにありますか。それとも、私が作成してアップロードしますか。」
4.  「この Droplet を既存の DigitalOcean プロジェクトに割り当てますか。割り当てる場合は、プロジェクト名または ID を教えてください。」
5.  「DNS の要件: `<domain>` と `storage.<domain>` (CDN を使わない場合) に A/AAAA レコードを作成する必要があります。certbot を実行する前に作成できますか。」
6.  「メール用 DNS の要件: `--smtp-from` のドメインには SPF、DMARC、DKIM のレコードが必要です。ないと、新しい受信者への招待メールや確認メールが気づかれないまま破棄されることがあります。すでに設定済みですか。それとも、SMTP プロバイダーが必要とするレコードを一覧にしましょうか。」
7.  ユーザーが DO トークンの入手先を尋ねた場合は、次のリンクを案内します。

```
https://cloud.digitalocean.com/account/api/tokens
```

ラウンド 2 の質問 (サイズ、場所、デプロイのオプション):

1.  「リージョンはデフォルトの `nyc3` にしますか。それとも別のリージョンにしますか。」
2.  「サイズはデフォルト (2 vCPU / 2 GB) にしますか。それとも別の CPU/RAM にしますか。」
3.  「API で利用できる最新の Ubuntu LTS を使いますか。それとも特定の Ubuntu バージョンを使いますか。」
4.  「ブートストラップ後に使う SSH ユーザーは何にしますか (デフォルト: `wlsly`)。デフォルトのままでもかまいません。」
5.  「マネージド Postgres DB を使いますか。それとも同梱の Postgres コンテナを使いますか。」
6.  マネージド Postgres を選んだ場合: 「DB のリージョンとサイズはどうしますか。`nyc3` と 1 vCPU / 2 GB をデフォルトにできます。」
7.  マネージド Postgres を選んだ場合: 「プロバイダーに表示されている月額料金で作成を進めてよいですか。」
8.  「必要な SMTP 設定 (host、user、password、from) を教えてください。OTEL は任意です。必要な場合のみ教えてください。」
9.  「作成には管理者のメールアドレスが必要です。今ご提供ください (フラグ: `--email` / `-e`)。ご提供がない場合は、確認のため 2 回入力をお願いします。」

入力を早く集めるため、ラウンド 2 では「すべてデフォルトで進める」という選択肢を示してください。

答えが足りない場合は、的を絞った追加の質問 (1〜2 問) をします。

## ワークフロー (DigitalOcean、単一インスタンス)

1.  **必要な入力を集める (足りないものは聞く)。**

-   DigitalOcean の API トークン
-   プロジェクト名または ID (任意。未指定または見つからない場合は、デフォルトのプロジェクトを使います)
-   リージョン
-   インスタンスのサイズの目標 (例: 4 vCPU / 16 GB RAM)
-   ドメイン名
-   SSH 公開鍵 (または、作成して登録することへの同意)
-   最初のログインとパスワードの再設定に使う管理者のメールアドレス (作成時は必須。`--email` / `-e`)。未入力の場合は 2 回入力してもらい、一致を確認します。
-   任意: カスタム TLS 証明書か certbot か
-   DNS を操作できることの確認 (`<domain>` と、使う場合は `storage.<domain>` に A/AAAA レコードを作成できること)

1.  **インスタンスタイプを取得してサイズを選ぶ。**

-   `references/digitalocean.md` のサイズ用スニペットを使い、CPU/RAM で一覧を絞り込みます。
-   候補が複数ある場合は、ユーザーに確認します。

2b. **Ubuntu イメージの slug を解決する。**

-   `GET /v2/images?type=distribution&per_page=200` を呼び出し、Ubuntu で絞り込みます。
-   ユーザーが特定のバージョンを指定した場合は、その slug があることを確かめて、確認を取ります。
-   指定がない場合は、一覧から最新の LTS の slug を選び、作成前にユーザーに確認します。

1.  **Droplet を作成する。**

-   DigitalOcean API を直接使います (doctl は任意です)。
-   イメージ: ユーザーの指定を優先します。指定がなければ、API の一覧から最新の LTS を選びます。
-   SSH 鍵のフィンガープリントを割り当てるか、鍵を作成してアップロードします。
-   モニタリングを有効にします。
-   プロジェクト名/ID が指定されたものの見つからなかった場合は、プロジェクトなし (デフォルトのプロジェクト) で Droplet を作成します。
-   注: マネージド DB の作成は、Droplet の作成と並行して進められます。

## 並行して進めるための指針

エージェントは、互いに関係のない作業を重ねて進め、実際にかかる時間を短くします。

-   ユーザーからの入力を集めている間 (ラウンド 2) でも、トークン、リージョン、サイズ、イメージ、SSH 鍵がすでに確定していれば、Droplet の作成を始めます。
-   Droplet を用意している間に、マネージド Postgres の作成を始めます (希望された場合)。
-   Droplet が起動している間に、残りの入力 (SMTP、OTEL、管理者のメールアドレス) を集め続けます。
-   ある手順に欠かせない入力が足りない場合は、その手順をいったん止めて、並行できる他の作業を続けます。
-   DNS を DigitalOcean で管理している場合は、Droplet の IP が分かり次第、A/AAAA レコードを作成または更新します (デプロイの手順全体が終わるのを待たないでください)。

1.  **DNS とドメイン。**

-   DNS を DigitalOcean で作成・更新するか、外部の DNS のままにするかを聞きます。
-   DigitalOcean の DNS を使う場合は、Droplet の IP を指す A/AAAA レコードを作成します。

1.  **SSH でホストを設定する。**

-   root 以外のユーザー (例: `wlsly`) を作成し、sudo 権限を付与して、SSH の authorized keys をコピーします。
-   次に進む前に、**root からログアウトし、root 以外のユーザーで再ログインします。**
-   Docker と docker compose をインストールします。
-   AppStore から Welley CLI のバイナリを取得し、実行できるようにします。
    -   ベース URL: `https://appstore.allroads.social/v1/social.wellesley.wlycli/latest?develop=stable&platform=<os>/<arch>`
    -   OS とアーキテクチャに合わせて、正しい `platform` の値を選びます。
        -   Linux x86\_64: `linux/amd64`
        -   Linux arm64: `linux/arm64`
        -   macOS x86\_64: `darwin/amd64`
        -   macOS arm64: `darwin/arm64`
        -   Windows x86\_64: `windows/amd64`
    -   例 (Linux x86\_64):
        
        ```
        curl -fL 'https://appstore.allroads.social/v1/social.wellesley.wlycli/latest?develop=stable&platform=linux/amd64' -o wlycli
        chmod +x wlycli
        ```
        
    -   レスポンスヘッダーには、次のものが含まれる想定です。
        -   `x-artifact-display-name: Welley CLI`
        -   `x-artifact-full-name: social.wellesley.wlycli`
        -   `x-artifact-kind: cli`
        -   `x-artifact-platform: <os>/<arch>` (例: `linux/amd64`)
        -   `x-artifact-sha256: <sha256>`
        -   `x-artifact-version: <version>`
        -   `x-channel: develop`
    -   インストールの前に、できれば HEAD リクエストでヘッダーを確認し、アーティファクト名、種類、プラットフォーム、チャンネルが、求めたバイナリと一致していることを確かめてください。
-   CLI バイナリを確認します。`wlycli -v` と `wlycli deployment --help` を実行して、想定どおりのビルド情報かどうかを見ます。

1.  **Welley をデプロイする。**

-   必要なオプションを付けて `wlycli deployment create` を実行します。
-   単一インスタンスのデフォルトを使います (ユーザーから求められない限り、外部の Postgres は使いません)。
-   マネージド Postgres を希望された場合は、先に作成し (DigitalOcean の DB)、返された host/port/db/user/password を `--postgres-*` フラグに使います。
-   DB ユーザーのパスワードが API や UI から返されない場合は、`wlycli` を実行する前に DB ユーザーの認証情報をリセットして、新しい認証情報を取得します。
-   非対話で実行する場合は `--email` / `-e` を渡す必要があります。渡さないとコマンドは失敗します。
-   `--base-dir` は親ディレクトリを指します。プロジェクトのサブディレクトリは CLI が自動で作成します。
-   プロジェクトのディレクトリやコンテナがすでにある場合、CLI は実行を拒否します。やり直す前に、片付けが必要です。

1.  **証明書 (必須)。**

-   デフォルト: Certbot の standalone を使います (非対話)。
-   **certbot を実行する前に、ユーザーがドメイン (使う場合は `storage.<domain>` も) の A/AAAA レコードを Droplet の IP に向けておく必要があります。**
-   CLI は `--public-ip` (検出された IP を上書き) に対応しています。チェックをどうしても省く必要がある場合のために、`--skip-dns-preflight` もあります。
-   **DNS が設定済みだと決めつけないでください。ユーザーに聞き、はっきりした確認が来るまで待ちます。**
-   **ユーザーがはっきり求めた場合を除き、`--skip-dns-preflight` は使わないでください。**
-   CDN を設定していない場合は、certbot の証明書リクエストに `storage.<domain>` を含める必要があります。
-   自己署名証明書は、ユーザーがはっきり求めた場合にだけ使い、`--self-signed-certs` を渡します (Go で自己署名証明書を生成します)。
-   ユーザーに DNS の設定手順を伝え、確認が来るまで待ちます。
-   「`<domain>` が Droplet の IP に解決されるよう、A/AAAA レコードを作成してください。」
-   「`storage.<domain>` を使う場合は、そのドメインにも A/AAAA レコードを作成してください。」
-   「DNS が反映されたら教えてください。続きを進めます。名前解決の確認を頼まれない限り、私には反映の状況は見えません。」
-   エージェント自身が、`ping -c 1 <domain>` を使って DNS の反映を確認し、その IP を Droplet の IP と比べてもかまいません。一致していれば、先に進みます。

7b. **メール用 DNS (SPF / DMARC / DKIM)。**

-   SPF と DMARC がないドメインから送ったメールは、SMTP プロバイダー側では配信済みと表示されていても、初めての受信者宛てだと Gmail や Outlook に破棄されることがよくあります。
-   `--smtp-from` のドメインに SPF レコードがない場合、または `_dmarc.<domain>` か上位のドメインに DMARC レコードがない場合、`deployment create` は `Warning:` を表示します。デプロイは止まりません。この警告は無視せず、ユーザーに見せて、追加するレコードを伝えてください。
    -   `<mail-domain>` の TXT `v=spf1 include:<provider> ~all` (include の中身は SMTP プロバイダーによって決まります。すでに SPF レコードがある場合は統合し、2 つ目を追加しないでください)
    -   `_dmarc.<mail-domain>` の TXT `v=DMARC1; p=none; rua=mailto:dmarc@<mail-domain>`
    -   DKIM: ドメインの検証時に SMTP プロバイダーが生成する `<selector>._domainkey.<mail-domain>` のレコード
-   `dig +short TXT <mail-domain>` と `dig +short TXT _dmarc.<mail-domain>` で確認します。
-   CLI は DKIM を確認できません (セレクターはプロバイダーごとに異なるためです)。`create` が警告を表示しなかった場合でも、SMTP プロバイダーで DKIM の検証が済んでいるか、必ずユーザーに確認してください。Amazon SES などのプロバイダーでは、SPF はプロバイダー自身のエンベロープドメインに対して確認されるため、DMARC を通せるのは DKIM だけです。SES では、_Verified identities_ の `<mail-domain>` を開き、DKIM が _Successful_ になっている必要があります。

1.  **確認して報告する。**

-   `docker ps` とサービスのヘルスチェック用エンドポイントを確認します。
-   最終的な接続情報と、次にやることを伝えます。

## SSH でのアップデートの流れ

ユーザーが「サーバーをアップデートして」と言った場合:

1.  対象のホスト/IP と、SSH ユーザー/鍵を聞きます。
2.  `wlycli deployment update --domain <domain> [--force-restart]` を実行します。
3.  コンテナと Nginx の状態を確認します。

## 参考資料

-   DigitalOcean API のスニペットと例: `references/digitalocean.md`

## シークレットとフラグの対応

シークレットには、次のフラグ名を使います。

-   SMTP host: `--smtp-host`
-   SMTP user: `--smtp-user`
-   SMTP password: `--smtp-password`
-   SMTP from: `--smtp-from`
-   OTEL endpoint: `--otel-endpoint`
-   OTEL headers: `--otel-headers`

## 最小限の create コマンド

必須のフラグ:

-   `--domain`
-   `--email`
-   `--smtp-host`
-   `--smtp-user`
-   `--smtp-password`
-   `--smtp-from`

最小限の例:

```
wlycli deployment create \
  --domain example.com \
  --email admin@example.com \
  --smtp-host smtp.example.com \
  --smtp-user user \
  --smtp-password pass \
  --smtp-from noreply@example.com
```

## マネージド Postgres のデフォルト (DigitalOcean と CLI)

DigitalOcean のマネージド Postgres を使う場合は、CLI のデフォルトに頼らず、`--postgres-*` フラグをすべて明示的に指定してください。

-   DO の一般的なデフォルト:
    -   ポート: `25060`
    -   データベース: `defaultdb`
    -   ユーザー: `doadmin`
-   CLI のデフォルト (フラグを省いた場合):
    -   ポート: `5432`
    -   データベース: `wlsly`
    -   ユーザー: `wlsly`

## CLI を使うときの注意

-   バージョンの確認: `wlycli -v` または `wlycli --version` (フラグ)。`version` というサブコマンドはありません。
-   `--base-dir` は、デフォルト以外の場所を使いたい場合にだけ指定します (デフォルトは `~/.welley`)。権限があれば、CLI がそのディレクトリを作成します。
-   非対話の SSH 実行では、入力待ちを避けるため、`--email` と必要なフラグをすべて付けてください。
-   ユーザーがサーバー上に CLI バイナリを用意している場合 (例: `~/wcli`) は、それをそのまま使います。求められない限り、移動しないでください。
-   DO API を呼び出すときは、`references/digitalocean.md` の**スニペットをそのままコピー**してください。Python の解析部分を書き直したり、アレンジしたり、即興で作ったりしないでください。エスケープやクォートのような小さな変更でも、シェルによっては動かなくなります。
-   **SSH でリモートスクリプトを実行する場合:** 必ず `ssh host 'bash -s' << 'EOF'` (クォート付きの heredoc) を使ってください。`ssh host "..."` のようにダブルクォートで囲む書き方は使わないでください。zsh が、リモートホストに送る前に `$(...)` や `[...]` をローカルで展開してしまいます。

## 失敗したときの復旧

`deployment create` が失敗してやり直す場合:

-   片付けには `wlycli deployment delete --domain <domain> --yes-please-delete-all-the-data` を使います。
-   delete は、root 所有のファイルをコンテナ経由で片付けようとします。それでも失敗する場合は、`--base-dir` の下にあるプロジェクトのディレクトリを、昇格した権限で削除してから、再実行してください。

## ポート 80 の事前テスト (任意)

certbot が失敗した場合は、再実行する前にポート 80 に届くかをテストします。

```
docker run --rm -d -p 80:80 --name test-http nginx:alpine
curl -s http://<domain>:80
docker rm -f test-http
```

## ほかの文書

-   [**Welley CLI** docs/index.md](https://welley.com/ja/administrators/docs/cli/)
-   [**クイックスタート: 最小構成の Welley デプロイ** docs/quick-start.md](https://welley.com/ja/administrators/docs/quick-start/)
-   [**openai.yaml** skills/deploy-server/agents/openai.yaml](https://welley.com/ja/administrators/docs/deploy-server-openai-yaml/)
-   [**DigitalOceanリファレンス** skills/deploy-server/references/digitalocean.md](https://welley.com/ja/administrators/docs/deploy-server-digitalocean/)
