Compare commits

...

333 Commits

Author SHA1 Message Date
445c94788e Refactor: consolidate pkgmgr updates and remove legacy roles
Details:
- Added pkgmgr update task directly in pkgmgr role (pkgmgr pull --all)
- Removed deprecated update-pkgmgr role and references
- Removed deprecated update-pip role and references
- Simplified update-compose by dropping update-pkgmgr include

https://chatgpt.com/share/68bbeff1-27a0-800f-bef3-03ab597595fd
2025-09-06 10:46:39 +02:00
aac9704e8b Refactor: remove legacy update-docker role and references
Details:
- Removed update-docker role (README, meta, vars, tasks, script)
- Cleaned references from group_vars, update-compose, and docs
- Adjusted web-app-matrix role (removed @todo pointing to update-docker)
- Updated administrator guide (update-docker no longer mentioned)

Ref: https://chatgpt.com/share/68bbeff1-27a0-800f-bef3-03ab597595fd
2025-09-06 10:32:33 +02:00
a57a5f8828 Refactor: remove Python-based Listmonk upgrade logic and implement upgrade as Ansible task
Details:
- Removed upgrade_listmonk() function and related calls from update-docker script
- Added dedicated Ansible task in web-app-listmonk role to run non-interactive DB/schema upgrade
- Conditional execution via MODE_UPDATE

Ref: https://chatgpt.com/share/68bbeff1-27a0-800f-bef3-03ab597595fd
2025-09-06 10:25:41 +02:00
90843726de keycloak: update realm mail settings to use smtp_server.json.j2 (SPOT); merge via kc_merge_path; fix display name and SSL handling
See: https://chatgpt.com/share/68bb0b25-96bc-800f-8ff7-9ca8d7c7af11
2025-09-05 18:09:33 +02:00
d25da76117 Solved wrong variable bug 2025-09-05 17:30:08 +02:00
d48a1b3c0a Solved missing variable bugs. Role is not fully implemented need to pause development on it for the moment 2025-09-05 17:07:15 +02:00
2839d2e1a4 In between commit Magento implementation 2025-09-05 17:01:13 +02:00
00c99e58e9 Cleaned up bridgy fed 2025-09-04 17:09:35 +02:00
904040589e Added correct variables and health check 2025-09-04 15:13:10 +02:00
9f3d300bca Removed unneccessary handlers 2025-09-04 14:04:53 +02:00
9e253a2d09 Bluesky: Patch hardcoded IPCC_URL and proxy /ipcc
- Added Ansible replace task to override IPCC_URL in geolocation.tsx to same-origin '/ipcc'
- Extended Nginx extra_locations.conf to proxy /ipcc requests to https://bsky.app/ipcc
- Ensures frontend avoids CORS errors when fetching IP geolocation

See: https://chatgpt.com/share/68b97be3-0278-800f-9ee0-94389ca3ac0c
2025-09-04 13:45:57 +02:00
49120b0dcf Added more CSP headers 2025-09-04 13:36:35 +02:00
b6f91ab9d3 changed database_user to database_username 2025-09-04 12:45:22 +02:00
77e8e7ed7e Magento 2.4.8 refactor:
- Switch to split containers (markoshust/magento-php:8.2-fpm + magento-nginx:latest)
- Disable central DB; use app-local MariaDB and pin to 11.4
- Composer bootstrap of Magento in php container (Adobe repo keys), idempotent via creates
- Make setup:install idempotent; run as container user 'app'
- Wire OpenSearch (security disabled) and depends_on ordering
- Add credentials schema (adobe_public_key/adobe_private_key)
- Update vars for php/nginx/search containers + MAGENTO_USER
- Remove legacy docs (Administration.md, Upgrade.md)
Context: changes derived from our ChatGPT session about getting Magento 2.4.8 running with MariaDB 11.4.
Conversation: https://chatgpt.com/share/68b8dc30-361c-800f-aa69-88df514cb160
2025-09-04 12:45:03 +02:00
32bc17e0c3 Optimized whitespacing 2025-09-04 12:41:11 +02:00
e294637cb6 Changed db config path attribut 2025-09-04 12:34:13 +02:00
577767bed6 sys-svc-rdbms: Refactor database service templates and add version support for Magento
- Unified Jinja2 variable spacing in tasks and templates
- Introduced database_image and database_version variables in vars/database.yml
- Updated mariadb.yml.j2 and postgres.yml.j2 to use {{ database_image }}:{{ database_version }}
- Ensured env file paths and includes are consistent
- Prepared support for versioned database images (needed for Magento deployment)

