Featured image of post Sakura VPSにBlueskyのPDSサーバーを作ったときの備忘録

Sakura VPSにBlueskyのPDSサーバーを作ったときの備忘録

目次

背景

  • X(旧: Twitter)のマイアカウントがなぜかsuspendedになってしまった
  • おそらくAIによって自動的にsuspend状態になったと思うが、データが人質に取られた状態
  • しょうがないので、重い腰を上げてマイクロポストも自己主権型のデータ管理に移行する事に決めた
  • マストドンかBlueskyで悩んだが、Blueskyに決めた
  • BlueskyのPDSのサーバーはSakura VPSを利用する

課題

Web2の問題

X.comなどの、Web2(従来のWebサービス)には次のような問題があった。

  • アカウントについて
    • データの主権がない(あくまでプラットフォーマーの所有物)
    • UGC(User-Generated Contents)にプラットフォーマーの規制が入る
  • アカウントが凍結されると
    • 作成したデータが回収不可能になる
    • そのアカウントのIDを使ったOAuthのログインが全滅する

つまり、Web2時代はプラットフォーマーがアカウントを管理する権限があった。
故に、データやIDをプラットフォーマーが独占する課題があった。
あくまで、Web2時代のUserはプラットフォーマーの小作人状態である。

Web2 vs. Web3

  • そこで、自前のデータ管理をするDIDなどの仕組みが生まれた
  • DIDはDecentralized Identityの略であり、分散型識別子と呼ばれる
  • いわゆる自己主権型アイデンティティ(SSI: Self-sovereign Identity)の一種
  • プラットフォーマーにユーザーのデータの主権を渡さない事を意味する
Web管理形態主体性
Web2集中依存
Web3分散独立

Web2 vs. Web3

At Protocol

ActivityPub vs. AT Protocol

マイクロポストを実行するには次ActivityPub と AT Protocolがあるが、それぞれの違いは次。

特徴ActivityPubAT Protocol
目的コンテンツ共有のためのプロトコル分散型SNSエコシステム全体を支える基盤設計
分散型アイデンティティ限定的対応
データポータビリティ部分的(インスタンス間では困難な場合あり)完全対応
モデレーションインスタンスごとインスタンスごと + ユーザー個別
採用サービスマストドン、PeerTube、PixelfedなどBluesky
通信速度やや複雑(JSON-LDベース)軽量で高速

一長一短だが、結局、AT ProtocolのBlueskyを利用することに決めた。

理由:

  • マストドンよりBlueskyの方が検索が強いのが一番の理由
  • ActivityPubはW3Cなのでオフィシャル感が強いが、DIDもまたW3Cの仕様なので半オフィシャル
  • Threadsが1億人はユーザーがいるが、Blueskyも数千万人はいる
  • ActivityPubの欠点を補ったのがAT Protocolなので後者の方が新しい
  • マストドンよりはBlueskyの方がユーザーが多い

At Protocolの全体像

At Protocolの全体像

At Protocolには次のようなコンポーネントがある。

  • App View
    • ユーザーが利用するアプリケーションのUIや体験を提供する層
    • アプリ側の機能(フィード生成、検索、ランキング)
  • PDS(Personal Data Server)
    • ユーザーのデータを保存・管理し、分散型IDを提供
    • 自己主権型データ管理を可能にする
  • Client
    • ユーザーが操作するアプリ(Blueskyアプリなど)
    • App ViewやPDSと連携してデータを取得・送信する

独自ドメインを使ったハンドル名のつけ方

シンプルにハンドル名をドメイン名にしたいならDNSにTextのレコードを追加すればいい。
この場合はPDSはBlueskyのPDSを使う事になる。

1
2
_atproto.me1    TXT    did=did:plc:xxxxxxxxxxxxxxxxxxxxxxxx
_atproto.me2    TXT    did=did:plc:yyyyyyyyyyyyyyyyyyyyyyyy

microsoftの例。

1
2
$ dig _atproto.microsoft.com TXT +noall +answer
_atproto.microsoft.com. 3600    IN      TXT     "did=did:plc:fivojrvylkim4nuo3pfqcf3k"

この通り、txtレコードを追加している。

  • このドメイン認証の仕組みによって、blueskyのhandle名のDNSによる認証が可能になっている

  • また、ハンドル名とDNSが対応するため、次のように、階層構造にすることも可能になる

  • 総合ニュース news.media.example

  • 速報 breaking.media.example

PLCとは

  • PLCはPublic Ledger of Credentialsの略で、BlueskyのDIDのMethod
  • つまり、blockchainのchainのように、web上で安価なLedgerを実現している
  • これはDIDを使っているが、半中央集権的な役割を示している
  • これによって、DIDとAt protocolの名前解決が行われる

