ai/at
1
0
Files
at/install.zsh
2025-12-06 18:32:12 +09:00

532 lines
16 KiB
Bash
Executable File

#!/bin/zsh
function at-repos-env() {
APP_PASSWORD=xxx
host=syu.is
handle=ai.syui.ai
did=did:plc:6qyecktefllvenje24fcxnie
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"
"feed"
)
handles=(
"syui.syui.ai"
"ai.syui.ai"
"apple.syu.is"
)
d=${0:a:h}
dh=${0:a:h:h}
name=${host%%.*}
domain=${host##*.}
dport=5000
}
# Arrays for patch management
typeset -a FAILED_PATCHES
# Patch file lists
typeset -a PATCH_FILES_CURL
PATCH_FILES_CURL=(
"4367-atproto-services-bsky-api.diff:https://raw.githubusercontent.com/bluesky-social/atproto/refs/heads/main/services/bsky/api.js:services/bsky/api.js"
"4367-atproto-services-pds-index.diff:https://raw.githubusercontent.com/bluesky-social/atproto/refs/heads/main/services/pds/index.js:services/pds/index.js"
)
typeset -a PATCH_FILES
PATCH_FILES=(
"170-pds-oauth-same-site-fix.patch"
"8980-social-app-disable-proxy.diff"
"disable-statsig-sdk.diff"
"140-social-app-yarn-network-timeout.patch"
"130-atproto-ozone-enable-daemon-v2.patch"
"190-bgs-disable-ratelimit.patch"
"200-feed-generator-custom.patch"
)
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 -u
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-avatar-write() {
did_admin=did:plc:6qyecktefllvenje24fcxnie
dt=$d/repos/social-app/src
cd $dt
grep -R syu.is .|cut -d : -f 1|sort -u|xargs sed -i "s/syu.is/${host}/g"
grep -R web.syu.is .|cut -d : -f 1|sort -u|xargs sed -i "s/web.syu.is/web.${host}/g"
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
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"
}
# Common patch function with status detection
function apply-patch() {
local patch_name=$1
local target_dir=$2
local patch_file=$3
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📝 Patch: ${patch_name}"
echo " Target: ${target_dir}"
echo " File: ${patch_file}"
pushd ${target_dir} > /dev/null
# Check if patch is already applied (reverse dry-run succeeds)
if patch --dry-run -p1 -R < ${patch_file} > /dev/null 2>&1; then
echo "✅ Already applied - skipping"
popd > /dev/null
echo ""
return 0
fi
# Check if patch can be applied (forward dry-run succeeds)
if patch --dry-run -p1 < ${patch_file} > /dev/null 2>&1; then
echo "🔧 Applying patch..."
if patch -p1 < ${patch_file}; then
echo "✅ Applied successfully"
popd > /dev/null
echo ""
return 0
else
echo "❌ Failed to apply"
FAILED_PATCHES+=("${patch_name} (${patch_file})")
popd > /dev/null
echo ""
return 1
fi
else
echo "⚠️ Cannot apply - file may have been modified"
echo " Please check manually"
FAILED_PATCHES+=("${patch_name} (${patch_file}) - file modified")
popd > /dev/null
echo ""
return 1
fi
}
# Function to display failed patches summary
function show-failed-patches() {
if [ ${#FAILED_PATCHES[@]} -eq 0 ]; then
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ All patches applied successfully!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
return 0
fi
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "⚠️ FAILED PATCHES SUMMARY"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "The following patches could not be applied:"
echo ""
for failed_patch in "${FAILED_PATCHES[@]}"; do
echo "${failed_patch}"
done
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
}
# Helper function for applying patches
function patch-apply() {
local name=$1
local target=$2
local patch_file=$3
apply-patch "${name}" "$d/repos/${target}" "$d/patching/${patch_file}"
}
# Helper function for patches with curl download
function patch-apply-with-curl() {
local name=$1
local target=$2
local patch_file=$3
local download_url=$4
local download_target=$5
curl -sL "${download_url}" -o "$d/repos/${target}/${download_target}"
apply-patch "${name}" "$d/repos/${target}" "$d/patching/${patch_file}"
}
# Auto-apply patches from list
function at-repos-patch-apply-all() {
# Apply curl patches
for patch_info in "${PATCH_FILES_CURL[@]}"; do
local filename="${patch_info%%:*}"
local rest="${patch_info#*:}"
local download_url="${rest%%:*}"
local download_target="${rest#*:}"
local title="${filename%.*}"
local repo=""
# Determine repo from filename
if [[ $filename == *"atproto"* ]]; then
repo="atproto"
elif [[ $filename == *"pds"* ]]; then
repo="atproto"
fi
patch-apply-with-curl "$title" "$repo" "$filename" "$download_url" "$download_target"
done
# Apply regular patches
for filename in "${PATCH_FILES[@]}"; do
local title="${filename%.*}"
local repo=""
# Determine repo from filename
if [[ $filename == *"social-app"* || $filename == *"statsig"* ]]; then
repo="social-app"
elif [[ $filename == *"atproto"* ]]; then
repo="atproto"
elif [[ $filename == *"pds"* ]]; then
repo="atproto"
elif [[ $filename == *"indigo"* || $filename == *"bgs"* ]]; then
repo="indigo"
elif [[ $filename == *"feed"* ]]; then
repo="feed-generator"
fi
patch-apply "$title" "$repo" "$filename"
done
}
function at-repos-ozone-patch() {
cd $d/repos
d_=$d/repos/ozone
rm -rf ${d_}
git clone https://github.com/bluesky-social/ozone
apply-patch "Ozone enable daemon" "${d_}" "$d/patching/122-ozone-enable-daemon.diff"
if [ -f "$d/patching/150-ozone-plc-fix.patch" ]; then
apply-patch "Ozone plc fix" "${d_}" "$d/patching/150-ozone-plc-fix.patch"
fi
if [ -f "$d/patching/160-ozone-oauth-redirect-fix.patch" ]; then
apply-patch "Ozone oauth redirect fix" "${d_}" "$d/patching/160-ozone-oauth-redirect-fix.patch"
fi
# Apply constants fix and do additional sed replacements
pushd ${d_} > /dev/null
if [ -f "$d/patching/121-ozone-constants-fix.patch" ]; then
patch -p1 < "$d/patching/121-ozone-constants-fix.patch" 2>/dev/null || true
fi
# Replace process.env with env()
sed -i 's/process\.env\.\(NEXT_PUBLIC_[A-Z_]*\)/env('\''\1'\'')/g' lib/constants.ts 2>/dev/null || true
sed -i 's/process\.env\.NODE_ENV/env('\''NODE_ENV'\'')/g' lib/constants.ts 2>/dev/null || true
# Add missing SOCIAL_APP_DOMAIN constant after SOCIAL_APP_URL
sed -i '/^export const SOCIAL_APP_URL =/,/^$/{ /^$/a\
export const SOCIAL_APP_DOMAIN =\
env('\''NEXT_PUBLIC_SOCIAL_APP_DOMAIN'\'') || '\''bsky.app'\''\
}' lib/constants.ts 2>/dev/null || true
# Fix multiline process.env patterns
sed -i '/^export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/,/^ : 7$/ {
s/^export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = env('\''NEXT_PUBLIC_NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS'\'')/
/^ \.NEXT_PUBLIC_NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS$/d
}' lib/constants.ts 2>/dev/null || true
sed -i '/^export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/,/^ : 30$/ {
s/^export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = env('\''NEXT_PUBLIC_YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS'\'')/
/^ \.NEXT_PUBLIC_YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS$/d
}' lib/constants.ts 2>/dev/null || true
sed -i '/^export const HIGH_PROFILE_FOLLOWER_THRESHOLD = process\.env$/,/^ : Infinity$/ {
s/^export const HIGH_PROFILE_FOLLOWER_THRESHOLD = process\.env$/export const HIGH_PROFILE_FOLLOWER_THRESHOLD = env('\''NEXT_PUBLIC_HIGH_PROFILE_FOLLOWER_THRESHOLD'\'')/
/^ \.NEXT_PUBLIC_HIGH_PROFILE_FOLLOWER_THRESHOLD$/d
}' lib/constants.ts 2>/dev/null || true
# Fix parseInt() to handle undefined by adding || ''
sed -i "s/parseInt(env('\([^']*\)'))/parseInt(env('\1') || '0')/g" lib/constants.ts 2>/dev/null || true
popd > /dev/null
}
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() {
if [ -n "$(docker ps -q -f name=registry)" ]; then
echo "Registry is already running."
docker restart registry
docker stop registry
docker rm registry
docker volume rm registry-data 2>/dev/null || true
fi
docker run -d -p ${dport}:${dport} --name registry \
--restart=always \
-v registry-data:/var/lib/registry \
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}-web: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
}
function at-repos-reset-bgs-db() {
dp=at-database-1
BGS_ADMIN_KEY=`cat $d/envs/bgs | grep BGS_ADMIN_KEY | cut -d '=' -f 2`
echo "🛑 Stopping BGS..."
docker compose stop bgs
echo "🗑️ Cleaning data..."
sudo rm -rf $d/data/bgs/*
echo "♻️ Resetting Databases..."
docker exec -i $dp psql -U postgres -c "DROP DATABASE IF EXISTS bgs;"
docker exec -i $dp psql -U postgres -c "CREATE DATABASE bgs;"
docker exec -i $dp psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE bgs TO postgres;"
docker exec -i $dp psql -U postgres -c "DROP DATABASE IF EXISTS carstore;"
docker exec -i $dp psql -U postgres -c "CREATE DATABASE carstore;"
docker exec -i $dp psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE carstore TO postgres;"
echo "🚀 Starting BGS to initialize tables..."
docker compose up -d bgs
echo "⏳ Waiting 10s for BGS migration..."
sleep 10
echo "⚙️ Updating Slurp Config..."
docker exec -i $dp psql -U postgres -d bgs -c "UPDATE slurp_configs SET new_subs_disabled = false, new_pds_per_day_limit = 1000 WHERE id = 1;"
echo "🔗 Registering Trusted Domain & Resetting Repos..."
# Retry loop for addTrustedDomain as BGS might still be warming up
for i in {1..5}; do
if curl -f -X POST "https://bgs.${host}/admin/pds/addTrustedDomain?domain=${host}" -H "Authorization: Bearer ${BGS_ADMIN_KEY}"; then
echo "✅ Trusted domain registered"
break
fi
echo "Bot failed to contact BGS (attempt $i/5)... waiting 5s"
sleep 5
done
for ((i=1; i<=${#handles}; i++)); do
handle=${handles[$i]}
did=`curl -sL "https://${host}/xrpc/com.atproto.repo.describeRepo?repo=${handle}" |jq -r .did`
if [ ! -z "$did" ] && [ "$did" != "null" ]; then
echo "Resetting repo: $handle ($did)"
curl -X POST "https://bgs.${host}/admin/repo/reset?did=${did}" \
-H "Authorization: Bearer ${BGS_ADMIN_KEY}"
else
echo "Skipping reset for $handle (DID not found)"
fi
done
}
function at-repos-feed-generator-start-push() {
cd $d/repos/feed-generator
yarn install
FEEDGEN_HANDLE=${handle}
FEEDGEN_PASSWORD=${APP_PASSWORD}
FEEDGEN_RECORD_NAME=app
FEEDGEN_AVATAR=$d/repos/atproto/packages/dev-env/assets/at.png
npx tsx scripts/publish.ts
}
function at-repos-feed-generator-update() {
resp=$(curl -sL -X POST -H "Content-Type: application/json" -d "{\"identifier\":\"$handle\",\"password\":\"${APP_PASSWORD}\"}" https://${host}/xrpc/com.atproto.server.createSession)
token=$(echo $resp | jq -r .accessJwt)
if [ -z "$token" ] || [ "$token" == "null" ]; then
echo "Login failed: $resp"
exit 1
fi
avatar_json="{\"\$type\":\"blob\",\"ref\":{\"\$link\":\"${img_id}\"},\"mimeType\":\"image/jpeg\",\"size\":375259}"
# 3. Delete cmd record
#curl -sL -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $token" \
# -d "{\"repo\":\"$handle\",\"collection\":\"app.bsky.feed.generator\",\"rkey\":\"cmd\"}" \
# https://${host}/xrpc/com.atproto.repo.deleteRecord
# 4. Put app record
echo "Creating app record..."
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
# Create JSON payload
# Note: feeding avatar_json directly into jq
payload=$(jq -n \
--arg repo "$handle" \
--arg collection "app.bsky.feed.generator" \
--arg rkey "app" \
--arg did "did:web:feed.${host}" \
--arg type "app.bsky.feed.generator" \
--arg created "$now" \
--arg display "App Feed" \
--arg desc "Automated App Feed" \
--argjson avatar "$avatar_json" \
'{
repo: $repo,
collection: $collection,
rkey: $rkey,
record: {
did: $did,
"$type": $type,
createdAt: $created,
displayName: $display,
description: $desc,
avatar: $avatar
}
}')
curl -sL -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $token" \
-d @- \
https://${host}/xrpc/com.atproto.repo.putRecord
}
at-repos-env
case "$1" in
pull)
at-repos-clone
at-repos-pull
exit
;;
patch)
at-repos-social-app-avatar-write
at-repos-patch-apply-all
at-repos-ozone-patch
show-failed-patches
exit
;;
build)
at-repos-build-docker-atproto $2
exit
;;
push)
at-repos-push-docker $2
exit
;;
reset)
at-repos-push-reset
exit
;;
down)
cd $d;docker compose down
exit
;;
feed-push)
at-repos-feed-generator-start-push
exit
;;
esac
case "`cat /etc/hostname`" in
at)
if [ "$1" = "bgs-reset" ];then
at-repos-reset-bgs-db
exit
fi
at-repos-pull-docker
exit
;;
*)
at-repos-clone
at-repos-pull
at-repos-social-app-avatar-write
at-repos-patch-apply-all
at-repos-ozone-patch
show-failed-patches
at-repos-build-docker-atproto
at-repos-push-docker
cd $d; docker compose down
;;
esac