feat(frontend): rename inj roles to sys-front-*, add sys-svc-cdn, cache-busting lookup

Introduce sys-svc-cdn (cdn_paths/cdn_urls/cdn_dirs) and ensure CDN directories + latest symlink.

Rename sys-srv-web-inj-* → sys-front-inj-*; update includes/templates; serve shared/per-app CSS & JS via CDN.

Add lookup_plugins/local_mtime_qs.py for mtime-based cache busting; split CSS into default.css/bootstrap.css + optional per-app style.css.

CSP: use style-src-elem; drop unsafe-inline for styles. Services: fix SYS_SERVICE_ALL_ENABLED bool and controlled flush.

BREAKING CHANGE: role names changed; replace includes and references accordingly.

Conversation: https://chatgpt.com/share/68b55494-9ec4-800f-b559-44707029141d
This commit is contained in:
2025-09-01 10:10:23 +02:00
parent 3f8e7c1733
commit 231fd567b3
123 changed files with 1789 additions and 1393 deletions

View File

@@ -228,7 +228,7 @@ def parse_meta_dependencies(role_dir: str) -> List[str]:
def sanitize_run_once_var(role_name: str) -> str:
"""
Generate run_once variable name from role name.
Example: 'sys-srv-web-inj-logout' -> 'run_once_sys_srv_web_inj_logout'
Example: 'sys-front-inj-logout' -> 'run_once_sys_front_inj_logout'
"""
return "run_once_" + role_name.replace("-", "_")

View File

@@ -0,0 +1,53 @@
from __future__ import annotations
from ansible.plugins.lookup import LookupBase
from ansible.errors import AnsibleError
import os
class LookupModule(LookupBase):
"""
Return a cache-busting string based on the LOCAL file's mtime.
Usage (single path → string via Jinja):
{{ lookup('local_mtime_qs', '/path/to/file.css') }}
-> "?version=1712323456"
Options:
param (str): query parameter name (default: "version")
mode (str): "qs" (default) → returns "?<param>=<mtime>"
"epoch" → returns "<mtime>"
Multiple paths (returns list, one result per term):
{{ lookup('local_mtime_qs', '/a.js', '/b.js', param='v') }}
"""
def run(self, terms, variables=None, **kwargs):
if not terms:
return []
param = kwargs.get('param', 'version')
mode = kwargs.get('mode', 'qs')
if mode not in ('qs', 'epoch'):
raise AnsibleError("local_mtime_qs: 'mode' must be 'qs' or 'epoch'")
results = []
for term in terms:
path = os.path.abspath(os.path.expanduser(str(term)))
# Fail fast if path is missing or not a regular file
if not os.path.exists(path):
raise AnsibleError(f"local_mtime_qs: file does not exist: {path}")
if not os.path.isfile(path):
raise AnsibleError(f"local_mtime_qs: not a regular file: {path}")
try:
mtime = int(os.stat(path).st_mtime)
except OSError as e:
raise AnsibleError(f"local_mtime_qs: cannot stat '{path}': {e}")
if mode == 'qs':
results.append(f"?{param}={mtime}")
else: # mode == 'epoch'
results.append(str(mtime))
return results

View File

@@ -56,6 +56,16 @@ roles:
description: "Stack levels to setup the server"
icon: "fas fa-bars-staggered"
invokable: false
front:
title: "System Frontend Helpers"
description: "Frontend helpers for reverse-proxied apps (injection, shared assets, CDN plumbing)."
icon: "fas fa-wand-magic-sparkles"
invokable: false
inj:
title: "Injection"
description: "Composable HTML injection roles (CSS, JS, logout interceptor, analytics, desktop iframe) for Nginx/OpenResty via sub_filter/Lua with CDN-backed assets."
icon: "fas fa-filter"
invokable: false
update:
title: "Updates & Package Management"
description: "OS & package updates"
@@ -106,11 +116,6 @@ roles:
description: "General server roles for provisioning and managing server infrastructure—covering web servers, proxy servers, network services, and other backend components."
icon: "fas fa-server"
invokable: false
web:
title: "Webserver"
description: "Web-server roles for installing and configuring Nginx (core, TLS, injection filters, composer modules)."
icon: "fas fa-server"
invokable: false
proxy:
title: "Proxy Server"
description: "Proxy-server roles for virtual-host orchestration and reverse-proxy setups."

View File

@@ -1,8 +1,14 @@
---
- name: Setup locale.gen
template: src=locale.gen dest=/etc/locale.gen
template:
src: locale.gen.j2
dest: /etc/locale.gen
- name: Setup locale.conf
template: src=locale.conf dest=/etc/locale.conf
template:
src: locale.conf.j2
dest: /etc/locale.conf
- name: Generate locales
shell: locale-gen
become: true

View File

@@ -1,2 +0,0 @@
LANG=en_US.UTF-8
LANGUAGE=en_US.UTF-8

View File

@@ -0,0 +1,2 @@
LANG={{ HOST_LL_CC }}.UTF-8
LANGUAGE={{ HOST_LL_CC }}.UTF-8

View File

@@ -2,7 +2,7 @@
This Ansible role composes and orchestrates all necessary HTTPS-layer tasks and HTML-content injections for your webserver domains. It integrates two key sub-roles into a unified workflow:
1. **`sys-srv-web-inj-compose`**
1. **`sys-front-inj-all`**
Injects global HTML snippets (CSS, Matomo tracking, iFrame notifier, custom JavaScript) into responses using Nginx `sub_filter`.
2. **`sys-svc-certs`**
Handles issuing, renewing, and managing TLS certificates via ACME/Certbot.

View File

@@ -1,8 +1,8 @@
# run_once_srv_composer: deactivated
- name: "include role sys-srv-web-inj-compose for '{{ domain }}'"
- name: "include role sys-front-inj-all for '{{ domain }}'"
include_role:
name: sys-srv-web-inj-compose
name: sys-front-inj-all
- name: "include role sys-svc-certs for '{{ domain }}'"
include_role:

View File

@@ -35,6 +35,6 @@ location {{location}}
{% if proxy_lua_enabled %}
proxy_set_header Accept-Encoding "";
{% include 'roles/sys-srv-web-inj-compose/templates/location.lua.j2'%}
{% include 'roles/sys-front-inj-all/templates/location.lua.j2'%}
{% endif %}
}

View File

@@ -7,7 +7,7 @@ server
{% include 'roles/web-app-oauth2-proxy/templates/endpoint.conf.j2'%}
{% endif %}
{% include 'roles/sys-srv-web-inj-compose/templates/server.conf.j2'%}
{% include 'roles/sys-front-inj-all/templates/server.conf.j2'%}
{% if proxy_extra_configuration is defined %}
{# Additional Domain Specific Configuration #}

View File

@@ -8,7 +8,7 @@ server {
{% include 'roles/srv-letsencrypt/templates/ssl_header.j2' %}
{% include 'roles/sys-srv-web-inj-compose/templates/server.conf.j2' %}
{% include 'roles/sys-front-inj-all/templates/server.conf.j2' %}
client_max_body_size {{ client_max_body_size | default('100m') }};
keepalive_timeout 70;

View File

@@ -1,4 +1,3 @@
# roles/sys-srv-web-inj-compose/filter_plugins/inj_enabled.py
#
# Usage in tasks:
# - set_fact:

View File

@@ -2,10 +2,10 @@
Jinja filter: `inj_features(kind)` filters a list of features to only those
that actually provide the corresponding snippet template file.
- kind='head' -> roles/sys-srv-web-inj-<feature>/templates/head_sub.j2
- kind='body' -> roles/sys-srv-web-inj-<feature>/templates/body_sub.j2
- kind='head' -> roles/sys-front-inj-<feature>/templates/head_sub.j2
- kind='body' -> roles/sys-front-inj-<feature>/templates/body_sub.j2
If the feature's role directory (roles/sys-srv-web-inj-<feature>) does not
If the feature's role directory (roles/sys-front-inj-<feature>) does not
exist, this filter raises FileNotFoundError.
Usage in a template:
@@ -15,13 +15,13 @@ Usage in a template:
import os
# This file lives at: roles/sys-srv-web-inj-compose/filter_plugins/inj_snippets.py
# This file lives at: roles/sys-front-inj-all/filter_plugins/inj_snippets.py
_THIS_DIR = os.path.dirname(__file__)
_ROLE_DIR = os.path.abspath(os.path.join(_THIS_DIR, "..")) # roles/sys-srv-web-inj-compose
_ROLE_DIR = os.path.abspath(os.path.join(_THIS_DIR, "..")) # roles/sys-front-inj-all
_ROLES_DIR = os.path.abspath(os.path.join(_ROLE_DIR, "..")) # roles
def _feature_role_dir(feature: str) -> str:
return os.path.join(_ROLES_DIR, f"sys-srv-web-inj-{feature}")
return os.path.join(_ROLES_DIR, f"sys-front-inj-{feature}")
def _has_snippet(feature: str, kind: str) -> bool:
if kind not in ("head", "body"):

View File

@@ -14,7 +14,7 @@ galaxy_info:
- theming
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://s.infinito.nexus/code/tree/main/roles/sys-srv-web-inj-compose"
documentation: "https://s.infinito.nexus/code/tree/main/roles/sys-front-inj-all"
min_ansible_version: "2.9"
platforms:
- name: Any

View File

@@ -2,39 +2,10 @@
set_fact:
inj_enabled: "{{ applications | inj_enabled(application_id, SRV_WEB_INJ_COMP_FEATURES_ALL) }}"
- block:
- name: Include dependency 'srv-core'
include_role:
name: srv-core
when: run_once_srv_core is not defined
- include_tasks: utils/run_once.yml
when: run_once_sys_srv_web_inj_compose is not defined
- name: "Activate Portfolio iFrame notifier for '{{ domain }}'"
- name: "Load CDN Service for '{{ domain }}'"
include_role:
name: sys-srv-web-inj-desktop
public: true # Vars used in templates
when: inj_enabled.desktop
- name: "Load CDN for '{{ domain }}'"
include_role:
name: web-svc-cdn
public: false
when:
- inj_enabled.logout
- inj_enabled.desktop
- application_id != 'web-svc-cdn'
- run_once_web_svc_cdn is not defined
- name: Overwritte CDN handlers with neutral handlers
ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/utils/load_handlers.yml"
loop:
- svc-prx-openresty
- docker-compose
loop_control:
label: "{{ item }}"
vars:
handler_role_name: "{{ item }}"
name: sys-svc-cdn
public: true # Expose variables so that they can be used in all injection roles
- name: Reinitialize 'inj_enabled' for '{{ domain }}', after modification by CDN
set_fact:
@@ -42,25 +13,37 @@
inj_head_features: "{{ SRV_WEB_INJ_COMP_FEATURES_ALL | inj_features('head') }}"
inj_body_features: "{{ SRV_WEB_INJ_COMP_FEATURES_ALL | inj_features('body') }}"
- name: "Activate Desktop iFrame notifier for '{{ domain }}'"
include_role:
name: sys-front-inj-desktop
public: true # Vars used in templates
when: inj_enabled.desktop
- name: "Activate Corporate CSS for '{{ domain }}'"
include_role:
name: sys-srv-web-inj-css
when:
- inj_enabled.css
- run_once_sys_srv_web_inj_css is not defined
name: sys-front-inj-css
when: inj_enabled.css
- name: "Activate Matomo Tracking for '{{ domain }}'"
include_role:
name: sys-srv-web-inj-matomo
name: sys-front-inj-matomo
when: inj_enabled.matomo
- name: "Activate Javascript for '{{ domain }}'"
include_role:
name: sys-srv-web-inj-javascript
name: sys-front-inj-javascript
when: inj_enabled.javascript
- name: "Activate logout proxy for '{{ domain }}'"
include_role:
name: sys-srv-web-inj-logout
name: sys-front-inj-logout
public: true # Vars used in templates
when: inj_enabled.logout
- block:
- name: Include dependency 'srv-core'
include_role:
name: srv-core
when: run_once_srv_core is not defined
- include_tasks: utils/run_once.yml
when: run_once_sys_front_inj_all is not defined

View File

@@ -3,7 +3,7 @@
{% set kind = list_name | regex_replace('_snippets$','') %}
{% for f in features if inj_enabled.get(f) -%}
{{ list_name }}[#{{ list_name }} + 1] = [=[
{%- include 'roles/sys-srv-web-inj-' ~ f ~ '/templates/' ~ kind ~ '_sub.j2' -%}
{%- include 'roles/sys-front-inj-' ~ f ~ '/templates/' ~ kind ~ '_sub.j2' -%}
]=]
{% endfor -%}
{%- endmacro %}

View File

@@ -1,7 +1,3 @@
{% if inj_enabled.css %}
{% include 'roles/sys-srv-web-inj-css/templates/location.conf.j2' %}
{% endif %}
{% if inj_enabled.logout %}
{% include 'roles/web-svc-logout/templates/logout-proxy.conf.j2' %}
{% endif %}

View File

@@ -2,12 +2,12 @@
## Description
This Ansible role ensures **consistent global theming** across all Nginx-served applications by injecting a unified `global.css` file.
This Ansible role ensures **consistent global theming** across all Nginx-served applications by injecting CSS files.
The role leverages [`colorscheme-generator`](https://github.com/kevinveenbirkenbach/colorscheme-generator/) to generate a dynamic, customizable color palette for light and dark mode, compatible with popular web tools like **Bootstrap**, **Keycloak**, **Nextcloud**, **Taiga**, **Mastodon**, and many more.
## Overview
This role deploys a centralized global stylesheet (`global.css`) that overrides the default theming of web applications served via Nginx. It's optimized to run only once per deployment and generates a **cache-busting version number** based on file modification timestamps.
This role deploys a centralized global stylesheet that overrides the default theming of web applications served via Nginx. It's optimized to run only once per deployment and generates a **cache-busting version number** based on file modification timestamps.
It includes support for **dark mode**, **custom fonts**, and **extensive Bootstrap and UI component overrides**.
## Purpose
@@ -18,7 +18,7 @@ It makes all applications feel like part of the same ecosystem — visually and
## Features
- 🎨 **Dynamic Theming** via [`colorscheme-generator`](https://github.com/kevinveenbirkenbach/colorscheme-generator/)
- 📁 **Unified global.css** deployment for all Nginx applications
- 📁 **Unified CSS Base Configuration** deployment for all Nginx applications
- 🌒 **Dark mode support** out of the box
- 🚫 **No duplication** tasks run once per deployment
- ⏱️ **Versioning logic** to bust browser cache

View File

@@ -0,0 +1,21 @@
- name: Include dependency 'srv-core'
include_role:
name: srv-core
when: run_once_srv_core is not defined
- name: Generate color palette with colorscheme-generator
set_fact:
color_palette: "{{ lookup('colorscheme', CSS_BASE_COLOR, count=CSS_COUNT, shades=CSS_SHADES) }}"
- name: Generate inverted color palette with colorscheme-generator
set_fact:
inverted_color_palette: "{{ lookup('colorscheme', CSS_BASE_COLOR, count=CSS_COUNT, shades=CSS_SHADES, invert_lightness=True) }}"
- name: Deploy default CSS files
template:
src: "{{ ['css', item ~ '.j2'] | path_join }}"
dest: "{{ [cdn.shared.css, item] | path_join }}"
owner: "{{ NGINX.USER }}"
group: "{{ NGINX.USER }}"
mode: '0644'
loop: "{{ CSS_FILES }}"

View File

@@ -0,0 +1,25 @@
- block:
- include_tasks: 01_core.yml
- include_tasks: utils/run_once.yml
when: run_once_sys_front_inj_css is not defined
- name: "Resolve optional app style.css source for '{{ application_id }}'"
vars:
app_role_dir: "{{ playbook_dir }}/roles/{{ application_id }}"
_app_style_src: >-
{{ lookup('first_found', {
'files': ['templates/style.css.j2','files/style.css'],
'paths': [app_role_dir]
}, errors='ignore') | default('', true) }}
set_fact:
app_style_src: "{{ _app_style_src }}"
app_style_present: "{{ _app_style_src | length > 0 }}"
- name: "Deploy per-app '{{ app_style_src }}' to '{{ css_app_dst }}'"
when: app_style_present
copy:
content: "{{ lookup('template', app_style_src) }}"
dest: "{{ css_app_dst }}"
owner: "{{ NGINX.USER }}"
group: "{{ NGINX.USER }}"
mode: '0644'

View File

@@ -0,0 +1,69 @@
/* Buttons (Background, Text, Border, and Shadow)
Now using a button background that is only slightly darker than the overall background */
html[native-dark-active] .btn, .btn {
background-color: var(--color-01-87);
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-70), var(--color-01-91), var(--color-01-95), var(--color-01-95));
color: var(--color-01-50);
border-color: var(--color-01-80);
cursor: pointer;
}
/* Navigation (Background and Text Colors) */
.navbar, .navbar-light, .navbar-dark, .navbar.bg-light {
background-color: var(--color-01-90);
/* New Gradient based on original background (90 -5, 90, 90 +1, 90 +5) */
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-85), var(--color-01-90), var(--color-01-91), var(--color-01-95));
color: var(--color-01-50);
border-color: var(--color-01-85);
}
.navbar a {
color: var(--color-01-40);
}
.navbar a.dropdown-item {
color: var(--color-01-43);
}
/* Cards / Containers (Background, Border, and Shadow)
Cards now use a slightly lighter background and a bold, clear shadow */
.card {
background-color: var(--color-01-90);
/* New Gradient based on original background (90 -5, 90, 90 +1, 90 +5) */
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-85), var(--color-01-90), var(--color-01-91), var(--color-01-95));
border-color: var(--color-01-85);
color: var(--color-01-12);
}
.card-body {
color: var(--color-01-40);
}
/* Dropdown Menu and Submenu (Background, Text, and Shadow) */
.navbar .dropdown-menu,
.nav-item .dropdown-menu {
background-color: var(--color-01-80);
/* New Gradient based on original background (80 -5, 80, 80 +1, 80 +5) */
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-75), var(--color-01-80), var(--color-01-81), var(--color-01-85));
color: var(--color-01-40);
}
.navbar-nav {
--bs-nav-link-hover-color: var(--color-01-17);
}
.dropdown-item {
color: var(--color-01-40);
background-color: var(--color-01-80);
/* New Gradient based on original background (80 -5, 80, 80 +1, 80 +5) */
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-75), var(--color-01-80), var(--color-01-81), var(--color-01-85));
}
.dropdown-item:hover,
.dropdown-item:focus {
background-color: var(--color-01-65);
/* New Gradient based on original background (65 -5, 65, 65 +1, 65 +5) */
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-60), var(--color-01-65), var(--color-01-66), var(--color-01-70));
color: var(--color-01-40);
}

View File

@@ -0,0 +1,297 @@
/***
Global Theming Styles Color and Shadow Variables
HINT:
- Better overwritte CSS variables instead of individual elements.
- Don't use !important. If possible use a specific selector.
*/
{% if design.font.import_url %}
@import url('{{ design.font.import_url }}');
{% endif %}
/* Auto-generated by colorscheme-generator */
:root {
{% for var_name, color in color_palette.items() %}
{{ var_name }}: {{ color }};
{% endfor %}
}
@media (prefers-color-scheme: dark) {
:root {
{% for var_name, color in inverted_color_palette.items() %}
{{ var_name }}: {{ color }};
{% endfor %}
}
}
:root, ::after, ::before, ::backdrop {
/* For Dark Mode Plugin
* @See https://chromewebstore.google.com/detail/dark-mode/dmghijelimhndkbmpgbldicpogfkceaj
*/
--native-dark-accent-color: var(--color-01-60); /* was #a9a9a9 */
--native-dark-bg-color: var(--color-01-10); /* was #292929 */
--native-dark-bg-image-color: rgba(var(--color-01-rgb-01), 0.10); /* remains the same, or adjust if needed */
--native-dark-border-color: var(--color-01-40); /* was #555555 */
--native-dark-box-shadow: 0 0 0 1px rgb(var(--color-01-rgb-99), / 10%);
--native-dark-cite-color: var(--color-01-70); /* was #92de92 you might adjust if a green tone is needed */
--native-dark-fill-color: var(--color-01-50); /* was #7d7d7d */
--native-dark-font-color: var(--color-01-95); /* was #dcdcdc */
--native-dark-link-color: var(--color-01-80); /* was #8db2e5 */
--native-dark-visited-link-color: var(--color-01-85); /* was #c76ed7 */
}
/* Bootstrap Overrides (Color/Shadow Variables Only) */
:root {
--bs-black: var(--color-01-01); /* Original tone: Black (#000) */
--bs-white: var(--color-01-99); /* Original tone: White (#fff) */
--bs-gray: var(--color-01-50); /* Original tone: Gray (#6c757d) */
--bs-gray-dark: var(--color-01-20); /* Original tone: Dark Gray (#343a40) */
{% for i in range(1, 10) %}
{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #}
{% set gray = i * 100 %}
{% set color = 100 - i * 10 %}
--bs-gray-{{ gray }}: var(--color-01-{{ "%02d" % color }});
{% endfor %}
--bs-primary: var(--color-01-65); /* Original tone: Blue (#0d6efd) */
--bs-light: var(--color-01-95); /* Original tone: Light (#f8f9fa) */
--bs-dark: var(--color-01-10); /* Original tone: Dark (#212529) */
--bs-primary-rgb: var(--color-01-rgb-65); /* Original tone: Blue (13, 110, 253) */
--bs-secondary-rgb: var(--color-01-rgb-50); /* Original tone: Grayish (#6c757d / 108, 117, 125) */
--bs-light-rgb: var(--color-01-rgb-95); /* Original tone: Light (248, 249, 250) */
--bs-dark-rgb: var(--color-01-rgb-10); /* Original tone: Dark (33, 37, 41) */
--bs-white-rgb: var(--color-01-rgb-99); /* Original tone: White (255, 255, 255) */
--bs-black-rgb: var(--color-01-rgb-01); /* Original tone: Black (0, 0, 0) */
--bs-body-color-rgb: var(--color-01-rgb-10); /* Original tone: Dark (#212529 / 33, 37, 41) */
--bs-body-bg-rgb: var(--color-01-rgb-99); /* Original tone: White (#fff / 255, 255, 255) */
--bs-body-color: var(--color-01-10); /* Original tone: Dark (#212529) */
--bs-body-bg: var(--color-01-99); /* Original tone: White (#fff) */
--bs-border-color: var(--color-01-85); /* Original tone: Gray (#dee2e6) */
--bs-link-color: var(--color-01-65); /* Original tone: Blue (#0d6efd) */
--bs-link-hover-color: var(--color-01-60); /* Original tone: Darker Blue (#0a58ca) */
--bs-code-color: var(--color-01-55); /* Original tone: Pink (#d63384) */
--bs-highlight-bg: var(--color-01-93); /* Original tone: Light Yellow (#fff3cd) */
--bs-list-group-bg: var(--color-01-40);
--bs-emphasis-color: var(--color-01-01); /* Gemappt von #000 */
--bs-emphasis-color-rgb: var(--color-01-rgb-01); /* Gemappt von 0, 0, 0 */
--bs-secondary-color: rgba(var(--color-01-rgb-10), 0.75); /* Gemappt von rgba(33, 37, 41, 0.75) */
--bs-secondary-color-rgb: var(--color-01-rgb-10); /* Gemappt von 33, 37, 41 */
--bs-secondary-bg: var(--color-01-90); /* Gemappt von #e9ecef */
--bs-secondary-bg-rgb: var(--color-01-rgb-90); /* Gemappt von 233, 236, 239 */
--bs-tertiary-color: rgba(var(--color-01-rgb-10), 0.5); /* Gemappt von rgba(33, 37, 41, 0.5) */
--bs-tertiary-color-rgb: var(--color-01-rgb-10); /* Gemappt von 33, 37, 41 */
--bs-tertiary-bg: var(--color-01-95); /* Gemappt von #f8f9fa */
--bs-tertiary-bg-rgb: var(--color-01-rgb-95); /* Gemappt von 248, 249, 250 */
--bs-link-color-rgb: var(--color-01-rgb-65); /* Gemappt von 13, 110, 253 */
--bs-link-hover-color-rgb: var(--color-01-rgb-60); /* Gemappt von 10, 88, 202 */
--bs-highlight-color: var(--color-01-10); /* Gemappt von #212529 */
--bs-border-color-translucent: rgba(var(--color-01-rgb-01), 0.175); /* Gemappt von rgba(0, 0, 0, 0.175) */
--bs-focus-ring-color: rgba(var(--color-01-rgb-65), 0.25); /* Gemappt von rgba(13, 110, 253, 0.25) */
--bs-table-color: var(--bs-emphasis-color);
--bs-table-bg: var(--color-01-99); /* White (#fff) */
--bs-table-border-color: var(--color-01-99); /* White (#fff) */
--bs-table-striped-bg: var(--color-01-85); /* Light Gray (entspricht ca. #dee2e6) */
--bs-table-hover-color: var(--color-01-01); /* Black (#000) */
--bs-table-hover-bg: rgba(var(--bs-emphasis-color-rgb), 0.075);
}
/* Global Defaults (Colors Only) */
body, html[native-dark-active] {
background-color: var(--color-01-93);
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-93), var(--color-01-91), var(--color-01-95), var(--color-01-93));
background-attachment: fixed;
color: var(--color-01-40);
font-family: {{design.font.type}};
}
{# All links (applies to all anchor elements regardless of state) #}
a {
color: var(--color-01-50);
}
{# Unvisited links (applies only to links that have not been visited) #}
a:link {
color: var(--color-01-55);
}
{# Visited links (applies only to links that have been visited) #}
a:visited {
color: var(--color-01-45);
}
{# Hover state (applies when the mouse pointer is over the link) #}
a:hover {
color: var(--color-01-60);
}
{# Active state (applies during the time the link is being activated, e.g., on click) #}
a:active {
color: var(--color-01-65);
}
/** Set default buttons transparent **/
html[native-dark-active] button, button{
background-color: var(--color-01-87);
}
button:hover, .btn:hover {
filter: brightness(0.9);
}
/* {# Invalid state: when the input value fails validation criteria. Use danger color for error indication. #} */
input:invalid,
textarea:invalid,
select:invalid {
background-color: var(--color-01-01);
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-01), var(--color-01-10));
/* Use Bootstrap danger color for error messages */
color: var(--bs-danger);
border-color: var(--color-01-20);
}
/* {# Valid state: when the input value meets all validation criteria. Use success color for confirmation. #} */
input:valid,
textarea:valid,
select:valid {
background-color: var(--color-01-80);
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-80), var(--color-01-90));
/* Use Bootstrap success color for confirmation messages */
color: var(--bs-success);
border-color: var(--color-01-70);
}
/* {# Required field: applied to elements that must be filled out by the user. Use warning color for emphasis. #} */
input:required,
textarea:required,
select:required {
background-color: var(--color-01-50);
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-50), var(--color-01-60));
/* Use Bootstrap warning color to indicate a required field */
color: var(--bs-warning);
border-color: var(--color-01-70);
}
/* {# Optional field: applied to elements that are not mandatory. Use info color to denote additional information. #} */
input:optional,
textarea:optional,
select:optional {
background-color: var(--color-01-60);
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-60), var(--color-01-70));
/* Use Bootstrap info color to indicate optional information */
color: var(--bs-info);
border-color: var(--color-01-70);
}
/* {# Read-only state: when an element is not editable by the user. #} */
input:read-only,
textarea:read-only,
select:read-only {
background-color: var(--color-01-80);
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-90), var(--color-01-70));
color: var(--color-01-20);
border-color: var(--color-01-50);
}
/* {# Read-write state: when an element is editable by the user. #} */
input:read-write,
textarea:read-write,
select:read-write {
background-color: var(--color-01-70);
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-70), var(--color-01-80));
color: var(--color-01-40);
border-color: var(--color-01-70);
}
/* {# In-range: for inputs with a defined range, when the value is within the allowed limits. #} */
input:in-range,
textarea:in-range,
select:in-range {
background-color: var(--color-01-70);
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-70), var(--color-01-85));
color: var(--color-01-40);
border-color: var(--color-01-70);
}
/* {# Out-of-range: for inputs with a defined range, when the value falls outside the allowed limits. #} */
input:out-of-range,
textarea:out-of-range,
select:out-of-range {
background-color: var(--color-01-10);
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-10), var(--color-01-30));
color: var(--color-01-10);
border-color: var(--color-01-50);
}
/* {# Placeholder-shown: when the input field is displaying its placeholder text. #} */
input:placeholder-shown,
textarea:placeholder-shown,
select:placeholder-shown {
background-color: var(--color-01-82);
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-82), var(--color-01-90));
color: var(--color-01-40);
border-color: var(--color-01-70);
}
/* {# Focus state: when the element is focused by the user. #} */
input:focus,
textarea:focus,
select:focus {
background-color: var(--color-01-75);
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-75), var(--color-01-85));
color: var(--color-01-40);
border-color: var(--color-01-50);
}
/* {# Hover state: when the mouse pointer is over the element. #} */
input:hover,
textarea:hover,
select:hover {
background-color: var(--color-01-78);
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-78), var(--color-01-88));
color: var(--color-01-40);
border-color: var(--color-01-65);
}
/* {# Active state: when the element is being activated (e.g., clicked). #} */
input:active,
textarea:active,
select:active {
background-color: var(--color-01-68);
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-68), var(--color-01-78));
color: var(--color-01-40);
border-color: var(--color-01-60);
}
/* {# Checked state: specifically for radio buttons and checkboxes when selected. #} */
input:checked {
background-color: var(--color-01-90);
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-90), var(--color-01-99));
color: var(--color-01-40);
border-color: var(--color-01-70);
}
option {
background-color: var(--color-01-82);
color: var(--color-01-07);
}
/* Tables (Borders and Header Colors) */
th, td {
border-color: var(--color-01-70);
}
thead {
background-color: var(--color-01-80);
/* New Gradient based on original background (80 -5, 80, 80 +1, 80 +5) */
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-75), var(--color-01-80), var(--color-01-81), var(--color-01-85));
color: var(--color-01-40);
}
/* Headings (Text Color) */
h1, h2, h3, h4, h5, h6, p{
color: var(--color-01-10);
}

View File