先ほどのMSのDID Documentなどは以下になる。

At Protocol

DID Document

see did:plc:fivojrvylkim4nuo3pfqcf3k

Sakura VPSのセットアップ

サーバー情報

以下のSpecでサーバーを用意した。

  • OS Ubuntu 24.04
  • 仮想2Core
  • 1GB メモリー
  • SSD 50GB

サーバーの管理ユーザー情報を設定

  • 管理ユーザー名
    • ubuntu
  • SSHキー設定
    • Githubのpubkeyを使う
    • PWのログインは許可しない

サーバーに関する設定

サーバーの初期セットアップは、Sakura VPSのstartupスクリプトを利用する。

サーバーに関する設定

スタートアップスクリプトの内容は以下などになる。

  • パッケージ更新(yum update/apt upgrade)
  • 日本語環境 ja_JP.UTF-8 への変更
  • ホスト名の設定
  • SSH ポートの変更
  • ログインユーザー名の変更
  • キーボード配列のVM内部設定をUS配列に変更
  • firewall 有効化
  • ウェブ管理インタフェース Cockpit インストール
  • IPv6 有効化
  • スワップ(swapfile)の作成
  • Snappy インストール
  • SELinux 有効化 (RHEL系のみ)
  • crashdump 有効化
  • タイムゾーンの変更

セキュリティグループ(パケットフィルター)

次のTPCポートを許可する。

  • SSH
    • Port 2222
    • from 自分のグローバルIPアドレス
  • Web
    • Port 80/443
    • from any

なお、元々の22は削除する。

接続テスト

下のssh configで接続する。

1
2
3
4
5
Host pds
    User                ubuntu
    HostName            207.71.120.209
    Port                2222
    IdentityFile        ~/.ssh/id_xxx_rsa

DNSの設定

次のような形でPDSのサーバーを指定する。

NameTypeValue TTL
xxx.netA12.34.56.78 600
*.xxx.netA12.34.56.78 600

pdsのインストール

作業スペースの作成。

1
2
$ mkdir workspace
$ cd workspace

公式ドキュメントに従いインストールする。

1
$ wget https://raw.githubusercontent.com/Bluesky-social/pds/main/installer.sh

Ubuntu 24.04にまだ対応していないので、installer.sh:90らへんを以下のように修正する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
  # Check for a supported distribution.
  SUPPORTED_OS="true"
  # if [[ "${DISTRIB_ID}" == "ubuntu" ]]; then
  #   if [[ "${DISTRIB_CODENAME}" == "focal" ]]; then
  #     SUPPORTED_OS="true"
  #     echo "* Detected supported distribution Ubuntu 20.04 LTS"
  #   elif [[ "${DISTRIB_CODENAME}" == "jammy" ]]; then
  #     SUPPORTED_OS="true"
  #     echo "* Detected supported distribution Ubuntu 22.04 LTS"
  #   elif [[ "${DISTRIB_CODENAME}" == "mantic" ]]; then
  #     SUPPORTED_OS="true"
  #     echo "* Detected supported distribution Ubuntu 23.10 LTS"
  #   fi
  # elif [[ "${DISTRIB_ID}" == "debian" ]]; then
  #   if [[ "${DISTRIB_CODENAME}" == "bullseye" ]]; then
  #     SUPPORTED_OS="true"
  #     echo "* Detected supported distribution Debian 11"
  #   elif [[ "${DISTRIB_CODENAME}" == "bookworm" ]]; then
  #     SUPPORTED_OS="true"
  #     echo "* Detected supported distribution Debian 12"
  #   fi
  # fi

インストールする

1
$ sudo bash installer.sh

次の設定をWizardに従い設定する。

  • ドメイン名
  • 管理者Emailアドレス
  • 最初のユーザー
    • ハンドルネーム
    • メールアドレス

失敗した場合は、installerに冪等性があるため、何度も実行すればいい。
成功すると、次のような画面になる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Create a PDS user account? (y/N): y
Enter an email address (e.g. alice@xxx.net): xxx@example.com
Enter a handle (e.g. alice.xxx.net): xxx.xxx.net

Account created successfully!
-----------------------------
Handle   : xxx.xxx.net
DID      : did:plc:xxx
Password : xxxxxxxxxxxxxxxxxxxxxxxxxxx
-----------------------------
Save this password, it will not be displayed again.

/pdsにファイルが生成される。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$ sudo ls -al /pds/

