180 Commits

Author SHA1 Message Date
002f8de3ec feat(proxy): add configurable client_max_body_size for HTML and upload locations
This commit introduces a unified mechanism to configure client_max_body_size
for both HTML and upload locations in the sys-svc-proxy role. The directive
is now injected early in html.conf.j2 and moved to a dedicated block in
upload.conf.j2 to ensure consistent behavior for large file uploads such as
OpenProject attachments.

Additionally:
- Added client_max_body_size variable override from web-app-openproject (set to 30m).
- Reordered header includes to avoid duplicate injection.
- Improved comments and structure for better clarity.

Reference: https://chatgpt.com/share/691d873e-9b50-800f-ae70-baf8bf1e5454
2025-11-19 10:02:33 +01:00
68a8128d38 Increase WordPress container resources (cpus=1, mem_reservation=0.5g, mem_limit=1.5g, pids_limit=512)
Ref: Updated based on performance guidance from ChatGPT conversation:
https://chatgpt.com/share/691c8f1f-306c-800f-92b0-1bbe8e2ba5c4
2025-11-18 16:22:33 +01:00
36f9573fdf feat(filters): enforce safe Node.js heap sizing via reusable filter
- Add node_autosize filter (node_max_old_space_size) using get_app_conf
- Raise error when mem_limit < min_mb to prevent OOM-kill misconfigurations
- Wire Whiteboard NODE_OPTIONS and increase mem_limit to 1g; set cpus=1
- Refactor PeerTube to use the same filter; simplify vars
- Add unit tests; keep integration filters usage green

Context: https://chatgpt.com/share/690e0499-6a94-800f-b8ed-2c5124690103
2025-11-07 15:39:54 +01:00
493d5bbbda refactor(web-app-shopware): externalize trusted proxy and host configuration with mounted framework.yaml
- added new file roles/web-app-shopware/files/framework.yaml defining trusted_proxies and trusted_headers for Symfony
 - mounted framework.yaml into /var/www/html/config/packages/ in docker-compose
 - exposed new role vars SHOPWARE_FRAMEWORK_HOST/DOCKER for mounting path
 - rendered framework.yaml via Ansible copy task with proper permissions
 - adjusted env.j2 to set TRUSTED_PROXIES and TRUSTED_HOSTS dynamically from domains and networks
 - added SHOPWARE_DOMAIN var to vars/main.yml
 - removed inline framework.yaml creation from Dockerfile (now managed via mount)
 - updated proxy template (html.conf.j2) to include X-Forwarded-Ssl header
 - improved init.sh permission handling for shared volumes

See ChatGPT conversation for implementation details and rationale:
https://chatgpt.com/share/690d4fe7-2830-800f-8b6d-b868e7fe0e97
2025-11-07 02:48:49 +01:00
2fcbae8fc7 Added z.clarity.ms to mini-qr 2025-11-07 00:18:01 +01:00
02f38d60db Added z.clarity.ms to mini-qr 2025-11-07 00:02:36 +01:00
d66ad37c5d enh(shopware): improve healthchecks and proxy configuration
Removed obsolete EXPOSE/healthcheck from Dockerfile and added robust service-specific healthchecks:

- web: HTTP robots.txt check

- worker/scheduler: php -v runtime check

- opensearch: cluster health API check

Added TRUSTED_PROXIES=* for proxy-aware headers and centralized OPENSEARCH_PORT in vars.

Context: discussed implementation details in ChatGPT conversation on 2025-11-06 — https://chatgpt.com/share/690c9fb3-79f4-800f-bbdf-ea370c8f142c
2025-11-06 14:17:00 +01:00
0c16f9c43c Optimized code 2025-11-05 20:46:33 +01:00
7330aeb8ec feat(web-app-peertube): add dynamic performance tuning for heap and transcoding concurrency
- Dynamically calculate PEERTUBE_MAX_OLD_SPACE_SIZE (~35% of container RAM, clamped between 768–3072 MB)
- Dynamically calculate PEERTUBE_TRANSCODING_CONCURRENCY (~½ vCPUs, min 1, max 8)
- Added default resource limits for Redis and Peertube containers
- Updated test suite to include human_to_bytes filter in built-in filter list

https://chatgpt.com/share/690914d2-6100-800f-a850-94e6d226e7c9
2025-11-03 21:47:38 +01:00
d3aad632c0 Merge branch 'master' of github.com:kevinveenbirkenbach/infinito-nexus 2025-11-03 16:41:13 +01:00
d1bad3d7a6 Added joomla user for install 2025-11-03 11:24:56 +01:00
43056a8b92 Activated CSS and Desktop for shopware 2025-11-03 11:20:03 +01:00
0bf286f62a Enhance Shopware role: fix init script permissions, CSP for data: fonts, and unify shell usage
- Added 'font-src data:' to CSP whitelist to allow inline fonts in Admin UI
- Refactored init.sh to run as root only for volume permission setup, then drop privileges to www-data
- Unified all bash invocations to sh for POSIX compliance
- Added missing 'bundles' named volume and mount to Docker Compose
- Set init container to run as root (0:0) for permission setup
- Added admin user rename step via Ansible task

See discussion: https://chatgpt.com/share/69087361-859c-800f-862c-7413350cca3e
2025-11-03 10:18:45 +01:00
df8390f386 Refactor category sorting in docker_cards_grouped lookup plugin, restructure Shopware task sequence, and extend menu categories (Commerce, Storage). Added unit tests for lookup plugin.
Conversation reference: https://chatgpt.com/share/6908642f-29cc-800f-89ec-fd6de9892b44
2025-11-03 09:14:15 +01:00
48557b06e3 refactor(web-app-shopware): make init script idempotent and handle admin via Ansible
- moved init.sh from template to files/ for direct copying and bind mounting
- removed hardcoded user creation from init process
- added database emptiness check before running system:install
- added new task 03_admin.yml to ensure admin user exists and update password/email via Ansible
- switched docker exec shell from bash to sh for Alpine compatibility
- updated Dockerfile and docker-compose.yml accordingly for mount-based init script
2025-11-03 03:36:13 +01:00
1cff5778d3 Activated debugging 2025-11-03 02:42:52 +01:00
60e2c972d6 Fix Shopware Docker build: add Redis support and align network includes
- Added symfony/redis-messenger installation with ignored build-time PHP extension checks
- Installed php83-redis in runtime stage
- Ensured consistent network includes across all Shopware services in docker-compose template
- Improves compatibility with Redis-based Symfony Messenger transport during init phase

https://chatgpt.com/share/6908068e-0bb8-800f-8855-7b3913c57158
2025-11-03 02:34:51 +01:00
637de6a190 Added network to init 2025-11-03 02:00:36 +01:00
f5efbce205 feat(shopware): migrate to single Shopware base image and split services (web/worker/scheduler/init)
• Introduce init container and runtime-ready Dockerfile (Alpine) installing php83-gd/intl/pdo_mysql
• Disable composer scripts in builder and ignore build-time ext reqs
• New docker-compose template (web/worker/scheduler/opensearch) + persistent volumes
• Use TRUSTED_PROXIES env; fix APP_URL formatting; set OPENSEARCH_HOST=opensearch
• Replace SHOPWARE_PHP_CONTAINER refs with SHOPWARE_WEB_CONTAINER in tasks
• Render and copy init.sh via volumes path
• Remove old nginx/php split and legacy DB env task
• Fix svc-db-postgres var: database_type now uses entity_name
https://chatgpt.com/share/6907fc58-7c28-800f-a993-c207f28859c9
2025-11-03 01:51:38 +01:00
d6f3618d70 Add reusable HTTP healthcheck template and integrate into Shopware and Taiga roles 2025-11-02 22:26:42 +01:00
773655efb5 Used correct image and deactivated oidc and ldap 2025-11-02 21:40:03 +01:00
7bc9f7abd9 Refactor Shopware role to use dedicated OpenSearch service and improved environment handling.
Changes include:
- Added OpenSearch configuration and variable definitions (image, version, heap, memory limits)
- Replaced legacy search/elasticsearch logic with OpenSearch integration
- Updated docker-compose template for OpenSearch with proper JVM heap and ulimits
- Ensured both OPENSEARCH_URL and ELASTICSEARCH_URL are set for compatibility

Reference: https://chatgpt.com/share/6907b0d4-ab14-800f-b576-62c0d26c8ad1
2025-11-02 21:05:52 +01:00
ec7b8662dd Implemented service name 2025-11-02 20:36:20 +01:00
d1ccfd9cdd Add new Shopware 6 role with OIDC/LDAP plugin integration and Docker-based deployment configuration.
Includes:
- New role: web-app-shopware (Docker, MariaDB, Redis, OpenSearch)
- Updated networks and ports configuration
- Automated install, migration, and admin creation
- Optional IAM integration via OIDC/LDAP plugins

Reference: https://chatgpt.com/share/6907b0d4-ab14-800f-b576-62c0d26c8ad1
2025-11-02 20:29:13 +01:00
d61c81634c Add Joomla CLI paths and implement non-interactive admin password reset via CLI
Ref: https://chatgpt.com/share/69039c22-f530-800f-a641-fd2636d5b6af
2025-10-30 18:11:18 +01:00
265f815b48 Optimized Listmonk and Nextcloud CSP for hcaptcha 2025-10-30 16:02:09 +01:00
f8e5110730 Add Redis readiness check before Nextcloud upgrade and add retry logic for maintenance repair
This prevents OCC repair failures caused by Redis still loading its dataset after container restarts.
See context: https://chatgpt.com/share/690377ba-1520-800f-b8c1-bc93fbd9232f
2025-10-30 15:36:00 +01:00
37b213f96a Refactor XWiki OIDC activation to use REST-based authenticationService update (reliable alternative to Groovy) — see ChatGPT discussion: https://chatgpt.com/share/69005d88-6bf8-800f-af41-73b0e5dc9c13 2025-10-29 11:12:19 +01:00
5ef525eac9 Optimized CSP for Gitlab 2025-10-28 08:26:53 +01:00
295ae7e477 Solved Mediawiki CPS bug whichg prevented OIDC Login 2025-10-27 20:33:07 +01:00
c67ccc1df6 Used path_join @ web-app-friendica 2025-10-26 15:48:28 +01:00
cb483f60d1 optimized for easier debugging 2025-10-25 12:52:17 +02:00
2be73502ca Solved tests 2025-10-25 11:46:36 +02:00
57d5269b07 CSP (Safari-safe): merge -elem/-attr into base; respect explicit disables; no mirror-back; header only for documents/workers
- Add CSP3 support for style/script: include -elem and -attr directives
- Base (style-src, script-src) now unions elem/attr (CSP2/Safari fallback)
- Respect explicit base disables (e.g. style-src.unsafe-inline: false)
- Hashes only when 'unsafe-inline' absent in the final base tokens
- Nginx: set CSP only for HTML/worker via header_filter_by_lua_block; drop for subresources
- Remove per-location header_filter; keep body_filter only
- Update app role flags to *-attr where appropriate; extend desktop CSS sources
- Add comprehensive unit tests for union/explicit-disable/no-mirror-back

Ref: https://chatgpt.com/share/68f87a0a-cebc-800f-bb3e-8c8ab4dee8ee
2025-10-22 13:53:06 +02:00
1eefdea050 Solved CSP errors for MiniQR 2025-10-22 12:49:22 +02:00
561160504e Add new web-app-mini-qr role
- Introduced new role 'web-app-mini-qr' to deploy the lightweight, self-hosted Mini-QR application.
- Added dedicated subnet and localhost port mapping (8059) in group_vars.
- Ensured proper dependency structure and run_once handling in MIG role.
- Included upstream reference and CSP whitelist for temporary clarity.ms removal tracking.
- Added README.md and meta information following the Infinito.Nexus web-app schema.

See: https://chatgpt.com/share/68f890ab-5960-800f-85f8-ba30bd4350fe
2025-10-22 10:07:35 +02:00
9a4bf91276 feat(nextcloud): enable custom Alpine-based Whiteboard image with Chromium & ffmpeg support
- Added role tasks to deploy templated Dockerfile for Whiteboard service
- Configured build context and custom image name (nextcloud_whiteboard_custom)
- Increased PID limits and shm_size for stable recording
- Adjusted user ID variable naming consistency
- Integrated path_join for service directory variables
- Fixed build permissions (install as root, revert to nobody)

Reference: ChatGPT conversation https://chatgpt.com/share/68f771c6-0e98-800f-99ca-9e367f4cd0c2
2025-10-21 13:44:11 +02:00
468b6e734c Deactivated whiteboar 2025-10-20 21:17:06 +02:00
83cb94b6ff Refactored Redis resource include macro and increased memory limits
- Replaced deprecated lookup(vars=...) in svc-db-redis with macro-based include (Ansible/Jinja safe)
- Redis now uses higher resource values (1 CPU, 1G reserved, 8G max, 512 pids)
- Enables stable Whiteboard operation with >3.5 GB Redis memory usage
- Related conversation: https://chatgpt.com/share/68f67a00-d598-800f-a6be-ee5987e66fba
2025-10-20 20:08:38 +02:00
6857295969 Fix variable definition test to recognize block-style Jinja 'set ... endset' statements
This update extends the regex to detect block-style variable definitions such as:
  {% set var %} ... {% endset %}
Previously, only inline 'set var =' syntax was recognized, causing false positives
like '_snippet' being flagged as undefined in Jinja templates.

Reference: https://chatgpt.com/share/68f6799a-eb80-800f-ab5c-7c196d4c4661
2025-10-20 20:04:40 +02:00
8ab398f679 nextcloud:whiteboard: wait for Redis before start (depends_on: service_healthy) to prevent early SocketClosedUnexpectedlyError
Context: added depends_on on redis for the Whiteboard service so websockets don’t crash when Redis isn’t ready yet. See discussion: https://chatgpt.com/share/68f65a3e-aa54-800f-a1a7-e6878775fd7e
2025-10-20 17:50:47 +02:00
31133ddd90 Enhancement: Fix for Nextcloud Whiteboard recording and collaboration server
- Added Chromium headless flags and writable font cache/tmp volumes
- Enabled WebSocket proxy forwarding for /whiteboard/
- Verified and adjusted CSP and frontend integration
- Added Whiteboard-related variables and volumes in main.yml

See ChatGPT conversation (20 Oct 2025):
https://chatgpt.com/share/68f655e1-fa3c-800f-b35f-4f875dfed4fd
2025-10-20 17:31:59 +02:00
783b1e152d Added numpy 2025-10-20 11:03:44 +02:00
eca567fefd Made gitea LDAP Source primary domain independent 2025-10-18 10:54:39 +02:00
905f461ee8 Add basic healthcheck to oauth2-proxy container template using binary version check for distroless compatibility
Reference: https://chatgpt.com/share/68f35550-4248-800f-9c6a-dbd49a48592e
2025-10-18 10:52:58 +02:00
9f0b259ba9 Merge branch 'master' of github.com:kevinveenbirkenbach/infinito-nexus 2025-10-18 09:41:18 +02:00
06e4323faa Added ansible environmnet 2025-10-17 23:07:43 +02:00
3d99226f37 Refactor BigBlueButton and backup task structure:
- Moved database seed variables from vars/main.yml to task-level include in BigBlueButton
- Simplified core include logic in sys-ctl-bkp-docker-2-loc
- Ensured clean conditional for BKP_DOCKER_2_LOC_DB_ENABLED
See: https://chatgpt.com/share/68f216f7-62d8-800f-94e3-c82e4418e51b (deutsch)
2025-10-17 12:14:39 +02:00
73ba09fbe2 Optimize SSH connection performance by disabling GSSAPI authentication and reverse DNS lookups
- Added 'GSSAPIAuthentication no' to prevent unnecessary Kerberos negotiation delays.
- Added 'UseDNS no' to skip reverse DNS resolution during SSH login, improving connection speed.
- Both changes improve SSH responsiveness, especially in non-domain environments.

Reference: https://chatgpt.com/share/68efc179-1a10-800f-9656-1e8731b40546
2025-10-15 18:37:09 +02:00
01ea9b76ce Enable pipelining globally and modernize SSH settings
- Activated pipelining in [defaults] for better performance.
- Replaced deprecated 'scp_if_ssh' with 'transfer_method'.
- Flattened multi-line ssh_args for compatibility.
- Verified configuration parsing as discussed in https://chatgpt.com/share/68efc179-1a10-800f-9656-1e8731b40546
2025-10-15 17:45:16 +02:00
c22acf202f Solved bugs 2025-10-15 17:03:57 +02:00
61e138c1a6 Optimize OpenLDAP container resources for up to 5k users (1.25 CPU / 1.5GB RAM / 1024 PIDs). See https://chatgpt.com/share/68ef7228-4028-800f-8986-54206a51b9c1 2025-10-15 12:06:51 +02:00
07c8e036ec Deactivated change when because its anyhow not trackable 2025-10-15 10:27:12 +02:00
0b36059cd2 feat(web-app-gitea): add optional Redis integration for caching, sessions, and queues
This update introduces conditional Redis support for Gitea, allowing connection
to either a local or centralized Redis instance depending on configuration.
Includes resource limits for the Redis service and corresponding environment
variables for cache, session, and queue backends.

Reference: ChatGPT conversation on centralized vs per-app Redis architecture (2025-10-15).
https://chatgpt.com/share/68ef5930-49c8-800f-b6b8-069e6fefda01
2025-10-15 10:20:18 +02:00
d76e384ae3 Enhance CertUtils to return the newest matching certificate and add comprehensive unit tests
- Added run_openssl_dates() to extract notBefore/notAfter timestamps.
- Modified mapping logic to store multiple cert entries per SAN with metadata.
- find_cert_for_domain() now selects the newest certificate based on notBefore and mtime.
- Exact SAN matches take precedence over wildcard matches.
- Added new unit tests (test_cert_utils_newest.py) verifying freshness logic, fallback handling, and wildcard behavior.

Reference: https://chatgpt.com/share/68ef4b4c-41d4-800f-9e50-5da4b6be1105
2025-10-15 09:21:00 +02:00
e6f4f3a6a4 feat(cli/build/defaults): ensure deterministic alphabetical sorting for applications and users
- Added sorting by application key and user key before YAML output.
- Ensures stable and reproducible file generation across runs.
- Added comprehensive unit tests verifying key order and output stability.

See: https://chatgpt.com/share/68ef4778-a848-800f-a50b-a46a3b878797
2025-10-15 09:04:39 +02:00
a80b26ed9e Moved bbb database seeding 2025-10-15 08:50:21 +02:00
45ec7b0ead Optimized include text 2025-10-15 08:39:37 +02:00
ec396d130c Optimized time schedule 2025-10-15 08:37:51 +02:00
93c2fbedd7 Added setting of timezone 2025-10-15 02:24:25 +02:00
d006f0ba5e Optimized schedule 2025-10-15 02:13:13 +02:00
dd43722e02 Raised memory for baserow 2025-10-14 21:59:10 +02:00
05d7ddc491 svc-bkp-rmt-2-loc: migrate pull script to Python + add unit tests; lock down backup-provider ACLs
- Replace Bash pull-specific-host.sh with Python pull-specific-host.py (argparse, identical logic)
- Update role vars and runner template to call python script
- Add __init__.py files for test discovery/imports
- Add unittest: tests/unit/roles/svc-bkp-rmt-2-loc/files/test_pull_specific_host.py (mocks subprocess/os/time; covers success, no types, find-fail, retry-exhaustion)
- Backup provider SSH wrapper: align allowed ls path (backup-docker-to-local)
- Split user role tasks: 01_core (sudoers), 02_permissions_ssh (SSH keys + wrapper), 03_permissions_folders (ownership + default ACLs + depth-limited chown/chmod)
- Ensure default ACLs grant rwx to 'backup' and none to group/other; keep sudo rsync working

