ai/at
1
0
This commit is contained in:
2024-10-18 22:49:50 +09:00
parent bce11adb82
commit c9612b87a7
34 changed files with 1942 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
repos
.claude

View File

@@ -0,0 +1,48 @@
# at
- https://github.com/bluesky-social/atproto
- https://github.com/bluesky-social/atproto/discussions/2026
|word|name|example|
|---|---|---|
|at|uri|at://example.com|
|@|user|@example.com|
|[at]proto|repo|`git@github.com:bluesky-social/atproto`|
|[at]mosphere|system|pds, bsky(appview), ozone, bgs, plc|
|[a]uthenticated [t]ransfer|protocol|[did](https://www.w3.org/TR/did-core/)|
- https://atproto.com/ja/guides/glossary
## account
- [ai@syu.is](https://syu.is/profile/did:plc:6qyecktefllvenje24fcxnie)
- [ai@bsky.app](https://bsky.app/profile/did:plc:6qyecktefllvenje24fcxnie)
- https://plc.syu.is/did:plc:6qyecktefllvenje24fcxnie
- https://plc.directory/did:plc:6qyecktefllvenje24fcxnie
```sh
$ curl -sL syu.is/xrpc/_health
# latest
# https://github.com/bluesky-social/atproto/blob/main/packages/pds/package.json
$ curl -sL https://raw.githubusercontent.com/bluesky-social/atproto/refs/heads/main/packages/pds/package.json |jq -r .version
```
```sh
$ handle=ai.syui.ai
$ curl -sL "syu.is/xrpc/com.atproto.repo.describeRepo?repo=${handle}" |jq -r .did
did:plc:6qyecktefllvenje24fcxnie
$ curl -sL "syu.is/xrpc/com.atproto.repo.listRecords?repo=${handle}&collection=app.bsky.feed.post&reverse=true&limit=1"
{"records":[{"uri":"at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.post/3l6s2riuouk2j","cid":"bafyreibjohl7va4upkibw5twaxdd4jg3l6rmfatu4dpjjfd5xkb2ijtlx4","value":{"text":"hello","$type":"app.bsky.feed.post","langs":["ja"],"createdAt":"2024-10-18T13:21:39.809Z"}}],"cursor":"3l6s2riuouk2j"}
```
## feed
> at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/cmd
- https://api.bsky.app/xrpc/app.bsky.feed.getFeed?feed=at://did:plc:4hqjfn7m6n5hno3doamuhgef/app.bsky.feed.generator/cmd
- https://feed.syu.is/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:plc:4hqjfn7m6n5hno3doamuhgef/app.bsky.feed.generator/cmd
- https://desc.syu.is/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/cmd

139
compose.yml Normal file
View File

@@ -0,0 +1,139 @@
services:
database:
image: postgres:16-alpine
restart: always
env_file:
- ./envs/postgres
volumes:
- ./configs/postgres/init/:/docker-entrypoint-initdb.d/
- ./data/postgres/:/var/lib/postgresql/data/
healthcheck:
test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"
interval: 5s
retries: 20
redis:
image: redis:alpine
restart: always
volumes:
- ./data/redis/:/data/
healthcheck:
test: ["CMD", "redis-cli", "ping", "|", "grep", "PONG"]
interval: 1s
timeout: 5s
retries: 5
plc:
ports:
- 2582:3000
build:
context: ./repos/did-method-plc/
dockerfile: packages/server/Dockerfile
restart: always
env_file:
- ./envs/plc
depends_on:
database:
condition: service_healthy
pds:
ports:
- 2583:3000
build:
context: ./repos/atproto/
dockerfile: services/pds/Dockerfile
restart: always
env_file:
- ./envs/pds
volumes:
- ./data/pds/:/data/
command: node --enable-source-maps index.js
depends_on:
database:
condition: service_healthy
bsky:
ports:
- 2584:2584
build:
context: ./repos/atproto/
dockerfile: services/bsky/Dockerfile
restart: always
env_file:
- ./envs/bsky
user: root
volumes:
- ./data/bsky/:/data/
command: node --enable-source-maps api.js
depends_on:
database:
condition: service_healthy
redis:
condition: service_healthy
bgs:
ports:
- 2470:2470
build:
context: ./repos/indigo/
dockerfile: cmd/bigsky/Dockerfile
restart: always
env_file:
- ./envs/bgs
volumes:
- ./data/bgs/:/data/
depends_on:
database:
condition: service_healthy
social-app:
ports:
- 8100:8100
build:
context: ./repos/social-app/
dockerfile: Dockerfile
restart: always
env_file:
- ./envs/social-app
command: "/usr/bin/bskyweb serve"
jetstream:
build:
context: ./repos/jetstream/
dockerfile: Dockerfile
ports:
- 6008:6008
volumes:
- ./data/jetstream:/data
restart: always
env_file:
- ./envs/jetstream
ozone-web:
build:
context: ./repos/ozone/
ports:
- 2586:3000
restart: always
volumes:
- ./data/ozone/:/data/
env_file:
- ./envs/ozone
depends_on:
database:
condition: service_healthy
ozone:
build:
context: ./repos/atproto/
dockerfile: services/ozone/Dockerfile
ports:
- 2585:3000
restart: always
command: node --enable-source-maps api.js
volumes:
- ./data/ozone/:/data/
env_file:
- ./envs/ozone

View File

@@ -0,0 +1,35 @@
-- PLC
CREATE DATABASE plc;
GRANT ALL PRIVILEGES ON DATABASE plc TO postgres;
-- BGS
CREATE DATABASE bgs;
CREATE DATABASE carstore;
GRANT ALL PRIVILEGES ON DATABASE bgs TO postgres;
GRANT ALL PRIVILEGES ON DATABASE carstore TO postgres;
-- bsky(AppView)
--CREATE DATABASE appview;
--GRANT ALL PRIVILEGES ON DATABASE appview TO postgres;
CREATE DATABASE bsky;
GRANT ALL PRIVILEGES ON DATABASE bsky TO postgres;
-- ozone(Moderation)
--CREATE DATABASE moderation;
--GRANT ALL PRIVILEGES ON DATABASE moderation TO postgres;
CREATE DATABASE ozone;
GRANT ALL PRIVILEGES ON DATABASE ozone TO postgres;
-- search(palomar)
CREATE DATABASE search;
GRANT ALL PRIVILEGES ON DATABASE search TO postgres;
--CREATE DATABASE palomar;
--GRANT ALL PRIVILEGES ON DATABASE palomar TO postgres;
-- PDS
CREATE DATABASE pds;
GRANT ALL PRIVILEGES ON DATABASE pds TO postgres;
-- BSYNC
CREATE DATABASE bsync;
GRANT ALL PRIVILEGES ON DATABASE bsync TO postgres;

6
envs/bgs Normal file
View File

@@ -0,0 +1,6 @@
DATABASE_URL=postgres://postgres:postgres@database/bgs
CARSTORE_DATABASE_URL=postgres://postgres:postgres@database/carstore
DATA_DIR=/data
ATP_PLC_HOST=https://plc.${host}
BGS_NEW_PDS_PER_DAY_LIMIT=1000
BGS_ADMIN_KEY=

19
envs/bsky Normal file
View File

@@ -0,0 +1,19 @@
BSKY_PORT=2584
BSKY_BLOB_CACHE_LOC=/data/
BSKY_BSYNC_HTTP_VERSION=1.1
BSKY_BSYNC_PORT=3002
BSKY_BSYNC_URL=http://bsky:3002
BSKY_COURIER_URL=http://fake-courier.example.invalid/
BSKY_DATAPLANE_HTTP_VERSION=1.1
BSKY_DATAPLANE_PORT=3001
BSKY_DATAPLANE_URLS=http://bsky:3001
BSKY_DB_POSTGRES_URL=postgres://postgres:postgres@database/bsky
BSKY_DID_PLC_URL=https://plc.${host}
BSKY_PUBLIC_URL=https://bsky.${host}
BSKY_REPO_PROVIDER=wss://bgs.${host}
BSKY_SERVER_DID=did:web:bsky.${host}
MOD_SERVICE_DID=did:web:ozone.${host}
#BSKY_IMG_URI_ENDPOINT=https://bsky.${host}/img
BSKY_ADMIN_PASSWORDS
BSKY_SERVICE_SIGNING_KEY

5
envs/jetstream Normal file
View File

@@ -0,0 +1,5 @@
JETSTREAM_WS_URL=wss://bgs.${host}/xrpc/com.atproto.sync.subscribeRepos
JETSTREAM_DATA_DIR=/data
JETSTREAM_LISTEN_ADDR=:6008
JETSTREAM_METRICS_LISTEN_ADDR=:6009
JETSTREAM_LIVENESS_TTL=96h

29
envs/ozone Normal file
View File

@@ -0,0 +1,29 @@
OZONE_SERVER_DID=did:web:ozone.${host}
OZONE_PUBLIC_URL=https://ozone.${host}
OZONE_ADMIN_HANDLE=${user}.${host}
OZONE_MODERATOR_DIDS=${did}
OZONE_ADMIN_DIDS=${did}
OZONE_DB_POSTGRES_URL=postgres://postgres:postgres@database/ozone
OZONE_DID_PLC_URL=https://plc.${host}
NEXT_PUBLIC_PLC_DIRECTORY_URL=https://plc.${host}
NEXT_PUBLIC_OZONE_SERVICE_DID=did:web:ozone.${host}
NEXT_PUBLIC_SOCIAL_APP_DOMAIN=mod.${host}
NEXT_PUBLIC_SOCIAL_APP_URL=https://mod.${host}
OZONE_APPVIEW_DID=did:web:bsky.${host}
OZONE_APPVIEW_URL=https://bsky.${host}
OZONE_APPVIEW_PUSH_EVENTS=false
OZONE_PDS_DID=did:web:${host}
OZONE_PDS_URL=https://${host}
OZONE_DEV_MODE=true
OZONE_DB_MIGRATE=1
OZONE_ADMIN_PASSWORD
OZONE_SIGNING_KEY_HEX
OZONE_BLOB_DIVERT_ADMIN_PASSWORD
OZONE_VERIFIER_URL
OZONE_VERIFIER_DID
OZONE_VERIFIER_PASSWORD
OZONE_VERIFIER_ISSUERS_TO_INDEX
OZONE_VERIFIER_JETSTREAM_URL
OZONE_APPVIEW_PUSH_EVENTS=true

23
envs/pds Normal file
View File

@@ -0,0 +1,23 @@
PDS_HOSTNAME=${host}
PDS_DB_POSTGRES_URL=postgres://postgres:postgres@database/pds
PDS_DATA_DIRECTORY=/data
PDS_BLOBSTORE_DISK_LOCATION=/data/img/static
#PDS_BLOBSTORE_DISK_TMP_LOCATION=/data/img/tmp
PDS_BSKY_APP_VIEW_DID=did:web:bsky.${host}
PDS_BSKY_APP_VIEW_URL=https://bsky.${host}
PDS_CRAWLERS=https://bgs.${host}
PDS_SEQUENCER_ENABLED=true
PDS_SEQUENCER_DB_LOCATION=/data/sequencer.sqlite
PDS_DEV_MODE=true
PDS_DID_PLC_URL=https://plc.${host}
PDS_ENABLE_DID_DOC_WITH_SESSION=true
PDS_INVITE_INTERVAL=604800000
PDS_SERVICE_DID=did:web:${host}
PDS_EMAIL_FROM_ADDRESS=no-reply@${host}
PDS_INVITE_REQUIRED=true
PDS_EMAIL_SMTP_URL=smtps://${user}:${app_password}@smtp.gmail.com
PDS_ADMIN_PASSWORD
PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX
PDS_REPO_SIGNING_KEY_K256_PRIVATE_KEY_HEX
PDS_JWT_SECRET

4
envs/plc Normal file
View File

@@ -0,0 +1,4 @@
DATABASE_URL=postgres://postgres:postgres@database/plc
DB_CREDS_JSON='{"username":"postgres","password":"postgres","host":"database","port":"5432","database":"plc"}'
ENABLE_MIGRATIONS=true
DB_MIGRATE_CREDS_JSON='{"username":"postgres","password":"postgres","host":"database","port":"5432","database":"plc"}'

4
envs/postgres Normal file
View File

@@ -0,0 +1,4 @@
#POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=healthcheck

3
envs/social-app Normal file
View File

@@ -0,0 +1,3 @@
ATP_APPVIEW_HOST=https://public.api.bsky.app
EXPO_PUBLIC_BLUESKY_PROXY_DID=did:web:api.bsky.app
EXPO_PUBLIC_ENV=production

52
icons/Logotype.tsx Normal file
View File

@@ -0,0 +1,52 @@
import React from 'react'
import Svg, {Path, SvgProps, PathProps} from 'react-native-svg'
import {usePalette} from '#/lib/hooks/usePalette'
const ratio = 17 / 64
export function Logotype({
fill,
...rest
}: {fill?: PathProps['fill']} & SvgProps) {
const pal = usePalette('default')
// @ts-ignore it's fiiiiine
const size = parseInt(rest.width || 32)
return (
<Svg
fill="none"
viewBox="0 0 2821.6379 794.29016"
{...rest}
width={size}
height={Number(size) * ratio}>
<g
transform="matrix(0.1,0,0,-0.1,-282.80153,1445)"
fill="#000000"
stroke="none"
>
<path
d="m 24787,14443 c -4,-3 -7,-224 -7,-490 v -483 h 545 545 v 490 490 h -538 c -296,0 -542,-3 -545,-7 z"
/>
<path
d="m 5190,13285 c -8,-3 -96,-12 -195,-20 -199,-16 -296,-32 -430,-70 -49,-14 -115,-32 -145,-40 -153,-39 -504,-198 -662,-301 -21,-13 -57,-36 -80,-51 -24,-16 -72,-50 -109,-78 -241,-182 -377,-315 -528,-517 -119,-158 -120,-160 -106,-188 23,-45 140,-140 560,-457 110,-84 319,-242 465,-353 146,-110 369,-279 495,-375 791,-600 723,-549 1049,-799 269,-207 398,-307 524,-403 l 114,-86 -49,-49 c -128,-131 -378,-258 -588,-299 -66,-13 -357,-13 -420,0 -115,23 -172,39 -202,54 -18,10 -37,17 -43,17 -24,0 -171,81 -255,141 -50,35 -146,121 -215,192 -69,70 -133,127 -142,127 -19,0 -153,-63 -177,-83 -9,-7 -54,-35 -101,-62 -47,-26 -110,-64 -140,-85 -30,-20 -97,-61 -148,-91 -51,-30 -107,-62 -124,-72 -18,-10 -57,-38 -87,-61 -70,-53 -252,-168 -446,-281 -82,-49 -158,-95 -168,-104 -16,-16 -14,-21 34,-106 165,-289 544,-666 867,-860 9,-6 35,-22 58,-37 36,-23 267,-138 349,-173 108,-47 160,-67 240,-93 150,-48 201,-62 228,-62 14,0 64,-9 109,-19 197,-46 302,-56 573,-56 197,1 281,5 345,17 47,9 110,19 141,22 31,3 75,13 99,21 23,8 56,15 72,15 17,0 51,7 77,15 25,8 80,24 121,36 41,12 125,41 185,66 61,25 124,50 140,56 17,7 89,42 160,78 113,58 177,98 395,246 82,56 273,232 403,371 127,136 237,271 237,291 0,12 -208,179 -425,342 -186,140 -1121,843 -1720,1294 -264,199 -589,444 -723,546 -134,101 -274,208 -312,237 -39,29 -70,58 -70,66 0,21 107,115 203,179 95,64 237,133 295,143 20,4 49,12 63,20 96,48 519,48 619,-1 14,-7 41,-16 60,-20 65,-13 262,-118 360,-191 99,-74 250,-230 372,-384 34,-44 70,-80 80,-80 9,0 39,15 67,33 28,17 70,44 94,58 23,15 121,76 217,136 96,60 254,156 350,213 96,56 272,160 390,230 118,70 230,135 248,144 41,21 41,45 -3,116 -18,30 -46,75 -61,100 -64,106 -252,348 -352,454 -106,112 -169,171 -293,274 -197,164 -310,235 -594,376 -215,106 -519,205 -735,240 -137,22 -574,51 -610,41 z"
/>
<path
d="m 29095,12736 c -421,-44 -744,-157 -975,-342 -259,-207 -396,-446 -465,-809 -16,-84 -23,-301 -14,-425 36,-518 257,-859 694,-1070 166,-80 284,-116 716,-215 449,-103 514,-121 646,-186 218,-107 308,-253 306,-494 -2,-303 -188,-477 -588,-551 -140,-26 -640,-27 -825,-1 -275,38 -486,94 -776,203 -94,35 -174,64 -178,64 -3,0 -6,-175 -6,-389 v -388 l 88,-41 c 404,-187 905,-282 1486,-282 675,0 1154,150 1465,459 196,194 311,434 362,756 20,121 17,476 -5,600 -89,517 -358,800 -945,994 -130,43 -241,71 -616,156 -137,31 -299,73 -360,92 -331,106 -455,246 -455,511 1,249 127,412 387,501 272,92 801,76 1269,-39 116,-28 326,-96 432,-138 l 52,-22 -2,406 -3,406 -66,28 c -154,66 -413,140 -604,174 -279,49 -756,69 -1020,42 z"
/>
<path
d="m 9630,12676 c 0,-2 403,-996 896,-2208 493,-1211 898,-2211 901,-2220 6,-21 -135,-386 -193,-499 -104,-202 -256,-324 -471,-380 -118,-30 -340,-43 -516,-29 -84,6 -185,17 -225,24 -40,7 -75,10 -77,7 -3,-2 -5,-163 -5,-357 v -353 l 63,-30 c 194,-93 493,-138 801,-120 414,23 683,115 937,319 174,140 357,402 474,680 26,62 1945,5159 1945,5167 0,2 -232,2 -516,1 l -517,-3 -556,-1550 c -485,-1350 -560,-1550 -578,-1553 -18,-3 -26,11 -62,105 -23,59 -295,758 -605,1553 l -562,1445 -567,3 c -312,1 -567,0 -567,-2 z"
/>
<path
d="m 15690,10867 c 0,-1970 -1,-1936 56,-2153 128,-497 461,-787 1014,-885 147,-26 440,-36 605,-20 413,40 834,200 1181,447 35,25 73,44 88,44 14,0 26,-1 26,-3 0,-2 20,-94 45,-205 25,-111 45,-204 45,-207 0,-3 209,-5 465,-5 h 465 v 2400 2400 h -535 -535 l -2,-1866 -3,-1866 -115,-50 c -425,-185 -743,-252 -1088,-227 -302,21 -472,109 -562,293 -79,161 -74,11 -77,1969 l -3,1747 h -535 -535 z"
/>
<path
d="m 24785,12668 c -3,-7 -4,-1086 -3,-2398 l 3,-2385 538,-3 537,-2 v 2400 2400 h -535 c -419,0 -537,-3 -540,-12 z"
/>
<path
d="m 21660,8275 v -545 h 565 565 v 545 545 h -565 -565 z"
/>
</g>
</Svg>
)
}

32
icons/title.svg Normal file
View File

@@ -0,0 +1,32 @@
<svg
viewBox="0 0 2821.6379 794.29016"
preserveAspectRatio="xMidYMid"
xmlns:svg="http://www.w3.org/2000/svg">
<g
transform="matrix(0.1,0,0,-0.1,-282.80153,1445)"
fill="#000000"
stroke="none"
>
<path
d="m 24787,14443 c -4,-3 -7,-224 -7,-490 v -483 h 545 545 v 490 490 h -538 c -296,0 -542,-3 -545,-7 z"
/>
<path
d="m 5190,13285 c -8,-3 -96,-12 -195,-20 -199,-16 -296,-32 -430,-70 -49,-14 -115,-32 -145,-40 -153,-39 -504,-198 -662,-301 -21,-13 -57,-36 -80,-51 -24,-16 -72,-50 -109,-78 -241,-182 -377,-315 -528,-517 -119,-158 -120,-160 -106,-188 23,-45 140,-140 560,-457 110,-84 319,-242 465,-353 146,-110 369,-279 495,-375 791,-600 723,-549 1049,-799 269,-207 398,-307 524,-403 l 114,-86 -49,-49 c -128,-131 -378,-258 -588,-299 -66,-13 -357,-13 -420,0 -115,23 -172,39 -202,54 -18,10 -37,17 -43,17 -24,0 -171,81 -255,141 -50,35 -146,121 -215,192 -69,70 -133,127 -142,127 -19,0 -153,-63 -177,-83 -9,-7 -54,-35 -101,-62 -47,-26 -110,-64 -140,-85 -30,-20 -97,-61 -148,-91 -51,-30 -107,-62 -124,-72 -18,-10 -57,-38 -87,-61 -70,-53 -252,-168 -446,-281 -82,-49 -158,-95 -168,-104 -16,-16 -14,-21 34,-106 165,-289 544,-666 867,-860 9,-6 35,-22 58,-37 36,-23 267,-138 349,-173 108,-47 160,-67 240,-93 150,-48 201,-62 228,-62 14,0 64,-9 109,-19 197,-46 302,-56 573,-56 197,1 281,5 345,17 47,9 110,19 141,22 31,3 75,13 99,21 23,8 56,15 72,15 17,0 51,7 77,15 25,8 80,24 121,36 41,12 125,41 185,66 61,25 124,50 140,56 17,7 89,42 160,78 113,58 177,98 395,246 82,56 273,232 403,371 127,136 237,271 237,291 0,12 -208,179 -425,342 -186,140 -1121,843 -1720,1294 -264,199 -589,444 -723,546 -134,101 -274,208 -312,237 -39,29 -70,58 -70,66 0,21 107,115 203,179 95,64 237,133 295,143 20,4 49,12 63,20 96,48 519,48 619,-1 14,-7 41,-16 60,-20 65,-13 262,-118 360,-191 99,-74 250,-230 372,-384 34,-44 70,-80 80,-80 9,0 39,15 67,33 28,17 70,44 94,58 23,15 121,76 217,136 96,60 254,156 350,213 96,56 272,160 390,230 118,70 230,135 248,144 41,21 41,45 -3,116 -18,30 -46,75 -61,100 -64,106 -252,348 -352,454 -106,112 -169,171 -293,274 -197,164 -310,235 -594,376 -215,106 -519,205 -735,240 -137,22 -574,51 -610,41 z"
/>
<path
d="m 29095,12736 c -421,-44 -744,-157 -975,-342 -259,-207 -396,-446 -465,-809 -16,-84 -23,-301 -14,-425 36,-518 257,-859 694,-1070 166,-80 284,-116 716,-215 449,-103 514,-121 646,-186 218,-107 308,-253 306,-494 -2,-303 -188,-477 -588,-551 -140,-26 -640,-27 -825,-1 -275,38 -486,94 -776,203 -94,35 -174,64 -178,64 -3,0 -6,-175 -6,-389 v -388 l 88,-41 c 404,-187 905,-282 1486,-282 675,0 1154,150 1465,459 196,194 311,434 362,756 20,121 17,476 -5,600 -89,517 -358,800 -945,994 -130,43 -241,71 -616,156 -137,31 -299,73 -360,92 -331,106 -455,246 -455,511 1,249 127,412 387,501 272,92 801,76 1269,-39 116,-28 326,-96 432,-138 l 52,-22 -2,406 -3,406 -66,28 c -154,66 -413,140 -604,174 -279,49 -756,69 -1020,42 z"
/>
<path
d="m 9630,12676 c 0,-2 403,-996 896,-2208 493,-1211 898,-2211 901,-2220 6,-21 -135,-386 -193,-499 -104,-202 -256,-324 -471,-380 -118,-30 -340,-43 -516,-29 -84,6 -185,17 -225,24 -40,7 -75,10 -77,7 -3,-2 -5,-163 -5,-357 v -353 l 63,-30 c 194,-93 493,-138 801,-120 414,23 683,115 937,319 174,140 357,402 474,680 26,62 1945,5159 1945,5167 0,2 -232,2 -516,1 l -517,-3 -556,-1550 c -485,-1350 -560,-1550 -578,-1553 -18,-3 -26,11 -62,105 -23,59 -295,758 -605,1553 l -562,1445 -567,3 c -312,1 -567,0 -567,-2 z"
/>
<path
d="m 15690,10867 c 0,-1970 -1,-1936 56,-2153 128,-497 461,-787 1014,-885 147,-26 440,-36 605,-20 413,40 834,200 1181,447 35,25 73,44 88,44 14,0 26,-1 26,-3 0,-2 20,-94 45,-205 25,-111 45,-204 45,-207 0,-3 209,-5 465,-5 h 465 v 2400 2400 h -535 -535 l -2,-1866 -3,-1866 -115,-50 c -425,-185 -743,-252 -1088,-227 -302,21 -472,109 -562,293 -79,161 -74,11 -77,1969 l -3,1747 h -535 -535 z"
/>
<path
d="m 24785,12668 c -3,-7 -4,-1086 -3,-2398 l 3,-2385 538,-3 537,-2 v 2400 2400 h -535 c -419,0 -537,-3 -540,-12 z"
/>
<path
d="m 21660,8275 v -545 h 565 565 v 545 545 h -565 -565 z"
/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

328
install.zsh Executable file
View File

@@ -0,0 +1,328 @@
#!/bin/zsh
# ./install.zsh $HOST
repos_v='{}'
function at-repos-env() {
host=$1
if [ -z "$1" ];then
host=syu.is
fi
did=did:plc:6qyecktefllvenje24fcxnie
icon=https://git.syui.ai/ai/at/raw/branch/main/icons/Logotype.tsx
repos=(
https://github.com/did-method-plc/did-method-plc
https://github.com/bluesky-social/indigo
https://github.com/bluesky-social/atproto
https://github.com/bluesky-social/social-app
https://github.com/bluesky-social/feed-generator
https://github.com/bluesky-social/ozone
https://github.com/bluesky-social/jetstream
)
services=( bsky plc pds jetstream bgs ozone social-app )
d=${0:a:h}
dh=${0:a:h:h}
name=${host%%.*}
domain=${host##*.}
dport=5000
}
function at-repos-json() {
f=~/.config/atproto/token.json
j="{ \"did\": \"did:plc:6qyecktefllvenje24fcxnie\", \"didDoc\": { \"service\": [ { \"serviceEndpoint\": \"https://syu.is\" } ] }, \"handle\": \"ai.syu.is\", \"accessJwt\": \"xxx\" }"
if [ ! -f "$f" ];then
mkdir -p ~/.config/atproto
echo $j >> $f
fi
echo $f
}
function at-repos-token() {
at-repos-json
if [ -z "$host" ] && [ -f $f ];then
host=`cat $f|jq -r ".didDoc.service.[].serviceEndpoint"`
handle=`cat $f|jq -r ".handle"`
did=`cat $f|jq -r ".did"`
token=`cat $f|jq -r ".token"`
host=${host##*/}
fi
name=${host%%.*}
domain=${host##*.}
}
function at-repos-clone() {
if [ ! -d $d/repos ];then
mkdir -p $d/repos
fi
cd $d/repos
for ((i=1; i<=${#repos}; i++)); do
repo=${repos[$i]}
echo $repo
if [ ! -d $d/repos/${repo##*/} ];then
git clone $repo
fi
done
if [ ! -f $d/repos/feed-generator/Dockerfile ] && [ -f $d/docker/feed/Dockerfile ];then
cp -rf $d/docker/feed/Dockerfile $d/repos/feed-generator/
fi
}
function at-repos-pull() {
cd $d/repos
for ((i=1; i<=${#repos}; i++)); do
repo=${repos[$i]}
echo $repo
if [ -d $d/repos/${repo##*/} ];then
cd $d/repos/${repo##*/}
git stash
if ! git pull;then
rm -rf $d/repos/${repo##*/}
at-repos-clone
fi
fi
rv=$(echo "$repos_v" | jq -r ".[\"${repo##*/}\"]")
if [ "$rv" != "null" ];then
cd $d/repos/${repo##*/}
git reset --hard $rv
cd ..
fi
done
cd $d
}
function at-repos-social-app-icon() {
curl -sL https://raw.githubusercontent.com/bluesky-social/social-app/main/src/view/icons/Logotype.tsx -o $d/repos/social-app/src/view/icons/Logotype.tsx
if [ -d $d/icons ];then
mkdir -p $d/icons
fi
cp -rf $d/repos/social-app/src/view/icons/Logotype.tsx $d/icons/
}
function at-repos-social-app-icon-origin() {
curl -sL $icon -o $d/icons/Logotype.tsx
}
function at-repos-social-app-avatar-write() {
did_admin=did:plc:6qyecktefllvenje24fcxnie
dt=$d/repos/social-app/src
cd $dt
f=$dt/lib/constants.ts
sed -i "s#export const BSKY_SERVICE = 'https://bsky.social'#export const BSKY_SERVICE = 'https://${host}'#g" $f
sed -i "s#export const BSKY_SERVICE_DID = 'did:web:bsky.social'#export const BSKY_SERVICE_DID = 'did:web:${host}'#g" $f
sed -i "s#export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app'#export const PUBLIC_BSKY_SERVICE = 'https://bsky.${host}'#g" $f
sed -i "s#export const PUBLIC_APPVIEW = 'https://api.bsky.app'#export const PUBLIC_APPVIEW = 'https://bsky.${host}'#g" $f
sed -i "s#export const PUBLIC_APPVIEW_DID = 'did:web:api.bsky.app'#export const PUBLIC_APPVIEW_DID = 'did:web:bsky.${host}'#g" $f
# Disable external services (CORS fix)
f=$dt/state/geolocation/const.ts
curl -sL https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/src/state/geolocation/const.ts -o $f
cat > $f << 'GEOEOF'
import {type GeolocationStatus} from '#/state/geolocation/types'
import {BAPP_CONFIG_DEV_URL, IS_DEV} from '#/env'
import {type Device} from '#/storage'
export const IPCC_URL = `https://bsky.app/ipcc`
// Disabled for self-hosted environment to avoid CORS errors
export const BAPP_CONFIG_URL_PROD = null
export const BAPP_CONFIG_URL = null
export const GEOLOCATION_CONFIG_URL = BAPP_CONFIG_URL
export const DEFAULT_GEOLOCATION_CONFIG: Device['geolocation'] = {
countryCode: undefined,
regionCode: undefined,
ageRestrictedGeos: [],
ageBlockedGeos: [],
}
export const DEFAULT_GEOLOCATION_STATUS: GeolocationStatus = {
countryCode: undefined,
regionCode: undefined,
isAgeRestrictedGeo: false,
isAgeBlockedGeo: false,
}
GEOEOF
# Add null check to geolocation config.ts to prevent fetch(null) errors
f=$dt/state/geolocation/config.ts
curl -sL https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/src/state/geolocation/config.ts -o $f
# Add null check at the beginning of getGeolocationConfig function (after line with 'url: string,')
sed -i "s/): Promise<Device\['geolocation'\]> {/): Promise<Device['geolocation']> {\n if (!url) return undefined/" $f
# Disable Statsig (CORS fix)
f=$dt/lib/statsig/statsig.tsx
sed -i "s#api: 'https://events.bsky.app/v2'#api: '' // Disabled for self-hosted#g" $f
# Disable SDK initialization to prevent statsigapi.net connections
sed -i "s#const SDK_KEY = 'client-SXJakO39w9vIhl3D44u8UupyzFl4oZ2qPIkjwcvuPsV'#const SDK_KEY = '' // Disabled for self-hosted#g" $f
f=$dt/view/icons/Logotype.tsx
o=$d/icons/Logotype.tsx
cp -rf $o $f
f=$dt/view/com/util/UserAvatar.tsx
curl -sL https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/src/view/com/util/UserAvatar.tsx -o $f
sed -i "s#/img/avatar/plain/#https://cdn.web.syu.is/img/avatar/plain/#g" $f
sed -i "s#/img/avatar_thumbnail/plain/#https://bsky.${host}/img/avatar/plain/#g" $f
sed -i "s#source={{uri: avatar}}#source={{ uri: hackModifyThumbnailPath(avatar, 1 > 0), }}#g" $f
curl -sL https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/src/lib/strings/url-helpers.ts -o $dt/lib/strings/url-helpers.ts
sed -i "s#https://go.web.syu.is/redirect?u=\${encodeURIComponent(url)}#\${url}#g" $dt/lib/strings/url-helpers.ts
grep -R $did_admin .|cut -d : -f 1|sort -u|xargs sed -i "s/${did_admin}/${did}/g"
}
function at-repos-atproto-service-bsky-api-patch() {
# https://github.com/itaru2622/bluesky-selfhost-env/blob/master/patching/105-atproto-services-for-docker.diff
f=$d/repos/atproto/services/bsky/api.js
curl -sL https://raw.githubusercontent.com/bluesky-social/atproto/refs/heads/main/services/bsky/api.js -o $f
d_=$d/repos/atproto
p_=$d/patching/4367-atproto-services-bsky-api.diff
echo "applying patch: under ${f} for ${p_}"
pushd ${d_}
patch -p1 < ${p_}
popd
}
function at-repos-atproto-service-pds-index-patch() {
f=$d/repos/atproto/services/pds/index.js
curl -sL https://raw.githubusercontent.com/bluesky-social/atproto/refs/heads/main/services/pds/index.js -o $f
d_=$d/repos/atproto
p_=$d/patching/4367-atproto-services-pds-index.diff
echo "applying patch: under ${f} for ${p_}"
pushd ${d_}
patch -p1 < ${p_}
popd
}
function at-repos-social-app-agent-patch() {
f=$d/repos/social-app/src/state/session/agent.ts
p_=$d/patching/8980-social-app-disable-proxy.diff
d_=$d/repos/social-app
echo "applying patch: under ${f} for ${p_}"
pushd ${d_}
patch -p1 < ${p_}
popd
}
function at-repos-social-app-disable-external-services-patch() {
f=$d/repos/social-app/src/state/geolocation/const.ts
p_=$d/patching/8980-social-app-disable-external-services.diff
d_=$d/repos/social-app
echo "applying patch: under ${f} for ${p_}"
pushd ${d_}
patch -p1 < ${p_}
popd
}
function at-repos-atproto-service-ozone-api-patch() {
f=$d/repos/atproto/services/ozone/api.js
d_=$d/repos/atproto
p_=$d/patching/130-atproto-ozone-enable-daemon-v2.patch
echo "applying patch: under ${f} for ${p_}"
pushd ${d_}
patch -p1 < ${p_}
popd
}
function at-repos-ozone-patch() {
#DOMAIN=syu.is
cd $d/repos
d_=$d/repos/ozone
rm -rf ${d_}
p_=$d/patching/120-ozone-runtimeEnvVars.diff
git clone https://github.com/bluesky-social/ozone
cd ${d_}
pushd ${d_}
echo "applying patch: under ${d_} for ${p_}"
patch -p1 < ${p_}
popd
p_=$d/patching/122-ozone-enable-daemon.diff
echo "applying patch: under ${d_} for ${p_}"
pushd ${d_}
patch -p1 < ${p_}
popd
p_=$d/patching/121-ozone-constants-fix.patch
echo "applying patch: under ${d_} for ${p_}"
pushd ${d_}
patch -p1 < ${p_} || true
popd
}
function at-repos-build-docker-atproto() {
cd $d
docker image prune -a
if [ -z "$1" ];then
for ((i=1; i<=${#services}; i++)); do
service=${services[$i]}
docker compose build --no-cache $service
done
else
docker compose build --no-cache $1
fi
}
function at-repos-push-reset() {
docker restart registry
docker stop registry
docker rm registry
docker volume rm registry-data 2>/dev/null || true
docker run -d -p ${dport}:${dport} --name registry \
--restart=always \
-v registry-data:/var/lib/registry \
registry:2
sleep 3
docker run -d -p ${dport}:${dport} --name registry --restart=always registry:2
}
function at-repos-push-docker() {
if [ -z "$1" ];then
for ((i=1; i<=${#services}; i++)); do
service=${services[$i]}
docker tag at-${service}:latest localhost:${dport}/${service}:latest
docker push localhost:${dport}/${service}:latest
if [ "$service" = "ozone" ];then
docker tag at-${service}:latest localhost:${dport}/${service}-web:latest
docker push localhost:${dport}/${service}-web:latest
fi
done
else
docker tag at-${1}:latest localhost:${dport}/${1}:latest
docker push localhost:${dport}/${1}:latest
fi
}
function at-repos-pull-docker() {
cd $d
docker image prune -a
docker compose up -d --pull always
}
at-repos-env
case "`cat /etc/hostname`" in
at)
at-repos-pull-docker
exit
;;
*)
at-repos-push-reset
at-repos-clone
at-repos-pull
at-repos-social-app-icon
at-repos-social-app-icon-origin
at-repos-social-app-avatar-write
at-repos-social-app-agent-patch
at-repos-social-app-disable-external-services-patch
at-repos-atproto-service-bsky-api-patch
at-repos-atproto-service-pds-index-patch
at-repos-atproto-service-ozone-api-patch
at-repos-ozone-patch
if [ -n "$1" ];then
at-repos-build-docker-atproto $1
at-repos-push-docker $1
exit
fi
at-repos-build-docker-atproto
at-repos-push-docker
cd $d; docker compose down
;;
esac

0
ios/.keep Normal file
View File

134
ios/AppInfo.tsx Normal file
View File

@@ -0,0 +1,134 @@
import React from 'react'
import {View, Text, StyleSheet, Pressable, Linking} from 'react-native'
interface AppInfoProps {
onLinkPress?: (url: string) => void
}
export default function AppInfo({onLinkPress}: AppInfoProps) {
const handleLinkPress = (url: string) => {
if (onLinkPress) {
onLinkPress(url)
} else {
Linking.openURL(url)
}
}
return (
<View style={styles.container}>
<View style={styles.section}>
<Text style={styles.sectionTitle}>About This App</Text>
<Text style={styles.paragraph}>
This is a customized AT Protocol social networking client. It allows you to
connect to any Personal Data Server (PDS) and participate in the decentralized
social network.
</Text>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Key Features</Text>
<View style={styles.list}>
<Text style={styles.listItem}> Connect to any AT Protocol PDS</Text>
<Text style={styles.listItem}> Post text, images, and videos</Text>
<Text style={styles.listItem}> Follow users and view timelines</Text>
<Text style={styles.listItem}> Customize feeds and moderation settings</Text>
<Text style={styles.listItem}> Direct messaging support</Text>
</View>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Open Source</Text>
<Text style={styles.paragraph}>
This application is based on the Bluesky social-app, licensed under the MIT
License. The original source code is available at:
</Text>
<Pressable
onPress={() =>
handleLinkPress('https://github.com/bluesky-social/social-app')
}>
<Text style={styles.link}>github.com/bluesky-social/social-app</Text>
</Pressable>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>AT Protocol</Text>
<Text style={styles.paragraph}>
This app uses the AT Protocol (Authenticated Transfer Protocol), an open and
decentralized standard for social applications.
</Text>
<Pressable onPress={() => handleLinkPress('https://atproto.com')}>
<Text style={styles.link}>atproto.com</Text>
</Pressable>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>License</Text>
<Text style={styles.paragraph}>
Copyright 20232025 Bluesky Social PBC
</Text>
<Text style={styles.paragraph}>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software.
</Text>
<Text style={styles.paragraph}>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
</Text>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Contact</Text>
<Pressable onPress={() => handleLinkPress('https://syu.is')}>
<Text style={styles.link}>https://syu.is</Text>
</Pressable>
</View>
<View style={styles.section}>
<Text style={styles.versionText}>Version 1.0.0</Text>
</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
section: {
marginBottom: 24,
},
sectionTitle: {
fontSize: 20,
fontWeight: '600',
color: '#1d1d1f',
marginBottom: 12,
},
paragraph: {
fontSize: 15,
lineHeight: 22,
color: '#3a3a3c',
marginBottom: 8,
},
list: {
marginLeft: 8,
marginTop: 8,
},
listItem: {
fontSize: 15,
lineHeight: 24,
color: '#3a3a3c',
},
link: {
fontSize: 15,
color: '#007aff',
textDecorationLine: 'underline',
marginTop: 8,
},
versionText: {
fontSize: 13,
color: '#8e8e93',
fontStyle: 'italic',
},
})

95
ios/LicenseNotice.tsx Normal file
View File

@@ -0,0 +1,95 @@
import React from 'react'
import {View, Text, StyleSheet, Pressable, Linking} from 'react-native'
export default function LicenseNotice() {
return (
<View style={styles.container}>
<Text style={styles.title}>Open Source Licenses</Text>
<View style={styles.section}>
<Text style={styles.projectName}>Bluesky Social App</Text>
<Text style={styles.license}>MIT License</Text>
<Text style={styles.copyright}>Copyright 20232025 Bluesky Social PBC</Text>
<Text style={styles.licenseText}>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
</Text>
<Text style={styles.licenseText}>
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
</Text>
<Text style={styles.licenseText}>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</Text>
<Pressable
onPress={() =>
Linking.openURL('https://github.com/bluesky-social/social-app')
}>
<Text style={styles.link}>View Source Code</Text>
</Pressable>
</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
color: '#1d1d1f',
},
section: {
marginBottom: 24,
padding: 16,
backgroundColor: '#f5f5f7',
borderRadius: 8,
},
projectName: {
fontSize: 18,
fontWeight: '600',
marginBottom: 8,
color: '#1d1d1f',
},
license: {
fontSize: 14,
fontWeight: '500',
color: '#007aff',
marginBottom: 4,
},
copyright: {
fontSize: 13,
color: '#3a3a3c',
marginBottom: 12,
},
licenseText: {
fontSize: 12,
lineHeight: 18,
color: '#3a3a3c',
marginBottom: 12,
},
link: {
fontSize: 14,
color: '#007aff',
textDecorationLine: 'underline',
marginTop: 8,
},
})

163
ios/PrivacyContent.tsx Normal file
View File

@@ -0,0 +1,163 @@
import React from 'react'
import {View, Text, StyleSheet, Pressable, Linking} from 'react-native'
interface PrivacyContentProps {
onLinkPress?: (url: string) => void
}
export default function PrivacyContent({onLinkPress}: PrivacyContentProps) {
const handleLinkPress = (url: string) => {
if (onLinkPress) {
onLinkPress(url)
} else {
Linking.openURL(url)
}
}
return (
<View style={styles.container}>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Introduction</Text>
<Text style={styles.paragraph}>
This Privacy Policy explains how this AT Protocol client application
(hereinafter referred to as "the App") handles personal information.
Please read this policy carefully before using the App.
</Text>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Information We Collect</Text>
<Text style={styles.paragraph}>
The App may collect and use the following information:
</Text>
<Text style={styles.subTitle}>1. Information Collected Automatically</Text>
<View style={styles.list}>
<Text style={styles.listItem}> Device information (model, OS version)</Text>
<Text style={styles.listItem}> App usage data (sessions, features used)</Text>
<Text style={styles.listItem}> Crash logs and performance data</Text>
</View>
<Text style={styles.subTitle}>2. Information Provided by Users</Text>
<View style={styles.list}>
<Text style={styles.listItem}>
DID (Decentralized Identifier) and handle for authentication
</Text>
<Text style={styles.listItem}> Posts, media, and social interactions</Text>
<Text style={styles.listItem}> Profile information (avatar, display name, bio)</Text>
</View>
<View style={styles.highlight}>
<Text style={styles.highlightText}>
Important: Your data is stored on your chosen PDS (Personal Data Server).
This app does not store your content on our servers.
</Text>
</View>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>How We Use Your Information</Text>
<View style={styles.list}>
<Text style={styles.listItem}>
To provide AT Protocol social networking features
</Text>
<Text style={styles.listItem}> To improve app performance and user experience</Text>
<Text style={styles.listItem}> To diagnose and fix technical issues</Text>
</View>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Data Sharing</Text>
<Text style={styles.paragraph}>
The App interacts with your chosen PDS and AppView services. Your posts and
profile information are shared according to the AT Protocol specification and
your privacy settings.
</Text>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Your Rights</Text>
<Text style={styles.paragraph}>
You have the right to access, modify, or delete your data through your PDS.
You can also switch to a different PDS at any time while maintaining your
identity.
</Text>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Contact</Text>
<Text style={styles.paragraph}>
For questions about this Privacy Policy, please contact:
</Text>
<Pressable onPress={() => handleLinkPress('https://syu.is')}>
<Text style={styles.link}>https://syu.is</Text>
</Pressable>
</View>
<View style={styles.section}>
<Text style={styles.lastUpdated}>Last Updated: December 3, 2025</Text>
</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
section: {
marginBottom: 24,
},
sectionTitle: {
fontSize: 20,
fontWeight: '600',
color: '#1d1d1f',
marginBottom: 12,
},
subTitle: {
fontSize: 16,
fontWeight: '500',
color: '#1d1d1f',
marginTop: 12,
marginBottom: 8,
},
paragraph: {
fontSize: 15,
lineHeight: 22,
color: '#3a3a3c',
marginBottom: 8,
},
list: {
marginLeft: 8,
marginTop: 8,
},
listItem: {
fontSize: 15,
lineHeight: 24,
color: '#3a3a3c',
},
highlight: {
backgroundColor: '#fff3cd',
borderLeftWidth: 4,
borderLeftColor: '#ffc107',
padding: 12,
marginTop: 12,
borderRadius: 4,
},
highlightText: {
fontSize: 14,
lineHeight: 20,
color: '#856404',
},
link: {
fontSize: 15,
color: '#007aff',
textDecorationLine: 'underline',
marginTop: 8,
},
lastUpdated: {
fontSize: 13,
color: '#8e8e93',
fontStyle: 'italic',
},
})

View File

@@ -0,0 +1,42 @@
import React from 'react'
import {View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect} from '@react-navigation/native'
import {usePalette} from '#/lib/hooks/usePalette'
import {
type CommonNavigatorParams,
type NativeStackScreenProps,
} from '#/lib/routes/types'
import {s} from '#/lib/styles'
import {useSetMinimalShellMode} from '#/state/shell'
import {ScrollView} from '#/view/com/util/Views'
import * as Layout from '#/components/Layout'
import {ViewHeader} from '../com/util/ViewHeader'
import PrivacyContent from '#/components/custom/PrivacyContent'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PrivacyPolicy'>
export const PrivacyPolicyScreen = (_props: Props) => {
const pal = usePalette('default')
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
useFocusEffect(
React.useCallback(() => {
setMinimalShellMode(false)
}, [setMinimalShellMode]),
)
return (
<Layout.Screen>
<ViewHeader title={_(msg`Privacy Policy`)} />
<ScrollView style={[s.hContentRegion, pal.view]}>
<View style={[s.p20]}>
<PrivacyContent />
</View>
<View style={s.footerSpacer} />
</ScrollView>
</Layout.Screen>
)
}

38
ios/Support.screen.tsx Normal file
View File

@@ -0,0 +1,38 @@
import React from 'react'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect} from '@react-navigation/native'
import {usePalette} from '#/lib/hooks/usePalette'
import {
type CommonNavigatorParams,
type NativeStackScreenProps,
} from '#/lib/routes/types'
import {s} from '#/lib/styles'
import {useSetMinimalShellMode} from '#/state/shell'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {ScrollView} from '#/view/com/util/Views'
import * as Layout from '#/components/Layout'
import AppInfo from '#/components/custom/AppInfo'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Support'>
export const SupportScreen = (_props: Props) => {
const pal = usePalette('default')
const setMinimalShellMode = useSetMinimalShellMode()
const {_} = useLingui()
useFocusEffect(
React.useCallback(() => {
setMinimalShellMode(false)
}, [setMinimalShellMode]),
)
return (
<Layout.Screen>
<ViewHeader title={_(msg`App Info`)} />
<ScrollView style={[s.hContentRegion, pal.view]}>
<AppInfo />
</ScrollView>
</Layout.Screen>
)
}

9
ios/app.config.patch.js Normal file
View File

@@ -0,0 +1,9 @@
// Aiat app configuration overrides
module.exports = {
name: 'Aiat',
slug: 'aiat',
scheme: 'aiat',
owner: 'syui', // Your Expo account
bundleIdentifier: 'ai.syui.at',
// Icon will be set separately
}

44
ios/bin/build.zsh Executable file
View File

@@ -0,0 +1,44 @@
#!/bin/zsh
set -e
d=~/ai/at/repos/social-app
APP_NAME=Aiat
PKG=aiat
TEAM_NAME=
TEAM_ID=
CERT="Apple Distribution: ${TEAM_NAME} (${TEAM_ID})"
MAIL=user@example.com
KEY_CHAIN=EXAMPLE
cd $d
# npx expo prebuild --clean
# cd ios && pod install && cd ..
## アーカイブ
xcodebuild -workspace ios/${PKG}.xcworkspace \
-scheme ${PKG} \
-configuration Release \
-archivePath build/${APP_NAME}.xcarchive \
-allowProvisioningUpdates \
archive
cd build
# IPA作成
rm -rf Payload ${APP_NAME}.ipa
mkdir -p Payload
cp -R ${APP_NAME}.xcarchive/Products/Applications/${PKG}.app Payload/
cp ../store.mobileprovision Payload/${PKG}.app/embedded.mobileprovision
# entitlements抽出
security cms -D -i Payload/${PKG}.app/embedded.mobileprovision > /tmp/profile.plist
/usr/libexec/PlistBuddy -x -c "Print :Entitlements" /tmp/profile.plist > /tmp/entitlements.plist
codesign -f -s "$CERT" Payload/${PKG}.app/Frameworks/*.framework 2>/dev/null || true
codesign -f -s "$CERT" --entitlements /tmp/entitlements.plist Payload/${PKG}.app
zip -r ${APP_NAME}.ipa Payload
xcrun altool --upload-app -f ${APP_NAME}.ipa -t ios -u "${MAIL}" -p "@keychain:${KEY_CHAIN}"
echo "Upload complete"

86
ios/bin/install.zsh Normal file
View File

@@ -0,0 +1,86 @@
#!/bin/zsh
if [ "$1" = "social-app-custom" ];then
at-social-app-custom-pages
at-social-app-custom-screens
at-social-app-aiat-config
at-social-app-aiat-logo
at-origin-social-app
exit
fi
function at-social-app-custom-pages() {
d_=$d/repos/social-app
custom=$d/social-app-custom
echo "copying custom components to social-app"
# Create components directory if not exists
mkdir -p ${d_}/src/components/custom
# Copy custom components
cp ${custom}/PrivacyContent.tsx ${d_}/src/components/custom/
cp ${custom}/AppInfo.tsx ${d_}/src/components/custom/
echo "custom components copied successfully"
}
function at-social-app-aiat-config() {
d_=$d/repos/social-app
custom=$d/social-app-custom
echo "applying Aiat configuration"
# Update app.config.js
cd ${d_}
# Backup original
cp app.config.js app.config.js.orig
# Apply changes using sed
sed -i "s/name: 'Bluesky'/name: 'Aiat'/g" app.config.js
sed -i "s/slug: 'bluesky'/slug: 'aiat'/g" app.config.js
sed -i "s/scheme: 'bluesky'/scheme: 'aiat'/g" app.config.js
sed -i "s/owner: 'blueskysocial'/owner: 'syui'/g" app.config.js
sed -i "s/bundleIdentifier: 'xyz.blueskyweb.app'/bundleIdentifier: 'ai.syui.at'/g" app.config.js
# Update package.json name
sed -i 's/"name": "bsky.app"/"name": "aiat"/g' package.json
echo "Aiat configuration applied"
}
function at-social-app-aiat-logo() {
d_=$d/repos/social-app
custom=$d/social-app-custom
echo "applying Aiat logo"
# Create logo directory if not exists
mkdir -p ${custom}/assets
# Copy logo if exists in custom folder
if [ -f ${custom}/assets/icon.png ]; then
cp ${custom}/assets/icon.png ${d_}/assets/app-icons/ios_icon_default_next.png
echo "Aiat logo applied"
else
echo "Warning: Logo file not found at ${custom}/assets/icon.png"
echo "Please add your logo file there"
fi
}
function at-social-app-custom-screens() {
d_=$d/repos/social-app
custom=$d/social-app-custom
echo "applying custom screens"
# Copy custom screen files
cp ${custom}/PrivacyPolicy.screen.tsx ${d_}/src/view/screens/PrivacyPolicy.tsx
cp ${custom}/Support.screen.tsx ${d_}/src/view/screens/Support.tsx
cp ${custom}/LicenseNotice.tsx ${d_}/src/components/custom/
echo "custom screens applied"
}

0
patching/.keep Normal file
View File

View File

@@ -0,0 +1,119 @@
diff --git a/app/layout.tsx b/app/layout.tsx
index bfc3470..9350629 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -5,6 +5,7 @@ import 'yet-another-react-lightbox/styles.css'
import 'yet-another-react-lightbox/plugins/thumbnails.css'
import 'yet-another-react-lightbox/plugins/captions.css'
import { ToastContainer } from 'react-toastify'
+import { PublicEnvScript } from 'next-runtime-env';
import { Shell } from '@/shell/Shell'
import { CommandPaletteRoot } from '@/shell/CommandPalette/Root'
@@ -36,6 +37,7 @@ export default function RootLayout({
isDarkModeEnabled() ? 'dark' : ''
}`}
>
+ <head>
<title>Ozone</title>
<link
rel="icon"
@@ -43,6 +45,8 @@ export default function RootLayout({
sizes="any"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
+ <PublicEnvScript />
+ </head>
<body className="h-full overflow-hidden">
<ToastContainer
position="bottom-right"
diff --git a/environment.d.ts b/environment.d.ts
index 7a47cc2..33ab29f 100644
--- a/environment.d.ts
+++ b/environment.d.ts
@@ -9,6 +9,8 @@ declare global {
NEXT_PUBLIC_OZONE_SERVICE_DID?: string // e.g. did:plc:xxx#atproto_labeler
NEXT_PUBLIC_OZONE_PUBLIC_URL?: string // e.g. https://ozone.example.com (falls back to window.location.origin)
NEXT_PUBLIC_SOCIAL_APP_URL?: string // e.g. https://bsky.app
+ NEXT_PUBLIC_SOCIAL_APP_DOMAIN?: string // e.g. bsky.app
+ NEXT_PUBLIC_HANDLE_RESOLVER_URL?: string // e.g. https://api.bsky.app
}
}
}
diff --git a/lib/constants.ts b/lib/constants.ts
index fe7c0e8..c286ac6 100644
--- a/lib/constants.ts
+++ b/lib/constants.ts
@@ -1,29 +1,32 @@
+import { env } from 'next-runtime-env';
+
export const OAUTH_SCOPE = 'atproto transition:generic'
export const OZONE_SERVICE_DID =
- process.env.NEXT_PUBLIC_OZONE_SERVICE_DID || undefined
+ env('NEXT_PUBLIC_OZONE_SERVICE_DID') || undefined
export const OZONE_PUBLIC_URL =
- process.env.NEXT_PUBLIC_OZONE_PUBLIC_URL || undefined
+ env('NEXT_PUBLIC_OZONE_PUBLIC_URL') || undefined
export const PLC_DIRECTORY_URL =
- process.env.NEXT_PUBLIC_PLC_DIRECTORY_URL ||
+ env('NEXT_PUBLIC_PLC_DIRECTORY_URL') ||
(process.env.NODE_ENV === 'development'
? 'http://localhost:2582'
: 'https://plc.directory')
-export const QUEUE_CONFIG = process.env.NEXT_PUBLIC_QUEUE_CONFIG || '{}'
+export const QUEUE_CONFIG = env('NEXT_PUBLIC_QUEUE_CONFIG') || '{}'
-export const QUEUE_SEED = process.env.NEXT_PUBLIC_QUEUE_SEED || ''
+export const QUEUE_SEED = env('NEXT_PUBLIC_QUEUE_SEED') || ''
+export const SOCIAL_APP_DOMAIN = env('NEXT_PUBLIC_SOCIAL_APP_DOMAIN') || 'bsky.app'
export const SOCIAL_APP_URL =
- process.env.NEXT_PUBLIC_SOCIAL_APP_URL ||
+ env('NEXT_PUBLIC_SOCIAL_APP_URL') ||
(process.env.NODE_ENV === 'development'
? 'http://localhost:2584'
- : 'https://bsky.app')
+ : `https://${SOCIAL_APP_DOMAIN}`)
export const HANDLE_RESOLVER_URL =
- process.env.NEXT_PUBLIC_HANDLE_RESOLVER_URL ||
+ env('NEXT_PUBLIC_HANDLE_RESOLVER_URL') ||
(process.env.NODE_ENV === 'development'
? 'http://localhost:2584'
: 'https://api.bsky.app')
diff --git a/lib/util.ts b/lib/util.ts
index 0aa4460..ecec7d1 100644
--- a/lib/util.ts
+++ b/lib/util.ts
@@ -1,5 +1,5 @@
import { CollectionId } from '@/reports/helpers/subject'
-import { SOCIAL_APP_URL } from './constants'
+import { SOCIAL_APP_URL, SOCIAL_APP_DOMAIN } from './constants'
import { AtUri } from '@atproto/api'
export function classNames(...classes: (string | undefined)[]) {
@@ -57,7 +57,7 @@ export function takesKeyboardEvt(el?: EventTarget | null) {
)
}
-const blueSkyUrlMatcher = new RegExp('(https?://)?.*bsky.app')
+const blueSkyUrlMatcher = new RegExp('(https?://)?.*'+ `${SOCIAL_APP_DOMAIN}`)
export const isBlueSkyAppUrl = (url: string) => blueSkyUrlMatcher.test(url)
diff --git a/package.json b/package.json
index 8919841..750dce9 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"kbar": "^0.1.0-beta.45",
"lande": "^1.0.10",
"next": "15.2.4",
+ "next-runtime-env": "^3.2.1",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-dropzone": "^14.3.5",

View File

@@ -0,0 +1,99 @@
--- a/lib/constants.ts
+++ b/lib/constants.ts
@@ -1,29 +1,32 @@
+import { env } from 'next-runtime-env';
+
export const OAUTH_SCOPE = 'atproto transition:generic transition:chat.bsky'
export const OZONE_SERVICE_DID =
- process.env.NEXT_PUBLIC_OZONE_SERVICE_DID || undefined
+ env('NEXT_PUBLIC_OZONE_SERVICE_DID') || undefined
export const OZONE_PUBLIC_URL =
- process.env.NEXT_PUBLIC_OZONE_PUBLIC_URL || undefined
+ env('NEXT_PUBLIC_OZONE_PUBLIC_URL') || undefined
export const PLC_DIRECTORY_URL =
- process.env.NEXT_PUBLIC_PLC_DIRECTORY_URL ||
+ env('NEXT_PUBLIC_PLC_DIRECTORY_URL') ||
(process.env.NODE_ENV === 'development'
? 'http://localhost:2582'
: 'https://plc.directory')
-export const QUEUE_CONFIG = process.env.NEXT_PUBLIC_QUEUE_CONFIG || '{}'
+export const QUEUE_CONFIG = env('NEXT_PUBLIC_QUEUE_CONFIG') || '{}'
-export const QUEUE_SEED = process.env.NEXT_PUBLIC_QUEUE_SEED || ''
+export const QUEUE_SEED = env('NEXT_PUBLIC_QUEUE_SEED') || ''
+export const SOCIAL_APP_DOMAIN = env('NEXT_PUBLIC_SOCIAL_APP_DOMAIN') || 'bsky.app'
export const SOCIAL_APP_URL =
- process.env.NEXT_PUBLIC_SOCIAL_APP_URL ||
+ env('NEXT_PUBLIC_SOCIAL_APP_URL') ||
(process.env.NODE_ENV === 'development'
? 'http://localhost:2584'
- : 'https://bsky.app')
+ : `https://${SOCIAL_APP_DOMAIN}`)
export const HANDLE_RESOLVER_URL =
- process.env.NEXT_PUBLIC_HANDLE_RESOLVER_URL ||
+ env('NEXT_PUBLIC_HANDLE_RESOLVER_URL') ||
(process.env.NODE_ENV === 'development'
? 'http://localhost:2584'
: 'https://api.bsky.app')
@@ -36,25 +39,25 @@
export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process.env
.NEXT_PUBLIC_NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS
- ? parseInt(process.env.NEXT_PUBLIC_NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS)
+ ? parseInt(env('NEXT_PUBLIC_NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS'))
: 7
export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process.env
.NEXT_PUBLIC_YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS
- ? parseInt(process.env.NEXT_PUBLIC_YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS)
+ ? parseInt(env('NEXT_PUBLIC_YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS'))
: 30
export const DOMAINS_ALLOWING_EMAIL_COMMUNICATION = (
- process.env.NEXT_PUBLIC_DOMAINS_ALLOWING_EMAIL_COMMUNICATION || ''
+ env('NEXT_PUBLIC_DOMAINS_ALLOWING_EMAIL_COMMUNICATION') || ''
).split(',')
export const HIGH_PROFILE_FOLLOWER_THRESHOLD = process.env
.NEXT_PUBLIC_HIGH_PROFILE_FOLLOWER_THRESHOLD
- ? parseInt(process.env.NEXT_PUBLIC_HIGH_PROFILE_FOLLOWER_THRESHOLD)
+ ? parseInt(env('NEXT_PUBLIC_HIGH_PROFILE_FOLLOWER_THRESHOLD'))
: Infinity
export const FALLBACK_VIDEO_URL = (
- process.env.NEXT_PUBLIC_FALLBACK_VIDEO_URL || ''
+ env('NEXT_PUBLIC_FALLBACK_VIDEO_URL') || ''
).split(':')
// strike to account suspension duration mapping (in hours)
@@ -87,18 +90,18 @@
export const STRIKE_TO_SUSPENSION_DURATION_IN_HOURS =
parseStrikeSuspensionConfig(
- process.env.NEXT_PUBLIC_STRIKE_SUSPENSION_CONFIG || '',
+ env('NEXT_PUBLIC_STRIKE_SUSPENSION_CONFIG') || '',
)
export const AUTOMATED_ACTION_EMAIL_IDS = {
warningWithTakedown:
- process.env.NEXT_PUBLIC_WARNING_WITH_TAKEDOWN_EMAIL_TEMPLATE_ID,
+ env('NEXT_PUBLIC_WARNING_WITH_TAKEDOWN_EMAIL_TEMPLATE_ID'),
suspensionWithTakedown:
- process.env.NEXT_PUBLIC_SUSPENSION_WITH_TAKEDOWN_EMAIL_TEMPLATE_ID,
+ env('NEXT_PUBLIC_SUSPENSION_WITH_TAKEDOWN_EMAIL_TEMPLATE_ID'),
suspensionWithoutTakedown:
- process.env.NEXT_PUBLIC_SUSPENSION_WITHOUT_TAKEDOWN_EMAIL_TEMPLATE_ID,
+ env('NEXT_PUBLIC_SUSPENSION_WITHOUT_TAKEDOWN_EMAIL_TEMPLATE_ID'),
permanentTakedown:
- process.env.NEXT_PUBLIC_PERMANENT_TAKEDOWN_EMAIL_TEMPLATE_ID,
+ env('NEXT_PUBLIC_PERMANENT_TAKEDOWN_EMAIL_TEMPLATE_ID'),
takedownWithoutStrike:
- process.env.NEXT_PUBLIC_TAKEDOWN_WITHOUT_STRIKE_EMAIL_TEMPLATE_ID,
+ env('NEXT_PUBLIC_TAKEDOWN_WITHOUT_STRIKE_EMAIL_TEMPLATE_ID'),
}

View File

@@ -0,0 +1,82 @@
diff --git a/service/index.js b/service/index.js
index 943c281..7721cd9 100644
--- a/service/index.js
+++ b/service/index.js
@@ -1,5 +1,7 @@
const next = require('next')
-const {
+const ozone = require('@atproto/ozone')
+/*
+{
readEnv,
httpLogger,
envToCfg,
@@ -7,6 +9,7 @@ const {
OzoneService,
Database,
} = require('@atproto/ozone')
+*/
const pkg = require('@atproto/ozone/package.json')
async function main() {
@@ -16,37 +19,48 @@ async function main() {
const frontendHandler = frontend.getRequestHandler()
await frontend.prepare()
// backend
- const env = readEnv()
+ const env = ozone.readEnv()
env.version ??= pkg.version
- const config = envToCfg(env)
- const secrets = envToSecrets(env)
+ const config = ozone.envToCfg(env)
+ const secrets = ozone.envToSecrets(env)
const migrate = process.env.OZONE_DB_MIGRATE === '1'
if (migrate) {
- const db = new Database({
+ const db = new ozone.Database({
url: config.db.postgresUrl,
schema: config.db.postgresSchema,
})
await db.migrateToLatestOrThrow()
await db.close()
}
- const ozone = await OzoneService.create(config, secrets)
+ const server = await ozone.OzoneService.create(config, secrets)
// setup handlers
- ozone.app.get('/.well-known/ozone-metadata.json', (_req, res) => {
+ server.app.get('/.well-known/ozone-metadata.json', (_req, res) => {
return res.json({
- did: ozone.ctx.cfg.service.did,
- url: ozone.ctx.cfg.service.publicUrl,
- publicKey: ozone.ctx.signingKey.did(),
+ did: server.ctx.cfg.service.did,
+ url: server.ctx.cfg.service.publicUrl,
+ publicKey: server.ctx.signingKey.did(),
})
})
// Note: We must use `use()` here. This should be the last middleware.
- ozone.app.use((req, res) => {
+ server.app.use((req, res) => {
void frontendHandler(req, res, undefined)
})
// run
- const httpServer = await ozone.start()
+ const httpServer = await server.start()
+ // starts: involve ops from atproto/packages/dev-env/src/ozone.ts >>>
+ ozone.httpLogger.info('starts ozone daemon')
+ const daemon = await ozone.OzoneDaemon.create(config, secrets)
+ await daemon.start()
+ //if (process.env.OZONE_ENABLE_EVENT_REVERSER != 'true') // atproto/services/ozone/daemon.js doesn't stop eventReverser
+ //{
+ // ozone.httpLogger.info('disable ozone daemon eventReverser')
+ // await daemon.ctx.eventReverser.destroy()
+ //}
+ // ends: involve ops from atproto/packages/dev-env/src/ozone.ts <<<
+
/** @type {import('net').AddressInfo} */
const addr = httpServer.address()
- httpLogger.info(`Ozone is running at http://localhost:${addr.port}`)
+ ozone.httpLogger.info(`Ozone is running at http://localhost:${addr.port}`)
}
main().catch(console.error)

View File

@@ -0,0 +1,28 @@
--- a/services/ozone/api.js
+++ b/services/ozone/api.js
@@ -23,6 +23,7 @@
Database,
OzoneService,
envToCfg,
+ OzoneDaemon,
envToSecrets,
httpLogger,
readEnv,
@@ -79,10 +80,17 @@
httpLogger.info('ozone is running')
+ // Start OzoneDaemon for label events
+ httpLogger.info('starting ozone daemon')
+ const daemon = await OzoneDaemon.create(cfg, secrets)
+ await daemon.start()
+ httpLogger.info('ozone daemon is running')
+
// Graceful shutdown (see also https://aws.amazon.com/blogs/containers/graceful-shutdowns-with-ecs/)
process.on('SIGTERM', async () => {
httpLogger.info('ozone is stopping')
+ await daemon.destroy()
await ozone.destroy()
httpLogger.info('ozone is stopped')

View File

@@ -0,0 +1,33 @@
--- a/services/ozone/api.js
+++ b/services/ozone/api.js
@@ -20,6 +20,7 @@ const {
MultiImageInvalidator,
} = require('@atproto/aws')
const {
Database,
OzoneService,
+ OzoneDaemon,
envToCfg,
envToSecrets,
httpLogger,
@@ -76,10 +77,17 @@ const main = async () => {
const ozone = await OzoneService.create(cfg, secrets, { imgInvalidator })
await ozone.start()
httpLogger.info('ozone is running')
+ // Start OzoneDaemon for label events
+ httpLogger.info('starting ozone daemon')
+ const daemon = await OzoneDaemon.create(cfg, secrets)
+ await daemon.start()
+ httpLogger.info('ozone daemon is running')
+
// Graceful shutdown (see also https://aws.amazon.com/blogs/containers/graceful-shutdowns-with-ecs/)
process.on('SIGTERM', async () => {
httpLogger.info('ozone is stopping')
+ await daemon.destroy()
await ozone.destroy()
httpLogger.info('ozone is stopped')

View File

@@ -0,0 +1,160 @@
--- a/services/bsky/api.js 2025-12-03 11:04:54
+++ b/services/bsky/api.js 2025-12-03 11:00:02
@@ -1,62 +1,105 @@
/* eslint-env node */
/* eslint-disable import/order */
-
+// https://github.com/bluesky-social/atproto/blob/main/services/bsky/api.js
'use strict'
-const dd = require('dd-trace')
+//const dd = require('dd-trace')
+//
+//dd.tracer
+// .init()
+// .use('http2', {
+// client: true, // calls into dataplane
+// server: false,
+// })
+// .use('express', {
+// hooks: {
+// request: (span, req) => {
+// maintainXrpcResource(span, req)
+// },
+// },
+// })
-dd.tracer
- .init()
- .use('http2', {
- client: true, // calls into dataplane
- server: false,
- })
- .use('express', {
- hooks: {
- request: (span, req) => {
- maintainXrpcResource(span, req)
- },
- },
- })
-
// modify tracer in order to track calls to dataplane as a service with proper resource names
const DATAPLANE_PREFIX = '/bsky.Service/'
-const origStartSpan = dd.tracer._tracer.startSpan
-dd.tracer._tracer.startSpan = function (name, options) {
- if (
- name !== 'http.request' ||
- options?.tags?.component !== 'http2' ||
- !options?.tags?.['http.url']
- ) {
- return origStartSpan.call(this, name, options)
- }
- const uri = new URL(options.tags['http.url'])
- if (!uri.pathname.startsWith(DATAPLANE_PREFIX)) {
- return origStartSpan.call(this, name, options)
- }
- options.tags['service.name'] = 'dataplane-bsky'
- options.tags['resource.name'] = uri.pathname.slice(DATAPLANE_PREFIX.length)
- return origStartSpan.call(this, name, options)
-}
+//const origStartSpan = dd.tracer._tracer.startSpan
+//dd.tracer._tracer.startSpan = function (name, options) {
+// if (
+// name !== 'http.request' ||
+// options?.tags?.component !== 'http2' ||
+// !options?.tags?.['http.url']
+// ) {
+// return origStartSpan.call(this, name, options)
+// }
+// const uri = new URL(options.tags['http.url'])
+// if (!uri.pathname.startsWith(DATAPLANE_PREFIX)) {
+// return origStartSpan.call(this, name, options)
+// }
+// options.tags['service.name'] = 'dataplane-bsky'
+// options.tags['resource.name'] = uri.pathname.slice(DATAPLANE_PREFIX.length)
+// return origStartSpan.call(this, name, options)
+//}
// Tracer code above must come before anything else
const assert = require('node:assert')
const cluster = require('node:cluster')
const path = require('node:path')
-const { BskyAppView, ServerConfig } = require('@atproto/bsky')
-const { Secp256k1Keypair } = require('@atproto/crypto')
+const bsky = require('/app/packages/bsky') // import all bsky features
+const { Secp256k1Keypair } = require('/app/packages/crypto')
const main = async () => {
const env = getEnv()
- const config = ServerConfig.readEnv()
+ const config = bsky.ServerConfig.readEnv()
assert(env.serviceSigningKey, 'must set BSKY_SERVICE_SIGNING_KEY')
const signingKey = await Secp256k1Keypair.import(env.serviceSigningKey)
- const bsky = BskyAppView.create({ config, signingKey })
- await bsky.start()
+
+// starts: involve logics in packages/dev-env/src/bsky.ts >>>>>>>>>>>>>
+// Separate migration db in case migration changes some connection state that we need in the tests, e.g. "alter database ... set ..."
+ const migrationDb = new bsky.Database({
+ url: env.dbPostgresUrl,
+ schema: env.dbPostgresSchema,
+ })
+ if (env.migration) {
+ await migrationDb.migrateToOrThrow(env.migration)
+ } else {
+ await migrationDb.migrateToLatestOrThrow()
+ }
+ await migrationDb.close()
+
+ const db = new bsky.Database({
+ url: env.dbPostgresUrl,
+ schema: env.dbPostgresSchema,
+ poolSize: 10,
+ })
+
+ const dataplane = await bsky.DataPlaneServer.create(
+ db,
+ env.dataplanePort,
+ config.didPlcUrl
+ )
+
+ const bsync = await bsky.MockBsync.create(db, env.bsyncPort)
+
+// ends: involve logics in packages/dev-env/src/bsky.ts <<<<<<<<<<<<<
+
+ const server = bsky.BskyAppView.create({ config, signingKey })
+// starts: involve logics in packages/dev-env/src/bsky.ts >>>>>>>>>>>>>
+ const sub = new bsky.RepoSubscription({
+ service: env.repoProvider,
+ db,
+ idResolver: dataplane.idResolver,
+ background: new bsky.BackgroundQueue(db),
+ })
+// ends: involve logics in packages/dev-env/src/bsky.ts <<<<<<<<<<<<<
+ await server.start()
+ sub.start() // involve logics in packages/dev-env/src/bsky.ts
// Graceful shutdown (see also https://aws.amazon.com/blogs/containers/graceful-shutdowns-with-ecs/)
const shutdown = async () => {
- await bsky.destroy()
+ await server.destroy()
+ await bsync.destroy()
+ await dataplane.destroy()
+ await sub.destroy()
+ await db.close()
}
process.on('SIGTERM', shutdown)
process.on('disconnect', shutdown) // when clustering
@@ -64,6 +107,12 @@
const getEnv = () => ({
serviceSigningKey: process.env.BSKY_SERVICE_SIGNING_KEY || undefined,
+ dbPostgresUrl: process.env.BSKY_DB_POSTGRES_URL || undefined,
+ dbPostgresSchema: process.env.BSKY_DB_POSTGRES_SCHEMA || undefined,
+ dataplanePort : maybeParseInt(process.env.BSKY_DATAPLANE_PORT) || undefined,
+ bsyncPort : maybeParseInt(process.env.BSKY_BSYNC_PORT) || undefined,
+ migration: process.env.ENABLE_MIGRATIONS === 'true' || undefined,
+ repoProvider: process.env.BSKY_REPO_PROVIDER || undefined
})
const maybeParseInt = (str) => {

View File

@@ -0,0 +1,20 @@
--- a/services/pds/index.js 2025-12-03 11:04:54
+++ b/services/pds/index.js 2025-12-02 22:11:39
@@ -1,5 +1,5 @@
/* eslint-env node */
-
+// https://github.com/bluesky-social/atproto/blob/main/services/pds/index.js
'use strict'
const {
@@ -8,8 +8,8 @@
envToSecrets,
httpLogger,
readEnv,
-} = require('@atproto/pds')
-const pkg = require('@atproto/pds/package.json')
+} = require('/app/packages/pds')
+const pkg = require('/app/packages/pds/package.json')
const main = async () => {
const env = readEnv()

View File

@@ -0,0 +1,17 @@
--- a/src/state/geolocation/const.ts
+++ b/src/state/geolocation/const.ts
@@ -3,9 +3,10 @@ import {BAPP_CONFIG_DEV_URL, IS_DEV} from '#/env'
import {type Device} from '#/storage'
export const IPCC_URL = `https://bsky.app/ipcc`
-export const BAPP_CONFIG_URL_PROD = `https://ip.bsky.app/config`
-export const BAPP_CONFIG_URL = IS_DEV
- ? (BAPP_CONFIG_DEV_URL ?? BAPP_CONFIG_URL_PROD)
- : BAPP_CONFIG_URL_PROD
+// Disabled for self-hosted environment to avoid CORS errors
+// export const BAPP_CONFIG_URL_PROD = `https://ip.bsky.app/config`
+// export const BAPP_CONFIG_URL = IS_DEV
+// ? (BAPP_CONFIG_DEV_URL ?? BAPP_CONFIG_URL_PROD)
+// : BAPP_CONFIG_URL_PROD
+export const BAPP_CONFIG_URL = null
export const GEOLOCATION_CONFIG_URL = BAPP_CONFIG_URL

View File

@@ -0,0 +1,44 @@
diff --git a/src/state/session/agent.ts b/src/state/session/agent.ts
index 36d19299b..ba095436a 100644
--- a/src/state/session/agent.ts
+++ b/src/state/session/agent.ts
@@ -39,7 +39,8 @@ export function createPublicAgent() {
configureModerationForGuest() // Side effect but only relevant for tests
const agent = new BskyAppAgent({service: PUBLIC_BSKY_SERVICE})
- agent.configureProxy(BLUESKY_PROXY_HEADER.get())
+ // Disable proxy for self-hosted environments
+ // agent.configureProxy(BLUESKY_PROXY_HEADER.get())
return agent
}
@@ -77,7 +78,8 @@ export async function createAgentAndResume(
}
}
- agent.configureProxy(BLUESKY_PROXY_HEADER.get())
+ // Disable proxy for self-hosted environments
+ // agent.configureProxy(BLUESKY_PROXY_HEADER.get())
return agent.prepare(gates, moderation, onSessionChange)
}
@@ -112,7 +114,8 @@ export async function createAgentAndLogin(
const gates = tryFetchGates(account.did, 'prefer-fresh-gates')
const moderation = configureModerationForAccount(agent, account)
- agent.configureProxy(BLUESKY_PROXY_HEADER.get())
+ // Disable proxy for self-hosted environments
+ // agent.configureProxy(BLUESKY_PROXY_HEADER.get())
return agent.prepare(gates, moderation, onSessionChange)
}
@@ -201,7 +204,8 @@ export async function createAgentAndCreateAccount(
logger.error(e, {message: `session: failed snoozeEmailConfirmationPrompt`})
}
- agent.configureProxy(BLUESKY_PROXY_HEADER.get())
+ // Disable proxy for self-hosted environments
+ // agent.configureProxy(BLUESKY_PROXY_HEADER.get())
return agent.prepare(gates, moderation, onSessionChange)
}