diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 47658dc..0f7eb0b 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,7 +1,8 @@ { "permissions": { "allow": [ - "Bash(ls -la /home/iroddis/dev/sovereign/.*)" + "Bash(ls -la /home/iroddis/dev/sovereign/.*)", + "Bash(ls -la /home/iroddis/dev/sovereign/roles/*/templates/)" ] } } diff --git a/inventories/production/group_vars/all.yml b/inventories/production/group_vars/all.yml index fc30086..5f6a19b 100644 --- a/inventories/production/group_vars/all.yml +++ b/inventories/production/group_vars/all.yml @@ -7,6 +7,25 @@ # Base domain - all services are subdomains of this base_domain: "example.com" +# ============================================================================= +# BRANDING +# Applied across all services that support custom branding. +# ============================================================================= + +# Display name shown in service UIs and email subjects +tenant_name: "Example Corp" + +# Path to a logo image on the Ansible control machine (PNG or SVG recommended). +# Leave empty to use each service's default logo. +# Example: "files/logo.png" +tenant_logo_local_path: "" + +# Primary brand colour (hex). Used for backgrounds, buttons, and highlights. +tenant_primary_color: "#2563eb" + +# Accent / secondary colour (hex). +tenant_accent_color: "#1e40af" + # Base directory for all service data sovereign_base_dir: /opt/sovereign diff --git a/roles/authentik/tasks/main.yml b/roles/authentik/tasks/main.yml index 463e44f..fe0f641 100644 --- a/roles/authentik/tasks/main.yml +++ b/roles/authentik/tasks/main.yml @@ -7,10 +7,25 @@ loop: - "{{ authentik_data_dir }}" - "{{ authentik_data_dir }}/media" + - "{{ authentik_data_dir }}/media/branding" - "{{ authentik_data_dir }}/custom-templates" + - "{{ authentik_data_dir }}/blueprints" - "{{ authentik_data_dir }}/certs" - "{{ authentik_data_dir }}/postgres" +- name: Deploy Authentik branding blueprint + ansible.builtin.template: + src: branding-blueprint.yaml.j2 + dest: "{{ authentik_data_dir }}/blueprints/sovereign-branding.yaml" + mode: '0644' + +- name: Copy tenant logo to Authentik media + ansible.builtin.copy: + src: "{{ tenant_logo_local_path }}" + dest: "{{ authentik_data_dir }}/media/branding/logo.png" + mode: '0644' + when: tenant_logo_local_path | default('') != '' + - name: Deploy Authentik docker-compose ansible.builtin.template: src: docker-compose.yml.j2 diff --git a/roles/authentik/templates/branding-blueprint.yaml.j2 b/roles/authentik/templates/branding-blueprint.yaml.j2 new file mode 100644 index 0000000..efb99e8 --- /dev/null +++ b/roles/authentik/templates/branding-blueprint.yaml.j2 @@ -0,0 +1,23 @@ +--- +# Sovereign branding blueprint — applied automatically by Authentik on startup. +# Customises the default brand with the tenant name, logo, and colour scheme. +version: 1 +metadata: + name: Sovereign Tenant Branding + labels: + blueprints.goauthentik.io/description: Tenant branding configuration managed by Ansible +entries: + - model: authentik_brands.brand + state: present + identifiers: + domain: authentik-default + attrs: + branding_title: "{{ tenant_name }}" +{% if tenant_logo_local_path | default('') != '' %} + branding_logo: /media/branding/logo.png + branding_favicon: /media/branding/logo.png +{% endif %} + branding_custom_css: | + :root { + --ak-accent: {{ tenant_primary_color | default('#fd4b2d') }}; + } diff --git a/roles/authentik/templates/docker-compose.yml.j2 b/roles/authentik/templates/docker-compose.yml.j2 index f64b3ce..e018c1e 100644 --- a/roles/authentik/templates/docker-compose.yml.j2 +++ b/roles/authentik/templates/docker-compose.yml.j2 @@ -61,6 +61,7 @@ services: volumes: - {{ authentik_data_dir }}/media:/media - {{ authentik_data_dir }}/custom-templates:/templates + - {{ authentik_data_dir }}/blueprints:/blueprints/custom - {{ authentik_data_dir }}/certs:/certs ports: - "127.0.0.1:9001:9000" diff --git a/roles/forgejo/tasks/main.yml b/roles/forgejo/tasks/main.yml index 5008baf..d7ee855 100644 --- a/roles/forgejo/tasks/main.yml +++ b/roles/forgejo/tasks/main.yml @@ -7,8 +7,16 @@ loop: - "{{ forgejo_data_dir }}" - "{{ forgejo_data_dir }}/data" + - "{{ forgejo_data_dir }}/data/gitea/public/img" - "{{ forgejo_data_dir }}/config" +- name: Copy tenant logo to Forgejo custom assets + ansible.builtin.copy: + src: "{{ tenant_logo_local_path }}" + dest: "{{ forgejo_data_dir }}/data/gitea/public/img/logo.png" + mode: '0644' + when: tenant_logo_local_path | default('') != '' + - name: Deploy Forgejo docker-compose ansible.builtin.template: src: docker-compose.yml.j2 diff --git a/roles/forgejo/templates/docker-compose.yml.j2 b/roles/forgejo/templates/docker-compose.yml.j2 index ec0cab7..dd57681 100644 --- a/roles/forgejo/templates/docker-compose.yml.j2 +++ b/roles/forgejo/templates/docker-compose.yml.j2 @@ -49,6 +49,7 @@ services: FORGEJO__openid__ENABLE_OPENID_SIGNUP: "false" FORGEJO__oauth2_client__REGISTER_EMAIL_CONFIRM: "false" FORGEJO__oauth2_client__ENABLE_AUTO_REGISTRATION: "true" + FORGEJO____APP_NAME: "{{ tenant_name }}" FORGEJO__log__LEVEL: warn ports: - "{{ forgejo_ssh_port }}:22" diff --git a/roles/jitsi/tasks/main.yml b/roles/jitsi/tasks/main.yml index 3592d7b..d5705f2 100644 --- a/roles/jitsi/tasks/main.yml +++ b/roles/jitsi/tasks/main.yml @@ -11,6 +11,21 @@ - "{{ jitsi_data_dir }}/jicofo" - "{{ jitsi_data_dir }}/jvb" +- name: Deploy Jitsi custom interface config + ansible.builtin.template: + src: custom-interface-config.js.j2 + dest: "{{ jitsi_data_dir }}/web/custom-interface-config.js" + mode: '0644' + notify: restart jitsi + +- name: Copy tenant logo to Jitsi watermark + ansible.builtin.copy: + src: "{{ tenant_logo_local_path }}" + dest: "{{ jitsi_data_dir }}/web/watermark.png" + mode: '0644' + when: tenant_logo_local_path | default('') != '' + notify: restart jitsi + - name: Deploy Jitsi docker-compose ansible.builtin.template: src: docker-compose.yml.j2 diff --git a/roles/jitsi/templates/custom-interface-config.js.j2 b/roles/jitsi/templates/custom-interface-config.js.j2 new file mode 100644 index 0000000..38bedb4 --- /dev/null +++ b/roles/jitsi/templates/custom-interface-config.js.j2 @@ -0,0 +1,6 @@ +// Sovereign tenant branding overrides for Jitsi Meet. +// This file is appended to interface_config.js by the Jitsi web container. +interfaceConfig.APP_NAME = '{{ tenant_name }}'; +interfaceConfig.NATIVE_APP_NAME = '{{ tenant_name }}'; +interfaceConfig.PROVIDER_NAME = '{{ tenant_name }}'; +interfaceConfig.BRAND_WATERMARK_LINK = 'https://{{ base_domain }}'; diff --git a/roles/jitsi/templates/docker-compose.yml.j2 b/roles/jitsi/templates/docker-compose.yml.j2 index 420b48b..f95e895 100644 --- a/roles/jitsi/templates/docker-compose.yml.j2 +++ b/roles/jitsi/templates/docker-compose.yml.j2 @@ -5,6 +5,7 @@ services: restart: unless-stopped environment: PUBLIC_URL: "https://{{ jitsi_domain }}" + JITSI_WATERMARKLINK: "https://{{ base_domain }}" XMPP_SERVER: jitsi-prosody XMPP_DOMAIN: meet.jitsi XMPP_AUTH_DOMAIN: auth.meet.jitsi diff --git a/roles/matrix/defaults/main.yml b/roles/matrix/defaults/main.yml index bea827e..5c693dc 100644 --- a/roles/matrix/defaults/main.yml +++ b/roles/matrix/defaults/main.yml @@ -1,2 +1,4 @@ --- matrix_data_dir: "{{ sovereign_base_dir }}/matrix" +# Element theme: "light" or "dark" +element_theme: "light" diff --git a/roles/matrix/templates/element-config.json.j2 b/roles/matrix/templates/element-config.json.j2 index 4c57088..ea12ae8 100644 --- a/roles/matrix/templates/element-config.json.j2 +++ b/roles/matrix/templates/element-config.json.j2 @@ -5,7 +5,7 @@ "server_name": "{{ matrix_domain }}" } }, - "brand": "Element", + "brand": "{{ tenant_name }}", "integrations_ui_url": "https://scalar.vector.im/", "integrations_rest_url": "https://scalar.vector.im/api", "bug_report_endpoint_url": "", @@ -13,7 +13,7 @@ "show_labs_settings": false, "features": {}, "default_federate": true, - "default_theme": "light", + "default_theme": "{{ element_theme | default('light') }}", "room_directory": { "servers": ["{{ matrix_domain }}"] }, diff --git a/roles/nextcloud/tasks/main.yml b/roles/nextcloud/tasks/main.yml index 1966549..35143e6 100644 --- a/roles/nextcloud/tasks/main.yml +++ b/roles/nextcloud/tasks/main.yml @@ -19,3 +19,33 @@ community.docker.docker_compose_v2: project_src: "{{ nextcloud_data_dir }}" state: present + +- name: Wait for Nextcloud to be ready + ansible.builtin.command: + cmd: docker exec -u www-data nextcloud php occ status --output=json + register: nc_status + until: (nc_status.stdout | from_json).installed is defined and (nc_status.stdout | from_json).installed + retries: 30 + delay: 10 + changed_when: false + +- name: Copy tenant logo into Nextcloud container volume + ansible.builtin.copy: + src: "{{ tenant_logo_local_path }}" + dest: "{{ nextcloud_data_dir }}/data/sovereign-logo.png" + mode: '0644' + when: tenant_logo_local_path | default('') != '' + +- name: Configure Nextcloud theming — name and colour + ansible.builtin.command: + cmd: docker exec -u www-data nextcloud php occ theming:config {{ item.key }} "{{ item.value }}" + loop: + - { key: name, value: "{{ tenant_name }}" } + - { key: color, value: "{{ tenant_primary_color | default('#2563eb') }}" } + changed_when: false + +- name: Configure Nextcloud theming — logo + ansible.builtin.command: + cmd: docker exec -u www-data nextcloud php occ theming:config logo /var/www/html/sovereign-logo.png + when: tenant_logo_local_path | default('') != '' + changed_when: false diff --git a/roles/roundcube/defaults/main.yml b/roles/roundcube/defaults/main.yml index 7c6e483..4759820 100644 --- a/roles/roundcube/defaults/main.yml +++ b/roles/roundcube/defaults/main.yml @@ -1,2 +1,4 @@ --- roundcube_data_dir: "{{ sovereign_base_dir }}/roundcube" +# Roundcube UI skin. The official image ships "elastic" and "larry". +roundcube_skin: "elastic" diff --git a/roles/roundcube/tasks/main.yml b/roles/roundcube/tasks/main.yml index 158d8b0..f8c5086 100644 --- a/roles/roundcube/tasks/main.yml +++ b/roles/roundcube/tasks/main.yml @@ -6,6 +6,14 @@ mode: '0755' loop: - "{{ roundcube_data_dir }}" + - "{{ roundcube_data_dir }}/config" + +- name: Deploy Roundcube custom branding config + ansible.builtin.template: + src: custom.inc.php.j2 + dest: "{{ roundcube_data_dir }}/config/custom.inc.php" + mode: '0644' + notify: restart roundcube - name: Deploy Roundcube docker-compose ansible.builtin.template: diff --git a/roles/roundcube/templates/custom.inc.php.j2 b/roles/roundcube/templates/custom.inc.php.j2 new file mode 100644 index 0000000..2ae0ee5 --- /dev/null +++ b/roles/roundcube/templates/custom.inc.php.j2 @@ -0,0 +1,5 @@ +