240 lines
7.0 KiB
YAML
240 lines
7.0 KiB
YAML
# 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
|