@@ -0,0 +1,8 @@
{% set __css_tpl_dir = [playbook_dir, 'roles', 'sys-front-inj-css', 'templates', 'css'] | path_join %}
{% for css_file in ['default.css','bootstrap.css'] %}
<link rel="stylesheet" href="{{ [ cdn_urls.shared.css, css_file, lookup('local_mtime_qs', [__css_tpl_dir, css_file ~ '.j2'] | path_join)] | url_join }}">
{% endfor %}
{% if app_style_present | bool %}
<link rel="stylesheet" href="{{ [ cdn_urls.role.release.css, 'style.css', lookup('local_mtime_qs', app_style_src)] | url_join }}">
{% endif %}

View File

@@ -0,0 +1,8 @@
# Constants
CSS_FILES: ['default.css','bootstrap.css']
CSS_BASE_COLOR: "{{ design.css.colors.base }}"
CSS_COUNT: 7
CSS_SHADES: 100
# Variables
css_app_dst: "{{ [cdn.role.release.css, 'style.css'] | path_join }}"

View File

@@ -0,0 +1,7 @@
- name: Deploy {{ INJ_DESKTOP_JS_FILE_NAME }}
template:
src: "{{ INJ_DESKTOP_JS_FILE_NAME }}.j2"
dest: "{{ INJ_DESKTOP_JS_FILE_DESTINATION }}"
owner: "{{ NGINX.USER }}"
group: "{{ NGINX.USER }}"
mode: '0644'

View File

@@ -5,7 +5,7 @@
when: run_once_srv_core is not defined
- include_tasks: 01_deploy.yml
- include_tasks: utils/run_once.yml
when: run_once_sys_srv_web_inj_desktop is not defined
when: run_once_sys_front_inj_desktop is not defined
# --- Build tiny inline initializer (CSP-hashed) ---
- name: "Load iFrame init code for '{{ application_id }}'"

View File

@@ -0,0 +1 @@
<script src="{{ cdn_urls.shared.js }}/{{ INJ_DESKTOP_JS_FILE_NAME }}{{ lookup('local_mtime_qs', [playbook_dir, 'roles', 'sys-front-inj-desktop', 'templates', INJ_DESKTOP_JS_FILE_NAME ~ '.j2'] | path_join) }}"></script>

View File

@@ -0,0 +1,2 @@
INJ_DESKTOP_JS_FILE_NAME: "iframe-handler.js"
INJ_DESKTOP_JS_FILE_DESTINATION: "{{ [cdn.shared.js, INJ_DESKTOP_JS_FILE_NAME] | path_join }}"

View File

@@ -5,7 +5,7 @@
name: srv-core
when: run_once_srv_core is not defined
- include_tasks: utils/run_once.yml
when: run_once_sys_srv_web_inj_javascript is not defined
when: run_once_sys_front_inj_javascript is not defined
- name: "Load JavaScript code for '{{ application_id }}'"
set_fact:

View File

@@ -1,10 +1,10 @@
# sys-srv-web-inj-logout
# sys-front-inj-logout
This role injects a catcher that intercepts all logout elements in HTML pages served by Nginx and redirects them to a centralized logout endpoint via JavaScript.
## Description
The `sys-srv-web-inj-logout` Ansible role automatically embeds a lightweight JavaScript snippet into your web application's HTML responses. This script identifies logout links, buttons, forms, and other elements, overrides their target URLs, and ensures users are redirected to a central OIDC logout endpoint, providing a consistent single signout experience.
The `sys-front-inj-logout` Ansible role automatically embeds a lightweight JavaScript snippet into your web application's HTML responses. This script identifies logout links, buttons, forms, and other elements, overrides their target URLs, and ensures users are redirected to a central OIDC logout endpoint, providing a consistent single signout experience.
## Overview

View File

@@ -1,6 +1,6 @@
galaxy_info:
author: "Kevin VeenBirkenbach"
role_name: "sys-srv-web-inj-logout"
role_name: "sys-front-inj-logout"
description: >
Injects a JavaScript snippet via Nginx sub_filter that intercepts all logout actions
(links, buttons, forms) and redirects users to a centralized OIDC logout endpoint.
@@ -21,4 +21,4 @@ galaxy_info:
Kevin VeenBirkenbach Consulting & Coaching Solutions https://www.veen.world
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://s.infinito.nexus/code/tree/main/roles/sys-srv-web-inj-logout"
documentation: "https://s.infinito.nexus/code/tree/main/roles/sys-front-inj-logout"

View File

@@ -1,8 +1,8 @@
- block:
- include_tasks: 01_core.yml
- set_fact:
run_once_sys_srv_web_inj_logout: true
when: run_once_sys_srv_web_inj_logout is not defined
run_once_sys_front_inj_logout: true
when: run_once_sys_front_inj_logout is not defined
- name: "Load logout code for '{{ application_id }}'"
set_fact:

View File

@@ -0,0 +1 @@
<script src="{{ cdn_urls.shared.js }}/{{ INJ_LOGOUT_JS_FILE_NAME }}{{ lookup('local_mtime_qs', [playbook_dir, 'roles', 'sys-front-inj-logout', 'templates', INJ_LOGOUT_JS_FILE_NAME ~ '.j2'] | path_join) }}"></script>

View File

@@ -0,0 +1,2 @@
INJ_LOGOUT_JS_FILE_NAME: "logout.js"
INJ_LOGOUT_JS_DESTINATION: "{{ [cdn.shared.js, INJ_LOGOUT_JS_FILE_NAME] | path_join }}"

View File

@@ -13,7 +13,7 @@ galaxy_info:
- analytics
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://s.infinito.nexus/code/tree/main/roles/sys-srv-web-inj-matomo"
documentation: "https://s.infinito.nexus/code/tree/main/roles/sys-front-inj-matomo"
min_ansible_version: "2.9"
platforms:
- name: Any

View File

@@ -4,7 +4,7 @@
name: srv-core
when: run_once_srv_core is not defined
- include_tasks: utils/run_once.yml
when: run_once_sys_srv_web_inj_matomo is not defined
when: run_once_sys_front_inj_matomo is not defined
- name: "Relevant variables for role: {{ role_path | basename }}"
debug:

View File

@@ -1,4 +1,4 @@
SYS_SERVICE_ALL_ENABLED: "{{ MODE_DEBUG }}"
SYS_SERVICE_ALL_ENABLED: "{{ MODE_DEBUG | bool }}"
SYS_SERVICE_DEFAULT_STATE: "{{ 'restarted' if MODE_DEBUG else omit }}"
SYS_SERVICE_DEFAULT_RUNTIME: "86400s" # Maximum total runtime a service is allowed to run before being stopped
SYS_SERVICE_SUPPRESS_FLUSH: [] # Services where the flush should be suppressed

View File

@@ -46,5 +46,5 @@
command: /bin/true
notify: refresh systemctl service
when: not system_service_uses_at
when: (SYS_SERVICE_ALL_ENABLED | bool or system_force_flush | bool)
when: system_force_flush | bool

View File

@@ -6,7 +6,7 @@ system_service_role_dir: "{{ [ playbook_dir, 'roles', system_service_role_
system_service_script_dir: "{{ [ PATH_SYSTEMCTL_SCRIPTS, system_service_id ] | path_join }}"
## Settings
system_force_flush: false # When set to true it activates the flushing of services :)
system_force_flush: "{{ SYS_SERVICE_ALL_ENABLED | bool }}" # When set to true it activates the flushing of services. defaults to SYS_SERVICE_ALL_ENABLED
system_service_suppress_flush: "{{ (system_service_id in SYS_SERVICE_SUPPRESS_FLUSH) | bool }}" # When set to true it suppresses the flushing of services
system_service_copy_files: true # When set to false file copying will be skipped
system_service_timer_enabled: false # When set to true timer will be loaded

View File

@@ -1,29 +0,0 @@
- name: Include dependency 'srv-core'
include_role:
name: srv-core
when: run_once_srv_core is not defined
- name: Generate color palette with colorscheme-generator
set_fact:
color_palette: "{{ lookup('colorscheme', global_css_base_color, count=global_css_count, shades=global_css_shades) }}"
- name: Generate inverted color palette with colorscheme-generator
set_fact:
inverted_color_palette: "{{ lookup('colorscheme', global_css_base_color, count=global_css_count, shades=global_css_shades, invert_lightness=True) }}"
- name: Deploy global.css
template:
src: global.css.j2
dest: "{{ global_css_destination }}"
owner: "{{ NGINX.USER }}"
group: "{{ NGINX.USER }}"
mode: '0644'
- name: Get stat for global.css
stat:
path: "{{ global_css_destination }}"
register: global_css_stat
- name: Set global_css_version
set_fact:
global_css_version: "{{ global_css_stat.stat.mtime }}"

View File

@@ -1,4 +0,0 @@
- block:
- include_tasks: 01_core.yml
- include_tasks: utils/run_once.yml
when: run_once_sys_srv_web_inj_css is not defined

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
<link rel="stylesheet" type="text/css" href="/global.css?version={{ global_css_version }}">

View File

@@ -1,3 +0,0 @@
location = /global.css {
root {{ NGINX.DIRECTORIES.DATA.CDN }};
}

View File

@@ -1,4 +0,0 @@
global_css_destination: "{{ NGINX.DIRECTORIES.DATA.CDN }}global.css"
global_css_base_color: "{{ design.css.colors.base }}"
global_css_count: 7
global_css_shades: 100

View File

@@ -1,16 +0,0 @@
- name: Deploy iframe-handler.js
template:
src: iframe-handler.js.j2
dest: "{{ INJ_DESKTOP_JS_FILE_DESTINATION }}"
owner: "{{ NGINX.USER }}"
group: "{{ NGINX.USER }}"
mode: '0644'
- name: Get stat for iframe-handler.js
stat:
path: "{{ INJ_DESKTOP_JS_FILE_DESTINATION }}"
register: inj_port_ui_js_stat
- name: Set inj_port_ui_js_version
set_fact:
inj_port_ui_js_version: "{{ inj_port_ui_js_stat.stat.mtime }}"

View File

@@ -1 +0,0 @@
<script src="{{ domains | get_url('web-svc-cdn', WEB_PROTOCOL) }}/{{ INJ_DESKTOP_JS_FILE_NAME }}?{{ inj_port_ui_js_version }}"></script>

View File

