comms: tidy stack and guest naming
This commit is contained in:
parent
94c1395c8c
commit
2a6f0a8db3
@ -2,7 +2,7 @@
|
|||||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||||
kind: Kustomization
|
kind: Kustomization
|
||||||
metadata:
|
metadata:
|
||||||
name: communication
|
name: comms
|
||||||
namespace: flux-system
|
namespace: flux-system
|
||||||
spec:
|
spec:
|
||||||
interval: 10m
|
interval: 10m
|
||||||
@ -5,7 +5,7 @@ resources:
|
|||||||
- gitea/kustomization.yaml
|
- gitea/kustomization.yaml
|
||||||
- vault/kustomization.yaml
|
- vault/kustomization.yaml
|
||||||
- vaultwarden/kustomization.yaml
|
- vaultwarden/kustomization.yaml
|
||||||
- communication/kustomization.yaml
|
- comms/kustomization.yaml
|
||||||
- crypto/kustomization.yaml
|
- crypto/kustomization.yaml
|
||||||
- monerod/kustomization.yaml
|
- monerod/kustomization.yaml
|
||||||
- pegasus/kustomization.yaml
|
- pegasus/kustomization.yaml
|
||||||
|
|||||||
32
services/comms/NOTES.md
Normal file
32
services/comms/NOTES.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# services/comms/NOTES.md
|
||||||
|
|
||||||
|
Purpose: Matrix + Element + LiveKit stack for Othrys (live.bstein.dev).
|
||||||
|
|
||||||
|
Core flow
|
||||||
|
- Matrix Authentication Service (MAS) handles login/SSO and issues Matrix access tokens.
|
||||||
|
- Synapse is the homeserver; MAS fronts login, Synapse serves client/server APIs.
|
||||||
|
- Element Web provides the main UI; Element Call embeds LiveKit for group video.
|
||||||
|
- LiveKit handles SFU media; Coturn provides TURN for NAT traversal.
|
||||||
|
- matrix-guest-register provides guest accounts + guest sessions (no Keycloak).
|
||||||
|
|
||||||
|
Operational jobs
|
||||||
|
- mas-db-ensure-job: ensures MAS database role/database + secret in comms.
|
||||||
|
- comms-secrets-ensure-job: creates runtime secrets (TURN, LiveKit, Synapse, atlasbot).
|
||||||
|
- synapse-signingkey-ensure-job: ensures Synapse signing key secret.
|
||||||
|
- synapse-seeder-admin-ensure-job: ensures Synapse admin user exists.
|
||||||
|
- synapse-user-seed-job: seeds atlasbot + othrys-seeder users/passwords.
|
||||||
|
- mas-local-users-ensure-job: ensures MAS local users exist (seeder/bot).
|
||||||
|
- seed-othrys-room: (suspended) creates Othrys + joins locals.
|
||||||
|
- reset-othrys-room: one-off room reset + pin invite.
|
||||||
|
- pin-othrys-invite: (suspended) pin invite message if missing.
|
||||||
|
- guest-name-randomizer: renames numeric/guest users to adj-noun names.
|
||||||
|
- bstein-force-leave: one-off room leave cleanup.
|
||||||
|
|
||||||
|
Manual re-runs
|
||||||
|
- Bump the job name suffix (e.g., reset-othrys-room-9) to re-run a one-off job.
|
||||||
|
- Unsuspend a CronJob only when needed; re-suspend after completion.
|
||||||
|
|
||||||
|
Ports
|
||||||
|
- Traefik (HTTPS) via LB on 192.168.22.9.
|
||||||
|
- Coturn LB on 192.168.22.5 (3478/5349 + UDP range).
|
||||||
|
- LiveKit LB on 192.168.22.6 (7880/7881/7882/7883).
|
||||||
@ -7,6 +7,8 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
schedule: "*/1 * * * *"
|
schedule: "*/1 * * * *"
|
||||||
suspend: false
|
suspend: false
|
||||||
|
successfulJobsHistoryLimit: 1
|
||||||
|
failedJobsHistoryLimit: 1
|
||||||
jobTemplate:
|
jobTemplate:
|
||||||
spec:
|
spec:
|
||||||
backoffLimit: 0
|
backoffLimit: 0
|
||||||
@ -30,6 +32,8 @@ spec:
|
|||||||
env:
|
env:
|
||||||
- name: SYNAPSE_BASE
|
- name: SYNAPSE_BASE
|
||||||
value: http://othrys-synapse-matrix-synapse:8008
|
value: http://othrys-synapse-matrix-synapse:8008
|
||||||
|
- name: AUTH_BASE
|
||||||
|
value: http://matrix-authentication-service:8080
|
||||||
- name: MAS_ADMIN_CLIENT_ID
|
- name: MAS_ADMIN_CLIENT_ID
|
||||||
value: 01KDXMVQBQ5JNY6SEJPZW6Z8BM
|
value: 01KDXMVQBQ5JNY6SEJPZW6Z8BM
|
||||||
- name: MAS_ADMIN_CLIENT_SECRET_FILE
|
- name: MAS_ADMIN_CLIENT_SECRET_FILE
|
||||||
@ -40,6 +44,11 @@ spec:
|
|||||||
value: http://matrix-authentication-service:8080/oauth2/token
|
value: http://matrix-authentication-service:8080/oauth2/token
|
||||||
- name: SEEDER_USER
|
- name: SEEDER_USER
|
||||||
value: othrys-seeder
|
value: othrys-seeder
|
||||||
|
- name: SEEDER_PASS
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: atlasbot-credentials-runtime
|
||||||
|
key: seeder-password
|
||||||
command:
|
command:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
- -c
|
- -c
|
||||||
@ -66,11 +75,13 @@ spec:
|
|||||||
]
|
]
|
||||||
|
|
||||||
BASE = os.environ["SYNAPSE_BASE"]
|
BASE = os.environ["SYNAPSE_BASE"]
|
||||||
|
AUTH_BASE = os.environ.get("AUTH_BASE", BASE)
|
||||||
MAS_ADMIN_CLIENT_ID = os.environ["MAS_ADMIN_CLIENT_ID"]
|
MAS_ADMIN_CLIENT_ID = os.environ["MAS_ADMIN_CLIENT_ID"]
|
||||||
MAS_ADMIN_CLIENT_SECRET_FILE = os.environ["MAS_ADMIN_CLIENT_SECRET_FILE"]
|
MAS_ADMIN_CLIENT_SECRET_FILE = os.environ["MAS_ADMIN_CLIENT_SECRET_FILE"]
|
||||||
MAS_ADMIN_API_BASE = os.environ["MAS_ADMIN_API_BASE"].rstrip("/")
|
MAS_ADMIN_API_BASE = os.environ["MAS_ADMIN_API_BASE"].rstrip("/")
|
||||||
MAS_TOKEN_URL = os.environ["MAS_TOKEN_URL"]
|
MAS_TOKEN_URL = os.environ["MAS_TOKEN_URL"]
|
||||||
SEEDER_USER = os.environ["SEEDER_USER"]
|
SEEDER_USER = os.environ["SEEDER_USER"]
|
||||||
|
SEEDER_PASS = os.environ["SEEDER_PASS"]
|
||||||
ROOM_ALIAS = "#othrys:live.bstein.dev"
|
ROOM_ALIAS = "#othrys:live.bstein.dev"
|
||||||
|
|
||||||
def mas_admin_token():
|
def mas_admin_token():
|
||||||
@ -126,6 +137,19 @@ spec:
|
|||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def login(user, password):
|
||||||
|
r = requests.post(
|
||||||
|
f"{AUTH_BASE}/_matrix/client/v3/login",
|
||||||
|
json={
|
||||||
|
"type": "m.login.password",
|
||||||
|
"identifier": {"type": "m.id.user", "user": user},
|
||||||
|
"password": password,
|
||||||
|
},
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
return r.json()["access_token"]
|
||||||
|
|
||||||
def resolve_alias(token, alias):
|
def resolve_alias(token, alias):
|
||||||
headers = {"Authorization": f"Bearer {token}"}
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
enc = urllib.parse.quote(alias)
|
enc = urllib.parse.quote(alias)
|
||||||
@ -167,6 +191,23 @@ spec:
|
|||||||
break
|
break
|
||||||
return users
|
return users
|
||||||
|
|
||||||
|
def synapse_list_users(token):
|
||||||
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
users = []
|
||||||
|
from_token = None
|
||||||
|
while True:
|
||||||
|
url = f"{BASE}/_synapse/admin/v2/users?local=true&deactivated=false&limit=100"
|
||||||
|
if from_token:
|
||||||
|
url += f"&from={urllib.parse.quote(from_token)}"
|
||||||
|
r = requests.get(url, headers=headers, timeout=30)
|
||||||
|
r.raise_for_status()
|
||||||
|
payload = r.json()
|
||||||
|
users.extend(payload.get("users", []))
|
||||||
|
from_token = payload.get("next_token")
|
||||||
|
if not from_token:
|
||||||
|
break
|
||||||
|
return users
|
||||||
|
|
||||||
def user_id_for_username(username):
|
def user_id_for_username(username):
|
||||||
return f"@{username}:live.bstein.dev"
|
return f"@{username}:live.bstein.dev"
|
||||||
|
|
||||||
@ -176,6 +217,18 @@ spec:
|
|||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
return r.json().get("displayname")
|
return r.json().get("displayname")
|
||||||
|
|
||||||
|
def get_displayname_admin(token, user_id):
|
||||||
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
r = requests.get(
|
||||||
|
f"{BASE}/_synapse/admin/v2/users/{urllib.parse.quote(user_id)}",
|
||||||
|
headers=headers,
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
if r.status_code == 404:
|
||||||
|
return None
|
||||||
|
r.raise_for_status()
|
||||||
|
return r.json().get("displayname")
|
||||||
|
|
||||||
def set_displayname(token, room_id, user_id, name, in_room):
|
def set_displayname(token, room_id, user_id, name, in_room):
|
||||||
headers = {"Authorization": f"Bearer {token}"}
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
payload = {"displayname": name}
|
payload = {"displayname": name}
|
||||||
@ -191,6 +244,25 @@ spec:
|
|||||||
content = {"membership": "join", "displayname": name}
|
content = {"membership": "join", "displayname": name}
|
||||||
requests.put(state_url, headers=headers, json=content, timeout=30)
|
requests.put(state_url, headers=headers, json=content, timeout=30)
|
||||||
|
|
||||||
|
def set_displayname_admin(token, user_id, name):
|
||||||
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
payload = {"displayname": name}
|
||||||
|
r = requests.put(
|
||||||
|
f"{BASE}/_synapse/admin/v2/users/{urllib.parse.quote(user_id)}",
|
||||||
|
headers=headers,
|
||||||
|
json=payload,
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
if r.status_code in (200, 201, 204):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def needs_rename_username(username):
|
||||||
|
return username.isdigit() or username.startswith("guest-")
|
||||||
|
|
||||||
|
def needs_rename_display(display):
|
||||||
|
return not display or display.isdigit() or display.startswith("guest-")
|
||||||
|
|
||||||
admin_token = mas_admin_token()
|
admin_token = mas_admin_token()
|
||||||
seeder_id = mas_user_id(admin_token, SEEDER_USER)
|
seeder_id = mas_user_id(admin_token, SEEDER_USER)
|
||||||
seeder_token, seeder_session = mas_personal_session(admin_token, seeder_id)
|
seeder_token, seeder_session = mas_personal_session(admin_token, seeder_id)
|
||||||
@ -201,19 +273,22 @@ spec:
|
|||||||
mas_revoke_session(admin_token, seeder_session)
|
mas_revoke_session(admin_token, seeder_session)
|
||||||
|
|
||||||
users = mas_list_users(admin_token)
|
users = mas_list_users(admin_token)
|
||||||
|
mas_usernames = set()
|
||||||
for user in users:
|
for user in users:
|
||||||
attrs = user.get("attributes") or {}
|
attrs = user.get("attributes") or {}
|
||||||
username = attrs.get("username") or ""
|
username = attrs.get("username") or ""
|
||||||
|
if username:
|
||||||
|
mas_usernames.add(username)
|
||||||
legacy_guest = attrs.get("legacy_guest")
|
legacy_guest = attrs.get("legacy_guest")
|
||||||
if not username:
|
if not username:
|
||||||
continue
|
continue
|
||||||
if not (legacy_guest or username.isdigit() or username.startswith("guest-")):
|
if not (legacy_guest or needs_rename_username(username)):
|
||||||
continue
|
continue
|
||||||
user_id = user_id_for_username(username)
|
user_id = user_id_for_username(username)
|
||||||
access_token, session_id = mas_personal_session(admin_token, user["id"])
|
access_token, session_id = mas_personal_session(admin_token, user["id"])
|
||||||
try:
|
try:
|
||||||
display = get_displayname(access_token, user_id)
|
display = get_displayname(access_token, user_id)
|
||||||
if display and (not display.isdigit()) and (not display.startswith("guest-")):
|
if display and not needs_rename_display(display):
|
||||||
continue
|
continue
|
||||||
new = None
|
new = None
|
||||||
for _ in range(30):
|
for _ in range(30):
|
||||||
@ -227,4 +302,30 @@ spec:
|
|||||||
set_displayname(access_token, room_id, user_id, new, user_id in members)
|
set_displayname(access_token, room_id, user_id, new, user_id in members)
|
||||||
finally:
|
finally:
|
||||||
mas_revoke_session(admin_token, session_id)
|
mas_revoke_session(admin_token, session_id)
|
||||||
|
|
||||||
|
seeder_token = login(SEEDER_USER, SEEDER_PASS)
|
||||||
|
for entry in synapse_list_users(seeder_token):
|
||||||
|
user_id = entry.get("name") or ""
|
||||||
|
if not user_id.startswith("@"):
|
||||||
|
continue
|
||||||
|
localpart = user_id.split(":", 1)[0].lstrip("@")
|
||||||
|
if localpart in mas_usernames:
|
||||||
|
continue
|
||||||
|
is_guest = entry.get("is_guest")
|
||||||
|
if not (is_guest or needs_rename_username(localpart)):
|
||||||
|
continue
|
||||||
|
display = get_displayname_admin(seeder_token, user_id)
|
||||||
|
if display and not needs_rename_display(display):
|
||||||
|
continue
|
||||||
|
new = None
|
||||||
|
for _ in range(30):
|
||||||
|
candidate = f"{random.choice(ADJ)}-{random.choice(NOUN)}"
|
||||||
|
if candidate not in existing:
|
||||||
|
new = candidate
|
||||||
|
existing.add(candidate)
|
||||||
|
break
|
||||||
|
if not new:
|
||||||
|
continue
|
||||||
|
if not set_displayname_admin(seeder_token, user_id, new):
|
||||||
|
continue
|
||||||
PY
|
PY
|
||||||
|
|||||||
@ -27,8 +27,16 @@ data:
|
|||||||
RATE_MAX = int(os.environ.get("RATE_MAX", "30"))
|
RATE_MAX = int(os.environ.get("RATE_MAX", "30"))
|
||||||
_rate = {} # ip -> [window_start, count]
|
_rate = {} # ip -> [window_start, count]
|
||||||
|
|
||||||
ADJ = ["brisk", "calm", "eager", "gentle", "merry", "nifty", "rapid", "sunny", "witty", "zesty"]
|
ADJ = [
|
||||||
NOUN = ["otter", "falcon", "comet", "ember", "grove", "harbor", "meadow", "raven", "river", "summit"]
|
"brisk","calm","eager","gentle","merry","nifty","rapid","sunny","witty","zesty",
|
||||||
|
"amber","bold","bright","crisp","daring","frosty","glad","jolly","lively","mellow",
|
||||||
|
"quiet","ripe","serene","spry","tidy","vivid","warm","wild","clever","kind",
|
||||||
|
]
|
||||||
|
NOUN = [
|
||||||
|
"otter","falcon","comet","ember","grove","harbor","meadow","raven","river","summit",
|
||||||
|
"breeze","cedar","cinder","cove","delta","forest","glade","lark","marsh","peak",
|
||||||
|
"pine","quartz","reef","ridge","sable","sage","shore","thunder","vale","zephyr",
|
||||||
|
]
|
||||||
|
|
||||||
def _json(method, url, *, headers=None, body=None, timeout=20):
|
def _json(method, url, *, headers=None, body=None, timeout=20):
|
||||||
hdrs = {"Content-Type": "application/json"}
|
hdrs = {"Content-Type": "application/json"}
|
||||||
|
|||||||
@ -4,41 +4,41 @@ kind: Kustomization
|
|||||||
namespace: comms
|
namespace: comms
|
||||||
resources:
|
resources:
|
||||||
- namespace.yaml
|
- namespace.yaml
|
||||||
- atlasbot-rbac.yaml
|
|
||||||
- synapse-rendered.yaml
|
|
||||||
- synapse-signingkey-ensure-job.yaml
|
|
||||||
- synapse-seeder-admin-ensure-job.yaml
|
|
||||||
- mas-configmap.yaml
|
- mas-configmap.yaml
|
||||||
- mas-admin-client-secret-ensure-job.yaml
|
|
||||||
- mas-secrets-ensure-rbac.yaml
|
|
||||||
- comms-secrets-ensure-rbac.yaml
|
|
||||||
- mas-db-ensure-rbac.yaml
|
|
||||||
- mas-db-ensure-job.yaml
|
|
||||||
- comms-secrets-ensure-job.yaml
|
|
||||||
- synapse-user-seed-job.yaml
|
|
||||||
- mas-local-users-ensure-job.yaml
|
|
||||||
- mas-deployment.yaml
|
|
||||||
- element-rendered.yaml
|
- element-rendered.yaml
|
||||||
- livekit-config.yaml
|
- livekit-config.yaml
|
||||||
- livekit.yaml
|
|
||||||
- coturn.yaml
|
|
||||||
- livekit-token-deployment.yaml
|
|
||||||
- livekit-ingress.yaml
|
|
||||||
- livekit-middlewares.yaml
|
|
||||||
- element-call-config.yaml
|
- element-call-config.yaml
|
||||||
- element-call-deployment.yaml
|
- element-call-deployment.yaml
|
||||||
- reset-othrys-room-job.yaml
|
|
||||||
- bstein-force-leave-job.yaml
|
|
||||||
- pin-othrys-job.yaml
|
|
||||||
- guest-name-job.yaml
|
|
||||||
- guest-register-configmap.yaml
|
- guest-register-configmap.yaml
|
||||||
- guest-register-deployment.yaml
|
- guest-register-deployment.yaml
|
||||||
- guest-register-service.yaml
|
- guest-register-service.yaml
|
||||||
- matrix-ingress.yaml
|
|
||||||
- atlasbot-configmap.yaml
|
- atlasbot-configmap.yaml
|
||||||
- atlasbot-deployment.yaml
|
- atlasbot-deployment.yaml
|
||||||
- seed-othrys-room.yaml
|
|
||||||
- wellknown.yaml
|
- wellknown.yaml
|
||||||
|
- atlasbot-rbac.yaml
|
||||||
|
- mas-secrets-ensure-rbac.yaml
|
||||||
|
- comms-secrets-ensure-rbac.yaml
|
||||||
|
- mas-db-ensure-rbac.yaml
|
||||||
|
- mas-admin-client-secret-ensure-job.yaml
|
||||||
|
- mas-db-ensure-job.yaml
|
||||||
|
- comms-secrets-ensure-job.yaml
|
||||||
|
- synapse-signingkey-ensure-job.yaml
|
||||||
|
- synapse-seeder-admin-ensure-job.yaml
|
||||||
|
- synapse-user-seed-job.yaml
|
||||||
|
- mas-local-users-ensure-job.yaml
|
||||||
|
- synapse-rendered.yaml
|
||||||
|
- mas-deployment.yaml
|
||||||
|
- livekit-token-deployment.yaml
|
||||||
|
- livekit.yaml
|
||||||
|
- coturn.yaml
|
||||||
|
- seed-othrys-room.yaml
|
||||||
|
- guest-name-job.yaml
|
||||||
|
- pin-othrys-job.yaml
|
||||||
|
- reset-othrys-room-job.yaml
|
||||||
|
- bstein-force-leave-job.yaml
|
||||||
|
- livekit-ingress.yaml
|
||||||
|
- livekit-middlewares.yaml
|
||||||
|
- matrix-ingress.yaml
|
||||||
|
|
||||||
patches:
|
patches:
|
||||||
- path: synapse-deployment-strategy-patch.yaml
|
- path: synapse-deployment-strategy-patch.yaml
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
# services/comms/mas-db-secret.yaml
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: mas-db
|
|
||||||
namespace: comms
|
|
||||||
type: Opaque
|
|
||||||
Loading…
x
Reference in New Issue
Block a user