Ref: ChatGPT discussion (2025-10-14) — https://chatgpt.com/share/68ee920a-9b98-800f-8806-ddcfe0255149
2025-10-14 20:10:49 +02:00
e54436821c Refactor sys-front-inj-all dependencies handling
Moved CDN and logout role inclusions into a dedicated '01_dependencies.yml' file for better modularity and reusability.
Added variable injection support via 'vars:' to allow flexible configuration like 'proxy_extra_configuration'.

See: https://chatgpt.com/share/68ee880d-cd80-800f-8dda-9e981631a5c7
2025-10-14 19:27:56 +02:00
ed73a37795 Improve get_app_conf robustness and add skip_missing_app parameter support
- Added new optional parameter 'skip_missing_app' to get_app_conf() in module_utils/config_utils.py to safely return defaults when applications are missing.
- Updated group_vars/all/00_general.yml and roles/web-app-nextcloud/config/main.yml to include skip_missing_app=True in all Nextcloud-related calls.
- Added comprehensive unit tests under tests/unit/module_utils/test_config_utils.py covering missing app handling, schema enforcement, nested lists, and index edge cases.

Ref: https://chatgpt.com/share/68ee6b5c-6db0-800f-bc20-d51470d7b39f
2025-10-14 17:25:37 +02:00
adff9271fd Solved rmt backup bugs 2025-10-14 16:29:42 +02:00
2f0fb2cb69 Merged network definitions before application definitions 2025-10-14 15:52:28 +02:00
6abf2629e0 Removed false - 2025-10-13 19:03:23 +02:00
6a8e0f38d8 fix(web-svc-collabora): add required Docker capabilities and resource limits for Collabora Jails
- Added security_opt (seccomp=unconfined, apparmor=unconfined) and cap_add (MKNOD, SYS_CHROOT, SETUID, SETGID, FOWNER)
  to allow Collabora's sandbox (coolmount/systemplate) to mount and chroot properly
- Increased resource limits (2 CPUs, 2 GB RAM, 2048 PIDs) to prevent document timeout and OOM issues
- Resolves 'coolmount: Operation not permitted' and systemplate performance warnings

Refs: https://chatgpt.com/share/68ed03cd-1afc-800f-904e-d1c1cb133914
2025-10-13 15:52:50 +02:00
ae618cbf19 refactor(web-app-desktop, web-app-discourse): improve initialization handling and HTTP readiness check
- Added HTTP readiness check for Desktop application to ensure all logos can be downloaded during initialization
- Introduced 'http_port' variable for better readability
- Simplified role execution structure by moving run_once inclusion into core task file
- Adjusted docker compose handler flushing behavior
- Applied consistent structure to Discourse role

See: https://chatgpt.com/share/68ed02aa-b44c-800f-a125-de8600b102d4
2025-10-13 15:48:26 +02:00
c835ca8f2c refactor(front-injection): stabilize run_once flow and explicit web-service loading
- sys-front-inj-all: load web-svc-cdn and web-svc-logout once; reinitialize inj_enabled after services; move run_once block to top; reorder injections.
- sys-front-inj-css: move run_once call into 01_core; fix app_style_present default; simplify main.
- sys-front-inj-desktop/js/matomo: deactivate per-role run_once blocks; keep utils/run_once where appropriate.
- sys-front-inj-logout: switch to files/logout.js + copy; update head_sub mtime lookup; mark set_fact tasks unchanged.
- sys-svc-cdn: inline former 01_core tasks into main; ensure shared/vendor dirs and set run_once in guarded block; remove 01_core.yml.

Rationale: prevent cascading 'false_condition: run_once_sys_svc_cdn is not defined' skips by setting run-once facts only after the necessary tasks and avoiding parent-scope guards; improves determinism and handler flushing.

Conversation: https://chatgpt.com/share/68ecfaa5-94a0-800f-b1b6-2b969074651f
2025-10-13 15:12:23 +02:00
087175a3c7 Solved mailu token bug 2025-10-13 10:50:59 +02:00
3da645f3b8 Mailu/MSMTP: split token mgmt, idempotent reload, safer guards
• Rename: 02_create-user.yml → 02_manage_user.yml; 03_create-token.yml → 03a_manage_user_token.yml + 03b_create_user_token.yml
• Only (re)run sys-svc-msmtp when no-reply token exists; set run_once_sys_svc_msmtp=true in 01_core
• Reset by setting run_once_sys_svc_msmtp=false after creating no-reply token; then include sys-svc-msmtp
• Harden when-guards (no '{{ }}' in when, safe .get lookups)
• Minor formatting and failed_when readability

Conversation: https://chatgpt.com/share/68ebd196-a264-800f-a215-3a89d0f96c79
2025-10-12 18:05:00 +02:00
a996e2190f feat(logout): wire injector to web-svc-logout and add robust CORS/CSP for /logout
- sys-front-inj-logout: depend on web-svc-logout (run-once guarded) and simplify task flow.
- web-svc-logout: align feature flags/formatting and extend CSP:
  - add cdn.jsdelivr.net to connect/script/style and quote values.
- Nginx: move CORS config into logout-proxy.conf.j2 with dynamic vars:
  - Access-Control-Allow-Origin set to canonical logout origin,
  - Allow-Credentials=true,
  - Allow-Methods=GET, OPTIONS,
  - basic headers list (Accept, Authorization),
  - cache disabled for /logout responses.
- Drop obsolete CORS var passing from 01_core.yml; headers now templated at proxy layer.

Prepares clean cross-origin logout orchestration from https://logout.veen.world.

Refs: ChatGPT discussion – https://chatgpt.com/share/68ebb75f-0170-800f-93c5-e5cb438b8ed4
2025-10-12 16:16:47 +02:00
7dccffd52d Optimized variable whitespacing 2025-10-12 14:37:45 +02:00
853f2c3e2d Added mastodon to disc space cleanup script 2025-10-12 14:37:19 +02:00
b2978a3141 Changed mailu token name 2025-10-12 14:36:13 +02:00
0e0b703ccd Added cleanup to mastodon 2025-10-12 14:03:28 +02:00
0b86b2f057 Set MODE_CLEANUP default true and solved tld localhost user name bug 2025-10-12 01:15:52 +02:00
80e048a274 Fix: make EspoCRM entrypoint POSIX-compliant and remove illegal 'pipefail' usage
The previous entrypoint script used 'set -euo pipefail', which caused runtime errors
because /bin/sh (Dash/BusyBox) does not support 'pipefail'. This commit makes the
entrypoint fully POSIX-safe, adds robust fallbacks for missing scripts, and improves
logging. Also removes a trailing newline in the navigator Docker Compose template.

Related ChatGPT discussion: https://chatgpt.com/share/68eab0b7-7a64-800f-a8aa-e7d7262a262e
2025-10-11 21:33:07 +02:00
2610aec293 Deactivated cakeday plugin because it's an onboard plugin 2025-10-11 18:23:38 +02:00
07db162368 Reformated navigation role 2025-10-11 18:04:58 +02:00
a526d1adc4 Solved Keycloak Master Email Configuration Update settings 2025-10-11 16:57:36 +02:00
ca95079111 Added Email Configuration for Keycloak Master Realm 2025-10-11 16:45:50 +02:00
e410d66cb4 Add health check for Keycloak container and grant global 'admin' realm role to permanent admin user
This update waits for the Keycloak container to become healthy before attempting login and replaces the old realm-management based role assignment with the global 'admin' realm role.
See: https://chatgpt.com/share/68e99953-e988-800f-8b82-9ffb14c11910
2025-10-11 01:40:48 +02:00
ab48cf522f fix(keycloak): make permanent admin creation idempotent and fix password command
- prevent task failure when 'User exists with same username'
- remove invalid '--temporary false' flag from set-password command
- ensure realm-admin role grant remains idempotent

See: https://chatgpt.com/share/68e99271-fdb0-800f-a8ad-11c15d02a670
2025-10-11 01:10:57 +02:00
41c12bdc12 Refactor Keycloak permanent admin creation:
- Remove jq dependency to avoid container command errors
- Use username-based operations instead of user ID lookups
- Improve idempotency and portability across environments

See: https://chatgpt.com/share/68e98e77-9b3c-800f-8393-71b0be22cb46
2025-10-11 00:54:04 +02:00
aae463b602 Optimized keycloak setup procedures 2025-10-11 00:20:27 +02:00
bb50551533 Restrucutred keycloak accounts 2025-10-10 23:40:58 +02:00
098099b41e Refactored web-app-keycloak 2025-10-10 22:45:26 +02:00
0a7d767252 Added contains to filter 2025-10-10 22:16:23 +02:00
d88599f76c Added 'to_nice_json' exception 2025-10-10 22:16:22 +02:00
4d9890406e fix(sys-ctl-hlth-csp): ensure '--' separator is added when passing ignore list to checkcsp
Updated README to reflect correct usage with '--', adjusted script.py to always append separator, and simplified task template handling for consistency.

Ref: https://chatgpt.com/share/68dfc69b-7c94-800f-871b-3525deb8e374
2025-10-03 20:50:49 +02:00
59b652958f feat(sys-ctl-hlth-csp): add support for ignoring network block domains
Introduced new variable HEALTH_CSP_IGNORE_NETWORK_BLOCKS_FROM (list, default [])
to suppress network block reports (e.g., ORB) from specific external domains.
Updated script.py to accept and forward the flag, extended systemd exec command
in tasks, added defaults, and documented usage in README.

Ref: https://chatgpt.com/share/68dfc69b-7c94-800f-871b-3525deb8e374
2025-10-03 15:23:57 +02:00
a327adf8db Removed failing healthcheck 2025-10-02 20:02:10 +02:00
7a38cb90fb Added correct resources for baserow 2025-10-02 19:59:04 +02:00
9d6cf03f5b Fix: Replace unsupported /dev/tcp healthcheck with onboard PHP socket check for websocket service
Replaced the previous shell-based /dev/tcp healthcheck with a PHP fsockopen() test to ensure compatibility with minimal base images. This avoids dependency on missing tools like nc or curl and provides a reliable onboard check.

Conversation: https://chatgpt.com/share/68deb8ec-d920-800f-bd35-2869544fe30f
2025-10-02 19:40:13 +02:00
9439ac7f76 fix(web-app-xwiki): raise XWiki container resources and align YAML formatting
- Set cpus=1.0, mem_reservation=1g, mem_limit=2g, pids_limit=1024
- Keep LTS image/tag templating and Postgres type
- Normalize spacing/alignment for readability

Reason: Tomcat/XWiki needs >1 GB; low limits caused slow boots/502 upstream not ready.
Conversation: https://chatgpt.com/share/68de5266-c8a0-800f-bfbc-de85262de53e
2025-10-02 12:22:49 +02:00
23353ac878 infra(sys-service): centralize async control + pre-deploy backup safeguard
- Added MODE_BACKUP to trigger backup before the rest of the deployment

- sys-ctl-bkp-docker-2-loc: force linear sync and force flush when MODE_BACKUP is true

- Unified name resolution via system_service_name across handlers and tasks

- Introduced system_service_force_linear_sync and system_service_force_flush (rename from system_force_flush)

- Drive async/poll via system_service_async/system_service_poll using omit when disabled

- Propagated per-role overrides (cleanup, repair, cert tasks) for clarity and safety

- Minor formatting and consistency cleanups

Why: Ensure the backup runs before the deployment routine to safeguard data integrity.

Refs: Conversation https://chatgpt.com/share/68de4c41-b6e4-800f-85cd-ce6949097b5e
Signed-off-by: Kevin Veen-Birkenbach <kevin@veen.world>
2025-10-02 11:58:23 +02:00
8beda2d45d fix(svc-db-postgres): pin Postgres version to 17-3.5, add entity_name var, and dynamically resolve major version for dev package
- Changed default Docker image version from 'latest' to '17-3.5' in config
- Introduced entity_name var for consistent lookups
- Added POSTGRES_VERSION and POSTGRES_VERSION_MAJOR extraction
- Updated Dockerfile to install postgresql-server-dev-<major> with default fallback to 'all'
- Minor YAML formatting improvements

Ref: https://chatgpt.com/share/68de40b4-2eb8-800f-ab5b-11cc873c3604
2025-10-02 11:07:17 +02:00
5773409bd7 Changed nextcloud domain to next.cloud.primary_domain 2025-10-02 09:19:32 +02:00
b3ea962338 Implemented sleeping time for server 2025-10-02 09:08:32 +02:00
b9fbf92461 proxy(cors): make ACAO opt-in; remove hardcoded default
Stop forcing Access-Control-Allow-Origin to $scheme://$host. This default broke Element (element.infinito.nexus) -> Synapse (matrix.infinito.nexus) CORS and blocked login. Now ACAO is only set when 'aca_origin' is provided; otherwise we defer to the upstream app (e.g., Synapse) to emit correct CORS headers. Also convert top comments to Jinja block comment.

Discussion & debugging details: https://chatgpt.com/share/68de2236-4aec-800f-adc5-d025922c8753
2025-10-02 08:57:14 +02:00
6824e444b0 Changed bitnami images to legacy. See https://github.com/bitnami/containers/issues/83267. 2025-10-02 07:31:20 +02:00
5cdcc18a99 Fix PeerTube OIDC plugin automation
- Store oidc_settings as proper YAML dict with correct keys
- Ensure plugin is installed only if missing
- Update DB settings as jsonb and enforce enabled/uninstalled state
- Add CLI enforcement for plugin activation
- Correct task conditions (enable/disable logic) with boolean filters

Ref: https://chatgpt.com/share/68dd1d16-9b34-800f-b2bf-a3fe058f25b1
2025-10-01 14:23:07 +02:00
e7702948b8 EspoCRM role: custom image + single data volume + runtime flag setter
• Build a custom image and replace upstream entrypoint with docker-entrypoint-custom.sh (strict fail on flag script).

• Introduce set_flags.php and wire via ESPOCRM_SET_FLAGS_SCRIPT; apply flags at container start; clear cache afterwards.

• Keep exactly one Docker volume (data:/var/www/html/); drop separate custom/extensions mounts.

• Compose: use custom image, add healthchecks & depends_on for daemon/websocket; keep service healthy gating.

• Ansible: deploy scripts, build & up via handlers; patch siteUrl as www-data; run upgrade non-fatal; always run flag setter.

• Vars/Env: add ESPO_INIT_* toggles and ESPOCRM_SET_FLAGS_SCRIPT; refactor variables for scripts & custom image paths.

Conversation context: https://chatgpt.com/share/68dd1992-020c-800f-bcf5-2db60cb4aab2
2025-10-01 14:08:09 +02:00
09a4c243d7 Add centralized include for Access-Control-Allow headers across proxy/service Nginx templates and align ACA vars for simpleicons task.
Ref: https://chatgpt.com/share/68dbf59c-f424-800f-aa27-787db52e260f
2025-09-30 17:22:28 +02:00
1d5a50abf2 Optimized path building 2025-09-30 16:36:28 +02:00
0d99c7f297 Nextcloud: refactor Talk → HPB, switch to bridge mode, and template cleanups
- Change Talk (HPB) network_mode from host → bridge and drop TURN relay range mapping
- Remove obsolete nginx restart handler; rely on 'docker compose up' notify
- Fix spreed task condition to use HPB standalone flag
- docker-compose.yml.j2: parameterize service names, use NEXTCLOUD_*_SERVICE vars, align host-gateway condition with HPB, tidy ports/expose/network blocks
- env.j2/nginx configs: rename TALK_* → HPB_* variables and locations; use templated NEXTCLOUD_SERVICE for php upstream
- vars: introduce entity_name; centralize *SERVICE keys; rename all Talk vars to HPB; adjust whiteboard keys; compute URLs/JSON configs accordingly
- spreed plugin vars: point to HPB signaling/STUN/TURN and internal secret

Ref: https://chatgpt.com/share/68db9f41-16ec-800f-9cdf-7530862f89aa
2025-09-30 12:52:15 +02:00
0a17e54d8c Nextcloud: set conservative Docker resource limits and template cleanups
- Add CPU/memory/PID limits for redis, database, proxy, cron, talk, whiteboard
- Keep nextcloud service unchanged except existing settings
- Normalize service_name templating and indentation in docker-compose.yml.j2
- Mount Janus config for Talk via volume

Ref: https://chatgpt.com/share/68db9f41-16ec-800f-9cdf-7530862f89aa
2025-09-30 11:54:14 +02:00
bf94338845 Nextcloud/Nginx: wire Talk signaling WS location via reusable snippet
Conditionally include the generic WebSocket proxy block for NEXTCLOUD_TALK_SIGNALING_ENABLED. Set location_ws to '^~ <location>' and ws_port to NEXTCLOUD_PORT, then include roles/sys-svc-proxy/templates/location/ws.conf.j2. This enables proper Upgrade/Connection headers and disables buffering for the signaling path.

Context: https://chatgpt.com/share/68db9f41-16ec-800f-9cdf-7530862f89aa
2025-09-30 11:17:54 +02:00
5d42b78b3d Nextcloud: extend CSP for Talk & disable keeporsweep
CSP: add cloud.<PRIMARY_DOMAIN> to connect-src and frame-src (both HTTP and WS) and allow worker-src 'blob:' for web workers used by Talk/Collabora.

Apps: disable keeporsweep (installation no longer possible) and document reason.

Context: https://chatgpt.com/share/68db9f41-16ec-800f-9cdf-7530862f89aa
2025-09-30 11:15:32 +02:00
26a1992d84 Nextcloud/Talk: add Janus config & fix WebSocket proxying
Nginx: define 'map $http_upgrade $connection_upgrade' once in http{} and reuse; drop duplicate map from ws_generic vhost; tidy ws location headers/spacing. Nextcloud: add WS location for standalone signaling; render & mount Janus config (NAT 1:1, ICE enforce/ignore lists, libnice hardening); extend CSP (connect-src/frame-src for cloud & collabora, worker-src blob:); disable keeporsweep app; replace nginx reload handler with compose up; add NEXTCLOUD_HOST_JANUS_CONF_PATH and related vars.

Context: https://chatgpt.com/share/68db9f41-16ec-800f-9cdf-7530862f89aa
2025-09-30 11:14:15 +02:00
2439beb95a Added correct minio http statuscodes 2025-09-29 17:29:29 +02:00
251f7b227d Add healthchecks for all Taiga services, fix RabbitMQ env var names, and define TAIGA_HOSTNAME
Details:
- Implemented healthchecks for taiga, async, rabbitmq, front, events, protected, and gateway
- Corrected RabbitMQ env variables (RABBITMQ_DEFAULT_USER/PASS/VHOST/ERLANG_COOKIE)
- Added TAIGA_HOSTNAME for backend service

See: https://chatgpt.com/share/68da9d6b-b164-800f-bcb7-410b40219a1e
2025-09-29 17:09:42 +02:00
3fbb9c38a8 Solved coturn volume bug 2025-09-29 15:33:50 +02:00
29e8b3a590 Deactivated recording for Big Blue Button 2025-09-29 15:23:48 +02:00
27b89d8fb6 Taiga: refactor service naming & resource limits
Add CPU/memory/pids limits for taiga, async, front, gateway, events, async-rabbitmq, events-rabbitmq, manager, and protected. Align manager service usage (was taiga-manage) in admin tasks and inits compose. Switch to variable-driven service names (TAIGA_* vars), add container_name patterns, normalize volume mappings via TAIGA_VOLUME_STATIC/MEDIA, fix depends_on to use TAIGA_* vars, and set RabbitMQ hostnames from vars. Remove obsolete Development.md.

Conversation reference: https://chatgpt.com/share/68da83b7-0cb4-800f-9702-d8a2d4ebea71  (replace with this chat’s share link)
2025-09-29 15:04:12 +02:00
55f2d15e93 Fix coturn container/volume separation: use COTURN_CONTAINER for container_name and map COTURN_VOLUME to /var/lib/coturn
Details:
- Removed anonoumys volume
- Renamed container_name variable to COTURN_CONTAINER
- Added dedicated COTURN_VOLUME with _data suffix for persistence
- Mount COTURN_VOLUME into /var/lib/coturn

Reference: https://chatgpt.com/share/68da6f12-b238-800f-932b-e37c8a50dddd
2025-09-29 13:36:30 +02:00
aa19a97ed6 CORS/CSP hardening & centralization
- Add reusable Nginx include: roles/sys-svc-proxy/templates/headers/access_control_allow.conf.j2
  (dynamic ACAO/credentials/methods/headers via role vars)
- Set global 'Vary: Origin' in nginx.conf.j2 to prevent cache poisoning
- CSP: allow Simple Icons via connect-src when feature is enabled
- Front proxy: rename vars to lowercase + flush handlers after config deploy
- Desktop: gate & load Simple Icons role; inject brand logos when enabled
- Bluesky + Logout: replace inline CORS with centralized include
- Simpleicons: public CORS (ACAO='*', no credentials), keep GET/OPTIONS, allow headers
- Taiga: adjust canonical domain to taiga.kanban.{{ PRIMARY_DOMAIN }}
- LibreTranslate: remove unused images/versions keys

Fixes: https://open.project.infinito.nexus/projects/cymais/work_packages/342/activity
Discussion: https://chatgpt.com/share/68da5e27-ffd4-800f-91a3-0ef103058d44
2025-09-29 12:23:58 +02:00
c06d1c4d17 Refactor yay update handling:
- Move AUR update task into dev-yay role
- Centralize defaults (AUR_HELPER, AUR_BUILDER_USER, etc.)
- Remove separate update-yay role (redundant)

See conversation with ChatGPT https://chatgpt.com/share/68da3219-6d78-800f-92ad-0a5061bac8be and related work item:
https://open.project.infinito.nexus/projects/cymais/work_packages/341/activity
2025-09-29 09:16:02 +02:00
66f294537d Replaced fixed 'web' service call for exec with 'ESPOCRM_SERVICE' variable for exec call 2025-09-28 15:52:44 +02:00
a9097a3ec3 web-app-espocrm: add resource limits, init/stop settings and cleanups
- Added CPU, memory and PID limits for espocrm, daemon and websocket services
- Enabled init process and graceful stop (SIGTERM, 30s) in docker-compose
- Adjusted env template (removed forced True/default flags)
- Introduced entity_name/ESPOCRM_SERVICE in vars for service naming
- Minor cleanup of get_app_conf defaults

Ref: https://chatgpt.com/share/68d937ce-9c34-800f-9136-54baed9c91c7
2025-09-28 15:50:28 +02:00
fc59c64273 Nextcloud Talk: fix virtual-background web check by
- adding explicit MIME types for .wasm and .tflite in internal Nginx
- relaxing CSP (script-src: allow 'unsafe-eval') for WebAssembly
- removing obsolete turnserver draft.
Details: https://chatgpt.com/share/68d7dd39-50b8-800f-ab59-cfb1d3cf07cb
2025-09-27 14:49:42 +02:00
dbbb3510f3 Refactor TURN/STUN config:
- Removed ?transport=udp from Nextcloud Talk TURN server definitions
- Dropped --no-tcp-relay to allow TCP fallback
- Removed invalid UDP mapping on TLS port
- Introduced switch between REST secret auth and lt-cred-mech via COTURN_USER_AUTH_ENABLED
- Added user_auth_enabled flag in coturn config for flexibility

See: https://chatgpt.com/share/68d7d601-3558-800f-bc84-00d7e8fc3243
2025-09-27 14:18:29 +02:00
eb3bf543a4 Removed turn and stun protocol prefix 2025-09-27 13:58:46 +02:00
4f5602c791 Nextcloud Talk: fix TURN/STUN config
- Removed duplicate Admin Manual link in README
- Fixed turnserver.config.php draft return syntax
- Unified onboard port handling in docker-compose and env
- Updated vars to define STUN/TURN configs with correct schemas
- Ensured spreed plugin config serializes clean JSON arrays

Ref: https://chatgpt.com/share/68d7cfa2-7378-800f-9ecf-09b6bb768f13
2025-09-27 13:51:17 +02:00
75d476267e Optimized Nextcloud variables 2025-09-27 12:14:57 +02:00
c3e5db7f2e Cleaned up LDAP entries to keep it more clean 2025-09-27 11:30:39 +02:00
dfd2d243b7 Enabled recordings for BBB because https://github.com/bigbluebutton/bigbluebutton/issues/9202 was solved 2025-09-27 11:28:07 +02:00
78ad2ea4b6 nextcloud(spreed): output valid JSON via to_json for signaling/stun/turn; keep internal_secret plain https://chatgpt.com/share/68d75f71-6de8-800f-854c-207771c8d883 2025-09-27 05:52:32 +02:00
c362e160fc Nextcloud: switch Talk to host networking; update proxy routing and compose; centralize Talk secrets & spreed config; remove Greenlight block
Conversation: https://chatgpt.com/share/68d74e25-c068-800f-ae20-d0e34ac8ee12
2025-09-27 05:03:48 +02:00
a044028e03 Nextcloud Talk integration cleanup: unify secrets and signaling config
- Replace inline get_app_conf secrets in env.j2 with dedicated vars (TURN, signaling, internal)
- Correctly model signaling_servers as object {servers, secret} in spreed.yml
- Use UDP stun_turn port instead of TLS for transport=udp
- Add fallback logic for standalone Coturn role in main.yml
- Remove obsolete Greenlight section from BBB override

Ref: https://chatgpt.com/share/68d74e25-c068-800f-ae20-d0e34ac8ee12
2025-09-27 04:39:11 +02:00
7405883b48 BigBlueButton & Nextcloud:
- Switch to custom BBB Docker repository
- Externalize Coturn and Collabora by default
- Add dedicated 03_dependencies.yml for dependency handling
- Improve env templating with lowercased feature flags
- Add conditional healthcheck for Greenlight
- Refactor TURN/STUN/relay handling with role variable _BBB_COTURN_ROLE
- Extend Collabora/Greenlight dependency wiring in override file
- Nextcloud Talk: refine vars and enable/disable logic with separate plugin/service flags, add network_mode support and conditional nginx proxy block

Ref: https://chatgpt.com/share/68d741ff-a544-800f-9e81-a565e0bab0eb
2025-09-27 03:46:57 +02:00
85db0a40db Refactor Coturn port configuration: unify STUN and TURN into stun_turn and stun_turn_tls, update vars, docker-compose template, and add robust healthcheck [https://chatgpt.com/share/68d73a2d-ef34-800f-90d2-1628822ca541] 2025-09-27 03:14:53 +02:00
8af39c32ec Override docker conf variables from parents 2025-09-27 02:41:13 +02:00
31e86ac0fc Optimized networks 2025-09-27 02:21:19 +02:00
4d223f1784 feat(web-svc-coturn): add configurable network_mode (default host) and adjust credential generation
- Introduced `COTURN_NETWORK_MODE` to support both host and bridge modes
- Updated docker-compose template to skip port publishing in host mode
- Changed user_password credential algorithm to random_hex for stronger randomness
- Set default network_mode: host in config

Ref: https://chatgpt.com/share/68d72a50-c36c-800f-9367-32c4ae520000
2025-09-27 02:05:48 +02:00
926def3d01 web-svc-coturn: Add resource limits and fix docker-compose template
- Set CPU, memory reservation/limit, and PID limit for coturn
- Ensure docker_compose_file_creation_enabled and disable git repo pulling
- Move certificate mounts to volumes and fix env var interpolation in command
- Correct realm and user formatting

See: https://chatgpt.com/share/66f65f18-799c-800a-95f4-b6b26511e9cb
2025-09-27 01:40:37 +02:00
083b7d2914 Refactor relay port ranges: set coturn to 20000–39999, BigBlueButton to 40000–49999, and Nextcloud to 50000–59999
See: https://chatgpt.com/share/68d6f6c2-f7c0-800f-bc2e-10876afff4a8
2025-09-26 22:26:44 +02:00
73a38e0b2b Refactor TURN/STUN handling:
- Split internal/external Coturn for BBB and Nextcloud
- Added dedicated relay port ranges per app
- Updated env and compose overrides for coturn
- Ensure coturn role is loaded conditionally
- Standardize credential/env passing for coturn
@See https://chatgpt.com/share/68d6f376-4878-800f-b4f7-62822caa49ea
2025-09-26 22:11:55 +02:00
e3c0880e98 Fix semi-stateless run_once label and load both docker-compose & openresty handlers
See: https://chatgpt.com/share/68d6f2f3-59a4-800f-b20e-ed1c7df9e2ff
2025-09-26 22:09:39 +02:00
a817d964e4 refactor(front-stack): introduce sys-stk-front-base and semi-stateless stack; improve coturn role docs
- Extract common HTTPS + Cloudflare + handler bootstrap into new role sys-stk-front-base
- Update sys-stk-front-proxy, web-svc-cdn, web-svc-file, web-svc-html to depend on sys-stk-front-base
- Add new sys-stk-semi-stateless role combining front-base + back-stateless
- Update web-svc-coturn to use sys-stk-semi-stateless and rewrite README/meta with detailed Coturn description
- Unify sys-util-csp-cert README heading

Ref: ChatGPT conversation https://chatgpt.com/share/68d6cea2-3570-800f-acb3-c3277317f17b
2025-09-26 20:25:53 +02:00
7572134e9d Removed leftover 2025-09-26 19:39:38 +02:00
97af4990aa refactor(webserver): rename roles and update references
- Rename sys-svc-webserver -> sys-svc-webserver-core
- Rename sys-stk-front-pure -> sys-svc-webserver-https
- Update includes, run_once flags, and docs across:
  * sys-ctl-mtn-cert-renew
  * sys-front-inj-*
  * sys-stk-front-proxy
  * sys-svc-certs
  * sys-svc-cln-domains
  * web-opt-rdr-*
  * web-svc-*
- Remove redundant webserver include in web-opt-rdr-www
- Fix documentation links

Ref: ChatGPT conversation https://chatgpt.com/share/68d6cea2-3570-800f-acb3-c3277317f17b
2025-09-26 19:34:42 +02:00
b6d0535173 Cleaned up comment 2025-09-26 18:53:46 +02:00
27d33435f8 fix(bbb): align TURN/STUN configuration with shared coturn service
- added entity_name to vars for consistent docker.service lookup
- switched docker_repository_* vars to use entity_name dynamically
- introduced BBB_TURN_DOMAIN, BBB_TURN_PORT, and BBB_STUN_PORT
  → fallback to web-svc-coturn when BBB_COTURN_ENABLED is false
- updated env.j2 to use new BBB_TURN_* vars instead of hardcoded domain/ports
- cleaned up obsolete comments and spacing

Conversation: https://chatgpt.com/share/68d6c4a8-d524-800f-9592-e8a3407cd721
2025-09-26 18:53:21 +02:00
3cc4014edf feat(coturn): add dedicated web-svc-coturn role with schema, ports, network, and docker-compose template
- registered subnet 192.168.104.48/28 for coturn in group_vars/all/09_networks.yml
- defined public ports for stun/turn and relay port range in group_vars/all/10_ports.yml
- removed obsolete TODO.md and env.j2 from role
- added schema/main.yml with credentials validation (user_password, auth_secret)
- refactored tasks to load sys-stk-back-stateless instead of sys-stk-full-stateful
- implemented docker-compose.yml.j2 with auth-secret + lt-cred-mech and TLS config
- restructured vars/main.yml with docker, ports, credentials, and certificates
- updated config/main.yml.j2 with canonical domain and service definitions

Conversation: https://chatgpt.com/share/68d6c4a8-d524-800f-9592-e8a3407cd721
2025-09-26 18:52:13 +02:00
63da669c33 Removed unnecessary 'sys-svc-proxy' wrapper tasks 2025-09-26 18:50:12 +02:00
fb04a4c7a0 Restructured Front Proxy variables 2025-09-26 18:43:09 +02:00
2968ac7f0a Removed unnecessary sys-svc-webserver import 2025-09-26 18:22:46 +02:00
1daa53017e Refactor BigBlueButton role:
- Aligned schema/main.yml credential definitions with consistent spacing
- Changed PostgreSQL secret to use random_hex_32 instead of bcrypt
- Improved administrator creation logic in tasks/02_administrator.yml:
  * First try with primary password
  * Retry with starred password if OIDC is enabled
  * Fallback to user:set_admin_role if both fail
See: https://chatgpt.com/share/68d6aa34-19cc-800f-828a-a5121fda589f
2025-09-26 16:59:28 +02:00
9082443753 Refactor docker compose exec usage
Introduce centralized variables:
- docker_compose_command_base
- docker_compose_command_exec

Replaced hardcoded 'docker compose exec' with '{{ docker_compose_command_exec }}'
across multiple roles (BigBlueButton, EspoCRM, Friendica, Listmonk, Mailu, Matrix, OpenProject).
Ensures consistent environment file loading and reduces duplicated code.

Details: https://chatgpt.com/share/68d6a276-19d0-800f-839d-d191d97f7c41
2025-09-26 16:26:17 +02:00
bcee1fecdf feat(inventory): add random_hex_32 generator
feat(bbb/schema): auto-generate etherpad_api_key; set fsesl_password to alphanumeric_32
test(unit): add InventoryManager tests (Option B) expecting feature-generated creds as plain strings
docs: full autocreation of credentials for BigBlueButton now enabled
See: https://chatgpt.com/share/68d69ee8-3fd4-800f-9209-60026b338934
2025-09-26 16:11:05 +02:00
0602148caa bbb: pin mediasoup to IPv4-only and single worker via compose override
Set MS_WORKERS=1, MS_ENABLE_IPV6=false, and MS_WEBRTC_LISTEN_IPS to announce only EXTERNAL_IPv4 for webrtc-sfu. Helps avoid mediasoup router init issues seen when IPv6 is present.

Context/conversation: https://chatgpt.com/share/68d69a0e-22b0-800f-890b-13721a35f51b
2025-09-26 15:50:28 +02:00
cbfb991e79 Hardened BBB Version 2025-09-26 15:21:01 +02:00
fa7b1400bd Created mail account for blackhole to prevent delivery failure messages 2025-09-26 15:11:33 +02:00
c7cae93597 Optimized IP6 deactivation 2025-09-26 13:46:55 +02:00
6ea0d09f14 bbb: WIP—stabilize env/compose wiring & prep SFU override
Context: debugging mediasoup/WebRTC failures caused by empty/interpolated vars (EXTERNAL_IPv4, etc.).
- Normalize config/main.yml (ip6_enabled flag, enable greenlight/coturn) and tidy formatting.
- Extend vars/main.yml with BBB_* switches (IPv6, Greenlight, Coturn), TURN/Coturn cert paths.
- env.j2: wire secrets & toggles, guard IPv6 via BBB_IP6_ENABLED, switch LDAP/OIDC to role flags, add TURN/STUN, and general cleanup.
- tasks/main.yml: use BBB_* fact names, robust path joins, write docker-compose.override.yml, and notify compose on env/override changes.
- tasks/01_docker-compose.yml: reference new BBB_DOCKER_COMPOSE_* facts.
- Add templates/docker-compose.override.yml.j2 (placeholder for SFU overrides to avoid bad defaults during runs).
Rationale: make Compose brings deterministic (no empty ), paving the way to set MS_WEBRTC_LISTEN_IPS in override without risk.

Chat reference: debugging thread with GPT-5 Thinking on 2025-09-26 https://chatgpt.com/share/68d59d98-4388-800f-a627-07b6a603d0b2.
2025-09-26 12:49:12 +02:00
5e4cda0ac9 Documented docker-compose.override.yml 2025-09-26 12:14:08 +02:00
1d29617f85 Added creation of docker-compose.override.yml file 2025-09-26 12:03:47 +02:00
7c5ad8e6a1 Optimized XWIKI Nextcloud Bridge 2025-09-26 09:35:14 +02:00
a26538d1b3 web-app-openproject: upgrade to OpenProject 15
- bumped image version from 14 to 15
- removed dedicated migration task (now handled by upstream entrypoints)
- renamed tasks for cleaner numbering:
  * 02_settings.yml → 01_settings.yml
  * 03_ldap.yml → 02_ldap.yml
  * 04_admin.yml → 03_admin.yml

Ref: https://chatgpt.com/share/68d57770-2430-800f-ae53-e7eda6993a8d
2025-09-25 19:39:45 +02:00
f55b0ca797 web-app-openproject: migrate from OpenProject 13 to 14
- updated base image from openproject/community:13 to openproject/openproject:14
- added dedicated migration task (db:migrate + schema cache clear)
- moved settings, ldap, and admin tasks to separate files
- adjusted docker-compose template to use OPENPROJECT_WEB_SERVICE / OPENPROJECT_SEEDER_SERVICE variables
- replaced postinstall.sh with precompile-assets.sh
- ensured depends_on uses variable-based service names

Ref: https://chatgpt.com/share/68d57770-2430-800f-ae53-e7eda6993a8d
2025-09-25 19:10:46 +02:00
6f3522dc28 fix(csp): resolve all CSP-related issues and extend webserver health checks
- Added _normalize_codes to support lists of valid HTTP status codes
- Updated web_health_expectations to handle multiple codes, deduplication, and fallback logic
- Extended unit tests with coverage for list/default combinations, invalid values, and alias behavior
- Fixed Flowise CSP flags and whitelist entries
- Adjusted Flowise, MinIO, and Pretix docker service resource limits
- Updated docker-compose templates with explicit service_name
- Corrected MinIO status_codes to 301 redirects

 All CSP errors fixed

See details: https://chatgpt.com/share/68d557ad-fc10-800f-b68b-0411d20ea6eb
2025-09-25 18:05:41 +02:00
5186eb5714 Optimized OpenProject and CSP rules 2025-09-25 14:47:28 +02:00
73bcdcaf45 Deactivated proxying of bluesky web domain 2025-09-25 13:31:18 +02:00
9e402c863f Optimized Bleusky API redirect domain 2025-09-25 13:29:45 +02:00
84865d61b8 Install swapfile tool correct 2025-09-25 13:16:13 +02:00
423850d3e6 Refactor svc-opt-swapfile role: move core logic into 01_core.yml, simplify tasks/main.yml, and integrate swapfile setup into sys-svc-docker/01_core.yml to prevent OOM failures. See https://chatgpt.com/share/68d518f2-ba0c-800f-8a3a-c6b045763ac6 2025-09-25 12:27:13 +02:00
598f4e854a Increase OpenProject container resources
- Raised web service to 3 CPUs, 3–4 GB RAM, 2048 pids
- Raised worker service to 2 CPUs, 2–3 GB RAM, 2048 pids
- Increased cache mem_reservation to 512m
- Adjusted formatting for proxy service

Ref: https://chatgpt.com/share/68d513c1-8c10-800f-bf57-351754e3f5c2
2025-09-25 12:05:03 +02:00
1f99a6b84b Refactor: force early evaluation of BlueSky redirect_domain_mappings before include_role
Ensures that redirect_domain_mappings is resolved via set_fact
before passing it into the web-opt-rdr-domains role.
See: https://chatgpt.com/share/68d51125-14f4-800f-be6a-a7be3faeb028
2025-09-25 11:55:13 +02:00
189aaaa9ec Deactivated OpenProject LDAP Administrator Flag 2025-09-25 11:10:46 +02:00
ca52dcda43 Refactor OpenProject role:
- Add CPU, memory and PID limits to all services in config/main.yml to prevent OOM
- Replace old LDAP admin bootstrap with new 02_admin.yml using OPENPROJECT_ADMINISTRATOR_* vars
- Standardize variable names (uppercase convention)
- Fix HTTPS/HSTS port check (443 instead of 433)
- Allow docker_restart_policy override in base.yml.j2
- Cleanup redundant LDAP admin runner in 01_ldap.yml
See: https://chatgpt.com/share/68d40c6e-ab9c-800f-a4a0-d9338d8c1b32
2025-09-24 17:22:47 +02:00
4f59e8e48b Added cdn.jsdelivr.net to connect-src for web-app-desktop 2025-09-24 15:35:11 +02:00
a993c153dd fix(docker-container): ensure service_name and context are passed correctly to resource.yml.j2 by switching from lookup() to include with indent filter
Ref: https://chatgpt.com/share/68d3db3d-b6b4-800f-be4b-24ac50005552
2025-09-24 13:51:44 +02:00
8d6ebb4693 Mailu/Redis: add explicit service resource limits & clamav_db volume
- use lookup(template) for redis resource injection
- add cpus/mem/pids configs for all Mailu services
- switch antivirus to dedicated clamav_db volume
- add MAILU_CLAMAV_VOLUME var
- cleanup set service_name per service in docker-compose template
https://chatgpt.com/share/68d3d69b-06f0-800f-8c4d-4a74471ab961
2025-09-24 13:31:54 +02:00
567babfdfc Fix CPU resource calculation by enforcing a minimum of 0.5 cores per container using list-based max filter. See: https://chatgpt.com/share/68d3d645-e4c4-800f-8910-b6b27bb408e7 2025-09-24 13:30:32 +02:00
18e5f001d0 Mailu: disable hardened_malloc LD_PRELOAD (set to empty) to prevent /proc/cpuinfo PermissionError in socrate startup
Details: https://chatgpt.com/share/68d3ba3b-783c-800f-bf3d-0b0ef1296f93
2025-09-24 11:31:44 +02:00
7d9cb5820f feat(jvm): add robust JVM sizing filters and apply across Confluence/Jira
Introduce filter_plugins/jvm_filters.py with jvm_max_mb/jvm_min_mb. Derive Xmx/Xms from docker mem_limit/mem_reservation using safe rules: Xmx=min(70% limit, limit-1024MB, 12288MB), floored at 1024MB; Xms=min(Xmx/2, reservation, Xmx), floored at 512MB. Parse human-readable sizes (k/m/g/t) with binary units.

Wire filters into roles: set JVM_MINIMUM_MEMORY/JVM_MAXIMUM_MEMORY via filters; stop relying on host RAM. Keep env templates simple and stable.

Add unit tests under tests/unit/filter_plugins/test_jvm_filters.py covering typical sizes, floors, caps, invalid inputs, and entity-name derivation.

Ref: https://chatgpt.com/share/68d3b9f6-8d18-800f-aa8d-8a743ddf164d
2025-09-24 11:29:40 +02:00
369 changed files with 6140 additions and 1829 deletions

View File

@@ -1,5 +1,6 @@
[defaults]
# --- Performance & Behavior ---
pipelining = True
forks = 25
strategy = linear
gathering = smart
@@ -14,19 +15,14 @@ stdout_callback = yaml
callbacks_enabled = profile_tasks,timer
# --- Plugin paths ---
filter_plugins = ./filter_plugins
filter_plugins = ./filter_plugins
lookup_plugins = ./lookup_plugins
module_utils = ./module_utils
[ssh_connection]
# Multiplexing: safer socket path in HOME instead of /tmp
ssh_args = -o ControlMaster=auto -o ControlPersist=20s -o ControlPath=~/.ssh/ansible-%h-%p-%r \
-o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o StrictHostKeyChecking=accept-new \
-o PreferredAuthentications=publickey,password,keyboard-interactive
# Pipelining boosts speed; works fine if sudoers does not enforce "requiretty"
ssh_args = -o ControlMaster=auto -o ControlPersist=20s -o ControlPath=~/.ssh/ansible-%h-%p-%r -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o StrictHostKeyChecking=accept-new -o PreferredAuthentications=publickey,password,keyboard-interactive
pipelining = True
scp_if_ssh = smart
transfer_method = smart
[persistent_connection]
connect_timeout = 30

View File

@@ -83,6 +83,13 @@ class DefaultsGenerator:
print(f"Error during rendering: {e}", file=sys.stderr)
sys.exit(1)
# Sort applications by application key for stable output
apps = result.get("defaults_applications", {})
if isinstance(apps, dict) and apps:
result["defaults_applications"] = {
k: apps[k] for k in sorted(apps.keys())
}
# Write output
self.output_file.parent.mkdir(parents=True, exist_ok=True)
with self.output_file.open("w", encoding="utf-8") as f:

View File

@@ -220,6 +220,10 @@ def main():
print(f"Error building user entries: {e}", file=sys.stderr)
sys.exit(1)
# Sort users by key for deterministic output
if isinstance(users, dict) and users:
users = OrderedDict(sorted(users.items()))
# Convert OrderedDict into plain dict for YAML
default_users = {'default_users': users}
plain_data = dictify(default_users)

View File

@@ -10,9 +10,23 @@ from module_utils.config_utils import get_app_conf
from module_utils.get_url import get_url
def _dedup_preserve(seq):
"""Return a list with stable order and unique items."""
seen = set()
out = []
for x in seq:
if x not in seen:
seen.add(x)
out.append(x)
return out
class FilterModule(object):
"""
Custom filters for Content Security Policy generation and CSP-related utilities.
Jinja filters for building a robust, CSP3-aware Content-Security-Policy header.
Safari/CSP2 compatibility is ensured by merging the -elem/-attr variants into the base
directives (style-src, script-src). We intentionally do NOT mirror back into -elem/-attr
to allow true CSP3 granularity on modern browsers.
"""
def filters(self):
@@ -61,11 +75,14 @@ class FilterModule(object):
"""
Returns CSP flag tokens (e.g., "'unsafe-eval'", "'unsafe-inline'") for a directive,
merging sane defaults with app config.
Default: 'unsafe-inline' is enabled for style-src and style-src-elem.
Defaults:
- For styles we enable 'unsafe-inline' by default (style-src, style-src-elem, style-src-attr),
because many apps rely on inline styles / style attributes.
- For scripts we do NOT enable 'unsafe-inline' by default.
"""
# Defaults that apply to all apps
default_flags = {}
if directive in ('style-src', 'style-src-elem'):
if directive in ('style-src', 'style-src-elem', 'style-src-attr'):
default_flags = {'unsafe-inline': True}
configured = get_app_conf(
@@ -76,7 +93,6 @@ class FilterModule(object):
{}
)
# Merge defaults with configured flags (configured overrides defaults)
merged = {**default_flags, **configured}
tokens = []
@@ -131,77 +147,148 @@ class FilterModule(object):
):
"""
Builds the Content-Security-Policy header value dynamically based on application settings.
- Flags (e.g., 'unsafe-eval', 'unsafe-inline') are read from server.csp.flags.<directive>,
with sane defaults applied in get_csp_flags (always 'unsafe-inline' for style-src and style-src-elem).
- Inline hashes are read from server.csp.hashes.<directive>.
- Whitelists are read from server.csp.whitelist.<directive>.
- Inline hashes are added only if the final tokens do NOT include 'unsafe-inline'.
Key points:
- CSP3-aware: supports base/elem/attr for styles and scripts.
- Safari/CSP2 fallback: base directives (style-src, script-src) always include
the union of their -elem/-attr variants.
- We do NOT mirror back into -elem/-attr; finer CSP3 rules remain effective
on modern browsers if you choose to use them.
- If the app explicitly disables a token on the *base* (e.g. style-src.unsafe-inline: false),
that token is removed from the merged base even if present in elem/attr.
- Inline hashes are added ONLY if that directive does NOT include 'unsafe-inline'.
- Whitelists/flags/hashes read from:
server.csp.whitelist.<directive>
server.csp.flags.<directive>
server.csp.hashes.<directive>
- “Smart defaults”:
* internal CDN for style/script elem and connect
* Matomo endpoints (if feature enabled) for script-elem/connect
* Simpleicons (if feature enabled) for connect
* reCAPTCHA (if feature enabled) for script-elem/frame-src
* frame-ancestors extended for desktop/logout/keycloak if enabled
"""
try:
directives = [
'default-src', # Fallback source list for content types not explicitly listed
'connect-src', # Allowed URLs for XHR, WebSockets, EventSource, fetch()
'frame-ancestors', # Who may embed this page
'frame-src', # Sources for nested browsing contexts (e.g., <iframe>)
'script-src', # Sources for script execution
'script-src-elem', # Sources for <script> elements
'style-src', # Sources for inline styles and <style>/<link> elements
'style-src-elem', # Sources for <style> and <link rel="stylesheet">
'font-src', # Sources for fonts
'worker-src', # Sources for workers
'manifest-src', # Sources for web app manifests
'media-src', # Sources for audio and video
'default-src',
'connect-src',
'frame-ancestors',
'frame-src',
'script-src',
'script-src-elem',
'script-src-attr',
'style-src',
'style-src-elem',
'style-src-attr',
'font-src',
'worker-src',
'manifest-src',
'media-src',
]
parts = []
tokens_by_dir = {}
explicit_flags_by_dir = {}
for directive in directives:
# Collect explicit flags (to later respect explicit "False" on base during merge)
explicit_flags = get_app_conf(
applications,
application_id,
'server.csp.flags.' + directive,
False,
{}
)
explicit_flags_by_dir[directive] = explicit_flags
tokens = ["'self'"]
# 1) Load flags (includes defaults from get_csp_flags)
# 1) Flags (with sane defaults)
flags = self.get_csp_flags(applications, application_id, directive)
tokens += flags
# 2) Allow fetching from internal CDN by default for selected directives
if directive in ['script-src-elem', 'connect-src', 'style-src-elem']:
# 2) Internal CDN defaults for selected directives
if directive in ('script-src-elem', 'connect-src', 'style-src-elem', 'style-src'):
tokens.append(get_url(domains, 'web-svc-cdn', web_protocol))
# 3) Matomo integration if feature is enabled
if directive in ['script-src-elem', 'connect-src']:
# 3) Matomo (if enabled)
if directive in ('script-src-elem', 'connect-src'):
if self.is_feature_enabled(applications, matomo_feature_name, application_id):
tokens.append(get_url(domains, 'web-app-matomo', web_protocol))
# 4) ReCaptcha integration (scripts + frames) if feature is enabled
# 4) Simpleicons (if enabled) typically used via connect-src (fetch)
if directive == 'connect-src':
if self.is_feature_enabled(applications, 'simpleicons', application_id):
tokens.append(get_url(domains, 'web-svc-simpleicons', web_protocol))
# 5) reCAPTCHA (if enabled) scripts + frames
if self.is_feature_enabled(applications, 'recaptcha', application_id):
if directive in ['script-src-elem', 'frame-src']:
if directive in ('script-src-elem', 'frame-src'):
tokens.append('https://www.gstatic.com')
tokens.append('https://www.google.com')
# 5) Frame ancestors handling (desktop + logout support)
# 6) Frame ancestors (desktop + logout)
if directive == 'frame-ancestors':
if self.is_feature_enabled(applications, 'desktop', application_id):
# Allow being embedded by the desktop app domain (and potentially its parent)
# Allow being embedded by the desktop app domain's site
domain = domains.get('web-app-desktop')[0]
sld_tld = ".".join(domain.split(".")[-2:]) # e.g., example.com
tokens.append(f"{sld_tld}")
if self.is_feature_enabled(applications, 'logout', application_id):
# Allow embedding via logout proxy and Keycloak app
tokens.append(get_url(domains, 'web-svc-logout', web_protocol))
tokens.append(get_url(domains, 'web-app-keycloak', web_protocol))
# 6) Custom whitelist entries
# 7) Custom whitelist
tokens += self.get_csp_whitelist(applications, application_id, directive)
# 7) Add inline content hashes ONLY if final tokens do NOT include 'unsafe-inline'
# (Check tokens, not flags, to include defaults and later modifications.)
# 8) Inline hashes (only if this directive does NOT include 'unsafe-inline')
if "'unsafe-inline'" not in tokens:
for snippet in self.get_csp_inline_content(applications, application_id, directive):
tokens.append(self.get_csp_hash(snippet))
# Append directive
parts.append(f"{directive} {' '.join(tokens)};")
tokens_by_dir[directive] = _dedup_preserve(tokens)
# 8) Static img-src directive (kept permissive for data/blob and any host)
# ----------------------------------------------------------
# CSP3 families → ensure CSP2 fallback (Safari-safe)
# Merge style/script families so base contains union of elem/attr.
# Respect explicit disables on the base (e.g. unsafe-inline=False).
# Do NOT mirror back into elem/attr (keep granularity).
# ----------------------------------------------------------
def _strip_if_disabled(unioned_tokens, explicit_flags, name):
"""
Remove a token (e.g. 'unsafe-inline') from the unioned token list
if it is explicitly disabled in the base directive flags.
"""
if isinstance(explicit_flags, dict) and explicit_flags.get(name) is False:
tok = f"'{name}'"
return [t for t in unioned_tokens if t != tok]
return unioned_tokens
def merge_family(base_key, elem_key, attr_key):
base = tokens_by_dir.get(base_key, [])
elem = tokens_by_dir.get(elem_key, [])
attr = tokens_by_dir.get(attr_key, [])
union = _dedup_preserve(base + elem + attr)
# Respect explicit disables on the base
explicit_base = explicit_flags_by_dir.get(base_key, {})
# The most relevant flags for script/style:
for flag_name in ('unsafe-inline', 'unsafe-eval'):
union = _strip_if_disabled(union, explicit_base, flag_name)
tokens_by_dir[base_key] = union # write back only to base
merge_family('style-src', 'style-src-elem', 'style-src-attr')
merge_family('script-src', 'script-src-elem', 'script-src-attr')
# ----------------------------------------------------------
# Assemble header
# ----------------------------------------------------------
parts = []
for directive in directives:
if directive in tokens_by_dir:
parts.append(f"{directive} {' '.join(tokens_by_dir[directive])};")
# Keep permissive img-src for data/blob + any host (as before)
parts.append("img-src * data: blob:;")
return ' '.join(parts)