@@ -1,2 +0,0 @@
INJ_DESKTOP_JS_FILE_NAME: "iframe-handler.js"
INJ_DESKTOP_JS_FILE_DESTINATION: "{{ [ NGINX.DIRECTORIES.DATA.CDN, INJ_DESKTOP_JS_FILE_NAME ] | path_join }}"

View File

@@ -1 +0,0 @@
<script src="{{ domains | get_url('web-svc-cdn', WEB_PROTOCOL) }}/{{ INJ_LOGOUT_JS_FILE_NAME }}?{{ INJ_LOGOUT_JS_VERSION }}"></script>

View File

@@ -1,2 +0,0 @@
INJ_LOGOUT_JS_FILE_NAME: "logout.js"
INJ_LOGOUT_JS_DESTINATION: "{{ [ NGINX.DIRECTORIES.DATA.CDN, INJ_LOGOUT_JS_FILE_NAME ] | path_join }}"

View File

@@ -8,7 +8,7 @@ This role bootstraps **per-domain Nginx configuration**: it requests TLS certifi
A higher-level orchestration wrapper, *sys-stk-front-proxy* ties together several lower-level roles:
1. **`sys-srv-web-inj-compose`** applies global tweaks and includes.
1. **`sys-front-inj-all`** applies global tweaks and includes.
2. **`sys-svc-certs`** obtains Lets Encrypt certificates.
3. **Domain template deployment** copies a Jinja2 vHost from *srv-proxy-core*.
4. **`web-app-oauth2-proxy`** *(optional)* protects the site with OAuth2.

View File

@@ -0,0 +1,19 @@
# sys-svc-cdn
CDN helper role for building a consistent asset tree, URLs, and on-disk layout.
## Description
Provides compact filters and defaults to define CDN paths, turn them into public URLs, collect required directories, and prepare the filesystem (including a `latest` release link).
## Overview
Defines a per-role CDN structure under `roles/<application_id>/<version>` plus shared and vendor areas. Exposes ready-to-use variables (`cdn`, `cdn_dirs`, `cdn_urls`) and ensures directories exist. Optionally links the current release to `latest`.
## Features
* Jinja filters: `cdn_paths`, `cdn_urls`, `cdn_dirs`
* Variables: `CDN_ROOT`, `CDN_VERSION`, `CDN_BASE_URL`, `cdn`, `cdn_dirs`, `cdn_urls`
* Creates shared/vendor/release directories
* Maintains `roles/<id>/latest` symlink (when version ≠ `latest`)
* Plays nicely with `web-svc-cdn` without circular inclusion

View File

@@ -0,0 +1,17 @@
import os
def cdn_dirs(tree):
out = set()
def walk(v):
if isinstance(v, dict):
for x in v.values(): walk(x)
elif isinstance(v, list):
for x in v: walk(x)
elif isinstance(v, str) and os.path.isabs(v):
out.add(v)
walk(tree)
return sorted(out)
class FilterModule(object):
def filters(self):
return {"cdn_dirs": cdn_dirs}

View File

@@ -0,0 +1,46 @@
import datetime
import os
def cdn_paths(cdn_root, application_id, version):
"""
Build a structured dictionary of all CDN paths for a given application.
Args:
cdn_root (str): Base CDN root, e.g. /var/www/cdn
application_id (str): Role/application identifier
version (str): Release version string (default: current UTC timestamp)
Returns:
dict: Hierarchical CDN path structure
"""
cdn_root = os.path.abspath(cdn_root)
return {
"root": cdn_root,
"shared": {
"root": os.path.join(cdn_root, "_shared"),
"css": os.path.join(cdn_root, "_shared", "css"),
"js": os.path.join(cdn_root, "_shared", "js"),
"img": os.path.join(cdn_root, "_shared", "img"),
"fonts": os.path.join(cdn_root, "_shared", "fonts"),
},
"vendor": os.path.join(cdn_root, "vendor"),
"role": {
"id": application_id,
"root": os.path.join(cdn_root, "roles", application_id),
"version": version,
"release": {
"root": os.path.join(cdn_root, "roles", application_id, version),
"css": os.path.join(cdn_root, "roles", application_id, version, "css"),
"js": os.path.join(cdn_root, "roles", application_id, version, "js"),
"img": os.path.join(cdn_root, "roles", application_id, version, "img"),
"fonts": os.path.join(cdn_root, "roles", application_id, version, "fonts"),
},
},
}
class FilterModule(object):
def filters(self):
return {
"cdn_paths": cdn_paths,
}

View File

@@ -0,0 +1,60 @@
# filter_plugins/cdn_urls.py
import os
def _to_url_tree(obj, cdn_root, base_url):
"""
Recursively walk a nested dict and replace any string paths under cdn_root
with URLs based on base_url. Non-path strings (e.g. role.id, role.version)
are left untouched.
"""
if isinstance(obj, dict):
return {k: _to_url_tree(v, cdn_root, base_url) for k, v in obj.items()}
if isinstance(obj, list):
return [_to_url_tree(v, cdn_root, base_url) for v in obj]
if isinstance(obj, str):
# Normalize inputs
norm_root = os.path.abspath(cdn_root)
norm_val = os.path.abspath(obj)
if norm_val.startswith(norm_root):
# Compute path relative to CDN root and map to URL
rel = os.path.relpath(norm_val, norm_root)
# Handle root itself ('.') → empty path
if rel == ".":
rel = ""
# Always forward slashes for URLs
rel_url = rel.replace(os.sep, "/")
base = base_url.rstrip("/")
return f"{base}/{rel_url}" if rel_url else f"{base}/"
# Non-CDN string → leave as-is (e.g., role.id / role.version)
return obj
# Any other type → return as-is
return obj
def cdn_urls(cdn_dict, base_url):
"""
Create a URL-structured dict from a CDN path dict.
Args:
cdn_dict (dict): output of cdn_paths(...), containing absolute paths
base_url (str): CDN base URL, e.g. https://cdn.example.com
Returns:
dict: same shape as cdn_dict, but with URLs instead of filesystem paths
for any strings pointing under cdn_dict['root'].
Keys like role.id and role.version remain strings as-is.
"""
if not isinstance(cdn_dict, dict) or "root" not in cdn_dict:
raise ValueError("cdn_urls expects a dict from cdn_paths with a 'root' key")
return _to_url_tree(cdn_dict, cdn_dict["root"], base_url)
class FilterModule(object):
def filters(self):
return {
"cdn_urls": cdn_urls,
}

View File

@@ -0,0 +1,24 @@
---
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Prepares and manages the CDN folder structure with shared, vendor, and per-role release directories."
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions
https://www.veen.world
min_ansible_version: "2.9"
platforms:
- name: Any
versions:
- all
galaxy_tags:
- cdn
- nginx
- assets
- roles
- versioning
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://s.infinito.nexus/code/tree/main/roles/sys-svc-cdn"

View File

@@ -0,0 +1,41 @@
---
- block:
- name: "Load CDN for '{{ domain }}'"
include_role:
name: web-svc-cdn
public: false
when:
#- inj_enabled.logout
#- inj_enabled.desktop
- application_id != 'web-svc-cdn'
- run_once_web_svc_cdn is not defined
- name: Overwritte CDN handlers with neutral handlers
ansible.builtin.include_tasks: "{{ [ playbook_dir, 'tasks/utils/load_handlers.yml'] | path_join }}"
loop:
- svc-prx-openresty
- docker-compose
loop_control:
label: "{{ item }}"
vars:
handler_role_name: "{{ item }}"
- include_tasks: utils/run_once.yml
when:
- run_once_sys_svc_cdn is not defined
- name: Ensure CDN directories exist
file:
path: "{{ item }}"
state: directory
owner: "{{ NGINX.USER }}"
group: "{{ NGINX.USER }}"
mode: "0755"
loop: "{{ cdn_dirs }}"
- name: Ensure 'latest' symlink points to current release
file:
src: "{{ cdn.role.release.root }}"
dest: "{{ [cdn.role.root, 'latest'] | path_join }}"
state: link
force: true
when: CDN_VERSION != 'latest'

View File

@@ -0,0 +1,19 @@
# Base CDN root (shared across all roles)
CDN_ROOT: "{{ NGINX.DIRECTORIES.DATA.CDN }}"
# Default version identifier: UTC timestamp
CDN_VERSION: "latest" # Latest is used atm because timestamp based would just lead to an unneccessary overhead
# Base Url to deliver the files
CDN_BASE_URL: "{{ domains | get_url('web-svc-cdn', WEB_PROTOCOL) }}"
# Role specific CDN paths
## Build CDN path structure (via filter)
cdn: "{{ CDN_ROOT | cdn_paths(application_id, CDN_VERSION) }}"
## Flatten CDN dict to list of all string paths
cdn_dirs: "{{ cdn | cdn_dirs }}"
# Dictionary with all urls
cdn_urls: "{{ cdn | cdn_urls(CDN_BASE_URL) }}"

View File