Ref: https://chatgpt.com/share/68b96a9d-c100-800f-856f-cd23d1eda2ed
2025-09-04 12:32:34 +02:00
e77f8da510 Added debug options to mastodon 2025-09-04 11:50:14 +02:00
4738b263ec Added docker_volume_path filter_plugin 2025-09-04 11:49:40 +02:00
0a588023a7 feat(bluesky): fix CORS by serving /config same-origin and pinning BAPP_CONFIG_URL
- Add `server.config_upstream_url` default in `roles/web-app-bluesky/config/main.yml`
  to define upstream for /config (defaults to https://ip.bsky.app/config).
- Introduce front-proxy injection `extra_locations.conf.j2` that:
  - proxies `/config` to the upstream,
  - sets SNI and correct Host header,
  - normalizes CORS headers for same-origin consumption.
- Wire the proxy injection only for the Web domain in
  `roles/web-app-bluesky/tasks/main.yml` via `proxy_extra_configuration`.
- Force fresh social-app checkout and patch
  `src/state/geolocation.tsx` to `const BAPP_CONFIG_URL = '/config'`
  in `roles/web-app-bluesky/tasks/02_social_app.yml`; notify `docker compose build` and `up`.
- Tidy and re-group PDS env in `roles/web-app-bluesky/templates/env.j2` (no functional change).
- Add vars in `roles/web-app-bluesky/vars/main.yml`:
  - `BLUESKY_FRONT_PROXY_CONTENT` (renders the extra locations),
  - `BLUESKY_CONFIG_UPSTREAM_URL` (reads `server.config_upstream_url`).

Security/Scope:
- Only affects the Bluesky web frontend (same-origin `/config`); PDS/API and AppView remain unchanged.

Refs:
- Conversation: https://chatgpt.com/share/68b8dd3a-2100-800f-959e-1495f6320aab
2025-09-04 02:29:10 +02:00
d2fa90774b Added fediverse bridge draft 2025-09-04 02:26:27 +02:00
0e72dcbe36 feat(magento): switch to ghcr.io/alexcheng1982/docker-magento2:2.4.6-p3; update Compose/Env/Tasks/Docs
• Docs: updated to MAGENTO_VOLUME; removed Installation/User_Administration guides
• Compose: volume path → /var/www/html; switched variables to MAGENTO_*/MYSQL_*/OPENSEARCH_*
• Env: new variable set + APACHE_SERVERNAME
• Task: setup:install via docker compose exec (multiline form)
• Schema: removed obsolete credentials definition
Link: https://chatgpt.com/share/68b8dc30-361c-800f-aa69-88df514cb160
2025-09-04 02:25:49 +02:00
4f8ce598a9 Mastodon: allow internal chess host & refactor var names; OpenLDAP: safer get_app_conf
- Add ALLOWED_PRIVATE_ADDRESSES to .env (from svc-db-postgres) to handle 422 Mastodon::PrivateNetworkAddressError
- Switch docker-compose to MASTODON_* variables and align vars/main.yml
- Always run 01_setup.yml during deployment (removed conditional flag)
- OpenLDAP: remove implicit True default on network.local to avoid unintended truthy behavior

Context: chess.infinito.nexus resolved to 192.168.200.30 (private IP) from Mastodon; targeted allowlist unblocks federation lookups.

Ref: https://chat.openai.com/share/REPLACE_WITH_THIS_CONVERSATION_LINK
2025-09-03 21:44:47 +02:00
3769e66d8d Updated CSP for bluesky 2025-09-03 20:55:21 +02:00
33a5fadf67 web-app-chess: fix Corepack/Yarn EACCES and switch to ARG-driven Dockerfile
• Add roles/web-app-chess/files/Dockerfile using build ARGs (CHESS_VERSION, CHESS_REPO_URL, CHESS_REPO_REF, CHESS_ENTRYPOINT_REL, CHESS_ENTRYPOINT_INT, CHESS_APP_DATA_DIR, CONTAINER_PORT). Enable Corepack/Yarn as root in the runtime stage to avoid EACCES on /usr/local/bin symlinks, then drop privileges to 'node'.

• Delete Jinja-based templates/Dockerfile.j2; docker-compose now passes former Jinja vars via build.args. • Update templates/docker-compose.yml.j2 to forward all required build args. • Update config/main.yml: add CSP flag 'script-src-elem: unsafe-inline'.

Ref: https://chatgpt.com/share/68b88d3d-3bd8-800f-9723-e8df0cdc37e2
2025-09-03 20:47:50 +02:00
699a6b6f1e feat(web-app-magento): add Magento role + network/ports
- add role files (docs, vars, config, tasks, schema, templates)

- networks: add web-app-magento 192.168.103.208/28

- ports: add localhost http 8052

Conversation: https://chatgpt.com/share/68b8820f-f864-800f-8819-da509b99cee2
2025-09-03 20:00:01 +02:00
61c29eee60 web-app-chess: build/runtime hardening & feature enablement
Build: use Yarn 4 via Corepack; immutable install with inline builds.

Runtime: enable Corepack as user 'node', use project-local cache (/app/.yarn/cache), add curl; fix ownership.

Entrypoint: generate keys in correct dir; run 'yarn install --immutable --inline-builds' before migrations; wait for Postgres.

Config: enable matomo/css/desktop; notify 'docker compose build' on entrypoint changes.

Docs: rename README title to 'Chess'.

Ref: ChatGPT conversation (2025-09-03) — https://chatgpt.com/share/68b88126-7a6c-800f-acae-ae61ed577f46
2025-09-03 19:56:13 +02:00
d5204fb5c2 Removed unnecessary env loading 2025-09-03 17:41:53 +02:00
751615b1a4 Changed 09_ports.yml to 10_ports.yml 2025-09-03 17:41:14 +02:00
e2993d2912 Added more CSP urls for bluesky 2025-09-03 17:31:29 +02:00
24b6647bfb Corrected variable 2025-09-03 17:30:31 +02:00
d2dc2eab5f web-app-bluesky: refactor role, add Cloudflare DNS integration, split tasks
Changes: add AppView port; add CSP whitelist; new tasks (01_pds, 02_social_app, 03_dns); switch templates to BLUESKY_* vars; update docker-compose and env; TCP healthcheck; remove admin_password from schema.

Conversation context: https://chatgpt.com/share/68b85276-e0ec-800f-90ec-480a1d528593
2025-09-03 16:37:35 +02:00
a1130e33d7 web-app-chess: refactor runtime & entrypoint
- Move entrypoint to files/ and deploy via copy
- Parameterize APP_KEY_FILE, data dir, and entrypoint paths
- Require explicit PORT/PG envs (remove fallbacks)
- Drop stray header from config/main.yml
- Dockerfile: use templated data dir & entrypoint; keep node user
- Compose: set custom image, adjust volume mapping
- env: derive APP_SCHEME from WEB_PROTOCOL; NODE_ENV from ENVIRONMENT
- tasks: add 01_core and simplify main to include it

Ref: https://chatgpt.com/share/68b851c5-4dd8-800f-8e9e-22b985597b8f
2025-09-03 16:34:04 +02:00
df122905eb mailu: include base defaults for oletools (env_file/LD_PRELOAD)
Add base include to oletools service so it inherits env_file (LD_PRELOAD=/usr/lib/libhardened_malloc.so) and other defaults. Fixes crash: PermissionError: '/proc/cpuinfo' during hardened_malloc compatibility probe when LD_PRELOAD was absent. Aligns oletools with other Mailu services.

Refs: ChatGPT discussion – https://chatgpt.com/share/68b837ba-c9cc-800f-b5d9-62b60d6fafd9
2025-09-03 14:42:50 +02:00
d093a22d61 Added correct CSP for JIRA 2025-09-03 11:35:24 +02:00
5e550ce3a3 sys-ctl-rpr-docker-soft: switch to STRICT label mode and adapt tests
- script.py now resolves docker-compose project and working_dir strictly from container labels
- removed container-name fallback logic
- adjusted sys-ctl-hlth-docker-container to include sys-ctl-rpr-docker-soft
- cleaned up sys-svc-docker dependencies
- updated unit tests to mock docker inspect and os.path.isfile for STRICT mode

Conversation: https://chatgpt.com/share/68b80927-b800-800f-a909-0fe8d110fd0e
2025-09-03 11:24:14 +02:00
0ada12e3ca Enabled rpr service by failed health checkl isntead of tiumer 2025-09-03 10:46:46 +02:00
1a5ce4a7fa web-app-bookwyrm, web-app-confluence:
- Fix BookWyrm email SSL/TLS handling (use ternary without 'not' for clarity)
- Add truststore_enabled flag in Confluence config and vars
- Wire JVM_SUPPORT_RECOMMENDED_ARGS to disable UPM signature check if truststore is disabled
- Add placeholder style.css.j2 for Confluence

See conversation: https://chatgpt.com/share/68b80024-7100-800f-a2fe-ba8b9f5cec05
2025-09-03 10:45:41 +02:00
a9abb3ce5d Added unsafe-eval csp to jira 2025-09-03 09:43:07 +02:00
71ceb339fc Fix Confluence & BookWyrm setup:
- Add docker compose build trigger in docker-compose tasks
- Cleanup svc-prx-openresty vars
- Enable unsafe-inline CSP flags for BookWyrm, Confluence, Jira to allow Atlassian inline scripts
- Generalize CONFLUENCE_HOME usage in vars, env and docker-compose
- Ensure confluence-init.properties written with correct home
- Add JVM_SUPPORT_RECOMMENDED_ARGS to pass atlassian.home
- Update README to reference {{ CONFLUENCE_HOME }}

See: https://chatgpt.com/share/68b7582a-aeb8-800f-a14f-e98c5b4e6c70
2025-09-02 22:49:02 +02:00
61bba3d2ef feat(bookwyrm): production-ready runtime + Redis wiring
- Dockerfile: build & install gunicorn wheels
- compose: run initdb before start; use `python -m gunicorn`
- env: add POSTGRES_* and BookWyrm Redis aliases (BROKER/ACTIVITY/CACHE) + CACHE_URL
- vars: add cache URL, DB indices, and URL aliases for Redis

Ref: https://chatgpt.com/share/68b7492b-3200-800f-80c4-295bc3233d68
2025-09-02 21:45:11 +02:00
0bde4295c7 Implemented correct confluence version 2025-09-02 17:01:58 +02:00
8059f272d5 Refactor Confluence and Jira env templates to use official Atlassian ATL_* database variables instead of unused custom placeholders. Ensures containers connect directly to PostgreSQL without relying on CONFLUENCE_DATABASE_* or JIRA_DATABASE_* vars. See conversation: https://chatgpt.com/share/68b6ddfd-3c44-800f-a57e-244dbd7ceeb5 2025-09-02 14:07:38 +02:00
7c814e6e83 BookWyrm: update Dockerfile and env handling
- Remove ARG BOOKWYRM_VERSION default, use Jinja variable directly
- Add proper SMTP environment variables mapping (EMAIL_HOST, EMAIL_PORT, TLS/SSL flags, user, password, default_from)
- Ensure env.j2 uses BookWyrm-expected names only
Ref: ChatGPT conversation 2025-09-02 https://chatgpt.com/share/68b6dc73-3784-800f-9a7e-340be498a412
2025-09-02 14:01:04 +02:00
d760c042c2 Atlassian JVM sizing: cast memory vars to int before floor-division
Apply |int to TOTAL_MB and dependent values to prevent 'unsupported operand type(s) for //' during templating in Confluence and Jira roles.

Context: discussion on 2025-09-02 — https://chatgpt.com/share/68b6d386-4490-800f-9bad-aa7be1571ebe
2025-09-02 13:22:59 +02:00
6cac8085a8 feat(web-app-chess): add castling.club role with ports, networks, and build setup
- Added network subnet (192.168.103.192/28) and port 8050 for web-app-chess
- Replaced stub README with usability-focused description of castling.club
- Implemented config, vars, meta, and tasks for web-app-chess
- Added Dockerfile, docker-compose.yml, env, and docker-entrypoint.sh templates
- Integrated entrypoint asset placement
- Updated meta to reflect usability and software features

Ref: https://chatgpt.com/share/68b6c65a-3de8-800f-86b2-a110920cd50e
2025-09-02 13:21:15 +02:00
3a83f3d14e Refactor BookWyrm role: switch to source-built Dockerfile, update README/meta for usability, add env improvements (ALLOWED_HOSTS, Redis vars, Celery broker), and pin version v0.7.5. See https://chatgpt.com/share/68b6d273-abc4-800f-ad3f-e1a5b9f8dad0 2025-09-02 13:18:32 +02:00
61d852c508 Added ports and networks for bookwyrm, jira, confluence 2025-09-02 12:08:20 +02:00
188b098503 Confluence/Jira roles: add READMEs, switch to custom images, proxy/JVM envs, and integer-safe heap sizing
Confluence: README added; demo disables OIDC/LDAP; Dockerfile overlay; docker-compose now uses CONFLUENCE_CUSTOM_IMAGE and DB depends include; env.j2 adds ATL_* and JVM_*; vars use integer math (//) for Xmx/Xms and expose CUSTOM_IMAGE.

Jira: initial role skeleton with README, config/meta/tasks; Dockerfile overlay; docker-compose using JIRA_CUSTOM_IMAGE and DB depends include; env.j2 with proxy + JVM envs; vars with integer-safe memory sizing.

Context: https://chatgpt.com/share/68b6b592-2250-800f-b68e-b37ae98dbe70
2025-09-02 12:07:34 +02:00
bc56940e55 Implement initial BookWyrm role
- Removed obsolete TODO.md
- Added config/main.yml with service, feature, CSP, and registration settings
- Added schema/main.yml defining vaulted SECRET_KEY (alphanumeric)
- Added tasks/main.yml to load stateful stack
- Added Dockerfile.j2 ensuring data/media dirs
- Added docker-compose.yml.j2 with application, worker, redis, volumes
- Added env.j2 with registration, secrets, DB, Redis, OIDC support
- Extended vars/main.yml with BookWyrm variables and OIDC, Docker, Redis settings
- Updated meta/main.yml with logo and run_after dependencies

Ref: https://chatgpt.com/share/68b6c060-3a0c-800f-89f8-e114a16a4a80
2025-09-02 12:03:11 +02:00
5dfc2efb5a Used port variable 2025-09-02 11:59:50 +02:00
7f9dc65b37 Add README.md files for web-app-bookwyrm, web-app-postmarks, and web-app-socialhome roles
Introduce integration test to ensure all web-app-* roles contain a README.md (required for Web App Desktop visibility)

See: https://chatgpt.com/share/68b6be49-7b78-800f-a3ff-bf922b4b083f
2025-09-02 11:52:34 +02:00
163a925096 fix(docker-compose): proper lock path + robust pull for buildable services
- Store pull lock under ${PATH_DOCKER_COMPOSE_PULL_LOCK_DIR}/<hash>.lock so global cleanup removes it reliably
- If any service defines `build:`, run `docker compose build --pull` before pulling
- Use `docker compose pull --ignore-buildable` when supported; otherwise tolerate pull failures for locally built images

This prevents failures when images are meant to be built locally (e.g., custom images) and ensures lock handling is consistent.

Ref: https://chatgpt.com/share/68b6b592-2250-800f-b68e-b37ae98dbe70
2025-09-02 11:15:28 +02:00
a8c88634b5 cleanup: remove unused handlers and add integration test for unused handlers
Removed obsolete handlers from roles (VirtualBox, backup-to-USB, OpenLDAP)
and introduced an integration test under tests/integration/test_handlers_invoked.py
that ensures all handlers defined in roles/*/handlers are actually notified
somewhere in the code base. This keeps the repository clean by preventing
unused or forgotten handlers from accumulating.

Ref: https://chatgpt.com/share/68b6b28e-4388-800f-87d2-34dfb34b8d36
2025-09-02 11:02:30 +02:00
ce3fe1cd51 Nextcloud: integrate Talk & Whiteboard; adjust ports & healthchecks
- Enable Spreed (Talk); signaling via /standalone-signaling/
- STUN/TURN: move STUN to 3480 (3479 occupied by BBB), keep TURN 5350 reserved
- docker-compose: expose internal WS ports; explicit TURN port mapping
- Healthchecks: add nc-based TCP checks (roles/docker-container/templates/healthcheck/nc.yml.j2)
- Nginx: location proxy to talk:8081
- Schema: add talk_* secrets (turn/signaling/internal)
- Plugins: configure spreed/whiteboard via vars/*; remove old task files
- Ports matrix (group_vars/all/09_ports.yml) updated/commented

Conversation: https://chatgpt.com/share/68b61a6a-e1dc-800f-b793-4aa600bc0166
2025-09-02 00:13:23 +02:00
7ca8b7c71d feat(nextcloud): integrate Talk & Whiteboard; refactor to NEXTCLOUD_* vars; full-stack setup
config(ports): add Nextcloud websocket port (4003); canonical domains (nextcloud/talk/whiteboard)

refactor: unify get_app_conf usage & Jinja spacing; migrate paths/handlers to new NEXTCLOUD_* vars

feat(plugins): split plugin routines; configure Whiteboard via occ (URL + JWT)

fix(oidc): use NEXTCLOUD_URL for logout; correct LDAP attribute mappings; add OIDC flavor switch

feat: Whiteboard container & reverse-proxy location; Talk STUN/WS ports; Redis URL for Whiteboard

chore: drop obsolete TODO; minor cleanups in oauth2-proxy, matrix, peertube, pgadmin, phpldapadmin, pixelfed, phpmyadmin

security(schema): Bluesky jwt_secret now base64_prefixed_32; add Nextcloud whiteboard_jwt_secret

db: normalize postgres image tag templating; central DB host checks spacing fixes

ops: add full-stack bootstrap (certs, proxy, volumes); internal nginx config reload handler update

refs: https://chatgpt.com/share/68b5f5b7-8d64-800f-b001-1241f818dc0e
2025-09-01 21:37:02 +02:00
110381e80c Refactored peertube role and implemented config volume 2025-09-01 18:19:50 +02:00
b02d88adc0 Refactored server roles for better readability 2025-09-01 18:08:35 +02:00
b7065837df MediaWiki: switch feature.css to false and add custom Vector 2022 override stylesheet
See: https://chatgpt.com/share/68b5b925-f418-800f-8f84-de744dd2d093
2025-09-01 17:18:12 +02:00
c98a2378c4 Added is defined condition 2025-09-01 17:05:30 +02:00
4ae3cee36c web-svc-logout: merge logout domains into CSP connect-src and refactor task flow
• Add tasks/01_core.yml to set applications[application_id].server.csp.whitelist['connect-src'] = LOGOUT_CONNECT_SRC_NEW.

• Switch tasks/main.yml to include 01_core.yml (run-once guard preserved).

• Update templates/env.j2 to emit LOGOUT_DOMAINS as a comma-separated list.

• Rework vars/main.yml: compute LOGOUT_DOMAINS, derive LOGOUT_ORIGINS with WEB_PROTOCOL, read connect-src via the get_app_conf filter, and merge/dedupe (unique).

Rationale: ensure CSP allows cross-domain logout requests for all configured services.

Conversation: https://chatgpt.com/share/68b5b07d-b208-800f-b6b2-f26934607c8a
2025-09-01 16:41:33 +02:00
b834f0c95c Implemented config image for pretix 2025-09-01 16:20:04 +02:00
9f734dff17 web-app-pretix: fix healthcheck and allowed hosts
- Add Host header to curl healthcheck when container_hostname is defined
- Use PRETIX_PRETIX_ALLOWED_HOSTS to fix Django 400 Bad Request during healthcheck
- Centralize PRETIX_HOSTNAME from container_hostname var
- Add Redis broker/result backend config for Celery

See: https://chatgpt.com/share/68b59c42-c0fc-800f-9bfb-f1137c59b3de
2025-09-01 15:15:04 +02:00
6fa4d00547 Refactor CDN and run_once handling
- Move run_once include from main.yml to 01_core.yml in desk-gnome-caffeine and desk-ssh
- Introduce sys-svc-cdn/01_core.yml to handle shared/vendor dirs once and role dirs per run
- Replace cdn.* with cdn_paths_all.* across inj roles
- Split cdn_dirs into cdn_dirs_role and CDN_DIRS_GLOBAL
- Ensure cdn_urls uses cdn_paths_all

Details: https://chatgpt.com/share/68b58d64-1e28-800f-8907-36926a9e9a9b
2025-09-01 14:11:36 +02:00
7254667186 Nextcloud: make app:update more robust by retrying once with retries/until (fixes transient migration errors)
See: https://chatgpt.com/share/68b57e29-4420-800f-b326-b34d09fa64b5
2025-09-01 13:06:44 +02:00
aaedaab3da refactor(web-app-mediawiki): unify debug & oidc handling via _ensure_require, introduce host-side prep, switch to bind mounts
- Removed obsolete Installation.md, TODO.md, 02_debug.yml, 05_oidc.yml and legacy debug enable/disable tasks
- Added 01_prep.yml to render debug.php/oidc.php on host side before container start
- Introduced _ensure_require.yml for generic require_once management in LocalSettings.php
- Renamed 01_install.yml -> 02_install.yml to align with new numbering
- Updated docker-compose.yml.j2 to bind-mount mw-local into /opt/mw-local
- Adjusted vars/main.yml to define MEDIAWIKI_LOCAL_MOUNT_DIR and MEDIAWIKI_LOCAL_PATH
- Templates debug.php.j2 and oidc.php.j2 now gated by MODE_DEBUG and MEDIAWIKI_OIDC_ENABLED
- main.yml now orchestrates prep, install, debug, extensions, oidc require, admin consistently

Ref: https://chatgpt.com/share/68b57db2-efcc-800f-a733-aca952298437
2025-09-01 13:04:57 +02:00
7791bd8c04 Implement filter checks: ensure all defined filters are used and remove dead code
Integration tests added/updated:
- tests/integration/test_filters_usage.py: AST-based detection of filter definitions (FilterModule.filters), robust Jinja detection ({{ ... }}, {% ... %}, {% filter ... %}), plus Python call tracking; fails if a filter is used only under tests/.
- tests/integration/test_filters_are_defined.py: inverse check — every filter used in .yml/.yaml/.j2/.jinja2/.tmpl must be defined locally. Scans only inside Jinja blocks and ignores pipes inside strings (e.g., lookup('pipe', "... | grep ... | awk ...")) to avoid false positives like trusted_hosts, woff/woff2, etc.

Bug fixes & robustness:
- Build regexes without %-string formatting to avoid ValueError from literal '%' in Jinja tags.
- Strip quoted strings in usage analysis so sed/grep/awk pipes are not miscounted as filters.
- Prevent self-matches in the defining file.

Cleanup / removal of dead code:
- Removed unused filter plugins and related unit tests:
  * filter_plugins/alias_domains_map.py
  * filter_plugins/get_application_id.py
  * filter_plugins/load_configuration.py
  * filter_plugins/safe.py
  * filter_plugins/safe_join.py
  * roles/svc-db-openldap/filter_plugins/build_ldap_nested_group_entries.py
  * roles/sys-ctl-bkp-docker-2-loc/filter_plugins/dict_to_cli_args.py
  * corresponding tests under tests/unit/*
- roles/svc-db-postgres/filter_plugins/split_postgres_connections.py: dropped no-longer-needed list_postgres_roles API; adjusted tests.

Misc:
- sys-stk-front-proxy/defaults/main.yml: clarified valid vhost_flavour values (comma-separated).

Ref: https://chatgpt.com/share/68b56bac-c4f8-800f-aeef-6708dbb44199
2025-09-01 11:47:51 +02:00
34b3f3b0ad Optimized healthcheck link for web-app-yourls 2025-09-01 10:54:08 +02:00
94fe58b5da safe_join: raise ValueError on None parameters and update tests
Changed safe_join to raise ValueError if base or tail is None instead of returning 'None/path'.
Adjusted unit tests accordingly to expect exceptions for None inputs and kept empty-string handling valid.

Ref: https://chatgpt.com/share/68b55850-e854-800f-9702-09ea956b8dc4
2025-09-01 10:25:08 +02:00
9feb766e6f replaced style-src-elem by style-src 2025-09-01 10:14:03 +02:00
231fd567b3 feat(frontend): rename inj roles to sys-front-*, add sys-svc-cdn, cache-busting lookup
Introduce sys-svc-cdn (cdn_paths/cdn_urls/cdn_dirs) and ensure CDN directories + latest symlink.

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

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

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

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

Conversation: https://chatgpt.com/share/68b55494-9ec4-800f-b559-44707029141d
2025-09-01 10:10:23 +02:00
3f8e7c1733 Refactor CSP filter:
- Move default 'unsafe-inline' for style-src and style-src-elem into get_csp_flags
- Ensure hashes are only added if 'unsafe-inline' not in final tokens
- Improve comments and structure
- Extend unit tests to cover default flags, overrides, and final-token logic
See: https://chatgpt.com/share/68b54520-5cfc-800f-9bac-45093740df78
2025-09-01 09:03:22 +02:00
3bfab9ef8e feat(filter_plugins/url_join): add query parameter support
- Support query elements starting with '?' or '&'
  * First query element normalized to '?', subsequent to '&'
  * Each query element must be exactly one 'key=value' pair
  * Query elements may only appear after path elements
  * Once query starts, no more path elements are allowed
- Extend test suite with success and failure cases for query handling

See: https://chatgpt.com/share/68b537ea-d198-800f-927a-940c4de832f2
2025-09-01 08:16:22 +02:00
f1870c07be refactor(filter_plugins/url_join): enforce mandatory scheme and raise specific AnsibleFilterError messages
Improved url_join filter:
- Requires first element to contain a valid '<scheme>://'
- Raises specific errors for None, empty list, wrong type, missing scheme,
  extra schemes in later parts, or string conversion failures
- Provides clearer error messages with index context in parts

See: https://chatgpt.com/share/68b537ea-d198-800f-927a-940c4de832f2
2025-09-01 08:06:48 +02:00
d0cec9a7d4 CSP filters: add explicit style-src-elem handling and improve unit tests
See ChatGPT conversation: https://chatgpt.com/share/68b4a82c-e0c8-800f-9273-9165ce1aa8d6
2025-08-31 21:53:39 +02:00
1dbd714a56 yourls: move container_port/healthcheck to vars; listen on 8080
• Removed hardcoded container_port/container_healthcheck from docker-compose.yml.j2
• Added container_port=8080 and container_healthcheck to vars/main.yml
• Rationale: current image listens on 8080; centralizes settings in vars

Ref: https://chatgpt.com/share/68b4a69d-e4b0-800f-a4f8-6c8e4fc55ee4
2025-08-31 21:48:24 +02:00
3a17b2979e Refactor CSP filters to use get_url for domain resolution and update tests to check CSP directives order-independently. See: https://chatgpt.com/share/68b49e5c-6774-800f-9d8e-a3f980799c08 2025-08-31 21:11:57 +02:00
bb0530c2ac Optimized yourls variables and healthcheck 2025-08-31 20:38:02 +02:00
aa2eb53776 fix(csp): always include internal CDN in script-src/connect-src and update tests accordingly
See ChatGPT conversation: https://chatgpt.com/share/68b492b8-847c-800f-82a9-fb890d4add7f
2025-08-31 20:22:05 +02:00
5f66c1a622 feat(postgres): add split_postgres_connections filter and average pool fact
Compute POSTGRES_ALLOWED_AVG_CONNECTIONS once and propagate to app roles (gitlab, mastodon, listmonk, matrix, pretix, mobilizon, openproject, discourse). Fix docker-compose postgres command (-c flags split). Add unit tests. Minor env/locale tweaks and includes.

Conversation: https://chatgpt.com/share/68b48e72-cc28-800f-9c21-270cbc17d82a
2025-08-31 20:04:14 +02:00
b3dfb8bf22 Fix: Resolved Discourse plugin bug and unified variable/path handling
- Discourse: fixed 'DISCOURSE_CONTAINERS_DIR' and 'DISCOURSE_APPLICATION_YML_DEST'
- Nextcloud: improved plugin enable/configure tasks formatting
- WordPress: unified OIDC, msmtp, and upload.ini variables and tasks
- General: aligned spacing and switched to path_join for consistency
2025-08-29 20:53:36 +02:00
db642c1c39 refactor(schedule): unify service timeouts, rename 08_timer.yml → 08_schedule.yml, fix docker repair/update timeouts, raise WP upload limit
See https://chatgpt.com/share/68b1deb9-2534-800f-b28f-7f19925b1fa7
2025-08-29 19:09:28 +02:00
2fccebbd1f Enforce uppercase README.md and TODO.md filenames
- Renamed all Readme.md → README.md
- Renamed all Todo.md → TODO.md
- Added integration test (tests/integration/test_filename_conventions.py) to automatically check naming convention.

Background:
Consistency in file naming (uppercase README.md and TODO.md) avoids issues with case-sensitive filesystems and ensures desktop cards (e.g. Pretix) are properly included.
Ref: https://chatgpt.com/share/68b1d135-c688-800f-9441-46a3cbfee175
2025-08-29 18:11:53 +02:00
c23fbd8ec4 Add new role web-app-confluence
Introduced a new Ansible role for deploying Atlassian Confluence within the Infinito.Nexus ecosystem.
The role follows the same structure as web-app-pretix and includes:

- : Core variables, database config, OIDC integration.
- : Docker service definitions, features (Matomo, CSS, OIDC, logout, central DB).
- : Loads docker, db and proxy stack.
- : Placeholder for schema definitions.
- :
  -  (base for OIDC plugins/extensions),
  -  (service orchestration),
  -  (environment configuration).
- : Metadata, license, company, logo (Font Awesome book-open icon).

Canonical domain is set to `confluence.{{ PRIMARY_DOMAIN }}`.
This role ensures Confluence integrates seamlessly with Keycloak OIDC and the Infinito.Nexus service stack.

Conversation: https://chatgpt.com/share/68b1d006-bbd4-800f-9d2e-9c8a8af2c00f
2025-08-29 18:07:01 +02:00
2999d9af77 web-app-pretix: fully implemented role
Summary:
- Replace draft with complete README (features, resources, credits).
- Remove obsolete Todo.md.
- Switch to custom image tag (PRETIX_IMAGE_CUSTOM) and install 'pretix-oidc' in Dockerfile.
- Drop unused 'config' volume; keep persistent 'data' only.
- Rename docker-compose service from 'application' to 'pretix' and use container_port.
- Use standard depends_on include for DB/Redis (dmbs_excl).
- Align vars to docker.services.pretix.* (image/version/name); add PRETIX_IMAGE_CUSTOM.

Breaking:
- Service key changed to 'pretix' under docker.services.
- 'config' volume removed from compose.

Status:
- Pretix role is now fully implemented and production-ready.

Reference:
- Conversation: https://chatgpt.com/share/68b1cb34-b7dc-800f-8b39-c183124972f2
2025-08-29 17:46:31 +02:00
2809ffb9f0 Added correct fa class and description for pretix 2025-08-29 17:02:50 +02:00
cb12114ce8 Added correct run_after for pretix 2025-08-29 16:47:21 +02:00
ba99e558f7 Improve SAN certbundle task logic and messages
- Fixed typo: 'seperat' → 'separate'
- Added more robust changed_when conditions (stdout + stderr, handle already-issued, rate-limit, service-down cases)
- Added explicit warnings for Let's Encrypt rate limits (exact set and generic)
- Improved readability of SAN encapsulation task with descriptive name

See conversation: https://chatgpt.com/share/68b1bc75-c3a0-800f-8861-fcf4f5f4a48c
2025-08-29 16:45:03 +02:00
2aed0f97d2 Enhance timeout_start_sec_for_domains filter to accept dict, list, or str
- Updated filter to handle dict (domain map), list (flattened domains), or single str inputs.
- Prevents duplicate 'www.' prefixes by checking prefix before adding.
- Adjusted unit tests:
  * Replaced old non-dict test with invalid type tests (int, None).
  * Added explicit tests for list and string input types.

See conversation: https://chatgpt.com/share/68b1ae9a-1ac0-800f-b49d-2915386a1a23
2025-08-29 15:57:00 +02:00
f36c7831b1 Implement dynamic TimeoutStartSec filter for domains and update roles
- Added new filter plugin 'timeout_start_sec_for_domains' to calculate TimeoutStartSec based on number of domains.
- Updated sys-ctl-hlth-csp and sys-ctl-hlth-webserver tasks to use the filter.
- Removed obsolete systemctl.service.j2 in sys-ctl-hlth-csp.
- Adjusted variable naming (CURRENT_PLAY_DOMAINS_ALL etc.) in multiple roles.
- Updated srv-letsencrypt and sys-svc-certs to use uppercase vars.
- Switched pretix role to sys-stk-full-stateful and removed leftover javascript.js.
- Added unittests for the new filter under tests/unit/filter_plugins.

See conversation: https://chatgpt.com/share/68b1ae9a-1ac0-800f-b49d-2915386a1a23
2025-08-29 15:44:31 +02:00
009bee531b Refactor role naming for TLS and proxy stack
- Renamed role `srv-tls-core` → `sys-svc-certs`
- Renamed role `srv-https-stack` → `sys-stk-front-pure`
- Renamed role `sys-stk-front` → `sys-stk-front-proxy`
- Updated all includes, READMEs, meta, and dependent roles accordingly

This improves clarity and consistency of naming conventions for certificate management and proxy orchestration.

See: https://chatgpt.com/share/68b19f2c-22b0-800f-ba9b-3f2c8fd427b0
2025-08-29 14:38:20 +02:00
4c7bb6d9db Solved path bugs and optimized them 2025-08-29 14:13:59 +02:00
092869b29a pretix: enable OIDC support
- add pretix-oidc plugin installation (Dockerfile, version 2.3.1 default)
- configure OIDC env vars (issuer, endpoints, client ID/secret, scopes, unique attribute)
- enable redis + database, add config/data volumes
- switch canonical domain to ticket.<PRIMARY_DOMAIN> with pretix.<PRIMARY_DOMAIN> alias
- mirror GitLab-style OIDC var structure for consistency

Implements pretix authentication via Keycloak/SSO.
See: https://chatgpt.com/share/68b19721-341c-800f-b372-527164474018
2025-08-29 14:04:03 +02:00
f4ea6c6c0f refactor(web-app-gitlab): restructure configuration and add OIDC support
- Added oidc feature flag in config
- Removed obsolete credentials schema (initial_root_password)
- Updated docker-compose.yml.j2 to use explicit GITLAB_* vars (image, version, container, volumes)
- Moved initial_root_password into vars/main.yml
- Introduced GITLAB_OMNIBUS_BASE and GITLAB_OMNIBUS_OIDC config lists
- Switched env.j2 to use GITLAB_OMNIBUS_ALL join

See conversation: https://chatgpt.com/share/68b1962c-3ee0-800f-a858-d4590ff6132a
2025-08-29 14:02:46 +02:00
3ed84717a7 Solved wireguard name bugs 2025-08-29 13:03:06 +02:00
1cfc2b7e23 Optimized mastodon url 2025-08-29 12:27:59 +02:00
01b9648650 Made OIDC secret UPPER 2025-08-29 12:27:29 +02:00
65d3b3040d Activated system_service_suppress_flush for journalctl servic4 2025-08-29 12:26:53 +02:00
28f7ac5aba Removed attendize because it isn't maintained anymore. Pretix is the successor. 2025-08-29 11:17:10 +02:00
19926b0c57 Optimized web-app-desktop variables 2025-08-29 11:04:52 +02:00
3a79d9d630 Optimized pkgmgr variables and removed 'Ensure main.py is executable' because it should be preset by repositories itself 2025-08-29 10:53:36 +02:00
983287a84a Finished mediawiki oidc implementation 2025-08-29 04:24:50 +02:00
dd9a9b6d84 feat(mediawiki): Refactor OIDC + debug; install Composer deps in-container; modularize role
Discussion: https://chatgpt.com/share/68b10c0a-c308-800f-93ac-2ffb386cf58b

- Split tasks into 01_install, 02_debug, 03_admin, 04_extensions, 05_oidc.
- Ensure unzip+git+composer on demand in the container; run Composer as www-data with COMPOSER_HOME=/tmp/composer.
- Idempotently unpack/install PluggableAuth & OpenIDConnect; run composer install only if vendor/ is missing.
- Add sanity check for Jumbojett\OpenIDConnectClient.
- Copy oidc.php only when changed and append a single require_once to LocalSettings.php.
- Use REL1_44-compatible numeric array for $wgPluggableAuth_Config; set $wgPluggableAuth_ButtonLabelMessage.
- Debug: add debug.php that logs to STDERR (visible via docker logs); toggle cleanly with MODE_DEBUG.
- Enable OIDC feature in config; add paths/OIDC/extension vars in vars/main.yml.

fix(services): include SYS_SERVICE_GROUP_CLEANUP in StartPre lock (ssd-hdd, docker-hard).

fix(desktop/joomla): simplify MODE_DEBUG templating.

chore: minor cleanups and renames.
2025-08-29 04:10:46 +02:00
23a2e081bf Optimized services 2025-08-29 01:11:06 +02:00
4cbd848026 Set SYS_TIMER_ALL_ENABLED ny default to DEBUG_MODE 2025-08-29 01:06:09 +02:00
d67f660152 Enabled CSS and Desktop for Mediawiki 2025-08-29 00:46:29 +02:00
5c6349321b Removed MyBB role, because it's deprecated and Discourse takes over 2025-08-29 00:12:35 +02:00
af1ee64246 web-app-mediawiki: installer-driven bootstrap, DB readiness, idempotent admin; drop LocalSettings bind-mount
Tasks:
- Enable docker_compose_flush_handlers=true so services come up immediately.
- Add DB readiness guard via maintenance/sql.php (SELECT 1).
- Run maintenance/install.php on empty schema with robust changed_when/failed_when (merge stdout+stderr); keep secrets hidden.
- Run maintenance/update.php for migrations with neutral changed_when unless work is done.
- Make admin creation idempotent: tolerate 'already exists' and 'Account exists', keep async+no_log.

Config changes:
- Remove LocalSettings.php template and its host bind-mount from compose.
- Drop MediaWiki settings path variables and META namespace variable (unused after switch).

Result: First boot is fully automated (schema + admin), subsequent runs are cleanly idempotent.

Ref: ChatGPT conversation (Aug 28, 2025, Europe/Berlin) — https://chatgpt.com/share/68b0d2e1-9bc0-800f-81a5-db03ce0b81e3.
2025-08-29 00:07:00 +02:00
d96bfc64a6 added possibility to deactivate docker service loading for performance 2025-08-28 22:47:05 +02:00
6ea8301364 Refactor: migrate cmp/* and srv/* roles into sys-stk/* and sys-svc/* namespaces
- Removed obsolete 'cmp' category, introduced 'stk' category (fa-bars-staggered icon).
- Renamed roles:
  * cmp-db-docker → sys-stk-back-stateful
  * cmp-docker-oauth2 → sys-stk-back-stateless
  * srv-domain-provision → sys-stk-front
  * cmp-db-docker-proxy → sys-stk-full-stateful
  * cmp-docker-proxy → sys-stk-full-stateless
  * cmp-rdbms → sys-svc-rdbms
- Updated all include_role references, vars, templates and README.md files.
- Adjusted run_once comments and variable paths accordingly.
- Updated all web-app roles to use new sys-stk/* and sys-svc/* roles.

Conversation: https://chatgpt.com/share/68b0ba66-09f8-800f-86fc-76c47009d431
2025-08-28 22:23:09 +02:00
92f5bf6481 refactor(web-app-mybb): remove obsolete Installation.md, introduce schema for secret_pin, and rework task/vars handling
- Removed outdated Installation.md (manual plugin instructions no longer needed)
- Added schema/main.yml with validation for secret_pin
- Added config.php.j2 template to manage DB + admin config
- Refactored tasks/main.yml to deploy config.php instead of legacy docker-compose
- Removed setup-domain.yml (TLS/domain handling moved to core roles)
- Updated docker-compose.yml.j2 to mount config.php and use new vars
- Cleaned up vars/main.yml: standardized MYBB_* variable names, added MYBB_SECRET_PIN, config paths, and container port

See ChatGPT conversation: https://chatgpt.com/share/68b0ae26-93ec-800f-8785-0da7c9303090
2025-08-28 21:29:58 +02:00
58c17bf043 web-app-mediawiki: template-driven LocalSettings.php + admin automation; compose & config tweaks
Config & features:
- roles/web-app-mediawiki/config/main.yml:
  - Add sitename ('Wiki on {{ PRIMARY_DOMAIN | upper }}') and meta_namespace ('Meta')
  - Enable central_database feature and database service
  - Move volumes under docker.volumes (correct indentation)

Tasks & automation:
- roles/web-app-mediawiki/tasks/main.yml:
  - Avoid immediate compose handler flush (docker_compose_flush_handlers: false), then explicit meta: flush_handlers
  - Deploy templated LocalSettings.php to host path
  - Create admin via maintenance/createAndPromote.php (docker exec, idempotent changed_when/failed_when)

Templates:
- roles/web-app-mediawiki/templates/LocalSettings.php.j2:
  - Set $wgSitename, $wgMetaNamespace, $wgServer from MEDIAWIKI_*
  - DB settings (mysql, host:port, name, user, password)
  - Mail settings (EmergencyContact/PasswordSender)
  - Default skin: vector
  - Load basic extensions (ParserFunctions, Cite)
- roles/web-app-mediawiki/templates/docker-compose.yml.j2:
  - Switch to MEDIAWIKI_* vars, mount LocalSettings.php (ro)
  - Use container_port, include curl healthcheck
  - Fix volumes name to MEDIAWIKI_VOLUME

Vars:
- roles/web-app-mediawiki/vars/main.yml:
  - Restructure with MEDIAWIKI_* (sitename, meta_namespace, URL, image/version/container/volume)
  - Define SETTINGS host/dock paths, container_port, default user (www-data)
  - Admin bootstrap vars (name/password/email)

Misc:
- Add empty schema/main.yml placeholder for future validation

Refs: ChatGPT conversation (2025-08-28, Europe/Berlin). Link: https://chatgpt.com/share/68b0ace6-f8f4-800f-b7a7-a51a6c5260f1
2025-08-28 21:28:47 +02:00
6c2d5c52c8 Attached 'not (system_service_suppress_flush | bool)' directly to handler 2025-08-28 21:16:04 +02:00
b919f39e35 Made stop unrequired for joomla container 2025-08-28 21:15:07 +02:00
9f2cfe65af Remove non-functional Joomla LDAP integration
- Disabled LDAP feature flag (set to false by default, with comment)
- Removed ldapautocreate plugin (PHP + XML)
- Deleted LDAP helper tasks (01_ldap_files.yml, 05_ldap.yml, 07_diagnose.yml)
- Deleted LDAP CLI helper scripts (cli.php, diagnose.php, plugins.php, auth-trace.php)
- Removed LDAP configuration variables from vars/main.yml
- Removed LDAP environment variables from env.j2
- Removed LDAP-specific mounts from docker-compose.yml.j2
- Dropped php-ldap installation from Dockerfile
- Renamed task files for consistent numbering (02->01_install, 03->02_debug, 04->03_patch, 06->04_assert)

Reason: LDAP integration was removed because it was not functional.

Conversation: https://chatgpt.com/share/68b09373-7aa8-800f-8f2c-11e27123bad1
2025-08-28 19:36:12 +02:00
fe399c3967 Added all LDAP changes before removing, because it doesn't work. Will trty to replace it by OIDC 2025-08-28 19:22:37 +02:00
ef801aa498 Joomla: Add LDAP autocreate plugin support
- Introduced autocreate_users feature flag in config/main.yml
- Added ldapautocreate.php and ldapautocreate.xml plugin files
- Implemented tasks/01_ldap_files.yml for plugin deployment
- Added tasks/05_ldap.yml to configure LDAP plugin and register ldapautocreate
- Renamed tasks for better structure (01→02, 02→03, etc.)
- Updated cli-ldap.php.j2 for clean parameter handling
- Mounted ldapautocreate plugin via docker-compose.yml.j2
- Extended vars/main.yml with LDAP autocreate configuration

Ref: https://chatgpt.com/share/68b0802f-bfd4-800f-b10a-57cf0c091f7e
2025-08-28 18:13:53 +02:00
18f3b1042f feat(web-app-joomla): reliable first-run install, safe debug toggler, DB patching, LDAP scaffolding
Why
- Fix flaky first-run installs and make config edits idempotent.
- Prepare LDAP support and allow optional inline CSP for UI.
- Improve observability and guard against broken configuration.php.

What
- config/main.yml: enable features.ldap; add CSP flags (allow inline style/script elem); minor spacing.
- tasks/: split into 01_install (wait for core, absolute CLI path), 02_debug (toggle $debug/$error_reporting safely), 03_patch (patch DB creds in configuration.php), 04_ldap (configure plugin via helper), 05_assert (optional php -l).
- templates/Dockerfile.j2: conditionally install/compile php-ldap (fallback to docker-php-ext-install with libsasl2-dev).
- templates/cli-ldap.php.j2: idempotently enable & configure Authentication - LDAP from env.
- templates/docker-compose.yml.j2: build custom image when LDAP is enabled; mount cli-ldap.php; pull_policy: never.
- templates/env.j2: add site/admin vars, MariaDB connector/env, full LDAP env.
- vars/main.yml: default to MariaDB (mysqli), add JOOMLA_* vars incl. JOOMLA_CONFIG_FILE.

Notes
- LDAP path implemented but NOT yet tested end-to-end.
- Ref: https://chatgpt.com/share/68b068a8-2aa4-800f-8cd1-56383561a9a8.
2025-08-28 16:33:45 +02:00
dece6228a4 Refactor docker-compose build logic and pull policy
- Added conditional '--pull' flag on retry in docker-compose build handler, tied to MODE_UPDATE
- Added 'pull_policy: never' to multiple docker-compose service templates to prevent unwanted image pulls
- Fixed minor formatting issues (e.g. Nextcloud volume spacing, WordPress desktop alignment)

Reference: https://chatgpt.com/share/68b0207a-4d9c-800f-b76f-9515885e5183
2025-08-28 11:25:35 +02:00
cb66fb2978 Refactor LDAP variable schema to use top-level constant LDAP and nested ALL-CAPS keys.
- Converted group_vars/all/13_ldap.yml from lower-case to ALL-CAPS nested keys.
- Updated all roles, tasks, templates, and filter_plugins to reference LDAP.* instead of ldap.*.
- Fixed Keycloak JSON templates to properly quote Jinja variables.
- Adjusted svc-db-openldap filter plugins and unit tests to handle new LDAP structure.
- Updated integration test to only check uniqueness of TOP-LEVEL ALL-CAPS constants, ignoring nested keys.

See: https://chatgpt.com/share/68b01017-efe0-800f-a508-7d7e2f1c8c8d
2025-08-28 10:15:48 +02:00
b9da6908ec keycloak(role): add realm support to generic updater
- Allow kc_object_kind='realm'
- Map endpoint to 'realms' and default lookup_field to 'id'
- Use realm-specific kcadm GET/UPDATE (no -r flag)
- Preserve immutables: id, realm
- Guard query-based ID resolution to non-realm objects

Context: fixing failure in 'Update REALM mail settings' task.
See: https://chatgpt.com/share/68affdb8-3d28-800f-8480-aa6a74000bf8
2025-08-28 08:57:29 +02:00
8baec17562 web-app-taiga: extract admin bootstrap into dedicated task; add robust upsert path
Add roles/web-app-taiga/tasks/01_administrator.yml to handle admin creation via 'createsuperuser' and, on failure, an upsert fallback using 'manage.py shell'. Ensures email, is_staff, is_superuser, is_active are set and password is updated when needed; emits CHANGED marker for idempotence.

Update roles/web-app-taiga/tasks/main.yml to include the new 01_administrator.yml task file, removing the inline admin logic for better separation of concerns.

Uses taiga-manage helper service and composes docker-compose.yml with docker-compose-inits.yml to inherit env/networks/volumes consistently.

Chat reference: https://chatgpt.com/share/68af7637-225c-800f-b670-2b948f5dea54
2025-08-27 23:58:37 +02:00
1401779a9d web-app-taiga: add manage/init flow and idempotent admin bootstrap; fix OIDC config and env quoting
config/main.yml: convert oidc from empty mapping to block; indent flavor under oidc; enable javascript feature.

tasks/main.yml: use path_join for taiga settings; create docker-compose-inits via TAIGA_DOCKER_COMPOSE_INIT_PATH; flush handlers; add idempotent createsuperuser via taiga-manage with async/poll and masked logs.

templates/docker-compose-inits.yml.j2: include compose/container base to inherit env and project settings.

templates/env.j2: quote WEB_PROTOCOL and WEBSOCKET_PROTOCOL.

templates/javascript.js.j2: add SSO warning include.

users/main.yml: add administrator email stub.

vars/main.yml: add js_application_name; restructure OIDC flavor flags; add compose PATH vars; expose TAIGA_SUPERUSER_* vars.

Chat reference: https://chatgpt.com/share/68af7637-225c-800f-b670-2b948f5dea54
2025-08-27 23:19:42 +02:00
707a3fc1d0 Optimized defaults for modes 2025-08-27 22:58:05 +02:00
d595d46e2e Solved unquoted bug 2025-08-27 22:30:03 +02:00
73d5651eea web-app-taiga: refactor OIDC gating + defaults
- Introduced dedicated variables in vars/main.yml:
  * TAIGA_FLAVOR_TAIGAIO
  * TAIGA_TAIGAIO_ENABLED
- Replaced inline Jinja2 get_app_conf checks with TAIGA_TAIGAIO_ENABLED for
  consistency in tasks, docker-compose template and env file.
- Adjusted env.j2 to use TAIGA_TAIGAIO_ENABLED instead of direct flavor checks.
- Enabled css by default (true instead of false).
- Cleaned up spacing/indentation in config and env.

This improves readability, reduces duplicated logic, and makes it easier to
maintain both OIDC flavors (robrotheram, taigaio).

Conversation: https://chatgpt.com/share/68af65b3-27c0-800f-964f-ff4f2d96ff5d
2025-08-27 22:08:35 +02:00
12a267827d Refactor websocket and Taiga variables
- Introduce WEBSOCKET_PROTOCOL derived from WEB_PROTOCOL (wss if https, else ws).
- Replace hardcoded websocket URLs in EspoCRM, Nextcloud and Taiga with {{ WEBSOCKET_PROTOCOL }}.
- Fix mautrix-imessage to use ws:// for internal synapse:8008.
- Standardize Pixelfed OIDC env spacing.
- Refactor Taiga variables to TAIGA_* naming convention and clean up EMAIL_BACKEND definition.

See: https://chatgpt.com/share/68af62fa-4dcc-800f-9aaf-cff746daab1e
2025-08-27 21:57:04 +02:00
c6cd6430bb Refactor Joomla role to new docker.* schema
- Move image definition from images.joomla to docker.services.joomla
- Add container name, container_port variable, and healthcheck
- Introduce JOOMLA_IMAGE, JOOMLA_VERSION, JOOMLA_CONTAINER, JOOMLA_VOLUME in vars
- Use volume mapping via docker.volumes.data

See: https://chatgpt.com/share/68af55a9-6514-800f-b6f7-1dc86356936e
2025-08-27 21:00:08 +02:00
67b2ebf001 Encapsulated code to pass performance tests 2025-08-27 20:58:00 +02:00
ebb6660473 Renamed Gitea variables 2025-08-27 20:49:35 +02:00
f62d09d8f1 Handle Let's Encrypt maintenance errors gracefully
- Extend certbundle task to ignore 'The service is down for maintenance or had an internal error'
  as a fatal failure.
- Add debug/warning output when this error occurs, so playbook does not stop but logs the issue.
- Ensure changed_when does not mark run as changed if only maintenance error was hit.

Ref: https://chatgpt.com/share/68af4e15-24cc-800f-b1dd-6a5f2380e35a
2025-08-27 20:28:25 +02:00
de159db918 web-app-wordpress: move msmtp configuration from Docker image to docker-compose mount
- Removed COPY of msmtp configuration from Dockerfile to avoid baking secrets/config into the image
- Added volume mount for host-side msmtp config ({{ WORDPRESS_HOST_MSMTP_CONF }}) in docker-compose.yml
- Keeps PHP upload.ini handling inside the image, but externalizes sensitive mail configuration
- Increases flexibility and avoids rebuilds when msmtp config changes

Ref: https://chatgpt.com/share/68af3c51-0544-800f-b76f-b2660c43addb
2025-08-27 19:12:03 +02:00
e2c2cf4bcf Updated sys-svc-msmtp execution condition 2025-08-27 18:12:49 +02:00
6e1e1ad5c5 Renamed pixelfed parameter 2025-08-27 18:11:31 +02:00
06baa4b03a Added correct validation handling 2025-08-27 18:10:49 +02:00
73e7fbdc8a refactor(web-app-wordpress): unify variable naming to uppercase WORDPRESS_* style
- Replaced all lowercase wordpress_* variables with uppercase WORDPRESS_* equivalents
- Ensured consistency across tasks, templates, and vars
- Improves readability and aligns with naming conventions

Conversation: https://chatgpt.com/share/68af29b5-8e7c-800f-bd12-48cc5956311c
2025-08-27 17:52:38 +02:00
bae2bc21ec Optimized system services included suppress option and solved bugs 2025-08-27 17:34:59 +02:00
a8f4dea9d2 Solved matrix name bug 2025-08-27 16:39:07 +02:00
5aaf2d28dc Refactor path handling, service conditions and dependencies
- Fixed incorrect filter usage in docker-compose handler (proper use of | path_join).
- Improved LetsEncrypt template by joining paths with filenames instead of appending manually.
- Enhanced sys-svc-msmtp task with an additional condition to only run if no-reply mailu_token exists.
- Updated Keycloak meta to depend on Mailu (ensuring token generation before setup).
- Refactored Keycloak import path variables to use path_join consistently.
- Adjusted Mailu meta dependency to run after Matomo instead of Keycloak.

See: https://chatgpt.com/share/68af13e6-edc0-800f-b76a-a5f427837173
2025-08-27 16:19:57 +02:00
5287bb4d74 Refactor Akaunting role and CSP handling
- Improved CSP filter to properly include web-svc-cdn and use protocol-aware domains
- Added Todo.md with redis and OIDC notes
- Enhanced Akaunting role config with CSP flags and redis option
- Updated schema to include app_key validation
- Reworked tasks to handle first-run marker logic cleanly
- Fixed docker-compose template (marker, healthcheck, setup flag)
- Expanded env.j2 with cache, email, proxy, and redis options
- Added javascript.js.j2 template for SSO warning
- Introduced structured vars for Akaunting role
- Removed deprecated update-repository-with-files.yml task

See conversation: https://chatgpt.com/share/68af00df-2c74-800f-90b6-6ac5b29acdcb
2025-08-27 14:58:44 +02:00
5446a1497e Optimized attendize role. Role can be removed as soon as pretix as alternative tool is implemented 2025-08-27 12:27:55 +02:00
19889a8cfc fix(credentials, akaunting):
- update cli/create/credentials.py to handle vault literals correctly:
  * strip 'vault |' headers and keep only ANSIBLE_VAULT body
  * skip reprocessing keys added in same run (no duplicate confirmation prompts)
  * detect both 'vault' and 'ANSIBLE_VAULT' as already encrypted

Refs: https://chatgpt.com/share/68aed780-ad4c-800f-877d-aa4c40a47755
2025-08-27 12:02:36 +02:00
d9980c0d8f feat(baserow): add one-time SSO warning JavaScript
- Introduced a generic sso_warning.js.j2 template under
  templates/roles/web-app/templates/javascripts/
- Included this template in web-app-baserow/templates/javascript.js.j2
- Added new variable js_application_name in
  roles/web-app-baserow/vars/main.yml to make the warning
  application-specific
- Implemented cookie-based logic so the warning is only shown once
  per user (default: 365 days)

Reference: https://chatgpt.com/share/68aecdae-82d0-800f-b05e-f2cb680664f1
2025-08-27 11:19:59 +02:00
35206aaafd Solved undeclared docker compose variable bug 2025-08-26 22:35:41 +02:00
942e8c9c12 Updated baserow CSP adn variables for new Infinito.Nexus structure 2025-08-26 22:20:31 +02:00
97f4045c68 Keycloak: align client attributes with realm dictionary
- Extended kc_force_attrs in tasks/main.yml to source 'publicClient',
  'serviceAccountsEnabled' and 'frontchannelLogout' directly from
  KEYCLOAK_DICTIONARY_REALM for consistency with import definitions.
- Updated default.json.j2 import template to set 'publicClient' to true.
- Public client mode is required so the frontend API of role 'web-app-desktop'
  can handle login/logout flows without client secret.

Ref: https://chatgpt.com/share/68ae0060-4fac-800f-9f02-22592a4087d3
2025-08-26 21:22:27 +02:00
c182ecf516 Refactor and cleanup OIDC, desktop, and web-app roles
- Improved OIDC variable definitions (12_oidc.yml)
- Added account/security/profile URLs
- Restructured web-app-desktop tasks and JS handling
- Introduced oidc.js and iframe.js with runtime loader
- Fixed nginx.conf, LDAP, and healthcheck templates spacing
- Improved Lua injection for CSP and snippets
- Fixed typos (WordPress, receive, etc.)
- Added silent-check-sso nginx location

Conversation: https://chatgpt.com/share/68ae0060-4fac-800f-9f02-22592a4087d3
2025-08-26 20:44:05 +02:00
ce033c370a Removed waiting for other services, otherwise it ends up breaking, waiting for hard restart service 2025-08-26 19:23:47 +02:00
a0477ad54c Switched OnFailure with StartPost 2025-08-26 19:10:41 +02:00
35c3681f55 sys-daemon & sys-service: align timeout handling
- Updated sys-daemon defaults:
  * Increased SYSTEMD_DEFAULT_TIMEOUT_START to 24h
  * Improved inline comments for clarity
- Changed sys-service vars:
  * Removed hardcoded 60s TimeoutStartSec
  * Now empty by default → inherits manager defaults from sys-daemon

See: https://chatgpt.com/share/68ade432-67f8-800f-b6c2-b8f87764479b
2025-08-26 18:48:45 +02:00
af97e71976 Fix: correct Docker Go template syntax in sys-ctl-rpr-docker-soft script
Replaced over-escaped '{{{{.Names}}}}' with proper '{{.Names}}'
in docker ps commands. This resolves 'failed to parse template:
unexpected "{" in command' errors during unhealthy/exited
container detection.

Reference: https://chatgpt.com/share/68addfd9-fa78-800f-abda-49161699e673
2025-08-26 18:25:25 +02:00
19a51fd718 Solved linebreak bug 2025-08-26 17:13:29 +02:00
b916173422 Renamed web-app-port-ui to web-app-desktop 2025-08-26 11:35:22 +02:00
9756a0f75f Extend repair scripts with env-file support and unit tests
- Added detect_env_file() to both sys-ctl-rpr-docker-soft and sys-ctl-rpr-docker-hard
  * prefer .env, fallback to .env/env
  * append --env-file parameter automatically
- Refactored soft script to use compose_cmd() for consistent command building
- Adjusted error recovery path in soft script to also respect env-file
- Extended unit tests for soft script to cover env-file priority and restart commands
- Added new unit tests for hard script verifying env-file priority, cwd handling,
  and --only filter logic

Ref: https://chatgpt.com/share/68ad7b30-7510-800f-8172-56f03a2f40f5
2025-08-26 11:15:59 +02:00
e417bc19bd Refactor sys-ctl-rpr-docker-soft role to use standalone Python script with argparse and unittests
- Replace Jinja2 template (script.py.j2) with raw Python script (files/script.py)
- Add argparse options: --manipulation, --manipulation-string, --timeout
- Implement timeout handling in wait_while_manipulation_running
- Update systemd ExecStart/ExecStartPre handling in tasks/01_core.yml
- Remove obsolete systemctl.service.j2 and script.py.j2 templates
- Add unittest suite under tests/unit/roles/sys-ctl-rpr-docker-soft/files/test_script.py
- Mock docker and systemctl calls in tests for safe execution

Reference: ChatGPT conversation (see https://chatgpt.com/share/68ad770b-ea84-800f-b378-559cb61fc43a)
2025-08-26 10:58:17 +02:00
7ad14673e1 sys-service: add ExecStartPost support and adjust health/repair roles
- extended generic systemctl template to support ExecStartPost
- health-docker-volumes: run main script with whitelist, trigger both compose alarm and cleanup on failure
- repair-docker-hard: added ExecStartPre lock, ExecStart, and ExecStartPost to trigger compose alarm always, plus cleanup on failure
- removed obsolete role-specific systemctl.service.j2 templates
- improved consistency across vars and defaults

See: https://chatgpt.com/share/68ad6cb8-c164-800f-96b6-a45c6c7779b3
2025-08-26 10:15:35 +02:00
eb781dbf8b fix(keycloak/ldap): make userObjectClasses JSON-safe and exclude posixAccount
- Render userObjectClasses via `tojson` (and trim) to avoid invalid control
  characters and ensure valid realm import parsing.
- Introduce KEYCLOAK_LDAP_USER_OBJECT_CLASSES in vars; exclude `posixAccount`
  for Keycloak’s LDAP config while keeping it for Ansible-managed UNIX users.
- Update UserStorageProvider template to use the new variable.

Rationale:
Keycloak must not require `posixAccount` on every LDAP user. We keep
`posixAccount` structural for Ansible provisioning, but filter it out for
Keycloak to prevent sync/import errors on entries without POSIX attributes.

Touched:
- roles/web-app-keycloak/templates/import/components/org.keycloak.storage.UserStorageProvider.json.j2
- roles/web-app-keycloak/vars/main.yml

Refs: conversation https://chatgpt.com/share/68aa1ef0-3658-800f-bdf4-5b57131d03b4
2025-08-23 22:05:26 +02:00
6016da6f1f Optimized bbb variables 2025-08-23 19:21:07 +02:00
8b2f0ac47b refactor(web-app-espocrm): improve config patching and container vars
- Replace `ESPOCRM_NAME` with `ESPOCRM_CONTAINER` for clarity and consistency.
- Drop unused `ESPOCRM_CONFIG_FILE_PUBLIC`, rely only on `config-internal.php`.
- Make DB credential patching idempotent using `grep` + `sed` checks.
- Replace direct sed edits for maintenance/cron/cache with EspoCRM ConfigWriter.
- Add fallback execution as root if www-data user cannot write config.
- Clear EspoCRM cache only when config changes and in update mode.
- Remove obsolete OIDC scopes inline task (now handled via env/vars).
- Fix docker-compose template to use `ESPOCRM_CONTAINER`.

This refactor makes the EspoCRM role more robust, idempotent, and aligned
with EspoCRM’s official ConfigWriter mechanism.

See conversation: https://chatgpt.com/share/68a87820-12f8-800f-90d6-01ba97a1b279
2025-08-22 16:01:48 +02:00
9d6d64e11d Renamed espocrm data volume 2025-08-22 14:49:25 +02:00
f1a2967a37 Implemented sys-svc-cln-anon-volumes as service so that it can be triggert after sys-ctl-rpr-docker-hard 2025-08-22 14:48:50 +02:00
95a2172fff Corrected link 2025-08-22 09:23:40 +02:00
dc3f4e05a8 sys-ctl-rpr-docker-hard: Refactor restart script with argparse & update systemd ExecStart
- Removed unused soft restart function and switched to argparse-based CLI.
- Added --only argument to selectively restart subdirectories.
- Updated systemctl service template to pass PATH_DOCKER_COMPOSE_INSTANCES as argument.
- Ensures service unit correctly invokes the Python script with target path.

See conversation: https://chatgpt.com/share/68a771d9-5fd8-800f-a410-08132699cc3a
2025-08-21 21:22:29 +02:00
e33944cda2 Solved service ignore parameter bugs 2025-08-21 21:04:21 +02:00
efa68cc1e0 sys-ctl: make service file generation deterministic and simplify ignore logic
- Added '| sort' to all service group lists and backup routine lists to ensure
  deterministic ordering and stable checksums across Ansible runs.
- Adjusted systemctl templates to use a single service variable
  ('SYS_SERVICE_BACKUP_RMT_2_LOC') instead of rejecting dynamic list entries,
  making the ignore logic simpler and more predictable.
- Fixed minor whitespace inconsistencies in Jinja templates to avoid
  unnecessary changes.

This change was made to prevent spurious 'changed' states in Ansible caused by
non-deterministic list order and to reduce complexity in service definitions.

See discussion: https://chatgpt.com/share/68a74c20-6300-800f-a44e-da43ae2f3dea
2025-08-21 18:43:17 +02:00
79e702a3ab web-svc-collabora: localize vars, adjust CSP, fix systemd perms; refactor role composition
- sys-service:
  - Set explicit ownership and permissions for generated unit files:
    owner=root, group=root, mode=0644. Prevents drift and makes idempotence
    predictable when handlers reload/refresh systemd.

- web-svc-collabora:
  - Move cmp-docker-proxy include into tasks/01_core.yml and run it
    before Nginx config generation. Use public: true only to initialize the
    proxy/compose context and docker_compose_flush_handlers: true to ensure
    timely handler execution.
  - Define role-local variables domain and http_port in vars/main.yml
    and use {{ domain }} for the Nginx server file path. These values MUST
    be defined locally because they cannot be reliably imported via
    public: true — other roles may override them later in the play, leading
    to leakage and nondeterministic behavior. Localizing avoids precedence
    conflicts without resorting to host-wide set_fact.
  - CSP adjusted: add server.security.flags.style-src.unsafe-inline: true
    to accommodate Collabora’s inline styles (requested as “csr” in notes).
  - Minor variable alignment/cleanup and TODO note for future refactor.

- Housekeeping:
  - Rename task title to reflect {{ domain }} usage.

Refs:
- Discussion and rationale in this chat https://chatgpt.com/share/68a731aa-d394-800f-9eb4-2499f45ed54b (2025-08-21, Europe/Berlin).
2025-08-21 16:48:37 +02:00
9180182d5b Optimized variables 2025-08-21 16:27:10 +02:00
535094d15d Added more update tasks for ESPOCRM config 2025-08-21 16:23:08 +02:00
658003f5b9 Added test user entry 2025-08-21 09:56:50 +02:00
3ff783df17 Updated mailu move docs 2025-08-21 09:49:36 +02:00
3df511aee9 Changed constructor order. emails need to be defned before users 2025-08-20 18:54:44 +02:00
c27d16322b Optimized variables 2025-08-20 18:17:13 +02:00
7a6e273ea4 In between commit, updated matrix and optimized mailu 2025-08-20 17:51:17 +02:00
384beae7c1 Added task to update default email settings 2025-08-20 16:41:53 +02:00
ad7e61e8b1 Set default buffer level for proxy basic conf, which are necessary for OIDC login 2025-08-20 15:56:32 +02:00
fa46523433 Update trusted domains for matomo 2025-08-20 15:35:08 +02:00
f4a380d802 Optimized alarm and system handlers 2025-08-20 15:17:04 +02:00
42d6c1799b sys-service: add systemd_directive filter and refactor service template
Introduced custom filter plugin to render optional systemd directives, refactored template to loop over directives, and adjusted default vars (TimeoutStartSec, RuntimeMaxSec handling).

Details: see ChatGPT conversation
https://chatgpt.com/share/68a5a730-6344-800f-b9a3-dc62d5902e9b
2025-08-20 12:46:07 +02:00
8608d89653 Implemented correct template for collabora 2025-08-20 09:07:33 +02:00
a4f39ac732 Renamed webserver roles to more speakable names 2025-08-20 08:54:17 +02:00
9cfb8f3a60 Different optimations for collabora 2025-08-20 08:34:12 +02:00
3e5344a46c Optimized Collabora CSP for Nextcloud 2025-08-20 07:03:02 +02:00
ec07d1a20b Added logic to start docker compose pull just once per directory 2025-08-20 07:02:27 +02:00
594d9417d1 handlers(docker): add once-per-directory docker compose pull with lockfile
- Introduced a new handler 'docker compose pull' that runs only once per
  {{ docker_compose.directories.instance }} directory by using a lock
  file under /run/ansible/compose-pull.
- Ensures idempotency by marking the task as changed only when a pull
  was actually executed.
- Restricted execution with 'when: MODE_UPDATE | bool'.
- Improves update workflow by avoiding redundant docker pulls during
  the same Ansible run.

Reference: ChatGPT discussion
https://chatgpt.com/share/68a55151-959c-800f-8b70-160ffe43e776
2025-08-20 06:42:49 +02:00
dc125e4843 Solved path bug 2025-08-20 06:18:52 +02:00
39a54294dd Moved update commands to nextcloud role 2025-08-20 06:07:33 +02:00
a57fe718de Optimized spacinbg 2025-08-20 05:49:35 +02:00
b6aec5fe33 Optimized features 2025-08-20 05:39:49 +02:00
de07d890dc Solvewd 'sys-ctl-bkp-docker-2-loc' bug 2025-08-20 05:25:24 +02:00
e27f355697 Solvewd tabulator bug 2025-08-20 05:02:16 +02:00
790762d397 Renamed some web apps to web servicesy 2025-08-20 05:00:24 +02:00
4ce681e643 Add integration test: ensure roles including 'sys-service' define system_service_id
This test scans all roles for tasks including:
  - include_role:
      name: sys-service

If present, the role must define a non-empty 'system_service_id' in vars/main.yml.
Helps enforce consistency and prevent misconfiguration.

Ref: https://chatgpt.com/share/68a536e5-c384-800f-937a-f9d91249950c
2025-08-20 04:46:27 +02:00
55cf3d0d8e Solved unit performance tests 2025-08-20 04:35:46 +02:00
2708b67751 Optimized webserver on failure 2025-08-20 04:12:42 +02:00
f477ee3731 Deactivated redis, moved version to correct place for web-svc-collabora 2025-08-20 03:40:37 +02:00
6d70f78989 fix(domain-filters): support dependency expansion via seed param
- Added missing 'Iterable' import in 'canonical_domains_map' to avoid NameError.
- Introduced 'seed' parameter so the filter can start traversal from current play apps
  while still emitting canonical domains for discovered dependencies (e.g. web-svc-collabora).
- Updated 01_constructor.yml to pass full 'applications' and a clean 'seed' list
  (using dict2items → key) instead of '.keys()' method calls, fixing integration
  test error: 'reference to application keys is invalid'.

This resolves issues where collabora domains were missing and integration tests failed.

Ref: https://chatgpt.com/share/68a51f9b-3924-800f-a41b-803d8dd10397
2025-08-20 03:07:14 +02:00
b867a52471 Refactor and extend role dependency resolution:
- Introduced module_utils/role_dependency_resolver.py with full support for include_role, import_role, meta dependencies, and run_after.
- Refactored cli/build/tree.py to use RoleDependencyResolver (added toggles for include/import/dependencies/run_after).
- Extended filter_plugins/canonical_domains_map.py with optional 'recursive' mode (ignores run_after by design).
- Updated roles/web-app-nextcloud to properly include Collabora dependency.
- Added comprehensive unittests under tests/unit/module_utils for RoleDependencyResolver.

Ref: https://chatgpt.com/share/68a519c8-8e54-800f-83c0-be38546620d9
2025-08-20 02:42:07 +02:00
78ee3e3c64 Deactivated on_failure for telegram and email 2025-08-20 01:20:06 +02:00
d7ece2a8c3 Optimized message 2025-08-20 01:03:07 +02:00
3794aa87b0 Optimized spacing 2025-08-20 01:02:29 +02:00
4cf996b1bb Removed old collabora 2025-08-20 01:02:11 +02:00
79517b2fe9 Optimized spacing 2025-08-20 01:01:32 +02:00
a84ee1240a Optimized collabora name 2025-08-20 01:00:51 +02:00
7019b307c5 Optimized collabora draft 2025-08-20 01:00:20 +02:00
838a8fc7a1 Solved svc-opt-ssd-hdd path bug 2025-08-19 21:50:55 +02:00
95aba805c0 Removed variable which leads to bugs in other contexts 2025-08-19 20:50:08 +02:00
0856c340c7 Removed unnecessary logic 2025-08-19 20:35:02 +02:00
b90a2f6c87 sys-ctl-alm-{email,telegram}: unescape instance names before alerts
Use `systemd-escape --unescape` to restore human-readable unit identifiers in
Telegram and Email alerts. Also ensure Telegram messages are URL-encoded and
Email status checks try both raw and escaped forms for robustness.

Fixes issue where slashes were shown as dashes in notifications.

Context: see ChatGPT conversation
https://chatgpt.com/share/68a4c171-db08-800f-8399-7e07f237a441
2025-08-19 20:25:15 +02:00
98e045196b Removed cleanup service lock 2025-08-19 19:06:58 +02:00
a10dd402b8 refactor: improve service handling and introduce MODE_ASSERT
- Improved get_service_name filter plugin (clearer suffix handling, consistent var names).
- Added MODE_ASSERT flag to optionally execute validation/assertion tasks.
- Fixed systemd unit handling: consistent use of %I instead of %i, correct escaping of instance names.
- Unified on_failure behavior and alarm composer scripts.
- Cleaned up redundant logging, handlers, and debug config.
- Strengthened sys-service template resolution with assert (only active when MODE_ASSERT).
- Simplified timer and suffix handling with get_service_name filter.
- Hardened sensitive tasks with no_log.
- Added conditional asserts across roles (Keycloak, DNS, Mailu, Discourse, etc.).

These changes improve consistency, safety, and validation across the automation stack.

Conversation: https://chatgpt.com/share/68a4ae28-483c-800f-b2f7-f64c7124c274
2025-08-19 19:02:52 +02:00
6e538eabc8 Enhance tree builder: detect include_role dependencies from tasks/*.yml
- Added logic to scan each role’s tasks/*.yml files for include_role usage
- Supports:
  * loop/with_items with literal strings → adds each role
  * patterns with variables inside literals (e.g. svc-db-{{database_type}}) → expanded to glob and matched
  * pure variable-only names ({{var}}) → ignored
  * pure literal names → added directly
- Merges discovered dependencies under graphs["dependencies"]["include_role"]
- Added dedicated unit test covering looped includes, glob patterns, pure literals, and ignoring pure variables

See ChatGPT conversation (https://chatgpt.com/share/68a4ace0-7268-800f-bd32-b475c5c9ba1d) for context.
2025-08-19 19:00:03 +02:00
82cc24a7f5 Added reset condition for openresty 2025-08-19 17:48:02 +02:00
26b392ea76 refactor!: replace sys-systemctl with sys-service, add sys-daemon, and rename systemctl_* → system_service_* across repo
- Swap role includes: sys-systemctl → sys-service in all roles
- Rename variables everywhere: systemctl_* → system_service_* (incl. systemctl_id → system_service_id)
- Templates: ExecStart now uses {{ system_service_script_exec }}; add optional RuntimeMaxSec via SYS_SERVICE_DEFAULT_RUNTIME
- Move SYS_SERVICE defaults into roles/sys-service/defaults (remove SYS_SERVICE_ALL_ENABLED & SYS_SERVICE_DEFAULT_STATE from group_vars/07_services.yml)
- Tidy group_vars/all/08_timer.yml formatting
- Introduce roles/sys-daemon:
  - default manager timeouts (timeouts.conf)
  - optional purge of /etc/systemd/system.conf.d
  - validation via systemd-analyze verify
  - handlers for daemon-reload & daemon-reexec
- Refactor sys-timer to system_service_* variables (docs and templates updated)
- Move filter_plugins/filetype.py under sys-service
- Update meta/README to point to official systemd docs
- Touch many roles (backup/cleanup/health/repair/certs/nginx/csp/wireguard/ssd-hdd/keyboard/update-docker/alarm compose/email/telegram/etc.) to new naming

BREAKING CHANGE:
- Role path/name change: use `sys-service` instead of `sys-systemctl`
- All `systemctl_*` vars are now `system_service_*` (e.g., on_calendar, state, timer_enabled, script_exec, id)
- If you have custom templates, adopt RuntimeMaxSec and new variable names

Chat context: https://chatgpt.com/share/68a47568-312c-800f-af3f-e98575446327
2025-08-19 15:00:44 +02:00
b49fdc509e Refactor alarm compose service and systemctl templates
- Fixed bug where not both alarm services (email + telegram) were triggered.
- Removed direct OnFailure references for email and telegram,
  now handled by unified compose service.
- Introduced 01_core.yml in sys-ctl-alm-compose to structure
  role execution (subservices → core service → test run).
- Added configurable variables SYSTEMCTL_ALARM_COMPOSER_SUBSERVICES
  and SYSTEMCTL_ALARM_COMPOSER_DUMMY_MESSAGE.
- Replaced dedicated @.service template with generic systemctl template
  using systemctl_tpl_* variables for flexibility.
- Updated script.sh.j2 to collect exit codes and print clear errors.
- Fixed typos and streamlined vars in sys-systemctl.

See conversation: https://chatgpt.com/share/68a46172-7c3c-800f-a69c-0cb9edd6839f
2025-08-19 13:35:39 +02:00
b1e8339283 Added /bin/systemctl start {{ SYS_SERVICE_CLEANUP_BACKUPS_OLD }} 2025-08-19 12:56:25 +02:00
f5db786878 Restart and activate all services and timer when in debug mode 2025-08-19 12:20:19 +02:00
7ef20474a0 Renamed sys-ctl-cln-backups to sys-ctl-cln-bkps 2025-08-19 12:15:33 +02:00
83b9f697ab Encapsulated again to see output in journald 2025-08-19 11:25:07 +02:00
dd7b5e844c removed /bin/sh -c encapsulation and solved wrong --ignore names 2025-08-19 11:15:36 +02:00
da01305cac Replaced {{ systemctl_id | get_service_script_path( by systemctl_script_exec 2025-08-19 10:56:46 +02:00
1082caddae refactor(sys-ctl-alm-compose, sys-timer-cln-bkps):
- update alarm compose unit to run email/telegram notifiers independently via multiple ExecStart lines
- ensure cleanup backup dependencies are included before timer setup with handler flush
conversation: https://chatgpt.com/share/68a43429-c0cc-800f-9cc9-9a5ae258dc50
2025-08-19 10:22:38 +02:00
242347878d Moved email host and domain SPOT to SYSTEM_EMAIL constant 2025-08-19 10:00:35 +02:00
f46aabe884 Moved healthcheck to the end so that it is setup after email configuration 2025-08-19 09:46:12 +02:00
d3cc187c3b Made System Email Variables UPPER 2025-08-19 09:34:18 +02:00
0a4b9bc8e4 Generated service names with function 2025-08-19 02:01:15 +02:00
2887e54cca Solved path bug 2025-08-19 01:48:43 +02:00
630fd43382 refactor(services): unify service/timer runtime control and cleanup handling
- Introduce SYS_SERVICE_ALL_ENABLED and SYS_TIMER_ALL_ENABLED runtime flags
- Add SYS_SERVICE_DEFAULT_STATE for consistent default handling
- Ensure all on-failure service names use lowercase software_name
- Load sys-svc-cln-anon-volumes role during Docker cleanup
- Allow forced service refresh when SYS_SERVICE_ALL_ENABLED is true
- Replace ACTIVATE_ALL_TIMERS with SYS_TIMER_ALL_ENABLED
- Use SYS_SERVICE_DEFAULT_STATE in sys-systemctl vars
- Remove redundant MIG build job fail check

Related to service/timer process control refactoring.
2025-08-19 01:27:37 +02:00
3114a7b586 solved missing vars bug 2025-08-19 01:01:09 +02:00
34d771266a Solved path bug 2025-08-19 00:46:47 +02:00
73b7d2728e Solved timer bug 2025-08-19 00:33:00 +02:00
fc4df980c5 Solved empty entry bug 2025-08-18 23:54:23 +02:00
763b43b44c Implemented dynamic script path to sys-ctl-cln-disc-space 2025-08-18 23:50:28 +02:00
db860e6ae3 Adapted load order 2025-08-18 23:47:14 +02:00
2ba486902f Deactivated file copying for sys-ctl-cln-faild-bkps 2025-08-18 23:34:06 +02:00
7848226f83 Optimized service configuration for allerts 2025-08-18 23:28:41 +02:00
185f37af52 Refactor systemctl service handling with @ support
- Unified variable naming: system_service_id → systemctl_id
- Added automatic removal of trailing '@' for role directory resolution
- Improved first_found search: prefer target role, fallback to sys-systemctl defaults
- Split template resolution logic to avoid undefined variable errors
- Added assertion in sys-timer to forbid '@' in systemctl_id
- Corrected default systemctl.service.j2 template description
- Cleaned up path handling and script directory generation

Context: conversation about fixing template resolution and @ handling
https://chatgpt.com/share/68a39994-1bb0-800f-a219-109e643c3efb
2025-08-18 23:22:46 +02:00
b9461026a6 refactor: improve get_service_name suffix handling and handler usage
- Updated filter_plugins/get_service_name.py:
  * Default suffix handling: auto-select .service (no '@') or .timer (with '@')
  * Explicit False disables suffix entirely
  * Explicit string suffix still supported
- Updated sys-systemctl handler to use new filter instead of SYS_SERVICE_SUFFIX
- Extended unit tests to cover new suffix behavior

Ref: https://chat.openai.com/share/8c2de9e6-daa0-44dd-ae13-d7a7d8d8b6d9
2025-08-18 22:36:31 +02:00
bf63e01b98 refactor(systemd-services): migrate SYS_SERVICE_SUFFIX usage to get_service_name filter
Replaced all hardcoded service name concatenations with the new get_service_name filter.
This ensures consistency, proper lowercase formatting, and correct handling of '@' suffixed units.

Added unittests for the filter (normal, custom suffix, '@'-units, and lowercase normalization).

Context: see ChatGPT discussion https://chatgpt.com/share/68a38beb-b9bc-800f-b7ed-cdd2b64b2604
2025-08-18 22:24:33 +02:00
4a600ac531 Added get_service_name 2025-08-18 22:10:52 +02:00
dc0bb555c1 Added another group_names validation 2025-08-18 21:37:07 +02:00
5adce08aea Optimized variable names 2025-08-18 21:26:46 +02:00
2569abc0be Refactor systemctl services and timers
- Unified service templates into generic systemctl templates
- Introduced reusable filter plugins for script path handling
- Updated path variables and service/timer definitions
- Migrated roles (backup, cleanup, repair, etc.) to use systemctl role
- Added sys-daemon role for core systemd cleanup
- Simplified timer handling via sys-timer role

Note: This is a large refactor and some errors may still exist. Further testing and adjustments will be needed.
2025-08-18 21:22:16 +02:00
3a839cfe37 Refactor systemctl services and categories due to alarm bugs
This commit restructures systemctl service definitions and category mappings.

Motivation: Alarm-related bugs revealed inconsistencies in service and role handling.

Preparation step: lays the groundwork for fixing the alarm issues by aligning categories, roles, and service templates.
2025-08-18 13:35:43 +02:00
29f50da226 Add custom Ansible filter plugin get_category_entries
This commit introduces a new Ansible filter plugin named
'get_category_entries', which returns all role names under the
roles/ directory that start with a given prefix.

Additionally, unit tests (unittest framework) have been added under
tests/unit/filterplugins/ to ensure correct behavior, including:

- Returns empty list when roles/ directory is missing
- Correctly filters and sorts by prefix
- Ignores non-directory entries
- Supports custom roles_path argument
- Returns all roles when prefix is empty

Reference: https://chatgpt.com/share/68a2f1ab-1fe8-800f-b22a-28c1c95802c2
2025-08-18 11:27:26 +02:00
a5941763ff refactor: normalize Jinja2 spacing in volume paths and add async support in backup task
- Standardized spacing in {{ docker_compose.directories.volumes }} across multiple roles
- Added async and poll support to sys-bkp-docker-2-loc database seeding and file permission tasks
- Moved Installation.md for web-app-matrix into docs/ for better structure
2025-08-18 01:05:01 +02:00
3d7bbabd7b mailu: enable central database, improve token creation task, and add migration guide
- Enabled central_database in Mailu config
- Improved API token creation task:
  * use curl -f to fail on HTTP errors
  * added explicit failed_when and changed_when conditions
- Adjusted docker-compose template spacing for readability
- Made logging level configurable (DEBUG when MODE_DEBUG is set)
- Added new documentation Move_Domain.md explaining safe procedure for migrating mailboxes to a new domain
2025-08-18 01:03:40 +02:00
e4b8c97e03 Solved port-ui keycloak url bug and optimized var names 2025-08-18 00:46:11 +02:00
29df95ed82 Optimized RBAC variables and async in keycloak 2025-08-18 00:15:41 +02:00
6443771d93 Optimized Mailu docs 2025-08-18 00:14:58 +02:00
d1cd87c843 Fix RBAC groups handling and refactor Keycloak role
- Fixed incorrect handling of RBAC group configuration (moved from OIDC claims into dedicated RBAC variable set).
- Unified RBAC group usage across applications (LAM, pgAdmin, phpLDAPadmin, phpMyAdmin, YOURLS).
- Replaced old 'KEYCLOAK_OIDC_RBAC_SCOPE_NAME' with dedicated 'KEYCLOAK_RBAC_GROUP_*' variables.
- Updated OAuth2 Proxy configuration to use 'RBAC.GROUP.CLAIM'.
- Refactored Keycloak role task structure:
  * Renamed and reorganized task files for clarity ('_update.yml', '02_cleanup.yml', etc.).
  * Introduced meta and dependency handling separation.
- Cleaned up Keycloak config defaults and recaptcha placeholders.
2025-08-17 23:27:01 +02:00
5f0762e4f6 Finished implementation of oauth2 import 2025-08-17 21:59:58 +02:00
5642793f4a Added parameter to skipp dependency loading to speed up debugging 2025-08-17 21:44:15 +02:00
7d0502ebc5 feat(keycloak): implement SPOT with Realm
Replace 01_import.yml with 01_initialize.yml (KEYCLOAK_HOST_IMPORT_DIR)
Add generic 02_update.yml (kcadm updater for clients/components)
- Resolve ID → read current → merge (kc_merge_path optional)
- Preserve immutable fields; support kc_force_attrs
Update tasks/main.yml:
- Readiness via KEYCLOAK_MASTER_REALM_URL; kcadm login
- Merge LDAP component config from Realm when KEYCLOAK_LDAP_ENABLED
- Update client settings incl. frontchannel.logout.url
realm.json.j2: include ldap.json in UserStorageProvider
ldap.json.j2: use KEYCLOAK_LDAP_* vars for bindDn/credential/connectionUrl
vars/main.yml: add KEYCLOAK_* URLs/dirs and KEYCLOAK_DICTIONARY_REALM(_RAW)
docker-compose.yml.j2: mount KEYCLOAK_HOST_IMPORT_DIR
Cleanup: remove 02_update_client_redirects.yml, 03_update-ldap-bind.yml, 04_ssh_public_key.yml; drop obsolete config flag; formatting

Note: redirectUris/webOrigins ordering may still cause changed=true; consider sorting for stability in a follow-up.
2025-08-17 14:27:33 +02:00
20c8d46f54 Keycloak import templates cleanup
- Removed all static 'id' fields from realm.json.j2, ldap.json.j2, and client.json.j2
- Replaced 'desktop-secret' with correct 'client-secret' authenticator type
- Standardized Jinja filters to use 'to_json' consistently
- Corrected defaultClientScopes entry from 'web-app-origins' to built-in 'web-origins'
- Verified LDAP mapper definitions and optional realm role mapping
- Ensured realm.json.j2 contains only required scopes

References: Chat with ChatGPT (2025-08-17)
https://chatgpt.com/share/68a1aaae-1b04-800f-aa8d-8a0ef6d33cba
2025-08-17 12:11:14 +02:00
a524c52f89 Created own ldap.json.j2 for better readability in keycloak 2025-08-17 11:49:50 +02:00
5c9ca20e04 Optimized keycloak variables 2025-08-17 11:40:15 +02:00
bfe18dd83c Refactor Keycloak role:
- Replace KEYCLOAK_KCADM_PATH with KEYCLOAK_EXEC_KCADM consistently
- Externalize client.json to separate Jinja2 template and include it in realm.json
- Simplify LDAP bind update to use explicit KEYCLOAK_LDAP_* vars
- Add async/poll support for long-running kcadm updates
- Restructure vars/main.yml: clearer grouping (General, Docker, Server, Update, LDAP, API)
- Compute redirectUris/webOrigins centrally in vars
- Align post.logout.redirect.uris handling with playbook

Conversation: https://chatgpt.com/share/68a1a11f-f8ac-800f-bada-cdc99a4fa1bf
2025-08-17 11:30:33 +02:00
0a83f3159a Updated keycloak variables 2025-08-17 10:47:40 +02:00
fb7b3a3c8e Added setting of frontchannel.logout.url for keycloak 2025-08-17 10:38:25 +02:00
42f9ebad34 Solved escaping bug 2025-08-17 09:35:19 +02:00
33b2d3f582 Optimized docker2local variables and constants 2025-08-17 09:26:46 +02:00
14e868a644 Fix OIDC issuer URL concatenation for Mastodon bug
- Removed trailing slash in '_oidc_client_issuer_url' to avoid issuer mismatch
- Use '.rstrip('/')' to normalize '_oidc_url'
- Switched to '~' concatenation instead of inline slashes for all OIDC endpoints
- Ensures that Mastodon and other OIDC clients match the issuer from Keycloak discovery

Change motivated by Mastodon issuer mismatch bug (OpenIDConnect::Discovery::DiscoveryFailed).
See related discussion: https://chatgpt.com/share/68a17d3c-c980-800f-934c-d56955b45f81
2025-08-17 09:02:38 +02:00
2a1a956739 feat(web-opt-rdr-www): split flavors into edge (Cloudflare redirect rule) and origin (Nginx redirect) with dynamic selection via prefered_flavor 2025-08-17 01:29:37 +02:00
bd2dde3af6 refactor: replace srv-web-7-7-dns-records with sys-dns-cloudflare-records
- removed obsolete role `srv-web-7-7-dns-records` (README, meta, tasks)
- updated Gitea role to use `sys-dns-cloudflare-records` with explicit record vars
- updated web-opt-rdr-www role to use new DNS role with zone detection (`to_zone`)
- added REDIRECT_WWW_FLAVOR var to support "edge" flavor selection
2025-08-16 23:52:46 +02:00
1126765da2 Fix variable definition test to detect set_fact and ansible.builtin.set_fact (both block and inline forms)
- Support fully qualified ansible.builtin.set_fact
- Parse inline set_fact mappings (e.g. set_fact: { a: 1, b: 2 })
- Continue scanning inside vars/set_fact blocks for Jinja {% set %}, {% for %}, and {% macro %}
- Ensures variables defined by set_fact are correctly recognized as defined
2025-08-16 23:51:27 +02:00
2620ee088e refactor(dns): unify Cloudflare + Hetzner handling across roles
- replaced CERTBOT_DNS_API_TOKEN with CLOUDFLARE_API_TOKEN everywhere
- introduced generic sys-dns-cloudflare-records role for managing DNS records
- added sys-dns-hetzner-rdns role with both Cloud (hcloud) and Robot API flavors
- updated Mailu role to:
  - generate DKIM before DNS setup
  - delegate DNS + rDNS records to the new generic roles
- removed legacy per-role Cloudflare vars (MAILU_CLOUDFLARE_API_TOKEN)
- extended group vars with HOSTING_PROVIDER for rDNS flavor decision
- added hetzner.hcloud collection to requirements

This consolidates DNS management into reusable roles,
supports both Cloudflare and Hetzner providers,
and standardizes variable naming across the project.
2025-08-16 21:43:01 +02:00
838a55ea94 Solved realm bug which appeared due to refactoring 2025-08-16 18:38:22 +02:00
1b26f1da8d Deactivated IP6 for Mailu 2025-08-16 18:17:09 +02:00
43362e1694 Optimized sys-hlth-csp performance 2025-08-16 18:03:44 +02:00
14d3f65a70 Included docker compose handler flush for mailu 2025-08-16 18:02:40 +02:00
b8ccd50ab2 Added async und logs 2025-08-16 17:29:16 +02:00
4a39cc90c0 Solved variable bugs in sys-svc-cert-sync-docker 2025-08-16 17:27:56 +02:00
0de26fa6c7 Solved bug existed due to difference between mailu domain and hostname difference. also refactored during this to find the bug 2025-08-16 14:29:07 +02:00
1bed83078e Added no_logs, asyncs, and optimized listmonk variable names 2025-08-16 02:00:13 +02:00
7ffd79ebd9 Added no_logs to mailu 2025-08-16 01:49:48 +02:00
2b7950920c Added no_logs 2025-08-16 01:41:37 +02:00
f0b323afee Added auto snippet for webserver injection 2025-08-16 01:31:49 +02:00
eadcb62f2a Added web-svc-logout as dependency for keycloak 2025-08-16 00:05:33 +02:00
cc2c1dc730 Renamed injection services 2025-08-16 00:01:46 +02:00
3b4821f7e7 Solved missing logout injection bug and refactored srv-web-7-7-inj-compose 2025-08-15 23:55:19 +02:00
5b64b47754 Added no_log 2025-08-15 23:18:44 +02:00
cb2b9462e1 Removed default 2025-08-15 21:56:20 +02:00
03564b34bb Optimized reset routine for docker images and specially discourse 2025-08-15 21:35:45 +02:00
e3b09e7f1a Refactoring of discourse role during debugging 2025-08-15 20:06:56 +02:00
3adb08fc68 Prevent exposition of applications credentials 2025-08-15 20:06:01 +02:00
e9a41bd40c Added deletion of containers to reset routine 2025-08-15 20:05:05 +02:00
cb539b038c Marked as not changed 2025-08-15 19:00:03 +02:00
3ac9bd9f90 Optimized variable typos 2025-08-15 18:43:42 +02:00
85a2f4b3d2 Solved matrix federation port bug 2025-08-15 18:37:18 +02:00
012426cf3b Added more matrix constants for easier debugging and readability 2025-08-15 18:15:58 +02:00
6c966bce2e Added health check and restart policy to openresty 2025-08-15 17:59:09 +02:00
3587531bda Removed unnecessary wait_for logic from mig 2025-08-15 15:45:20 +02:00
411a1f8931 Optimized LDAP_DN_BASE for hostname 2025-08-15 15:31:38 +02:00
cc51629337 Added spacing between {{}} 2025-08-15 15:21:48 +02:00
022800425d THE HUGE REFACTORING CALENDER WEEK 33; Optimized Matrix and during this updated variables, and implemented better reset and cleanup mode handling, also solved some initial setup bugs 2025-08-15 15:15:48 +02:00
0228014d34 Replaced .infinito.service and .infinito.timer by SOFTWARE_NAME suffix, optimized LICENSE link and update OIDC Realm and ID conf 2025-08-14 14:39:18 +02:00
1b638c366e Introduced variable SOFTWARE_NAME, to make better visible when software components are used. Will be relevant for OIDC 2025-08-14 12:49:06 +02:00
5c90c252d0 Optimized typos 2025-08-14 12:32:21 +02:00
4a65a254ae replaced port-ui-desktop with desktop to make it more speakable 2025-08-14 11:45:08 +02:00
5e00deea19 Implemented desktop csp policies 2025-08-14 11:40:09 +02:00
bf7b24c3ee Implemented get_app_conf 2025-08-14 11:14:15 +02:00
85924ab3c5 Optimized openproject csp 2025-08-14 10:59:19 +02:00
ac293c90f4 Optimized links, description and docs 2025-08-14 08:45:01 +02:00
e0f35c4bbd Added todos 2025-08-14 08:20:29 +02:00
989bee9522 Merged hp spectre and msi 2025-08-14 08:16:55 +02:00
2f12d8ea83 Added handler for discourse buiöd 2025-08-14 00:27:18 +02:00
58620f6695 Added async for DNS Records creation 2025-08-14 00:23:42 +02:00
abc064fa56 Added async for openproject settings 2025-08-14 00:07:09 +02:00
7f42462514 Fixed reload button bug 2025-08-13 23:50:35 +02:00
41cd6b7702 Replaced get_domain with get_url 2025-08-13 23:33:49 +02:00
a40d48bb03 Refactor srv-web-7-7-inj-port-ui-desktop to use CDN-served JS file with inline initializer
- Added vars/main.yml to define iframe-handler.js file name and destination
- Implemented 01_deploy.yml to deploy iframe-handler.js to CDN and set mtime-based version fact
- Split original iframe logic into:
  • iframe-handler.js (full logic, served from CDN)
  • iframe-init_one_liner.js.j2 (small inline bootstrap, CSP-hashed)
- Updated head_sub.j2 to load script from CDN instead of embedding full code
- Added body_sub.j2 for inline init code
- Updated iframe-handler.js.j2 with initIframeHandler() function and global exposure
- Activated role earlier in inj-compose with public: true so vars are available for templates
- Included 'port-ui-desktop' in body_snippets loop in location.lua.j2
- Disabled 'port-ui-desktop' feature in web-svc-cdn config by default

https://chatgpt.com/share/689d03a8-4c28-800f-8b06-58ce2807b075
2025-08-13 23:29:32 +02:00
2fba32d384 Solved listmonk path bug 2025-08-13 22:39:43 +02:00
f2a765d69a Removed unused ansible matrix role 2025-08-13 22:01:09 +02:00
c729edb525 Refactor async task handling
- Standardize async/poll usage with 'ASYNC_ENABLED | bool'
- Add async/poll parameters to Cloudflare, Nginx, Mailu, MIG, Nextcloud, and OpenLDAP tasks
- Update async configuration in 'group_vars/all/00_general.yml' to ensure boolean evaluation
- Allow CAA, cache, and DNS tasks to run asynchronously when enabled

https://chatgpt.com/share/689cd8cc-7fbc-800f-bd06-a667561573bf
2025-08-13 21:56:26 +02:00
597e9d5222 Refactor async execution handling across LDAP and Nextcloud roles
- Introduce global async configuration in group_vars/all/00_general.yml:
  - ASYNC_ENABLED (disabled in debug mode)
  - ASYNC_TIME (default 300s, omitted if async disabled)
  - ASYNC_POLL (0 for async fire-and-forget, 10 for sync mode)
- Replace hardcoded async/poll values with global vars in:
  - svc-db-openldap (03_users.yml, 04_update.yml)
  - web-app-mig (02_build_data.yml)
  - web-app-nextcloud (03_admin.yml, 04_system_config.yml, 05_plugin.yml,
    06_plugin_routines.yml, 07_plugin_enable_and_configure.yml)
- Guard changed_when and failed_when conditions to only evaluate in synchronous
  mode to avoid accessing undefined rc/stdout/stderr in async runs

  https://chatgpt.com/share/689cd8cc-7fbc-800f-bd06-a667561573bf
2025-08-13 20:26:40 +02:00
db0e030900 Renamed general and mode constants and implemented a check to verify that constants are just defined ones over the whole repository 2025-08-13 19:11:14 +02:00
004507e233 Optimized handler flushing 2025-08-13 18:17:05 +02:00
e2014b9b59 nextcloud(role): remove async → use batched shell; more robust changed_when/failed_when; fix quoting; refactor plugin routines; clean up vars
• 02_add_missing_indices.yml: switched to shell (+ansible_command_timeout), removed async/poll.

• 04_system_config.yml: batch OCC calls (set -euo pipefail, /bin/bash), safer quoting, change detection via ' set to '.

• 05_plugin.yml: disable task with stricter failed_when/changed_when (combine stdout+stderr).

• 06_plugin_routines.yml: disable incompatible plugins in a single batch; no async_status; robust changed_when.

• 07_plugin_enable_and_configure.yml: batch config:app:set, safe quoting, clear changed_when/failed_when.

• config/main.yml & vars/main.yml: removed performance.async.wait_for and nextcloud_wait_for_async_enabled.
2025-08-13 18:15:50 +02:00
567b1365c0 Nextcloud: async overhaul & task refactor (conditional wait, faster polling)
• Add config.performance.async.wait_for and expose as nextcloud_wait_for_async_enabled to toggle waiting for async jobs.

• Split system/admin/index maintenance into separate tasks: 02_add_missing_indices.yml, 03_admin.yml, 04_system_config.yml.

• Refactor plugin flow: rename 02_plugin→05_plugin, 03_plugin_routines→06_plugin_routines, 04_plugin_enable_and_configure→07_plugin_enable_and_configure; remove old 03_plugin_routines and 05_system.

• Harden async handling: filter async_status loops by ansible_job_id; conditionally wait only when nextcloud_wait_for_async_enabled; reduce delay to 1s.

• Reorder main.yml to run system steps before plugin setup; keep handlers flush earlier.

• env.j2: simplify get_app_conf lookups (drop extra True flag).

• vars/main.yml: add nextcloud_host_nginx_path and nextcloud_wait_for_async_enabled.

https://chatgpt.com/share/689c9d4a-1748-800f-b490-06a5a48dd831
2025-08-13 16:13:00 +02:00
e99fa77b91 Optimized docker handlers for espocrm and wordpress 2025-08-13 13:34:12 +02:00
80dad1a5ed Removed proxy_extra_configuration fact 2025-08-13 06:32:52 +02:00
03290eafe1 feat(proxy,bigbluebutton): use parameterized HTML location template & add build retry
- proxy(html.conf.j2):
  * Make proxy_pass more robust (strip '=', '^~' prefixes; ignore @/~ match locations)
  * Switch WS header to $connection_upgrade
  * Unify timeouts (proxy_connect_timeout 5s)
  * Lua optional: include only when proxy_lua_enabled=true; unset Accept-Encoding only then
  * Buffering via flag: proxy_buffering/proxy_request_buffering 'on' with Lua, otherwise 'off'
- proxy(media.conf.j2): minor formatting/spacing fix
- inj-css(head_sub.j2): consistent spacing for global_css_version
- bigbluebutton(tasks/main.yml):
  * Render HTML location block once before include_role (location='^~ /html5client', OAuth2/Lua disabled)
  * Pass rendered snippet via proxy_extra_configuration to the vHost
  * Cleanup afterwards: proxy_extra_configuration = undef()
- docker-compose(handlers):
  * Build with retry: if 'docker compose build' fails -> retry with '--no-cache --pull'
  * Enable BuildKit (DOCKER_BUILDKIT=1, COMPOSE_DOCKER_CLI_BUILD=1)
- vars: trailing newline / minor formatting

Motivation:
- BBB HTML5 client (^~ /html5client) needs a separate location without Lua/buffering.
- More resilient CI/CD builds via automatic no-cache retry.
- Cleaner headers/proxy defaults and fewer side effects.

Files:
- roles/docker-compose/handlers/main.yml
- roles/srv-proxy-7-4-core/templates/location/html.conf.j2
- roles/srv-proxy-7-4-core/templates/location/media.conf.j2
- roles/srv-web-7-7-inj-css/templates/head_sub.j2
- roles/web-app-bigbluebutton/tasks/main.yml
- roles/web-app-bigbluebutton/vars/main.yml
2025-08-13 06:01:50 +02:00
58c64bd7c6 Placed docker compose flush more specific 2025-08-13 03:53:13 +02:00
e497c001d6 keycloak: robust LDAP bind and connectionUrl update via kcadm (argv + JSON); strict ldap.*; idempotent
Switch to command:argv to avoid shell quoting and argument splitting issues.

Pass -s config values as JSON arrays via to_json, fixing previous errors: Cannot parse the JSON / failed at splitting arguments.

Also reconcile config.connectionUrl from ldap.server.uri.

Source desired values strictly from ldap.* (no computed defaults) and assert their presence.

Keep operation idempotent by reading current values and updating only on change.

Minor refactor: build reusable kcadm_argv_base and expand client state extraction.

Touch: roles/web-app-keycloak/tasks/03_update-ldap-bind.yml

https://chatgpt.com/share/689bea84-7188-800f-ba51-830a0735f24c
2025-08-13 03:30:14 +02:00
4fa1c6cfbd ansible: quote file modes; keycloak: robust LDAP bind update + config cleanup
Highlights
- Quote all file modes as strings ("0755"/"0770") across multiple roles to avoid YAML octal quirks and improve portability.
- Keycloak: introduce actions.{import_realm,update_ldap_bind} feature flags and wire them via vars/config.
- Implement idempotent LDAP bind updater (tasks/03_update-ldap-bind.yml):
  * kcadm login with no_log protection,
  * fetch LDAP UserStorage component by name,
  * compare current bindDn/bindCredential and update only when changed.
- Keycloak realm import template: keep providerId="ldap" and set name from keycloak_ldap_component_name.
- Centralize Keycloak readiness check in tasks/main.yml; remove duplicate waits from 02_update_client_redirects.yml and 04_ssh_public_key.yml.
- 01_import.yml: fix typo (keycloak), quote modes, tidy spacing, and replace Jinja-in-Jinja fileglob with concatenation.
- 02_update_client_redirects.yml: correct assert fail_msg filename; keep login-first flow.
- Minor template/vars tidy-ups (spacing, comments, consistent variable usage).

Files touched (excerpt)
- roles/*/*: replace 0755/0770 → "0755"/"0770"
- roles/web-app-keycloak/config/main.yml: add actions map
- roles/web-app-keycloak/vars/main.yml: unify Keycloak vars and feature flags
- roles/web-app-keycloak/tasks/{01_import,02_update_client_redirects,03_update-ldap-bind,04_ssh_public_key,main}.yml
- roles/web-app-keycloak/templates/{docker-compose.yml.j2,import/realm.json.j2}

https://chatgpt.com/share/689bda16-b138-800f-8258-e13f6d7d8239
2025-08-13 02:20:38 +02:00
53770f5308 Optimized flush order to solve yourls oauth2 proxy bug 2025-08-13 01:03:31 +02:00
13d8663796 Added version and repository to bbb 2025-08-13 00:35:14 +02:00
f31565e4c5 Optimized URLS 2025-08-13 00:33:47 +02:00
a4d8de2152 feat(web-app-espocrm): ensure 'siteUrl' is updated to canonical domain on deploy
Use EspoCRM's ConfigWriter API to patch the 'siteUrl' setting during updates.
This makes the process idempotent, avoids brittle regex replacements, and
ensures the running configuration stays in sync with the deployment domain.

https://chatgpt.com/share/689bb860-ba90-800f-adb5-4fa5a992b267
2025-08-12 23:56:19 +02:00
c744ebe3f9 feat(web-app-wordpress): add idempotent single-site domain update via WP-CLI
- New task 04_update_domain.yml updates home/siteurl only when needed
- DB-wide search-replace (old → new), GUID-safe, precise, tables-with-prefix
- Normalizes http→https, strips trailing slashes, then flushes cache/rewrites
- Guarded by is_multisite()==0; multisite untouched
- Wired into main.yml with auto target URL via domains|get_url

Fixes post-domain-change mixed/CSP issues due to hard-coded old URLs.

https://chatgpt.com/share/689bac2d-3610-800f-b6f0-41dc79d13a14
2025-08-12 23:03:59 +02:00
ce029881d0 cmp-rdbms: make vars resilient when database_type is empty
Fix a templating crash during docker-compose.yml rendering when a role sets database_type to an empty string or does not expose it (e.g., svc-prx-openresty). Previously _database_id resolved to 'svc-db-' and get_app_conf attempted to read 'docker.services..name', raising AppConfigKeyError: Application ID 'svc-db-' not found.

Changes:
- Introduce _dbtype = (database_type | d('') | trim) and build _database_id only if _dbtype is non-empty.
- Guard central DB lookups: use get_app_conf(..., strict=False, default='') and only when _dbtype is set.
- Default _database_consumer_entity_name to get_entity_name of database_application_id or fallback to application_id.
- Only resolve database_port when _dbtype is set; otherwise empty.
- Minor formatting fixes for env and URL strings.

Impact:
- Prevents failures in roles without a DB or with database_type=''.
- Keeps previous behavior intact for apps with a valid database_type (mariadb/postgres).
- Eliminates 'config_path: docker.services..name' errors while keeping compose templates stable.

https://chatgpt.com/share/689b9d11-6308-800f-b20c-2d9f18d832f1
2025-08-12 21:59:37 +02:00
94da112736 perf(friendica): single-pass patch for DB creds + system.url; align env URL; tidy vars
- Patch local.config.php in one sed exec:
  * hostname, database, username, password
  * system.url via '#' delimiter to avoid URL slash escaping
  * Single notify: docker compose up
- env.j2:
  * FRIENDICA_URL now uses domains|get_url(application_id, WEB_PROTOCOL)
  * Simplify FRIENDICA_DEBUGGING with |lower
  * Normalize spacing for readability
- vars/main.yml:
  * Minor cleanups (comment header, spacing)
  * Consistent friendica_docker_ldap_config path construction

Why: fewer container execs ⇒ faster runs; idempotent key updates; consistent URL configuration across env and PHP config.
Risk: requires WEB_PROTOCOL and domains|get_url to be defined in inventory/vars as elsewhere in the project.

https://chatgpt.com/share/689b92af-b184-800f-9664-2450e00b29d6
2025-08-12 21:15:33 +02:00
b62df5599d Optimized typos 2025-08-12 19:17:02 +02:00
1585 changed files with 19186 additions and 12270 deletions

View File

@@ -5,7 +5,7 @@ Thank you for your interest in contributing to Infinito.Nexus! We welcome contri
## How to Contribute
There are several ways you can help:
- **Reporting Issues:** Found a bug or have a feature request? Please open an issue on our [GitHub Issues page](https://github.com/kevinveenbirkenbach/infinito-nexus/issues) with a clear description and steps to reproduce the problem.
- **Reporting Issues:** Found a bug or have a feature request? Please open an issue on our [GitHub Issues page](https://s.infinito.nexus/issues) with a clear description and steps to reproduce the problem.
- **Code Contributions:** If you'd like to contribute code, fork the repository, create a new branch for your feature or bug fix, and submit a pull request. Ensure your code adheres to our coding style and includes tests where applicable.
- **Documentation:** Improving the documentation is a great way to contribute. Whether it's clarifying an existing section or adding new guides, your contributions help others understand and use Infinito.Nexus effectively.
- **Financial Contributions:** If you appreciate Infinito.Nexus and want to support its ongoing development, consider making a financial contribution. For more details, please see our [donate options](12_DONATE.md).

View File

@@ -1,9 +1,9 @@
# License Agreement
## Infinito.Nexus NonCommercial License (CNCL)
## Infinito.Nexus NonCommercial License
### Definitions
- **"Software":** Refers to *"[Infinito.Nexus - Cyber Master Infrastructure Solution](https://infinito.nexus/)"* and its associated source code.
- **"Software":** Refers to *"[Infinito.Nexus](https://infinito.nexus/)"* and its associated source code.
- **"Commercial Use":** Any use of the Software intended for direct or indirect financial gain, including but not limited to sales, rentals, or provision of services.
### Provisions

View File

@@ -1,9 +1,7 @@
# IT-Infrastructure Automation Framework 🚀
# Infinito.Nexus 🚀
**🔐 One login. ♾️ Infinite application**
*Automate the Provisioning of All Your Servers and Workstations with a Single OpenSource Script!*
![Infinito.Nexus Logo](assets/img/logo.png)
---
@@ -15,7 +13,7 @@
|---|---|
| 🌐 Try It Live | [![Infinito.Nexus](https://img.shields.io/badge/Infinito.Nexus-%2ECloud-000000?labelColor=004B8D&style=flat&borderRadius=8)](https://infinito.nexus) |
| 🔧 Request Your Setup | [![CyberMaster.Space](https://img.shields.io/badge/CyberMaster-%2ESpace-000000?labelColor=004B8D&style=flat&borderRadius=8)](https://cybermaster.space) |
| 📖 About This Project | [![GitHub Sponsors](https://img.shields.io/badge/Sponsor-GitHub%20Sponsors-blue?logo=github)](https://github.com/sponsors/kevinveenbirkenbach) [![Build Status](https://github.com/kevinveenbirkenbach/infinito-nexus/actions/workflows/test-cli.yml/badge.svg?branch=master)](https://github.com/kevinveenbirkenbach/infinito-nexus/actions/workflows/test-cli.yml?query=branch%3Amaster) [![View Source](https://img.shields.io/badge/View_Source-Repository-000000?logo=github&labelColor=004B8D&style=flat&borderRadius=8)](https://github.com/kevinveenbirkenbach/infinito-nexus) |
| 📖 About This Project | [![GitHub Sponsors](https://img.shields.io/badge/Sponsor-GitHub%20Sponsors-blue?logo=github)](https://github.com/sponsors/kevinveenbirkenbach) [![Build & Test Infinito.Nexus CLI in Docker Container](https://github.com/kevinveenbirkenbach/infinito-nexus/actions/workflows/test-cli.yml/badge.svg)](https://github.com/kevinveenbirkenbach/infinito-nexus/actions/workflows/test-cli.yml) [![View Source](https://img.shields.io/badge/View_Source-Repository-000000?logo=github&labelColor=004B8D&style=flat&borderRadius=8)](https://s.infinito.nexus/code) |
| ☕️ Support Us | [![Patreon](https://img.shields.io/badge/Support-Patreon-orange?logo=patreon)](https://www.patreon.com/c/kevinveenbirkenbach) [![Buy Me a Coffee](https://img.shields.io/badge/Buy%20me%20a%20Coffee-Funding-yellow?logo=buymeacoffee)](https://buymeacoffee.com/kevinveenbirkenbach) [![PayPal](https://img.shields.io/badge/Donate-PayPal-blue?logo=paypal)](https://s.veen.world/paypaldonate) [![Sponsor Infinito.Nexus](https://img.shields.io/badge/DonateInfinito.Nexus-000000?style=flat&labelColor=004B8D&logo=github-sponsors&logoColor=white&borderRadius=8)](https://github.com/sponsors/kevinveenbirkenbach) |
---
@@ -93,4 +91,4 @@ Infinito.Nexus is distributed under the **Infinito.Nexus NonCommercial License**
## Professional Setup & Support 💼
For expert installation and configuration visit [cybermaster.space](https://cybermaster.space/) or write to us at **[contact@infinito.nexus](mailto:contact@infinito.nexus)**.
For expert installation and configuration visit [cybermaster.space](https://cybermaster.space/) or write to us at **[contact@cymais.cloud](mailto:contact@cymais.cloud)**.

5
TODO.md Normal file
View File

@@ -0,0 +1,5 @@
# Todos
- Implement multi language
- Implement rbac administration interface
- Implement ``MASK_CREDENTIALS_IN_LOGS`` for all sensible tasks
- [Enable IP6 for docker](https://chatgpt.com/share/68a0acb8-db20-800f-9d2c-b34e38b5cdee).

View File

@@ -1,4 +0,0 @@
# Todos
- Implement multi language
- Implement rbac administration interface
- Implement ``MASK_CREDENTIALS_IN_LOGS`` for all sensible tasks

View File

@@ -189,7 +189,7 @@ def parse_args():
def main():
args = parse_args()
primary_domain = '{{ primary_domain }}'
primary_domain = '{{ SYSTEM_EMAIL.DOMAIN }}'
become_pwd = '{{ lookup("password", "/dev/null length=42 chars=ascii_letters,digits") }}'
try:

View File

@@ -72,7 +72,7 @@ def build_single_graph(
node = {'id': role}
node.update(meta['galaxy_info'])
node['doc_url'] = f"https://docs.infinito.nexus/roles/{role}/README.html"
node['source_url'] = f"https://github.com/kevinveenbirkenbach/infinito-nexus/tree/master/roles/{role}"
node['source_url'] = f"https://s.infinito.nexus/code/tree/master/roles/{role}"
nodes[role] = node
if max_depth > 0 and depth >= max_depth:

View File

@@ -102,8 +102,10 @@ def find_cycle(roles):
def topological_sort(graph, in_degree, roles=None):
"""
Perform topological sort on the dependency graph.
If `roles` is provided, on error it will include detailed debug info.
If a cycle is detected, raise an Exception with detailed debug info.
"""
from collections import deque
queue = deque([r for r, d in in_degree.items() if d == 0])
sorted_roles = []
local_in = dict(in_degree)
@@ -117,28 +119,26 @@ def topological_sort(graph, in_degree, roles=None):
queue.append(nbr)
if len(sorted_roles) != len(in_degree):
# Something went wrong: likely a cycle
cycle = find_cycle(roles or {})
if roles is not None:
if cycle:
header = f"Circular dependency detected: {' -> '.join(cycle)}"
else:
header = "Circular dependency detected among the roles!"
unsorted = [r for r in in_degree if r not in sorted_roles]
unsorted = [r for r in in_degree if r not in sorted_roles]
detail_lines = ["Unsorted roles and their dependencies:"]
header = "❌ Dependency resolution failed"
if cycle:
reason = f"Circular dependency detected: {' -> '.join(cycle)}"
else:
reason = "Unresolved dependencies among roles (possible cycle or missing role)."
details = []
if unsorted:
details.append("Unsorted roles and their declared run_after dependencies:")
for r in unsorted:
deps = roles.get(r, {}).get('run_after', [])
detail_lines.append(f" - {r} depends on {deps!r}")
details.append(f" - {r} depends on {deps!r}")
detail_lines.append("Full dependency graph:")
detail_lines.append(f" {dict(graph)!r}")
graph_repr = f"Full dependency graph: {dict(graph)!r}"
raise Exception("\n".join([header] + detail_lines))
else:
if cycle:
raise Exception(f"Circular dependency detected: {' -> '.join(cycle)}")
else:
raise Exception("Circular dependency detected among the roles!")
raise Exception("\n".join([header, reason] + details + [graph_repr]))
return sorted_roles

View File

@@ -5,10 +5,10 @@ import json
from typing import Dict, Any
from cli.build.graph import build_mappings, output_graph
from module_utils.role_dependency_resolver import RoleDependencyResolver
def find_roles(roles_dir: str):
"""Yield (role_name, role_path) for every subfolder in roles_dir."""
for entry in os.listdir(roles_dir):
path = os.path.join(roles_dir, entry)
if os.path.isdir(path):
@@ -16,46 +16,31 @@ def find_roles(roles_dir: str):
def main():
# default roles dir is ../../roles relative to this script
script_dir = os.path.dirname(os.path.abspath(__file__))
default_roles_dir = os.path.abspath(os.path.join(script_dir, '..', '..', 'roles'))
default_roles_dir = os.path.abspath(os.path.join(script_dir, "..", "..", "roles"))
parser = argparse.ArgumentParser(
description="Generate all graphs for each role and write meta/tree.json"
)
parser.add_argument(
'-d', '--role_dir',
default=default_roles_dir,
help=f"Path to roles directory (default: {default_roles_dir})"
)
parser.add_argument(
'-D', '--depth',
type=int,
default=0,
help="Max recursion depth (>0) or <=0 to stop on cycle"
)
parser.add_argument(
'-o', '--output',
choices=['yaml', 'json', 'console'],
default='json',
help="Output format"
)
parser.add_argument(
'-p', '--preview',
action='store_true',
help="Preview graphs to console instead of writing files"
)
parser.add_argument(
'-s', '--shadow-folder',
type=str,
default=None,
help="If set, writes tree.json to this shadow folder instead of the role's actual meta/ folder"
)
parser.add_argument(
'-v', '--verbose',
action='store_true',
help="Enable verbose logging"
)
parser.add_argument("-d", "--role_dir", default=default_roles_dir,
help=f"Path to roles directory (default: {default_roles_dir})")
parser.add_argument("-D", "--depth", type=int, default=0,
help="Max recursion depth (>0) or <=0 to stop on cycle")
parser.add_argument("-o", "--output", choices=["yaml", "json", "console"],
default="json", help="Output format")
parser.add_argument("-p", "--preview", action="store_true",
help="Preview graphs to console instead of writing files")
parser.add_argument("-s", "--shadow-folder", type=str, default=None,
help="If set, writes tree.json to this shadow folder instead of the role's actual meta/ folder")
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose logging")
# Toggles
parser.add_argument("--no-include-role", action="store_true", help="Do not scan include_role")
parser.add_argument("--no-import-role", action="store_true", help="Do not scan import_role")
parser.add_argument("--no-dependencies", action="store_true", help="Do not read meta/main.yml dependencies")
parser.add_argument("--no-run-after", action="store_true",
help="Do not read galaxy_info.run_after from meta/main.yml")
args = parser.parse_args()
if args.verbose:
@@ -65,6 +50,8 @@ def main():
print(f"Preview mode: {args.preview}")
print(f"Shadow folder: {args.shadow_folder}")
resolver = RoleDependencyResolver(args.role_dir)
for role_name, role_path in find_roles(args.role_dir):
if args.verbose:
print(f"Processing role: {role_name}")
@@ -75,24 +62,43 @@ def main():
max_depth=args.depth
)
# Direct deps (depth=1) getrennt erfasst für buckets
inc_roles, imp_roles = resolver._scan_tasks(role_path)
meta_deps = resolver._extract_meta_dependencies(role_path)
run_after = set()
if not args.no_run_after:
run_after = resolver._extract_meta_run_after(role_path)
if any([not args.no_include_role and inc_roles,
not args.no_import_role and imp_roles,
not args.no_dependencies and meta_deps,
not args.no_run_after and run_after]):
deps_root = graphs.setdefault("dependencies", {})
if not args.no_include_role and inc_roles:
deps_root["include_role"] = sorted(inc_roles)
if not args.no_import_role and imp_roles:
deps_root["import_role"] = sorted(imp_roles)
if not args.no_dependencies and meta_deps:
deps_root["dependencies"] = sorted(meta_deps)
if not args.no_run_after and run_after:
deps_root["run_after"] = sorted(run_after)
graphs["dependencies"] = deps_root
if args.preview:
for key, data in graphs.items():
if args.verbose:
print(f"Previewing graph '{key}' for role '{role_name}'")
output_graph(data, 'console', role_name, key)
output_graph(data, "console", role_name, key)
else:
# Decide on output folder
if args.shadow_folder:
tree_file = os.path.join(
args.shadow_folder, role_name, 'meta', 'tree.json'
)
tree_file = os.path.join(args.shadow_folder, role_name, "meta", "tree.json")
else:
tree_file = os.path.join(role_path, 'meta', 'tree.json')
tree_file = os.path.join(role_path, "meta", "tree.json")
os.makedirs(os.path.dirname(tree_file), exist_ok=True)
with open(tree_file, 'w') as f:
with open(tree_file, "w", encoding="utf-8") as f:
json.dump(graphs, f, indent=2)
print(f"Wrote {tree_file}")
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@@ -1,14 +1,29 @@
#!/usr/bin/env python3
"""
Selectively add & vault NEW credentials in your inventory, preserving comments
and formatting. Existing values are left untouched unless --force is used.
Usage example:
infinito create credentials \
--role-path roles/web-app-akaunting \
--inventory-file host_vars/echoserver.yml \
--vault-password-file .pass/echoserver.txt \
--set credentials.database_password=mysecret
"""
import argparse
import subprocess
import sys
from pathlib import Path
import yaml
from typing import Dict, Any
from module_utils.manager.inventory import InventoryManager
from module_utils.handler.vault import VaultHandler, VaultScalar
from module_utils.handler.yaml import YamlHandler
from yaml.dumper import SafeDumper
from typing import Dict, Any, Union
from ruamel.yaml import YAML
from ruamel.yaml.comments import CommentedMap
from module_utils.manager.inventory import InventoryManager
from module_utils.handler.vault import VaultHandler # uses your existing handler
# ---------- helpers ----------
def ask_for_confirmation(key: str) -> bool:
"""Prompt the user for confirmation to overwrite an existing value."""
@@ -18,35 +33,117 @@ def ask_for_confirmation(key: str) -> bool:
return confirmation == 'y'
def main():
def ensure_map(node: CommentedMap, key: str) -> CommentedMap:
"""
Ensure node[key] exists and is a mapping (CommentedMap) for round-trip safety.
"""
if key not in node or not isinstance(node.get(key), CommentedMap):
node[key] = CommentedMap()
return node[key]
def _is_ruamel_vault(val: Any) -> bool:
"""Detect if a ruamel scalar already carries the !vault tag."""
try:
return getattr(val, 'tag', None) == '!vault'
except Exception:
return False
def _is_vault_encrypted(val: Any) -> bool:
"""
Detect if value is already a vault string or a ruamel !vault scalar.
Accept both '$ANSIBLE_VAULT' and '!vault' markers.
"""
if _is_ruamel_vault(val):
return True
if isinstance(val, str) and ("$ANSIBLE_VAULT" in val or "!vault" in val):
return True
return False
def _vault_body(text: str) -> str:
"""
Return only the vault body starting from the first line that contains
'$ANSIBLE_VAULT'. If not found, return the original text.
Also strips any leading '!vault |' header if present.
"""
lines = text.splitlines()
for i, ln in enumerate(lines):
if "$ANSIBLE_VAULT" in ln:
return "\n".join(lines[i:])
return text
def _make_vault_scalar_from_text(text: str) -> Any:
"""
Build a ruamel object representing a literal block scalar tagged with !vault
by parsing a tiny YAML snippet. This avoids depending on yaml_set_tag().
"""
body = _vault_body(text)
indented = " " + body.replace("\n", "\n ") # proper block scalar indentation
snippet = f"v: !vault |\n{indented}\n"
y = YAML(typ="rt")
return y.load(snippet)["v"]
def to_vault_block(vault_handler: VaultHandler, value: Union[str, Any], label: str) -> Any:
"""
Return a ruamel scalar tagged as !vault. If the input value is already
vault-encrypted (string contains $ANSIBLE_VAULT or is a !vault scalar), reuse/wrap.
Otherwise, encrypt plaintext via ansible-vault.
"""
# Already a ruamel !vault scalar → reuse
if _is_ruamel_vault(value):
return value
# Already an encrypted string (may include '!vault |' or just the header)
if isinstance(value, str) and ("$ANSIBLE_VAULT" in value or "!vault" in value):
return _make_vault_scalar_from_text(value)
# Plaintext → encrypt now
snippet = vault_handler.encrypt_string(str(value), label)
return _make_vault_scalar_from_text(snippet)
def parse_overrides(pairs: list[str]) -> Dict[str, str]:
"""
Parse --set key=value pairs into a dict.
Supports both 'credentials.key=val' and 'key=val' (short) forms.
"""
out: Dict[str, str] = {}
for pair in pairs:
k, v = pair.split("=", 1)
out[k.strip()] = v.strip()
return out
# ---------- main ----------
def main() -> int:
parser = argparse.ArgumentParser(
description="Selectively vault credentials + become-password in your inventory."
description="Selectively add & vault NEW credentials in your inventory, preserving comments/formatting."
)
parser.add_argument("--role-path", required=True, help="Path to your role")
parser.add_argument("--inventory-file", required=True, help="Host vars file to update")
parser.add_argument("--vault-password-file", required=True, help="Vault password file")
parser.add_argument(
"--role-path", required=True, help="Path to your role"
)
parser.add_argument(
"--inventory-file", required=True, help="Host vars file to update"
)
parser.add_argument(
"--vault-password-file", required=True, help="Vault password file"
)
parser.add_argument(
"--set", nargs="*", default=[], help="Override values key.subkey=VALUE"
"--set", nargs="*", default=[],
help="Override values key[.subkey]=VALUE (applied to NEW keys; with --force also to existing)"
)
parser.add_argument(
"-f", "--force", action="store_true",
help="Force overwrite without confirmation"
help="Allow overrides to replace existing values (will ask per key unless combined with --yes)"
)
parser.add_argument(
"-y", "--yes", action="store_true",
help="Non-interactive: assume 'yes' for all overwrite confirmations when --force is used"
)
args = parser.parse_args()
# Parse overrides
overrides = {
k.strip(): v.strip()
for pair in args.set for k, v in [pair.split("=", 1)]
}
overrides = parse_overrides(args.set)
# Initialize inventory manager
# Initialize inventory manager (provides schema + app_id + vault)
manager = InventoryManager(
role_path=Path(args.role_path),
inventory_path=Path(args.inventory_file),
@@ -54,62 +151,90 @@ def main():
overrides=overrides
)
# Load existing credentials to preserve
existing_apps = manager.inventory.get("applications", {})
existing_creds = {}
if manager.app_id in existing_apps:
existing_creds = existing_apps[manager.app_id].get("credentials", {}).copy()
# 1) Load existing inventory with ruamel (round-trip)
yaml_rt = YAML(typ="rt")
yaml_rt.preserve_quotes = True
# Apply schema (may generate defaults)
updated_inventory = manager.apply_schema()
with open(args.inventory_file, "r", encoding="utf-8") as f:
data = yaml_rt.load(f) # CommentedMap or None
if data is None:
data = CommentedMap()
# Restore existing database_password if present
apps = updated_inventory.setdefault("applications", {})
app_block = apps.setdefault(manager.app_id, {})
creds = app_block.setdefault("credentials", {})
if "database_password" in existing_creds:
creds["database_password"] = existing_creds["database_password"]
# 2) Get schema-applied structure (defaults etc.) for *non-destructive* merge
schema_inventory: Dict[str, Any] = manager.apply_schema()
# Store original plaintext values
original_plain = {key: str(val) for key, val in creds.items()}
# 3) Ensure structural path exists
apps = ensure_map(data, "applications")
app_block = ensure_map(apps, manager.app_id)
creds = ensure_map(app_block, "credentials")
for key, raw_val in list(creds.items()):
# Skip if already vaulted
if isinstance(raw_val, VaultScalar) or str(raw_val).lstrip().startswith("$ANSIBLE_VAULT"):
# 4) Determine defaults we could add
schema_apps = schema_inventory.get("applications", {})
schema_app_block = schema_apps.get(manager.app_id, {})
schema_creds = schema_app_block.get("credentials", {}) if isinstance(schema_app_block, dict) else {}
# 5) Add ONLY missing credential keys
newly_added_keys = set()
for key, default_val in schema_creds.items():
if key in creds:
# existing → do not touch (preserve plaintext/vault/formatting/comments)
continue
# Determine plaintext
plain = original_plain.get(key, "")
if key in overrides and (args.force or ask_for_confirmation(key)):
plain = overrides[key]
# Value to use for the new key
# Priority: --set exact key → default from schema → empty string
ov = overrides.get(f"credentials.{key}", None)
if ov is None:
ov = overrides.get(key, None)
# Encrypt the plaintext
encrypted = manager.vault_handler.encrypt_string(plain, key)
lines = encrypted.splitlines()
indent = len(lines[1]) - len(lines[1].lstrip())
body = "\n".join(line[indent:] for line in lines[1:])
creds[key] = VaultScalar(body)
# Vault top-level become password if present
if "ansible_become_password" in updated_inventory:
val = str(updated_inventory["ansible_become_password"])
if val.lstrip().startswith("$ANSIBLE_VAULT"):
updated_inventory["ansible_become_password"] = VaultScalar(val)
if ov is not None:
value_for_new_key: Union[str, Any] = ov
else:
snippet = manager.vault_handler.encrypt_string(
val, "ansible_become_password"
if _is_vault_encrypted(default_val):
# Schema already provides a vault value → take it as-is
creds[key] = to_vault_block(manager.vault_handler, default_val, key)
newly_added_keys.add(key)
continue
value_for_new_key = "" if default_val is None else str(default_val)
# Insert as !vault literal (encrypt if needed)
creds[key] = to_vault_block(manager.vault_handler, value_for_new_key, key)
newly_added_keys.add(key)
# 6) ansible_become_password: only add if missing;
# never rewrite an existing one unless --force (+ confirm/--yes) and override provided.
if "ansible_become_password" not in data:
val = overrides.get("ansible_become_password", None)
if val is not None:
data["ansible_become_password"] = to_vault_block(
manager.vault_handler, val, "ansible_become_password"
)
lines = snippet.splitlines()
indent = len(lines[1]) - len(lines[1].lstrip())
body = "\n".join(line[indent:] for line in lines[1:])
updated_inventory["ansible_become_password"] = VaultScalar(body)
else:
if args.force and "ansible_become_password" in overrides:
do_overwrite = args.yes or ask_for_confirmation("ansible_become_password")
if do_overwrite:
data["ansible_become_password"] = to_vault_block(
manager.vault_handler, overrides["ansible_become_password"], "ansible_become_password"
)
# Write back to file
# 7) Overrides for existing credential keys (only with --force)
if args.force:
for ov_key, ov_val in overrides.items():
# Accept both 'credentials.key' and bare 'key'
key = ov_key.split(".", 1)[1] if ov_key.startswith("credentials.") else ov_key
if key in creds:
# If we just added it in this run, don't ask again or rewrap
if key in newly_added_keys:
continue
if args.yes or ask_for_confirmation(key):
creds[key] = to_vault_block(manager.vault_handler, ov_val, key)
# 8) Write back with ruamel (preserve formatting & comments)
with open(args.inventory_file, "w", encoding="utf-8") as f:
yaml.dump(updated_inventory, f, sort_keys=False, Dumper=SafeDumper)
yaml_rt.dump(data, f)
print(f"Inventory selectively vaulted{args.inventory_file}")
print(f"Added new credentials without touching existing formatting/comments{args.inventory_file}")
return 0
if __name__ == "__main__":
main()
sys.exit(main())

View File

@@ -11,8 +11,8 @@ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')
from module_utils.entity_name_utils import get_entity_name
# Paths to the group-vars files
PORTS_FILE = './group_vars/all/09_ports.yml'
NETWORKS_FILE = './group_vars/all/10_networks.yml'
PORTS_FILE = './group_vars/all/10_ports.yml'
NETWORKS_FILE = './group_vars/all/09_networks.yml'
ROLE_TEMPLATE_DIR = './templates/roles/web-app'
ROLES_DIR = './roles'

View File

@@ -191,13 +191,14 @@ def main():
validate_application_ids(args.inventory, args.id)
modes = {
"mode_reset": args.reset,
"mode_test": args.test,
"mode_update": args.update,
"mode_backup": args.backup,
"mode_cleanup": args.cleanup,
"mode_logs": args.logs,
"enable_debug": args.debug,
"MODE_RESET": args.reset,
"MODE_TEST": args.test,
"MODE_UPDATE": args.update,
"MODE_BACKUP": args.backup,
"MODE_CLEANUP": args.cleanup,
"MODE_LOGS": args.logs,
"MODE_DEBUG": args.debug,
"MODE_ASSERT": not args.skip_validation,
"host_type": args.host_type
}

View File

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

View File

@@ -1,8 +1,8 @@
# Infinito.Nexus Architecture Overview
# Infinito.Nexus Architecture
## Introduction
Infinito.Nexus (Cyber Master Infrastructure Solution) is a modular, open-source IT infrastructure automation platform designed to simplify the deployment, management, and security of self-hosted environments.
[Infinito.Nexus](https://infinito.nexus) is a modular, open-source IT infrastructure automation platform designed to simplify the deployment, management, and security of self-hosted environments.
It provides a flexible, scalable, and secure architecture based on modern [DevOps](https://en.wikipedia.org/wiki/DevOps) principles, leveraging technologies like [Ansible](https://en.wikipedia.org/wiki/Ansible_(software)), [Docker](https://en.wikipedia.org/wiki/Docker_(software)), and [Infrastructure as Code (IaC)](https://en.wikipedia.org/wiki/Infrastructure_as_code).

View File

@@ -1,6 +1,6 @@
# 🚀 Deployment Guide
This section explains how to deploy and manage the **Cyber Master Infrastructure Solution (Infinito.Nexus)** using Ansible. Infinito.Nexus uses a collection of Ansible tasks, which are controlled via different **"modes"** — such as **updates**, **backups**, **resets**, and **cleanup** operations.
This section explains how to deploy and manage **[Infinito.Nexus](https://infinito.nexus)** using Ansible. Infinito.Nexus uses a collection of Ansible tasks, which are controlled via different **"modes"** — such as **updates**, **backups**, **resets**, and **cleanup** operations.
---

View File

@@ -15,8 +15,8 @@ Follow these guides to install and configure Infinito.Nexus:
- **Networking & VPN** - Configure `WireGuard`, `OpenVPN`, and `Nginx Reverse Proxy`.
## Managing & Updating Infinito.Nexus 🔄
- Regularly update services using `update-docker`, `update-pacman`, or `update-apt`.
- Monitor system health with `sys-hlth-btrfs`, `sys-hlth-webserver`, and `sys-hlth-docker-container`.
- Automate system maintenance with `sys-lock`, `sys-cln-bkps-service`, and `sys-rpr-docker-hard`.
- Regularly update services using `update-pacman`, or `update-apt`.
- Monitor system health with `sys-ctl-hlth-btrfs`, `sys-ctl-hlth-webserver`, and `sys-ctl-hlth-docker-container`.
- Automate system maintenance with `sys-lock`, `sys-ctl-cln-bkps`, and `sys-ctl-rpr-docker-hard`.
For more details, refer to the specific guides above.

View File

@@ -25,7 +25,7 @@ Contributing to Infinito.Nexus
Want to contribute to the project or explore the source code? Check out our **GitHub repository**:
- `Infinito.Nexus GitHub Repository <https://github.com/kevinveenbirkenbach/infinito-nexus/tree/master/roles>`_
- `Infinito.Nexus GitHub Repository <https://s.infinito.nexus/code/tree/master/roles>`_
Contribution Guidelines
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -1,86 +0,0 @@
from ansible.errors import AnsibleFilterError
class FilterModule(object):
def filters(self):
return {'alias_domains_map': self.alias_domains_map}
def alias_domains_map(self, apps, primary_domain):
"""
Build a map of application IDs to their alias domains.
- If no `domains` key → []
- If `domains` exists but is an empty dict → return the original cfg
- Explicit `aliases` are used (default appended if missing)
- If only `canonical` defined and it doesn't include default, default is added
- Invalid types raise AnsibleFilterError
"""
def parse_entry(domains_cfg, key, app_id):
if key not in domains_cfg:
return None
entry = domains_cfg[key]
if isinstance(entry, dict):
values = list(entry.values())
elif isinstance(entry, list):
values = entry
else:
raise AnsibleFilterError(
f"Unexpected type for 'domains.{key}' in application '{app_id}': {type(entry).__name__}"
)
for d in values:
if not isinstance(d, str) or not d.strip():
raise AnsibleFilterError(
f"Invalid domain entry in '{key}' for application '{app_id}': {d!r}"
)
return values
def default_domain(app_id, primary):
return f"{app_id}.{primary}"
# 1) Precompute canonical domains per app (fallback to default)
canonical_map = {}
for app_id, cfg in apps.items():
domains_cfg = cfg.get('server',{}).get('domains',{})
entry = domains_cfg.get('canonical')
if entry is None:
canonical_map[app_id] = [default_domain(app_id, primary_domain)]
elif isinstance(entry, dict):
canonical_map[app_id] = list(entry.values())
elif isinstance(entry, list):
canonical_map[app_id] = list(entry)
else:
raise AnsibleFilterError(
f"Unexpected type for 'server.domains.canonical' in application '{app_id}': {type(entry).__name__}"
)
# 2) Build alias list per app
result = {}
for app_id, cfg in apps.items():
domains_cfg = cfg.get('server',{}).get('domains')
# no domains key → no aliases
if domains_cfg is None:
result[app_id] = []
continue
# empty domains dict → return the original cfg
if isinstance(domains_cfg, dict) and not domains_cfg:
result[app_id] = cfg
continue
# otherwise, compute aliases
aliases = parse_entry(domains_cfg, 'aliases', app_id) or []
default = default_domain(app_id, primary_domain)
has_aliases = 'aliases' in domains_cfg
has_canon = 'canonical' in domains_cfg
if has_aliases:
if default not in aliases:
aliases.append(default)
elif has_canon:
canon = canonical_map.get(app_id, [])
if default not in canon and default not in aliases:
aliases.append(default)
result[app_id] = aliases
return result

View File

@@ -4,47 +4,83 @@ import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from module_utils.entity_name_utils import get_entity_name
from module_utils.role_dependency_resolver import RoleDependencyResolver
from typing import Iterable
class FilterModule(object):
def filters(self):
return {'canonical_domains_map': self.canonical_domains_map}
def canonical_domains_map(self, apps, primary_domain):
def canonical_domains_map(
self,
apps,
PRIMARY_DOMAIN,
*,
recursive: bool = False,
roles_base_dir: str | None = None,
seed: Iterable[str] | None = None,
):
"""
Maps applications to their canonical domains, checking for conflicts
and ensuring all domains are valid and unique across applications.
Build { app_id: [canonical domains...] }.
Rekursiv werden nur include_role, import_role und meta/main.yml:dependencies verfolgt.
'run_after' wird hier absichtlich ignoriert.
"""
if not isinstance(apps, dict):
raise AnsibleFilterError(f"'apps' must be a dict, got {type(apps).__name__}")
app_keys = set(apps.keys())
seed_keys = set(seed) if seed is not None else app_keys
if recursive:
roles_base_dir = roles_base_dir or os.path.join(os.getcwd(), "roles")
if not os.path.isdir(roles_base_dir):
raise AnsibleFilterError(
f"roles_base_dir '{roles_base_dir}' not found or not a directory."
)
resolver = RoleDependencyResolver(roles_base_dir)
discovered_roles = resolver.resolve_transitively(
start_roles=seed_keys,
resolve_include_role=True,
resolve_import_role=True,
resolve_dependencies=True,
resolve_run_after=False,
max_depth=None,
)
# all discovered roles that actually have config entries in `apps`
target_apps = discovered_roles & app_keys
else:
target_apps = seed_keys
result = {}
seen_domains = {}
for app_id, cfg in apps.items():
if app_id.startswith((
"web-",
"svc-db-" # Database services can also be exposed to the internet. It is just listening to the port, but the domain is used for port mapping
)):
if not isinstance(cfg, dict):
raise AnsibleFilterError(
f"Invalid configuration for application '{app_id}': "
f"expected a dict, got {cfg!r}"
for app_id in sorted(target_apps):
cfg = apps.get(app_id)
if cfg is None:
continue
if not str(app_id).startswith(("web-", "svc-db-")):
continue
if not isinstance(cfg, dict):
raise AnsibleFilterError(
f"Invalid configuration for application '{app_id}': expected dict, got {cfg!r}"
)
domains_cfg = cfg.get('server',{}).get('domains',{})
if not domains_cfg or 'canonical' not in domains_cfg:
self._add_default_domain(app_id, primary_domain, seen_domains, result)
continue
canonical_domains = domains_cfg['canonical']
self._process_canonical_domains(app_id, canonical_domains, seen_domains, result)
domains_cfg = cfg.get('server', {}).get('domains', {})
if not domains_cfg or 'canonical' not in domains_cfg:
self._add_default_domain(app_id, PRIMARY_DOMAIN, seen_domains, result)
continue
canonical_domains = domains_cfg['canonical']
self._process_canonical_domains(app_id, canonical_domains, seen_domains, result)
return result
def _add_default_domain(self, app_id, primary_domain, seen_domains, result):
"""
Add the default domain for an application if no canonical domains are defined.
Ensures the domain is unique across applications.
"""
def _add_default_domain(self, app_id, PRIMARY_DOMAIN, seen_domains, result):
entity_name = get_entity_name(app_id)
default_domain = f"{entity_name}.{primary_domain}"
default_domain = f"{entity_name}.{PRIMARY_DOMAIN}"
if default_domain in seen_domains:
raise AnsibleFilterError(
f"Domain '{default_domain}' is already configured for "
@@ -54,40 +90,21 @@ class FilterModule(object):
result[app_id] = [default_domain]
def _process_canonical_domains(self, app_id, canonical_domains, seen_domains, result):
"""
Process the canonical domains for an application, handling both lists and dicts,
and ensuring each domain is unique.
"""
if isinstance(canonical_domains, dict):
self._process_canonical_domains_dict(app_id, canonical_domains, seen_domains, result)
for _, domain in canonical_domains.items():
self._validate_and_check_domain(app_id, domain, seen_domains)
result[app_id] = canonical_domains.copy()
elif isinstance(canonical_domains, list):
self._process_canonical_domains_list(app_id, canonical_domains, seen_domains, result)
for domain in canonical_domains:
self._validate_and_check_domain(app_id, domain, seen_domains)
result[app_id] = list(canonical_domains)
else:
raise AnsibleFilterError(
f"Unexpected type for 'server.domains.canonical' in application '{app_id}': "
f"{type(canonical_domains).__name__}"
)
def _process_canonical_domains_dict(self, app_id, domains_dict, seen_domains, result):
"""
Process a dictionary of canonical domains for an application.
"""
for name, domain in domains_dict.items():
self._validate_and_check_domain(app_id, domain, seen_domains)
result[app_id] = domains_dict.copy()
def _process_canonical_domains_list(self, app_id, domains_list, seen_domains, result):
"""
Process a list of canonical domains for an application.
"""
for domain in domains_list:
self._validate_and_check_domain(app_id, domain, seen_domains)
result[app_id] = list(domains_list)
def _validate_and_check_domain(self, app_id, domain, seen_domains):
"""
Validate the domain and check if it has already been assigned to another application.
"""
if not isinstance(domain, str) or not domain.strip():
raise AnsibleFilterError(
f"Invalid domain entry in 'canonical' for application '{app_id}': {domain!r}"

View File

@@ -1,6 +1,14 @@
from ansible.errors import AnsibleFilterError
import hashlib
import base64
import sys
import os
# Ensure module_utils is importable when this filter runs from Ansible
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from module_utils.config_utils import get_app_conf
from module_utils.get_url import get_url
class FilterModule(object):
"""
@@ -12,18 +20,36 @@ class FilterModule(object):
'build_csp_header': self.build_csp_header,
}
# -------------------------------
# Helpers
# -------------------------------
@staticmethod
def is_feature_enabled(applications: dict, feature: str, application_id: str) -> bool:
"""
Return True if applications[application_id].features[feature] is truthy.
Returns True if applications[application_id].features[feature] is truthy.
"""
app = applications.get(application_id, {})
return bool(app.get('features', {}).get(feature, False))
return get_app_conf(
applications,
application_id,
'features.' + feature,
False,
False
)
@staticmethod
def get_csp_whitelist(applications, application_id, directive):
app = applications.get(application_id, {})
wl = app.get('server',{}).get('csp', {}).get('whitelist', {}).get(directive, [])
"""
Returns a list of additional whitelist entries for a given directive.
Accepts both scalar and list in config; always returns a list.
"""
wl = get_app_conf(
applications,
application_id,
'server.csp.whitelist.' + directive,
False,
[]
)
if isinstance(wl, list):
return wl
if wl:
@@ -33,26 +59,45 @@ class FilterModule(object):
@staticmethod
def get_csp_flags(applications, application_id, directive):
"""
Dynamically extract all CSP flags for a given directive and return them as tokens,
e.g., "'unsafe-eval'", "'unsafe-inline'", etc.
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.
"""
app = applications.get(application_id, {})
flags = app.get('server',{}).get('csp', {}).get('flags', {}).get(directive, {})
tokens = []
# Defaults that apply to all apps
default_flags = {}
if directive in ('style-src', 'style-src-elem'):
default_flags = {'unsafe-inline': True}
for flag_name, enabled in flags.items():
configured = get_app_conf(
applications,
application_id,
'server.csp.flags.' + directive,
False,
{}
)
# Merge defaults with configured flags (configured overrides defaults)
merged = {**default_flags, **configured}
tokens = []
for flag_name, enabled in merged.items():
if enabled:
tokens.append(f"'{flag_name}'")
return tokens
@staticmethod
def get_csp_inline_content(applications, application_id, directive):
"""
Return inline script/style snippets to hash for a given CSP directive.
Returns inline script/style snippets to hash for a given directive.
Accepts both scalar and list in config; always returns a list.
"""
app = applications.get(application_id, {})
snippets = app.get('server',{}).get('csp', {}).get('hashes', {}).get(directive, [])
snippets = get_app_conf(
applications,
application_id,
'server.csp.hashes.' + directive,
False,
[]
)
if isinstance(snippets, list):
return snippets
if snippets:
@@ -62,7 +107,7 @@ class FilterModule(object):
@staticmethod
def get_csp_hash(content):
"""
Compute the SHA256 hash of the given inline content and return
Computes the SHA256 hash of the given inline content and returns
a CSP token like "'sha256-<base64>'".
"""
try:
@@ -72,6 +117,10 @@ class FilterModule(object):
except Exception as exc:
raise AnsibleFilterError(f"get_csp_hash failed: {exc}")
# -------------------------------
# Main builder
# -------------------------------
def build_csp_header(
self,
applications,
@@ -81,81 +130,80 @@ class FilterModule(object):
matomo_feature_name='matomo'
):
"""
Build the Content-Security-Policy header value dynamically based on application settings.
Inline hashes are read from applications[application_id].csp.hashes
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'.
"""
try:
directives = [
'default-src',
'connect-src',
'frame-ancestors',
'frame-src',
'script-src',
'script-src-elem',
'style-src',
'font-src',
'worker-src',
'manifest-src',
'media-src',
'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
]
parts = []
for directive in directives:
tokens = ["'self'"]
# unsafe-eval / unsafe-inline flags
# 1) Load flags (includes defaults from get_csp_flags)
flags = self.get_csp_flags(applications, application_id, directive)
tokens += flags
# Matomo integration
if (
self.is_feature_enabled(applications, matomo_feature_name, application_id)
and directive in ['script-src-elem', 'connect-src']
):
matomo_domain = domains.get('web-app-matomo')[0]
if matomo_domain:
tokens.append(f"{web_protocol}://{matomo_domain}")
# 2) Allow fetching from internal CDN by default for selected directives
if directive in ['script-src-elem', 'connect-src', 'style-src-elem']:
tokens.append(get_url(domains, 'web-svc-cdn', web_protocol))
# ReCaptcha integration: allow loading scripts from Google if feature enabled
# 3) Matomo integration if feature is 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
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')
# Allow the loading of js from the cdn
if directive == 'script-src-elem' and self.is_feature_enabled(applications, 'logout', application_id):
domain = domains.get('web-svc-cdn')[0]
tokens.append(f"{domain}")
# 5) Frame ancestors handling (desktop + logout support)
if directive == 'frame-ancestors':
# Enable loading via ancestors
if self.is_feature_enabled(applications, 'port-ui-desktop', application_id):
domain = domains.get('web-app-port-ui')[0]
sld_tld = ".".join(domain.split(".")[-2:]) # yields "example.com"
tokens.append(f"{sld_tld}") # yields "*.example.com"
if self.is_feature_enabled(applications, 'desktop', application_id):
# Allow being embedded by the desktop app domain (and potentially its parent)
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 logout via infinito logout proxy
domain = domains.get('web-svc-logout')[0]
tokens.append(f"{domain}")
# Allow logout via keycloak app
domain = domains.get('web-app-keycloak')[0]
tokens.append(f"{domain}")
# whitelist
# 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
tokens += self.get_csp_whitelist(applications, application_id, directive)
# only add hashes if 'unsafe-inline' is NOT in flags
if "'unsafe-inline'" not in flags:
# 7) Add inline content hashes ONLY if final tokens do NOT include 'unsafe-inline'
# (Check tokens, not flags, to include defaults and later modifications.)
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)};")
# static img-src
# 8) Static img-src directive (kept permissive for data/blob and any host)
parts.append("img-src * data: blob:;")
return ' '.join(parts)
except Exception as exc:

View File

@@ -7,7 +7,7 @@ class FilterModule(object):
def filters(self):
return {'domain_mappings': self.domain_mappings}
def domain_mappings(self, apps, primary_domain):
def domain_mappings(self, apps, PRIMARY_DOMAIN):
"""
Build a flat list of redirect mappings for all apps:
- source: each alias domain
@@ -43,7 +43,7 @@ class FilterModule(object):
domains_cfg = cfg.get('server',{}).get('domains',{})
entry = domains_cfg.get('canonical')
if entry is None:
canonical_map[app_id] = [default_domain(app_id, primary_domain)]
canonical_map[app_id] = [default_domain(app_id, PRIMARY_DOMAIN)]
elif isinstance(entry, dict):
canonical_map[app_id] = list(entry.values())
elif isinstance(entry, list):
@@ -61,11 +61,11 @@ class FilterModule(object):
alias_map[app_id] = []
continue
if isinstance(domains_cfg, dict) and not domains_cfg:
alias_map[app_id] = [default_domain(app_id, primary_domain)]
alias_map[app_id] = [default_domain(app_id, PRIMARY_DOMAIN)]
continue
aliases = parse_entry(domains_cfg, 'aliases', app_id) or []
default = default_domain(app_id, primary_domain)
default = default_domain(app_id, PRIMARY_DOMAIN)
has_aliases = 'aliases' in domains_cfg
has_canonical = 'canonical' in domains_cfg
@@ -84,7 +84,7 @@ class FilterModule(object):
mappings = []
for app_id, sources in alias_map.items():
canon_list = canonical_map.get(app_id, [])
target = canon_list[0] if canon_list else default_domain(app_id, primary_domain)
target = canon_list[0] if canon_list else default_domain(app_id, PRIMARY_DOMAIN)
for src in sources:
if src == target:
# skip self-redirects

View File

@@ -0,0 +1,19 @@
# filter_plugins/domain_tools.py
# Returns the DNS zone (SLD.TLD) from a hostname.
# Pure-Python, no external deps; handles simple cases. For exotic TLDs use tldextract (see note).
from ansible.errors import AnsibleFilterError
def to_zone(hostname: str) -> str:
if not isinstance(hostname, str) or not hostname.strip():
raise AnsibleFilterError("to_zone: hostname must be a non-empty string")
parts = hostname.strip(".").split(".")
if len(parts) < 2:
raise AnsibleFilterError(f"to_zone: '{hostname}' has no TLD part")
# naive default: last two labels -> SLD.TLD
return ".".join(parts[-2:])
class FilterModule(object):
def filters(self):
return {
"to_zone": to_zone,
}

View File

@@ -1,49 +0,0 @@
import os
import re
import yaml
from ansible.errors import AnsibleFilterError
def get_application_id(role_name):
"""
Jinja2/Ansible filter: given a role name, load its vars/main.yml and return the application_id value.
"""
# Construct path: assumes current working directory is project root
vars_file = os.path.join(os.getcwd(), 'roles', role_name, 'vars', 'main.yml')
if not os.path.isfile(vars_file):
raise AnsibleFilterError(f"Vars file not found for role '{role_name}': {vars_file}")
try:
# Read entire file content to avoid lazy stream issues
with open(vars_file, 'r', encoding='utf-8') as f:
content = f.read()
data = yaml.safe_load(content)
except Exception as e:
raise AnsibleFilterError(f"Error reading YAML from {vars_file}: {e}")
# Ensure parsed data is a mapping
if not isinstance(data, dict):
raise AnsibleFilterError(
f"Error reading YAML from {vars_file}: expected mapping, got {type(data).__name__}"
)
# Detect malformed YAML: no valid identifier-like keys
valid_key_pattern = re.compile(r'^[A-Za-z_][A-Za-z0-9_]*$')
if data and not any(valid_key_pattern.match(k) for k in data.keys()):
raise AnsibleFilterError(f"Error reading YAML from {vars_file}: invalid top-level keys")
if 'application_id' not in data:
raise AnsibleFilterError(f"Key 'application_id' not found in {vars_file}")
return data['application_id']
class FilterModule(object):
"""
Ansible filter plugin entry point.
"""
def filters(self):
return {
'get_application_id': get_application_id,
}

View File

@@ -0,0 +1,31 @@
# Custom Ansible filter to get all role names under "roles/" with a given prefix.
import os
def get_category_entries(prefix, roles_path="roles"):
"""
Returns a list of role names under the given roles_path
that start with the specified prefix.
:param prefix: String prefix to match role names.
:param roles_path: Path to the roles directory (default: 'roles').
:return: List of matching role names.
"""
if not os.path.isdir(roles_path):
return []
roles = []
for entry in os.listdir(roles_path):
full_path = os.path.join(roles_path, entry)
if os.path.isdir(full_path) and entry.startswith(prefix):
roles.append(entry)
return sorted(roles)
class FilterModule(object):
""" Custom filters for Ansible """
def filters(self):
return {
"get_category_entries": get_category_entries
}

View File

@@ -0,0 +1,37 @@
"""
Custom Ansible filter to build a systemctl unit name (always lowercase).
Rules:
- If `systemctl_id` ends with '@': drop the '@' and return
"{systemctl_id_without_at}.{software_name}@{suffix_handling}".
- Else: return "{systemctl_id}.{software_name}{suffix_handling}".
Suffix handling:
- Default "" → automatically pick:
- ".service" if no '@' in systemctl_id
- ".timer" if '@' in systemctl_id
- Explicit False → no suffix at all
- Any string → ".{suffix}" (lowercased)
"""
def get_service_name(systemctl_id, software_name, suffix=""):
sid = str(systemctl_id).strip().lower()
software_name = str(software_name).strip().lower()
# Determine suffix
if suffix is False:
sfx = "" # no suffix at all
elif suffix == "" or suffix is None:
sfx = ".service"
else:
sfx = str(suffix).strip().lower()
if sid.endswith("@"):
base = sid[:-1] # drop the trailing '@'
return f"{base}.{software_name}@{sfx}"
else:
return f"{sid}.{software_name}{sfx}"
class FilterModule(object):
def filters(self):
return {"get_service_name": get_service_name}

View File

@@ -0,0 +1,24 @@
# filter_plugins/get_service_script_path.py
# Custom Ansible filter to generate service script paths.
def get_service_script_path(systemctl_id, script_type):
"""
Build the path to a service script based on systemctl_id and type.
:param systemctl_id: The identifier of the system service.
:param script_type: The script type/extension (e.g., sh, py, yml).
:return: The full path string.
"""
if not systemctl_id or not script_type:
raise ValueError("Both systemctl_id and script_type are required")
return f"/opt/scripts/systemctl/{systemctl_id}/script.{script_type}"
class FilterModule(object):
""" Custom filters for Ansible """
def filters(self):
return {
"get_service_script_path": get_service_script_path
}

View File

@@ -1,122 +0,0 @@
import os
import yaml
import re
from ansible.errors import AnsibleFilterError
# in-memory cache: application_id → (parsed_yaml, is_nested)
_cfg_cache = {}
def load_configuration(application_id, key):
if not isinstance(key, str):
raise AnsibleFilterError("Key must be a dotted-string, e.g. 'features.matomo'")
# locate roles/
here = os.path.dirname(__file__)
root = os.path.abspath(os.path.join(here, '..'))
roles_dir = os.path.join(root, 'roles')
if not os.path.isdir(roles_dir):
raise AnsibleFilterError(f"Roles directory not found at {roles_dir}")
# first time? load & cache
if application_id not in _cfg_cache:
config_path = None
# 1) primary: vars/main.yml declares it
for role in os.listdir(roles_dir):
mv = os.path.join(roles_dir, role, 'vars', 'main.yml')
if os.path.exists(mv):
try:
md = yaml.safe_load(open(mv)) or {}
except Exception:
md = {}
if md.get('application_id') == application_id:
cf = os.path.join(roles_dir, role, "config" , "main.yml")
if not os.path.exists(cf):
raise AnsibleFilterError(
f"Role '{role}' declares '{application_id}' but missing config/main.yml"
)
config_path = cf
break
# 2) fallback nested
if config_path is None:
for role in os.listdir(roles_dir):
cf = os.path.join(roles_dir, role, "config" , "main.yml")
if not os.path.exists(cf):
continue
try:
dd = yaml.safe_load(open(cf)) or {}
except Exception:
dd = {}
if isinstance(dd, dict) and application_id in dd:
config_path = cf
break
# 3) fallback flat
if config_path is None:
for role in os.listdir(roles_dir):
cf = os.path.join(roles_dir, role, "config" , "main.yml")
if not os.path.exists(cf):
continue
try:
dd = yaml.safe_load(open(cf)) or {}
except Exception:
dd = {}
# flat style: dict with all non-dict values
if isinstance(dd, dict) and not any(isinstance(v, dict) for v in dd.values()):
config_path = cf
break
if config_path is None:
return None
# parse once
try:
parsed = yaml.safe_load(open(config_path)) or {}
except Exception as e:
raise AnsibleFilterError(f"Error loading config/main.yml at {config_path}: {e}")
# detect nested vs flat
is_nested = isinstance(parsed, dict) and (application_id in parsed)
_cfg_cache[application_id] = (parsed, is_nested)
parsed, is_nested = _cfg_cache[application_id]
# pick base entry
entry = parsed[application_id] if is_nested else parsed
# resolve dotted key
key_parts = key.split('.')
for part in key_parts:
# Check if part has an index (e.g., domains.canonical[0])
match = re.match(r'([^\[]+)\[([0-9]+)\]', part)
if match:
part, index = match.groups()
index = int(index)
if isinstance(entry, dict) and part in entry:
entry = entry[part]
# Check if entry is a list and access the index
if isinstance(entry, list) and 0 <= index < len(entry):
entry = entry[index]
else:
raise AnsibleFilterError(
f"Index '{index}' out of range for key '{part}' in application '{application_id}'"
)
else:
raise AnsibleFilterError(
f"Key '{part}' not found under application '{application_id}'"
)
else:
if isinstance(entry, dict) and part in entry:
entry = entry[part]
else:
raise AnsibleFilterError(
f"Key '{part}' not found under application '{application_id}'"
)
return entry
class FilterModule(object):
def filters(self):
return {'load_configuration': load_configuration}

View File

@@ -19,7 +19,7 @@ class FilterModule(object):
Usage in Jinja:
{{ redirect_list
| add_redirect_if_group('lam',
'ldap.' ~ primary_domain,
'ldap.' ~ PRIMARY_DOMAIN,
domains | get_domain('web-app-lam'),
group_names) }}
"""

View File

@@ -1,55 +0,0 @@
from jinja2 import Undefined
def safe_placeholders(template: str, mapping: dict = None) -> str:
"""
Format a template like "{url}/logo.png".
If mapping is provided (not None) and ANY placeholder is missing or maps to None/empty string, the function will raise KeyError.
If mapping is None, missing placeholders or invalid templates return empty string.
Numerical zero or False are considered valid values.
Any other formatting errors return an empty string.
"""
# Non-string templates yield empty
if not isinstance(template, str):
return ''
class SafeDict(dict):
def __getitem__(self, key):
val = super().get(key, None)
# Treat None or empty string as missing
if val is None or (isinstance(val, str) and val == ''):
raise KeyError(key)
return val
def __missing__(self, key):
raise KeyError(key)
silent = mapping is None
data = mapping or {}
try:
return template.format_map(SafeDict(data))
except KeyError:
if silent:
return ''
raise
except Exception:
return ''
def safe_var(value):
"""
Ansible filter: returns the value unchanged unless it's Undefined or None,
in which case returns an empty string.
Catches all exceptions and yields ''.
"""
try:
if isinstance(value, Undefined) or value is None:
return ''
return value
except Exception:
return ''
class FilterModule(object):
def filters(self):
return {
'safe_var': safe_var,
'safe_placeholders': safe_placeholders,
}

View File

@@ -1,28 +0,0 @@
"""
Ansible filter plugin that joins a base string and a tail path safely.
If the base is falsy (None, empty, etc.), returns an empty string.
"""
def safe_join(base, tail):
"""
Safely join base and tail into a path or URL.
- base: the base string. If falsy, returns ''.
- tail: the string to append. Leading/trailing slashes are handled.
- On any exception, returns ''.
"""
try:
if not base:
return ''
base_str = str(base).rstrip('/')
tail_str = str(tail).lstrip('/')
return f"{base_str}/{tail_str}"
except Exception:
return ''
class FilterModule(object):
def filters(self):
return {
'safe_join': safe_join,
}

View File

@@ -0,0 +1,67 @@
# filter_plugins/timeout_start_sec_for_domains.py (nur Kern geändert)
from ansible.errors import AnsibleFilterError
class FilterModule(object):
def filters(self):
return {
"timeout_start_sec_for_domains": self.timeout_start_sec_for_domains,
}
def timeout_start_sec_for_domains(
self,
domains_dict,
include_www=True,
per_domain_seconds=25,
overhead_seconds=30,
min_seconds=120,
max_seconds=3600,
):
"""
Args:
domains_dict (dict | list[str] | str): Either the domain mapping dict
(values can be str | list[str] | dict[str,str]) or an already
flattened list of domains, or a single domain string.
include_www (bool): If true, add 'www.<domain>' for non-www entries.
...
"""
try:
# Local flattener for dict inputs (like your generate_all_domains source)
def _flatten_from_dict(domains_map):
flat = []
for v in (domains_map or {}).values():
if isinstance(v, str):
flat.append(v)
elif isinstance(v, list):
flat.extend(v)
elif isinstance(v, dict):
flat.extend(v.values())
return flat
# Accept dict | list | str
if isinstance(domains_dict, dict):
flat = _flatten_from_dict(domains_dict)
elif isinstance(domains_dict, list):
flat = list(domains_dict)
elif isinstance(domains_dict, str):
flat = [domains_dict]
else:
raise AnsibleFilterError(
"Expected 'domains_dict' to be dict | list | str."
)
if include_www:
base_unique = sorted(set(flat))
www_variants = [f"www.{d}" for d in base_unique if not str(d).lower().startswith("www.")]
flat.extend(www_variants)
unique_domains = sorted(set(flat))
count = len(unique_domains)
raw = overhead_seconds + per_domain_seconds * count
clamped = max(min_seconds, min(max_seconds, int(raw)))
return clamped
except AnsibleFilterError:
raise
except Exception as exc:
raise AnsibleFilterError(f"timeout_start_sec_for_domains failed: {exc}")

146
filter_plugins/url_join.py Normal file
View File

@@ -0,0 +1,146 @@
"""
Ansible filter plugin that safely joins URL components from a list.
- Requires a valid '<scheme>://' in the first element (any RFC-3986-ish scheme)
- Preserves the double slash after the scheme, collapses other duplicate slashes
- Supports query parts introduced by elements starting with '?' or '&'
* first query element uses '?', subsequent use '&' (regardless of given prefix)
* each query element must be exactly one 'key=value' pair
* query elements may only appear after path elements; once query starts, no more path parts
- Raises specific AnsibleFilterError messages for common misuse
"""
import re
from ansible.errors import AnsibleFilterError
_SCHEME_RE = re.compile(r'^([a-zA-Z][a-zA-Z0-9+.\-]*://)(.*)$')
_QUERY_PAIR_RE = re.compile(r'^[^&=?#]+=[^&?#]*$') # key=value (no '&', no extra '?' or '#')
def _to_str_or_error(obj, index):
"""Cast to str, raising a specific AnsibleFilterError with index context."""
try:
return str(obj)
except Exception as e:
raise AnsibleFilterError(
f"url_join: unable to convert part at index {index} to string: {e}"
)
def url_join(parts):
"""
Join a list of URL parts, URL-aware (scheme, path, query).
Args:
parts (list|tuple): URL segments. First element MUST include '<scheme>://'.
Path elements are plain strings.
Query elements must start with '?' or '&' and contain exactly one 'key=value'.
Returns:
str: Joined URL.
Raises:
AnsibleFilterError: with specific, descriptive messages.
"""
# --- basic input validation ---
if parts is None:
raise AnsibleFilterError("url_join: parts must be a non-empty list; got None")
if not isinstance(parts, (list, tuple)):
raise AnsibleFilterError(
f"url_join: parts must be a list/tuple; got {type(parts).__name__}"
)
if len(parts) == 0:
raise AnsibleFilterError("url_join: parts must be a non-empty list")
# --- first element must carry a scheme ---
first_raw = parts[0]
if first_raw is None:
raise AnsibleFilterError(
"url_join: first element must include a scheme like 'https://'; got None"
)
first_str = _to_str_or_error(first_raw, 0)
m = _SCHEME_RE.match(first_str)
if not m:
raise AnsibleFilterError(
"url_join: first element must start with '<scheme>://', e.g. 'https://example.com'; "
f"got '{first_str}'"
)
scheme = m.group(1) # e.g., 'https://', 'ftp://', 'myapp+v1://'
after_scheme = m.group(2).lstrip('/') # strip only leading slashes right after scheme
# --- iterate parts: collect path parts until first query part; then only query parts allowed ---
path_parts = []
query_pairs = []
in_query = False
for i, p in enumerate(parts):
if p is None:
# skip None silently (consistent with path_join-ish behavior)
continue
s = _to_str_or_error(p, i)
# disallow additional scheme in later parts
if i > 0 and "://" in s:
raise AnsibleFilterError(
f"url_join: only the first element may contain a scheme; part at index {i} "
f"looks like a URL with scheme ('{s}')."
)
# first element: replace with remainder after scheme and continue
if i == 0:
s = after_scheme
# check if this is a query element (starts with ? or &)
if s.startswith('?') or s.startswith('&'):
in_query = True
raw_pair = s[1:] # strip the leading ? or &
if raw_pair == '':
raise AnsibleFilterError(
f"url_join: query element at index {i} is empty; expected '?key=value' or '&key=value'"
)
# Disallow multiple pairs in a single element; enforce exactly one key=value
if '&' in raw_pair:
raise AnsibleFilterError(
f"url_join: query element at index {i} must contain exactly one 'key=value' pair "
f"without '&'; got '{s}'"
)
if not _QUERY_PAIR_RE.match(raw_pair):
raise AnsibleFilterError(
f"url_join: query element at index {i} must match 'key=value' (no extra '?', '&', '#'); got '{s}'"
)
query_pairs.append(raw_pair)
else:
# non-query element
if in_query:
# once query started, no more path parts allowed
raise AnsibleFilterError(
f"url_join: path element found at index {i} after query parameters started; "
f"query parts must come last"
)
# normal path part: strip slashes to avoid duplicate '/'
path_parts.append(s.strip('/'))
# normalize path: remove empty chunks
path_parts = [p for p in path_parts if p != '']
# --- build result ---
# path portion
if path_parts:
joined_path = "/".join(path_parts)
base = scheme + joined_path
else:
# no path beyond scheme
base = scheme
# query portion
if query_pairs:
base = base + "?" + "&".join(query_pairs)
return base
class FilterModule(object):
def filters(self):
return {
'url_join': url_join,
}

View File

@@ -0,0 +1,21 @@
from ansible.errors import AnsibleFilterError
def docker_volume_path(volume_name: str) -> str:
"""
Returns the absolute filesystem path of a Docker volume.
Example:
"akaunting_data" -> "/var/lib/docker/volumes/akaunting_data/_data/"
"""
if not volume_name or not isinstance(volume_name, str):
raise AnsibleFilterError(f"Invalid volume name: {volume_name}")
return f"/var/lib/docker/volumes/{volume_name}/_data/"
class FilterModule(object):
"""Docker volume path filters."""
def filters(self):
return {
"docker_volume_path": docker_volume_path,
}

View File

@@ -1,4 +1,8 @@
INFINITO_ENVIRONMENT: "production" # Possible values: production, development
SOFTWARE_NAME: "Infinito.Nexus" # Name of the software
# Deployment
ENVIRONMENT: "production" # Possible values: production, development
DEPLOYMENT_MODE: "single" # Use single, if you deploy on one server. Use cluster if you setup in cluster mode.
# If true, sensitive credentials will be masked or hidden from all Ansible task logs
# Recommendet to set to true
@@ -18,54 +22,41 @@ HOST_TIME_FORMAT: "HH:mm"
HOST_THOUSAND_SEPARATOR: "."
HOST_DECIMAL_MARK: ","
# Deployment mode
deployment_mode: "single" # Use single, if you deploy on one server. Use cluster if you setup in cluster mode.
# Web
WEB_PROTOCOL: "https" # Web protocol type. Use https or http. If you run local you need to change it to http
WEB_PORT: "{{ 443 if WEB_PROTOCOL == 'https' else 80 }}" # Default port web applications will listen to
## Domain
primary_domain_tld: "localhost" # Top Level Domain of the server
primary_domain_sld: "infinito" # Second Level Domain of the server
primary_domain: "{{primary_domain_sld}}.{{primary_domain_tld}}" # Primary Domain of the server
# Websocket
WEBSOCKET_PROTOCOL: "{{ 'wss' if WEB_PROTOCOL == 'https' else 'ws' }}"
# Server Tact Variables
# Domain
PRIMARY_DOMAIN: "localhost" # Primary Domain of the server
## Ours in which the server is "awake" (100% working). Rest of the time is reserved for maintanance
hours_server_awake: "0..23"
DNS_PROVIDER: cloudflare # The DNS Provider\Registrar for the domain
## Random delay for systemd timers to avoid peak loads.
randomized_delay_sec: "5min"
# Runtime Variables for Process Control
activate_all_timers: false # Activates all timers, independend if the handlers had been triggered
# This enables debugging in ansible and in the apps
# You SHOULD NOT enable this on production servers
enable_debug: false
dns_provider: cloudflare # The DNS Provider\Registrar for the domain
HOSTING_PROVIDER: hetzner # Provider which hosts the server
# Which ACME method to use: webroot, cloudflare, or hetzner
certbot_acme_challenge_method: "cloudflare"
certbot_credentials_dir: /etc/certbot
certbot_credentials_file: "{{ certbot_credentials_dir }}/{{ certbot_acme_challenge_method }}.ini"
certbot_dns_api_token: "" # Define in inventory file: More information here: group_vars/all/docs/CLOUDFLARE_API_TOKEN.md
certbot_dns_propagation_wait_seconds: 300 # How long should the script wait for DNS propagation before continuing
certbot_flavor: san # Possible options: san (recommended, with a dns flavor like cloudflare, or hetzner), wildcard(doesn't function with www redirect), dedicated
CERTBOT_ACME_CHALLENGE_METHOD: "cloudflare"
CERTBOT_CREDENTIALS_DIR: /etc/certbot
CERTBOT_CREDENTIALS_FILE: "{{ CERTBOT_CREDENTIALS_DIR }}/{{ CERTBOT_ACME_CHALLENGE_METHOD }}.ini"
CERTBOT_DNS_PROPAGATION_WAIT_SECONDS: 300 # How long should the script wait for DNS propagation before continuing
CERTBOT_FLAVOR: san # Possible options: san (recommended, with a dns flavor like cloudflare, or hetzner), wildcard(doesn't function with www redirect), dedicated
# Path where Certbot stores challenge webroot files
letsencrypt_webroot_path: "/var/lib/letsencrypt/"
# Letsencrypt
LETSENCRYPT_WEBROOT_PATH: "/var/lib/letsencrypt/" # Path where Certbot stores challenge webroot files
LETSENCRYPT_BASE_PATH: "/etc/letsencrypt/" # Base directory containing Certbot configuration, account data, and archives
LETSENCRYPT_LIVE_PATH: "{{ LETSENCRYPT_BASE_PATH }}live/" # Symlink directory for the current active certificate and private key
# Base directory containing Certbot configuration, account data, and archives
letsencrypt_base_path: "/etc/letsencrypt/"
## Docker
DOCKER_RESTART_POLICY: "unless-stopped" # Default restart parameter for docker containers
DOCKER_VARS_FILE: "{{ playbook_dir }}/roles/docker-compose/vars/docker-compose.yml" # File containing docker compose variables used by other services
DOCKER_WHITELISTET_ANON_VOLUMES: [] # Volumes which should be ignored during docker anonymous health check
# Symlink directory for the current active certificate and private key
letsencrypt_live_path: "{{ letsencrypt_base_path }}live/"
## Docker Role Specific Parameters
DOCKER_RESTART_POLICY: "unless-stopped"
DOCKER_VARS_FILE: "{{ playbook_dir }}/roles/docker-compose/vars/docker-compose.yml"
# Asyn Confitguration
ASYNC_ENABLED: "{{ not MODE_DEBUG | bool }}" # Activate async, deactivated for debugging
ASYNC_TIME: "{{ 300 if ASYNC_ENABLED | bool else omit }}" # Run for mnax 5min
ASYNC_POLL: "{{ 0 if ASYNC_ENABLED | bool else 10 }}" # Don't wait for task
# default value if not set via CLI (-e) or in playbook vars
allowed_applications: []
@@ -83,4 +74,11 @@ _applications_nextcloud_oidc_flavor: >-
| get_app_conf('web-app-nextcloud','features.ldap',False, True)
else 'sociallogin'
)
}}
}}
# Role-based access control
# @See https://en.wikipedia.org/wiki/Role-based_access_control
RBAC:
GROUP:
NAME: "/roles" # Name of the group which holds the RBAC roles
CLAIM: "groups" # Name of the claim containing the RBAC groups

View File

@@ -1,8 +1,10 @@
# Mode
# The following modes can be combined with each other
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_test: false # Executes test routines instead of productive routines
mode_update: true # Executes updates
mode_backup: true # Activates the backup before the update procedure
mode_cleanup: true # Cleanup unused files and configurations
MODE_TEST: false # Executes test routines instead of productive routines
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_BACKUP: "{{ MODE_UPDATE }}" # Activates the backup before the update procedure
MODE_CLEANUP: "{{ MODE_DEBUG }}" # Cleanup unused files and configurations
MODE_ASSERT: "{{ MODE_DEBUG }}" # Executes validation tasks during the run.

View File

@@ -0,0 +1,8 @@
# Email Configuration
DEFAULT_SYSTEM_EMAIL:
DOMAIN: "{{ PRIMARY_DOMAIN }}"
HOST: "mail.{{ PRIMARY_DOMAIN }}"
PORT: 465
TLS: true # true for TLS and false for SSL
START_TLS: false
SMTP: true

View File

@@ -1,9 +0,0 @@
# Email Configuration
default_system_email:
domain: "{{primary_domain}}"
host: "mail.{{primary_domain}}"
port: 465
tls: true # true for TLS and false for SSL
start_tls: false
smtp: true
# password: # Needs to be defined in inventory file

View File

@@ -1,38 +0,0 @@
# System maintenance Services
## Timeouts to wait for other services to stop
system_maintenance_lock_timeout_cleanup_services: "15min"
system_maintenance_lock_timeout_storage_optimizer: "10min"
system_maintenance_lock_timeout_backup_services: "1h"
system_maintenance_lock_timeout_heal_docker: "30min"
system_maintenance_lock_timeout_update_docker: "2min"
system_maintenance_lock_timeout_restart_docker: "{{system_maintenance_lock_timeout_update_docker}}"
## Services
### Defined Services for Backup Tasks
system_maintenance_backup_services:
- "sys-bkp-docker-2-loc"
- "svc-bkp-rmt-2-loc"
- "svc-bkp-loc-2-usb"
- "sys-bkp-docker-2-loc-everything"
### Defined Services for System Cleanup
system_maintenance_cleanup_services:
- "sys-cln-backups"
- "sys-cln-disc-space"
- "sys-cln-faild-bkps"
### Services that Manipulate the System
system_maintenance_manipulation_services:
- "sys-rpr-docker-soft"
- "update-docker"
- "svc-opt-ssd-hdd"
- "sys-rpr-docker-hard"
## Total System Maintenance Services
system_maintenance_services: "{{ system_maintenance_backup_services + system_maintenance_cleanup_services + system_maintenance_manipulation_services }}"
### Define Variables for Docker Volume Health services
whitelisted_anonymous_docker_volumes: []

View File

@@ -0,0 +1,32 @@
# Webserver Configuration
# Helper
_nginx_www_dir: "{{ applications | get_app_conf('svc-prx-openresty','docker.volumes.www') }}"
_nginx_dir: "{{ applications | get_app_conf('svc-prx-openresty','docker.volumes.nginx') }}"
_nginx_conf_dir: "{{ _nginx_dir }}conf.d/"
_nginx_http_dir: "{{ _nginx_conf_dir }}http/"
## Nginx-Specific Path Configurations
NGINX:
FILES:
CONFIGURATION: "{{ _nginx_dir }}nginx.conf"
DIRECTORIES:
CONFIGURATION: "{{ _nginx_conf_dir }}" # Configuration directory
HTTP:
GLOBAL: "{{ _nginx_http_dir }}global/" # Contains global configurations which will be loaded into the http block
SERVERS: "{{ _nginx_http_dir }}servers/" # Contains one configuration per domain
MAPS: "{{ _nginx_http_dir }}maps/" # Contains mappings
STREAMS: "{{ _nginx_conf_dir }}streams/" # Contains streams configuration e.g. for ldaps
DATA:
WWW: "{{ _nginx_www_dir }}"
WELL_KNOWN: "/usr/share/nginx/well-known/" # Path where well-known files are stored
HTML: "{{ _nginx_www_dir }}public_html/" # Path where the static homepage files are stored
FILES: "{{ _nginx_www_dir }}public_files/" # Path where the web accessable files are stored
CDN: "{{ _nginx_www_dir }}public_cdn/" # Contains files which will be accessable via the content delivery network
GLOBAL: "{{ _nginx_www_dir }}global/" # Directory containing files which will be globaly accessable, @Todo remove this when css migrated to CDN
CACHE:
GENERAL: "/tmp/cache_nginx_general/" # Directory which nginx uses to cache general data
IMAGE: "/tmp/cache_nginx_image/" # Directory which nginx uses to cache images
USER: "http" # Default nginx user in ArchLinux
# @todo It propably makes sense to distinguish between target and source mount path, so that the config files can be stored in the openresty volumes folder

View File

@@ -1,26 +0,0 @@
# Webserver Configuration
# Helper
_nginx_www_dir: /var/www/
## Nginx-Specific Path Configurations
nginx:
files:
configuration: "/etc/nginx/nginx.conf"
directories:
configuration: "/etc/nginx/conf.d/" # Configuration directory
http:
global: "/etc/nginx/conf.d/http/global/" # Contains global configurations which will be loaded into the http block
servers: "/etc/nginx/conf.d/http/servers/" # Contains one configuration per domain
maps: "/etc/nginx/conf.d/http/maps/" # Contains mappings
streams: "/etc/nginx/conf.d/streams/" # Contains streams configuration e.g. for ldaps
data:
www: "{{ _nginx_www_dir }}"
well_known: "/usr/share/nginx/well-known/" # Path where well-known files are stored
html: "{{ _nginx_www_dir }}public_html/" # Path where the static homepage files are stored
files: "{{ _nginx_www_dir }}public_files/" # Path where the web accessable files are stored
cdn: "{{ _nginx_www_dir }}public_cdn/" # Contains files which will be accessable via the content delivery network
global: "{{ _nginx_www_dir }}global/" # Directory containing files which will be globaly accessable
cache:
general: "/tmp/cache_nginx_general/" # Directory which nginx uses to cache general data
image: "/tmp/cache_nginx_image/" # Directory which nginx uses to cache images
user: "http" # Default nginx user in ArchLinux

View File

@@ -0,0 +1,9 @@
# Path Variables for Key Directories and Scripts
PATH_ADMINISTRATOR_HOME: "/home/administrator/"
PATH_ADMINISTRATOR_SCRIPTS: "/opt/scripts/"
PATH_SYSTEMCTL_SCRIPTS: "{{ [ PATH_ADMINISTRATOR_SCRIPTS, 'systemctl' ] | path_join }}"
PATH_DOCKER_COMPOSE_INSTANCES: "/opt/docker/"
PATH_SYSTEM_LOCK_SCRIPT: "/opt/scripts/sys-lock.py"
PATH_SYSTEM_SERVICE_DIR: "/etc/systemd/system"
PATH_DOCKER_COMPOSE_PULL_LOCK_DIR: "/run/ansible/compose-pull/"

View File

@@ -1,6 +0,0 @@
# Path Variables for Key Directories and Scripts
path_administrator_home: "/home/administrator/"
path_administrator_scripts: "/opt/scripts/"
path_docker_compose_instances: "/opt/docker/"
path_system_lock_script: "/opt/scripts/sys-lock.py"

View File

@@ -0,0 +1,51 @@
# Services
## Meta
SYS_SERVICE_SUFFIX: ".{{ SOFTWARE_NAME | lower }}.service"
## Names
SYS_SERVICE_CLEANUP_BACKUPS_FAILED: "{{ 'sys-ctl-cln-faild-bkps' | get_service_name(SOFTWARE_NAME) }}"
SYS_SERVICE_CLEANUP_ANONYMOUS_VOLUMES: "{{ 'sys-ctl-cln-anon-volumes' | get_service_name(SOFTWARE_NAME) }}"
SYS_SERVICE_OPTIMIZE_DRIVE: "{{ 'svc-opt-ssd-hdd' | get_service_name(SOFTWARE_NAME) }}"
SYS_SERVICE_BACKUP_RMT_2_LOC: "{{ 'svc-bkp-rmt-2-loc' | get_service_name(SOFTWARE_NAME) }}"
SYS_SERVICE_BACKUP_DOCKER_2_LOC: "{{ 'sys-ctl-bkp-docker-2-loc' | get_service_name(SOFTWARE_NAME) }}"
SYS_SERVICE_REPAIR_DOCKER_SOFT: "{{ 'sys-ctl-rpr-docker-soft' | get_service_name(SOFTWARE_NAME) }}"
SYS_SERVICE_REPAIR_DOCKER_HARD: "{{ 'sys-ctl-rpr-docker-hard' | get_service_name(SOFTWARE_NAME) }}"
## On Failure
SYS_SERVICE_ON_FAILURE_COMPOSE: "{{ ('sys-ctl-alm-compose@') | get_service_name(SOFTWARE_NAME, False) }}%n.service"
## Groups
SYS_SERVICE_GROUP_BACKUPS: >
{{ (('sys-ctl-bkp-' | get_category_entries) + ('svc-bkp-' | get_category_entries))
| map('regex_replace', '$', SYS_SERVICE_SUFFIX) | list | sort }}
SYS_SERVICE_GROUP_CLEANUP: >
{{ ('sys-ctl-cln-' | get_category_entries)
| map('regex_replace', '$', SYS_SERVICE_SUFFIX) | list | sort }}
SYS_SERVICE_GROUP_REPAIR: >
{{ ('sys-ctl-rpr-' | get_category_entries)
| map('regex_replace', '$', SYS_SERVICE_SUFFIX) | list | sort }}
SYS_SERVICE_GROUP_OPTIMIZATION: >
{{ ('svc-opt-' | get_category_entries)
| map('regex_replace', '$', SYS_SERVICE_SUFFIX) | list | sort }}
SYS_SERVICE_GROUP_MAINTANANCE: >
{{ ('svc-mtn-' | get_category_entries)
| map('regex_replace', '$', SYS_SERVICE_SUFFIX) | list | sort }}
## Collection of services to manipulate the system
SYS_SERVICE_GROUP_MANIPULATION: >
{{
(
SYS_SERVICE_GROUP_BACKUPS +
SYS_SERVICE_GROUP_CLEANUP +
SYS_SERVICE_GROUP_REPAIR +
SYS_SERVICE_GROUP_OPTIMIZATION +
SYS_SERVICE_GROUP_MAINTANANCE
) | sort
}}

View File

@@ -1,29 +0,0 @@
## Schedule for Health Checks
on_calendar_health_btrfs: "*-*-* 00:00:00" # Check once per day the btrfs for errors
on_calendar_health_journalctl: "*-*-* 00:00:00" # Check once per day the journalctl for errors
on_calendar_health_disc_space: "*-*-* 06,12,18,00:00:00" # Check four times per day if there is sufficient disc space
on_calendar_health_docker_container: "*-*-* {{ hours_server_awake }}:00:00" # Check once per hour if the docker containers are healthy
on_calendar_health_docker_volumes: "*-*-* {{ hours_server_awake }}:15:00" # Check once per hour if the docker volumes are healthy
on_calendar_health_csp_crawler: "*-*-* {{ hours_server_awake }}:30:00" # Check once per hour if all CSP are fullfilled available
on_calendar_health_nginx: "*-*-* {{ hours_server_awake }}:45:00" # Check once per hour if all webservices are available
on_calendar_health_msmtp: "*-*-* 00:00:00" # Check once per day SMTP Server
## Schedule for Cleanup Tasks
on_calendar_cleanup_backups: "*-*-* 00,06,12,18:30:00" # Cleanup backups every 6 hours, MUST be called before disc space cleanup
on_calendar_cleanup_disc_space: "*-*-* 07,13,19,01:30:00" # Cleanup disc space every 6 hours
on_calendar_cleanup_certs: "*-*-* 12,00:45:00" # Deletes and revokes unused certs
## Schedule for Backup Tasks
on_calendar_backup_docker_to_local: "*-*-* 03:30:00"
on_calendar_backup_remote_to_local: "*-*-* 21:30:00"
## Schedule for Maintenance Tasks
on_calendar_heal_docker: "*-*-* {{ hours_server_awake }}:30:00" # Heal unhealthy docker instances once per hour
on_calendar_renew_lets_encrypt_certificates: "*-*-* 12,00:30:00" # Renew Mailu certificates twice per day
on_calendar_deploy_certificates: "*-*-* 13,01:30:00" # Deploy letsencrypt certificates twice per day to docker containers
on_calendar_msi_keyboard_color: "*-*-* *:*:00" # Change the keyboard color every minute
on_calendar_cleanup_failed_docker: "*-*-* 12:00:00" # Clean up failed docker backups every noon
on_calendar_btrfs_auto_balancer: "Sat *-*-01..07 00:00:00" # Execute btrfs auto balancer every first Saturday of a month
on_calendar_restart_docker: "Sun *-*-* 08:00:00" # Restart docker instances every Sunday at 8:00 AM
on_calendar_nextcloud: "22" # Do nextcloud maintanace between 22:00 and 02:00

View File

@@ -0,0 +1,52 @@
# Service Timers
## Meta
SYS_TIMER_ALL_ENABLED: "{{ MODE_DEBUG }}" # Runtime Variables for Process Control - Activates all timers, independend if the handlers had been triggered
## 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
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_CLEANUP_SERVICES: "15min"
SYS_TIMEOUT_DOCKER_UPDATE: "20min"
SYS_TIMEOUT_STORAGE_OPTIMIZER: "{{ SYS_TIMEOUT_DOCKER_UPDATE }}"
SYS_TIMEOUT_BACKUP_SERVICES: "60min"
## On Calendar
### Schedule for health checks
SYS_SCHEDULE_HEALTH_BTRFS: "*-*-* 00:00:00" # Check once per day the btrfs for errors
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_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
### 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
### Schedule for backup tasks
SYS_SCHEDULE_BACKUP_DOCKER_TO_LOCAL: "*-*-* 03:30:00"
SYS_SCHEDULE_BACKUP_REMOTE_TO_LOCAL: "*-*-* 21:30:00"
### 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
### Animation
SYS_SCHEDULE_ANIMATION_KEYBOARD_COLOR: "*-*-* *:*:00" # Change the keyboard color every minute

View File

@@ -10,7 +10,7 @@ defaults_networks:
# /28 Networks, 14 Usable Ip Addresses
web-app-akaunting:
subnet: 192.168.101.0/28
web-app-attendize:
web-app-confluence:
subnet: 192.168.101.16/28
web-app-baserow:
subnet: 192.168.101.32/28
@@ -34,8 +34,8 @@ defaults_networks:
subnet: 192.168.101.176/28
web-app-listmonk:
subnet: 192.168.101.192/28
# Free:
# subnet: 192.168.101.208/28
web-app-jira:
subnet: 192.168.101.208/28
web-app-matomo:
subnet: 192.168.101.224/28
web-app-mastodon:
@@ -44,11 +44,11 @@ defaults_networks:
subnet: 192.168.102.0/28
web-app-mailu:
# Use one of the last container ips for dns resolving so that it isn't used
dns: 192.168.102.29
subnet: 192.168.102.16/28
dns_resolver: 192.168.102.29
subnet: 192.168.102.16/28
web-app-moodle:
subnet: 192.168.102.32/28
web-app-mybb:
web-app-bookwyrm:
subnet: 192.168.102.48/28
web-app-nextcloud:
subnet: 192.168.102.64/28
@@ -84,11 +84,11 @@ defaults_networks:
subnet: 192.168.103.64/28
web-app-syncope:
subnet: 192.168.103.80/28
web-app-collabora:
web-svc-collabora:
subnet: 192.168.103.96/28
web-svc-simpleicons:
subnet: 192.168.103.112/28
web-app-libretranslate:
web-svc-libretranslate:
subnet: 192.168.103.128/28
web-app-pretix:
subnet: 192.168.103.144/28
@@ -96,6 +96,12 @@ defaults_networks:
subnet: 192.168.103.160/28
web-svc-logout:
subnet: 192.168.103.176/28
web-app-chess:
subnet: 192.168.103.192/28
web-app-magento:
subnet: 192.168.103.208/28
web-app-bridgy-fed:
subnet: 192.168.103.224/28
# /24 Networks / 254 Usable Clients
web-app-bigbluebutton:

View File

@@ -2,12 +2,12 @@ ports:
# Ports which are exposed to localhost
localhost:
database:
svc-db-postgres: 5432
svc-db-mariadb: 3306
svc-db-postgres: 5432
svc-db-mariadb: 3306
# https://developer.mozilla.org/de/docs/Web/API/WebSockets_API
websocket:
web-app-mastodon: 4001
web-app-espocrm: 4002
web-app-mastodon: 4001
web-app-espocrm: 4002
oauth2_proxy:
web-app-phpmyadmin: 4181
web-app-lam: 4182
@@ -26,7 +26,7 @@ ports:
web-app-gitea: 8002
web-app-wordpress: 8003
web-app-mediawiki: 8004
web-app-mybb: 8005
web-app-confluence: 8005
web-app-yourls: 8006
web-app-mailu: 8007
web-app-elk: 8008
@@ -36,7 +36,7 @@ ports:
web-app-funkwhale: 8012
web-app-roulette-wheel: 8013
web-app-joomla: 8014
web-app-attendize: 8015
web-app-jira: 8015
web-app-pgadmin: 8016
web-app-baserow: 8017
web-app-matomo: 8018
@@ -50,7 +50,7 @@ ports:
web-app-moodle: 8026
web-app-taiga: 8027
web-app-friendica: 8028
web-app-port-ui: 8029
web-app-desktop: 8029
web-app-bluesky_api: 8030
web-app-bluesky_web: 8031
web-app-keycloak: 8032
@@ -63,13 +63,18 @@ ports:
web-app-navigator: 8039
web-app-espocrm: 8040
web-app-syncope: 8041
web-app-collabora: 8042
web-svc-collabora: 8042
web-app-mobilizon: 8043
web-svc-simpleicons: 8044
web-app-libretranslate: 8045
web-svc-libretranslate: 8045
web-app-pretix: 8046
web-app-mig: 8047
web-svc-logout: 8048
web-app-bookwyrm: 8049
web-app-chess: 8050
web-app-bluesky_view: 8051
web-app-magento: 8052
web-app-bridgy-fed: 8053
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
@@ -80,9 +85,10 @@ ports:
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
web-app-nextcloud: 3479
# 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
web-app-nextcloud: 5350 # Not used yet
federation:
web-app-matrix_synapse: 8448

View File

@@ -7,38 +7,43 @@
#############################################
# @see https://en.wikipedia.org/wiki/OpenID_Connect
## Helper Variables:
_oidc_client_realm: "{{ oidc.client.realm if oidc.client is defined and oidc.client.realm is defined else primary_domain }}"
_oidc_url: "{{
(oidc.url
if (oidc is defined and oidc.url is defined)
else WEB_PROTOCOL ~ '://' ~ (domains | get_domain('web-app-keycloak'))
)
}}"
_oidc_client_issuer_url: "{{ _oidc_url }}/realms/{{_oidc_client_realm}}"
_oidc_client_id: "{{ oidc.client.id if oidc.client is defined and oidc.client.id is defined else primary_domain }}"
# Helper Variables:
_oidc_client_realm: "{{ OIDC.CLIENT.REALM if OIDC.CLIENT is defined and OIDC.CLIENT.REALM is defined else SOFTWARE_NAME | lower }}"
_oidc_url: "{{
( OIDC.URL
if (OIDC is defined and OIDC.URL is defined)
else domains | get_url('web-app-keycloak', WEB_PROTOCOL)
).rstrip('/')
}}"
_oidc_client_issuer_url: "{{ _oidc_url ~ '/realms/' ~ _oidc_client_realm }}"
_oidc_client_id: "{{ OIDC.CLIENT.ID if OIDC.CLIENT is defined and OIDC.CLIENT.ID is defined else SOFTWARE_NAME | lower }}"
_oidc_account_url: "{{ _oidc_client_issuer_url ~ '/account' }}"
_oidc_protocol_oidc: "{{ _oidc_client_issuer_url ~ '/protocol/openid-connect' }}"
# Definition
defaults_oidc:
url: "{{ _oidc_url }}"
client:
id: "{{ _oidc_client_id }}" # Client identifier, typically matching your primary domain
# secret: # Client secret for authenticating with the OIDC provider (set in the inventory file). Recommend greater then 32 characters
realm: "{{_oidc_client_realm}}" # The realm to which the client belongs in the OIDC provider
issuer_url: "{{_oidc_client_issuer_url}}" # Base URL of the OIDC provider (issuer)
discovery_document: "{{_oidc_client_issuer_url}}/.well-known/openid-configuration" # URL for fetching the provider's configuration details
authorize_url: "{{_oidc_client_issuer_url}}/protocol/openid-connect/auth" # Endpoint to start the authorization process
token_url: "{{_oidc_client_issuer_url}}/protocol/openid-connect/token" # Endpoint to exchange authorization codes for tokens (note: 'token_url' may be a typo for 'token_url')
user_info_url: "{{_oidc_client_issuer_url}}/protocol/openid-connect/userinfo" # Endpoint to retrieve user information
logout_url: "{{_oidc_client_issuer_url}}/protocol/openid-connect/logout" # Endpoint to log out the user
change_credentials: "{{_oidc_client_issuer_url}}account/account-security/signing-in" # URL for managing or changing user credentials
certs: "{{_oidc_client_issuer_url}}/protocol/openid-connect/certs" # JSON Web Key Set (JWKS)
reset_credentials: "{{_oidc_client_issuer_url}}/login-actions/reset-credentials?client_id={{ _oidc_client_id }}" # Password reset url
button_text: "SSO Login ({{primary_domain | upper}})" # Default button text
attributes:
URL: "{{ _oidc_url }}"
CLIENT:
ID: "{{ _oidc_client_id }}" # Client identifier, typically matching your primary domain
# SECRET: # Client secret for authenticating with the OIDC provider (set in the inventory file). Recommend greater then 32 characters
REALM: "{{ _oidc_client_realm }}" # The realm to which the client belongs in the OIDC provider
ISSUER_URL: "{{ _oidc_client_issuer_url }}" # Base URL of the OIDC provider (issuer)
DISCOVERY_DOCUMENT: "{{ _oidc_client_issuer_url ~ '/.well-known/openid-configuration' }}" # URL for fetching the provider's configuration details
AUTHORIZE_URL: "{{ _oidc_protocol_oidc ~ '/auth' }}" # Endpoint to start the authorization process
TOKEN_URL: "{{ _oidc_protocol_oidc ~ '/token' }}" # Endpoint to exchange authorization codes for tokens (note: 'token_url' may be a typo for 'token_url')
USER_INFO_URL: "{{ _oidc_protocol_oidc ~ '/userinfo' }}" # Endpoint to retrieve user information
LOGOUT_URL: "{{ _oidc_protocol_oidc ~ '/logout' }}" # Endpoint to log out the user
CERTS: "{{ _oidc_protocol_oidc ~ '/certs' }}" # JSON Web Key Set (JWKS)
ACCOUNT:
URL: "{{ _oidc_account_url }}" # Entry point for the user settings console
PROFILE_URL: "{{ _oidc_account_url ~ '/#/personal-info' }}" # Section for managing personal information
SECURITY_URL: "{{ _oidc_account_url ~ '/#/security/signingin' }}" # Section for managing login and security settings
CHANGE_CREDENTIALS: "{{ _oidc_account_url ~ '/account-security/signing-in' }}" # URL for managing or changing user credentials
RESET_CREDENTIALS: "{{ _oidc_client_issuer_url ~ '/login-actions/reset-credentials?client_id=' ~ _oidc_client_id }}" # Password reset url
BUTTON_TEXT: "SSO Login ({{ PRIMARY_DOMAIN | upper }})" # Default button text
ATTRIBUTES:
# Attribut to identify the user
username: "preferred_username"
given_name: "givenName"
family_name: "surname"
email: "email"
claims:
groups: "groups"
USERNAME: "preferred_username"
GIVEN_NAME: "givenName"
FAMILY_NAME: "surname"
EMAIL: "email"

View File

@@ -5,31 +5,31 @@
# Helper Variables:
# Keep in mind to mapp this variables if there is ever the possibility for the user to define them in the inventory
_ldap_dn_base: "dc={{primary_domain_sld}},dc={{primary_domain_tld}}"
_ldap_docker_network_enabled: "{{ applications | get_app_conf('svc-db-openldap', 'network.docker') }}"
_ldap_protocol: "{{ 'ldap' if _ldap_docker_network_enabled else 'ldaps' }}"
_ldap_server_port: "{{ ports.localhost[_ldap_protocol]['svc-db-openldap'] }}"
_ldap_name: "{{ applications | get_app_conf('svc-db-openldap', 'docker.services.openldap.name') }}"
_ldap_domain: "{{ primary_domain }}" # LDAP is jsut listening to a port not to a dedicated domain, so primary domain should be sufficient
_ldap_user_id: "uid"
_ldap_filters_users_all: "(|(objectclass=inetOrgPerson))"
LDAP_DN_BASE: "{{ PRIMARY_DOMAIN.split('.') | map('regex_replace', '^(.*)$', 'dc=\\1') | join(',') }}"
_ldap_docker_network_enabled: "{{ applications | get_app_conf('svc-db-openldap', 'network.docker') }}"
_ldap_protocol: "{{ 'ldap' if _ldap_docker_network_enabled else 'ldaps' }}"
_ldap_server_port: "{{ ports.localhost[_ldap_protocol]['svc-db-openldap'] }}"
_ldap_name: "{{ applications | get_app_conf('svc-db-openldap', 'docker.services.openldap.name') }}"
_ldap_domain: "{{ PRIMARY_DOMAIN }}" # LDAP is jsut listening to a port not to a dedicated domain, so primary domain should be sufficient
_ldap_user_id: "uid"
_ldap_filters_users_all: "(|(objectclass=inetOrgPerson))"
ldap:
LDAP:
# Distinguished Names (DN)
dn:
DN:
# -------------------------------------------------------------------------
# Base DN / Suffix
# This is the top-level naming context for your directory, used as the
# default search base for most operations (e.g. adding users, groups).
# Example: “dc=example,dc=com”
root: "{{_ldap_dn_base}}"
administrator:
ROOT: "{{ LDAP_DN_BASE }}"
ADMINISTRATOR:
# -------------------------------------------------------------------------
# Data-Tree Administrator Bind DN
# The DN used to authenticate for regular directory operations under
# the data tree (adding users, modifying attributes, creating OUs, etc.).
# Typically: “cn=admin,dc=example,dc=com”
data: "cn={{ applications['svc-db-openldap'].users.administrator.username }},{{ _ldap_dn_base }}"
DATA: "cn={{ applications['svc-db-openldap'].users.administrator.username }},{{ LDAP_DN_BASE }}"
# -------------------------------------------------------------------------
# Config-Tree Administrator Bind DN
@@ -37,9 +37,9 @@ ldap:
# need to load or modify schema, overlays, modules, or other server-
# level settings.
# Typically: “cn=admin,cn=config”
configuration: "cn={{ applications['svc-db-openldap'].users.administrator.username }},cn=config"
CONFIGURATION: "cn={{ applications['svc-db-openldap'].users.administrator.username }},cn=config"
ou:
OU:
# -------------------------------------------------------------------------
# Organizational Units (OUs)
# Pre-created containers in the directory tree to logically separate entries:
@@ -47,9 +47,9 @@ ldap:
# groups: Contains organizational or business groups (e.g., departments, teams).
# roles: Contains application-specific RBAC roles
# (e.g., "cn=app1-user", "cn=yourls-admin").
users: "ou=users,{{ _ldap_dn_base }}"
groups: "ou=groups,{{ _ldap_dn_base }}"
roles: "ou=roles,{{ _ldap_dn_base }}"
USERS: "ou=users,{{ LDAP_DN_BASE }}"
GROUPS: "ou=groups,{{ LDAP_DN_BASE }}"
ROLES: "ou=roles,{{ LDAP_DN_BASE }}"
# -------------------------------------------------------------------------
# Additional Notes
@@ -59,17 +59,17 @@ ldap:
# for ordinary user/group operations, and vice versa.
# Password to access dn.bind
bind_credential: "{{ applications | get_app_conf('svc-db-openldap', 'credentials.administrator_database_password') }}"
server:
domain: "{{ _ldap_name if _ldap_docker_network_enabled else _ldap_domain }}" # Mapping for public or locale access
port: "{{ _ldap_server_port }}"
uri: "{{ _ldap_protocol }}://{{ _ldap_name if _ldap_docker_network_enabled else _ldap_domain }}:{{ _ldap_server_port }}"
security: "" #TLS, SSL - Leave empty for none
network:
local: "{{ _ldap_docker_network_enabled }}" # Uses the application configuration to define if local network should be available or not
user:
objects:
structural:
BIND_CREDENTIAL: "{{ applications | get_app_conf('svc-db-openldap', 'credentials.administrator_database_password') }}"
SERVER:
DOMAIN: "{{ _ldap_name if _ldap_docker_network_enabled else _ldap_domain }}" # Mapping for public or locale access
PORT: "{{ _ldap_server_port }}"
URI: "{{ _ldap_protocol }}://{{ _ldap_name if _ldap_docker_network_enabled else _ldap_domain }}:{{ _ldap_server_port }}"
SECURITY: "" #TLS, SSL - Leave empty for none
NETWORK:
LOCAL: "{{ _ldap_docker_network_enabled }}" # Uses the application configuration to define if local network should be available or not
USER:
OBJECTS:
STRUCTURAL:
- person # Structural Classes define the core identity of an entry:
# • Specify mandatory attributes (e.g. sn, cn)
# • Each entry must have exactly one structural class
@@ -77,26 +77,26 @@ ldap:
# (e.g. mail, employeeNumber)
- posixAccount # Provides UNIX account attributes (uidNumber, gidNumber,
# homeDirectory)
auxiliary:
nextloud_user: "nextcloudUser" # Auxiliary Classes attach optional attributes without
AUXILIARY:
NEXTCLOUD_USER: "nextcloudUser" # Auxiliary Classes attach optional attributes without
# changing the entrys structural role. Here they add
# nextcloudQuota and nextcloudEnabled for Nextcloud.
ssh_public_key: "ldapPublicKey" # Allows storing SSH public keys for services like Gitea.
attributes:
SSH_PUBLIC_KEY: "ldapPublicKey" # Allows storing SSH public keys for services like Gitea.
ATTRIBUTES:
# Attribut to identify the user
id: "{{ _ldap_user_id }}"
mail: "mail"
fullname: "cn"
firstname: "givenname"
surname: "sn"
ssh_public_key: "sshPublicKey"
nextcloud_quota: "nextcloudQuota"
filters:
users:
login: "(&{{ _ldap_filters_users_all }}({{_ldap_user_id}}=%{{_ldap_user_id}}))"
all: "{{ _ldap_filters_users_all }}"
rbac:
flavors:
ID: "{{ _ldap_user_id }}"
MAIL: "mail"
FULLNAME: "cn"
FIRSTNAME: "givenname"
SURNAME: "sn"
SSH_PUBLIC_KEY: "sshPublicKey"
NEXTCLOUD_QUOTA: "nextcloudQuota"
FILTERS:
USERS:
LOGIN: "(&{{ _ldap_filters_users_all }}({{_ldap_user_id}}=%{{_ldap_user_id}}))"
ALL: "{{ _ldap_filters_users_all }}"
RBAC:
FLAVORS:
# Valid values posixGroup, groupOfNames
- groupOfNames
# - posixGroup

View File

@@ -2,8 +2,8 @@
defaults_service_provider:
type: "legal" # Accepted Values: natural, legal
company:
titel: "Infinito.Nexus by Kevin Veen-Birkenbach"
slogan: "Infinito.Nexus — Empowering a Sovereign Digital Future."
titel: "{{ SOFTWARE_NAME }} by Kevin Veen-Birkenbach"
slogan: "{{ SOFTWARE_NAME }} — Empowering a Sovereign Digital Future."
address:
street: "Binary Avenue 01"
city: "Cybertown"
@@ -11,7 +11,7 @@ defaults_service_provider:
country: "Nexusland"
logo: "{{ applications['web-svc-asset'].url ~ '/img/logo.png' }}"
platform:
titel: "Infinito.Nexus"
titel: "{{ SOFTWARE_NAME }}"
subtitel: "One login. Infinite applications."
logo: "{{ applications['web-svc-asset'].url ~ '/img/logo.png' }}"
favicon: "{{ applications['web-svc-asset'].url ~ '/img/favicon.ico' }}"
@@ -19,9 +19,9 @@ defaults_service_provider:
web-app-bluesky: >-
{{ ('@' ~ users.contact.username ~ '.' ~ domains['web-app-bluesky'].api)
if 'web-app-bluesky' in group_names else '' }}
email: "{{ users.contact.username ~ '@' ~ primary_domain if 'web-app-mailu' in group_names else '' }}"
email: "{{ users.contact.username ~ '@' ~ PRIMARY_DOMAIN if 'web-app-mailu' in group_names else '' }}"
mastodon: "{{ '@' ~ users.contact.username ~ '@' ~ domains | get_domain('web-app-mastodon') if 'web-app-mastodon' in group_names else '' }}"
matrix: "{{ '@' ~ users.contact.username ~ ':' ~ domains['web-app-matrix'].synapse if 'web-app-matrix' in group_names else '' }}"
matrix: "{{ '@' ~ users.contact.username ~ ':' ~ applications | get_app_conf('web-app-matrix', 'server_name') if 'web-app-matrix' in group_names else '' }}"
peertube: "{{ '@' ~ users.contact.username ~ '@' ~ domains | get_domain('web-app-peertube') if 'web-app-peertube' in group_names else '' }}"
pixelfed: "{{ '@' ~ users.contact.username ~ '@' ~ domains | get_domain('web-app-pixelfed') if 'web-app-pixelfed' in group_names else '' }}"
phone: "+0 000 000 404"
@@ -29,5 +29,5 @@ defaults_service_provider:
legal:
editorial_responsible: "Johannes Gutenberg"
source_code: "https://github.com/kevinveenbirkenbach/infinito-nexus"
imprint: "{{WEB_PROTOCOL}}://{{ domains | get_domain('web-svc-html') }}/imprint.html"
source_code: "https://s.{{ SOFTWARE_NAME | lower }}/code"
imprint: "{{ domains | get_url('web-svc-html', WEB_PROTOCOL) }}/imprint.html"

View File

@@ -1,6 +1,6 @@
backups_folder_path: "/Backups/" # Path to the backups folder
BACKUPS_FOLDER_PATH: "/Backups/" # Path to the backups folder
# Storage Space-Related Configurations
size_percent_maximum_backup: 75 # Maximum storage space in percent for backups
size_percent_cleanup_disc_space: 85 # Threshold for triggering cleanup actions
size_percent_disc_space_warning: 90 # Warning threshold in percent for free disk space
SIZE_PERCENT_MAXIMUM_BACKUP: 75 # Maximum storage space in percent for backups
SIZE_PERCENT_CLEANUP_DISC_SPACE: 85 # Threshold for triggering cleanup actions
SIZE_PERCENT_DISC_SPACE_WARNING: 90 # Warning threshold in percent for free disk space

View File

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

View File

@@ -250,7 +250,7 @@ if __name__ == "__main__":
"For commercial use, a license agreement with Kevin Veen-Birkenbach is required. \n",
Style.DIM
))
print(color_text("License: https://s.veen.world/cncl", Style.DIM))
print(color_text("License: https://s.infinito.nexus/license", Style.DIM))
print()
print(color_text("🎉🌈 Happy IT Infrastructuring! 🚀🔧✨", Fore.MAGENTA + Style.BRIGHT))
print()

View File

@@ -0,0 +1,296 @@
import os
import fnmatch
import re
from typing import Dict, Set, Iterable, Tuple, Optional
import yaml
class RoleDependencyResolver:
_RE_PURE_JINJA = re.compile(r"\s*\{\{\s*[^}]+\s*\}\}\s*$")
def __init__(self, roles_dir: str):
self.roles_dir = roles_dir
# -------------------------- public API --------------------------
def resolve_transitively(
self,
start_roles: Iterable[str],
*,
resolve_include_role: bool = True,
resolve_import_role: bool = True,
resolve_dependencies: bool = True,
resolve_run_after: bool = False,
max_depth: Optional[int] = None,
) -> Set[str]:
to_visit = list(dict.fromkeys(start_roles))
visited: Set[str] = set()
depth: Dict[str, int] = {}
for r in to_visit:
depth[r] = 0
while to_visit:
role = to_visit.pop()
cur_d = depth.get(role, 0)
if role in visited:
continue
visited.add(role)
if max_depth is not None and cur_d >= max_depth:
continue
for dep in self.get_role_dependencies(
role,
resolve_include_role=resolve_include_role,
resolve_import_role=resolve_import_role,
resolve_dependencies=resolve_dependencies,
resolve_run_after=resolve_run_after,
):
if dep not in visited:
to_visit.append(dep)
depth[dep] = cur_d + 1
return visited
def get_role_dependencies(
self,
role_name: str,
*,
resolve_include_role: bool = True,
resolve_import_role: bool = True,
resolve_dependencies: bool = True,
resolve_run_after: bool = False,
) -> Set[str]:
role_path = os.path.join(self.roles_dir, role_name)
if not os.path.isdir(role_path):
return set()
deps: Set[str] = set()
if resolve_include_role or resolve_import_role:
includes, imports = self._scan_tasks(role_path)
if resolve_include_role:
deps |= includes
if resolve_import_role:
deps |= imports
if resolve_dependencies:
deps |= self._extract_meta_dependencies(role_path)
if resolve_run_after:
deps |= self._extract_meta_run_after(role_path)
return deps
# -------------------------- scanning helpers --------------------------
def _scan_tasks(self, role_path: str) -> Tuple[Set[str], Set[str]]:
tasks_dir = os.path.join(role_path, "tasks")
include_roles: Set[str] = set()
import_roles: Set[str] = set()
if not os.path.isdir(tasks_dir):
return include_roles, import_roles
all_roles = self._list_role_dirs(self.roles_dir)
candidates = []
for root, _, files in os.walk(tasks_dir):
for f in files:
if f.endswith(".yml") or f.endswith(".yaml"):
candidates.append(os.path.join(root, f))
for file_path in candidates:
try:
with open(file_path, "r", encoding="utf-8") as f:
docs = list(yaml.safe_load_all(f))
except Exception:
inc, imp = self._tolerant_scan_file(file_path, all_roles)
include_roles |= inc
import_roles |= imp
continue
for doc in docs or []:
if not isinstance(doc, list):
continue
for task in doc:
if not isinstance(task, dict):
continue
if "include_role" in task:
include_roles |= self._extract_from_task(task, "include_role", all_roles)
if "import_role" in task:
import_roles |= self._extract_from_task(task, "import_role", all_roles)
return include_roles, import_roles
def _extract_from_task(self, task: dict, key: str, all_roles: Iterable[str]) -> Set[str]:
roles: Set[str] = set()
spec = task.get(key)
if not isinstance(spec, dict):
return roles
name = spec.get("name")
loop_val = self._collect_loop_values(task)
if loop_val is not None:
for item in self._iter_flat(loop_val):
cand = self._role_from_loop_item(item, name_template=name)
if cand:
roles.add(cand)
if isinstance(name, str) and name.strip() and not self._is_pure_jinja_var(name):
pattern = self._jinja_to_glob(name) if ("{{" in name and "}}" in name) else name
self._match_glob_into(pattern, all_roles, roles)
return roles
if isinstance(name, str) and name.strip():
if "{{" in name and "}}" in name:
if self._is_pure_jinja_var(name):
return roles
pattern = self._jinja_to_glob(name)
self._match_glob_into(pattern, all_roles, roles)
else:
roles.add(name.strip())
return roles
def _collect_loop_values(self, task: dict):
for k in ("loop", "with_items", "with_list", "with_flattened"):
if k in task:
return task[k]
return None
def _iter_flat(self, value):
if isinstance(value, list):
for v in value:
if isinstance(v, list):
for x in v:
yield x
else:
yield v
def _role_from_loop_item(self, item, name_template=None) -> Optional[str]:
tmpl = (name_template or "").strip() if isinstance(name_template, str) else ""
if isinstance(item, str):
if tmpl in ("{{ item }}", "{{item}}") or not tmpl or "item" in tmpl:
return item.strip()
return None
if isinstance(item, dict):
for k in ("role", "name"):
v = item.get(k)
if isinstance(v, str) and v.strip():
if tmpl in (f"{{{{ item.{k} }}}}", f"{{{{item.{k}}}}}") or not tmpl or "item" in tmpl:
return v.strip()
return None
def _match_glob_into(self, pattern: str, all_roles: Iterable[str], out: Set[str]):
if "*" in pattern or "?" in pattern or "[" in pattern:
for r in all_roles:
if fnmatch.fnmatch(r, pattern):
out.add(r)
else:
out.add(pattern)
def test_jinja_mixed_name_glob_matching(self):
"""
include_role:
name: "prefix-{{ item }}-suffix"
loop: [x, y]
Existing roles: prefix-x-suffix, prefix-y-suffix, prefix-z-suffix
Expectation:
- NO raw loop items ('x', 'y') end up as roles
- Glob matching resolves to all three concrete roles
"""
make_role(self.roles_dir, "A")
for rn in ["prefix-x-suffix", "prefix-y-suffix", "prefix-z-suffix"]:
make_role(self.roles_dir, rn)
write(
os.path.join(self.roles_dir, "A", "tasks", "main.yml"),
"""
- name: jinja-mixed glob
include_role:
name: "prefix-{{ item }}-suffix"
loop:
- x
- y
"""
)
r = RoleDependencyResolver(self.roles_dir)
deps = r.get_role_dependencies("A")
# ensure no raw loop items leak into the results
self.assertNotIn("x", deps)
self.assertNotIn("y", deps)
# only the resolved role names should be present
self.assertEqual(
deps,
{"prefix-x-suffix", "prefix-y-suffix", "prefix-z-suffix"},
)
# -------------------------- meta helpers --------------------------
def _extract_meta_dependencies(self, role_path: str) -> Set[str]:
deps: Set[str] = set()
meta_main = os.path.join(role_path, "meta", "main.yml")
if not os.path.isfile(meta_main):
return deps
try:
with open(meta_main, "r", encoding="utf-8") as f:
meta = yaml.safe_load(f) or {}
raw_deps = meta.get("dependencies", [])
if isinstance(raw_deps, list):
for item in raw_deps:
if isinstance(item, str):
deps.add(item.strip())
elif isinstance(item, dict):
r = item.get("role")
if isinstance(r, str) and r.strip():
deps.add(r.strip())
except Exception:
pass
return deps
def _extract_meta_run_after(self, role_path: str) -> Set[str]:
deps: Set[str] = set()
meta_main = os.path.join(role_path, "meta", "main.yml")
if not os.path.isfile(meta_main):
return deps
try:
with open(meta_main, "r", encoding="utf-8") as f:
meta = yaml.safe_load(f) or {}
galaxy_info = meta.get("galaxy_info", {})
run_after = galaxy_info.get("run_after", [])
if isinstance(run_after, list):
for item in run_after:
if isinstance(item, str) and item.strip():
deps.add(item.strip())
except Exception:
pass
return deps
# -------------------------- small utils --------------------------
def _list_role_dirs(self, roles_dir: str) -> list[str]:
return [
d for d in os.listdir(roles_dir)
if os.path.isdir(os.path.join(roles_dir, d))
]
@classmethod
def _is_pure_jinja_var(cls, s: str) -> bool:
return bool(cls._RE_PURE_JINJA.fullmatch(s or ""))
@staticmethod
def _jinja_to_glob(s: str) -> str:
pattern = re.sub(r"\{\{[^}]+\}\}", "*", s or "")
pattern = re.sub(r"\*{2,}", "*", pattern)
return pattern.strip()

View File

@@ -1,4 +1,4 @@
- name: Execute Infinito.Nexus Play
- name: Execute {{ SOFTWARE_NAME }} Play
hosts: all
tasks:
- name: "Load 'constructor' tasks"

View File

@@ -1,5 +1,6 @@
collections:
- name: kewlfft.aur
- name: community.general
- name: hetzner.hcloud
yay:
- python-simpleaudio

View File

@@ -1,4 +1,9 @@
roles:
docker:
title: "Docker Toolkit"
description: "Generic Docker helpers and utilities (compose wrappers, container tooling)."
icon: "fas fa-docker"
invokable: false
dev:
title: "Software Development Utilties"
invokable: false
@@ -6,41 +11,76 @@ roles:
title: "System"
description: "System near components. Will be automaticly called if necessary from other roles."
invokable: false
alm:
title: "Alerting"
description: "Notification handlers for system events"
icon: "fas fa-bell"
ctl:
title: "Control"
description: "Control layer for system lifecycle management—handling cleanup, monitoring, backups, alerting, maintenance, and repair tasks."
icon: "fas fa-cogs"
invokable: false
cln:
title: "Cleanup"
description: "Roles for cleaning up various system resources—old backups, unused certificates, temporary files, Docker volumes, disk caches, deprecated domains, and more."
icon: "fas fa-trash-alt"
cln:
title: "Cleanup"
description: "Roles for cleaning up various system resources—old backups, unused certificates, temporary files, Docker volumes, disk caches, deprecated domains, and more."
icon: "fas fa-trash-alt"
invokable: false
hlth:
title: "Monitoring"
description: "Roles for system monitoring and health checks—encompassing bot-style automated checks and core low-level monitors for logs, containers, disk usage, and more."
icon: "fas fa-chart-area"
invokable: false
bkp:
title: "Backup & Restore"
description: "Backup strategies & restore procedures"
icon: "fas fa-hdd"
invokable: false
alm:
title: "Alerting"
description: "Notification handlers for system events"
icon: "fas fa-bell"
invokable: false
mtn:
title: "Maintenance"
description: "Maintenance roles for certificates, system upkeep, and recurring operational tasks."
icon: "fas fa-tools"
invokable: false
rpr:
title: "Repair"
description: "Repair and recovery roles—handling hard/soft recovery of Docker, Btrfs balancers, and other low-level system fixes."
icon: "fas fa-wrench"
invokable: false
dns:
title: "DNS Automation"
description: "DNS providers, records, and rDNS management (Cloudflare, Hetzner, etc.)."
icon: "fas fa-network-wired"
invokable: false
hlth:
title: "Monitoring"
description: "Roles for system monitoring and health checks—encompassing bot-style automated checks and core low-level monitors for logs, containers, disk usage, and more."
icon: "fas fa-chart-area"
stk:
title: "Stack"
description: "Stack levels to setup the server"
icon: "fas fa-bars-staggered"
invokable: false
bkp:
title: "Backup & Restore"
description: "Backup strategies & restore procedures"
icon: "fas fa-hdd"
front:
title: "System Frontend Helpers"
description: "Frontend helpers for reverse-proxied apps (injection, shared assets, CDN plumbing)."
icon: "fas fa-wand-magic-sparkles"
invokable: false
inj:
title: "Injection"
description: "Composable HTML injection roles (CSS, JS, logout interceptor, analytics, desktop iframe) for Nginx/OpenResty via sub_filter/Lua with CDN-backed assets."
icon: "fas fa-filter"
invokable: false
update:
title: "Updates & Package Management"
description: "OS & package updates"
icon: "fas fa-sync"
invokable: true
pkgmgr:
title: "Package Manager Helpers"
description: "Helpers for package managers and unified install flows."
icon: "fas fa-box-open"
invokable: false
drv:
title: "Drivers"
description: "Roles for installing and configuring hardware drivers—covering printers, graphics, input devices, and other peripheral support."
icon: "fas fa-microchip"
invokable: true
# core:
# title: "Core & System"
# description: "Fundamental system configuration"
# icon: "fas fa-cogs"
# invokable: true
gen:
title: "Generic"
description: "Helper roles & installers (git, locales, timer, etc.)"
@@ -66,20 +106,10 @@ roles:
description: "Utility roles for server-side configuration and management—covering corporate identity provisioning, network helpers, and other service-oriented toolkits."
icon: "fas fa-cogs"
invokable: true
srv:
title: "Server"
description: "General server roles for provisioning and managing server infrastructure—covering web servers, proxy servers, network services, and other backend components."
icon: "fas fa-server"
invokable: false
web:
title: "Webserver"
description: "Web-server roles for installing and configuring Nginx (core, TLS, injection filters, composer modules)."
icon: "fas fa-server"
invokable: false
proxy:
title: "Proxy Server"
description: "Proxy-server roles for virtual-host orchestration and reverse-proxy setups."
icon: "fas fa-project-diagram"
dev:
title: "Developer Utilities"
description: "Developer-centric server utilities and admin toolkits."
icon: "fas fa-code"
invokable: false
web:
title: "Web Infrastructure"
@@ -99,11 +129,6 @@ roles:
title: "Webserver Optimation"
description: "Tools which help to optimize webservers"
invokable: true
net:
title: "Network"
description: "Network setup (DNS, Let's Encrypt HTTP, WireGuard, etc.)"
icon: "fas fa-globe"
invokable: true
svc:
title: "Services"
description: "Infrastructure services like databases"
@@ -123,7 +148,11 @@ roles:
description: "Reverseproxy roles for routing and loadbalancing traffic to backend services"
icon: "fas fa-project-diagram"
invokable: true
net:
title: "Network"
description: "Network setup (DNS, Let's Encrypt HTTP, WireGuard, etc.)"
icon: "fas fa-globe"
invokable: true
user:
title: "Users & Access"
description: "User accounts & access control"

View File

@@ -1,11 +0,0 @@
# Database Docker with Web Proxy
This role builds on `cmp-db-docker` by adding a reverse-proxy frontend for HTTP access to your database service.
## Features
- **Database Composition**
Leverages the `cmp-db-docker` role to stand up your containerized database (PostgreSQL, MariaDB, etc.) with backups and user management.
- **Reverse Proxy**
Includes the `srv-proxy-6-6-domain` role to configure a proxy (e.g. nginx) for routing HTTP(S) traffic to your database UI or management endpoint.

View File

@@ -1 +0,0 @@
DATABASE_VARS_FILE: "{{ playbook_dir }}/roles/cmp-rdbms/vars/database.yml"

View File

@@ -1,14 +0,0 @@
# run_once_cmp_docker_proxy: deactivated
# To load the proxy first is just implemented due to some issues with BBB
- name: "For '{{ application_id }}': include role srv-proxy-6-6-domain"
include_role:
name: srv-proxy-6-6-domain
vars:
domain: "{{ domains | get_domain(application_id) }}"
http_port: "{{ ports.localhost.http[application_id] }}"
- name: "For '{{ application_id }}': Load cmp-docker-oauth2"
include_role:
name: cmp-docker-oauth2

View File

@@ -1,5 +0,0 @@
MYSQL_DATABASE="{{database_name}}"
MYSQL_USER="{{database_username}}"
MYSQL_PASSWORD="{{database_password}}"
MYSQL_ROOT_PASSWORD="{{database_password}}"
MARIADB_AUTO_UPGRADE="1"

View File

@@ -1,4 +0,0 @@
POSTGRES_PASSWORD={{database_password}}
POSTGRES_USER={{database_username}}
POSTGRES_DB={{database_name}}
POSTGRES_INITDB_ARGS=--encoding=UTF8 --locale=C

View File

@@ -1 +0,0 @@
{% include 'roles/cmp-rdbms/templates/services/' + database_type + '.yml.j2' %}

View File

@@ -1,17 +0,0 @@
# Helper variables
_database_id: "svc-db-{{ database_type }}"
_database_central_name: "{{ applications | get_app_conf( _database_id, 'docker.services.' ~ database_type ~ '.name') }}"
_database_consumer_entity_name: "{{ database_application_id | get_entity_name }}"
_database_central_enabled: "{{ applications | get_app_conf(database_application_id, 'features.central_database', False) }}"
# Definition
database_name: "{{ _database_consumer_entity_name }}"
database_instance: "{{ _database_central_name if _database_central_enabled else database_name }}" # This could lead to bugs at dedicated database @todo cleanup
database_host: "{{ _database_central_name if _database_central_enabled else 'database' }}" # This could lead to bugs at dedicated database @todo cleanup
database_username: "{{ _database_consumer_entity_name }}"
database_password: "{{ applications | get_app_conf(database_application_id, 'credentials.database_password', true) }}"
database_port: "{{ ports.localhost.database[ _database_id ] }}"
database_env: "{{docker_compose.directories.env}}{{database_type}}.env"
database_url_jdbc: "jdbc:{{ database_type if database_type == 'mariadb' else 'postgresql' }}://{{ database_host }}:{{ database_port }}/{{ database_name }}"
database_url_full: "{{database_type}}://{{database_username}}:{{database_password}}@{{database_host}}:{{database_port}}/{{ database_name }}"
database_volume: "{{ _database_consumer_entity_name ~ '_' if not _database_central_enabled }}{{ database_host }}"

View File

@@ -2,8 +2,8 @@
galaxy_info:
author: "Kevin Veen-Birchenbach"
description: "Installs VLC, libaacs and libbluray for Blu-ray playback on Arch Linuxbased systems."
license: "Infinito.Nexus NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birchenbach
Consulting & Coaching Solutions
@@ -12,9 +12,9 @@ galaxy_info:
- vlc
- bluray
- media
repository: "https://github.com/kevinveenbirkenbach/infinito-nexus"
issue_tracker_url: "https://github.com/kevinveenbirkenbach/infinito-nexus/issues"
documentation: "https://github.com/kevinveenbirkenbach/infinito-nexus/tree/main/roles/desk-bluray-player"
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://s.infinito.nexus/code/tree/main/roles/desk-bluray-player"
min_ansible_version: "2.9"
platforms:
- name: Archlinux

View File

@@ -25,5 +25,5 @@ The purpose of this role is to automate the provisioning of a secure Chromium en
Developed and maintained by **Kevin Veen-Birkenbach**.
Learn more at [www.veen.world](https://www.veen.world)
Part of the [Infinito.Nexus Project](https://github.com/kevinveenbirkenbach/infinito-nexus)
License: [Infinito.Nexus NonCommercial License (CNCL)](https://s.veen.world/cncl)
Part of the [Infinito.Nexus Project](https://s.infinito.nexus/code)
License: [Infinito.Nexus NonCommercial License](https://s.infinito.nexus/license)

View File

@@ -2,8 +2,8 @@
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Automates the installation and configuration of the Chromium browser with enforced security extensions."
license: "Infinito.Nexus NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions
@@ -29,7 +29,7 @@ galaxy_info:
- enterprise-policy
- security
- automation
repository: https://github.com/kevinveenbirkenbach/infinito-nexus
issue_tracker_url: https://github.com/kevinveenbirkenbach/infinito-nexus/issues
repository: https://s.infinito.nexus/code
issue_tracker_url: https://s.infinito.nexus/issues
documentation: "https://docs.infinito.nexus/"
dependencies: []

View File

@@ -2,8 +2,8 @@
galaxy_info:
author: "Kevin Veen-Birchenbach"
description: "Installs CopyQ clipboard manager on Pacman-based systems and configures autostart for the current user."
license: "Infinito.Nexus NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birchenbach
Consulting & Coaching Solutions
@@ -16,9 +16,9 @@ galaxy_info:
- cli
logo:
class: fa fa-clipboard
repository: "https://github.com/kevinveenbirkenbach/infinito-nexus"
issue_tracker_url: "https://github.com/kevinveenbirkenbach/infinito-nexus/issues"
documentation: "https://github.com/kevinveenbirkenbach/infinito-nexus/tree/main/roles/desk-copyq"
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://s.infinito.nexus/code/tree/main/roles/desk-copyq"
min_ansible_version: "2.9"
platforms:
- name: Archlinux

View File

@@ -2,8 +2,8 @@
galaxy_info:
author: "Kevin Veen-Birchenbach"
description: "Installs Docker and Docker Compose, and adds a user to the Docker group for non-root usage on development machines."
license: "Infinito.Nexus NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birchenbach
Consulting & Coaching Solutions
@@ -11,9 +11,9 @@ galaxy_info:
galaxy_tags:
- docker
- development
repository: "https://github.com/kevinveenbirkenbach/infinito-nexus"
issue_tracker_url: "https://github.com/kevinveenbirkenbach/infinito-nexus/issues"
documentation: "https://github.com/kevinveenbirkenbach/infinito-nexus/tree/main/roles/desk-docker"
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://s.infinito.nexus/code/tree/main/roles/desk-docker"
min_ansible_version: "2.9"
platforms:
- name: Archlinux

View File

@@ -24,5 +24,5 @@ The role automates the provisioning of a secure Firefox environment, reducing ma
Developed and maintained by **Kevin Veen-Birkenbach**.
Learn more at [www.veen.world](https://www.veen.world)
Part of the [Infinito.Nexus Project](https://github.com/kevinveenbirkenbach/infinito-nexus)
License: [Infinito.Nexus NonCommercial License (CNCL)](https://s.veen.world/cncl)
Part of the [Infinito.Nexus Project](https://s.infinito.nexus/code)
License: [Infinito.Nexus NonCommercial License](https://s.infinito.nexus/license)

View File

@@ -2,8 +2,8 @@
galaxy_info:
author: "Kevin Veen-Birchenbach"
description: "Automates Firefox installation and enforces Enterprise Policies (auto-install extensions) on Arch Linux."
license: "Infinito.Nexus NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birchenbach
Consulting & Coaching Solutions
@@ -12,9 +12,9 @@ galaxy_info:
- firefox
- enterprise-policy
- browser
repository: "https://github.com/kevinveenbirkenbach/infinito-nexus"
issue_tracker_url: "https://github.com/kevinveenbirkenbach/infinito-nexus/issues"
documentation: "https://github.com/kevinveenbirkenbach/infinito-nexus/tree/main/roles/desk-firefox"
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://s.infinito.nexus/code/tree/main/roles/desk-firefox"
min_ansible_version: "2.9"
platforms:
- name: Archlinux

View File

@@ -21,4 +21,4 @@ Developed and maintained by **Kevin Veen-Birkenbach**.
Learn more at [www.veen.world](https://www.veen.world)
For Git configuration details, see [git-configurator on GitHub](https://github.com/kevinveenbirkenbach/git-configurator).
License: [Infinito.Nexus NonCommercial License (CNCL)](https://s.veen.world/cncl)
License: [Infinito.Nexus NonCommercial License](https://s.infinito.nexus/license)

View File

@@ -2,8 +2,8 @@
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Installs Git and configures it using a custom git-configurator for personal computers."
license: "Infinito.Nexus NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions

View File

@@ -20,4 +20,4 @@ The purpose of this role is to ensure uninterrupted workflow by keeping the desk
Developed and maintained by **Kevin Veen-Birkenbach**.
Learn more at [www.veen.world](https://www.veen.world)
License: [Infinito.Nexus NonCommercial License (CNCL)](https://s.veen.world/cncl)
License: [Infinito.Nexus NonCommercial License](https://s.infinito.nexus/license)

View File

@@ -1,8 +1,8 @@
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Installs caffeine-ng and configures it to autostart for preventing screen sleep on GNOME."
license: "Infinito.Nexus NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
min_ansible_version: "2.4"
platforms:
- name: Archlinux

View File

@@ -19,3 +19,5 @@
template:
src: caffeine.desktop.j2
dest: "{{auto_start_directory}}caffeine.desktop"
- include_tasks: utils/run_once.yml

View File

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

View File

@@ -22,4 +22,4 @@ The purpose of this role is to enhance and customize the GNOME desktop environme
Developed and maintained by **Kevin Veen-Birkenbach**.
Learn more at [www.veen.world](https://www.veen.world)
License: [Infinito.Nexus NonCommercial License (CNCL)](https://s.veen.world/cncl)
License: [Infinito.Nexus NonCommercial License](https://s.infinito.nexus/license)

View File

@@ -2,8 +2,8 @@
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Configures GNOME Shell extensions and installs the CLI GNOME Extension Manager for managing extensions."
license: "Infinito.Nexus NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions

View File

@@ -22,4 +22,4 @@ The purpose of this role is to ensure that GNOME Terminal is installed and prope
Developed and maintained by **Kevin Veen-Birkenbach**.
Learn more at [www.veen.world](https://www.veen.world)
License: [Infinito.Nexus NonCommercial License (CNCL)](https://s.veen.world/cncl)
License: [Infinito.Nexus NonCommercial License](https://s.infinito.nexus/license)

View File

@@ -2,8 +2,8 @@
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Installs GNOME Terminal on Arch Linux, providing a modern terminal emulator for the GNOME desktop environment."
license: "Infinito.Nexus NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions

View File

@@ -24,4 +24,4 @@ The purpose of this role is to provide a complete GNOME desktop experience by or
Developed and maintained by **Kevin Veen-Birkenbach**.
Learn more at [www.veen.world](https://www.veen.world)
License: [Infinito.Nexus NonCommercial License (CNCL)](https://s.veen.world/cncl)
License: [Infinito.Nexus NonCommercial License](https://s.infinito.nexus/license)

View File

@@ -2,8 +2,8 @@
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Aggregates essential GNOME desktop roles—including caffeine, extensions, and terminal—for a complete GNOME environment on Linux."
license: "Infinito.Nexus NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions

View File

@@ -2,8 +2,8 @@
galaxy_info:
author: "Kevin Veen-Birchenbach"
description: "Installs GnuCash finance management software on Pacman-based systems, ensuring the latest version is present."
license: "Infinito.Nexus NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birchenbach
Consulting & Coaching Solutions
@@ -12,9 +12,9 @@ galaxy_info:
- gnucash
- finance
- accounting
repository: "https://github.com/kevinveenbirkenbach/infinito-nexus"
issue_tracker_url: "https://github.com/kevinveenbirkenbach/infinito-nexus/issues"
documentation: "https://github.com/kevinveenbirkenbach/infinito-nexus/tree/main/roles/desk-gnucash"
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://s.infinito.nexus/code/tree/main/roles/desk-gnucash"
min_ansible_version: "2.9"
platforms:
- name: Archlinux

View File

@@ -2,8 +2,8 @@
galaxy_info:
author: "Kevin Veen-Birchenbach"
description: "Installs Jrnl CLI journal application on Pacman-based systems for command-line journaling."
license: "Infinito.Nexus NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birchenbach
Consulting & Coaching Solutions
@@ -12,9 +12,9 @@ galaxy_info:
- jrnl
- journal
- cli
repository: "https://github.com/kevinveenbirkenbach/infinito-nexus"
issue_tracker_url: "https://github.com/kevinveenbirkenbach/infinito-nexus/issues"
documentation: "https://github.com/kevinveenbirkenbach/infinito-nexus/tree/main/roles/desk-jrnl"
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://s.infinito.nexus/code/tree/main/roles/desk-jrnl"
min_ansible_version: "2.9"
platforms:
- name: Archlinux

View File

@@ -2,8 +2,8 @@
galaxy_info:
author: "Kevin Veen-Birchenbach"
description: "Installs KeePassXC password manager on Pacman-based systems."
license: "Infinito.Nexus NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birchenbach
Consulting & Coaching Solutions
@@ -12,9 +12,9 @@ galaxy_info:
- keepassxc
- security
- passwords
repository: "https://github.com/kevinveenbirkenbach/infinito-nexus"
issue_tracker_url: "https://github.com/kevinveenbirkenbach/infinito-nexus/issues"
documentation: "https://github.com/kevinveenbirkenbach/infinito-nexus/tree/main/roles/desk-keepassxc"
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://s.infinito.nexus/code/tree/main/roles/desk-keepassxc"
min_ansible_version: "2.9"
platforms:
- name: Archlinux

View File

@@ -21,4 +21,4 @@ The purpose of this role is to automate the installation and configuration of Li
Developed and maintained by **Kevin Veen-Birkenbach**.
Learn more at [www.veen.world](https://www.veen.world)
License: [Infinito.Nexus NonCommercial License (CNCL)](https://s.veen.world/cncl)
License: [Infinito.Nexus NonCommercial License](https://s.infinito.nexus/license)

View File

@@ -2,8 +2,8 @@
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Installs LibreOffice along with Liberation fonts and language packages on Arch Linux systems for a complete office suite experience."
license: "Infinito.Nexus NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions

View File

@@ -9,4 +9,4 @@
community.general.pacman:
name: "libreoffice-{{ applications['desk-libreoffice'].flavor }}-{{ item }}"
state: present
loop: "{{libreoffice_languages}}"
loop: "{{ libreoffice_languages }}"

View File

@@ -2,8 +2,8 @@
galaxy_info:
author: "Kevin VeenBirchenbach"
description: "Installs micro CLI text editor on Pacmanbased systems."
license: "Infinito.Nexus NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin VeenBirchenbach
Consulting & Coaching Solutions
@@ -12,9 +12,9 @@ galaxy_info:
- micro
- editor
- cli
repository: "https://github.com/kevinveenbirkenbach/infinito-nexus"
issue_tracker_url: "https://github.com/kevinveenbirkenbach/infinito-nexus/issues"
documentation: "https://github.com/kevinveenbirkenbach/infinito-nexus/tree/main/roles/desk-micro"
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://s.infinito.nexus/code/tree/main/roles/desk-micro"
logo:
class: "fas fa-terminal"
min_ansible_version: "2.9"

View File

@@ -2,8 +2,8 @@
galaxy_info:
author: "Kevin VeenBirchenbach"
description: "Installs neovim CLI text editor on Pacmanbased systems."
license: "Infinito.Nexus NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin VeenBirchenbach
Consulting & Coaching Solutions
@@ -12,9 +12,9 @@ galaxy_info:
- neovim
- editor
- cli
repository: "https://github.com/kevinveenbirkenbach/infinito-nexus"
issue_tracker_url: "https://github.com/kevinveenbirkenbach/infinito-nexus/issues"
documentation: "https://github.com/kevinveenbirkenbach/infinito-nexus/tree/main/roles/desk-neovim"
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://s.infinito.nexus/code/tree/main/roles/desk-neovim"
logo:
class: "fab fa-vim"
min_ansible_version: "2.9"

View File

@@ -24,5 +24,5 @@ The purpose of this role is to automate the configuration of cloud-integrated us
Developed and maintained by **Kevin Veen-Birkenbach**.
Learn more at [www.veen.world](https://www.veen.world)
Part of the [Infinito.Nexus Project](https://github.com/kevinveenbirkenbach/infinito-nexus)
License: [Infinito.Nexus NonCommercial License (CNCL)](https://s.veen.world/cncl)
Part of the [Infinito.Nexus Project](https://s.infinito.nexus/code)
License: [Infinito.Nexus NonCommercial License](https://s.infinito.nexus/license)

View File

@@ -2,8 +2,8 @@
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Installs and links Nextcloud desktop client folders for cloud-integrated user environments."
license: "Infinito.Nexus NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions
@@ -20,8 +20,8 @@ galaxy_info:
- user
- desktop
- automation
repository: https://github.com/kevinveenbirkenbach/infinito-nexus
issue_tracker_url: https://github.com/kevinveenbirkenbach/infinito-nexus/issues
repository: https://s.infinito.nexus/code
issue_tracker_url: https://s.infinito.nexus/issues
documentation: "https://docs.infinito.nexus/"
dependencies: []

View File

@@ -2,8 +2,8 @@
galaxy_info:
author: "Kevin Veen-Birchenbach"
description: "Installs OBS Studio for streaming and recording on Pacman-based systems."
license: "Infinito.Nexus NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birchenbach
Consulting & Coaching Solutions
@@ -12,9 +12,9 @@ galaxy_info:
- obs
- streaming
- recording
repository: "https://github.com/kevinveenbirkenbach/infinito-nexus"
issue_tracker_url: "https://github.com/kevinveenbirkenbach/infinito-nexus/issues"
documentation: "https://github.com/kevinveenbirkenbach/infinito-nexus/tree/main/roles/desk-obs"
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://s.infinito.nexus/code/tree/main/roles/desk-obs"
min_ansible_version: "2.9"
platforms:
- name: Archlinux

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