View File

@@ -20,9 +20,10 @@ def get_docker_paths(application_id: str, path_docker_compose_instances: str) ->
'config': f"{base}config/",
},
'files': {
'env': f"{base}.env/env",
'docker_compose': f"{base}docker-compose.yml",
'dockerfile': f"{base}Dockerfile",
'env': f"{base}.env/env",
'docker_compose': f"{base}docker-compose.yml",
'docker_compose_override': f"{base}docker-compose.override.yml",
'dockerfile': f"{base}Dockerfile",
}
}

View File

@@ -0,0 +1,77 @@
from __future__ import annotations
import sys, os, re
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from ansible.errors import AnsibleFilterError
from module_utils.config_utils import get_app_conf
from module_utils.entity_name_utils import get_entity_name
_UNIT_RE = re.compile(r'^\s*(\d+(?:\.\d+)?)\s*([kKmMgGtT]?[bB]?)?\s*$')
_FACTORS = {
'': 1, 'b': 1,
'k': 1024, 'kb': 1024,
'm': 1024**2, 'mb': 1024**2,
'g': 1024**3, 'gb': 1024**3,
't': 1024**4, 'tb': 1024**4,
}
def _to_bytes(v: str) -> int:
if v is None:
raise AnsibleFilterError("jvm_filters: size value is None")
s = str(v).strip()
m = _UNIT_RE.match(s)
if not m:
raise AnsibleFilterError(f"jvm_filters: invalid size '{v}'")
num, unit = m.group(1), (m.group(2) or '').lower()
try:
val = float(num)
except ValueError as e:
raise AnsibleFilterError(f"jvm_filters: invalid numeric size '{v}'") from e
factor = _FACTORS.get(unit)
if factor is None:
raise AnsibleFilterError(f"jvm_filters: unknown unit in '{v}'")
return int(val * factor)
def _to_mb(v: str) -> int:
return max(0, _to_bytes(v) // (1024 * 1024))
def _svc(app_id: str) -> str:
return get_entity_name(app_id)
def _mem_limit_mb(apps: dict, app_id: str) -> int:
svc = _svc(app_id)
raw = get_app_conf(apps, app_id, f"docker.services.{svc}.mem_limit")
mb = _to_mb(raw)
if mb <= 0:
raise AnsibleFilterError(f"jvm_filters: mem_limit for '{svc}' must be > 0 MB (got '{raw}')")
return mb
def _mem_res_mb(apps: dict, app_id: str) -> int:
svc = _svc(app_id)
raw = get_app_conf(apps, app_id, f"docker.services.{svc}.mem_reservation")
mb = _to_mb(raw)
if mb <= 0:
raise AnsibleFilterError(f"jvm_filters: mem_reservation for '{svc}' must be > 0 MB (got '{raw}')")
return mb
def jvm_max_mb(apps: dict, app_id: str) -> int:
"""Xmx = min( floor(0.7*limit), limit-1024, 12288 ) with floor at 1024 MB."""
limit_mb = _mem_limit_mb(apps, app_id)
c1 = (limit_mb * 7) // 10
c2 = max(0, limit_mb - 1024)
c3 = 12288
return max(1024, min(c1, c2, c3))
def jvm_min_mb(apps: dict, app_id: str) -> int:
"""Xms = min( floor(Xmx/2), mem_reservation, Xmx ) with floor at 512 MB."""
xmx = jvm_max_mb(apps, app_id)
res = _mem_res_mb(apps, app_id)
return max(512, min(xmx // 2, res, xmx))
class FilterModule(object):
def filters(self):
return {
"jvm_max_mb": jvm_max_mb,
"jvm_min_mb": jvm_min_mb,
}

View File

@@ -0,0 +1,141 @@
# filter_plugins/node_autosize.py
# Reuse app config to derive sensible Node.js heap sizes for containers.
#
# Usage example (Jinja):
# {{ applications | node_max_old_space_size('web-app-nextcloud', 'whiteboard') }}
#
# Heuristics (defaults):
# - candidate = 35% of mem_limit
# - min = 768 MB (required minimum)
# - cap = min(3072 MB, 60% of mem_limit)
#
# NEW: If mem_limit (container cgroup RAM) is smaller than min_mb, we raise an
# exception — to prevent a misconfiguration where Node's heap could exceed the cgroup
# and be OOM-killed.
from __future__ import annotations
import re
from ansible.errors import AnsibleFilterError
# Import the shared config resolver from module_utils
try:
from module_utils.config_utils import get_app_conf, AppConfigKeyError
except Exception as e:
raise AnsibleFilterError(
f"Failed to import get_app_conf from module_utils.config_utils: {e}"
)
_SIZE_RE = re.compile(r"^\s*(\d+(?:\.\d+)?)\s*([kmgtp]?i?b?)?\s*$", re.IGNORECASE)
_MULT = {
"": 1,
"b": 1,
"k": 10**3, "kb": 10**3,
"m": 10**6, "mb": 10**6,
"g": 10**9, "gb": 10**9,
"t": 10**12, "tb": 10**12,
"p": 10**15, "pb": 10**15,
"kib": 1024,
"mib": 1024**2,
"gib": 1024**3,
"tib": 1024**4,
"pib": 1024**5,
}
def _to_bytes(val):
"""Convert numeric or string memory limits (e.g. '512m', '2GiB') to bytes."""
if val is None or val == "":
return None
if isinstance(val, (int, float)):
return int(val)
if not isinstance(val, str):
raise AnsibleFilterError(f"Unsupported mem_limit type: {type(val).__name__}")
m = _SIZE_RE.match(val)
if not m:
raise AnsibleFilterError(f"Unrecognized mem_limit string: {val!r}")
num = float(m.group(1))
unit = (m.group(2) or "").lower()
if unit not in _MULT:
raise AnsibleFilterError(f"Unknown unit in mem_limit: {unit!r}")
return int(num * _MULT[unit])
def _mb(bytes_val: int) -> int:
"""Return decimal MB (10^6) as integer — Node expects MB units."""
return int(round(bytes_val / 10**6))
def _compute_old_space_mb(
total_mb: int, pct: float, min_mb: int, hardcap_mb: int, safety_cap_pct: float
) -> int:
"""
Compute Node.js old-space heap (MB) with safe minimum and cap handling.
NOTE: The calling function ensures total_mb >= min_mb; here we only
apply the sizing heuristics and caps.
"""
candidate = int(total_mb * float(pct))
safety_cap = int(total_mb * float(safety_cap_pct))
final_cap = min(int(hardcap_mb), safety_cap)
# Enforce minimum first; only apply cap if it's above the minimum
candidate = max(candidate, int(min_mb))
if final_cap >= int(min_mb):
candidate = min(candidate, final_cap)
# Never below a tiny hard floor
return max(candidate, 128)
def node_max_old_space_size(
applications: dict,
application_id: str,
service_name: str,
pct: float = 0.35,
min_mb: int = 768,
hardcap_mb: int = 3072,
safety_cap_pct: float = 0.60,
) -> int:
"""
Derive Node.js --max-old-space-size (MB) from the service's mem_limit in app config.
Looks up: docker.services.<service_name>.mem_limit for the given application_id.
Raises:
AnsibleFilterError if mem_limit is missing/invalid OR if mem_limit (MB) < min_mb.
"""
try:
mem_limit = get_app_conf(
applications=applications,
application_id=application_id,
config_path=f"docker.services.{service_name}.mem_limit",
strict=True,
default=None,
)
except AppConfigKeyError as e:
raise AnsibleFilterError(str(e))
if mem_limit in (None, False, ""):
raise AnsibleFilterError(
f"mem_limit not set for application '{application_id}', service '{service_name}'"
)
total_bytes = _to_bytes(mem_limit)
total_mb = _mb(total_bytes)
# NEW: guardrail — refuse to size a heap larger than the cgroup limit
if total_mb < int(min_mb):
raise AnsibleFilterError(
f"mem_limit ({total_mb} MB) is below the required minimum heap ({int(min_mb)} MB) "
f"for application '{application_id}', service '{service_name}'. "
f"Increase mem_limit or lower min_mb."
)
return _compute_old_space_mb(total_mb, pct, min_mb, hardcap_mb, safety_cap_pct)
class FilterModule(object):
def filters(self):
return {
"node_max_old_space_size": node_max_old_space_size,
}

View File

@@ -76,8 +76,9 @@ _applications_nextcloud_oidc_flavor: >-
False,
'oidc_login'
if applications
| get_app_conf('web-app-nextcloud','features.ldap',False, True)
else 'sociallogin'
| get_app_conf('web-app-nextcloud','features.ldap',False, True, True)
else 'sociallogin',
True
)
}}

