From 8b97cbfd9b1d3fe8da13f042d31e37fa84768c15 Mon Sep 17 00:00:00 2001
From: syui <syui@syui.ai>
Date: Mon, 5 May 2025 08:15:49 +0900
Subject: [PATCH] add ozone

---
 compose.yml                            |  85 ++++++++++--------
 envs/jetstream                         |   5 ++
 envs/ozone                             |   6 ++
 install.zsh                            |  25 ++++++
 patching/120-ozone-runtimeEnvVars.diff | 119 +++++++++++++++++++++++++
 patching/122-ozone-enable-daemon.diff  |  82 +++++++++++++++++
 scpt/at.zsh                            |   2 +-
 scpt/src/ai/syui/game/character.zsh    |  16 ++--
 scpt/src/func.zsh                      |   8 +-
 scpt/src/is/syu/main.zsh               |   5 ++
 10 files changed, 306 insertions(+), 47 deletions(-)
 create mode 100644 envs/jetstream
 create mode 100644 patching/120-ozone-runtimeEnvVars.diff
 create mode 100644 patching/122-ozone-enable-daemon.diff

diff --git a/compose.yml b/compose.yml
index 88a53f9..813393a 100644
--- a/compose.yml
+++ b/compose.yml
@@ -67,41 +67,6 @@ services:
       database:
         condition: service_healthy
 
-#  ozone:
-#    ports:
-#      - 2585:3000
-#    build:
-#      context: ./repos/atproto/
-#      dockerfile: services/ozone/Dockerfile
-#    restart: always
-#    command: node --enable-source-maps api.js
-#    volumes:
-#      - ./data/ozone/:/data/
-#      - ./repos/ozone.js:/app/services/ozone/api.js:ro
-#    env_file:
-#      - ./envs/ozone
-#    healthcheck:
-#      test: "wget -q --spider http://localhost:3000/xrpc/_health"
-#      interval: 5s
-#      retries: 20
-#    depends_on:
-#      database:
-#        condition: service_healthy
-#
-#  ozone-daemon:
-#    build:
-#      context: ./repos/atproto/
-#      dockerfile: services/ozone/Dockerfile
-#    restart: always
-#    command: node --enable-source-maps daemon.js
-#    env_file:
-#      - ./envs/ozone
-#    depends_on:
-#      ozone:
-#        condition: service_healthy
-#      database:
-#        condition: service_healthy
-
   social-app:
     ports:
       - 8100:8100
@@ -131,3 +96,53 @@ services:
         condition: service_healthy
       redis:
         condition: service_healthy
+
+  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
+
+  ozone-daemon:
+    build:
+      context: ./repos/atproto/
+      dockerfile: services/ozone/Dockerfile
+    restart: always
+    command: node --enable-source-maps daemon.js
+    volumes:
+      - ./data/ozone/:/data/
+    env_file:
+      - ./envs/ozone
diff --git a/envs/jetstream b/envs/jetstream
new file mode 100644
index 0000000..324bbd7
--- /dev/null
+++ b/envs/jetstream
@@ -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
diff --git a/envs/ozone b/envs/ozone
index 72b0158..514fea6 100644
--- a/envs/ozone
+++ b/envs/ozone
@@ -19,3 +19,9 @@ 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
diff --git a/install.zsh b/install.zsh
index 51858e6..4324b2a 100755
--- a/install.zsh
+++ b/install.zsh
@@ -15,6 +15,8 @@ function at-repos-env() {
 		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
 	)
 	d=${0:a:h}
 	dh=${0:a:h:h}
@@ -144,6 +146,28 @@ function at-repos-social-app-patch() {
 	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
+
+	#cp -rf $d/repos/atproto/service/ozone/* $d/ozone/service/
+}
+
 function at-repos-docker() {
 	cd $d
 	docker compose build && docker compose up -d
@@ -157,6 +181,7 @@ at-repos-social-app-icon-origin
 at-repos-social-app-write
 at-repos-bsky-patch
 at-repos-social-app-patch
+at-repos-ozone-patch
 
 echo "[y]docker compose build && up"
 read key
diff --git a/patching/120-ozone-runtimeEnvVars.diff b/patching/120-ozone-runtimeEnvVars.diff
new file mode 100644
index 0000000..4f369a8
--- /dev/null
+++ b/patching/120-ozone-runtimeEnvVars.diff
@@ -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",
diff --git a/patching/122-ozone-enable-daemon.diff b/patching/122-ozone-enable-daemon.diff
new file mode 100644
index 0000000..3b783df
--- /dev/null
+++ b/patching/122-ozone-enable-daemon.diff
@@ -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)
diff --git a/scpt/at.zsh b/scpt/at.zsh
index 6987456..c10ccd7 100755
--- a/scpt/at.zsh
+++ b/scpt/at.zsh
@@ -61,7 +61,7 @@ case $1 in
 		verify-icon
 		;;
 	self-col)
