Skip to content

feat(overleaf): persist activation links via post-Ansible SSH fetch - #165

Merged
DarkSerme merged 1 commit into
stagingfrom
feature/overleaf-activation-links
Jun 29, 2026
Merged

feat(overleaf): persist activation links via post-Ansible SSH fetch#165
DarkSerme merged 1 commit into
stagingfrom
feature/overleaf-activation-links

Conversation

@DarkSerme

Copy link
Copy Markdown
Contributor

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 (mode 0600 root: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

Heat Stack → SSH-Creds wie bisher in DB (vor Ansible)
        ↓
Ansible Playbook → schreibt /opt/dozilab/OVERLEAF_USERS.json
        ↓
[NEU] AnsibleService.fetch_remote_json(...)
        ↓
[NEU] DeploymentCredentialService.persist_activation_links(...)
        → DeploymentInstanceAccess rows mit access_type=ACTIVATION_LINK
        → connection_url = die URL, group_id = course_groups.id (NULL für Admin)
        ↓
GET /deployments/{id}/credentials  (unverändert)
        ↓
Frontend rendert als klickbaren Link  (separater Frontend-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 Datei 0600 root:root ist (plain scp als unprivilegierter Login-User scheitert). Niemals raisend — fehlende Datei → INFO-Log + None zurück, sodass andere Apps die diese Datei nicht schreiben unverändert laufen.
  • AccessType.ACTIVATION_LINK + Alembic-Migration ALTER 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 den password or ssh_private_key-Filter in _extract_access_entries bewusst. Strippt Trailing-Whitespace. Wenn die Username-Zuordnung scheitert: Warning loggen statt mit group_id=NULL schreiben (sonst würde der Link via Student-Self-Service-Filter an niemanden gehen — sicherer als an alle).
  • deploy_tasks.py — Rückgabewert von persist_credentials_for_stack wird auf instance gebunden. Nach erfolgreichem run_playbooks (Zeile ~347): Fetch + Persist in eigenem try/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 generischen app.yaml: post_ansible_outputs-Pattern, sobald eine zweite App das braucht. Helper-Methode auf AnsibleService ist aber bereits generisch.

Bewusst NICHT geändert

  • API-Endpoints (/deployments/{id}/credentials, Student-Self-Service): access_type wird über .value als String serialisiert → neue Enum-Werte laufen durch.
  • Schemas (DeploymentCredentialEntry.access_type: str): permissiv, keine Whitelist.
  • Student-Filter: arbeitet nur über 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

alembic upgrade head

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 mit group_id IS NULL; 2 Gruppen-Einträge mit Mapping → 2 Zeilen mit korrekter group_id; unbekannter Username → 0 Zeilen + Warning; URL mit Trailing-\n → gespeichert ohne.
  • Migration: alembic upgrade head gegen Dev-PG → SELECT enum_range(NULL::accesstype); enthält ACTIVATION_LINK.

End-to-End:

  1. Overleaf-Deployment mit ≥1 Gruppe via Wizard starten.
  2. Im Deployment-Log nach Persisted N activation-link credential(s) for stack 1 suchen.
  3. Dozenten-Credentials-View: zusätzliche „Aktivierungslink"-Zeile (Admin) + eine pro Gruppe (separater Frontend-PR).
  4. Als Student in „Gruppe 1" → /api/v1/student/.../credentials zeigt nur den eigenen Gruppen-Link.
  5. Negativ-Fall: create_overleaf_users=false → Playbook schreibt keine JSON → Deployment-Log enthält INFO Remote file not present, skipping: /opt/dozilab/OVERLEAF_USERS.json. Deployment erfolgreich.

Verbundene PRs

  • Frontend-PR (folgt) — rendert die neuen activation_link-Zeilen als klickbaren Link, Password-Block ausgeblendet.
  • AppStore-Apps main: Playbook-trim-Filter für die Activation-URL-Extraktion (Trailing-\n aus Overleaf-CLI-Output entfernen).

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.
@DarkSerme
DarkSerme requested a review from xian421 June 29, 2026 10:31
@DarkSerme
DarkSerme merged commit 61e03e6 into staging Jun 29, 2026
6 checks passed
@DarkSerme
DarkSerme deleted the feature/overleaf-activation-links branch June 29, 2026 10:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant