apiVersion: v1
kind: Service
metadata:
  name: bsky
  namespace: atproto
spec:
  selector:
    app: bsky
  ports:
    - name: api
      port: 2584
      targetPort: 2584
    - name: dataplane
      port: 3001
      targetPort: 3001
    - name: bsync
      port: 3002
      targetPort: 3002
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: bsky-data
  namespace: atproto
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bsky
  namespace: atproto
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bsky
  template:
    metadata:
      labels:
        app: bsky
    spec:
      securityContext:
        runAsUser: 0
      containers:
        - name: bsky
          image: registry/bsky
          command: ["node", "--enable-source-maps", "api.js"]
          ports:
            - containerPort: 2584
            - containerPort: 3001
            - containerPort: 3002
          env:
            - name: BSKY_PORT
              value: "2584"
            - name: BSKY_BLOB_CACHE_LOC
              value: "/data/"
            - name: BSKY_BSYNC_HTTP_VERSION
              value: "1.1"
            - name: BSKY_BSYNC_PORT
              value: "3002"
            - name: BSKY_BSYNC_URL
              value: "http://localhost:3002"
            - name: BSKY_COURIER_URL
              value: "http://fake-courier.example.invalid/"
            - name: BSKY_DATAPLANE_HTTP_VERSION
              value: "1.1"
            - name: BSKY_DATAPLANE_PORT
              value: "3001"
            - name: BSKY_DATAPLANE_URLS
              value: "http://localhost:3001"
            - name: BSKY_DB_POSTGRES_URL
              value: "postgres://postgres:postgres@database/bsky"
            - name: BSKY_DID_PLC_URL
              value: "https://plc.syu.is"
            - name: BSKY_PUBLIC_URL
              value: "https://bsky.syu.is"
            - name: BSKY_REPO_PROVIDER
              value: "ws://bgs:2470"
            - name: BSKY_SERVER_DID
              value: "did:web:bsky.syu.is"
            - name: MOD_SERVICE_DID
              value: "did:web:ozone.syu.is"
            - name: BSKY_ADMIN_PASSWORDS
              valueFrom:
                secretKeyRef:
                  name: atproto-secrets
                  key: bsky-admin-passwords
            - name: BSKY_SERVICE_SIGNING_KEY
              valueFrom:
                secretKeyRef:
                  name: atproto-secrets
                  key: bsky-service-signing-key
          volumeMounts:
            - name: data
              mountPath: /data
          livenessProbe:
            httpGet:
              path: /xrpc/_health
              port: 2584
            initialDelaySeconds: 15
            periodSeconds: 10
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /xrpc/_health
              port: 2584
            initialDelaySeconds: 5
            periodSeconds: 5
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: bsky-data
---
## bsky subscription monitor
## subscriptionが停止していたらPodを再起動する
apiVersion: batch/v1
kind: CronJob
metadata:
  name: bsky-subscription-watchdog
  namespace: atproto
spec:
  schedule: "*/5 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: watchdog
              image: postgres:16-alpine
              command:
                - /bin/sh
                - -c
                - |
                  # BGSの最新seqを取得
                  LATEST_SEQ=$(psql -t -A "$DB_URL" -c "SELECT COALESCE(MAX(seq),0) FROM repo_event_records")
                  # bskyのsubscription cursorを取得
                  BSKY_CURSOR=$(psql -t -A "$BSKY_DB_URL" -c "SELECT COALESCE(state,0) FROM subscription WHERE service='ws://bgs:2470' LIMIT 1")
                  LAG=$((LATEST_SEQ - BSKY_CURSOR))
                  echo "BGS seq=$LATEST_SEQ, bsky cursor=$BSKY_CURSOR, lag=$LAG"
                  if [ "$LAG" -gt 50 ]; then
                    echo "WARN: bsky subscription lag=$LAG, restarting bsky pod"
                    # Podを削除すればDeploymentが再作成する
                    apk add --no-cache curl > /dev/null 2>&1
                    APISERVER=https://kubernetes.default.svc
                    TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
                    NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
                    # bsky podを取得して削除
                    POD=$(curl -s -k -H "Authorization: Bearer $TOKEN" \
                      "$APISERVER/api/v1/namespaces/$NAMESPACE/pods?labelSelector=app=bsky" \
                      | grep -o '"name":"bsky-[^"]*"' | head -1 | cut -d'"' -f4)
                    if [ -n "$POD" ]; then
                      curl -s -k -X DELETE -H "Authorization: Bearer $TOKEN" \
                        "$APISERVER/api/v1/namespaces/$NAMESPACE/pods/$POD"
                      echo "Deleted pod $POD"
                    fi
                  else
                    echo "OK: subscription is healthy"
                  fi
              env:
                - name: DB_URL
                  value: "postgres://postgres:postgres@database/bgs"
                - name: BSKY_DB_URL
                  value: "postgres://postgres:postgres@database/bsky"
          restartPolicy: OnFailure
          serviceAccountName: bsky-watchdog
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: bsky-watchdog
  namespace: atproto
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: bsky-watchdog
  namespace: atproto
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: bsky-watchdog
  namespace: atproto
subjects:
  - kind: ServiceAccount
    name: bsky-watchdog
    namespace: atproto
roleRef:
  kind: Role
  name: bsky-watchdog
  apiGroup: rbac.authorization.k8s.io