-		self-col
+		self-col $2
 		;;
 	*)
 		echo "${help[@]}"
diff --git a/scpt/src/ai/syui/game/character.zsh b/scpt/src/ai/syui/game/character.zsh
index 49b229d..5853f0c 100755
--- a/scpt/src/ai/syui/game/character.zsh
+++ b/scpt/src/ai/syui/game/character.zsh
@@ -219,8 +219,8 @@ delete_game_user() {
 function create_game_user() {
 	col=ai.syui.game
 	rkey=$1
-	did=$did
-	handle=$handle
+	handle_yui=$handle
+	did_yui=$did
 	pds=`cat $f|jq -r ".didDoc.service.[].serviceEndpoint"|cut -d / -f 3`
 	if [ "$pds" != "syu.is" ];then
 		pds=bsky.social
@@ -249,6 +249,8 @@ function create_game_user() {
 	if [ "$rkey" = "self" ];then
 		cname=manny
 		unset did handle gender
+		pds=bsky.social
+		plc=plc.directory
 	fi
 	imguri=https://cdn.bsky.app/img/feed_thumbnail/plain/$did_yui/${cid}@jpeg
 	season=1
@@ -286,8 +288,8 @@ function create_game_user() {
 
 	v1_json="
 	{
-		\"repo\": \"$handle\",
-		\"did\": \"$did\",
+		\"repo\": \"$handle_yui\",
+		\"did\": \"$did_yui\",
 		\"collection\": \"$col\",
 		\"rkey\": \"$rkey\",
 		\"record\": {
@@ -313,8 +315,8 @@ json_item="
 
 	json="
 	{
-		\"repo\": \"$handle\",
-		\"did\": \"$did\",
+		\"repo\": \"$handle_yui\",
+		\"did\": \"$did_yui\",
 		\"collection\": \"$col\",
 		\"rkey\": \"$rkey\",
 		\"record\": {
@@ -345,7 +347,7 @@ if echo $json|jq . ;then
 	read y
 	if [ "y" = "$y" ];then
 		t=`curl -sL -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d $json $url`
-		#echo $t|jq .
+		echo $t|jq .
 		#f=~/ai/ue/json/v${version}_${rkey}.json
 		#if [ -f $f ];then
 		#	get_game_user $rkey >! $f
diff --git a/scpt/src/func.zsh b/scpt/src/func.zsh
index aa19d31..34dc78b 100644
--- a/scpt/src/func.zsh
+++ b/scpt/src/func.zsh
@@ -10,6 +10,10 @@ function at-env() {
 	did=$did_yui
 	created=2025-04-21T00:00:00.323Z
 	docs_uri=https://docs.bsky.app/docs/api
+	at_uri=at://$handle/ai.syui.game/ai
+	did=`echo $at_uri|cut -d / -f 3`
+	collection=`echo $at_uri|cut -d / -f 4`
+	rkey=`echo $at_uri|cut -d / -f 5`
 	d=${0:a:h}
 	f=~/.config/ai/token.json 
 	if [ -f $f ];then
@@ -23,10 +27,6 @@ function at-env() {
 		plc=plc.syu.is
 		pds=$host
 	fi
-	at_uri=at://$handle/ai.syui.game/ai
-	did=`echo $at_uri|cut -d / -f 3`
-	collection=`echo $at_uri|cut -d / -f 4`
-	rkey=`echo $at_uri|cut -d / -f 5`
 }
 
 function at-unset() {
diff --git a/scpt/src/is/syu/main.zsh b/scpt/src/is/syu/main.zsh
index 0bfed20..9d5a3bb 100644
--- a/scpt/src/is/syu/main.zsh
+++ b/scpt/src/is/syu/main.zsh
@@ -1,6 +1,11 @@
 #!/bin/zsh
 
 function self-col(){
+	if [ -n "$1" ];then
+		t=`curl -sL "$pds/xrpc/com.atproto.repo.listRecords?repo=$handle&collection=$1"`
+		echo $t|jq .
+		exit
+	fi
 	col=(
 		ai.syui.game
 		ai.syui.system