this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Opake deployment prep

+833 -6
+19 -2
Makefile
··· 2 2 JUICEFS_METAURL := k8s/juicefs/metaurl.secret 3 3 TRANQUIL_DB_URL := k8s/pds/tranquil-database-url.secret 4 4 TRANQUIL_VALKEY_URL := k8s/pds/tranquil-valkey-url.secret 5 + OPAKE_STAGING_DB_URL := k8s/opake-staging/database-url.secret 5 6 6 7 .PHONY: secrets clean-secrets build 7 8 8 - secrets: $(JUICEFS_METAURL) $(TRANQUIL_DB_URL) $(TRANQUIL_VALKEY_URL) 9 + secrets: $(JUICEFS_METAURL) $(TRANQUIL_DB_URL) $(TRANQUIL_VALKEY_URL) $(OPAKE_STAGING_DB_URL) 9 10 10 11 $(JUICEFS_METAURL): k8s/juicefs/redis-password.secret 11 12 @pw=$$(cat $< | tr -d '\n') && \ ··· 22 23 @pw=$$(cat $< | tr -d '\n') && \ 23 24 printf 'redis://:%s@redis.juicefs.svc.cluster.local:6379/1' "$$pw" > $@ 24 25 26 + $(OPAKE_STAGING_DB_URL): k8s/postgres/opake-staging-password.secret 27 + @pw=$$(cat $< | tr -d '\n' | python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.stdin.read(),safe=""),end="")') && \ 28 + printf 'ecto://opake_staging:%s@postgres.postgres.svc.cluster.local:5432/opake_staging' "$$pw" > $@ 29 + 25 30 clean-secrets: 26 - rm -f $(JUICEFS_METAURL) $(TRANQUIL_DB_URL) $(TRANQUIL_VALKEY_URL) 31 + rm -f $(JUICEFS_METAURL) $(TRANQUIL_DB_URL) $(TRANQUIL_VALKEY_URL) $(OPAKE_STAGING_DB_URL) 27 32 28 33 # Tranquil PDS 29 34 TRANQUIL_REPO ?= /tmp/tranquil-pds ··· 43 48 44 49 push-tranquil-frontend: 45 50 docker push zot.sans-self.org/infra/tranquil-frontend:latest 51 + 52 + # Jetstream (self-hosted, ARM64) 53 + JETSTREAM_REPO ?= /tmp/jetstream 54 + 55 + .PHONY: build-jetstream push-jetstream 56 + 57 + build-jetstream: 58 + @test -d "$(JETSTREAM_REPO)" || { echo "error: jetstream repo not found at $(JETSTREAM_REPO)"; exit 1; } 59 + docker buildx build --platform linux/arm64 -t zot.sans-self.org/infra/jetstream:latest -f dockerfiles/jetstream.Dockerfile "$(JETSTREAM_REPO)" 60 + 61 + push-jetstream: 62 + docker push zot.sans-self.org/infra/jetstream:latest 46 63 47 64 # CI validate image 48 65 .PHONY: build-ci-validate push-ci-validate
+25
deploy-tranquil.sh
··· 1 + #!/usr/bin/env bash 2 + set -euo pipefail 3 + 4 + TRANQUIL_REPO="${TRANQUIL_REPO:-$HOME/Projects/tranquil-pds}" 5 + IMAGE="zot.sans-self.org/infra/tranquil-pds:latest" 6 + NAMESPACE="pds" 7 + DEPLOYMENT="tranquil-pds" 8 + 9 + if [[ ! -d "$TRANQUIL_REPO" ]]; then 10 + echo "error: tranquil-pds repo not found at $TRANQUIL_REPO" >&2 11 + exit 1 12 + fi 13 + 14 + echo "==> Building $IMAGE from $TRANQUIL_REPO" 15 + docker buildx build --platform linux/arm64 -t "$IMAGE" "$TRANQUIL_REPO" 16 + 17 + echo "==> Pushing $IMAGE" 18 + docker push "$IMAGE" 19 + 20 + echo "==> Restarting $DEPLOYMENT" 21 + kubectl rollout restart "deployment/$DEPLOYMENT" -n "$NAMESPACE" 22 + kubectl rollout status "deployment/$DEPLOYMENT" -n "$NAMESPACE" --timeout=120s 23 + 24 + echo "==> Done" 25 + kubectl get pods -n "$NAMESPACE" -l "app=$DEPLOYMENT"
+41
dns.tf
··· 98 98 ] 99 99 } 100 100 101 + resource "hcloud_zone_rrset" "opake_jetstream_a" { 102 + zone = hcloud_zone.opake.id 103 + name = "jetstream" 104 + type = "A" 105 + ttl = 300 106 + records = [ 107 + { value = local.cluster_ip }, 108 + ] 109 + } 110 + 111 + resource "hcloud_zone_rrset" "opake_staging_a" { 112 + zone = hcloud_zone.opake.id 113 + name = "staging" 114 + type = "A" 115 + ttl = 300 116 + records = [ 117 + { value = local.cluster_ip }, 118 + ] 119 + } 120 + 121 + resource "hcloud_zone_rrset" "opake_appview_staging_a" { 122 + zone = hcloud_zone.opake.id 123 + name = "appview.staging" 124 + type = "A" 125 + ttl = 300 126 + records = [ 127 + { value = local.cluster_ip }, 128 + ] 129 + } 130 + 131 + # atproto handle verification for opake.app 132 + resource "hcloud_zone_rrset" "opake_atproto" { 133 + zone = hcloud_zone.opake.id 134 + name = "_atproto" 135 + type = "TXT" 136 + ttl = 300 137 + records = [ 138 + { value = "\"did=did:plc:jayb7xptpsbj3w2krv63lztd\"" }, 139 + ] 140 + } 141 + 101 142 # Resend email verification (DKIM, SPF, DMARC, bounce MX) 102 143 resource "hcloud_zone_rrset" "resend_dkim" { 103 144 zone = hcloud_zone.sans_self.id
+18
dockerfiles/jetstream.Dockerfile
··· 1 + FROM golang:1.25-alpine AS build 2 + 3 + RUN apk add --no-cache git gcc musl-dev make 4 + 5 + WORKDIR /src 6 + COPY go.mod go.sum ./ 7 + RUN go mod download 8 + 9 + COPY pkg/ pkg/ 10 + COPY cmd/jetstream/ cmd/jetstream/ 11 + COPY Makefile . 12 + 13 + RUN CGO_ENABLED=1 GOOS=linux go build -o /jetstream cmd/jetstream/*.go 14 + 15 + FROM alpine:3 16 + RUN apk add --no-cache ca-certificates 17 + COPY --from=build /jetstream /jetstream 18 + CMD ["/jetstream"]
+45
jetstream_listen.py
··· 1 + #!/usr/bin/env python3 2 + """Listen to self-hosted Jetstream for app.opake.* events. 3 + Requires: kubectl port-forward -n opake svc/jetstream 6008:6008 4 + """ 5 + 6 + import websocket 7 + import json 8 + import sys 9 + 10 + JETSTREAM_URL = "ws://localhost:6008/subscribe" 11 + 12 + count = 0 13 + 14 + def on_message(ws, msg): 15 + global count 16 + count += 1 17 + if count % 5000 == 0: 18 + print(f"... {count} events processed", flush=True) 19 + 20 + data = json.loads(msg) 21 + commit = data.get('commit') 22 + if commit is None: 23 + return 24 + col = commit.get("collection", "") 25 + if col.startswith("app.opake."): 26 + print(json.dumps(data, indent=2), flush=True) 27 + 28 + def on_open(ws): 29 + print("connected to self-hosted jetstream", flush=True) 30 + 31 + def on_error(ws, error): 32 + print(f"error: {error}", file=sys.stderr, flush=True) 33 + 34 + def on_close(ws, status, msg): 35 + print(f"closed: {status} {msg}", file=sys.stderr, flush=True) 36 + 37 + if __name__ == "__main__": 38 + ws = websocket.WebSocketApp( 39 + JETSTREAM_URL, 40 + on_open=on_open, 41 + on_message=on_message, 42 + on_error=on_error, 43 + on_close=on_close, 44 + ) 45 + ws.run_forever()
+4
k8s/knot/deployment.yaml
··· 59 59 port: 5555 60 60 initialDelaySeconds: 5 61 61 periodSeconds: 10 62 + lifecycle: 63 + postStart: 64 + exec: 65 + command: ["mkdir", "-p", "/root/.config/git"] 62 66 # knot runs sshd via s6 — needs privilege escalation for privsep 63 67 # and SETUID/SETGID for s6-applyuidgid to drop to git user 64 68 securityContext:
+16
k8s/kustomization.yaml
··· 11 11 - alerting 12 12 - ci 13 13 - opake 14 + - opake-staging 14 15 15 16 generatorOptions: 16 17 disableNameSuffixHash: true ··· 91 92 files: 92 93 - bot-token=alerting/telegram-bot-token.secret 93 94 - chat-id=alerting/telegram-chat-id.secret 95 + - name: opake-staging-secrets 96 + namespace: opake-staging 97 + type: Opaque 98 + files: 99 + - secret-key-base=opake-staging/secret-key-base.secret 100 + - name: opake-staging-database-url 101 + namespace: opake-staging 102 + type: Opaque 103 + files: 104 + - url=opake-staging/database-url.secret 105 + - name: staging-basicauth 106 + namespace: opake-staging 107 + type: Opaque 108 + files: 109 + - users=opake-staging/basicauth.secret
+81
k8s/opake-staging/appview/deployment.yaml
··· 1 + apiVersion: apps/v1 2 + kind: Deployment 3 + metadata: 4 + name: opake-appview 5 + namespace: opake-staging 6 + spec: 7 + replicas: 1 8 + strategy: 9 + type: Recreate 10 + selector: 11 + matchLabels: 12 + app: opake-appview 13 + template: 14 + metadata: 15 + labels: 16 + app: opake-appview 17 + spec: 18 + terminationGracePeriodSeconds: 30 19 + securityContext: 20 + runAsUser: 1000 21 + runAsGroup: 1000 22 + runAsNonRoot: true 23 + containers: 24 + - name: appview 25 + image: zot.sans-self.org/opake/appview:staging 26 + imagePullPolicy: Always 27 + ports: 28 + - containerPort: 6100 29 + env: 30 + - name: DATABASE_URL 31 + valueFrom: 32 + secretKeyRef: 33 + name: opake-staging-database-url 34 + key: url 35 + - name: SECRET_KEY_BASE 36 + valueFrom: 37 + secretKeyRef: 38 + name: opake-staging-secrets 39 + key: secret-key-base 40 + - name: PHX_HOST 41 + value: appview.staging.opake.app 42 + - name: PHX_SERVER 43 + value: "true" 44 + - name: CORS_ORIGIN 45 + value: https://staging.opake.app 46 + - name: OPAKE_FRONTEND_URL 47 + value: https://staging.opake.app 48 + - name: JETSTREAM_URL 49 + value: wss://jetstream2.us-east.bsky.network/subscribe 50 + startupProbe: 51 + httpGet: 52 + path: /api/health 53 + port: 6100 54 + failureThreshold: 30 55 + periodSeconds: 2 56 + livenessProbe: 57 + httpGet: 58 + path: /api/health 59 + port: 6100 60 + periodSeconds: 30 61 + timeoutSeconds: 5 62 + failureThreshold: 3 63 + readinessProbe: 64 + httpGet: 65 + path: /api/health 66 + port: 6100 67 + periodSeconds: 10 68 + timeoutSeconds: 5 69 + failureThreshold: 2 70 + securityContext: 71 + allowPrivilegeEscalation: false 72 + capabilities: 73 + drop: 74 + - ALL 75 + resources: 76 + requests: 77 + cpu: 50m 78 + memory: 64Mi 79 + limits: 80 + cpu: 500m 81 + memory: 256Mi
+6
k8s/opake-staging/appview/kustomization.yaml
··· 1 + apiVersion: kustomize.config.k8s.io/v1beta1 2 + kind: Kustomization 3 + 4 + resources: 5 + - deployment.yaml 6 + - service.yaml
+12
k8s/opake-staging/appview/service.yaml
··· 1 + apiVersion: v1 2 + kind: Service 3 + metadata: 4 + name: opake-appview 5 + namespace: opake-staging 6 + spec: 7 + selector: 8 + app: opake-appview 9 + ports: 10 + - name: http 11 + port: 6100 12 + targetPort: 6100
k8s/opake-staging/basicauth.secret

This is a binary file and will not be displayed.

+13
k8s/opake-staging/cert.yaml
··· 1 + apiVersion: cert-manager.io/v1 2 + kind: Certificate 3 + metadata: 4 + name: staging-opake-app 5 + namespace: opake-staging 6 + spec: 7 + secretName: staging-opake-app-tls 8 + issuerRef: 9 + name: letsencrypt-prod 10 + kind: ClusterIssuer 11 + dnsNames: 12 + - staging.opake.app 13 + - appview.staging.opake.app
+23
k8s/opake-staging/ci-rbac.yaml
··· 1 + apiVersion: rbac.authorization.k8s.io/v1 2 + kind: Role 3 + metadata: 4 + name: deployment-restarter 5 + namespace: opake-staging 6 + rules: 7 + - apiGroups: ["apps"] 8 + resources: ["deployments"] 9 + verbs: ["get", "patch"] 10 + --- 11 + apiVersion: rbac.authorization.k8s.io/v1 12 + kind: RoleBinding 13 + metadata: 14 + name: loom-deployment-restarter 15 + namespace: opake-staging 16 + subjects: 17 + - kind: ServiceAccount 18 + name: loom-spindle-job-runner 19 + namespace: ci 20 + roleRef: 21 + kind: Role 22 + name: deployment-restarter 23 + apiGroup: rbac.authorization.k8s.io
k8s/opake-staging/database-url.secret

This is a binary file and will not be displayed.

+34
k8s/opake-staging/ingress.yaml
··· 1 + apiVersion: traefik.io/v1alpha1 2 + kind: Middleware 3 + metadata: 4 + name: staging-auth 5 + namespace: opake-staging 6 + spec: 7 + basicAuth: 8 + secret: staging-basicauth 9 + --- 10 + apiVersion: traefik.io/v1alpha1 11 + kind: IngressRoute 12 + metadata: 13 + name: opake-staging 14 + namespace: opake-staging 15 + spec: 16 + entryPoints: 17 + - websecure 18 + tls: 19 + secretName: staging-opake-app-tls 20 + routes: 21 + - match: Host(`appview.staging.opake.app`) 22 + kind: Rule 23 + middlewares: 24 + - name: staging-auth 25 + services: 26 + - name: opake-appview 27 + port: 6100 28 + - match: Host(`staging.opake.app`) 29 + kind: Rule 30 + middlewares: 31 + - name: staging-auth 32 + services: 33 + - name: opake-web 34 + port: 3000
+11
k8s/opake-staging/kustomization.yaml
··· 1 + apiVersion: kustomize.config.k8s.io/v1beta1 2 + kind: Kustomization 3 + 4 + resources: 5 + - namespace.yaml 6 + - cert.yaml 7 + - ingress.yaml 8 + - network-policy.yaml 9 + - ci-rbac.yaml 10 + - web 11 + - appview
+4
k8s/opake-staging/namespace.yaml
··· 1 + apiVersion: v1 2 + kind: Namespace 3 + metadata: 4 + name: opake-staging
+77
k8s/opake-staging/network-policy.yaml
··· 1 + apiVersion: networking.k8s.io/v1 2 + kind: NetworkPolicy 3 + metadata: 4 + name: web-ingress 5 + namespace: opake-staging 6 + spec: 7 + podSelector: 8 + matchLabels: 9 + app: opake-web 10 + policyTypes: 11 + - Ingress 12 + ingress: 13 + - from: 14 + - namespaceSelector: 15 + matchLabels: 16 + kubernetes.io/metadata.name: traefik 17 + ports: 18 + - port: 3000 19 + --- 20 + apiVersion: networking.k8s.io/v1 21 + kind: NetworkPolicy 22 + metadata: 23 + name: appview-ingress 24 + namespace: opake-staging 25 + spec: 26 + podSelector: 27 + matchLabels: 28 + app: opake-appview 29 + policyTypes: 30 + - Ingress 31 + ingress: 32 + - from: 33 + - namespaceSelector: 34 + matchLabels: 35 + kubernetes.io/metadata.name: traefik 36 + ports: 37 + - port: 6100 38 + --- 39 + apiVersion: networking.k8s.io/v1 40 + kind: NetworkPolicy 41 + metadata: 42 + name: appview-egress 43 + namespace: opake-staging 44 + spec: 45 + podSelector: 46 + matchLabels: 47 + app: opake-appview 48 + policyTypes: 49 + - Egress 50 + egress: 51 + # Postgres 52 + - to: 53 + - namespaceSelector: 54 + matchLabels: 55 + kubernetes.io/metadata.name: postgres 56 + ports: 57 + - port: 5432 58 + # DNS 59 + - to: 60 + - namespaceSelector: 61 + matchLabels: 62 + kubernetes.io/metadata.name: kube-system 63 + ports: 64 + - port: 53 65 + protocol: UDP 66 + - port: 53 67 + protocol: TCP 68 + # External (Jetstream WSS) 69 + - to: 70 + - ipBlock: 71 + cidr: 0.0.0.0/0 72 + except: 73 + - 10.0.0.0/8 74 + - 172.16.0.0/12 75 + - 192.168.0.0/16 76 + ports: 77 + - port: 443
k8s/opake-staging/secret-key-base.secret

This is a binary file and will not be displayed.

+61
k8s/opake-staging/web/deployment.yaml
··· 1 + apiVersion: apps/v1 2 + kind: Deployment 3 + metadata: 4 + name: opake-web 5 + namespace: opake-staging 6 + spec: 7 + replicas: 1 8 + selector: 9 + matchLabels: 10 + app: opake-web 11 + template: 12 + metadata: 13 + labels: 14 + app: opake-web 15 + spec: 16 + securityContext: 17 + runAsUser: 1000 18 + runAsGroup: 1000 19 + runAsNonRoot: true 20 + containers: 21 + - name: web 22 + image: zot.sans-self.org/opake/web:staging 23 + imagePullPolicy: Always 24 + ports: 25 + - containerPort: 3000 26 + env: 27 + - name: NODE_ENV 28 + value: production 29 + - name: PORT 30 + value: "3000" 31 + livenessProbe: 32 + httpGet: 33 + path: / 34 + port: 3000 35 + periodSeconds: 30 36 + failureThreshold: 3 37 + readinessProbe: 38 + httpGet: 39 + path: / 40 + port: 3000 41 + periodSeconds: 10 42 + failureThreshold: 2 43 + securityContext: 44 + allowPrivilegeEscalation: false 45 + capabilities: 46 + drop: 47 + - ALL 48 + readOnlyRootFilesystem: true 49 + resources: 50 + requests: 51 + cpu: 20m 52 + memory: 64Mi 53 + limits: 54 + cpu: 200m 55 + memory: 256Mi 56 + volumeMounts: 57 + - name: tmp 58 + mountPath: /tmp 59 + volumes: 60 + - name: tmp 61 + emptyDir: {}
+6
k8s/opake-staging/web/kustomization.yaml
··· 1 + apiVersion: kustomize.config.k8s.io/v1beta1 2 + kind: Kustomization 3 + 4 + resources: 5 + - deployment.yaml 6 + - service.yaml
+12
k8s/opake-staging/web/service.yaml
··· 1 + apiVersion: v1 2 + kind: Service 3 + metadata: 4 + name: opake-web 5 + namespace: opake-staging 6 + spec: 7 + selector: 8 + app: opake-web 9 + ports: 10 + - name: http 11 + port: 3000 12 + targetPort: 3000
+12
k8s/opake/jetstream/cert.yaml
··· 1 + apiVersion: cert-manager.io/v1 2 + kind: Certificate 3 + metadata: 4 + name: jetstream 5 + namespace: opake 6 + spec: 7 + secretName: jetstream-tls 8 + issuerRef: 9 + name: letsencrypt-prod 10 + kind: ClusterIssuer 11 + dnsNames: 12 + - jetstream.opake.app
+69
k8s/opake/jetstream/deployment.yaml
··· 1 + apiVersion: apps/v1 2 + kind: Deployment 3 + metadata: 4 + name: jetstream 5 + namespace: opake 6 + spec: 7 + replicas: 1 8 + strategy: 9 + type: Recreate 10 + selector: 11 + matchLabels: 12 + app: jetstream 13 + template: 14 + metadata: 15 + labels: 16 + app: jetstream 17 + spec: 18 + terminationGracePeriodSeconds: 30 19 + securityContext: 20 + fsGroup: 1000 21 + runAsUser: 1000 22 + runAsGroup: 1000 23 + runAsNonRoot: true 24 + containers: 25 + - name: jetstream 26 + image: zot.sans-self.org/infra/jetstream:latest 27 + ports: 28 + - name: ws 29 + containerPort: 6008 30 + - name: metrics 31 + containerPort: 6009 32 + env: 33 + - name: JETSTREAM_DATA_DIR 34 + value: /data 35 + - name: JETSTREAM_EVENT_TTL 36 + value: "1h" 37 + - name: JETSTREAM_LIVENESS_TTL 38 + value: "30s" 39 + volumeMounts: 40 + - name: data 41 + mountPath: /data 42 + livenessProbe: 43 + tcpSocket: 44 + port: 6008 45 + periodSeconds: 30 46 + timeoutSeconds: 5 47 + failureThreshold: 3 48 + readinessProbe: 49 + tcpSocket: 50 + port: 6008 51 + periodSeconds: 10 52 + timeoutSeconds: 5 53 + failureThreshold: 2 54 + securityContext: 55 + allowPrivilegeEscalation: false 56 + capabilities: 57 + drop: 58 + - ALL 59 + resources: 60 + requests: 61 + cpu: 50m 62 + memory: 128Mi 63 + limits: 64 + cpu: "1" 65 + memory: 512Mi 66 + volumes: 67 + - name: data 68 + persistentVolumeClaim: 69 + claimName: jetstream-data
+16
k8s/opake/jetstream/ingress.yaml
··· 1 + apiVersion: traefik.io/v1alpha1 2 + kind: IngressRoute 3 + metadata: 4 + name: jetstream 5 + namespace: opake 6 + spec: 7 + entryPoints: 8 + - websecure 9 + tls: 10 + secretName: jetstream-tls 11 + routes: 12 + - match: Host(`jetstream.opake.app`) 13 + kind: Rule 14 + services: 15 + - name: jetstream 16 + port: 6008
+8
k8s/opake/jetstream/kustomization.yaml
··· 1 + apiVersion: kustomize.config.k8s.io/v1beta1 2 + kind: Kustomization 3 + 4 + resources: 5 + - pvc.yaml 6 + - deployment.yaml 7 + - service.yaml 8 + - network-policy.yaml
+49
k8s/opake/jetstream/network-policy.yaml
··· 1 + apiVersion: networking.k8s.io/v1 2 + kind: NetworkPolicy 3 + metadata: 4 + name: jetstream-ingress 5 + namespace: opake 6 + spec: 7 + podSelector: 8 + matchLabels: 9 + app: jetstream 10 + policyTypes: 11 + - Ingress 12 + ingress: 13 + - from: 14 + - podSelector: {} 15 + ports: 16 + - port: 6008 17 + --- 18 + apiVersion: networking.k8s.io/v1 19 + kind: NetworkPolicy 20 + metadata: 21 + name: jetstream-egress 22 + namespace: opake 23 + spec: 24 + podSelector: 25 + matchLabels: 26 + app: jetstream 27 + policyTypes: 28 + - Egress 29 + egress: 30 + # DNS 31 + - to: 32 + - namespaceSelector: 33 + matchLabels: 34 + kubernetes.io/metadata.name: kube-system 35 + ports: 36 + - port: 53 37 + protocol: UDP 38 + - port: 53 39 + protocol: TCP 40 + # External HTTPS (relay firehose) 41 + - to: 42 + - ipBlock: 43 + cidr: 0.0.0.0/0 44 + except: 45 + - 10.0.0.0/8 46 + - 172.16.0.0/12 47 + - 192.168.0.0/16 48 + ports: 49 + - port: 443
+11
k8s/opake/jetstream/pvc.yaml
··· 1 + apiVersion: v1 2 + kind: PersistentVolumeClaim 3 + metadata: 4 + name: jetstream-data 5 + namespace: opake 6 + spec: 7 + accessModes: 8 + - ReadWriteOnce 9 + resources: 10 + requests: 11 + storage: 2Gi
+12
k8s/opake/jetstream/service.yaml
··· 1 + apiVersion: v1 2 + kind: Service 3 + metadata: 4 + name: jetstream 5 + namespace: opake 6 + spec: 7 + selector: 8 + app: jetstream 9 + ports: 10 + - name: ws 11 + port: 6008 12 + targetPort: 6008
+1
k8s/opake/kustomization.yaml
··· 5 5 - namespace.yaml 6 6 - web 7 7 - appview 8 + - jetstream
+6
k8s/opake/web/ingress.yaml
··· 9 9 tls: 10 10 secretName: opake-app-tls 11 11 routes: 12 + - match: Host(`opake.app`) && Path(`/.well-known/atproto-did`) 13 + kind: Rule 14 + priority: 100 15 + services: 16 + - name: pds-handle-proxy 17 + port: 3000 12 18 - match: Host(`opake.app`) 13 19 kind: Rule 14 20 services:
+1
k8s/opake/web/kustomization.yaml
··· 5 5 - deployment.yaml 6 6 - service.yaml 7 7 - ingress.yaml 8 + - pds-handle-proxy.yaml 8 9 - cert.yaml 9 10 10 11 images:
+10
k8s/opake/web/pds-handle-proxy.yaml
··· 1 + apiVersion: v1 2 + kind: Service 3 + metadata: 4 + name: pds-handle-proxy 5 + namespace: opake 6 + spec: 7 + type: ExternalName 8 + externalName: tranquil-pds.pds.svc.cluster.local 9 + ports: 10 + - port: 3000
+4
k8s/pds/network-policy.yaml
··· 106 106 - 172.16.0.0/12 107 107 - 192.168.0.0/16 108 108 ports: 109 + - port: 53 110 + protocol: UDP 111 + - port: 53 112 + protocol: TCP 109 113 - port: 443 110 114 - port: 587 111 115 ---
+1 -3
k8s/pds/tranquil-deployment.yaml
··· 4 4 name: tranquil-pds 5 5 namespace: pds 6 6 spec: 7 - replicas: 2 7 + replicas: 1 8 8 strategy: 9 9 type: RollingUpdate 10 10 rollingUpdate: ··· 73 73 secretKeyRef: 74 74 name: tranquil-secrets 75 75 key: master_key 76 - - name: PLC_ROTATION_KEY 77 - value: did:key:zQ3shqeSNmj7mgsxGbZofhCJ36uYzSM8WPFLcxy26WADVaH8c 78 76 - name: CACHE_BACKEND 79 77 value: valkey 80 78 - name: VALKEY_URL
+7 -1
k8s/postgres/initdb-configmap.yaml
··· 26 26 CREATE DATABASE pds OWNER tranquil; 27 27 EOSQL 28 28 29 - # Future services: add 04-another-service.sh following the same pattern 29 + 04-opake.sh: | 30 + #!/bin/sh 31 + set -e 32 + psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname postgres <<EOSQL 33 + CREATE USER opake_staging WITH PASSWORD '$OPAKE_STAGING_PASSWORD'; 34 + CREATE DATABASE opake_staging OWNER opake_staging; 35 + EOSQL
+1
k8s/postgres/kustomization.yaml
··· 20 20 - superuser-password=superuser-password.secret 21 21 - tranquil-password=postgres-password.secret 22 22 - replication-password=replication-password.secret 23 + - opake-staging-password=opake-staging-password.secret
k8s/postgres/opake-staging-password.secret

This is a binary file and will not be displayed.

+5
k8s/postgres/statefulset.yaml
··· 52 52 secretKeyRef: 53 53 name: postgres-credentials 54 54 key: tranquil-password 55 + - name: OPAKE_STAGING_PASSWORD 56 + valueFrom: 57 + secretKeyRef: 58 + name: postgres-credentials 59 + key: opake-staging-password 55 60 volumeMounts: 56 61 - name: data 57 62 mountPath: /var/lib/postgresql/data
+28
pds_live.py
··· 1 + #!/usr/bin/env python3 2 + """Connect to Tranquil firehose, print every message with timestamp.""" 3 + 4 + import asyncio 5 + import time 6 + 7 + import websockets 8 + 9 + PDS = "wss://sans-self.org/xrpc/com.atproto.sync.subscribeRepos" 10 + 11 + 12 + async def main(): 13 + print(f"Connecting to {PDS}", flush=True) 14 + async with websockets.connect(PDS, max_size=1 << 24, ping_interval=20) as ws: 15 + print(f"Connected at {time.strftime('%H:%M:%S')}", flush=True) 16 + count = 0 17 + while True: 18 + msg = await ws.recv() 19 + count += 1 20 + t = time.strftime('%H:%M:%S') 21 + if isinstance(msg, bytes): 22 + print(f"[{t}] #{count} binary {len(msg)}b", flush=True) 23 + else: 24 + print(f"[{t}] #{count} text {len(msg)}b", flush=True) 25 + 26 + 27 + if __name__ == "__main__": 28 + asyncio.run(main())
+84
relay_opake.py
··· 1 + #!/usr/bin/env python3 2 + """Subscribe to relay firehose and log all app.opake.* events.""" 3 + 4 + import sys 5 + import json 6 + import websocket 7 + import cbor2 8 + 9 + RELAY_URL = "wss://bsky.network/xrpc/com.atproto.sync.subscribeRepos" 10 + 11 + def decode_cbor_pair(data): 12 + """Decode two concatenated CBOR objects from a binary message.""" 13 + import io 14 + stream = io.BytesIO(data) 15 + decoder = cbor2.CBORDecoder(stream) 16 + header = decoder.decode() 17 + body = decoder.decode() 18 + return header, body 19 + 20 + def on_message(ws, message): 21 + if not isinstance(message, (bytes, bytearray)): 22 + return 23 + try: 24 + header, body = decode_cbor_pair(message) 25 + except Exception as e: 26 + print(f"CBOR decode failed: {type(e).__name__}: {e}", file=sys.stderr, flush=True) 27 + return 28 + 29 + if not isinstance(header, dict) or not isinstance(body, dict): 30 + return 31 + 32 + # only care about commit frames (op=1, t="#commit") 33 + t = header.get("t", "") 34 + if t != "#commit": 35 + return 36 + 37 + ops = body.get("ops", []) 38 + if not isinstance(ops, list): 39 + return 40 + 41 + did_raw = body.get("did", "?") 42 + did = did_raw.decode("utf-8") if isinstance(did_raw, (bytes, bytearray)) else str(did_raw) 43 + seq = body.get("seq", "?") 44 + 45 + 46 + for op in ops: 47 + if not isinstance(op, dict): 48 + continue 49 + path = op.get("path", "") 50 + if path.startswith("app.opake."): 51 + action = op.get("action", "?") 52 + cid_val = op.get("cid") 53 + cid_str = str(cid_val) if cid_val else "null" 54 + # check blocks size 55 + blocks = body.get("blocks", b"") 56 + blocks_len = len(blocks) if isinstance(blocks, (bytes, bytearray)) else 0 57 + print(f"[seq={seq}] {action} {path} did={did} cid={cid_str} blocks={blocks_len}B", flush=True) 58 + 59 + def on_error(ws, error): 60 + print(f"error: {error}", file=sys.stderr, flush=True) 61 + 62 + def on_close(ws, close_status, close_msg): 63 + print(f"closed: {close_status} {close_msg}", file=sys.stderr, flush=True) 64 + 65 + def on_open(ws): 66 + print("connected to relay firehose, filtering for app.opake.*", flush=True) 67 + 68 + msg_count = 0 69 + def on_message_with_heartbeat(ws, message): 70 + global msg_count 71 + msg_count += 1 72 + if msg_count % 10000 == 0: 73 + print(f"... {msg_count} messages processed", flush=True) 74 + on_message(ws, message) 75 + 76 + if __name__ == "__main__": 77 + ws = websocket.WebSocketApp( 78 + RELAY_URL, 79 + on_open=on_open, 80 + on_message=on_message_with_heartbeat, 81 + on_error=on_error, 82 + on_close=on_close, 83 + ) 84 + ws.run_forever()