diff --git a/roles/web-app-socialhome/TODO.md b/roles/web-app-socialhome/TODO.md index 9a64c909c3..f97a3cd899 100644 --- a/roles/web-app-socialhome/TODO.md +++ b/roles/web-app-socialhome/TODO.md @@ -40,7 +40,6 @@ Open items that only a live run can settle: ## Open from the design pass -- Title override: `SH_INSTANCE_NAME` reaches the home name only on the first boot, because `ensure_instance_identity` (upstream `socialhome/identity_bootstrap.py`) writes `display_name` once and `GET /api/instance/config` prefers that row; an inventory title set later changes the env but not the shown name. No route writes that row at 2026.10.8 (measured: `routes/instance.py` only reads `federation_repo.get_local_identity()`, and no `set`/`update` of the local identity exists in the package), and the row is the identity peers receive. Options: ask upstream for an admin route that renames the household, or accept first-boot only. - Shared sign-in budget: `POST /api/auth/token` allows 5 attempts per 15 minutes per `request.remote`, and the app ignores `X-Forwarded-For`, so every browser behind the front proxy and the deploy's own token request share one bucket (measured: all logins in `/api/admin/auth-audit` come from `192.168.75.1`). At 2026.10.11 the budget is the module constant `AUTH_TOKEN_RATE_LIMIT` in `socialhome/routes/users.py` with no env or TOML key, and `trusted_proxies` exists for the GFS only (`socialhome/global_server/config.py`); the design spec keeps one administrator token in the Playwright staging volume. Options: let the proxy reach the app with the client address (PROXY protocol or upstream trusted-proxy support), or ask upstream to honour a trusted forwarded header. - `tasks/01_setup.yml` comment is stale: at 2026.10.8 the headless seed marks setup complete itself (upstream `platform/standalone/adapter.py`), so the task always answers 409 (measured: `ok`, never `changed`). Options: drop the task or rewrite the comment. - `meta/csp.yml` keeps `script-src-elem unsafe-inline` for an inline theme script the 2026.10.8 shell no longer has (it loads `assets/theme-boot.js`; measured: the served `/feed` carries no inline script of its own). Options: drop the flag after checking `hlth-csp`; the injected design script then needs its CSP hash, which `sys-front-inj-design` adds. diff --git a/roles/web-app-socialhome/files/playwright/test-design.js b/roles/web-app-socialhome/files/playwright/test-design.js index 6116b6edad..be0a2b4a34 100644 --- a/roles/web-app-socialhome/files/playwright/test-design.js +++ b/roles/web-app-socialhome/files/playwright/test-design.js @@ -569,6 +569,12 @@ exports.register = function () { } await expect(page.locator("link[rel~='icon']").first()).toHaveAttribute("href", faviconUrl); } + if (title) { + const config = await apiGetOnion(page.request, `${base}/api/instance/config`); + expect((await config.json()).instance_name, "the household carries the configured title").toBe(title); + await visit(page, `${base}/feed`); + await expect(page.locator(TOPBAR_TITLE), "the feed is named after the household").toHaveText(title); + } }); test("design: the sign-in page shows the generated logo", async ({ page }) => { diff --git a/roles/web-app-socialhome/tasks/02_design.yml b/roles/web-app-socialhome/tasks/02_design.yml index 33a79c9f01..c133fae330 100644 --- a/roles/web-app-socialhome/tasks/02_design.yml +++ b/roles/web-app-socialhome/tasks/02_design.yml @@ -5,48 +5,20 @@ checksum_algorithm: sha1 register: socialhome_theme_marker -- name: "๐ŸŽจ Converge the household primary colour" - when: SOCIALHOME_DESIGN_ENABLED | bool or socialhome_theme_marker.stat.exists - block: - - name: "๐Ÿ”‘ Request an administrator token for the household theme" - uri_retry: - url: "{{ SOCIALHOME_INTERNAL_URL }}/api/auth/token" - method: POST - body_format: json - body: - username: "{{ SOCIALHOME_ADMIN_USERNAME }}" - password: "{{ SOCIALHOME_ADMIN_PASSWORD }}" - status_code: 200 - register: socialhome_theme_token - no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" +- name: "๐Ÿ“ฅ Look up the household name the role wrote last" + ansible.builtin.stat: + path: "{{ SOCIALHOME_NAME_STATE_FILE }}" + register: socialhome_name_marker - - name: "๐ŸŽจ Read the household theme" - uri_retry: - url: "{{ SOCIALHOME_INTERNAL_URL }}/api/theme" - method: GET - headers: - Authorization: "Bearer {{ socialhome_theme_token.json.token }}" - status_code: 200 - register: socialhome_theme - no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" +- name: "๐Ÿ“ฅ Read the household name the role wrote last" + ansible.builtin.slurp: + src: "{{ SOCIALHOME_NAME_STATE_FILE }}" + register: socialhome_name_state_file + when: socialhome_name_marker.stat.exists - - name: "๐ŸŽจ Write the household primary colour" - uri_retry: - url: "{{ SOCIALHOME_INTERNAL_URL }}/api/theme" - method: PUT - body_format: json - body: - primary_color: "{{ SOCIALHOME_THEME_PRIMARY }}" - headers: - Authorization: "Bearer {{ socialhome_theme_token.json.token }}" - status_code: 200 - register: socialhome_theme_written - changed_when: true - when: - - socialhome_theme.json.primary_color | lower != SOCIALHOME_THEME_PRIMARY - - socialhome_theme.json.primary_color | lower == SOCIALHOME_BRAND_PRIMARY or (socialhome_theme_marker.stat.exists and - socialhome_theme_marker.stat.checksum == (socialhome_theme.json.primary_color | lower | hash('sha1'))) - no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" +- name: "๐ŸŽจ Converge the household primary colour and name" + include_tasks: 02_design/converge.yml + when: SOCIALHOME_DESIGN_ENABLED | bool or socialhome_theme_marker.stat.exists or socialhome_name_marker.stat.exists - name: "๐Ÿท๏ธ Mark the household primary colour as the role's" stack_host_copy: @@ -57,8 +29,24 @@ - SOCIALHOME_DESIGN_ENABLED | bool - socialhome_theme_written is changed +- name: "๐Ÿท๏ธ Mark the household name as the role's" + stack_host_copy: + content: "{{ {'applied': SOCIALHOME_DESIGN_TITLE, 'previous': (socialhome_name_state.previous if socialhome_name_state | length > 0 else socialhome_name_current)} + | to_json }}" + dest: "{{ SOCIALHOME_NAME_STATE_FILE }}" + mode: "0644" + when: + - SOCIALHOME_DESIGN_TITLE | length > 0 + - socialhome_name_written is changed or (socialhome_name_state | length == 0 and socialhome_name_current == SOCIALHOME_DESIGN_TITLE) + - name: "๐Ÿงน Drop the household theme marker" ansible.builtin.file: path: "{{ SOCIALHOME_THEME_MARKER_FILE }}" state: absent when: not (SOCIALHOME_DESIGN_ENABLED | bool) + +- name: "๐Ÿงน Drop the household name marker" + ansible.builtin.file: + path: "{{ SOCIALHOME_NAME_STATE_FILE }}" + state: absent + when: SOCIALHOME_DESIGN_TITLE | length == 0 diff --git a/roles/web-app-socialhome/tasks/02_design/converge.yml b/roles/web-app-socialhome/tasks/02_design/converge.yml new file mode 100644 index 0000000000..ac72fc6129 --- /dev/null +++ b/roles/web-app-socialhome/tasks/02_design/converge.yml @@ -0,0 +1,73 @@ +--- +- name: "๐Ÿ”‘ Request an administrator token for the household theme" + uri_retry: + url: "{{ SOCIALHOME_INTERNAL_URL }}/api/auth/token" + method: POST + body_format: json + body: + username: "{{ SOCIALHOME_ADMIN_USERNAME }}" + password: "{{ SOCIALHOME_ADMIN_PASSWORD }}" + status_code: 200 + register: socialhome_theme_token + no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" + +- name: "๐ŸŽจ Read the household theme" + uri_retry: + url: "{{ SOCIALHOME_INTERNAL_URL }}/api/theme" + method: GET + headers: + Authorization: "Bearer {{ socialhome_theme_token.json.token }}" + status_code: 200 + register: socialhome_theme + no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" + +- name: "๐ŸŽจ Write the household primary colour" + uri_retry: + url: "{{ SOCIALHOME_INTERNAL_URL }}/api/theme" + method: PUT + body_format: json + body: + primary_color: "{{ SOCIALHOME_THEME_PRIMARY }}" + headers: + Authorization: "Bearer {{ socialhome_theme_token.json.token }}" + status_code: 200 + register: socialhome_theme_written + changed_when: true + when: + - socialhome_theme.json.primary_color | lower != SOCIALHOME_THEME_PRIMARY + - socialhome_theme.json.primary_color | lower == SOCIALHOME_BRAND_PRIMARY or (socialhome_theme_marker.stat.exists and socialhome_theme_marker.stat.checksum + == (socialhome_theme.json.primary_color | lower | hash('sha1'))) + no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" + +- name: "๐Ÿท๏ธ Read the household name" + uri_retry: + url: "{{ SOCIALHOME_INTERNAL_URL }}/api/instance/config" + method: GET + status_code: 200 + register: socialhome_instance_config + +- name: "๐Ÿท๏ธ Hold the household name and the name the role wrote last" + ansible.builtin.set_fact: + socialhome_name_current: "{{ socialhome_instance_config.json.instance_name }}" + socialhome_name_state: "{{ (socialhome_name_state_file.content | b64decode | from_json) if socialhome_name_marker.stat.exists else {} }}" + +- name: "๐Ÿท๏ธ Rename the household" + uri_retry: + url: "{{ SOCIALHOME_INTERNAL_URL }}/api/admin/instance" + method: PATCH + body_format: json + body: + display_name: "{{ socialhome_name_target }}" + headers: + Authorization: "Bearer {{ socialhome_theme_token.json.token }}" + status_code: 200 + vars: + socialhome_name_ours: "{{ socialhome_name_state | length > 0 and socialhome_name_current == socialhome_name_state.applied }}" + socialhome_name_target: >- + {{ (socialhome_name_state.previous if socialhome_name_ours | bool else '') if SOCIALHOME_DESIGN_TITLE | length == 0 + else ('' if (socialhome_name_state | length > 0 and not (socialhome_name_ours | bool)) or socialhome_name_current == SOCIALHOME_DESIGN_TITLE + else SOCIALHOME_DESIGN_TITLE) }} + register: socialhome_name_written + changed_when: true + when: socialhome_name_target | length > 0 + no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" diff --git a/roles/web-app-socialhome/tasks/main.yml b/roles/web-app-socialhome/tasks/main.yml index 17fb8f576a..c13e243e8f 100644 --- a/roles/web-app-socialhome/tasks/main.yml +++ b/roles/web-app-socialhome/tasks/main.yml @@ -22,5 +22,5 @@ - name: "๐Ÿ” Complete the standalone first-boot setup for '{{ application_id }}'" include_tasks: 01_setup.yml - - name: "๐ŸŽจ Converge the household theme for '{{ application_id }}'" + - name: "๐ŸŽจ Converge the household theme and name for '{{ application_id }}'" include_tasks: 02_design.yml diff --git a/roles/web-app-socialhome/vars/main.yml b/roles/web-app-socialhome/vars/main.yml index f94df8960c..7caa51932f 100644 --- a/roles/web-app-socialhome/vars/main.yml +++ b/roles/web-app-socialhome/vars/main.yml @@ -16,6 +16,9 @@ SOCIALHOME_CONFIG_HOST_FILE: "{{ [lookup('container', application_id, 'directori SOCIALHOME_MAX_STORAGE_BYTES: 4294967296 SOCIALHOME_DESIGN_ENABLED: "{{ lookup('config', application_id, 'services.design.enabled') | bool }}" +SOCIALHOME_DESIGN_TITLE: "{{ lookup('design', application_id).title if SOCIALHOME_DESIGN_ENABLED | bool and lookup('design', application_id).title is string else + '' }}" +SOCIALHOME_NAME_STATE_FILE: "{{ [lookup('container', application_id, 'directories.volumes'), 'household-name.json'] | path_join }}" SOCIALHOME_BRAND_PRIMARY: "#d2542a" SOCIALHOME_THEME_PRIMARY: >- {{ (lookup('design_palette', lookup('config', 'web-svc-design', 'services.design.colors.base')).light['--design-primary'] | lower)