From e4edcf1011fcbf264108aba62d98ec306aff6c6c Mon Sep 17 00:00:00 2001 From: Thomas Stallinger Date: Wed, 9 Sep 2026 14:59:09 +0200 Subject: [PATCH] Webapp-Mechanismus (Phase 2): neue Rolle webapp-shortcuts Kundeneigene URLs (workspace_webapps, siehe admin/provisioning-server) werden als .desktop-Menueintrag (/usr/share/applications) UND best-effort als Desktop-Icon ausgeliefert - kein present/absent pro Eintrag wie sonst im Katalog, sondern ein echter ABGLEICH: die Rolle bekommt bei jedem Lauf die komplette aktuelle Liste und sorgt dafuer, dass GENAU diese Eintraege existieren (Datei-Identifikation ueber die stabile DB-UUID, keine Namens-Slugifizierung noetig). Laeuft deshalb IMMER (site.yml-Play-Var tuxflotte_webapps, agent.py haengt sie immer in --tags ein, siehe admin/provisioning-agent), nicht nur bei Aenderungen. Desktop-Icon-Ablage bewusst pragmatisch: /etc/skel/Desktop (kuenftige Nutzer) + bestehende /home/*/Desktop-Ordner, falls schon vorhanden - kein erzwungenes Anlegen. Haengt an der noch offenen ADR-0026 (kein final festgelegter primaerer interaktiver Nutzer-Account) - deckt ab, was heute real existiert (z.B. den 'gast'-Account aus guest-session), ohne auf die endgueltige Klaerung zu warten. ECHT END-ZU-ENDE GETESTET (nicht nur Syntax-Check): echter ansible-playbook-Lauf gegen localhost mit Mock-Daten - Anlegen (mit+ohne Favicon), Aktualisieren, vollstaendiges Entfernen bei leerer Liste, Idempotenz. Favicon-Download-Pfad mit echter URL verifiziert. ansible-core dafuer neu installiert (vorher nicht vorhanden). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9 --- roles/webapp-shortcuts/defaults/main.yml | 3 + roles/webapp-shortcuts/tasks/main.yml | 140 ++++++++++++++++++ .../templates/webapp.desktop.j2 | 13 ++ site.yml | 13 ++ 4 files changed, 169 insertions(+) create mode 100644 roles/webapp-shortcuts/defaults/main.yml create mode 100644 roles/webapp-shortcuts/tasks/main.yml create mode 100644 roles/webapp-shortcuts/templates/webapp.desktop.j2 diff --git a/roles/webapp-shortcuts/defaults/main.yml b/roles/webapp-shortcuts/defaults/main.yml new file mode 100644 index 0000000..d7ac3a5 --- /dev/null +++ b/roles/webapp-shortcuts/defaults/main.yml @@ -0,0 +1,3 @@ +tuxflotte_webapps: [] +tuxflotte_webapp_icons_dir: /usr/local/share/icons/tuxflotte-webapps +tuxflotte_webapp_applications_dir: /usr/share/applications diff --git a/roles/webapp-shortcuts/tasks/main.yml b/roles/webapp-shortcuts/tasks/main.yml new file mode 100644 index 0000000..92bcaa4 --- /dev/null +++ b/roles/webapp-shortcuts/tasks/main.yml @@ -0,0 +1,140 @@ +# Webapp-Mechanismus (Phase 2, 09.09.2026): kundeneigene URLs (kein fester +# Katalog-Merkmal wie sonst im Auftragskatalog - jeder Workspace hat seine +# eigene, freie Liste, siehe fetch_workspace_webapps() in +# provisioning-server/app.py). Deshalb kein present/absent pro Eintrag wie +# bei den anderen Rollen, sondern ein ECHTER ABGLEICH: die Rolle bekommt bei +# jedem Lauf die komplette aktuelle Liste (tuxflotte_webapps) und sorgt +# dafür, dass GENAU diese Einträge existieren - alles andere (frühere, +# inzwischen entfernte Webapps) wird aufgeräumt. Läuft deshalb bei jedem +# ansible-pull IMMER (siehe agent.py apply_roles()), nicht nur wenn sich +# etwas geändert hat. +# +# Dateibenennung nutzt die stabile UUID aus der DB (item.id), keine +# Slugifizierung des Namens nötig - vermeidet Kollisionen/Sonderzeichen- +# Probleme bei Namensänderungen. + +- name: Ensure marker directory exists + ansible.builtin.file: + path: /run/tuxflotte/agent/applied + state: directory + mode: "0755" + +- name: Ensure icon and applications directories exist + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: "0755" + loop: + - "{{ tuxflotte_webapp_icons_dir }}" + - "{{ tuxflotte_webapp_applications_dir }}" + - /etc/skel/Desktop + +- name: Find existing tuxflotte-webapp .desktop files + ansible.builtin.find: + paths: "{{ tuxflotte_webapp_applications_dir }}" + patterns: "tuxflotte-webapp-*.desktop" + register: tuxflotte_webapp_existing_files + +- name: Desired vs. vorhandene .desktop-Pfade ermitteln + ansible.builtin.set_fact: + tuxflotte_webapp_desired_files: >- + {{ + tuxflotte_webapps + | map(attribute='id') + | map('regex_replace', '^(.*)$', tuxflotte_webapp_applications_dir ~ '/tuxflotte-webapp-\1.desktop') + | list + }} + tuxflotte_webapp_existing_paths: "{{ tuxflotte_webapp_existing_files.files | map(attribute='path') | list }}" + +- name: Stale-Dateien (nicht mehr in der aktuellen Liste) berechnen + ansible.builtin.set_fact: + tuxflotte_webapp_stale_files: "{{ tuxflotte_webapp_existing_paths | difference(tuxflotte_webapp_desired_files) }}" + +- name: Remove stale webapp .desktop files (nicht mehr in der aktuellen Liste) + ansible.builtin.file: + path: "{{ item }}" + state: absent + loop: "{{ tuxflotte_webapp_stale_files }}" + +- name: Download favicon for each webapp (falls vorhanden) + ansible.builtin.get_url: + url: "{{ item.icon_url }}" + dest: "{{ tuxflotte_webapp_icons_dir }}/{{ item.icon_url | basename }}" + mode: "0644" + timeout: 10 + loop: "{{ tuxflotte_webapps }}" + loop_control: + label: "{{ item.name }}" + when: item.icon_url is not none + # Best-effort: ein einzelnes nicht erreichbares Favicon (Seite down, + # Netzwerkproblem auf dem Geraet) soll nicht den ganzen Lauf abbrechen - + # der .desktop-Eintrag selbst wird trotzdem angelegt (Icon zeigt dann + # kurzzeitig auf eine fehlende Datei, GTK faellt auf ein Platzhalter-Icon + # zurueck, kein Absturz). + ignore_errors: true + +- name: Create/update .desktop menu entry for each webapp + ansible.builtin.template: + src: webapp.desktop.j2 + dest: "{{ tuxflotte_webapp_applications_dir }}/tuxflotte-webapp-{{ item.id }}.desktop" + mode: "0644" + loop: "{{ tuxflotte_webapps }}" + loop_control: + label: "{{ item.name }}" + +- name: Copy .desktop entries into /etc/skel/Desktop (für künftig neu angelegte Nutzer) + ansible.builtin.copy: + src: "{{ tuxflotte_webapp_applications_dir }}/tuxflotte-webapp-{{ item.id }}.desktop" + dest: "/etc/skel/Desktop/tuxflotte-webapp-{{ item.id }}.desktop" + mode: "0755" + remote_src: true + loop: "{{ tuxflotte_webapps }}" + loop_control: + label: "{{ item.name }}" + +- name: Remove stale .desktop entries from /etc/skel/Desktop + ansible.builtin.file: + path: "/etc/skel/Desktop/{{ item | basename }}" + state: absent + loop: "{{ tuxflotte_webapp_stale_files }}" + +# Bestehende Nutzer-Homeverzeichnisse (z.B. der "gast"-Account aus der +# guest-session-Rolle, falls aktiv): nur befüllen, wenn dort bereits ein +# Desktop-Ordner existiert (kein aufgezwungenes Anlegen für Nutzer, deren +# Desktop-Umgebung keine Desktop-Icons zeigt). ADR-0026 (kein primärer +# interaktiver Nutzer-Account bisher final festgelegt) ist hier eine +# bekannte offene Abhängigkeit - dieser bewusst pragmatische Ansatz deckt +# ab, was heute an Accounts real existiert, ohne auf die endgültige +# Klärung zu warten. Siehe project_phase2_app_store_merkmale-Memory. +- name: Bestehende Nutzer-Homeverzeichnisse mit Desktop-Ordner ermitteln + ansible.builtin.find: + paths: /home + file_type: directory + patterns: "Desktop" + recurse: true + depth: 2 + register: tuxflotte_webapp_user_desktops + +- name: .desktop-Einträge in bestehende Nutzer-Desktop-Ordner kopieren + ansible.builtin.copy: + src: "{{ tuxflotte_webapp_applications_dir }}/tuxflotte-webapp-{{ item.0.id }}.desktop" + dest: "{{ item.1.path }}/tuxflotte-webapp-{{ item.0.id }}.desktop" + mode: "0755" + remote_src: true + loop: "{{ tuxflotte_webapps | product(tuxflotte_webapp_user_desktops.files) | list }}" + loop_control: + label: "{{ item.0.name }} -> {{ item.1.path }}" + +- name: Überzählige .desktop-Einträge aus bestehenden Nutzer-Desktop-Ordnern entfernen + ansible.builtin.file: + path: "{{ item.1.path }}/{{ item.0 | basename }}" + state: absent + loop: "{{ tuxflotte_webapp_stale_files | product(tuxflotte_webapp_user_desktops.files) | list }}" + loop_control: + label: "{{ item.0 }} -> {{ item.1.path }}" + +- name: Record that the webapp-shortcuts role ran + ansible.builtin.copy: + dest: /run/tuxflotte/agent/applied/webapp-shortcuts.marker + content: "{{ ansible_date_time.iso8601 }}: {{ tuxflotte_webapps | length }} webapp(s)\n" + mode: "0644" diff --git a/roles/webapp-shortcuts/templates/webapp.desktop.j2 b/roles/webapp-shortcuts/templates/webapp.desktop.j2 new file mode 100644 index 0000000..373f2b6 --- /dev/null +++ b/roles/webapp-shortcuts/templates/webapp.desktop.j2 @@ -0,0 +1,13 @@ +[Desktop Entry] +Type=Application +Name={{ item.name }} +Comment=Tuxflotte-Webapp: {{ item.url }} +Exec=xdg-open {{ item.url }} +{% if item.icon_url %} +Icon={{ tuxflotte_webapp_icons_dir }}/{{ item.icon_url | basename }} +{% else %} +Icon=web-browser +{% endif %} +Terminal=false +Categories=Network;WebBrowser; +NoDisplay=false diff --git a/site.yml b/site.yml index aadf5ae..c571dfd 100644 --- a/site.yml +++ b/site.yml @@ -11,6 +11,11 @@ # z.B. {"standardbrowser": true} bei brave. Rein additiv, opak für den # Server/Agenten - nur die jeweilige Rolle kennt die Bedeutung. tuxflotte_auftrag_optionen: {} + # Vom Agenten per --extra-vars gesetzt (Check-in-Response "webapps", + # Phase 2 09.09.2026): Liste kundeneigener {id, name, url, icon_url}- + # Einträge, verarbeitet ausschließlich von der webapp-shortcuts-Rolle + # (siehe dort) - kein present/absent-Merkmal wie der Rest dieser Datei. + tuxflotte_webapps: [] tasks: - include_role: name: guest-session @@ -362,3 +367,11 @@ tuxflotte_state: "{{ tuxflotte_auftrag_states.get('turbowarp', 'present') }}" tuxflotte_optionen: "{{ tuxflotte_auftrag_optionen.get('turbowarp', {}) }}" tags: turbowarp + + - include_role: + name: webapp-shortcuts + apply: + tags: webapp-shortcuts + vars: + tuxflotte_webapps: "{{ tuxflotte_webapps }}" + tags: webapp-shortcuts