communication: stage guest-helper for prune

This commit is contained in:
Brad Stein 2025-12-31 12:15:18 -03:00
parent 71c58ee081
commit 6759817518
3 changed files with 256 additions and 0 deletions

View File

@ -0,0 +1,239 @@
# services/communication/guest-helper.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: guest-helper
data:
app.py: |
import os, uuid, string, random
import requests
from fastapi import FastAPI, HTTPException, Header
from pydantic import BaseModel
import uvicorn
BASE = os.environ.get("SYNAPSE_BASE", "http://othrys-synapse-matrix-synapse:8008")
SEED_USER = os.environ["SEEDER_USER"]
SEED_PASS = os.environ["SEEDER_PASS"]
SERVER_NAME = os.environ.get("SERVER_NAME", "live.bstein.dev")
ELEMENT_URL = os.environ.get("ELEMENT_URL", "https://live.bstein.dev")
ADMIN_TOKEN = os.environ.get("ADMIN_TOKEN")
app = FastAPI(title="Guest Helper", version="0.1.0")
class InviteRequest(BaseModel):
room: str # room_id or alias
display_name: str | None = None
def login(user, password):
res = requests.post(
f"{BASE}/_matrix/client/v3/login",
json={
"type": "m.login.password",
"identifier": {"type": "m.id.user", "user": user},
"password": password,
},
timeout=10,
)
if res.status_code != 200:
raise HTTPException(status_code=500, detail="seeder login failed")
return res.json()["access_token"]
def resolve_room(token, room):
headers = {"Authorization": f"Bearer {token}"}
if room.startswith("!"):
return room
if room.startswith("#"):
alias_enc = requests.utils.requote_uri(room)
r = requests.get(f"{BASE}/_matrix/client/v3/directory/room/{alias_enc}", headers=headers, timeout=10)
if r.status_code != 200:
raise HTTPException(status_code=400, detail="room alias not found")
return r.json()["room_id"]
raise HTTPException(status_code=400, detail="room must be room_id or alias")
def random_pwd():
alphabet = string.ascii_letters + string.digits
return "".join(random.choice(alphabet) for _ in range(20))
def create_guest(token, display):
uid = f"@guest-{uuid.uuid4().hex[:8]}:{SERVER_NAME}"
pwd = random_pwd()
headers = {"Authorization": f"Bearer {token}"}
body = {
"password": pwd,
"displayname": display or "Guest",
"admin": False,
"deactivated": False,
}
r = requests.put(
f"{BASE}/_synapse/admin/v2/users/{requests.utils.requote_uri(uid)}",
headers=headers,
json=body,
timeout=10,
)
if r.status_code not in (200, 201):
raise HTTPException(status_code=500, detail=f"user create failed: {r.text}")
return uid, pwd
def join_room_as(token, room_id, user_id):
headers = {"Authorization": f"Bearer {token}"}
r = requests.post(
f"{BASE}/_synapse/admin/v1/join/{requests.utils.requote_uri(room_id)}",
headers=headers,
json={"user_id": user_id},
timeout=10,
)
if r.status_code not in (200, 202):
raise HTTPException(status_code=500, detail=f"join failed: {r.text}")
def login_token(user, password):
r = requests.post(
f"{BASE}/_matrix/client/v3/login",
json={
"type": "m.login.password",
"identifier": {"type": "m.id.user", "user": user},
"password": password,
},
timeout=10,
)
if r.status_code != 200:
raise HTTPException(status_code=500, detail="guest login failed")
data = r.json()
return data["access_token"]
@app.post("/invite")
def invite(req: InviteRequest, x_admin_token: str | None = Header(default=None)):
if ADMIN_TOKEN and x_admin_token != ADMIN_TOKEN:
raise HTTPException(status_code=401, detail="unauthorized")
admin_token = login(SEED_USER, SEED_PASS)
room_id = resolve_room(admin_token, req.room)
guest_id, pwd = create_guest(admin_token, req.display_name)
join_room_as(admin_token, room_id, guest_id)
guest_token = login_token(guest_id, pwd)
join_url = f"{ELEMENT_URL}/#/room/{room_id}?access_token={guest_token}&user_id={guest_id}"
return {
"user_id": guest_id,
"password": pwd,
"room_id": room_id,
"access_token": guest_token,
"join_url": join_url,
}
def main():
uvicorn.run(app, host="0.0.0.0", port=8081)
if __name__ == "__main__":
main()
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: guest-helper
labels:
app: guest-helper
spec:
replicas: 1
selector:
matchLabels:
app: guest-helper
template:
metadata:
labels:
app: guest-helper
spec:
nodeSelector:
hardware: rpi5
containers:
- name: api
image: python:3.11-slim
command:
- /bin/sh
- -c
- |
pip install --no-cache-dir fastapi uvicorn requests && \
python /app/app.py
env:
- name: SYNAPSE_BASE
value: http://othrys-synapse-matrix-synapse:8008
- name: SEEDER_USER
value: othrys-seeder
- name: SEEDER_PASS
valueFrom:
secretKeyRef:
name: atlasbot-credentials
key: seeder-password
- name: SERVER_NAME
value: live.bstein.dev
- name: ELEMENT_URL
value: https://live.bstein.dev
- name: ADMIN_TOKEN
valueFrom:
secretKeyRef:
name: guest-helper-admin
key: ADMIN_TOKEN
optional: true
ports:
- name: http
containerPort: 8081
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 300m
memory: 256Mi
volumeMounts:
- name: code
mountPath: /app/app.py
subPath: app.py
volumes:
- name: code
configMap:
name: guest-helper
---
apiVersion: v1
kind: Service
metadata:
name: guest-helper
spec:
selector:
app: guest-helper
ports:
- name: http
port: 8081
targetPort: 8081
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: guest-helper
annotations:
kubernetes.io/ingress.class: traefik
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
cert-manager.io/cluster-issuer: letsencrypt
spec:
tls:
- hosts:
- live.bstein.dev
secretName: live-othrys-tls
rules:
- host: live.bstein.dev
http:
paths:
- path: /guest-helper
pathType: Prefix
backend:
service:
name: guest-helper
port:
number: 8081

View File

@ -22,3 +22,5 @@ resources:
- atlasbot-deployment.yaml
- seed-othrys-room.yaml
- wellknown.yaml
- synapse-federation-service.yaml
- guest-helper.yaml

View File

@ -0,0 +1,15 @@
# services/communication/synapse-federation-service.yaml
apiVersion: v1
kind: Service
metadata:
name: synapse-federation
spec:
clusterIP: 10.43.60.60
selector:
app.kubernetes.io/name: matrix-synapse
app.kubernetes.io/instance: othrys-synapse
ports:
- name: federation
port: 8448
targetPort: 8008
protocol: TCP