View File

@@ -5,5 +5,6 @@ MODE_DUMMY: false # Executes dummy/test routines instead
MODE_UPDATE: true # Executes updates
MODE_DEBUG: false # This enables debugging in ansible and in the apps, You SHOULD NOT enable this on production servers
MODE_RESET: false # Cleans up all Infinito.Nexus files. It's necessary to run to whole playbook and not particial roles when using this function.
MODE_CLEANUP: "{{ MODE_DEBUG | bool }}" # Cleanup unused files and configurations
MODE_CLEANUP: true # Cleanup unused files and configurations
MODE_ASSERT: "{{ MODE_DEBUG | bool }}" # Executes validation tasks during the run.
MODE_BACKUP: true # Executes the Backup before the deployment

View File

@@ -1,4 +1,3 @@
# Service Timers
## Meta
@@ -6,12 +5,12 @@ SYS_TIMER_ALL_ENABLED: "{{ MODE_DEBUG }}" # Runtime Var
## Server Tact Variables
HOURS_SERVER_AWAKE: "0..23" # Ours in which the server is "awake" (100% working). Rest of the time is reserved for maintanance
HOURS_SERVER_AWAKE: "6..23" # Ours in which the server is "awake" (100% working). Rest of the time is reserved for maintanance
RANDOMIZED_DELAY_SEC: "5min" # Random delay for systemd timers to avoid peak loads.
## Timeouts for all services
SYS_TIMEOUT_DOCKER_RPR_HARD: "10min"
SYS_TIMEOUT_DOCKER_RPR_SOFT: "{{ SYS_TIMEOUT_DOCKER_RPR_HARD }}"
SYS_TIMEOUT_DOCKER_RPR_SOFT: "{{ SYS_TIMEOUT_DOCKER_RPR_HARD }}"
SYS_TIMEOUT_CLEANUP_SERVICES: "15min"
SYS_TIMEOUT_DOCKER_UPDATE: "20min"
SYS_TIMEOUT_STORAGE_OPTIMIZER: "{{ SYS_TIMEOUT_DOCKER_UPDATE }}"
@@ -24,29 +23,29 @@ SYS_SCHEDULE_HEALTH_BTRFS: "*-*-* 00:00:00"
SYS_SCHEDULE_HEALTH_JOURNALCTL: "*-*-* 00:00:00" # Check once per day the journalctl for errors
SYS_SCHEDULE_HEALTH_DISC_SPACE: "*-*-* 06,12,18,00:00:00" # Check four times per day if there is sufficient disc space
SYS_SCHEDULE_HEALTH_DOCKER_CONTAINER: "*-*-* {{ HOURS_SERVER_AWAKE }}:00:00" # Check once per hour if the docker containers are healthy
SYS_SCHEDULE_HEALTH_DOCKER_VOLUMES: "*-*-* {{ HOURS_SERVER_AWAKE }}:15:00" # Check once per hour if the docker volumes are healthy
SYS_SCHEDULE_HEALTH_CSP_CRAWLER: "*-*-* {{ HOURS_SERVER_AWAKE }}:30:00" # Check once per hour if all CSP are fullfilled available
SYS_SCHEDULE_HEALTH_NGINX: "*-*-* {{ HOURS_SERVER_AWAKE }}:45:00" # Check once per hour if all webservices are available
SYS_SCHEDULE_HEALTH_DOCKER_VOLUMES: "*-*-* {{ HOURS_SERVER_AWAKE }}:00:00" # Check once per hour if the docker volumes are healthy
SYS_SCHEDULE_HEALTH_CSP_CRAWLER: "*-*-* {{ HOURS_SERVER_AWAKE }}:00:00" # Check once per hour if all CSP are fullfilled available
SYS_SCHEDULE_HEALTH_NGINX: "*-*-* {{ HOURS_SERVER_AWAKE }}:00:00" # Check once per hour if all webservices are available
SYS_SCHEDULE_HEALTH_MSMTP: "*-*-* 00:00:00" # Check once per day SMTP Server
### Schedule for cleanup tasks
SYS_SCHEDULE_CLEANUP_BACKUPS: "*-*-* 00,06,12,18:30:00" # Cleanup backups every 6 hours, MUST be called before disc space cleanup
SYS_SCHEDULE_CLEANUP_DISC_SPACE: "*-*-* 07,13,19,01:30:00" # Cleanup disc space every 6 hours
SYS_SCHEDULE_CLEANUP_CERTS: "*-*-* 12,00:45:00" # Deletes and revokes unused certs
SYS_SCHEDULE_CLEANUP_FAILED_BACKUPS: "*-*-* 12:00:00" # Clean up failed docker backups every noon
SYS_SCHEDULE_CLEANUP_CERTS: "*-*-* 20:00" # Deletes and revokes unused certs once per day
SYS_SCHEDULE_CLEANUP_FAILED_BACKUPS: "*-*-* 21:00" # Clean up failed docker backups once per day
SYS_SCHEDULE_CLEANUP_BACKUPS: "*-*-* 22:00" # Cleanup backups once per day, MUST be called before disc space cleanup
SYS_SCHEDULE_CLEANUP_DISC_SPACE: "*-*-* 23:00" # Cleanup disc space once per day
### Schedule for repair services
SYS_SCHEDULE_REPAIR_BTRFS_AUTO_BALANCER: "Sat *-*-01..07 00:00:00" # Execute btrfs auto balancer every first Saturday of a month
SYS_SCHEDULE_REPAIR_DOCKER_HARD: "Sun *-*-* 08:00:00" # Restart docker instances every Sunday at 8:00 AM
SYS_SCHEDULE_REPAIR_DOCKER_HARD: "Sun *-*-* 00:00:00" # Restart docker instances every Sunday
### Schedule for backup tasks
SYS_SCHEDULE_BACKUP_DOCKER_TO_LOCAL: "*-*-* 03:30:00"
SYS_SCHEDULE_BACKUP_REMOTE_TO_LOCAL: "*-*-* 21:30:00"
SYS_SCHEDULE_BACKUP_REMOTE_TO_LOCAL: "*-*-* 00:30:00" # Pull Backup of the previous day
SYS_SCHEDULE_BACKUP_DOCKER_TO_LOCAL: "*-*-* 01:00:00" # Backup the current day
### Schedule for Maintenance Tasks
SYS_SCHEDULE_MAINTANANCE_LETSENCRYPT_RENEW: "*-*-* 12,00:30:00" # Renew Mailu certificates twice per day
SYS_SCHEDULE_MAINTANANCE_LETSENCRYPT_DEPLOY: "*-*-* 13,01:30:00" # Deploy letsencrypt certificates twice per day to docker containers
SYS_SCHEDULE_MAINTANANCE_NEXTCLOUD: "22" # Do nextcloud maintanace between 22:00 and 02:00
SYS_SCHEDULE_MAINTANANCE_LETSENCRYPT_RENEW: "*-*-* 10,22:00:00" # Renew Mailu certificates twice per day
SYS_SCHEDULE_MAINTANANCE_LETSENCRYPT_DEPLOY: "*-*-* 11,23:00:00" # Deploy letsencrypt certificates twice per day to docker containers
SYS_SCHEDULE_MAINTANANCE_NEXTCLOUD: "21" # Do nextcloud maintanace between 21:00 and 01:00
### Animation
SYS_SCHEDULE_ANIMATION_KEYBOARD_COLOR: "*-*-* *:*:00" # Change the keyboard color every minute

View File

@@ -110,6 +110,12 @@ defaults_networks:
subnet: 192.168.104.16/28
web-app-minio:
subnet: 192.168.104.32/28
web-svc-coturn:
subnet: 192.168.104.48/28
web-app-mini-qr:
subnet: 192.168.104.64/28
web-app-shopware:
subnet: 192.168.104.80/28
# /24 Networks / 254 Usable Clients
web-app-bigbluebutton:

View File

@@ -80,20 +80,31 @@ ports:
web-app-flowise: 8056
web-app-minio_api: 8057
web-app-minio_console: 8058
web-app-mini-qr: 8059
web-app-shopware: 8060
web-app-bigbluebutton: 48087 # This port is predefined by bbb. @todo Try to change this to a 8XXX port
public:
# The following ports should be changed to 22 on the subdomain via stream mapping
ssh:
web-app-gitea: 2201
web-app-gitlab: 2202
web-app-gitea: 2201
web-app-gitlab: 2202
ldaps:
svc-db-openldap: 636
stun:
web-app-bigbluebutton: 3478 # Not sure if it's right placed here or if it should be moved to localhost section
# Occupied by BBB: 3479
web-app-nextcloud: 3480
turn:
web-app-bigbluebutton: 5349 # Not sure if it's right placed here or if it should be moved to localhost section
web-app-nextcloud: 5350 # Not used yet
svc-db-openldap: 636
stun_turn:
web-app-bigbluebutton: 3478 # Not sure if it's right placed here or if it should be moved to localhost section
# Occupied by BBB: 3479
web-app-nextcloud: 3480
web-svc-coturn: 3481
stun_turn_tls:
web-app-bigbluebutton: 5349 # Not sure if it's right placed here or if it should be moved to localhost section
web-app-nextcloud: 5350 # Not used yet
web-svc-coturn: 5351
federation:
web-app-matrix_synapse: 8448
relay_port_ranges:
web-svc-coturn_start: 20000
web-svc-coturn_end: 39999
web-app-bigbluebutton_start: 40000
web-app-bigbluebutton_end: 49999
web-app-nextcloud_start: 50000
web-app-nextcloud_end: 59999

View File

