サーバー版の設定項目
部屋のサーバー(Cloudflare版とVercel版)の設定を説明します。はじめに両方に共通する項目を、そのあとに置き場所ごとの項目と、値の変えかたを書きます。
共通の項目
| 名前 | 必須 | 既定値 | 内容 |
|---|---|---|---|
ACCESS_CONTROL_ALLOW_ORIGIN | ○ | — | 受けるAxeのOrigin。カンマ区切りで複数書ける |
DISCORD_CLIENT_ID | ※ | — | ログインに使うDiscordのアプリのClient ID(→ Discordのアプリを作る) |
DISCORD_CLIENT_SECRET | ※ | — | 同じアプリのClient Secret |
SESSION_SECRET | ※ | — | アクセストークン、招待、部屋のアセットのトークンに署名する鍵。32文字以上の乱数 |
ROOM_RETENTION_HOURS | 24 | 最後の1人が抜けてから部屋を消すまでの時間(1以上の整数) | |
ADMIN_CONTACT_URL | — | BANや緊急停止で断った人に見せる問い合わせ先(https://のページかmailto:) | |
ROOM_STORAGE_LIMIT_MB | — | 1つの部屋に置ける画像と音声の合計(MB)。入れなければ、合計の上限は無い | |
ADMIN_DISCORD_IDS | — | 管理画面(/admin)に入れる運営する人のDiscordのユーザーID。カンマ区切り。入れなければ管理画面は無い | |
ADMIN_PASSKEY_RESET | — | パスキーをすべて失くした運営する人のDiscordのユーザーID。入れたあいだだけ、新しいパスキーを登録できる | |
ADMIN_WEBHOOK_URL | — | 運営する人への知らせを送るDiscordのWebhookのURL。入れなければ知らせは送らない | |
ADMIN_STORAGE_ALERT_GB | — | 画像と音声の置き場所がこの量(GB)を超えたら知らせる。入れなければ置き場所を数えない |
ACCESS_CONTROL_ALLOW_ORIGIN
Axeを配信しているページのOriginを書きます(→ 設置の準備)。部屋のもの(作る・一覧・入る・削除)、部屋のアセット、ログインとアカウントは、ここに書いたOriginのページからしか受けません。Discordでログインしたあとに戻る先も、ここに書いたOriginのページだけです。
- スキーム(
https://)・ホスト名・ポートがすべて一致したときだけ通します。パスや末尾の/の違いは見ません。 *と書くと、どのOriginからも受けます。ただし*は、ログインのあとに戻る先には数えません。*だけにすると、だれもログインできず、部屋も作れません。AxeのOriginを、名前で書いてください。
ログインの3つ
DISCORD_CLIENT_ID、DISCORD_CLIENT_SECRET、SESSION_SECRETは、そろえて入れるか、どれも入れないかのどちらかにします。
- 3つとも入れる:ログインと部屋が使えます。ふつうはこれです。
- どれも入れない:正しい設定ですが、ログインも部屋も使えません。部屋を作ろうとすると
503 login-unavailableを返すので、部屋の一覧は空のままです。 - 一部だけ入れる(
SESSION_SECRETが32文字より短いときも):設定の誤りとして、部屋のサーバーのすべてのリクエストに500 config-invalidを返します。
部屋を作ったあとで3つを外すと、残っている部屋は、アクセストークン、招待、部屋のアセットのトークンを一切受けなくなります。部屋には見学としてだけ入れます。
SESSION_SECRETを替えると
SESSION_SECRETを替えると、それまでに出したアクセストークン、招待、部屋のアセットのトークンは、すぐにすべて使えなくなります。
- ログインしている人は、ログインし直さずに済みます。リフレッシュトークンは
SESSION_SECRETとは別に置いているので、Axeが新しいアクセストークンに替えます。 - 招待は、作り直します。前の招待で初めて入った人はPLとして覚えられるので、役割はGMが部屋で変えます。
漏れたかもしれないときのほかは、替えなくて構いません。
ADMIN_DISCORD_IDS
運営する人の管理画面に入れる人の、DiscordのユーザーIDです(→ 管理画面とBAN)。入る人は、このアカウントでDiscordにログインしたうえで、端末のパスキーで確かめます。DiscordのアプリのRedirectsに、/admin/login/callbackも足してください(→ 使えるようにする)。数字でないものが入っていると、設定の誤りとして、部屋のサーバーのすべてのリクエストに500 config-invalidを返します。
ADMIN_PASSKEY_RESET
すべての端末のパスキーを失くした運営する人の、DiscordのユーザーIDです(→ 端末を失くしたとき)。新しいパスキーを登録したら、外してください。
ADMIN_WEBHOOK_URL
回数の上限を超えた送信元や、管理画面へのログインがあったときに、知らせを送るDiscordのWebhookのURLです(→ Discordで知らせを受ける)。https://で始まらないと、設定の誤りとして、部屋のサーバーのすべてのリクエストに500 config-invalidを返します。
ADMIN_CONTACT_URL
BANや緊急停止で断った人に見せる問い合わせ先です。https://で始まるページか、mailto:で始まるメールアドレスを書きます(例:mailto:admin@example.com)。入れておくと、Axeが断られたことを出すときに、この問い合わせ先も出します(→ 部屋のサーバーのAPI)。共有の回線を巻き込んだBANのように、運営する人が知らずに止めてしまった人が、申し出られるようにするためのものです。問い合わせ先は、断られた人のだれにでも見えるので、秘密の値ではありません。https://かmailto:で始まらないと、設定の誤りとして、部屋のサーバーのすべてのリクエストに500 config-invalidを返します。
ROOM_STORAGE_LIMIT_MB
1つの部屋に置ける画像と音声の合計を、MB(100万バイト)の整数で書きます(例:500)。置こうとしたものを足すとこれを超えるときは、413 room-storage-fullで断ります(→ 部屋のサーバーのAPI)。アカウントや回線を替えながら1つの部屋に置き続ける使われかたで、置き場所の費用が積み上がるのを抑えます。1つ1つの画像と音声の大きさの上限(画像2MiB、音声10MiB)とは別にかかります。
- 部屋ごとの合計を、置いたときと消したときに足し引きして覚えておくので、置くたびに一覧を読むことはしません。一覧を読んで数えるのは、入れたあと、その部屋で初めて置こうとしたときの1度だけです。合計は、Cloudflare版では部屋のDurable Objectに、Vercel版ではRedisに覚えます。
- すでに上限を超えている部屋の画像と音声は消しません。新しく置くものだけを断ります。
- 0より大きい整数でないと、設定の誤りとして、部屋のサーバーのすべてのリクエストに
500 config-invalidを返します。
ADMIN_STORAGE_ALERT_GB
部屋に置かれた画像と音声の合計が、この量を超えたら知らせます(→ Discordで知らせを受ける)。GB(10億バイト)で書き、0.5のように小数も書けます。置き場所の無料枠に合わせるなら、Cloudflare版のR2は10です。入れたときだけ、1日1回、日本時間の3時ごろに置き場所を数えます。0より大きい数でないと、設定の誤りとして、部屋のサーバーのすべてのリクエストに500 config-invalidを返します。
ROOM_RETENTION_HOURS
部屋から最後の1人が抜けて、この時間が過ぎると、部屋とその画像と音声を消します(→ 部屋が消えるとき)。Cloudflare版の既定値の24は、wrangler.jsoncのvarsに書いてあります。この行を消すと、/v1/の下がすべて500 config-invalidになるので、変えるときも消さずに値だけを書き換えます。
Cloudflare版
値の置き場所
Cloudflare版の設定は、2つの場所に分かれています。
| 設定 | 置き場所 | 変えかた |
|---|---|---|
ACCESS_CONTROL_ALLOW_ORIGIN、ROOM_RETENTION_HOURS | wrangler.jsoncのvars | 複製したリポジトリのwrangler.jsoncを書き換える |
DISCORD_CLIENT_ID、DISCORD_CLIENT_SECRET、SESSION_SECRET、ADMIN_DISCORD_IDS、ADMIN_PASSKEY_RESET、ADMIN_WEBHOOK_URL、ADMIN_STORAGE_ALERT_GB、ROOM_STORAGE_LIMIT_MB、ADMIN_CONTACT_URL | Workerのシークレット | Cloudflareのダッシュボードで書き換える |
wrangler.jsoncに書いた値は、デプロイのたびにWorkerの設定を上書きします。Deployボタンで置いたときは、複製したリポジトリに変更が入るたびにデプロイし直すので、ACCESS_CONTROL_ALLOW_ORIGINはwrangler.jsoncの側で書き換えます(→ Cloudflareに置く)。wrangler.jsoncの既定のhttp://localhost:4200は、Axeを手元で開発するときのものです。
シークレットを変えるときは、次のようにします。
- ダッシュボードのWorkers & PagesからWorkerを開き、SettingsのVariables and Secretsを開きます。
- 変えたいシークレットのEditを押し、新しい値を入れます。足すときはAddを押し、種類をSecretにして、名前と値を入れます。
- Deployを押します。押すまで、変更は反映されません。
シークレットの値は、保存したあとは画面で見られません。デプロイし直しても消えません。
このリポジトリのCIからデプロイするときは、ACCESS_CONTROL_ALLOW_ORIGINもGitHubのSecretから入れ、同じ名前のWorkerのシークレットでwrangler.jsoncの値を置き換えます(→ 自分のリポジトリから自動でデプロイする)。
バインディング
Workerが使うCloudflareの資源です。wrangler.jsoncに書いてあり、最初のデプロイで作られます。
| 名前 | 種類 | 内容 |
|---|---|---|
ASSETS | R2バケット | 部屋ごとの画像と音声の置き場。名前は書かず、最初のデプロイで「<Workerの名前>-assets」が作られる |
ROOMS | Durable Object | 部屋ごとに1つ(RoomDurableObject)。名前は部屋の鍵 |
LOBBY | Durable Object | 部屋の一覧(LobbyDurableObject)。Workerに1つ |
ACCOUNTS | Durable Object | ログインした人ごとに1つ(AccountDurableObject)。名前はdiscord:<ID> |
MODERATION | Durable Object | 運営する人のBANと記録(ModerationDurableObject)。Workerに1つ(→ 管理画面とBAN) |
UPLOAD_LIMITER | レート制限 | 新しく置くPUTと、部屋のアセットを消すDELETEを、送信元ごとに合わせて1分100件まで |
READ_LIMITER | レート制限 | R2を読むGETとHEAD、部屋の一覧、鍵で1つ引く部屋、部屋のアセットの一覧を、送信元ごとに合わせて1分600件まで |
ROOM_LIMITER | レート制限 | 部屋を作る・削除する・部屋に入るのを、送信元ごとに合わせて1分30回まで |
AUTH_LIMITER | レート制限 | Discordから戻るのと、トークンの発行・更新、ログアウトを、送信元ごとに合わせて1分60回まで |
METRICS | Analytics Engine | 部屋で起きたことを書くデータセットudonarium_axe_rooms(→ 部屋の記録) |
- Durable Objectsは、SQLiteに状態を置く種類です。クラスの名前を変えたり消したりするときは、
migrationsに新しいtagを足します(今あるtagは書き換えません)。 - 部屋のフレームのgzip、パスワードのハッシュ、トークンの署名に
node:zlibとnode:cryptoを使うので、compatibility_flagsにnodejs_compatを入れています。
レート制限を変える
wrangler.jsoncのratelimitsのsimple.limitを書き換えます。period(数える期間の秒数)は、Cloudflareの決まりで10か60しか選べません。
{
"name": "UPLOAD_LIMITER",
"namespace_id": "1001",
"simple": { "limit": 100, "period": 60 },
}- 送信元は、IPv4ならアドレス、IPv6なら先頭の/64で数えます。IPv6の利用者は/64を丸ごと持っていることが多く、アドレスを替えるだけで別人として数えられるのを防ぐためです。
- すでに置いてあるものへのPUTは数えません。部屋に入ったAxeが、手元の画像と音声をまとめて確かめに来るためです。
- データセンターのキャッシュから返したGETとHEADは数えません。
- Cloudflareのレート制限は、データセンターごとに数えます。
namespace_idは同じアカウントのWorkerで共有されますが、数えるときの鍵に受けたホスト名を付けるので、同じアカウントに置いた別のWorkerとは混ざりません。
手元で動かす
cd apps/cloudflare
npm ci
cp .dev.vars.example .dev.vars # ログインの3つを書く
npm run dev # http://localhost:8787管理画面も試すときは、.dev.varsにADMIN_DISCORD_IDS(自分のDiscordのユーザーID)も書き、DiscordのアプリのRedirectsにhttp://localhost:8787/admin/login/callbackを足してから、http://localhost:8787/adminを開きます(→ 管理画面とBAN)。.dev.varsは、Gitに入らないようにしてあります。
Vercel版
項目
共通の項目に加えて、次のものを読みます。VercelのプロジェクトのEnvironment Variablesで足したり変えたりします。
| 名前 | 必須 | 既定値 | 内容 |
|---|---|---|---|
REDIS_URL | ○ | — | 部屋・一覧・アカウントを置くRedis。無ければKV_URLを読む(Vercel MarketplaceのRedisが入れる名前) |
BLOB_STORE_ID | ※ | — | 部屋の画像と音声を置くVercel Blobのストア(private)のID。Vercelの上では、これで読み書きする |
BLOB_READ_WRITE_TOKEN | ※ | — | 同じストアの鍵。Vercelの外で動かすときは、こちらを入れる |
ROOM_SOCKET_SECONDS | 280 | 部屋のWebSocketを、部屋のサーバーから閉じてつなぎ直してもらうまでの秒数(60以上) | |
REDIS_KEY_PREFIX | axe: | Redisのキーの頭。1つのRedisを、別のデプロイと分けて使うときに替える | |
CRON_SECRET | — | 掃除(/internal/sweep)を呼べる鍵。入れると、Vercel Cronのほかからは呼べなくなる。入れないときは、掃除は12時間に1回まで | |
BLOB_LOCAL_DIR | — | 手元で試すときに、画像と音声をこのディレクトリに置く。Vercelの上では使えない |
- ※のBlobの2つは、どちらかがあれば足ります。ストアをプロジェクトにつなぐと、Vercelが
BLOB_STORE_IDを入れます。 - ログインの3つを入れたら、RedisとBlobのストアも要ります。どれかが無い、または形が違うと、部屋のサーバーのすべてのリクエストに
500 config-invalidを返し、足りない設定の名前をFunctionのログに出します。
ROOM_SOCKET_SECONDS
Vercelは、Functionの最大実行時間(無料のプランは300秒)でWebSocketを切ります。そうなる前に、部屋のサーバーが自分から閉じて、Axeにすぐつなぎ直してもらいます(close code 1012、reason rotate)。席は15秒のあいだ残しておくので、ほかの人には、出たことも入ったことも見えません。有料のプランでvercel.jsonのmaxDurationを延ばしたときは、これも合わせて延ばせます(maxDurationより20秒ほど短くします)。
値を変える
Vercelでは、環境変数を変えても、いま動いているデプロイには反映されません。変えたあとは、Deploymentsから最新のデプロイを選び、Redeployを押します(→ Vercelに置く)。
手元で動かす
部屋のサーバーは、vercel devではWebSocketが通らないことがあるので、Vercelの上と同じNodeのサーバーを立てます。Redisはcompose.ymlで立てられます。
cd apps/vercel
npm ci
docker compose up -d redis
npm run serve # .env.local を読んで http://localhost:3000 で待つ(PORT で変えられる).env.localには、たとえば次のように書きます。
ACCESS_CONTROL_ALLOW_ORIGIN=http://localhost:4200
DISCORD_CLIENT_ID=...
DISCORD_CLIENT_SECRET=...
SESSION_SECRET=...
ADMIN_DISCORD_IDS=...
REDIS_URL=redis://localhost:6379
BLOB_LOCAL_DIR=.blobADMIN_DISCORD_IDSを書き、DiscordのアプリのRedirectsにhttp://localhost:3000/admin/login/callbackを足せば、http://localhost:3000/adminで管理画面を開けます。
BLOB_LOCAL_DIRに置くときは、1回のPUTで種類ごとの上限まで置けます(Blobへ直接上げる流れは使いません)。.envと.env.localは、Gitに入らないようにしてあります。