@@ -144,14 +144,6 @@ def update_discourse(directory):
else:
print("Discourse update skipped. No changes in git repository.")
def update_mastodon():
"""
Runs the database migration for Mastodon to ensure all required tables are up to date.
"""
print("Starting Mastodon database migration.")
run_command("docker compose exec -T web bash -c 'RAILS_ENV={{ ENVIRONMENT | lower }} bin/rails db:migrate'")
print("Mastodon database migration complete.")
def upgrade_listmonk():
"""
Runs the upgrade for Listmonk
@@ -220,8 +212,6 @@ if __name__ == "__main__":
update_discourse(dir_path)
elif os.path.basename(dir_path) == "listmonk":
upgrade_listmonk()
elif os.path.basename(dir_path) == "mastodon":
update_mastodon()
# @todo implement dedicated procedure for bluesky
# @todo implement dedicated procedure for taiga

View File

@@ -13,7 +13,7 @@ server:
- https://cdn.jsdelivr.net
- https://kit.fontawesome.com
- https://code.jquery.com/
style-src:
style-src-elem:
- https://cdn.jsdelivr.net
font-src:
- https://ka-f.fontawesome.com
@@ -24,12 +24,8 @@ server:
frame-src:
- "{{ WEB_PROTOCOL }}://*.{{ PRIMARY_DOMAIN }}"
flags:
style-src:
unsafe-inline: true
script-src:
unsafe-inline: true
script-src-elem:
unsafe-inline: true
domains:
canonical:
- "{{ PRIMARY_DOMAIN }}"

View File

@@ -0,0 +1,21 @@
.card-img-top i {
filter: drop-shadow(4px 4px 4px rgba(var(--color-01-rgb-23), 0.6));
}
.kanban-table-body .kanban-uses-box {
background-color: var(--color-01-81);
}
.kanban-swimlane-title {
border-bottom: none;
}
.navbar-toggler {
background-color: rgba(var(--color-01-rgb-75), 0.9);
border-color: var(--color-01-67)
}
.sys-ctl-alm-info {
--bs-sys-ctl-alm-color: var(--color-03-14);
--bs-sys-ctl-alm-bg: var(--color-01-86);
}

View File

@@ -0,0 +1,102 @@
/* Discourse */
:root section#main{
/* Base Colors */
--primary: var(--color-01-20); /* originally #203243 */
--secondary: var(--color-01-95); /* originally #eef4f7 */
--tertiary: var(--color-01-40); /* originally #416376 */
--quaternary: var(--color-01-50); /* originally #5e99b9 */
/* Header & Highlight */
--header_background: var(--color-01-86); /* originally #86bddb */
--header_primary: var(--color-01-20); /* same as --primary */
--highlight: var(--color-01-70); /* same as header_background */
--d-selected: var(--color-01-85); /* originally #bee0f2 */
--d-hover: var(--color-01-90); /* originally #d2efff */
/* Normally refers to secondary. Somehow this reference does not work.*/
--d-sidebar-background: var(--color-01-92);
--d-sidebar-footer-fade: var(--color-01-92);
/* RGB values */
--always-black-rgb: var(--color-01-rgb-01);
--primary-rgb: var(--color-01-rgb-20);
--primary-low-rgb: var(--color-01-rgb-95);
--primary-very-low-rgb: var(--color-01-rgb-99);
--secondary-rgb: var(--color-01-rgb-95);
--header_background-rgb: var(--color-01-rgb-70);
--tertiary-rgb: var(--color-01-rgb-40);
--highlight-rgb: var(--color-01-rgb-70);
--success-rgb: var(--color-01-rgb-50);
/* Primary Scale */
--primary-very-low: var(--color-01-99); /* originally #f7f9fb */
--primary-low: var(--color-01-95); /* originally #e3ebf2 */
--primary-low-mid: var(--color-01-75); /* originally #acc2d7 */
--primary-medium: var(--color-01-60); /* originally #7499bd */
--primary-high: var(--color-01-40); /* originally #487096 */
--primary-very-high: var(--color-01-20); /* originally #34516d */
--primary-50: var(--color-01-99); /* originally #f7f9fb */
{% for i in range(1, 10) %}
{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #}
{% set primary = i * 100 %}
{% set color = 100 - i * 8 %}
--primary-{{ primary }}: var(--color-01-{{ "%02d" % color }});
{% endfor %}
/* Header Primary Scale */
--header_primary-low: rgb(var(--color-01-rgb-75)); /* rgb(128, 180, 209) */
--header_primary-low-mid: rgb(var(--color-01-rgb-70)); /* rgb(110, 155, 181) */
--header_primary-medium: rgb(var(--color-01-rgb-60)); /* rgb(93, 132, 155) */
--header_primary-high: rgb(var(--color-01-rgb-50)); /* rgb(78, 112, 132) */
--header_primary-very-high: rgb(var(--color-01-rgb-20)); /* rgb(52, 76, 94) */
/* Secondary Scale */
--secondary-low: var(--color-01-20); /* originally #2f5163 */
--secondary-medium: var(--color-01-40); /* originally #4e88a5 */
--secondary-high: var(--color-01-60); /* originally #7ba9c1 */
--secondary-very-high: var(--color-01-90); /* originally #d7e5ec */
/* Tertiary Scale */
--tertiary-very-low: var(--color-01-99); /* originally #eaf0f3 */
--tertiary-low: var(--color-01-95); /* originally #dfe8ee */
--tertiary-medium: var(--color-01-60); /* originally #96b4c5 */
--tertiary-high: var(--color-01-40); /* originally #5886a0 */
--tertiary-hover: var(--color-01-20); /* originally #314a59 */
--tertiary-50: var(--color-01-99); /* originally #eaf0f3 */
{% for i in range(1, 10) %}
{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #}
{% set tertiary = i * 100 %}
{% set color = 100 - i * 5 %}
--tertiary-{{ tertiary }}: var(--color-01-{{ "%02d" % color }});
{% endfor %}
/* Quaternary */
--quaternary-low: var(--color-01-80); /* originally #cfe0ea */
/* Highlight */
--highlight-bg: var(--color-01-90); /* originally #dbebf4 */
--highlight-low: var(--color-01-90); /* originally #dbebf4 */
--highlight-medium: var(--color-01-80); /* originally #c3deed */
--highlight-high: var(--color-01-30); /* originally #286688 */
/* Combination Variables */
--blend-primary-secondary-5: rgb(var(--color-01-rgb-95)); /* originally rgb(232, 238, 241) */
--primary-med-or-secondary-med: var(--color-01-60); /* originally #7499bd */
--primary-med-or-secondary-high: var(--color-01-60); /* originally #7499bd */
--primary-high-or-secondary-low: var(--color-01-40); /* originally #487096 */
--primary-low-mid-or-secondary-high: var(--color-01-75); /* originally #acc2d7 */
--primary-low-mid-or-secondary-low: var(--color-01-75); /* originally #acc2d7 */
--primary-or-primary-low-mid: var(--color-01-20); /* originally #203243 */
--highlight-low-or-medium: var(--color-01-90); /* originally #dbebf4 */
--tertiary-or-tertiary-low: var(--color-01-40); /* originally #416376 */
--tertiary-low-or-tertiary-high: var(--color-01-95); /* originally #dfe8ee */
--tertiary-med-or-tertiary: var(--color-01-60); /* originally #96b4c5 */
--secondary-or-primary: var(--color-01-95); /* originally #eef4f7 */
--tertiary-or-white: var(--color-01-40); /* originally #416376 */
/* Float Kit */
--float-kit-arrow-stroke-color: var(--primary-low); /* already mapped above */
--float-kit-arrow-fill-color: var(--secondary); /* already mapped above */
}

View File

@@ -0,0 +1,141 @@
:root {
/* Base and derived colors are now referenced from the computed scale */
--color-01-primary: var(--color-01-50);
--color-01-primary-contrast: var(--color-01-99);
--color-01-primary-dark-1: var(--color-01-48);
--color-01-primary-dark-2: var(--color-01-47);
--color-01-primary-dark-3: var(--color-01-46);
--color-01-primary-dark-4: var(--color-01-45);
--color-01-primary-dark-5: var(--color-01-44);
--color-01-primary-dark-6: var(--color-01-43);
--color-01-primary-dark-7: var(--color-01-42);
--color-01-primary-light-1: var(--color-01-52);
--color-01-primary-light-2: var(--color-01-53);
--color-01-primary-light-3: var(--color-01-54);
--color-01-primary-light-4: var(--color-01-55);
--color-01-primary-light-5: var(--color-01-57);
--color-01-primary-light-6: var(--color-01-59);
--color-01-primary-light-7: var(--color-01-61);
/* Alpha variants reference the base RGB variable */
{% for i in range(1, 10) %}
{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #}
{% set alpha = i * 10 %}
--color-01-primary-alpha-{{ alpha }}: rgba(var(--color-01-rgb-50), 0.{{ alpha }});
{% endfor %}
--color-01-primary-hover: var(--color-01-primary-dark-1);
--color-01-primary-active: var(--color-01-primary-dark-2);
/* Secondary colors */
--color-01-secondary: var(--color-01-80);
--color-01-secondary-dark-1: var(--color-01-78);
--color-01-secondary-dark-2: var(--color-01-76);
--color-01-secondary-dark-3: var(--color-01-74);
--color-01-secondary-dark-4: var(--color-01-72);
--color-01-secondary-dark-5: var(--color-01-70);
--color-01-secondary-dark-6: var(--color-01-68);
--color-01-secondary-dark-7: var(--color-01-66);
--color-01-secondary-dark-8: var(--color-01-64);
--color-01-secondary-dark-9: var(--color-01-62);
--color-01-secondary-dark-10: var(--color-01-60);
--color-01-secondary-dark-11: var(--color-01-58);
--color-01-secondary-dark-12: var(--color-01-56);
--color-01-secondary-dark-13: var(--color-01-54);
--color-01-secondary-light-1: var(--color-01-92);
--color-01-secondary-light-2: var(--color-01-93);
--color-01-secondary-light-3: var(--color-01-94);
--color-01-secondary-light-4: var(--color-01-95);
{% for i in range(1, 10) %}
{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #}
{% set alpha = i * 10 %}
--color-01-secondary-alpha-{{ alpha }}: rgba(var(--color-01-rgb-80), 0.{{ alpha }});
{% endfor %}
--color-01-secondary-button: var(--color-01-secondary-dark-4);
--color-01-secondary-hover: var(--color-01-secondary-dark-5);
--color-01-secondary-active: var(--color-01-secondary-dark-6);
/* Console Colors */
--color-01-console-fg: var(--color-01-98);
--color-01-console-fg-subtle: var(--color-01-85);
--color-01-console-bg: var(--color-01-10);
--color-01-console-border: var(--color-01-40);
--color-01-console-hover-bg: var(--color-01-42);
--color-01-console-active-bg: var(--color-01-40);
--color-01-console-menu-bg: var(--color-01-38);
--color-01-console-menu-border:var(--color-01-45);
/* Body, Text, and Miscellaneous Colors */
--color-01-white: var(--color-01-99);
--color-01-grey: var(--color-01-60);
--color-01-grey-light: var(--color-01-65);
--color-01-body: var(--color-01-white);
--color-01-text-dark: var(--color-01-10);
--color-01-text: var(--color-01-40);
--color-01-text-light: var(--color-01-60);
--color-01-text-light-1: var(--color-01-65);
--color-01-text-light-2: var(--color-01-70);
--color-01-text-light-3: var(--color-01-75);
--color-01-footer: var(--color-01-nav-bg);
--color-01-timeline: var(--color-01-80);
/* Input Colors */
--color-01-input-text: var(--color-01-10);
--color-01-input-background: var(--color-01-white);
--color-01-input-toggle-background: var(--color-01-80);
--color-01-input-border: var(--color-01-secondary);
--color-01-input-border-hover: var(--color-01-secondary-dark-1);
/* Effects */
--color-01-light: var(--color-01-05);
--color-01-light-mimic-enabled: rgba(var(--color-01-rgb-05), calc(6 / 255 * 222 / 255 / 0.55));
--color-01-light-border: var(--color-01-05);
--color-01-hover: var(--color-01-05);
--color-01-hover-opaque: var(--color-01-95);
--color-01-active: var(--color-01-05);
/* Menu, Card, and Markup Colors */
--color-01-menu: var(--color-01-99);
--color-01-card: var(--color-01-99);
--color-01-markup-table-row: var(--color-01-01);
--color-01-markup-code-block: var(--color-01-01);
--color-01-markup-code-inline: var(--color-01-01);
--color-01-button: var(--color-01-99);
--color-01-code-bg: var(--color-01-99);
--color-01-shadow: var(--color-01-05);
--color-01-shadow-opaque: var(--color-01-85);
--color-01-secondary-bg: var(--color-01-95);
--color-01-expand-button: var(--color-01-98);
--color-01-placeholder-text: var(--color-01-text-light-3);
--color-01-editor-line-highlight: var(--color-01-primary-light-6);
--color-01-project-column-bg: var(--color-01-secondary-light-4);
--color-01-caret: var(--color-01-10);
/* Reaction and Tooltip Colors */
--color-01-reaction-bg: var(--color-01-05);
--color-01-reaction-hover-bg: var(--color-01-primary-light-5);
--color-01-reaction-active-bg: var(--color-01-primary-light-6);
--color-01-tooltip-text: var(--color-01-99);
--color-01-tooltip-bg: var(--color-01-05);
/* Navigation Colors */
--color-01-nav-bg: var(--color-01-99);
--color-01-nav-hover-bg: var(--color-01-secondary-light-1);
--color-01-nav-text: var(--color-01-40);
--color-01-secondary-nav-bg: var(--color-01-99);
/* Label and Accent Colors */
--color-01-label-text: var(--color-01-40);
--color-01-label-bg: var(--color-01-50);
--color-01-label-hover-bg: var(--color-01-60);
--color-01-label-active-bg: var(--color-01-70);
--color-01-accent: var(--color-01-primary-light-1);
--color-01-small-accent: var(--color-01-primary-light-6);
--color-01-highlight-fg: var(--color-01-10);
--color-01-highlight-bg: var(--color-01-99);
--color-01-overlay-backdrop: var(--color-01-05);
}

View File

@@ -0,0 +1,109 @@
:root{
/* --- Palette Black (Gray Scale) --- */
{% for i in range(1, 21) %}
{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #}
{% set black = i * 50 %}
{% set color = 100 - i * 5 %}
--pf-v5-global--palette--black-{{ black }}: var(--color-01-{{ "%02d" % color }});
{% endfor %}
/* --- White --- */
--pf-v5-global--palette--white: var(--color-01-99);
/* --- Background Colors --- */
--pf-v5-global--BackgroundColor--100: var(--color-01-99);
--pf-v5-global--BackgroundColor--150: var(--color-01-95);
--pf-v5-global--BackgroundColor--200: var(--color-01-85);
--pf-v5-global--BackgroundColor--300: var(--color-01-75);
--pf-v5-global--BackgroundColor--400: var(--color-01-65);
--pf-v5-global--BackgroundColor--light-100: var(--color-01-99);
--pf-v5-global--BackgroundColor--light-200: var(--color-01-95);
--pf-v5-global--BackgroundColor--light-300: var(--color-01-85);
--pf-v5-global--BackgroundColor--dark-100: var(--color-01-10);
--pf-v5-global--BackgroundColor--dark-200: var(--color-01-25);
--pf-v5-global--BackgroundColor--dark-300: var(--color-01-20);
--pf-v5-global--BackgroundColor--dark-400: var(--color-01-30);
--pf-v5-global--BackgroundColor--dark-transparent-100: rgba(var(--color-01-rgb-10),0.7);
--pf-v5-global--BackgroundColor--dark-transparent-200: rgba(var(--color-01-rgb-20),0.8);
/* --- Color Variables --- */
--pf-v5-global--color-01--100: var(--color-01-10);
--pf-v5-global--color-01--200: var(--color-01-40);
--pf-v5-global--color-01--300: var(--color-01-25);
--pf-v5-global--color-01--400: var(--color-01-50);
--pf-v5-global--color-01--light-100: var(--color-01-99);
--pf-v5-global--color-01--light-200: var(--color-01-85);
--pf-v5-global--color-01--light-300: var(--color-01-75);
--pf-v5-global--color-01--dark-100: var(--color-01-10);
--pf-v5-global--color-01--dark-200: var(--color-01-40);
/* --- Active Colors --- */
--pf-v5-global--active-color--100: var(--color-01-65);
--pf-v5-global--active-color--200: var(--color-01-95);
--pf-v5-global--active-color--300: var(--color-01-75);
--pf-v5-global--active-color--400: var(--color-01-85);
/* --- Disabled Colors --- */
--pf-v5-global--disabled-color--100: var(--color-01-40);
--pf-v5-global--disabled-color--200: var(--color-01-75);
--pf-v5-global--disabled-color--300: var(--color-01-85);
/* --- Primary Colors --- */
--pf-v5-global--primary-color--100: var(--color-01-65);
--pf-v5-global--primary-color--200: var(--color-01-40);
--pf-v5-global--primary-color--light-100: var(--color-01-75);
--pf-v5-global--primary-color--dark-100: var(--color-01-65);
/* --- Secondary Colors --- */
--pf-v5-global--secondary-color--100: var(--color-01-40);
/* --- Custom Colors --- */
--pf-v5-global--custom-color--100: var(--color-01-65);
--pf-v5-global--custom-color--200: var(--color-01-65);
--pf-v5-global--custom-color--300: var(--color-01-30);
/* --- Link Colors --- */
--pf-v5-global--link--Color: var(--color-01-65);
--pf-v5-global--link--color-01--hover: var(--color-01-40);
--pf-v5-global--link--color-01--light: var(--color-01-75);
--pf-v5-global--link--color-01--light--hover: var(--color-01-85);
--pf-v5-global--link--color-01--dark: var(--color-01-65);
--pf-v5-global--link--color-01--dark--hover: var(--color-01-40);
--pf-v5-global--link--color-01--visited: var(--color-01-40);
/* --- Border Colors --- */
--pf-v5-global--BorderColor--100: var(--color-01-75);
--pf-v5-global--BorderColor--200: var(--color-01-50);
--pf-v5-global--BorderColor--300: var(--color-01-85);
--pf-v5-global--BorderColor--400: var(--color-01-65);
--pf-v5-global--BorderColor--dark-100: var(--color-01-75);
--pf-v5-global--BorderColor--light-100: var(--color-01-65);
/* --- Icon Colors --- */
--pf-v5-global--icon--color-01--light: var(--color-01-40);
--pf-v5-global--icon--color-01--dark: var(--color-01-10);
--pf-v5-global--icon--color-01--light--light: var(--color-01-85);
--pf-v5-global--icon--color-01--dark--light: var(--color-01-99);
--pf-v5-global--icon--color-01--light--dark: var(--color-01-40);
--pf-v5-global--icon--color-01--dark--dark: var(--color-01-10);
}
.pf-v5-c-button.pf-m-tertiary {
--pf-v5-c-button--m-tertiary--BackgroundColor: var(--color-01-70);
{# Assume that the following line is necessary due to load order #}
background-color: var(--pf-v5-c-button--m-tertiary--BackgroundColor);
}
/* Additional Keykloak Configuration */
a.pf-v5-c-nav__link{
--pf-v5-c-nav__link--BackgroundColor: rgba(var(--color-01-rgb-56), 0.4);
}
/* Keycloak */
div#app header{
background-color: var(--color-01-60);
/* New Gradient based on original background (60 -5, 60, 60 +1, 60 +5) */
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-55), var(--color-01-60), var(--color-01-61), var(--color-01-65));
color: var(--color-01-98);
}

View File

@@ -0,0 +1,51 @@
:root {
--lam-background-color-default: var(--color-01-99); {# from #FFFFFF (very bright white) #}
--lam-input-bg-color: var(--color-01-98); {# from #fcfcfc (almost white) #}
--lam-text-color-default: var(--color-01-01); {# from #000000 (pure black) #}
--lam-border-color: var(--color-01-90); {# from #e8e8e8 (light grey) #}
--lam-border-color-primary: var(--color-01-15); {# from #01689e (dark blue) #}
--lam-border-color-secondary: var(--color-01-85); {# from #ffcb1d (bright yellow) #}
--lam-background-color-primary: var(--color-01-50); {# from #3daee9 (mid-tone blue) #}
--lam-background-color-secondary: var(--color-01-90); {# from #ffe233 (bright yellow) #}
--lam-text-color-primary: var(--color-01-99); {# from #ffffff (pure white) #}
--lam-text-color-secondary: var(--color-01-01); {# from #000000 (pure black) #}
--lam-text-color-ok: var(--color-01-10); {# from #237d0c (dark green) #}
--lam-table-background-color-bright: var(--color-01-98); {# from #fbfbfb (very light grey) #}
--lam-table-background-color-dark: var(--color-01-92); {# from #e8f3ff (light blue) #}
--lam-table-background-color-hover: var(--color-01-50); {# from #3daee9 (mid-tone blue) #}
--lam-table-text-color-hover: var(--color-01-99); {# from #ffffff (pure white) #}
--lam-table-border-color: var(--color-01-50); {# from #3daee9 (mid-tone blue) #}
}
/** LAM Specific **/
.lam-vertical-tabs-navigation li, .lam-vertical-tabs-navigation{
background-color: transparent !important;
border-color: transparent;
}
ul.lam-tab-navigation {
background: rgba(var(--color-01-rgb-90), 0.1);
border-color: transparent;
}
/* Not changable due to inline css */
.roundedShadowBox {
color: #000000;
}
.titleBar {
background-image: linear-gradient(var(--color-01-83), var(--color-01-92));
/* New Gradient based on original background (83 -5, 83, 83 +1, 83 +5) */
background-image: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-78), var(--color-01-83), var(--color-01-84), var(--color-01-88));
border-top-color: var(--color-01-78);
border-left-color: var(--color-01-87);
border-right-color: var(--color-01-87);
}
div.statusInfo {
background-color: var(--color-01-81);
/* New Gradient based on original background (81 -5, 81, 81 +1, 81 +5) */
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-76), var(--color-01-81), var(--color-01-82), var(--color-01-86));
color: var(--color-01-23);
}

View File

@@ -0,0 +1,35 @@
[class*=sidebar-dark-], .bg-mailu-logo {
background-color: var(--color-01-90);
/* New Gradient based on original background (90 -5, 90, 90 +1, 90 +5) */
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-85), var(--color-01-90), var(--color-01-91), var(--color-01-95));
}
div.statusError {
background-color: var(--color-01-60);
/* New Gradient based on original background (60 -5, 60, 60 +1, 60 +5) */
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-55), var(--color-01-60), var(--color-01-61), var(--color-01-65));
}
div.wrapper footer.main-footer, div.wrapper div.content-wrapper{
background-color: var(--color-01-85);
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-89), var(--color-01-85), var(--color-01-80), var(--color-01-79));
color: var(--color-01-39);
}
html.dark-mode #layout-menu .special-buttons a:not(:focus) {
background: none;
}
html.dark-mode #taskmenu a.selected, html.dark-mode .menu.toolbar a.selected {
background-color: rgba(var(--color-01-rgb-82), 0.5);
}
html.dark-mode .listing li.selected, html.dark-mode .listing li.selected>a, html.dark-mode .listing li.selected>div>a, html.dark-mode .listing tr.selected td {
color: var(--color-01-30);
background-color: rgba(var(--color-01-rgb-82), 0.5);
}
html.dark-mode .message-htmlpart {
background-color: rgba(var(--color-01-rgb-99), 0.08);
color: var(--color-01-15);
}

View File

@@ -0,0 +1,43 @@
div#mastodon, div#admin-wrapper {
/* Dropdown */
--dropdown-border-color: var(--color-01-35);
--dropdown-background-color: rgba(var(--color-01-rgb-03), 0.9);
--dropdown-shadow: 0 20px 25px -5px rgba(var(--color-01-rgb-01), 0.25),
0 8px 10px -6px rgba(var(--color-01-rgb-01), 0.25);
/* Modal */
--modal-background-color: rgba(var(--color-01-rgb-03), 0.7);
--modal-background-variant-color: rgba(var(--color-01-rgb-05), 0.7);
--modal-border-color: var(--color-01-35);
/* Background */
--background-border-color: var(--color-01-82);
--background-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%);
--background-color: var(--color-01-93);
--background-color-tint: rgba(var(--color-01-rgb-03), 0.9);
/* Surface */
--surface-background-color: var(--color-01-90);
--surface-variant-background-color: var(--color-01-89);
--surface-variant-active-background-color: var(--color-01-35);
--on-surface-color: rgba(var(--color-01-rgb-05), 0.5);
/* Media & Overlay */
--media-outline-color: rgba(var(--color-01-rgb-99), 0.15);
--overlay-icon-shadow: drop-shadow(0 0 8px rgba(var(--color-01-rgb-01), 0.25));
}
.swal2-popup {
color: #000;
}
/* Modal Overwrittes */
div.modal div.modal-content {
/* Colors adjusted to the existing scheme */
--bs-modal-color: var(--color-01-21); /* Text color: dark contrast against the light modal background */
--bs-modal-bg: var(--color-01-82); /* Background color, as desired */
--bs-modal-border-color: var(--color-01-82); /* A slightly darker border than the background */
--bs-modal-header-border-color: var(--color-01-87); /* Same shade as the modal border */
--bs-modal-footer-bg: var(--color-01-87); /* A slightly offset footer background (a bit darker than the main area) */
--bs-modal-footer-border-color: var(--color-01-87);
}

View File

@@ -4,7 +4,7 @@ server
{% include 'roles/srv-letsencrypt/templates/ssl_header.j2' %}
{% include 'roles/sys-srv-web-inj-compose/templates/server.conf.j2'%}
{% include 'roles/sys-front-inj-all/templates/server.conf.j2'%}
# Remove X-Powered-By, which is an information leak
fastcgi_hide_header X-Powered-By;

View File

@@ -0,0 +1,65 @@
:root{
--color-01-main-background: var(--color-01-84);
--color-01-main-background-rgb: rgba(var(--color-01-rgb-84),0.83);
--color-01-primary-element: var(--color-01-80);
--color-01-main-text: var(--color-01-40);
--color-01-background-hover: var(--color-01-65);
/** Calendar **/
--color-01-background-dark: var(--color-01-73); /** Days which aren't in the current month **/
--color-01-primary-element-light: var(--color-01-65);
}
/** Nextcloud specific **/
html.ng-csp header#header{
background-color: var(--color-01-80);
/* New Gradient based on original background (80 -5, 80, 80 +1, 80 +5) */
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-75), var(--color-01-80), var(--color-01-81), var(--color-01-85));
color: var(--color-01-17);
}
.files-list__row-name button, button.button-vue{
background: transparent;
}
html.ng-csp div#postsetupchecks ul.info{
background-color: transparent;
}
/** I wounder if this is correct here or if it belongs to the mastodon role */
div#mastodon .column-link{
color: var(--color-01-55);
}
div#mastodon .column-back-button {
color: var(--color-01-58);
}
div#mastodon textarea, div#mastodon input, div#mastodon .compose-form__highlightable {
background-color: var(--color-01-89);
/* New Gradient based on original background (89 -5, 89, 89 +1, 89 +5) */
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-84), var(--color-01-89), var(--color-01-90), var(--color-01-94));
color: var(--color-01-19);
}
div#mastodon .status-card__title, div#mastodon .display-name strong{
color: var(--color-01-33);
}
div#mastodon a.unhandled-link, div#mastodon .dropdown-button, div#mastodon .status__content a, div#mastodon .status-card__author{
color: var(--color-01-29);
}
div#mastodon .dropdown-button{
border: 1px solid #8c8dff;
}
div#mastodon .button, div#mastodon .button:active, div#mastodon .button:focus, div#mastodon .button:hover{
background-color: var(--color-01-71);
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-66), var(--color-01-71), var(--color-01-72), var(--color-01-76));
}
.compose-form__actions .icon-button {
color: var(--color-01-28);
}

View File

@@ -0,0 +1,12 @@
.box {
background-color: var(--color-01-92);
color: var(--color-01-10);
}
.subtitle {
color: inherit;
}
.has-background-light {
background-color: var(--color-01-96) !important;
}

View File

@@ -0,0 +1,9 @@
header.op-app-header{
background-color: var(--color-01-40);
background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-01-35), var(--color-01-40), var(--color-01-41), var(--color-01-45));
color: var(--color-01-40);
}
div#wrapper button, div#wrapper input, button.top-menu-search-button, div.menu-sidebar a{
background-color: transparent;
}

View File

@@ -3,7 +3,7 @@ server {
{% include 'roles/srv-letsencrypt/templates/ssl_header.j2' %}
{% include 'roles/sys-srv-web-inj-compose/templates/server.conf.j2'%}
{% include 'roles/sys-front-inj-all/templates/server.conf.j2'%}
{% include 'roles/srv-proxy-core/templates/headers/content_security_policy.conf.j2' %}

View File

@@ -0,0 +1,38 @@
body#custom-css {
--mainColor: var(--color-01-60); /* Original tone: hsl(24, 90%, 50%) vibrant orange */
--mainColorLighter: var(--color-01-70); /* Original tone: #f5873d lighter orange */
--mainColorLightest: var(--color-01-90); /* Original tone: #fce1cf very light orange/beige */
--mainColorVeryLight: var(--color-01-95); /* Original tone: #fff5eb almost white */
--mainHoverColor: var(--color-01-64); /* Original tone: #f47825 hover orange */
--mainBackgroundHoverColor: var(--color-01-92); /* Original tone: #e9ecef light gray */
--mainBackgroundColor: var(--color-01-99); /* Original tone: #fff white */
--mainForegroundColor: var(--color-01-10); /* Original tone: #212529 dark gray/black */
--greyForegroundColor: var(--color-01-50); /* Original tone: #585858 medium gray */
--greyBackgroundColor: var(--color-01-90); /* Original tone: #E5E5E5 light gray */
--greySecondaryBackgroundColor: var(--color-01-91); /* Original tone: #EFEFEF very light gray */
--menuBackgroundColor: var(--color-01-82); /* Original tone: #000 black */
--menuForegroundColor: var(--color-01-99); /* Original tone: #fff white */
--submenuBackgroundColor: var(--color-01-95); /* Original tone: #F7F7F7 off-white/light gray */
--channelBackgroundColor: var(--color-01-93); /* Original tone: #f6ede8 warm light beige */
--inputForegroundColor: var(--color-01-10); /* Original tone: #212529 dark gray */
--inputBackgroundColor: var(--color-01-99); /* Original tone: #fff white */
--inputPlaceholderColor: var(--color-01-55); /* Original tone: #797676 medium gray */
--inputBorderColor: var(--color-01-80); /* Original tone: #C6C6C6 light gray */
--textareaForegroundColor: var(--color-01-10); /* Original tone: #212529 dark gray */
--textareaBackgroundColor: var(--color-01-99); /* Original tone: #fff white */
--markdownTextareaBackgroundColor: var(--color-01-91); /* Original tone: #EFEFEF very light gray */
--actionButtonColor: var(--color-01-50); /* Original tone: #585858 medium gray */
--supportButtonColor: var(--actionButtonColor); /* Original tone: same as actionButtonColor (#585858) */
--activatedActionButtonColor: var(--color-01-10); /* Original tone: #212529 dark gray */
color: var(--mainForegroundColor);
background-color: var(--mainBackgroundColor);
}
div.searchbox input.autocomplete-input{
background-position: 12px;
background-repeat: no-repeat;
}
html[native-dark-active] my-app button, button {
background-color: transparent;
}

Some files were not shown because too many files have changed in this diff Show More