Bluesky PDSを自宅サーバーで建てようとする過程
Blueskyは、つぶやきを自前のサーバーに保持する PDS(Personal Data Server) を公開 している。PDSのセットアップに関する情報は以下の記事が詳しい。
2024年2月にPDSをBlueskyのソーシャルネットワークに参加させるフェデレーション機能が公開された。
マイナビの記事では以前流行ったMastodonとの比較が詳しく、ネットワークの健全性や運用に配慮した設計になっている点において安心感がある。
PDSのREADMEを読んでみよう。
リポジトリのAboutに Bluesky PDS (Personal Data Server) container image, compose file, and documentation
とあるように、DockerイメージとDocker Composeが置いてあり、コードはTypescriptの @atproto/pds にある。
インストールスクリプト で手軽に試せるようになっている一方で、スクリプト自体はDocker周辺のセットアップを行っているだけなので、専用のLinux VMを用意しなくてもDockerの実行環境があればいけそうな雰囲気を感じとった。ちなみにx86_64だけではなくarm64もサポートしているのがポイント高い。
手元のMacbookにLimaが入っていたので、サポートするUbuntu 22.04のVMを立ち上げてセットアップしてみよう。
$ limactl start ubuntu-lts.yaml
? Creating an instance "ubuntu-lts" [Use arrows to move, type to filter]
> Proceed with the current configuration
Open an editor to review or modify the current configuration
Choose another template (docker, podman, archlinux, fedora, ...)
Exit
[Enter押下]
INFO[0092] Starting the instance "ubuntu-lts" with VM driver "qemu"
:
$ limactl shell ubuntu-lts
$ cd
$ wget https://raw.githubusercontent.com/bluesky-social/pds/main/installer.sh
$ sudo bash ./installer.sh
* Detected supported distribution Ubuntu 22.04 LTS
curl: (7) Failed to connect to 169.254.169.254 port 80 after 1 ms: Network is unreachable
---------------------------------------
Add DNS Record for Public IP
---------------------------------------
From your DNS provider's control panel, create the required
DNS record with the value of your server's public IP address.
+ Any DNS name that can be resolved on the public internet will work.
+ Replace example.com below with any valid domain name you control.
+ A TTL of 600 seconds (10 minutes) is recommended.
Example DNS record:
NAME TYPE VALUE
---- ---- -----
example.com A Server's IP
*.example.com A Server's IP
**IMPORTANT**
It's recommended to wait 3-5 minutes after creating a new DNS record
before attempting to use it. This will allow time for the DNS record
to be fully updated.
Enter your public DNS address (e.g. example.com): pds.example.com
Enter an admin email address (e.g. you@example.com): bluesky@example.com
: (中略)
========================================================================
PDS installation successful!
------------------------------------------------------------------------
Check service status : sudo systemctl status pds
Watch service logs : sudo docker logs -f pds
Backup service data : /pds
PDS Admin command : pdsadmin
Required Firewall Ports
------------------------------------------------------------------------
Service Direction Port Protocol Source
------- --------- ---- -------- ----------------------
HTTP TLS verification Inbound 80 TCP Any
HTTP Control Panel Inbound 443 TCP Any
Required DNS entries
------------------------------------------------------------------------
Name Type Value
------- --------- ---------------
pds.takipone.com A Server's IP
*.pds.takipone.com A Server's IP
Detected public IP of this server: Server's IP
To see pdsadmin commands, run "pdsadmin help"
========================================================================
Create a PDS user account? (y/N):
[アカウント登録は今回はやらないのでEnter]
$
これでコンテナが立ち上がる
$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
60b03f3e270f caddy:2 "caddy run --config …" 2 minutes ago Up 2 minutes caddy
1601e08e12d8 containrrr/watchtower:latest "/watchtower" 2 minutes ago Up 2 minutes (healthy) watchtower
f2e75f8e83dc ghcr.io/bluesky-social/pds:0.4 "dumb-init -- node -…" 2 minutes ago Up 2 minutes pds
関連ファイルは /pds
に配置され、Docker周りの構成は /pds/compose.yaml
にある。 /pds
はrootにのみアクセス権があるため、 探索したいときはrootユーザーに su
か sudo -i
でスイッチするのが楽。
$ sudo -i
# cd /pds
# ls
account.sqlite caddy did_cache.sqlite-shm sequencer.sqlite
account.sqlite-shm compose.yaml did_cache.sqlite-wal sequencer.sqlite-shm
account.sqlite-wal did_cache.sqlite pds.env sequencer.sqlite-wal
# apt install tree
# tree caddy/
caddy/
├── data
│ └── caddy
│ ├── last_clean.json
│ └── locks
└── etc
└── caddy
└── Caddyfile
install.sh
と置いてあるファイルでだいたい構成が見えてくる。
version: '3.9'
services:
caddy:
container_name: caddy
image: caddy:2
network_mode: host
depends_on:
- pds
restart: unless-stopped
volumes:
- type: bind
source: /pds/caddy/data
target: /data
- type: bind
source: /pds/caddy/etc/caddy
target: /etc/caddy
pds:
container_name: pds
image: ghcr.io/bluesky-social/pds:0.4
network_mode: host
restart: unless-stopped
volumes:
- type: bind
source: /pds
target: /pds
env_file:
- /pds/pds.env
watchtower:
container_name: watchtower
image: containrrr/watchtower:latest
network_mode: host
volumes:
- type: bind
source: /var/run/docker.sock
target: /var/run/docker.sock
restart: unless-stopped
environment:
WATCHTOWER_CLEANUP: true
WATCHTOWER_SCHEDULE: "@midnight"
#
{
email bluesky@example.com
on_demand_tls {
ask http://localhost:3000/tls-check
}
}
*.pds.example.com, pds.example.com {
tls {
on_demand
}
reverse_proxy http://localhost:3000
}
インストール時に入力したadmin mail addressはCaddyのOn-Demand TLSで利用される模様。デフォルトではLet's EncryptとZeroSSLのアカウントとして利用するのかな。
PDS_HOSTNAME=pds.example.com
PDS_JWT_SECRET=b046edf971adb584cc1b8c3919f81667
PDS_ADMIN_PASSWORD=3c1ad73f77fc6d2c08b011acfae2f334
PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=aca1094cce7a6a627bf2650c69ee792ca85ebfbabbb65e2aa5b538db1ada98cb
PDS_DATA_DIRECTORY=/pds
PDS_BLOBSTORE_DISK_LOCATION=/pds/blocks
PDS_DID_PLC_URL=https://plc.directory
PDS_BSKY_APP_VIEW_URL=https://api.bsky.app
PDS_BSKY_APP_VIEW_DID=did:web:api.bsky.app
PDS_REPORT_SERVICE_URL=https://mod.bsky.app
PDS_REPORT_SERVICE_DID=did:plc:ar7c4by46qjdydhdevvrndac
PDS_CRAWLERS=https://bsky.network
LOG_ENABLED=true
シークレットやパスワードのアルゴリズムは install.sh
にある。
14 # Secure generator comands
15 GENERATE_SECURE_SECRET_CMD="openssl rand --hex 16"
16 GENERATE_K256_PRIVATE_KEY_CMD="openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32"
:
328 PDS_ADMIN_PASSWORD=$(eval "${GENERATE_SECURE_SECRET_CMD}")
:
331 PDS_JWT_SECRET=$(eval "${GENERATE_SECURE_SECRET_CMD}")
332 PDS_ADMIN_PASSWORD=${PDS_ADMIN_PASSWORD}
333 PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=$(eval "${GENERATE_K256_PRIVATE_KEY_CMD}")
以下のBrume 2の既存構成にpdsコンテナを追加することにした。
pds:
image: ghcr.io/bluesky-social/pds:0.4
network_mode: host
restart: unless-stopped
volumes:
- type: bind
source: /opt/example-com/pds
target: /pds
env_file:
- /opt/example-com/pds/pds.env
Nginxはブログ記事を参考にしつつ、以下を設定
- 既存のGhostへのリバースプロキシと共存させるため
/xrpc/
と/.well-known/
のみPDSのhttp://localhost:3000/
に向ける。 -
/xrpc/
はWebsocket向けにパラメータを調整する
http {
:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
:
location /xrpc/ {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://localhost:3000;
proxy_send_timeout 3600;
proxy_read_timeout 3600;
}
location /.well-known/ {
proxy_pass http://localhost:3000;
}
}
}
Brume 2(OpenWRT)はutil-linuxのコマンドが足りず、bashやjqなどを追加でインストールしたけど column
コマンドが見つからず pdsadmin コマンドがエラーになる。一方で本コマンドは同一ホストで実行する必要はなく、PDSの認証情報があれば任意のホストから実行できることに気づいたので、Ubuntu 22.04マシンのpds.envをBrume 2のものに差し替えて代替できた。
CloudFront、ACMはPDSのドメインとサブドメインのワイルドカードをCloudFrontディストリビューションのCNAMESとACM証明書のSANsにセットした。
CloudFrontは既定でWebsocketをプロキシできるので、Websocketに関する特段の対応はなし。
ビヘイビアはこんな感じで様子を見ている。
Tailscale FunnelのHTTPS終端のためにオリジンリクエストポリシーでHostヘッダを除外しているが、今のところPDSでホスト名を見るような処理はしていないようなので大丈夫そう。
一通り準備ができたらPDS AdminのDiscordの requests チャンネルにPDSのドメインを登録するとBlueskyのボットがクロールしに来てネットワークに参加できる。
あとはPDSのREADMEの通り、pdsadminで招待コードを発行してアカウント発行を進めればOK。ホスティングプロバイダーにPDSのドメインを指定する。
登録直後はプロフィールの挙動が不安定なので、Blueskyでハンドルを独自ドメインに設定するのと同様、[ハンドルを変更]からハンドル名を入力して[No DNS Panel]を選択、[Verify Text File]ボタンをクリックして検証が成功すれば安定した。
今回試した限りでは、表示される /.well-known/
は 400 User not found
というレスポンスで動いていないっぽい。ログを見る限りではresolveHandle APIにフォールバックして、そちらで検証が成功しているようだった。フォールバックについては、AT Protocolで詳しい仕様が定義されている。
最初に作成したアカウントが、アカウント作成時のなんらか初期設定に失敗したようで、getProfile APIが 400 User not found
になってしまったため、別のアカウントを作り直して回避できた。
一方で、一度作ったアカウントはPLC DID Directoryに登録されると pdsadmin account delete
でPDS上で削除しても残ってしまう模様。 takedown
で無効化するのが正しい手順だったのかもしれない。誰か消し方教えてください。↓にヒントがありそうな気はしている。