feat(overleaf): persist activation links via post-Ansible SSH fetch - #165
Merged
Conversation
Overleaf is the first app where credentials are not generated pre-Ansible (no password / SSH key per user) but rather as one-time activation URLs emitted by the Overleaf CLI during the playbook run. The playbook writes them to /opt/dozilab/OVERLEAF_USERS.json (mode 0600 root:root); without this change they live nowhere outside the VM. Wiring (generic, reusable for future apps that follow the same JSON shape): - AnsibleService: new fetch_remote_file / fetch_remote_json helpers that ssh+sudo cat a root-owned path off the VM. Never raises — missing file is logged as INFO and returns None so unrelated apps keep working. - AccessType.ACTIVATION_LINK added; new Alembic migration extends the postgres `accesstype` enum with the new value. - DeploymentCredentialService.persist_activation_links() takes the parsed JSON plus a username→course_groups.id map, writes one access row per admin + per group. Bypasses the pre-Ansible password/key filter on purpose. Unknown usernames are skipped with a warning rather than written with NULL group_id (which would leak to no student through the self-service filter). - deploy_tasks: after run_playbooks succeeds, attempt the fetch and the persist call. Wrapped in its own try/except — a fetch failure logs WARNING but never fails the deployment (the file remains on the VM for manual recovery). API/schema unchanged: access_type is already serialized as `.value` string, schemas don't validate the enum, student-self-service filters on group_id only — students automatically see their group's link, never the admin link.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Was und warum
Overleaf ist die erste App, bei der Credentials nicht vor Ansible generiert werden (keine Passwörter, keine SSH-Keys pro User). Stattdessen erzeugt der Overleaf-CLI während des Playbook-Runs einmalige Aktivierungslinks (
/user/activate?token=...) und das Playbook schreibt sie auf der VM nach/opt/dozilab/OVERLEAF_USERS.json(mode0600root:root). Vor diesem PR landen die Links nirgends außerhalb der VM — Lehrkraft müsste SSH-en und die Datei manuell auslesen.Datenfluss nach dem PR
Änderungen
Generisch (für jede zukünftige App nachnutzbar)
AnsibleService.fetch_remote_file/fetch_remote_json— neuer SSH-Read-Back-Helper.ssh sudo cat <path>weil die Datei0600 root:rootist (plainscpals unprivilegierter Login-User scheitert). Niemals raisend — fehlende Datei → INFO-Log +Nonezurück, sodass andere Apps die diese Datei nicht schreiben unverändert laufen.AccessType.ACTIVATION_LINK+ Alembic-MigrationALTER TYPE accesstype ADD VALUE IF NOT EXISTS 'ACTIVATION_LINK'. Idempotent (IF NOT EXISTS),downgrade()leer mit Kommentar zu PG-Enum-Removal.Overleaf-spezifisch
DeploymentCredentialService.persist_activation_links(instance_id, overleaf_users_json, username_to_group_id)— neue Methode, bypassed denpassword or ssh_private_key-Filter in_extract_access_entriesbewusst. Strippt Trailing-Whitespace. Wenn die Username-Zuordnung scheitert: Warning loggen statt mitgroup_id=NULLschreiben (sonst würde der Link via Student-Self-Service-Filter an niemanden gehen — sicherer als an alle).deploy_tasks.py— Rückgabewert vonpersist_credentials_for_stackwird aufinstancegebunden. Nach erfolgreichemrun_playbooks(Zeile ~347): Fetch + Persist in eigenemtry/except. Failed niemals das Deployment — die JSON liegt weiterhin auf der VM in/opt/dozilab/OVERLEAF_USERS.{json,txt}für manuelle Recovery.TODO-Kommentar im deploy_tasks für den generischenapp.yaml: post_ansible_outputs-Pattern, sobald eine zweite App das braucht. Helper-Methode aufAnsibleServiceist aber bereits generisch.Bewusst NICHT geändert
/deployments/{id}/credentials, Student-Self-Service):access_typewird über.valueals String serialisiert → neue Enum-Werte laufen durch.DeploymentCredentialEntry.access_type: str): permissiv, keine Whitelist.group_id→ Studenten sehen automatisch ihren Gruppen-Link, nie den Admin-Link (group_id=NULL)._extract_access_entries-Filter (Zeile 123): bleibt strikt. Pre-Ansible-Persistenz-Semantik bleibt unverändert.Migration ausführen
Pflicht vor dem ersten Overleaf-Deployment, sonst schlägt der erste Schreibversuch in eine
ACTIVATION_LINK-Zeile fehl.Testplan
Unit-Tests (kein VM nötig):
AnsibleService.fetch_remote_json: success → dict,"No such file"stderr → None+INFO, malformed JSON → None+WARNING, Timeout → None+WARNING.DeploymentCredentialService.persist_activation_links: Admin-Eintrag → 1 Zeile mitgroup_id IS NULL; 2 Gruppen-Einträge mit Mapping → 2 Zeilen mit korrektergroup_id; unbekannter Username → 0 Zeilen + Warning; URL mit Trailing-\n→ gespeichert ohne.alembic upgrade headgegen Dev-PG →SELECT enum_range(NULL::accesstype);enthältACTIVATION_LINK.End-to-End:
Persisted N activation-link credential(s) for stack 1suchen./api/v1/student/.../credentialszeigt nur den eigenen Gruppen-Link.create_overleaf_users=false→ Playbook schreibt keine JSON → Deployment-Log enthält INFORemote file not present, skipping: /opt/dozilab/OVERLEAF_USERS.json. Deployment erfolgreich.Verbundene PRs
activation_link-Zeilen als klickbaren Link, Password-Block ausgeblendet.main: Playbook-trim-Filter für die Activation-URL-Extraktion (Trailing-\naus Overleaf-CLI-Output entfernen).