total 524
drwx------  4 root root   4096 Jan 31 18:00 .
drwxr-xr-x 24 root root   4096 Jan 31 17:52 ..
-rw-r--r--  1 root root 192512 Jan 31 17:59 account.sqlite
-rw-r--r--  1 root root  32768 Jan 31 18:00 account.sqlite-shm
-rw-r--r--  1 root root  70072 Jan 31 18:00 account.sqlite-wal
drwxr-xr-x  3 root root   4096 Jan 31 18:00 actors
drwxr-xr-x  4 root root   4096 Jan 31 17:52 caddy
-rw-r--r--  1 root root    889 Jan 31 17:59 compose.yaml
-rw-r--r--  1 root root  28672 Jan 31 17:59 did_cache.sqlite
-rw-r--r--  1 root root  32768 Jan 31 18:00 did_cache.sqlite-shm
-rw-r--r--  1 root root   8272 Jan 31 18:00 did_cache.sqlite-wal
-rw-r--r--  1 root root    598 Jan 31 17:59 pds.env
-rw-r--r--  1 root root  40960 Jan 31 17:59 sequencer.sqlite
-rw-r--r--  1 root root  32768 Jan 31 18:00 sequencer.sqlite-shm
-rw-r--r--  1 root root  61832 Jan 31 18:00 sequencer.sqlite-wal

Sing upされたUserのcredentialをベースにBlueskyにログインする。

ログイン方法

アカウントプロバイダーに自作のPDSのドメイン名を入れる。

アカウントプロバイダー

後は、生成されたUserのクレデンシャルでログインすればOK。

PDSの確認

PDSはSystem serviceとして起動するようになっている。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
$ sudo systemctl status pds

