mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-09-09 03:37:37 +02:00
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:
19
roles/sys-svc-cdn/README.md
Normal file
19
roles/sys-svc-cdn/README.md
Normal 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
|
0
roles/sys-svc-cdn/__init__.py
Normal file
0
roles/sys-svc-cdn/__init__.py
Normal file
0
roles/sys-svc-cdn/filter_plugins/__init__.py
Normal file
0
roles/sys-svc-cdn/filter_plugins/__init__.py
Normal file
17
roles/sys-svc-cdn/filter_plugins/cdn_dirs.py
Normal file
17
roles/sys-svc-cdn/filter_plugins/cdn_dirs.py
Normal 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}
|
46
roles/sys-svc-cdn/filter_plugins/cdn_paths.py
Normal file
46
roles/sys-svc-cdn/filter_plugins/cdn_paths.py
Normal 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,
|
||||
}
|
60
roles/sys-svc-cdn/filter_plugins/cdn_urls.py
Normal file
60
roles/sys-svc-cdn/filter_plugins/cdn_urls.py
Normal 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,
|
||||
}
|
24
roles/sys-svc-cdn/meta/main.yml
Normal file
24
roles/sys-svc-cdn/meta/main.yml
Normal 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"
|
41
roles/sys-svc-cdn/tasks/main.yml
Normal file
41
roles/sys-svc-cdn/tasks/main.yml
Normal 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'
|
19
roles/sys-svc-cdn/vars/main.yml
Normal file
19
roles/sys-svc-cdn/vars/main.yml
Normal 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) }}"
|
Reference in New Issue
Block a user