@@ -20,9 +20,15 @@ RESOURCE_ACTIVE_DOCKER_CONTAINER_COUNT: >-
# Per-container fair share (numbers!), later we append 'g' only for the string fields in compose
RESOURCE_CPUS_NUM: >-
{{
((RESOURCE_AVAIL_CPUS | float) / (RESOURCE_ACTIVE_DOCKER_CONTAINER_COUNT | float))
| round(2)
[
(
((RESOURCE_AVAIL_CPUS | float) / (RESOURCE_ACTIVE_DOCKER_CONTAINER_COUNT | float))
| round(2)
),
0.5
] | max
}}
RESOURCE_MEM_RESERVATION_NUM: >-
{{
(((RESOURCE_AVAIL_MEM | float) / (RESOURCE_ACTIVE_DOCKER_CONTAINER_COUNT | float)) * 0.7)
@@ -38,4 +44,4 @@ RESOURCE_MEM_LIMIT_NUM: >-
RESOURCE_CPUS: "{{ RESOURCE_CPUS_NUM }}"
RESOURCE_MEM_RESERVATION: "{{ RESOURCE_MEM_RESERVATION_NUM }}g"
RESOURCE_MEM_LIMIT: "{{ RESOURCE_MEM_LIMIT_NUM }}g"
RESOURCE_PIDS_LIMIT: 512
RESOURCE_PIDS_LIMIT: 512

View File

@@ -6,6 +6,7 @@ __metaclass__ = type
import os
import subprocess
import time
from datetime import datetime
class CertUtils:
_domain_cert_mapping = None
@@ -22,6 +23,30 @@ class CertUtils:
except subprocess.CalledProcessError:
return ""
@staticmethod
def run_openssl_dates(cert_path):
"""
Returns (not_before_ts, not_after_ts) as POSIX timestamps or (None, None) on failure.
"""
try:
output = subprocess.check_output(
['openssl', 'x509', '-in', cert_path, '-noout', '-startdate', '-enddate'],
universal_newlines=True
)
nb, na = None, None
for line in output.splitlines():
line = line.strip()
if line.startswith('notBefore='):
nb = line.split('=', 1)[1].strip()
elif line.startswith('notAfter='):
na = line.split('=', 1)[1].strip()
def _parse(openssl_dt):
# OpenSSL format example: "Oct 10 12:34:56 2025 GMT"
return int(datetime.strptime(openssl_dt, "%b %d %H:%M:%S %Y %Z").timestamp())
return (_parse(nb) if nb else None, _parse(na) if na else None)
except Exception:
return (None, None)
@staticmethod
def extract_sans(cert_text):
dns_entries = []
@@ -59,7 +84,6 @@ class CertUtils:
else:
return domain == san
@classmethod
def build_snapshot(cls, cert_base_path):
snapshot = []
@@ -82,6 +106,17 @@ class CertUtils:
@classmethod
def refresh_cert_mapping(cls, cert_base_path, debug=False):
"""
Build mapping: SAN -> list of entries
entry = {
'folder': str,
'cert_path': str,
'mtime': float,
'not_before': int|None,
'not_after': int|None,
'is_wildcard': bool
}
"""
cert_files = cls.list_cert_files(cert_base_path)
mapping = {}
for cert_path in cert_files:
@@ -90,46 +125,82 @@ class CertUtils:
continue
sans = cls.extract_sans(cert_text)
folder = os.path.basename(os.path.dirname(cert_path))
try:
mtime = os.stat(cert_path).st_mtime
except FileNotFoundError:
mtime = 0.0
nb, na = cls.run_openssl_dates(cert_path)
for san in sans:
if san not in mapping:
mapping[san] = folder
entry = {
'folder': folder,
'cert_path': cert_path,
'mtime': mtime,
'not_before': nb,
'not_after': na,
'is_wildcard': san.startswith('*.'),
}
mapping.setdefault(san, []).append(entry)
cls._domain_cert_mapping = mapping
if debug:
print(f"[DEBUG] Refreshed domain-to-cert mapping: {mapping}")
print(f"[DEBUG] Refreshed domain-to-cert mapping (counts): "
f"{ {k: len(v) for k, v in mapping.items()} }")
@classmethod
def ensure_cert_mapping(cls, cert_base_path, debug=False):
if cls._domain_cert_mapping is None or cls.snapshot_changed(cert_base_path):
cls.refresh_cert_mapping(cert_base_path, debug)
@staticmethod
def _score_entry(entry):
"""
Return tuple used for sorting newest-first:
(not_before or -inf, mtime)
"""
nb = entry.get('not_before')
mtime = entry.get('mtime', 0.0)
return (nb if nb is not None else -1, mtime)
@classmethod
def find_cert_for_domain(cls, domain, cert_base_path, debug=False):
cls.ensure_cert_mapping(cert_base_path, debug)
exact_match = None
wildcard_match = None
candidates_exact = []
candidates_wild = []
for san, folder in cls._domain_cert_mapping.items():
for san, entries in cls._domain_cert_mapping.items():
if san == domain:
exact_match = folder
break
if san.startswith('*.'):
candidates_exact.extend(entries)
elif san.startswith('*.'):
base = san[2:]
if domain.count('.') == base.count('.') + 1 and domain.endswith('.' + base):
wildcard_match = folder
candidates_wild.extend(entries)
if exact_match:
if debug:
print(f"[DEBUG] Exact match for {domain} found in {exact_match}")
return exact_match
def _pick_newest(entries):
if not entries:
return None
# newest by (not_before, mtime)
best = max(entries, key=cls._score_entry)
return best
if wildcard_match:
if debug:
print(f"[DEBUG] Wildcard match for {domain} found in {wildcard_match}")
return wildcard_match
best_exact = _pick_newest(candidates_exact)
best_wild = _pick_newest(candidates_wild)
if best_exact and debug:
print(f"[DEBUG] Best exact match for {domain}: {best_exact['folder']} "
f"(not_before={best_exact['not_before']}, mtime={best_exact['mtime']})")
if best_wild and debug:
print(f"[DEBUG] Best wildcard match for {domain}: {best_wild['folder']} "
f"(not_before={best_wild['not_before']}, mtime={best_wild['mtime']})")
# Prefer exact if it exists; otherwise wildcard
chosen = best_exact or best_wild
if chosen:
return chosen['folder']
if debug:
print(f"[DEBUG] No certificate folder found for {domain}")
return None

View File

@@ -24,7 +24,7 @@ class ConfigEntryNotSetError(AppConfigKeyError):
pass
def get_app_conf(applications, application_id, config_path, strict=True, default=None):
def get_app_conf(applications, application_id, config_path, strict=True, default=None, skip_missing_app=False):
# Path to the schema file for this application
schema_path = os.path.join('roles', application_id, 'schema', 'main.yml')
@@ -133,6 +133,9 @@ def get_app_conf(applications, application_id, config_path, strict=True, default
try:
obj = applications[application_id]
except KeyError:
if skip_missing_app:
# Simply return default instead of failing
return default if default is not None else False
raise AppConfigKeyError(
f"Application ID '{application_id}' not found in applications dict.\n"
f"path_trace: {path_trace}\n"

View File

@@ -142,7 +142,8 @@ class InventoryManager:
"""
if algorithm == "random_hex":
return secrets.token_hex(64)
if algorithm == "random_hex_32":
return secrets.token_hex(32)
if algorithm == "sha256":
return hashlib.sha256(secrets.token_bytes(32)).hexdigest()
if algorithm == "sha1":

View File

@@ -3,4 +3,7 @@ collections:
- name: community.general
- name: hetzner.hcloud
yay:
- python-simpleaudio
- python-simpleaudio
- python-numpy
pacman:
- ansible

View File

@@ -153,6 +153,11 @@ roles:
description: "Core AI building blocks—model serving, OpenAI-compatible gateways, vector databases, orchestration, and chat UIs."
icon: "fas fa-brain"
invokable: true
bkp:
title: "Backup Services"
description: "Service-level backup and recovery components—handling automated data snapshots, remote backups, synchronization services, and backup orchestration across databases, files, and containers."
icon: "fas fa-database"
invokable: true
user:
title: "Users & Access"
description: "User accounts & access control"

View File

@@ -127,7 +127,7 @@
#de_BE@euro ISO-8859-15
#de_CH.UTF-8 UTF-8
#de_CH ISO-8859-1
de_DE.UTF-8 UTF-8
#de_DE.UTF-8 UTF-8
#de_DE ISO-8859-1
#de_DE@euro ISO-8859-15
#de_IT.UTF-8 UTF-8

View File

@@ -0,0 +1,4 @@
AUR_HELPER: yay
AUR_BUILDER_USER: aur_builder
AUR_BUILDER_GROUP: wheel
AUR_BUILDER_SUDOERS_PATH: /etc/sudoers.d/11-install-aur_builder

View File

@@ -6,42 +6,53 @@
- dev-git
- dev-base-devel
- name: install yay
- name: Install yay build prerequisites
community.general.pacman:
name:
- base-devel
- patch
state: present
- name: Create the `aur_builder` user
- name: Create the AUR builder user
become: true
ansible.builtin.user:
name: aur_builder
name: "{{ AUR_BUILDER_USER }}"
create_home: yes
group: wheel
group: "{{ AUR_BUILDER_GROUP }}"
- name: Allow the `aur_builder` user to run `sudo pacman` without a password
- name: Allow AUR builder to run pacman without password
become: true
ansible.builtin.lineinfile:
path: /etc/sudoers.d/11-install-aur_builder
line: 'aur_builder ALL=(ALL) NOPASSWD: /usr/bin/pacman'
path: "{{ AUR_BUILDER_SUDOERS_PATH }}"
line: '{{ AUR_BUILDER_USER }} ALL=(ALL) NOPASSWD: /usr/bin/pacman'
create: yes
validate: 'visudo -cf %s'
- name: Clone yay from AUR
become: true
become_user: aur_builder
become_user: "{{ AUR_BUILDER_USER }}"
git:
repo: https://aur.archlinux.org/yay.git
dest: /home/aur_builder/yay
dest: "/home/{{ AUR_BUILDER_USER }}/yay"
clone: yes
update: yes
- name: Build and install yay
become: true
become_user: aur_builder
become_user: "{{ AUR_BUILDER_USER }}"
shell: |
cd /home/aur_builder/yay
cd /home/{{ AUR_BUILDER_USER }}/yay
makepkg -si --noconfirm
args:
creates: /usr/bin/yay
- name: upgrade the system using yay, only act on AUR packages.
become: true
become_user: "{{ AUR_BUILDER_USER }}"
kewlfft.aur.aur:
upgrade: yes
use: "{{ AUR_HELPER }}"
aur_only: yes
when: MODE_UPDATE | bool
- include_tasks: utils/run_once.yml

View File

@@ -1,5 +1,3 @@
- block:
- include_tasks: 01_core.yml
- set_fact:
run_once_dev_yay: true
when: run_once_dev_yay is not defined

View File

@@ -1,15 +1,18 @@
- name: Set default docker_repository_path
set_fact:
docker_repository_path: "{{docker_compose.directories.services}}repository/"
docker_repository_path: "{{ [ docker_compose.directories.services, 'repository/' ] | path_join }}"
- name: pull docker repository
git:
repo: "{{ docker_repository_address }}"
dest: "{{ docker_repository_path }}"
version: "{{ docker_repository_branch | default('main') }}"
depth: 1
update: yes
recursive: yes
repo: "{{ docker_repository_address }}"
dest: "{{ docker_repository_path }}"
version: "{{ docker_repository_branch | default('main') }}"
single_branch: yes
depth: 1
update: yes
recursive: yes
force: yes
accept_hostkey: yes
notify:
- docker compose build
- docker compose up

View File

@@ -28,6 +28,21 @@
- env_template is failed
- "'Could not find or access' not in env_template.msg"
- name: "Create (optional) '{{ docker_compose.files.docker_compose_override }}'"
template:
src: "{{ item }}"
dest: "{{ docker_compose.files.docker_compose_override }}"
mode: '770'
force: yes
notify: docker compose up
register: docker_compose_override_template
loop:
- "{{ application_id | abs_role_path_by_application_id }}/templates/docker-compose.override.yml.j2"
- "{{ application_id | abs_role_path_by_application_id }}/files/docker-compose.override.yml"
failed_when:
- docker_compose_override_template is failed
- "'Could not find or access' not in docker_compose_override_template.msg"
- name: "Create (obligatoric) '{{ docker_compose.files.docker_compose }}'"
template:
src: "docker-compose.yml.j2"

View File

@@ -1,4 +1,6 @@
users:
blackhole:
description: "Everything what will be send to this user will disapear"
username: "blackhole"
username: "blackhole"
roles:
- mail-bot

View File

@@ -1,2 +1,4 @@
# @See https://chatgpt.com/share/67a23d18-fb54-800f-983c-d6d00752b0b4
docker_compose: "{{ application_id | get_docker_paths(PATH_DOCKER_COMPOSE_INSTANCES) }}"
docker_compose: "{{ application_id | get_docker_paths(PATH_DOCKER_COMPOSE_INSTANCES) }}"
docker_compose_command_base: "docker compose --env-file {{ docker_compose.files.env }}"
docker_compose_command_exec: "{{ docker_compose_command_base }} exec"

View File

@@ -1,11 +1,13 @@
{# Base for docker services #}
restart: {{ DOCKER_RESTART_POLICY }}
restart: {{ docker_restart_policy | default(DOCKER_RESTART_POLICY) }}
{% if application_id | has_env %}
env_file:
- "{{ docker_compose.files.env }}"
{% endif %}
logging:
driver: journald
{{ lookup('template', 'roles/docker-container/templates/resource.yml.j2') | indent(4) }}
{% filter indent(4) %}
{% include 'roles/docker-container/templates/resource.yml.j2' %}
{% endfilter %}
{{ "\n" }}

View File

@@ -0,0 +1,31 @@
{# ------------------------------------------------------------------------------
Healthcheck: HTTP Local
------------------------------------------------------------------------------
This template defines a generic HTTP healthcheck for containers exposing
a web service on a local port (e.g., Nginx, Apache, PHP-FPM, Shopware, etc.).
It uses `wget` or `curl` (as fallback) to test if the container responds on
http://127.0.0.1:{{ container_port }}/. If the request succeeds, Docker marks
the container as "healthy"; otherwise, as "unhealthy".
Parameters:
- container_port: The internal port the service listens on.
Timing:
- interval: 30s → Check every 30 seconds
- timeout: 5s → Each check must complete within 5 seconds
- retries: 5 → Mark unhealthy after 5 consecutive failures
- start_period: 20s → Grace period before health checks begin
Usage:
{% filter indent(4) %}
{% include 'roles/docker-container/templates/healthcheck/http.yml.j2' %}
{% endfilter %}
------------------------------------------------------------------------------
#}
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:{{ container_port }}/ >/dev/null || curl -fsS http://127.0.0.1:{{ container_port }}/ >/dev/null"]
interval: 30s
timeout: 5s
retries: 5
start_period: 20s

View File

@@ -4,7 +4,7 @@
run_once_pkgmgr_install: true
when: run_once_pkgmgr_install is not defined
- name: update {{ package_name }}
- name: "update {{ package_name }}"
ansible.builtin.shell: |
source ~/.venvs/pkgmgr/bin/activate
pkgmgr update {{ package_name }} --dependencies --clone-mode https

View File

View File

@@ -0,0 +1,132 @@
#!/usr/bin/env python3
import argparse
import os
import subprocess
import time
import sys
def run_command(command, capture_output=True, check=False, shell=True):
"""Run a shell command and return its output as string."""
try:
result = subprocess.run(
command,
capture_output=capture_output,
shell=shell,
text=True,
check=check
)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
if capture_output:
print(e.stdout)
print(e.stderr)
raise
def pull_backups(hostname: str):
print(f"pulling backups from: {hostname}")
errors = 0
print("loading meta data...")
remote_host = f"backup@{hostname}"
print(f"host address: {remote_host}")
remote_machine_id = run_command(f'ssh "{remote_host}" sha256sum /etc/machine-id')[:64]
print(f"remote machine id: {remote_machine_id}")
general_backup_machine_dir = f"/Backups/{remote_machine_id}/"
print(f"backup dir: {general_backup_machine_dir}")
try:
remote_backup_types = run_command(
f'ssh "{remote_host}" "find {general_backup_machine_dir} -maxdepth 1 -type d -execdir basename {{}} ;"'
).splitlines()
print(f"backup types: {' '.join(remote_backup_types)}")
except subprocess.CalledProcessError:
sys.exit(1)
for backup_type in remote_backup_types:
if backup_type == remote_machine_id:
continue
print(f"backup type: {backup_type}")
general_backup_type_dir = f"{general_backup_machine_dir}{backup_type}/"
general_versions_dir = general_backup_type_dir
# local previous version
try:
local_previous_version_dir = run_command(f"ls -d {general_versions_dir}* | tail -1")
except subprocess.CalledProcessError:
local_previous_version_dir = ""
print(f"last local backup: {local_previous_version_dir}")
# remote versions
remote_backup_versions = run_command(
f'ssh "{remote_host}" "ls -d /Backups/{remote_machine_id}/backup-docker-to-local/*"'
).splitlines()
print(f"remote backup versions: {' '.join(remote_backup_versions)}")
remote_last_backup_dir = remote_backup_versions[-1] if remote_backup_versions else ""
print(f"last remote backup: {remote_last_backup_dir}")
remote_source_path = f"{remote_host}:{remote_last_backup_dir}/"
print(f"source path: {remote_source_path}")
local_backup_destination_path = remote_last_backup_dir
print(f"backup destination: {local_backup_destination_path}")
print("creating local backup destination folder...")
os.makedirs(local_backup_destination_path, exist_ok=True)
rsync_command = (
f'rsync -abP --delete --delete-excluded --rsync-path="sudo rsync" '
f'--link-dest="{local_previous_version_dir}" "{remote_source_path}" "{local_backup_destination_path}"'
)
print("starting backup...")
print(f"executing: {rsync_command}")
retry_count = 0
max_retries = 12
retry_delay = 300 # 5 minutes
last_retry_start = 0
max_retry_duration = 43200 # 12 hours
rsync_exit_code = 1
while retry_count < max_retries:
print(f"Retry attempt: {retry_count + 1}")
if retry_count > 0:
current_time = int(time.time())
last_retry_duration = current_time - last_retry_start
if last_retry_duration >= max_retry_duration:
print("Last retry took more than 12 hours, increasing max retries to 12.")
max_retries = 12
last_retry_start = int(time.time())
rsync_exit_code = os.system(rsync_command)
if rsync_exit_code == 0:
break
retry_count += 1
time.sleep(retry_delay)
if rsync_exit_code != 0:
print(f"Error: rsync failed after {max_retries} attempts")
errors += 1
sys.exit(errors)
def main():
parser = argparse.ArgumentParser(
description="Pull backups from a remote backup host via rsync."
)
parser.add_argument(
"hostname",
help="Hostname from which backup should be pulled"
)
args = parser.parse_args()
pull_backups(args.hostname)
if __name__ == "__main__":
main()

View File

@@ -1,85 +0,0 @@
#!/bin/bash
# @param $1 hostname from which backup should be pulled
echo "pulling backups from: $1" &&
# error counter
errors=0 &&
echo "loading meta data..." &&
remote_host="backup@$1" &&
echo "host address: $remote_host" &&
remote_machine_id="$( (ssh "$remote_host" sha256sum /etc/machine-id) | head -c 64 )" &&
echo "remote machine id: $remote_machine_id" &&
general_backup_machine_dir="/Backups/$remote_machine_id/" &&
echo "backup dir: $general_backup_machine_dir" &&
remote_backup_types="$(ssh "$remote_host" "find $general_backup_machine_dir -maxdepth 1 -type d -execdir basename {} ;")" &&
echo "backup types: $remote_backup_types" || exit 1
for backup_type in $remote_backup_types; do
if [ "$backup_type" != "$remote_machine_id" ]; then
echo "backup type: $backup_type" &&
general_backup_type_dir="$general_backup_machine_dir""$backup_type/" &&
general_versions_dir="$general_backup_type_dir" &&
local_previous_version_dir="$(ls -d $general_versions_dir* | tail -1)" &&
echo "last local backup: $local_previous_version_dir" &&
remote_backup_versions="$(ssh "$remote_host" ls -d "$general_backup_type_dir"\*)" &&
echo "remote backup versions: $remote_backup_versions" &&
remote_last_backup_dir=$(echo "$remote_backup_versions" | tail -1) &&
echo "last remote backup: $remote_last_backup_dir" &&
remote_source_path="$remote_host:$remote_last_backup_dir/" &&
echo "source path: $remote_source_path" &&
local_backup_destination_path=$remote_last_backup_dir &&
echo "backup destination: $local_backup_destination_path" &&
echo "creating local backup destination folder..." &&
mkdir -vp "$local_backup_destination_path" &&
echo "starting backup..."
rsync_command='rsync -abP --delete --delete-excluded --rsync-path="sudo rsync" --link-dest="'$local_previous_version_dir'" "'$remote_source_path'" "'$local_backup_destination_path'"'
echo "executing: $rsync_command"
retry_count=0
max_retries=12
retry_delay=300 # Retry delay in seconds (5 minutes)
last_retry_start=0
max_retry_duration=43200 # Maximum duration for a single retry attempt (12 hours)
while [[ $retry_count -lt $max_retries ]]; do
echo "Retry attempt: $((retry_count + 1))"
if [[ $retry_count -gt 0 ]]; then
current_time=$(date +%s)
last_retry_duration=$((current_time - last_retry_start))
if [[ $last_retry_duration -ge $max_retry_duration ]]; then
echo "Last retry took more than 12 hours, increasing max retries to 12."
max_retries=12
fi
fi
last_retry_start=$(date +%s)
eval "$rsync_command"
rsync_exit_code=$?
if [[ $rsync_exit_code -eq 0 ]]; then
break
fi
retry_count=$((retry_count + 1))
sleep $retry_delay
done
if [[ $rsync_exit_code -ne 0 ]]; then
echo "Error: rsync failed after $max_retries attempts"
((errors += 1))
fi
fi
done
exit $errors;

View File

@@ -10,15 +10,15 @@
- include_tasks: utils/run_once.yml
when: run_once_svc_bkp_rmt_2_loc is not defined
- name: "create {{ DOCKER_BACKUP_REMOTE_2_LOCAL_DIR }}"
- name: "Create Directory '{{ DOCKER_BACKUP_REMOTE_2_LOCAL_DIR }}'"
file:
path: "{{ DOCKER_BACKUP_REMOTE_2_LOCAL_DIR }}"
state: directory
mode: "0755"
- name: create svc-bkp-rmt-2-loc.sh
- name: "Deploy '{{ DOCKER_BACKUP_REMOTE_2_LOCAL_SCRIPT }}'"
copy:
src: svc-bkp-rmt-2-loc.sh
src: "{{ DOCKER_BACKUP_REMOTE_2_LOCAL_FILE }}"
dest: "{{ DOCKER_BACKUP_REMOTE_2_LOCAL_SCRIPT }}"
mode: "0755"

View File

@@ -3,6 +3,6 @@
hosts="{{ DOCKER_BACKUP_REMOTE_2_LOCAL_BACKUP_PROVIDERS | join(' ') }}";
errors=0
for host in $hosts; do
bash {{ DOCKER_BACKUP_REMOTE_2_LOCAL_SCRIPT }} $host || ((errors+=1));
python {{ DOCKER_BACKUP_REMOTE_2_LOCAL_SCRIPT }} $host || ((errors+=1));
done;
exit $errors;

View File

@@ -1,5 +1,9 @@
# General
application_id: svc-bkp-rmt-2-loc
system_service_id: "{{ application_id }}"
system_service_id: "{{ application_id }}"
# Role Specific
DOCKER_BACKUP_REMOTE_2_LOCAL_DIR: '{{ PATH_ADMINISTRATOR_SCRIPTS }}{{ application_id }}/'
DOCKER_BACKUP_REMOTE_2_LOCAL_SCRIPT: "{{ DOCKER_BACKUP_REMOTE_2_LOCAL_DIR }}svc-bkp-rmt-2-loc.sh"
DOCKER_BACKUP_REMOTE_2_LOCAL_FILE: 'pull-specific-host.py'
DOCKER_BACKUP_REMOTE_2_LOCAL_SCRIPT: "{{ [ DOCKER_BACKUP_REMOTE_2_LOCAL_DIR , DOCKER_BACKUP_REMOTE_2_LOCAL_FILE ] | path_join }}"
DOCKER_BACKUP_REMOTE_2_LOCAL_BACKUP_PROVIDERS: "{{ applications | get_app_conf(application_id, 'backup_providers') }}"

View File

@@ -5,9 +5,14 @@ network:
docker:
services:
openldap:
image: "bitnami/openldap"
image: "bitnamilegacy/openldap"
name: "openldap"
version: "latest"
cpus: 1.25
# Optimized up to 5k user
mem_reservation: 1g
mem_limit: 1.5g
pids_limit: 1024
network: "openldap"
volumes:
data: "openldap_data"

View File

@@ -6,7 +6,7 @@ docker:
name: postgres
# Please set an version in your inventory file!
# Rolling release isn't recommended
version: "latest"
version: "17-3.5"
backup:
database_routine: true
cpus: "2.0"
@@ -14,5 +14,5 @@ docker:
mem_limit: "6g"
pids_limit: 1024
volumes:
data: "postgres_data"
network: "postgres"
data: "postgres_data"
network: "postgres"

View File

@@ -5,7 +5,7 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
git \
postgresql-server-dev-all \
postgresql-server-dev-{{ POSTGRES_VERSION_MAJOR | default('all', true) }} \
&& git clone https://github.com/pgvector/pgvector.git /tmp/pgvector \
&& cd /tmp/pgvector \
&& make \

View File

@@ -1,19 +1,21 @@
# General
application_id: svc-db-postgres
entity_name: "{{ application_id | get_entity_name }}"
# Docker
docker_compose_flush_handlers: true
# Docker Compose
database_type: "{{ application_id | get_entity_name }}"
database_type: "{{ entity_name }}"
## Postgres
POSTGRES_VOLUME: "{{ applications | get_app_conf(application_id, 'docker.volumes.data') }}"
POSTGRES_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.postgres.name') }}"
POSTGRES_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.postgres.image') }}"
POSTGRES_SUBNET: "{{ networks.local['svc-db-postgres'].subnet }}"
POSTGRES_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.' ~ entity_name ~ '.name') }}"
POSTGRES_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.' ~ entity_name ~ '.image') }}"
POSTGRES_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.' ~ entity_name ~ '.version') }}"
POSTGRES_VERSION_MAJOR: "{{ POSTGRES_VERSION | regex_replace('^([0-9]+).*', '\\1') }}"
POSTGRES_NETWORK_NAME: "{{ applications | get_app_conf(application_id, 'docker.network') }}"
POSTGRES_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.postgres.version') }}"
POSTGRES_SUBNET: "{{ networks.local['svc-db-postgres'].subnet }}"
POSTGRES_PASSWORD: "{{ applications | get_app_conf(application_id, 'credentials.POSTGRES_PASSWORD') }}"
POSTGRES_PORT: "{{ database_port | default(ports.localhost.database[ application_id ]) }}"
POSTGRES_INIT: "{{ database_username is defined and database_password is defined and database_name is defined }}"

View File

@@ -16,4 +16,12 @@
retries: 30
networks:
- default
{% macro include_resource_for(svc, indent=4) -%}
{% set service_name = svc -%}
{%- set _snippet -%}
{% include 'roles/docker-container/templates/resource.yml.j2' %}
{%- endset -%}
{{ _snippet | indent(indent, true) }}
{%- endmacro %}
{{ include_resource_for('redis') }}
{{ "\n" }}

View File

@@ -0,0 +1,14 @@
- name: Install '
include_role:
name: pkgmgr-install
vars:
package_name: "{{ SWAPFILE_PKG }}"
when: run_once_pkgmgr_install is not defined
- name: Execute create swapfile script
shell: "{{ SWAPFILE_PKG }} '{{ SWAPFILE_SIZE }}'"
become: true
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
- include_tasks: utils/run_once.yml

View File

@@ -1,19 +1,3 @@
- block:
- name: Include dependency 'pkgmgr-install'
include_role:
name: pkgmgr-install
when: run_once_pkgmgr_install is not defined
- include_tasks: utils/run_once.yml
- include_tasks: 01_core.yml
when: run_once_svc_opt_swapfile is not defined
- name: "pkgmgr install"
include_role:
name: pkgmgr-install
vars:
package_name: swap-forge
- name: Execute create swapfile script
shell: swap-forge "{{ SWAPFILE_SIZE }}"
become: true
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"

View File

@@ -1,3 +1,4 @@
application_id: "svc-opt-swapfile"
SWAPFILE_SIZE: "{{ applications | get_app_conf(application_id, 'swapfile_size') }}"
SWAPFILE_SIZE: "{{ applications | get_app_conf(application_id, 'swapfile_size') }}"
SWAPFILE_PKG: "swap-forge"

View File

@@ -13,7 +13,7 @@ get_backup_types="find /Backups/$hashed_machine_id/ -maxdepth 1 -type d -execdir
# @todo This configuration is not scalable yet. If other backup services then sys-ctl-bkp-docker-2-loc are integrated, this logic needs to be optimized
get_version_directories="ls -d /Backups/$hashed_machine_id/sys-ctl-bkp-docker-2-loc/*"
get_version_directories="ls -d /Backups/$hashed_machine_id/backup-docker-to-local/*"
last_version_directory="$($get_version_directories | tail -1)"
rsync_command="sudo rsync --server --sender -blogDtpre.iLsfxCIvu . $last_version_directory/"

View File

@@ -3,30 +3,6 @@
name: backup
create_home: yes
- name: create .ssh directory
file:
path: /home/backup/.ssh
state: directory
owner: backup
group: backup
mode: '0700'
- name: create /home/backup/.ssh/authorized_keys
template:
src: "authorized_keys.j2"
dest: /home/backup/.ssh/authorized_keys
owner: backup
group: backup
mode: '0644'
- name: create /home/backup/ssh-wrapper.sh
copy:
src: "ssh-wrapper.sh"
dest: /home/backup/ssh-wrapper.sh
owner: backup
group: backup
mode: '0700'
- name: grant backup sudo rights
copy:
src: "backup"
@@ -35,3 +11,9 @@
owner: root
group: root
notify: sshd restart
- include_tasks: 02_permissions_ssh.yml
- include_tasks: 03_permissions_folders.yml
- include_tasks: utils/run_once.yml

View File

@@ -0,0 +1,23 @@
- name: create .ssh directory
file:
path: /home/backup/.ssh
state: directory
owner: backup
group: backup
mode: '0700'
- name: create /home/backup/.ssh/authorized_keys
template:
src: "authorized_keys.j2"
dest: /home/backup/.ssh/authorized_keys
owner: backup
group: backup
mode: '0644'
- name: create /home/backup/ssh-wrapper.sh
copy:
src: "ssh-wrapper.sh"
dest: /home/backup/ssh-wrapper.sh
owner: backup
group: backup
mode: '0700'

View File

@@ -0,0 +1,66 @@
# Ensure the backups root exists and is owned by backup
- name: Ensure backups root exists and owned by backup
file:
path: "{{ BACKUPS_FOLDER_PATH }}"
state: directory
owner: backup
group: backup
mode: "0700"
# Explicit ACL so 'backup' has rwx, others none
- name: Grant ACL rwx on backups root to backup user
ansible.posix.acl:
path: "{{ BACKUPS_FOLDER_PATH }}"
entity: backup
etype: user
permissions: rwx
state: present
# Set default ACLs so new entries inherit rwx for backup and nothing for others
- name: Set default ACL (inherit) for backup user under backups root
ansible.posix.acl:
path: "{{ BACKUPS_FOLDER_PATH }}"
entity: backup
etype: user
permissions: rwx
default: true
state: present
# Remove default ACLs for group/others (defensive hardening)
# Default ACLs so new entries inherit only backup's rwx
- name: Default ACL for backup user (inherit)
ansible.posix.acl:
path: "{{ BACKUPS_FOLDER_PATH }}"
etype: user
entity: backup
permissions: rwx
default: true
state: present
# Explicitly set default group/other to no permissions (instead of absent)
- name: Default ACL for group -> none
ansible.posix.acl:
path: "{{ BACKUPS_FOLDER_PATH }}"
etype: group
permissions: '---'
default: true
state: present
- name: Default ACL for other -> none
ansible.posix.acl:
path: "{{ BACKUPS_FOLDER_PATH }}"
etype: other
permissions: '---'
default: true
state: present
- name: Fix ownership level 0..2 directories to backup:backup
ansible.builtin.shell: >
find "{{ BACKUPS_FOLDER_PATH }}" -mindepth 0 -maxdepth 2 -xdev -type d -exec chown backup:backup {} +
changed_when: false
- name: Fix perms level 0..2 directories to 0700
ansible.builtin.shell: >
find "{{ BACKUPS_FOLDER_PATH }}" -mindepth 0 -maxdepth 2 -xdev -type d -exec chmod 700 {} +
changed_when: false

View File

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

View File

@@ -5,21 +5,23 @@
- sys-ctl-alm-telegram
- sys-ctl-alm-email
vars:
flush_handlers: true
system_service_timer_enabled: false
system_service_copy_files: true
system_service_tpl_exec_start: "{{ system_service_script_exec }} %I"
system_service_tpl_on_failure: ""
flush_handlers: true
system_service_timer_enabled: false
system_service_copy_files: true
system_service_tpl_exec_start: "{{ system_service_script_exec }} %I"
system_service_tpl_on_failure: ""
system_service_force_linear_sync: false
- name: "Include core service for '{{ system_service_id }}'"
include_role:
name: sys-service
vars:
flush_handlers: true
system_service_timer_enabled: false
system_service_copy_files: true
system_service_tpl_exec_start: "{{ system_service_script_exec }} %I"
system_service_tpl_on_failure: "" # No on failure needed, because it's anyhow the default on failure procedure
flush_handlers: true
system_service_timer_enabled: false
system_service_copy_files: true
system_service_tpl_exec_start: "{{ system_service_script_exec }} %I"
system_service_tpl_on_failure: "" # No on failure needed, because it's anyhow the default on failure procedure
system_service_force_linear_sync: false
- name: Assert '{{ system_service_id }}'
block:

View File

@@ -1,8 +1,7 @@
- name: Include dependencies
include_role:
name: '{{ item }}'
loop:
- sys-svc-msmtp
name: "sys-svc-msmtp"
when: run_once_sys_svc_msmtp is not defined or run_once_sys_svc_msmtp is false
- include_role:
name: sys-service

View File

@@ -19,6 +19,8 @@
vars:
system_service_copy_files: false
system_service_timer_enabled: true
system_service_force_linear_sync: true
system_service_force_flush: "{{ MODE_BACKUP | bool }}"
system_service_on_calendar: "{{ SYS_SCHEDULE_BACKUP_DOCKER_TO_LOCAL }}"
system_service_tpl_exec_start_pre: '/usr/bin/python {{ PATH_SYSTEM_LOCK_SCRIPT }} {{ SYS_SERVICE_GROUP_MANIPULATION | join(" ") }} --ignore {{ SYS_SERVICE_BACKUP_DOCKER_2_LOC }} --timeout "{{ SYS_TIMEOUT_BACKUP_SERVICES }}"'
system_service_tpl_exec_start: "/bin/sh -c '{{ BKP_DOCKER_2_LOC_EXEC }}'"

View File

@@ -1,9 +1,6 @@
- block:
- include_tasks: 01_core.yml
when:
- run_once_sys_ctl_bkp_docker_2_loc is not defined
- include_tasks: 01_core.yml
when: run_once_sys_ctl_bkp_docker_2_loc is not defined
- name: "include 04_seed-database-to-backup.yml"
include_tasks: 04_seed-database-to-backup.yml
when:
- BKP_DOCKER_2_LOC_DB_ENABLED | bool
when: BKP_DOCKER_2_LOC_DB_ENABLED | bool

View File

@@ -12,6 +12,7 @@
system_service_tpl_exec_start: dockreap --no-confirmation
system_service_tpl_exec_start_pre: "" # Anonymous volumes can allways be removed. It isn't necessary to wait for any service to stop.
system_service_copy_files: false
system_service_force_linear_sync: false
- include_tasks: utils/run_once.yml
when:

View File

@@ -17,9 +17,10 @@
name: sys-service
vars:
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
system_service_tpl_exec_start: "{{ system_service_script_exec }} --backups-folder-path {{ BACKUPS_FOLDER_PATH }} --maximum-backup-size-percent {{SIZE_PERCENT_MAXIMUM_BACKUP}}"
system_service_tpl_exec_start: "{{ system_service_script_exec }} --backups-folder-path {{ BACKUPS_FOLDER_PATH }} --maximum-backup-size-percent {{ SIZE_PERCENT_MAXIMUM_BACKUP }}"
system_service_tpl_exec_start_pre: '/usr/bin/python {{ PATH_SYSTEM_LOCK_SCRIPT }} {{ SYS_SERVICE_GROUP_MANIPULATION | join(" ") }} --ignore {{ SYS_SERVICE_GROUP_CLEANUP | join(" ") }} --timeout "{{ SYS_TIMEOUT_BACKUP_SERVICES }}"'
system_service_copy_files: true
system_service_force_linear_sync: false
- include_tasks: utils/run_once.yml
vars:

View File

@@ -14,6 +14,7 @@
- include_role:
name: sys-service
vars:
system_service_timer_enabled: true
system_service_on_calendar: "{{ SYS_SCHEDULE_CLEANUP_CERTS }}"
system_service_copy_files: false
system_service_timer_enabled: true
system_service_on_calendar: "{{ SYS_SCHEDULE_CLEANUP_CERTS }}"
system_service_copy_files: false
system_service_force_linear_sync: false

View File

@@ -14,3 +14,4 @@
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
system_service_tpl_exec_start: "{{ system_service_script_exec }} {{ SIZE_PERCENT_CLEANUP_DISC_SPACE }}"
system_service_tpl_exec_start_pre: '/usr/bin/python {{ PATH_SYSTEM_LOCK_SCRIPT }} {{ SYS_SERVICE_GROUP_MANIPULATION | join(" ") }} --ignore {{ SYS_SERVICE_GROUP_CLEANUP | join(" ") }} --timeout "{{ SYS_TIMEOUT_BACKUP_SERVICES }}"'
system_service_force_linear_sync: false

View File

@@ -39,6 +39,18 @@ if [ "$force_freeing" = true ]; then
docker exec -u www-data $nextcloud_application_container /var/www/html/occ versions:cleanup || exit 6
fi
# Mastodon cleanup (remote media cache)
mastodon_application_container="{{ applications | get_app_conf('web-app-mastodon', 'docker.services.mastodon.name') }}"
mastodon_cleanup_days="1"
if [ -n "$mastodon_application_container" ] && docker ps -a --format '{% raw %}{{.Names}}{% endraw %}' | grep -qw "$mastodon_application_container"; then
echo "Cleaning up Mastodon media cache (older than ${mastodon_cleanup_days} days)" &&
docker exec -u root "$mastodon_application_container" bash -lc "bin/tootctl media remove --days=${mastodon_cleanup_days}" || exit 8
# Optional: additionally remove local thumbnail/cache files older than X days
# Warning: these will be regenerated when accessed, which may cause extra CPU/I/O load
# docker exec -u root "$mastodon_application_container" bash -lc "find /mastodon/public/system/cache -type f -mtime +${mastodon_cleanup_days} -delete" || exit 9
fi
fi
if command -v pacman >/dev/null 2>&1 ; then

View File

@@ -21,5 +21,5 @@
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
system_service_tpl_exec_start_pre: '/usr/bin/python {{ PATH_SYSTEM_LOCK_SCRIPT }} {{ SYS_SERVICE_GROUP_MANIPULATION | join(" ") }} --ignore {{ SYS_SERVICE_GROUP_CLEANUP| join(" ") }} --timeout "{{ SYS_TIMEOUT_CLEANUP_SERVICES }}"'
system_service_tpl_exec_start: '/bin/sh -c "{{ CLEANUP_FAILED_BACKUPS_PKG }} --all --workers {{ CLEANUP_FAILED_BACKUPS_WORKERS }} --yes"'
system_service_force_linear_sync: false
- include_tasks: utils/run_once.yml

View File

@@ -14,6 +14,32 @@ Designed for Archlinux systems, this role periodically checks whether web resour
- **Domain Extraction:** Parses all `.conf` files in the NGINX config folder to determine the list of domains to check.
- **Automated Execution:** Registers a systemd service and timer for recurring health checks.
- **Error Notification:** Integrates with `sys-ctl-alm-compose` for alerting on failure.
- **Ignore List Support:** Optional variable to suppress network block reports from specific external domains.
## Configuration
### Variables
- **`HEALTH_CSP_IGNORE_NETWORK_BLOCKS_FROM`** (list, default: `[]`)
Optional list of domains whose network block failures (e.g., ORB) should be ignored during CSP checks.
Example:
```yaml
HEALTH_CSP_IGNORE_NETWORK_BLOCKS_FROM:
- pxscdn.com
- cdn.example.org
```
This will run the CSP checker with:
```bash
checkcsp start --short --ignore-network-blocks-from pxscdn.com -- cdn.example.org <domains...>
```
### Systemd Integration
The role configures a systemd service and timer which executes the CSP crawler periodically against all NGINX domains.
## License
@@ -24,4 +50,4 @@ Infinito.Nexus NonCommercial License
Kevin Veen-Birkenbach
Consulting & Coaching Solutions
[https://www.veen.world](https://www.veen.world)
[https://www.veen.world](https://www.veen.world)

View File

@@ -0,0 +1,5 @@
# List of domains whose network block failures (e.g., ORB) should be ignored
# during CSP checks. This is useful for suppressing known external resources
# (e.g., third-party CDNs) that cannot be influenced but otherwise cause
# unnecessary alerts in the crawler reports.
HEALTH_CSP_IGNORE_NETWORK_BLOCKS_FROM: []

View File

@@ -21,11 +21,20 @@ def extract_domains(config_path):
print(f"Directory {config_path} not found.", file=sys.stderr)
return None
def run_checkcsp(domains):
def run_checkcsp(domains, ignore_network_blocks_from):
"""
Executes the 'checkcsp' command with the given domains.
Executes the 'checkcsp' command with the given domains and optional ignores.
"""
cmd = ["checkcsp", "start", "--short"] + domains
cmd = ["checkcsp", "start", "--short"]
# pass through ignore list only if not empty
if ignore_network_blocks_from:
cmd.append("--ignore-network-blocks-from")
cmd.extend(ignore_network_blocks_from)
cmd.append("--")
cmd += domains
try:
result = subprocess.run(cmd, check=True)
return result.returncode
@@ -45,6 +54,12 @@ def main():
required=True,
help="Directory containing NGINX .conf files"
)
parser.add_argument(
"--ignore-network-blocks-from",
nargs="*",
default=[],
help="Optional: one or more domains whose network block failures should be ignored"
)
args = parser.parse_args()
domains = extract_domains(args.nginx_config_dir)
@@ -55,7 +70,7 @@ def main():
print("No domains found to check.")
sys.exit(0)
rc = run_checkcsp(domains)
rc = run_checkcsp(domains, args.ignore_network_blocks_from)
sys.exit(rc)
if __name__ == "__main__":

View File

@@ -18,6 +18,9 @@
system_service_timer_enabled: true
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
system_service_tpl_timeout_start_sec: "{{ CURRENT_PLAY_DOMAINS_ALL | timeout_start_sec_for_domains }}"
system_service_tpl_exec_start: "{{ system_service_script_exec }} --nginx-config-dir={{ NGINX.DIRECTORIES.HTTP.SERVERS }}"
system_service_tpl_exec_start: >-
{{ system_service_script_exec }}
--nginx-config-dir={{ NGINX.DIRECTORIES.HTTP.SERVERS }}
--ignore-network-blocks-from {{ HEALTH_CSP_IGNORE_NETWORK_BLOCKS_FROM | join(' ') }}
- include_tasks: utils/run_once.yml

View File

@@ -1,4 +1,3 @@
# roles/sys-ctl-hlth-webserver/filter_plugins/web_health_expectations.py
import os
import sys
from collections.abc import Mapping
@@ -94,6 +93,26 @@ def _normalize_selection(group_names):
raise ValueError("web_health_expectations: 'group_names' must be provided and non-empty")
return sel
def _normalize_codes(x):
"""
Accepts:
- single code (int or str)
- list/tuple/set of codes
Returns a de-duplicated list of valid ints (100..599) in original order.
"""
if x is None:
return []
if isinstance(x, (list, tuple, set)):
out = []
seen = set()
for v in x:
c = _valid_http_code(v)
if c is not None and c not in seen:
seen.add(c)
out.append(c)
return out
c = _valid_http_code(x)
return [c] if c is not None else []
def web_health_expectations(applications, www_enabled: bool = False, group_names=None, redirect_maps=None):
"""Produce a **flat mapping**: domain -> [expected_status_codes].
@@ -138,17 +157,15 @@ def web_health_expectations(applications, www_enabled: bool = False, group_names
sc_map = {}
if isinstance(sc_raw, Mapping):
for k, v in sc_raw.items():
code = _valid_http_code(v)
if code is not None:
sc_map[str(k)] = code
codes = _normalize_codes(v)
if codes:
sc_map[str(k)] = codes
if isinstance(canonical_raw, Mapping) and canonical_raw:
for key, domains in canonical_raw.items():
domains_list = _to_list(domains, allow_mapping=False)
code = _valid_http_code(sc_map.get(key))
if code is None:
code = _valid_http_code(sc_map.get("default"))
expected = [code] if code is not None else list(DEFAULT_OK)
codes = sc_map.get(key) or sc_map.get("default")
expected = list(codes) if codes else list(DEFAULT_OK)
for d in domains_list:
if d:
expectations[d] = expected
@@ -156,8 +173,8 @@ def web_health_expectations(applications, www_enabled: bool = False, group_names
for d in _to_list(canonical_raw, allow_mapping=True):
if not d:
continue
code = _valid_http_code(sc_map.get("default"))
expectations[d] = [code] if code is not None else list(DEFAULT_OK)
codes = sc_map.get("default")
expectations[d] = list(codes) if codes else list(DEFAULT_OK)
for d in aliases:
if d:

View File

@@ -8,8 +8,9 @@
- include_role:
name: sys-service
vars:
system_service_state: restarted
system_service_on_calendar: "{{ SYS_SCHEDULE_MAINTANANCE_LETSENCRYPT_DEPLOY }}"
persistent: "true"
system_service_timer_enabled: true
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
system_service_state: restarted
system_service_on_calendar: "{{ SYS_SCHEDULE_MAINTANANCE_LETSENCRYPT_DEPLOY }}"
persistent: "true"
system_service_timer_enabled: true
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
system_service_force_linear_sync: false

View File

@@ -3,7 +3,7 @@
name: '{{ item }}'
loop:
- sys-svc-certbot
- sys-svc-webserver
- sys-svc-webserver-core
- sys-ctl-alm-compose
- name: install certbot
@@ -15,8 +15,9 @@
- include_role:
name: sys-service
vars:
system_service_copy_files: false
system_service_on_calendar: "{{ SYS_SCHEDULE_MAINTANANCE_LETSENCRYPT_RENEW }}"
persistent: true
system_service_timer_enabled: true
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
system_service_copy_files: false
system_service_on_calendar: "{{ SYS_SCHEDULE_MAINTANANCE_LETSENCRYPT_RENEW }}"
persistent: true
system_service_timer_enabled: true
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
system_service_force_linear_sync: false

View File

@@ -12,9 +12,10 @@
- include_role:
name: sys-service
vars:
system_service_suppress_flush: true # It takes a super long time - Better wait for failure of timed service instead of executing it on every play
system_service_copy_files: false
system_service_on_calendar: "{{ SYS_SCHEDULE_REPAIR_BTRFS_AUTO_BALANCER }}"
system_service_timer_enabled: true
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
system_service_tpl_exec_start: "/bin/sh -c 'btrfs-auto-balancer 90 10'"
system_service_suppress_flush: true # It takes a super long time - Better wait for failure of timed service instead of executing it on every play
system_service_copy_files: false
system_service_on_calendar: "{{ SYS_SCHEDULE_REPAIR_BTRFS_AUTO_BALANCER }}"
system_service_timer_enabled: true
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
system_service_tpl_exec_start: "/bin/sh -c 'btrfs-auto-balancer 90 10'"
system_service_force_linear_sync: true

View File

@@ -12,5 +12,6 @@
system_service_tpl_exec_start: '{{ system_service_script_exec }} {{ PATH_DOCKER_COMPOSE_INSTANCES }}'
system_service_tpl_exec_start_post: "/usr/bin/systemctl start {{ SYS_SERVICE_CLEANUP_ANONYMOUS_VOLUMES }}"
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
system_service_force_linear_sync: true
- include_tasks: utils/run_once.yml

View File

@@ -10,5 +10,6 @@
system_service_tpl_exec_start_pre: "/usr/bin/python {{ PATH_SYSTEM_LOCK_SCRIPT }} {{ SYS_SERVICE_GROUP_MANIPULATION | join(' ') }} --ignore {{ SYS_SERVICE_GROUP_CLEANUP| join(' ') }} {{ SYS_SERVICE_REPAIR_DOCKER_SOFT }} --timeout '{{ SYS_TIMEOUT_DOCKER_RPR_SOFT }}'"
system_service_tpl_exec_start: >
/bin/sh -c '{{ system_service_script_exec }} --manipulation-string "{{ SYS_SERVICE_GROUP_MANIPULATION | join(" ") }}" {{ PATH_DOCKER_COMPOSE_INSTANCES }}'
system_service_force_linear_sync: true
- include_tasks: utils/run_once.yml

View File

@@ -0,0 +1,16 @@
- name: "Load CDN for '{{ domain }}'"
include_role:
name: web-svc-cdn
public: false
when:
- application_id != 'web-svc-cdn'
- run_once_web_svc_cdn is not defined
- name: Load Logout for '{{ domain }}'
include_role:
name: web-svc-logout
public: false
when:
- run_once_web_svc_logout is not defined
- application_id != 'web-svc-logout'
- inj_enabled.logout

View File

@@ -1,22 +1,41 @@
- block:
- name: Include dependency 'sys-svc-webserver-core'
include_role:
name: sys-svc-webserver-core
when: run_once_sys_svc_webserver_core is not defined
- include_tasks: utils/run_once.yml
when: run_once_sys_front_inj_all is not defined
- name: Build inj_enabled
set_fact:
inj_enabled: "{{ applications | inj_enabled(application_id, SRV_WEB_INJ_COMP_FEATURES_ALL) }}"
- name: "Load CDN Service for '{{ domain }}'"
include_role:
name: sys-svc-cdn
public: true # Expose variables so that they can be used in all injection roles
- name: "Included dependent services"
include_tasks: 01_dependencies.yml
vars:
proxy_extra_configuration: ""
- name: Reinitialize 'inj_enabled' for '{{ domain }}', after modification by CDN
- name: Reinitialize 'inj_enabled' for '{{ domain }}', after loading the required webservices
set_fact:
inj_enabled: "{{ applications | inj_enabled(application_id, SRV_WEB_INJ_COMP_FEATURES_ALL) }}"
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: "Load CDN Service for '{{ domain }}'"
include_role:
name: sys-svc-cdn
public: true
- name: "Activate logout proxy for '{{ domain }}'"
include_role:
name: sys-front-inj-logout
public: true
when: inj_enabled.logout
- name: "Activate Desktop iFrame notifier for '{{ domain }}'"
include_role:
name: sys-front-inj-desktop
public: true # Vars used in templates
public: true
when: inj_enabled.desktop
- name: "Activate Corporate CSS for '{{ domain }}'"
@@ -33,17 +52,3 @@
include_role:
name: sys-front-inj-javascript
when: inj_enabled.javascript
- name: "Activate logout proxy for '{{ domain }}'"
include_role:
name: sys-front-inj-logout
public: true # Vars used in templates
when: inj_enabled.logout
- block:
- name: Include dependency 'sys-svc-webserver'
include_role:
name: sys-svc-webserver
when: run_once_sys_svc_webserver is not defined
- include_tasks: utils/run_once.yml
when: run_once_sys_front_inj_all is not defined

View File

@@ -10,17 +10,6 @@
lua_need_request_body on;
header_filter_by_lua_block {
local ct = ngx.header.content_type or ""
if ct:lower():find("^text/html") then
ngx.ctx.is_html = true
-- IMPORTANT: body will be modified → drop Content-Length to avoid mismatches
ngx.header.content_length = nil
else
ngx.ctx.is_html = false
end
}
body_filter_by_lua_block {
-- Only process HTML responses
if not ngx.ctx.is_html then

View File

@@ -1,8 +1,3 @@
- name: Include dependency 'sys-svc-webserver'
include_role:
name: sys-svc-webserver
when: run_once_sys_svc_webserver 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) }}"
@@ -19,3 +14,5 @@
group: "{{ NGINX.USER }}"
mode: '0644'
loop: "{{ CSS_FILES }}"
- include_tasks: utils/run_once.yml

View File

@@ -1,6 +1,4 @@
- block:
- include_tasks: 01_core.yml
- include_tasks: utils/run_once.yml
- include_tasks: 01_core.yml
when: run_once_sys_front_inj_css is not defined
- name: "Resolve optional app style.css source for '{{ application_id }}'"

View File

@@ -3,6 +3,6 @@
{% 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 %}
{% if app_style_present | default(false) | bool %}
<link rel="stylesheet" href="{{ [ cdn_urls.role.release.css, 'style.css', lookup('local_mtime_qs', app_style_src)] | url_join }}">
{% endif %}

View File

@@ -1,8 +1,4 @@
- block:
- name: Include dependency 'sys-svc-webserver'
include_role:
name: sys-svc-webserver
when: run_once_sys_svc_webserver is not defined
- include_tasks: 01_deploy.yml
- include_tasks: utils/run_once.yml
when: run_once_sys_front_inj_desktop is not defined

View File

@@ -1,11 +1,4 @@
- block:
- name: Include dependency 'sys-svc-webserver'
include_role:
name: sys-svc-webserver
when: run_once_sys_svc_webserver is not defined
- include_tasks: utils/run_once.yml
when: run_once_sys_front_inj_javascript is not defined
# run_once_sys_front_inj_javascript: deactivated
- name: "Load JavaScript code for '{{ application_id }}'"
set_fact:

View File

@@ -1,8 +1,6 @@
- name: Include dependency 'sys-svc-webserver'
include_role:
name: sys-svc-webserver
when:
- run_once_sys_svc_webserver is not defined
- name: "deploy the logout.js"
include_tasks: "02_deploy.yml"
include_tasks: "02_deploy.yml"
- set_fact:
run_once_sys_front_inj_logout: true
changed_when: false

View File

@@ -1,10 +1,10 @@
- name: Deploy logout.js
template:
src: logout.js.j2
dest: "{{ INJ_LOGOUT_JS_DESTINATION }}"
owner: "{{ NGINX.USER }}"
group: "{{ NGINX.USER }}"
mode: '0644'
copy:
src: logout.js
dest: "{{ INJ_LOGOUT_JS_DESTINATION }}"
owner: "{{ NGINX.USER }}"
group: "{{ NGINX.USER }}"
mode: '0644'
- name: Get stat for logout.js
stat:

View File

@@ -1,16 +1,16 @@
- block:
- include_tasks: 01_core.yml
- set_fact:
run_once_sys_front_inj_logout: true
- name: "Load base for '{{ application_id }}'"
include_tasks: 01_core.yml
when: run_once_sys_front_inj_logout is not defined
- name: "Load logout code for '{{ application_id }}'"
set_fact:
logout_code: "{{ lookup('template', 'logout_one_liner.js.j2') }}"
changed_when: false
- name: "Collapse logout code into one-liner for '{{ application_id }}'"
set_fact:
logout_code_one_liner: "{{ logout_code | to_one_liner }}"
changed_when: false
- name: "Append logout CSP hash for '{{ application_id }}'"
set_fact:

View File

@@ -1 +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>
<script src="{{ cdn_urls.shared.js }}/{{ INJ_LOGOUT_JS_FILE_NAME }}{{ lookup('local_mtime_qs', [playbook_dir, 'roles', 'sys-front-inj-logout', 'files', INJ_LOGOUT_JS_FILE_NAME] | path_join) }}"></script>

View File

@@ -1,10 +1,4 @@
- block:
- name: Include dependency 'sys-svc-webserver'
include_role:
name: sys-svc-webserver
when: run_once_sys_svc_webserver is not defined
- include_tasks: utils/run_once.yml
when: run_once_sys_front_inj_matomo is not defined
# run_once_sys_front_inj_matomo: deactivated
- name: "Relevant variables for role: {{ role_path | basename }}"
debug:

View File

@@ -1,19 +1,19 @@
- name: "Enable systemctl service"
systemd:
name: "{{ system_service_id | get_service_name(SOFTWARE_NAME) }}"
name: "{{ system_service_name }}"
enabled: yes
daemon_reload: true
become: true
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
async: "{{ system_service_async }}"
poll: "{{ system_service_poll }}"
listen: refresh systemctl service
- name: "Set systemctl service state"
systemd:
name: "{{ system_service_id | get_service_name(SOFTWARE_NAME) }}"
name: "{{ system_service_name }}"
state: "{{ system_service_state }}"
become: true
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
async: "{{ system_service_async }}"
poll: "{{ system_service_poll }}"
when: not (system_service_suppress_flush | bool)
listen: refresh systemctl service

View File

@@ -31,7 +31,7 @@
- name: "setup systemctl '{{ system_service_id }}'"
template:
src: "{{ system_service_template_src }}"
dest: "{{ [ PATH_SYSTEM_SERVICE_DIR, system_service_id | get_service_name(SOFTWARE_NAME) ] | path_join }}"
dest: "{{ [ PATH_SYSTEM_SERVICE_DIR, system_service_name ] | path_join }}"
owner: root
group: root
mode: '0644'
@@ -46,5 +46,5 @@
command: /bin/true
notify: refresh systemctl service
when: not system_service_uses_at
when: system_force_flush | bool
when: system_service_force_flush | bool

View File

@@ -1,22 +1,28 @@
UNIT_SUFFIX_REMOVER_PACKAGE: "unsure"
UNIT_SUFFIX_REMOVER_PACKAGE: "unsure"
system_service_name: "{{ system_service_id | get_service_name(SOFTWARE_NAME) }}"
## Paths
system_service_role_name: "{{ system_service_id | regex_replace('@','') }}"
system_service_role_dir: "{{ [ playbook_dir, 'roles', system_service_role_name ] | path_join }}"
system_service_script_dir: "{{ [ PATH_SYSTEMCTL_SCRIPTS, system_service_id ] | path_join }}"
system_service_role_name: "{{ system_service_id | regex_replace('@','') }}"
system_service_role_dir: "{{ [ playbook_dir, 'roles', system_service_role_name ] | path_join }}"
system_service_script_dir: "{{ [ PATH_SYSTEMCTL_SCRIPTS, system_service_id ] | path_join }}"
## Settings
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
system_service_state: "{{ SYS_SERVICE_DEFAULT_STATE }}"
system_service_force_linear_sync: "{{ system_service_name in SYS_SERVICE_GROUP_MANIPULATION }}" # Disables automatic async
system_service_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
system_service_state: "{{ SYS_SERVICE_DEFAULT_STATE }}"
## ASYNC Settings
system_service_async: "{{ omit if (system_service_force_linear_sync | bool or not ASYNC_ENABLED | bool) else ASYNC_TIME }}"
system_service_poll: "{{ omit if (system_service_force_linear_sync | bool or not ASYNC_ENABLED | bool) else ASYNC_POLL }}"
# Dynamic Loaded ( Just available when dependencies are loaded )
system_service_script_base: "{{ system_service_script_src | basename | regex_replace('\\.j2$', '') }}"
system_service_script_type: "{{ system_service_script_base | filetype }}"
system_service_script_inter: "/bin/{{ 'bash' if system_service_script_type == 'sh' else 'python3'}}"
system_service_script_exec: "{{ system_service_script_inter }} {{ system_service_id | get_service_script_path( system_service_script_type ) }}"
system_service_script_base: "{{ system_service_script_src | basename | regex_replace('\\.j2$', '') }}"
system_service_script_type: "{{ system_service_script_base | filetype }}"
system_service_script_inter: "/bin/{{ 'bash' if system_service_script_type == 'sh' else 'python3'}}"
system_service_script_exec: "{{ system_service_script_inter }} {{ system_service_id | get_service_script_path( system_service_script_type ) }}"
# Service template
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"

View File

@@ -0,0 +1,21 @@
# Front Base (HTTPS + Cloudflare + Handlers) 🚀
## Description
**sys-stk-front-base** bootstraps the front layer that most web-facing apps need:
- Ensures the HTTPS base via `sys-svc-webserver-https`
- (Optional) Cloudflare bootstrap (zone lookup, dev mode, purge)
- Wires OpenResty/Nginx handlers
- Leaves per-domain certificate issuance to consumer roles (or pass-through vars to `sys-util-csp-cert` if needed)
> This role is intentionally small and reusable. It prepares the ground so app roles can just render their vHost.
## Responsibilities
- Include `sys-svc-webserver-https` (once per host)
- Include Cloudflare tasks when `DNS_PROVIDER == "cloudflare"`
- Load handler utilities (e.g., `svc-prx-openresty`)
- Stay domain-agnostic: expect `domain` to be provided by the consumer
## Outputs
- Handler wiring completed
- HTTPS base ready (Nginx, ACME webroot)
- Cloudflare prepared (optional)

View File

@@ -1,6 +1,6 @@
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Updates AUR packages on Arch Linux systems using yay. This role automates the upgrade process for AUR packages, ensuring that the system remains up-to-date with the latest versions available in the Arch User Repository."
description: "Front bootstrap for web apps: HTTPS base, optional Cloudflare setup, and handler wiring."
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
@@ -9,16 +9,16 @@ galaxy_info:
https://www.veen.world
min_ansible_version: "2.9"
platforms:
- name: Archlinux
versions:
- rolling
- name: Archlinux
versions:
- rolling
galaxy_tags:
- aur
- update
- archlinux
- yay
- system
- maintenance
- nginx
- https
- cloudflare
- automation
- web
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://docs.infinito.nexus"
documentation: "https://docs.infinito.nexus/"
dependencies: []

View File

@@ -0,0 +1,14 @@
- block:
- name: Include dependency 'sys-svc-webserver-https'
include_role:
name: sys-svc-webserver-https
when: run_once_sys_svc_webserver_https is not defined
- include_tasks: utils/run_once.yml
when: run_once_sys_stk_front_base is not defined
- include_tasks: "01_cloudflare.yml"
when: DNS_PROVIDER == "cloudflare"
- include_tasks: "{{ [ playbook_dir, 'tasks/utils/load_handlers.yml' ] | path_join }}"
vars:
handler_role_name: "svc-prx-openresty"

View File

@@ -1,8 +1,5 @@
# default vhost flavour
vhost_flavour: "basic" # valid: basic, ws_generic
# build the full template path from the flavour
vhost_template_src: "roles/sys-svc-proxy/templates/vhost/{{ vhost_flavour }}.conf.j2"
vhost_flavour: "basic" # valid: basic, ws_generic
# Enable / Disable Proxy during development, for faster Debugging
SYS_STK_FRONT_PROXY_ENABLED: true

View File

@@ -1,26 +1,15 @@
- block:
- name: Include dependency 'sys-svc-proxy'
include_role:
name: sys-svc-proxy
when: run_once_sys_svc_proxy is not defined
- include_tasks: utils/run_once.yml
when: run_once_sys_stk_front_proxy is not defined
- name: Front bootstrap
include_role:
name: sys-stk-front-base
- include_tasks: "02_cloudflare.yml"
when: DNS_PROVIDER == "cloudflare"
- include_tasks: "{{ [ playbook_dir, 'tasks/utils/load_handlers.yml' ] | path_join }}"
vars:
handler_role_name: "svc-prx-openresty"
- name: "include role for {{ domain }} to receive certificates and do the modification routines"
- name: "include role for '{{ domain }}' to receive certificates and do the modification routines"
include_role:
name: sys-util-csp-cert
- name: "Copy nginx config to {{ configuration_destination }}"
- name: "Copy nginx config to '{{ front_proxy_domain_conf_dst }}'"
template:
src: "{{ vhost_template_src }}"
dest: "{{ configuration_destination }}"
src: "{{ front_proxy_domain_conf_src }}"
dest: "{{ front_proxy_domain_conf_dst }}"
register: nginx_conf
notify: restart openresty
@@ -39,4 +28,7 @@
when:
- site_check.status is defined
- not site_check.status in [200,301,302]
when: not nginx_conf.changed
when: not nginx_conf.changed
- name: "Restart Webserver for '{{ front_proxy_domain_conf_dst }}'"
meta: flush_handlers

View File

@@ -1 +1,2 @@
configuration_destination: "{{ [ NGINX.DIRECTORIES.HTTP.SERVERS, domain ~ '.conf'] | path_join }}"
front_proxy_domain_conf_dst: "{{ [ NGINX.DIRECTORIES.HTTP.SERVERS, domain ~ '.conf'] | path_join }}"
front_proxy_domain_conf_src: "roles/sys-svc-proxy/templates/vhost/{{ vhost_flavour }}.conf.j2"

View File

@@ -0,0 +1,13 @@
# Semi-Stateless Stack (Front + Back) ⚡
## Description
**sys-stk-semi-stateless** combines the front and back layer into a lightweight, mostly stateless web service stack:
- Front bootstrap via `sys-stk-front-base` (HTTPS base, optional Cloudflare, handlers)
- Backend via `sys-stk-back-stateless` (no persistent volumes/DB)
Ideal for services that need TLS/front glue but no database (e.g., TURN/STUN, gateways, simple APIs).
## Responsibilities
- Prepare the front layer (HTTPS / handlers / optional Cloudflare)
- Deploy the stateless backend (typically via Docker Compose)
- Keep domain variables (`domain`) and app-scoped variables (`application_id`) clearly separated

View File

@@ -0,0 +1,24 @@
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Combined semi-stateless app stack: front bootstrap + stateless backend."
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: Archlinux
versions:
- rolling
galaxy_tags:
- nginx
- https
- stateless
- backend
- cloudflare
- automation
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://docs.infinito.nexus/"

View File

@@ -0,0 +1,11 @@
# run_once_sys_stk_semi_stateless: deactivated
- name: "sys-stk-front-base"
include_role:
name: sys-stk-front-base
vars:
domain: "{{ domains | get_domain(application_id) }}"
- name: "For '{{ application_id }}': Load sys-stk-back-stateless"
include_role:
name: sys-stk-back-stateless

View File

@@ -1,21 +0,0 @@
- name: "Load CDN for '{{ domain }}'"
include_role:
name: web-svc-cdn
public: false
when:
- application_id != 'web-svc-cdn'
- run_once_web_svc_cdn is not defined
# ------------------------------------------------------------------
# Only-once creations (shared root and vendor)
# ------------------------------------------------------------------
- name: Ensure shared root and vendor exist (run once)
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ NGINX.USER }}"
group: "{{ NGINX.USER }}"
mode: "0755"
loop: "{{ CDN_DIRS_GLOBAL }}"
- include_tasks: utils/run_once.yml

View File

@@ -1,6 +1,14 @@
---
- block:
- include_tasks: 01_core.yml
- name: Ensure shared root and vendor exist (run once)
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ NGINX.USER }}"
group: "{{ NGINX.USER }}"
mode: "0755"
loop: "{{ CDN_DIRS_GLOBAL }}"
- include_tasks: utils/run_once.yml
when:
- run_once_sys_svc_cdn is not defined

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