a step by step process into an appview
or, at least, what i had to do to spin up bsky, from a fresh clone towards a smol appview that you can Do Things With
you will need
- linux server
- linux shell skills
- postgresql install
- docker (but podman should work)
- a wildcard certificate for your domain, say
*.asky.ln4.net
- certbot dns-01
- a wildcard certificate for the users of your domain, say
*.pds.asky.ln4.net
- you could go without wildcard certs but it gets much annoying
- if you're spinning up the system outside of internet access, you're able to still use certbot. spin a small vps, run certbot on it, then extract the certificate out to your deployment
plc #
- everything depends on the plc to function, so it must be first
git clone https://github.com/did-method-plc/did-method-plc
- setup plc. you can't use their ghcr images as they're locked behind auth
docker build -t bsky-plc . -f packages/server/Dockerfile
- configure a postgres. not going into too much detail
- NOTE: if you've used
yarn run start
, the env var for the database is actually a json rather than database uri - expose as
pds.asky.ln4.net
here's my docker-compose file as a reference
services: plc: image: bsky-plc:f96c898 network_mode: "host" environment: - 'DB_CREDS_JSON={"username": "bsky_plc", "password": "...", "port": "5432", "host": "localhost", "database": "bsky_plc"}' - 'DB_MIGRATE_CREDS_JSON={"username": "bsky_plc", "password": "...", "port": "5432", "host": "localhost", "database": "bsky_plc"}'' - "ENABLE_MIGRATIONS=true" - "PORT=32990" - "DEBUG_MODE=1" - "LOG_ENABLED=true" - "LOG_LEVEL=debug" - "LOG_DESTINATION=1"
relay #
- relay depends on plc for things like scraping new plcs
- everything else builds on top of it, it is core atproto infrastructure
- this depends on postgresql
- notes from https://whtwnd.com/bnewbold.net/entries/Notes%20on%20Running%20a%20Full-Network%20atproto%20Relay%20(July%202024)
git clone https://github.com/bluesky-social/indigo
docker build -t bsky-relay . -f cmd/bigsky/Dockerfile
- create an
asky
user on your favorite postgres instance
CREATE DATABASE bgs; CREATE DATABASE carstore;CREATE USER bsky_relay WITH PASSWORD 'AAAAAAAAAAAAAA';
GRANT ALL PRIVILEGES ON DATABASE bgs TO bsky_relay;
GRANT ALL PRIVILEGES ON DATABASE carstore TO bsky_relay;-- these are needed for newer versions of postgres
</span>c bgs postgres
GRANT ALL ON SCHEMA public TO bsky_relay;</span>c carstore postgres
GRANT ALL ON SCHEMA public TO bsky_relay;
services: relay: image: bsky-relay:a71f3c4d network_mode: "host" volumes: - "/opt/bsky_relay/data_bigsky:/data_bigsky" - "/opt/bsky_relay/data_bigsky_events:/data_bigsky_events" environment: - "ENVIRONMENT=production" - "DATABASE_URL=postgres://bsky_relay:AAAAAAAAAAAAAA@localhost:5432/bgs" - "CARSTORE_DATABASE_URL=postgres://bsky_relay:AAAAAAAAAAAAAA@localhost:5432/bgs_carstore" - "DATA_DIR=/data_bigsky" - "RELAY_PERSISTER_DIR=/data_bigsky_events" - "GOLOG_LOG_LEVEL=debug" - "RESOLVE_ADDRESS=1.1.1.1:53" - "FORCE_DNS_UDP=true" - "RELAY_COMPACT_INTERVAL=0" - "RELAY_DEFAULT_REPO_LIMIT=500000" - "MAX_CARSTORE_CONNECTIONS=12" - "MAX_METADB_CONNECTIONS=12" - "MAX_FETCH_CONCURRENCY=25" - "RELAY_CONCURRENCY_PER_PDS=20" - "RELAY_MAX_QUEUE_PER_PDS=200" - "RELAY_ADMIN_KEY=7cd50598e8ba49458883a3703d41e" - "ATP_PLC_HOST=https://plc.asky.ln4.net" - "RELAY_SPIDERING=true"
pds #
- we can put everyone in a single pds
- in https://bsky.app, that's https://bsky.social (as entryway, but i won't setup via entryway)
- i have my own setup based on compose, so i won't follow the upstream pds script at all. here's my dockerfile and docker-compose
FROM ghcr.io/bluesky-social/pds:0.4.67 RUN apk --no-cache add ca-certificates RUN update-ca-certificates -f # dirty patching is required as for my setups its either localhost 127.0.0.1 # or some other private ip range (like 100.x for tailscale, 172.x for the docker host machine ip, etc). # for some reason bluesky does explicit checks and breaks if any hostname resolves to these ips. im sure there's a reason internal to them but it's so. absurdly. annoying. patch all the checks for now RUN sed -i "s/ip.range() === 'unicast'/true/" /app/node_modules/.pnpm/@atproto-labs+fetch-node@0.1.3/node_modules/@atproto-labs/fetch-node/src/util.ts RUN sed -i "s/ip.range() !== 'unicast'/false/" /app/node_modules/.pnpm/@atproto-labs+fetch-node@0.1.3/node_modules/@atproto-labs/fetch-node/src/unicast.ts RUN sed -i "s/ip.range() === 'unicast'/true/" /app/node_modules/.pnpm/@atproto-labs+fetch-node@0.1.3/node_modules/@atproto-labs/fetch-node/dist/util.js RUN sed -i "s/ip.range() !== 'unicast'/false/" /app/node_modules/.pnpm/@atproto-labs+fetch-node@0.1.3/node_modules/@atproto-labs/fetch-node/dist/unicast.js
services: pds: image: ghcr.io/bluesky-social/pds:0.4.66 network_mode: "host" volumes: - "/opt/pds/data:/opt/pds/data" environment: - "PDS_DATA_DIRECTORY=/opt/pds/data" - "PDS_BLOBSTORE_DISK_LOCATION=/opt/pds/data/blobs" - "PDS_HOSTNAME=pds.asky.ln4.net" - "PDS_PORT=6877" - "PDS_BLOB_UPLOAD_LIMIT=52428800" - "PDS_DID_PLC_URL=https://plc.asky.ln4.net" - "PDS_BSKY_APP_VIEW_URL=https://appview.asky.ln4.net" - "PDS_BSKY_APP_VIEW_DID=did:web:appview.asky.ln4.net" - "PDS_REPORT_SERVICE_URL=https://mod.asky.ln4.net" - "PDS_REPORT_SERVICE_DID=did:web:mod.asky.ln4.net" - "PDS_CRAWLERS=https://relay.asky.ln4.net" - "LOG_ENABLED=true" - "PDS_JWT_SECRET=[redacted]" - "PDS_ADMIN_PASSWORD=[redacted]" - "PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=[redacted]"
appview #
- the bluesky appview is composed of 3 parts
- dataplane
- has a "reference implementation" we'll use that uses postgresql
- the bluesky PBLLC one is (at the moment) closed source but scales better via scylladb, more info on the index
- image proxy
- proxies PDSes images
- appview itself
- dataplane
- i deploy each separately for maximum understanding of how it glues together
- in https://bsky.app, that's https://public.api.bsky.app and https://api.bsky.app
- more information on what an appview entails: https://github.com/bluesky-social/atproto/discussions/2961
appview (dataplane) #
- dataplane isnt actually exposed on the bluesky codebase, requiring a wrapper script.
- https://github.com/lun-4/atproto/blob/luna.gluesky/luna/dataplane/src/bin.ts
- TODO: i had to fiddle with how folders are structured so that the build actually works. need to push that somewhere
- TODO: FUCK HTTP2 SO MUCH. i hate it to bits. you need http2 for dataplane.
services: dataplane: image: bsky-appview-dataplane:7b40155f0 network_mode: "host" environment: - "ENVIRONMENT=production" - "POSTGRES_URL=postgres://bsky_dataplane:fbc4751b9eff4426f4eeea77a24aa2579ec6ad94deca936613a84d9@localhost:5432/bsky_dataplane" - "POSTGRES_SCHEMA=public" - "POSTGRES_POOL_SIZE=10" - "NODE_ENV=development" - "LOG_ENABLED=true" - "LOG_LEVEL=info" - "NODE_OPTIONS=--use-openssl-ca" - "DID_PLC_URL=https://plc.asky.ln4.net" - "RELAY_WEBSOCKET_URL=wss://relay.asky.ln4.net"
appview (image) #
- image "cdn" isnt exposed either, requires wrapper
- https://github.com/lun-4/atproto/blob/luna.gluesky/luna/image/src/bin.ts
- TODO: i had to fiddle with how folders are structured so that the build actually works. need to push that somewhere
services: image: image: bsky-appview-image:7b40155f0 network_mode: "host" environment: - "PORT=9036" - "NODE_ENV=development" - "LOG_ENABLED=true" - "LOG_LEVEL=info" - "NODE_OPTIONS=--use-openssl-ca" - "APPVIEW_URL=https://appview.asky.ln4.net"
appview (appview!) #
- create an account on the pds via goat
- use that account in
BSKY_SERVER_DID
and you can also use it onMOD_SERVICE_DID
build:
docker build -t "bsky-appview-server:7b40155f0" . -f services/bsky/Dockerfile
run:
services: image: image: bsky-appview-server:7b40155f0 network_mode: "host" environment: - "NODE_ENV=development" - "LOG_ENABLED=true" - "LOG_LEVEL=info" - "NODE_OPTIONS=--use-openssl-ca" - "BSKY_PORT=2584" - "PUBLIC_URL=https://appview.asky.ln4.net" - "BSKY_DID_PLC_URL=https://plc.asky.ln4.net" - "BSKY_SERVER_DID=did:web:appview.asky.ln4.net" - "BSKY_BSYNC_URL=http://localhost:39999" - "BSKY_BLOB_RATE_LIMIT_BYPASS_KEY=asdf.com" - "BSKY_BLOB_RATE_LIMIT_BYPASS_HOSTNAME=asdf.com" - "MOD_SERVICE_DID=did:web:appview.asky.ln4.net" - "BSKY_DATAPLANE_URLS=https://dataplane.asky.ln4.net" - "BSKY_INDEXED_AT_EPOCH=2024-12-21T00:00:00Z" - "BSKY_SERVICE_SIGNING_KEY=cdfeb707e884deb9b8d462911c31efdd029a07c5e39f5793838f13109a88509e" - "BSKY_CDN_URL=https://image.asky.ln4.net" - "BSKY_VIDEO_PLAYLIST_URL_PATTERN=https://video.asky.ln4.net/watch/%s/%s/playlist.m3u8" - "BSKY_VIDEO_THUMBNAIL_URL_PATTERN=https://video.asky.ln4.net/watch/%s/%s/thumbnail.jpg"
social-app #
- the frontend you load on https://bsky.app
- it talks to appview
- currently this requires manual patching, which sucks a looot. i don't think they'd support a PR to make it configurable, so fork it is.
- git repo: https://github.com/appview-wg-bsky/social-app/tree/configurable-constants
build:
#!/bin/sh set -eux cmt=$(git rev-parse --short HEAD) docker build -t bsky-social-app:${cmt} \ --build-arg EXPO_PUBLIC_STAGING_SERVICE=https://staging.asky.ln4.net \ --build-arg EXPO_PUBLIC_BSKY_SERVICE=https://pds.asky.ln4.net \ --build-arg EXPO_PUBLIC_PUBLIC_BSKY_SERVICE=https://pds.asky.ln4.net \ --build-arg EXPO_PUBLIC_DEFAULT_FEED=at://did:plc:cx5biu5a5jp46o4pvnl2ngtx/app.bsky.feed.generator/%s \ --build-arg EXPO_PUBLIC_DISCOVER_FEED_URI=at://did:plc:cx5biu5a5jp46o4pvnl2ngtx/app.bsky.feed.generator/whats-hot \ --build-arg EXPO_PUBLIC_BSKY_APP_ACCOUNT_DID=did:plc:cx5biu5a5jp46o4pvnl2ngtx \ --build-arg EXPO_PUBLIC_BSKY_CHAT_DID_REFERENCE=did:web:chat.asky.ln4.net#bsky_chat \ # check DMs section on this!!!! -f Dockerfile .
run:
services: image: image: bsky-social-app:6570f56d8 command: ['/usr/bin/bskyweb', 'serve'] network_mode: "host" environment: - "ATP_APPVIEW_HOST=https://appview.asky.ln4.net" - "HTTP_ADDRESS=:8100"
troubleshooting?? #
- this is the step where you got enough of your setup to actually make an account that can do things... in theory.
- you may need to fix issues with your setup and at the worst case, drop following databases
- plc
- bgs / relay
- appview dataplane
here's a quick curl to get invite codes to create your account. you can use your deployed frontend to create them
curl --request POST \
--user "admin:<admin token>" \
--header "Content-Type: application/json" \
--data '{"useCount": 1}' \
"https://pds.asky.ln4.net/xrpc/com.atproto.server.createInviteCode"
feedgen #
- from talking with others, it seems appview already provides some basic feedgen services.
- however, i don't see any of that on my social-app installation. its likely i did something wrong with it
- either way, you may want to spin up your own feedgens for shenanigans. good thing is that external feedgens are already a part of the protocol and somewhat easy to spin up
- in https://bsky.app, that's feed generators
i made extragraph to make scriptable lua feedgens on upstream bluesky, but it also has functionality to provide a Following and a Discover feed on its own as well.
you can directly build it via docker build
, then
services: extragraph: image: extragraph:0.0.17 network_mode: "host" restart: on-failure volumes: - "/opt/extragraph/data:/data:z" environment: - "PORT=9032" - "FOLLOWING_FEED_ENABLE=1" - "FOLLOWING_FEED_NAME=following" - "FOLLOWING_FEED_DATABASE_PATH=/data/following_feed_state.db" - "DISCOVER_FEED_NAME=whats-hot" - "FEED_ACTOR_DID=did:plc:cx5biu5a5jp46o4pvnl2ngtx" - "SERVICE_ENDPOINT=https://feedgen.asky.ln4.net" - "ATPROTO_PLC_URL=https://plc.asky.ln4.net" - "RELAY_WEBSOCKET_ADDRESS=wss://relay.asky.ln4.net" - "APPVIEW_URL=https://appview.asky.ln4.net"
make sure you have an account on that did:plc! i created one manually and dubbed it "the asky service account". i followed extragraph's own instructions on using npm publishFeed
to create the feedgen on the service account. pdsls can be patched so it works on the local setup but that's left as an exercise to the reader (my pdsls is too old and im too lazy to update my patches atm lol)
from this point onwards, the next services are "goodies". they're effectively optional unless you want them.
DMs #
- in bluesky, DMs are a centralized service under
did:web:api.bsky.chat
, this is hardcoded in social-app. - i've made sasayaki which is an unofficial implementation of the service. provides just enough to make chat happen between two users.
docker build -t sasayaki:0.0.1 .
and then
services: sasayaki: image: sasayaki:0.0.1 network_mode: "host" restart: on-failure volumes: - "/opt/sasayaki/data:/data" environment: - "PORT=43091" - "APPVIEW_URL=https://appview.asky.ln4.net" - "ATPROTO_PLC_URL=https://plc.asky.ln4.net" - "SERVER_URL=chat.asky.ln4.net" - "DB_PATH=/data/data.db"
videos #
- in bluesky, videos are centralized service under https://video.bsky.app (not a did:web! though
did:web:video.bsky.app
is the did used for the upload api) - it works via creating a service token with that did:web and then calling the uploadVideo XRPC (see
lxm
field, as that's what pds uses to ensure that the video service can only call uploadBlob and nothing else)
{ "iat": 6666666, "iss": "did:plc:itsmemario", "aud": "did:web:pds.asky.ln4.net", "exp": 9999999999999, "lxm": "com.atproto.repo.uploadBlob", "jti": "something here" }
- i made douga that implements the bare minimum needed for video upload and playback. instructions on the README
ozone #
- not required, but
- in https://bsky.app, that's moderators
jetstream #
- a more friendly relay, https://docs.bsky.app/blog/jetstream
- more and more applications are requested to migrate to jetstream for lexicon filtering
- even more information regarding scaling the current bsky.app relay: https://github.com/bluesky-social/atproto/discussions/3036