背景
- 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 | 分散 | 独立 |

At Protocol
ActivityPub vs. AT Protocol
マイクロポストを実行するには次ActivityPub と AT Protocolがあるが、それぞれの違いは次。
特徴 | ActivityPub | AT 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には次のようなコンポーネントがある。
- 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などは以下になる。


see did:plc:fivojrvylkim4nuo3pfqcf3k
Sakura VPSのセットアップ
サーバー情報
以下のSpecでサーバーを用意した。
- OS Ubuntu 24.04
- 仮想2Core
- 1GB メモリー
- SSD 50GB
サーバーの管理ユーザー情報を設定
- 管理ユーザー名
- 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
なお、元々の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のサーバーを指定する。
Name | Type | Value TTL |
---|
xxx.net | A | 12.34.56.78 600 |
*.xxx.net | A | 12.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に従い設定する。
失敗した場合は、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のアップデート
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
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
を設定していないのが原因だった。
まとめ
参考文献