● pds.service - Bluesky PDS Service
     Loaded: loaded (/etc/systemd/system/pds.service; enabled; preset: enabled)
     Active: active (exited) since Fri 2025-01-31 18:00:00 JST; 9min ago
       Docs: https://github.com/Bluesky-social/pds
    Process: 5786 ExecStart=/usr/bin/docker compose --file /pds/compose.yaml up --detach (code=exited, >
   Main PID: 5786 (code=exited, status=0/SUCCESS)
        CPU: 151ms

Jan 31 18:00:00 tk2-236-27605 docker[5799]:  Container pds  Created
Jan 31 18:00:00 tk2-236-27605 docker[5799]:  Container caddy  Creating
Jan 31 18:00:00 tk2-236-27605 docker[5799]:  Container caddy  Created
Jan 31 18:00:00 tk2-236-27605 docker[5799]:  Container watchtower  Starting
Jan 31 18:00:00 tk2-236-27605 docker[5799]:  Container pds  Starting
Jan 31 18:00:00 tk2-236-27605 docker[5799]:  Container watchtower  Started
Jan 31 18:00:00 tk2-236-27605 docker[5799]:  Container pds  Started
Jan 31 18:00:00 tk2-236-27605 docker[5799]:  Container caddy  Starting
Jan 31 18:00:00 tk2-236-27605 docker[5799]:  Container caddy  Started
Jan 31 18:00:00 tk2-236-27605 systemd[1]: Finished pds.service - Bluesky PDS Service.

ヘルスチェック

1
2
$ curl https://xxx.net/xrpc/_health
{"version":"0.4.74"}

コンテナの詳細

serviceとしてdockerコンテナを立ち上げているので、dockerの法を見ればログが追える。

1
2
3
4
5
6
# docker ps
CONTAINER ID   IMAGE                            COMMAND                  CREATED       STATUS
      PORTS     NAMES
306c3a7ecf90   caddy:2                          "caddy run --config …"   8 hours ago   Up 8 hours                       caddy
da29d13d332b   ghcr.io/bluesky-social/pds:0.4   "dumb-init -- node -…"   8 hours ago   Up 8 hours                       pds
36ec198c4266   containrrr/watchtower:latest     "/watchtower"            8 hours ago   Up 8 hours (healthy)             watchtower

それぞれ以下になる。

  • caddy: HTTPS用
  • PDS: pdsの本体
  • watchtower: コンテナの自動更新用

リアルタイムのアクセスログは次で確認できる。

1
2
3
4
5
6
7
8
$ docker logs -f pds
{
  "level": 30,
  "time": 1738243213867,
  "pid": 7,
  "hostname": "xxx",
  "name": "pds",
  ....

もしくは、Serviceのログで確認する。

1
$ sudo journalctl -u pds

アカウント周りのコマンド

アカウント一覧

1
2
3
4
$ sudo pdsadmin account list
[sudo] password for ubuntu:
Handle       Email        DID
xxx.xxx.net  me@xxx.org  did:plc:xxxxxxx

アカウントの凍結と解除

1
2
$ sudo pdsadmin account takedown [did:plc:abcdef123456]
$ sudo pdsadmin account untakedown [did:plc:abcdef123456]

PWリセット

1
$ sudo pdsadmin account reset-password [did:plc:abcdef123456]

PDSのアップデート

1
$ sudo pdsadmin update

help

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$ sudo pdsadmin

pdsadmin help
--
update
  Update to the latest PDS version.
    e.g. pdsadmin update

account
  list
    List accounts
    e.g. pdsadmin account list
  create <EMAIL> <HANDLE>
    Create a new account
    e.g. pdsadmin account create alice@example.com alice.example.com
  delete <DID>
    Delete an account specified by DID.
    e.g. pdsadmin account delete did:plc:xyz123abc456
  takedown <DID>
    Takedown an account specified by DID.
    e.g. pdsadmin account takedown did:plc:xyz123abc456
  untakedown <DID>
    Remove a takedown from an account specified by DID.
    e.g. pdsadmin account untakedown did:plc:xyz123abc456
  reset-password <DID>
    Reset a password for an account specified by DID.
    e.g. pdsadmin account reset-password did:plc:xyz123abc456

request-crawl [<RELAY HOST>]
    Request a crawl from a relay host.
    e.g. pdsadmin request-crawl bsky.network

create-invite-code
  Create a new invite code.
    e.g. pdsadmin create-invite-code

help
    Display this help information.

メールアドレスの設定

  • bskyでメールの認証も行う
  • どうでもいいgmailアカウントでやるのがおすすめ
    • なお、GmailのPWではなく、App Passwordが必要なので注意
    • それの為には、Googleへの2FAが必要
    • さらに、ここからApp passwordを作れる

まず、オリジナルのpds.envをバックアップする。

1
2
3
$ sudo su - 
$ cd /pds/
$ cp pds.env pds.orig.env

その後、pds.envにSMTPの情報を追加する。

  • pds.envの注意点
    • PDS_EMAIL_FROM_ADDRESS
      • URIエンコードは不要
    • PDS_EMAIL_SMTP_URL
    • また、PDS_EMAIL_FROM_ADDRESSだけだとエラーになるので注意
    • 先に、SMTPのテストをするのがベター

1
2
PDS_EMAIL_FROM_ADDRESS=xxx@gmail.com
PDS_EMAIL_SMTP_URL="smtps://<username>:<app password>@smtp.gmail.com:465"

App PasswordをjqでURIエンコードする例。

1
2
$ echo -n "dddd aaaa bbb hoge" | jq -sRr @uri
dddd%20aaaa%20bbb%20hoge

先に、送信できるかPythonでテストする。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import smtplib
import os
import urllib.parse
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

# PDS_EMAIL_SMTP_URL の定義
PDS_EMAIL_SMTP_URL = "smtps://hogehoeg%40gmail.com:dddd%20aaaa%20bbb%20hoge@smtp.gmail.com:465"

# URLを解析
parsed_url = urllib.parse.urlparse(PDS_EMAIL_SMTP_URL)

smtp_server = parsed_url.hostname
smtp_port = parsed_url.port
username = urllib.parse.unquote(parsed_url.username)  # %40 をデコード
password = urllib.parse.unquote(parsed_url.password)  # パスワード部分

# 送信先アドレス
to_email = "recipient@example.com"  # 送信先のメールアドレス
from_email = username  # 送信元(Gmailアドレス)

# メール本文を作成
msg = MIMEMultipart()
msg["From"] = from_email
msg["To"] = to_email
msg["Subject"] = "SMTP送信テスト"

body = "このメールはPythonのSMTPテストです。"
msg.attach(MIMEText(body, "plain"))

# SMTPでメール送信
try:
    with smtplib.SMTP_SSL(smtp_server, smtp_port) as server:  # SSL接続
        server.login(username, password)  # 認証
        server.sendmail(from_email, to_email, msg.as_string())  # 送信
    print("メール送信成功!")
except Exception as e:
    print("メール送信エラー:", e)

最終的にpds.envを更新したらリスタートする。

1
$ sudo systemctl restart pds

トラブルシューティング

メールでエラーがでる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Error: Partial email config, must set both emailFromAddress and emailSmtpUrl
    at envToCfg (/app/node_modules/.pnpm/@atproto+pds@0.4.74/node_modules/@atproto/pds/src/config/config.ts:146:13)
    at main (/app/index.js:14:15)
    at Object.<anonymous> (/app/index.js:72:1)
    at Module._compile (node:internal/modules/cjs/loader:1376:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Module._load (node:internal/modules/cjs/loader:1023:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
    at node:internal/main/run_main_module:28:49

=> PDS_EMAIL_FROM_ADDRESSを設定していないのが原因だった。

まとめ

  • 言論の自由は大事

参考文献

Built with Hugo
テーマ StackJimmy によって設計されています。