From da06943f291f48234afef5f071a2471141fa863e Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Wed, 17 Dec 2025 12:33:30 +0100 Subject: [PATCH] Release version 0.3.0 --- CHANGELOG.md | 8 +++ module_utils/sounds.py | 46 +++++++++++-- pyproject.toml | 2 +- roles/desk-nextcloud/meta/main.yml | 4 +- roles/desk-nextcloud/tasks/main.yml | 69 +++++++++++-------- roles/desk-nextcloud/vars/main.yml | 15 +++- .../tasks/01_core.yml | 1 + .../tasks/02_pkgmgr_routines.yml | 49 ++++++++++--- roles/web-app-gitea/config/main.yml | 2 +- roles/web-app-gitea/vars/main.yml | 3 +- 10 files changed, 148 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c3cd37b..e9ad3fd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [0.3.0] - 2025-12-17 + +* - Introduced a layered Docker architecture: Infinito.Nexus now builds on pre-built pkgmgr base images, with a clear separation between base tooling, application source, and runtime logic. +- Standardized container paths (`/opt/src/infinito`) and switched to a global virtual environment to ensure reproducible builds and consistent test execution. +- Unit and lint tests now run reliably on this new layer model, both locally and in CI. +- Refactored build, setup, and deploy workflows to match the new layered design and improve maintainability. + + ## [0.2.1] - 2025-12-10 * restored full deployability of the Sphinx app by fixing the application_id scoping bug. diff --git a/module_utils/sounds.py b/module_utils/sounds.py index f10662fc..9dd0eaec 100644 --- a/module_utils/sounds.py +++ b/module_utils/sounds.py @@ -1,27 +1,55 @@ import os import warnings +from typing import Optional + +_SOUND_DISABLED_REASON: Optional[str] = None +_SOUND_WARNED: bool = False + + +def _warn_sound_disabled_once() -> None: + """ + Emit the 'Sound support disabled' warning at most once per Python process. + + Important: + - Do NOT warn at import time (avoids noisy unit test output). + - Warn only when a sound function is actually called. + """ + global _SOUND_WARNED + + if _SOUND_WARNED: + return + + if not _SOUND_DISABLED_REASON: + return + + _SOUND_WARNED = True + warnings.warn( + f"Sound support disabled: {_SOUND_DISABLED_REASON}", + RuntimeWarning, + stacklevel=2, + ) class DummySound: @staticmethod def play_start_sound() -> None: - pass + _warn_sound_disabled_once() @staticmethod def play_infinito_intro_sound() -> None: - pass + _warn_sound_disabled_once() @staticmethod def play_finished_successfully_sound() -> None: - pass + _warn_sound_disabled_once() @staticmethod def play_finished_failed_sound() -> None: - pass + _warn_sound_disabled_once() @staticmethod def play_warning_sound() -> None: - pass + _warn_sound_disabled_once() try: @@ -210,7 +238,9 @@ try: cls._prepare_and_play(freqs) @classmethod - def _prepare_and_play(cls, freqs: list[float], durations: list[float] | None = None) -> None: + def _prepare_and_play( + cls, freqs: list[float], durations: list[float] | None = None + ) -> None: count = len(freqs) if durations is None: @@ -223,5 +253,7 @@ try: cls._play(np.concatenate(waves)) except ImportError as exc: - warnings.warn(f"Sound support disabled: {exc}", RuntimeWarning) + # Do NOT warn at import time — this module is used in many unit tests / subprocess calls. + # Warn only when a sound method is actually invoked. + _SOUND_DISABLED_REASON = str(exc) Sound = DummySound diff --git a/pyproject.toml b/pyproject.toml index 05749755..df37ecf8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "infinito-nexus" -version = "0.0.0" +version = "0.3.0" description = "Infinito.Nexus" readme = "README.md" requires-python = ">=3.10" diff --git a/roles/desk-nextcloud/meta/main.yml b/roles/desk-nextcloud/meta/main.yml index 43e48d8c..39260a07 100644 --- a/roles/desk-nextcloud/meta/main.yml +++ b/roles/desk-nextcloud/meta/main.yml @@ -4,7 +4,7 @@ galaxy_info: description: "Installs and links Nextcloud desktop client folders for cloud-integrated user environments." license: "Infinito.Nexus NonCommercial License" license_url: "https://s.infinito.nexus/license" - company: | + company: | Kevin Veen-Birkenbach Consulting & Coaching Solutions https://www.veen.world @@ -23,5 +23,3 @@ galaxy_info: repository: https://s.infinito.nexus/code issue_tracker_url: https://s.infinito.nexus/issues documentation: "https://docs.infinito.nexus/" - -dependencies: [] \ No newline at end of file diff --git a/roles/desk-nextcloud/tasks/main.yml b/roles/desk-nextcloud/tasks/main.yml index a0f68e9f..2f7c905d 100644 --- a/roles/desk-nextcloud/tasks/main.yml +++ b/roles/desk-nextcloud/tasks/main.yml @@ -3,32 +3,45 @@ name: nextcloud-client state: present -- name: Link homefolders to cloud - ansible.builtin.file: - src: "{{nextcloud_cloud_directory}}{{ item }}" - dest: "{{nextcloud_user_home_directory}}{{ item }}" - owner: "{{ users[desktop_username].username }}" - group: "{{ users[desktop_username].username }}" - state: link - force: yes - ignore_errors: true # Just temporary @todo remove - loop: - - Templates - - Documents - - Videos - - Pictures - - Music - - Desktop - - Software - - Downloads - - Workspaces - - Books - - Screenshots +- name: Initialize dotlinker mapping list + ansible.builtin.set_fact: + nextcloud_dotlinker_mappings: [] -- name: Link dump folder - ansible.builtin.file: - src: "{{nextcloud_cloud_directory}}InstantUpload" - dest: "{{nextcloud_user_home_directory}}Dump" - owner: "{{ users[desktop_username].username }}" - group: "{{ users[desktop_username].username }}" - state: link \ No newline at end of file +- name: Build dotlinker mappings for Nextcloud folders + ansible.builtin.set_fact: + nextcloud_dotlinker_mappings: >- + {{ + nextcloud_dotlinker_mappings + [ + { + 'name': 'nextcloud-' ~ (item | lower), + 'backend': 'cloud', + 'src': nextcloud_user_home_directory ~ item, + 'dest': nextcloud_cloud_directory ~ item + } + ] + }} + loop: "{{ nextcloud_dotlinker_folders }}" + +- name: Add dump mapping (Dump -> InstantUpload) + ansible.builtin.set_fact: + nextcloud_dotlinker_mappings: >- + {{ + nextcloud_dotlinker_mappings + [ + { + 'name': 'nextcloud-dump', + 'backend': 'cloud', + 'src': nextcloud_user_home_directory ~ 'Dump', + 'dest': nextcloud_cloud_directory ~ 'InstantUpload' + } + ] + }} + +- name: Apply Nextcloud folder mappings via dotlinker + ansible.builtin.include_role: + name: desk-dotlinker + vars: + dotlinker_user: "{{ users[desktop_username].username }}" + dotlinker_config_path: "{{ nextcloud_user_home_directory }}.config/dotlinker/config.yaml" + dotlinker_replace: true + dotlinker_apply: true + dotlinker_mappings: "{{ nextcloud_dotlinker_mappings }}" diff --git a/roles/desk-nextcloud/vars/main.yml b/roles/desk-nextcloud/vars/main.yml index 6c994ba5..d94f0fe2 100644 --- a/roles/desk-nextcloud/vars/main.yml +++ b/roles/desk-nextcloud/vars/main.yml @@ -1,4 +1,17 @@ application_id: desk-nextcloud nextcloud_user_home_directory: "/home/{{ users[desktop_username].username }}/" nextcloud_cloud_fqdn: "{{ applications | get_app_conf(application_id, 'credentials.cloud_fqdn') }}" -nextcloud_cloud_directory: '{{ nextcloud_user_home_directory }}Clouds/{{nextcloud_cloud_fqdn}}/{{ users[desktop_username].username }}/' +nextcloud_cloud_directory: "{{ nextcloud_user_home_directory }}Clouds/{{ nextcloud_cloud_fqdn }}/{{ users[desktop_username].username }}/" + +nextcloud_dotlinker_folders: + - Templates + - Documents + - Videos + - Pictures + - Music + - Desktop + - Software + - Downloads + - Workspaces + - Books + - Screenshots diff --git a/roles/sys-ctl-bkp-docker-2-loc/tasks/01_core.yml b/roles/sys-ctl-bkp-docker-2-loc/tasks/01_core.yml index 3afac48c..84f6ea2f 100644 --- a/roles/sys-ctl-bkp-docker-2-loc/tasks/01_core.yml +++ b/roles/sys-ctl-bkp-docker-2-loc/tasks/01_core.yml @@ -5,6 +5,7 @@ - sys-bkp-provider - sys-ctl-alm-compose - sys-lock + - dev-nix - include_tasks: 02_pkgmgr_routines.yml when: backup_docker_to_local_folder is not defined diff --git a/roles/sys-ctl-bkp-docker-2-loc/tasks/02_pkgmgr_routines.yml b/roles/sys-ctl-bkp-docker-2-loc/tasks/02_pkgmgr_routines.yml index 5f25f255..4167802e 100644 --- a/roles/sys-ctl-bkp-docker-2-loc/tasks/02_pkgmgr_routines.yml +++ b/roles/sys-ctl-bkp-docker-2-loc/tasks/02_pkgmgr_routines.yml @@ -1,19 +1,50 @@ - block: - - name: "pkgmgr install {{ BKP_DOCKER_2_LOC_PKG }}" - include_role: - name: pkgmgr-install - vars: - package_name: "{{ BKP_DOCKER_2_LOC_PKG }}" + - name: "Install/update {{ BKP_DOCKER_2_LOC_PKG }} via pkgmgr (nix run)" + become: true + ansible.builtin.command: + argv: + - nix + - run + - --no-write-lock-file + - "github:kevinveenbirkenbach/package-manager#pkgmgr" + - -- + - update + - "{{ BKP_DOCKER_2_LOC_PKG }}" + - --dependencies + - --clone-mode + - shallow + register: pkgmgr_update_result + changed_when: > + ('already up to date' not in ((pkgmgr_update_result.stdout | default('') | lower) + ~ ' ' ~ (pkgmgr_update_result.stderr | default('') | lower))) + and + ('no command defined' not in ((pkgmgr_update_result.stdout | default('') | lower) + ~ ' ' ~ (pkgmgr_update_result.stderr | default('') | lower))) + failed_when: > + (pkgmgr_update_result.rc != 0) + and + ('no command defined' not in ((pkgmgr_update_result.stdout | default('') | lower) + ~ ' ' ~ (pkgmgr_update_result.stderr | default('') | lower))) - - name: "Retrieve {{ BKP_DOCKER_2_LOC_PKG }} path from pkgmgr" - command: "pkgmgr path {{ BKP_DOCKER_2_LOC_PKG }}" + - name: "Retrieve {{ BKP_DOCKER_2_LOC_PKG }} path via pkgmgr (nix run)" + become: true + ansible.builtin.command: + argv: + - nix + - run + - --no-write-lock-file + - "github:kevinveenbirkenbach/package-manager#pkgmgr" + - -- + - path + - "{{ BKP_DOCKER_2_LOC_PKG }}" register: pkgmgr_output changed_when: false - name: Set fact for backup_docker_to_local_folder - set_fact: - backup_docker_to_local_folder: "{{ pkgmgr_output.stdout }}/" + ansible.builtin.set_fact: + backup_docker_to_local_folder: "{{ (pkgmgr_output.stdout | trim) ~ '/' }}" changed_when: false + when: backup_docker_to_local_folder is not defined vars: BKP_DOCKER_2_LOC_PKG: backup-docker-to-local diff --git a/roles/web-app-gitea/config/main.yml b/roles/web-app-gitea/config/main.yml index fbe27cae..dbe25eec 100644 --- a/roles/web-app-gitea/config/main.yml +++ b/roles/web-app-gitea/config/main.yml @@ -6,7 +6,7 @@ configuration: default_push_create_private: True # Default private when creating a new repository with push-to-create. features: matomo: true - css: false + css: true desktop: true central_database: true ldap: true diff --git a/roles/web-app-gitea/vars/main.yml b/roles/web-app-gitea/vars/main.yml index 8f1cbc25..d7a20dc6 100644 --- a/roles/web-app-gitea/vars/main.yml +++ b/roles/web-app-gitea/vars/main.yml @@ -14,7 +14,8 @@ GITEA_LDAP_AUTH_ARGS: - '--bind-dn "{{ LDAP.DN.ADMINISTRATOR.DATA }}"' - '--bind-password "{{ LDAP.BIND_CREDENTIAL }}"' - '--user-search-base "{{ LDAP.DN.OU.USERS }}"' - - '--user-filter "(&(objectClass=inetOrgPerson)(uid=%s))"' + - '--user-filter "(&(objectClass=inetOrgPerson)(uid=%s))"' + - '--admin-filter "(memberOf=cn={{ application_id }}-administrator,{{ LDAP.DN.OU.ROLES }})"' - '--username-attribute "{{ LDAP.USER.ATTRIBUTES.ID }}"' - '--firstname-attribute "{{ LDAP.USER.ATTRIBUTES.FIRSTNAME }}"' - '--surname-attribute "{{ LDAP.USER.ATTRIBUTES.SURNAME }}"'