9 Commits

Author SHA1 Message Date
0e89d89b45 Make sound support optional and guard against missing audio dependencies
- Move simpleaudio to optional dependency (audio extra)
- Add DummySound fallback when optional audio libs are unavailable
- Import simpleaudio/numpy lazily with ImportError handling
- Remove Docker-specific sound disabling logic
- Improve typing and robustness of sound utilities

https://chatgpt.com/share/693dec1d-60bc-800f-8ffe-3886a9c265bd
2025-12-13 23:43:36 +01:00
d0882433c8 Refactor setup workflow and make install robust via virtualenv
- Introduce a dedicated Python virtualenv (deps target) and run all setup scripts through it
- Fix missing PyYAML errors in clean, CI, and Nix environments
- Refactor build defaults into cli/setup for clearer semantics
- Make setup deterministic and independent from system Python
- Replace early Makefile shell expansion with runtime evaluation
- Rename messy-test to test-messy and update deploy logic and tests accordingly
- Keep setup and test targets consistent across Makefile, CLI, and unit tests

https://chatgpt.com/share/693de226-00ac-800f-8cbd-06552b2f283c
2025-12-13 23:00:13 +01:00
600d7a1fe8 Ignored python package build files 2025-12-13 22:13:53 +01:00
0580839705 Makefile: unify Python interpreter via PYTHON variable
Avoids mixed system/Nix/venv Python usage and fixes missing PyYAML errors.

https://chatgpt.com/share/693dd6b2-14f0-800f-9b95-368d58b68f49
2025-12-13 22:12:12 +01:00
7070100363 Added missing PyYAML 2025-12-13 21:41:29 +01:00
ad813df0c5 Switch to pyproject.toml for Python dependencies
Introduce pyproject.toml as the single source of truth for Python dependencies.
Remove legacy requirements.txt and simplify requirements.yml to Ansible collections only.
Drop pytest in favor of the built-in unittest framework.

https://chatgpt.com/share/693dbe8c-8b64-800f-a6e5-41b7d21ae7e0
2025-12-13 20:29:09 +01:00
f8e2aa2b93 Added mirrors 2025-12-13 09:27:04 +01:00
d0a2c3fada Release version 0.2.1 2025-12-10 21:14:47 +01:00
75eaecce5b **Remove obsolete installation/administration docs, fix pgAdmin server mode condition, normalize git repository vars, and ensure correct application_id for web-app-sphinx**
* Remove outdated `Installation.md` and `Administration.md` documentation from Akaunting and Peertube roles
* Fix `server_mode` conditional in `web-app-pgadmin` to avoid unintended defaults
* Normalize formatting of git repository variables in `web-app-roulette-wheel`
* Explicitly set `application_id` when loading `sys-stk-full-stateless` in `web-app-sphinx` to prevent scoping issues

https://chatgpt.com/share/6939d42e-483c-800f-b0fc-be61caab615d
2025-12-10 21:12:15 +01:00
24 changed files with 312 additions and 271 deletions

2
.gitignore vendored
View File

@@ -10,3 +10,5 @@ venv
*tree.json *tree.json
roles/list.json roles/list.json
*.pyc *.pyc
*.egg-info
build

View File

@@ -1,3 +1,8 @@
## [0.2.1] - 2025-12-10
* restored full deployability of the Sphinx app by fixing the application_id scoping bug.
## [0.2.0] - 2025-12-10 ## [0.2.0] - 2025-12-10
* Added full Nix installer integration with dynamic upstream SHA256 verification, OS-specific installation paths, template-driven configuration, and updated pkgmgr integration. * Added full Nix installer integration with dynamic upstream SHA256 verification, OS-specific installation paths, template-driven configuration, and updated pkgmgr integration.

3
MIRRORS Normal file
View File

@@ -0,0 +1,3 @@
git@github.com:infinito-nexus/core.git
ssh://git@code.infinito.nexus:2201/infinito/nexus.git
git@github.com:kevinveenbirkenbach/infinito-nexus.git

View File

@@ -1,12 +1,14 @@
SHELL := /usr/bin/env bash
VENV ?= .venv
PYTHON := $(VENV)/bin/python
PIP := $(PYTHON) -m pip
ROLES_DIR := ./roles ROLES_DIR := ./roles
APPLICATIONS_OUT := ./group_vars/all/04_applications.yml APPLICATIONS_OUT := ./group_vars/all/04_applications.yml
APPLICATIONS_SCRIPT := ./cli/build/defaults/applications.py APPLICATIONS_SCRIPT := ./cli/setup/applications.py
USERS_SCRIPT := ./cli/setup/users.py
USERS_OUT := ./group_vars/all/03_users.yml USERS_OUT := ./group_vars/all/03_users.yml
USERS_SCRIPT := ./cli/build/defaults/users.py
INCLUDES_SCRIPT := ./cli/build/role_include.py INCLUDES_SCRIPT := ./cli/build/role_include.py
INCLUDE_GROUPS := $(shell python3 main.py meta categories invokable -s "-" --no-signal | tr '\n' ' ')
# Directory where these include-files will be written # Directory where these include-files will be written
INCLUDES_OUT_DIR := ./tasks/groups INCLUDES_OUT_DIR := ./tasks/groups
@@ -19,7 +21,7 @@ RESERVED_USERNAMES := $(shell \
| paste -sd, - \ | paste -sd, - \
) )
.PHONY: build install test .PHONY: deps setup setup-clean test-messy test install
clean-keep-logs: clean-keep-logs:
@echo "🧹 Cleaning ignored files but keeping logs/…" @echo "🧹 Cleaning ignored files but keeping logs/…"
@@ -31,11 +33,11 @@ clean:
list: list:
@echo Generating the roles list @echo Generating the roles list
python3 main.py build roles_list $(PYTHON) main.py build roles_list
tree: tree:
@echo Generating Tree @echo Generating Tree
python3 main.py build tree -D 2 --no-signal $(PYTHON) main.py build tree -D 2 --no-signal
mig: list tree mig: list tree
@echo Creating meta data for meta infinity graph @echo Creating meta data for meta infinity graph
@@ -45,41 +47,51 @@ dockerignore:
cat .gitignore > .dockerignore cat .gitignore > .dockerignore
echo ".git" >> .dockerignore echo ".git" >> .dockerignore
messy-build: dockerignore setup: deps dockerignore
@echo "🔧 Generating users defaults → $(USERS_OUT)" @echo "🔧 Generating users defaults → $(USERS_OUT)"
python3 $(USERS_SCRIPT) \ $(PYTHON) $(USERS_SCRIPT) \
--roles-dir $(ROLES_DIR) \ --roles-dir $(ROLES_DIR) \
--output $(USERS_OUT) \ --output $(USERS_OUT) \
--reserved-usernames "$(RESERVED_USERNAMES)" --reserved-usernames "$(RESERVED_USERNAMES)"
@echo "✅ Users defaults written to $(USERS_OUT)\n" @echo "✅ Users defaults written to $(USERS_OUT)\n"
@echo "🔧 Generating applications defaults → $(APPLICATIONS_OUT)" @echo "🔧 Generating applications defaults → $(APPLICATIONS_OUT)"
python3 $(APPLICATIONS_SCRIPT) \ $(PYTHON) $(APPLICATIONS_SCRIPT) \
--roles-dir $(ROLES_DIR) \ --roles-dir $(ROLES_DIR) \
--output-file $(APPLICATIONS_OUT) --output-file $(APPLICATIONS_OUT)
@echo "✅ Applications defaults written to $(APPLICATIONS_OUT)\n" @echo "✅ Applications defaults written to $(APPLICATIONS_OUT)\n"
@echo "🔧 Generating role-include files for each group…" @echo "🔧 Generating role-include files for each group…"
@mkdir -p $(INCLUDES_OUT_DIR) @mkdir -p $(INCLUDES_OUT_DIR)
@$(foreach grp,$(INCLUDE_GROUPS), \ @INCLUDE_GROUPS="$$( $(PYTHON) main.py meta categories invokable -s "-" --no-signal | tr '\n' ' ' )"; \
out=$(INCLUDES_OUT_DIR)/$(grp)roles.yml; \ for grp in $$INCLUDE_GROUPS; do \
echo "→ Building $$out (pattern: '$(grp)')…"; \ out="$(INCLUDES_OUT_DIR)/$${grp}roles.yml"; \
python3 $(INCLUDES_SCRIPT) $(ROLES_DIR) \ echo "→ Building $$out (pattern: '$$grp')…"; \
-p $(grp) -o $$out; \ $(PYTHON) $(INCLUDES_SCRIPT) $(ROLES_DIR) -p $$grp -o $$out; \
echo "$$out"; \ echo "$$out"; \
) done
messy-test: setup-clean: clean setup
@echo "Full build with cleanup before was executed."
test-messy:
@echo "🧪 Running Python tests…" @echo "🧪 Running Python tests…"
PYTHONPATH=. python -m unittest discover -s tests PYTHONPATH=. $(PYTHON) -m unittest discover -s tests
@echo "📑 Checking Ansible syntax…" @echo "📑 Checking Ansible syntax…"
ansible-playbook -i localhost, -c local $(foreach f,$(wildcard group_vars/all/*.yml),-e @$(f)) playbook.yml --syntax-check ansible-playbook -i localhost, -c local $(foreach f,$(wildcard group_vars/all/*.yml),-e @$(f)) playbook.yml --syntax-check
install: build test: setup-clean test-messy
@echo "⚙️ Install complete." @echo "Full test with setup-clean before was executed."
build: clean messy-build deps:
@echo "Full build with cleanup before was executed." @if [ ! -x "$(PYTHON)" ]; then \
echo "🐍 Creating virtualenv $(VENV)"; \
python3 -m venv $(VENV); \
fi
@echo "📦 Installing Python dependencies"
@$(PIP) install --upgrade pip setuptools wheel
@$(PIP) install -e .
install: deps
@echo "✅ Python environment installed (editable)."
test: build messy-test
@echo "Full test with build before was executed."

View File

@@ -95,8 +95,8 @@ def run_ansible_playbook(
# 4) Test Phase # 4) Test Phase
# --------------------------------------------------------- # ---------------------------------------------------------
if not skip_tests: if not skip_tests:
print("\n🧪 Running tests (make messy-test)...\n") print("\n🧪 Running tests (make test-messy)...\n")
subprocess.run(["make", "messy-test"], check=True) subprocess.run(["make", "test-messy"], check=True)
else: else:
print("\n🧪 Tests skipped (--skip-tests)\n") print("\n🧪 Tests skipped (--skip-tests)\n")

View File

@@ -209,7 +209,7 @@ def print_global_help(available, cli_dir):
Fore.CYAN Fore.CYAN
)) ))
print(color_text( print(color_text(
" corresponds to `cli/build/defaults/users.py`.", " corresponds to `cli/setup/users.py`.",
Fore.CYAN Fore.CYAN
)) ))
print() print()

View File

@@ -1,105 +1,142 @@
import os import os
import warnings
class DummySound: class DummySound:
@staticmethod @staticmethod
def play_start_sound(): pass def play_start_sound() -> None:
@staticmethod pass
def play_infinito_intro_sound(): pass
@staticmethod @staticmethod
def play_finished_successfully_sound(): pass def play_infinito_intro_sound() -> None:
@staticmethod pass
def play_finished_failed_sound(): pass
@staticmethod @staticmethod
def play_warning_sound(): pass def play_finished_successfully_sound() -> None:
pass
@staticmethod
def play_finished_failed_sound() -> None:
pass
@staticmethod
def play_warning_sound() -> None:
pass
_IN_DOCKER = os.path.exists('/.dockerenv')
if _IN_DOCKER:
Sound = DummySound
else:
try: try:
import numpy as np import numpy as np
import simpleaudio as sa import simpleaudio as sa
import shutil, subprocess, tempfile, wave as wavmod import shutil
import subprocess
import tempfile
import wave as wavmod
class Sound: class Sound:
""" """
Sound effects for the application with enhanced complexity. Sound effects for the application.
Each sound uses at least 6 distinct tones and lasts no more than max_length seconds,
except the intro sound which is a detailed 26-second Berlin techno-style build-up, 12-second celebration with a descending-fifth chord sequence of 7 chords, and breakdown with melodic background.
Transitions between phases now crossfade over 3 seconds for smoother flow.
""" """
fs = 44100 # Sampling rate (samples per second) fs = 44100
complexity_factor = 10 # Number of harmonics to sum for richer timbres complexity_factor = 10
max_length = 2.0 # Maximum total duration of any sound in seconds max_length = 2.0
@staticmethod @staticmethod
def _generate_complex_wave(frequency: float, duration: float, harmonics: int = None) -> np.ndarray: def _generate_complex_wave(
frequency: float,
duration: float,
harmonics: int | None = None,
) -> np.ndarray:
if harmonics is None: if harmonics is None:
harmonics = Sound.complexity_factor harmonics = Sound.complexity_factor
t = np.linspace(0, duration, int(Sound.fs * duration), False) t = np.linspace(0, duration, int(Sound.fs * duration), False)
wave = np.zeros_like(t) wave = np.zeros_like(t)
for n in range(1, harmonics + 1): for n in range(1, harmonics + 1):
wave += (1 / n) * np.sin(2 * np.pi * frequency * n * t) wave += (1 / n) * np.sin(2 * np.pi * frequency * n * t)
# ADSR envelope # ADSR envelope
attack = int(0.02 * Sound.fs) attack = int(0.02 * Sound.fs)
release = int(0.05 * Sound.fs) release = int(0.05 * Sound.fs)
env = np.ones_like(wave) env = np.ones_like(wave)
env[:attack] = np.linspace(0, 1, attack) env[:attack] = np.linspace(0, 1, attack)
env[-release:] = np.linspace(1, 0, release) env[-release:] = np.linspace(1, 0, release)
wave *= env wave *= env
wave /= np.max(np.abs(wave)) wave /= np.max(np.abs(wave))
return (wave * (2**15 - 1)).astype(np.int16) return (wave * (2**15 - 1)).astype(np.int16)
@staticmethod @staticmethod
def _crossfade(w1: np.ndarray, w2: np.ndarray, fade_len: int) -> np.ndarray: def _crossfade(w1: np.ndarray, w2: np.ndarray, fade_len: int) -> np.ndarray:
# Ensure fade_len less than each
fade_len = min(fade_len, len(w1), len(w2)) fade_len = min(fade_len, len(w1), len(w2))
if fade_len <= 0:
return np.concatenate([w1, w2])
fade_out = np.linspace(1, 0, fade_len) fade_out = np.linspace(1, 0, fade_len)
fade_in = np.linspace(0, 1, fade_len) fade_in = np.linspace(0, 1, fade_len)
w1_end = w1[-fade_len:] * fade_out
w2_start = w2[:fade_len] * fade_in w1_end = w1[-fade_len:].astype(np.float32) * fade_out
w2_start = w2[:fade_len].astype(np.float32) * fade_in
middle = (w1_end + w2_start).astype(np.int16) middle = (w1_end + w2_start).astype(np.int16)
return np.concatenate([w1[:-fade_len], middle, w2[fade_len:]]) return np.concatenate([w1[:-fade_len], middle, w2[fade_len:]])
@staticmethod @staticmethod
def _play_via_system(wave: np.ndarray): def _play_via_system(wave: np.ndarray) -> None:
# Write a temp WAV and play it via available system player
with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as f: with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as f:
fname = f.name fname = f.name
try: try:
with wavmod.open(fname, "wb") as w: with wavmod.open(fname, "wb") as w:
w.setnchannels(1) w.setnchannels(1)
w.setsampwidth(2) w.setsampwidth(2)
w.setframerate(Sound.fs) w.setframerate(Sound.fs)
w.writeframes(wave.tobytes()) w.writeframes(wave.tobytes())
def run(cmd):
return subprocess.run( def run(cmd: list[str]) -> bool:
cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL return (
).returncode == 0 subprocess.run(
# Preferred order: PipeWire → PulseAudio → ALSA → ffplay cmd,
if shutil.which("pw-play") and run(["pw-play", fname]): return stdout=subprocess.DEVNULL,
if shutil.which("paplay") and run(["paplay", fname]): return stderr=subprocess.DEVNULL,
if shutil.which("aplay") and run(["aplay", "-q", fname]): return check=False,
if shutil.which("ffplay") and run(["ffplay", "-autoexit", "-nodisp", fname]): return ).returncode
# Last resort if no system player exists: simpleaudio == 0
)
if shutil.which("pw-play") and run(["pw-play", fname]):
return
if shutil.which("paplay") and run(["paplay", fname]):
return
if shutil.which("aplay") and run(["aplay", "-q", fname]):
return
if shutil.which("ffplay") and run(["ffplay", "-autoexit", "-nodisp", fname]):
return
play_obj = sa.play_buffer(wave, 1, 2, Sound.fs) play_obj = sa.play_buffer(wave, 1, 2, Sound.fs)
play_obj.wait_done() play_obj.wait_done()
finally: finally:
try: os.unlink(fname) try:
except Exception: pass os.unlink(fname)
except Exception:
pass
@staticmethod @staticmethod
def _play(wave: np.ndarray): def _play(wave: np.ndarray) -> None:
# Switch via env: system | simpleaudio | auto (default)
backend = os.getenv("INFINITO_AUDIO_BACKEND", "auto").lower() backend = os.getenv("INFINITO_AUDIO_BACKEND", "auto").lower()
if backend == "system": if backend == "system":
return Sound._play_via_system(wave) Sound._play_via_system(wave)
return
if backend == "simpleaudio": if backend == "simpleaudio":
play_obj = sa.play_buffer(wave, 1, 2, Sound.fs) play_obj = sa.play_buffer(wave, 1, 2, Sound.fs)
play_obj.wait_done() play_obj.wait_done()
return return
# auto: try simpleaudio first; if it fails, fall back to system
# auto
try: try:
play_obj = sa.play_buffer(wave, 1, 2, Sound.fs) play_obj = sa.play_buffer(wave, 1, 2, Sound.fs)
play_obj.wait_done() play_obj.wait_done()
@@ -107,43 +144,44 @@ else:
Sound._play_via_system(wave) Sound._play_via_system(wave)
@classmethod @classmethod
def play_infinito_intro_sound(cls): def play_infinito_intro_sound(cls) -> None:
# Phase durations
build_time = 10.0 build_time = 10.0
celebr_time = 12.0 celebr_time = 12.0
breakdown_time = 10.0 breakdown_time = 10.0
overlap = 3.0 # seconds of crossfade overlap = 3.0
bass_seg = 0.125 # 1/8s kick
melody_seg = 0.25 # 2/8s melody bass_seg = 0.125
bass_freq = 65.41 # C2 kick melody_seg = 0.25
bass_freq = 65.41
melody_freqs = [261.63, 293.66, 329.63, 392.00, 440.00, 523.25] melody_freqs = [261.63, 293.66, 329.63, 392.00, 440.00, 523.25]
# Build-up phase
steps = int(build_time / (bass_seg + melody_seg)) steps = int(build_time / (bass_seg + melody_seg))
build_seq = [] build_seq: list[np.ndarray] = []
for i in range(steps): for i in range(steps):
amp = (i + 1) / steps amp = (i + 1) / steps
b = cls._generate_complex_wave(bass_freq, bass_seg).astype(np.float32) * amp b = cls._generate_complex_wave(bass_freq, bass_seg).astype(np.float32) * amp
m = cls._generate_complex_wave(melody_freqs[i % len(melody_freqs)], melody_seg).astype(np.float32) * amp m = cls._generate_complex_wave(
melody_freqs[i % len(melody_freqs)], melody_seg
).astype(np.float32) * amp
build_seq.append(b.astype(np.int16)) build_seq.append(b.astype(np.int16))
build_seq.append(m.astype(np.int16)) build_seq.append(m.astype(np.int16))
build_wave = np.concatenate(build_seq) build_wave = np.concatenate(build_seq)
# Celebration phase: 7 descending-fifth chords
roots = [523.25, 349.23, 233.08, 155.56, 103.83, 69.30, 46.25] roots = [523.25, 349.23, 233.08, 155.56, 103.83, 69.30, 46.25]
chord_time = celebr_time / len(roots) chord_time = celebr_time / len(roots)
celebr_seq = [] celebr_seq: list[np.ndarray] = []
for root in roots: for root in roots:
t = np.linspace(0, chord_time, int(cls.fs * chord_time), False) t = np.linspace(0, chord_time, int(cls.fs * chord_time), False)
chord = sum(np.sin(2 * np.pi * f * t) for f in [root, root * 5 / 4, root * 3 / 2]) chord = sum(np.sin(2 * np.pi * f * t) for f in [root, root * 5 / 4, root * 3 / 2])
chord /= np.max(np.abs(chord)) chord /= np.max(np.abs(chord))
celebr_seq.append((chord * (2**15 - 1)).astype(np.int16)) celebr_seq.append((chord * (2**15 - 1)).astype(np.int16))
celebr_wave = np.concatenate(celebr_seq)
# Breakdown phase (mirror of build-up) celebr_wave = np.concatenate(celebr_seq)
breakdown_wave = np.concatenate(list(reversed(build_seq))) breakdown_wave = np.concatenate(list(reversed(build_seq)))
# Crossfade transitions
fade_samples = int(overlap * cls.fs) fade_samples = int(overlap * cls.fs)
bc = cls._crossfade(build_wave, celebr_wave, fade_samples) bc = cls._crossfade(build_wave, celebr_wave, fade_samples)
full = cls._crossfade(bc, breakdown_wave, fade_samples) full = cls._crossfade(bc, breakdown_wave, fade_samples)
@@ -151,36 +189,39 @@ else:
cls._play(full) cls._play(full)
@classmethod @classmethod
def play_start_sound(cls): def play_start_sound(cls) -> None:
freqs = [523.25, 659.26, 783.99, 880.00, 1046.50, 1174.66] freqs = [523.25, 659.26, 783.99, 880.00, 1046.50, 1174.66]
cls._prepare_and_play(freqs) cls._prepare_and_play(freqs)
@classmethod @classmethod
def play_finished_successfully_sound(cls): def play_finished_successfully_sound(cls) -> None:
freqs = [523.25, 587.33, 659.26, 783.99, 880.00, 987.77] freqs = [523.25, 587.33, 659.26, 783.99, 880.00, 987.77]
cls._prepare_and_play(freqs) cls._prepare_and_play(freqs)
@classmethod @classmethod
def play_finished_failed_sound(cls): def play_finished_failed_sound(cls) -> None:
freqs = [880.00, 830.61, 783.99, 659.26, 622.25, 523.25] freqs = [880.00, 830.61, 783.99, 659.26, 622.25, 523.25]
durations = [0.4, 0.3, 0.25, 0.25, 0.25, 0.25] durations = [0.4, 0.3, 0.25, 0.25, 0.25, 0.25]
cls._prepare_and_play(freqs, durations) cls._prepare_and_play(freqs, durations)
@classmethod @classmethod
def play_warning_sound(cls): def play_warning_sound(cls) -> None:
freqs = [700.00, 550.00, 750.00, 500.00, 800.00, 450.00] freqs = [700.00, 550.00, 750.00, 500.00, 800.00, 450.00]
cls._prepare_and_play(freqs) cls._prepare_and_play(freqs)
@classmethod @classmethod
def _prepare_and_play(cls, freqs, durations=None): def _prepare_and_play(cls, freqs: list[float], durations: list[float] | None = None) -> None:
count = len(freqs) count = len(freqs)
if durations is None: if durations is None:
durations = [cls.max_length / count] * count durations = [cls.max_length / count] * count
else: else:
total = sum(durations) total = sum(durations)
durations = [d * cls.max_length / total for d in durations] durations = [d * cls.max_length / total for d in durations]
waves = [cls._generate_complex_wave(f, d) for f, d in zip(freqs, durations)] waves = [cls._generate_complex_wave(f, d) for f, d in zip(freqs, durations)]
cls._play(np.concatenate(waves)) cls._play(np.concatenate(waves))
except Exception:
warnings.warn("Sound support disabled: numpy or simpleaudio could not be imported", RuntimeWarning) except ImportError as exc:
warnings.warn(f"Sound support disabled: {exc}", RuntimeWarning)
Sound = DummySound Sound = DummySound

48
pyproject.toml Normal file
View File

@@ -0,0 +1,48 @@
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "infinito-nexus"
version = "0.0.0"
description = "Infinito.Nexus"
readme = "README.md"
requires-python = ">=3.10"
license = { file = "LICENSE.md" }
dependencies = [
"numpy",
"ansible",
"colorscheme-generator @ https://github.com/kevinveenbirkenbach/colorscheme-generator/archive/refs/tags/v0.3.0.zip",
"bcrypt",
"ruamel.yaml",
"PyYAML",
"tld",
"passlib",
"requests",
]
[project.optional-dependencies]
audio = [
"simpleaudio",
]
[tool.setuptools]
# Non-src layout: explicitly control packaged modules
packages = { find = { where = ["."], include = [
"cli*",
"filter_plugins*",
"lookup_plugins*",
"module_utils*",
"library*",
], exclude = [
"roles*",
"assets*",
"docs*",
"templates*",
"logs*",
"tasks*",
"tests*",
"__pycache__*",
] } }
include-package-data = true

View File

@@ -1,9 +0,0 @@
colorscheme-generator @ https://github.com/kevinveenbirkenbach/colorscheme-generator/archive/refs/tags/v0.3.0.zip
numpy
bcrypt
ruamel.yaml
tld
passlib
requests
ansible
pytest

View File

@@ -2,8 +2,3 @@ collections:
- name: kewlfft.aur - name: kewlfft.aur
- name: community.general - name: community.general
- name: hetzner.hcloud - name: hetzner.hcloud
yay:
- python-simpleaudio
- python-numpy
pacman:
- ansible

View File

@@ -1,29 +0,0 @@
# Installation Guide
1. **Navigate to the Docker Compose Directory**
Change into the directory where the Docker Compose files reside.
```bash
cd {{ PATH_DOCKER_COMPOSE_INSTANCES }}akaunting/
```
2. **Set Environment Variables**
Ensure timeouts are increased to handle long operations:
```bash
export COMPOSE_HTTP_TIMEOUT=600
export DOCKER_CLIENT_TIMEOUT=600
```
3. **Start Akaunting Service**
Run the setup command with the `AKAUNTING_SETUP` variable:
```bash
AKAUNTING_SETUP=true docker-compose -p akaunting up -d
```
4. **Finalizing Setup**
After verifying that the web interface works, restart services:
```bash
docker-compose down
docker-compose -p akaunting up -d
```
For further details, visit the [Akaunting Documentation](https://akaunting.com/) and the [Akaunting GitHub Repository](https://github.com/akaunting/docker).

View File

@@ -1,29 +0,0 @@
# Administration
## track docker container status
```bash
watch -n 2 "docker ps -a | grep peertube"
```
## clean rebuild
```bash
cd {{ PATH_DOCKER_COMPOSE_INSTANCES }}peertube/ &&
docker-compose down
docker volume rm peertube_assets peertube_config peertube_data peertube_database peertube_redis
docker-compose up -d
```
## access terminal
```bash
docker-compose exec -it application /bin/bash
```
## update config
```bash
apt update && apt install nano && nano ./config/default.yaml
```
## get root pasword
```bash
docker logs peertube-application-1 | grep -A1 root
```

View File

@@ -5,4 +5,4 @@
- name: "configure pgadmin servers" - name: "configure pgadmin servers"
include_tasks: configuration.yml include_tasks: configuration.yml
when: applications | get_app_conf(application_id, 'server_mode', True) | bool when: applications | get_app_conf(application_id, 'server_mode') | bool

View File

@@ -16,6 +16,8 @@
- name: "load docker, proxy for '{{ application_id }}'" - name: "load docker, proxy for '{{ application_id }}'"
include_role: include_role:
name: sys-stk-full-stateless name: sys-stk-full-stateless
vars:
application_id: "web-app-sphinx"
# Hack because it wasn't possible to fix an handler bug in pkgmgr install # Hack because it wasn't possible to fix an handler bug in pkgmgr install
- name: „Trigger“ docker compose up - name: „Trigger“ docker compose up

View File

@@ -234,8 +234,8 @@ class TestRunAnsiblePlaybook(unittest.TestCase):
"Expected 'make messy-build' when skip_build=False", "Expected 'make messy-build' when skip_build=False",
) )
self.assertTrue( self.assertTrue(
any(call == ["make", "messy-test"] for call in calls), any(call == ["make", "test-messy"] for call in calls),
"Expected 'make messy-test' when skip_tests=False", "Expected 'make test-messy' when skip_tests=False",
) )
self.assertTrue( self.assertTrue(
any( any(
@@ -330,7 +330,7 @@ class TestRunAnsiblePlaybook(unittest.TestCase):
# No cleanup, no build, no tests, no inventory validation # No cleanup, no build, no tests, no inventory validation
self.assertFalse(any(call == ["make", "clean"] for call in calls)) self.assertFalse(any(call == ["make", "clean"] for call in calls))
self.assertFalse(any(call == ["make", "messy-build"] for call in calls)) self.assertFalse(any(call == ["make", "messy-build"] for call in calls))
self.assertFalse(any(call == ["make", "messy-test"] for call in calls)) self.assertFalse(any(call == ["make", "test-messy"] for call in calls))
self.assertFalse( self.assertFalse(
any( any(
isinstance(call, list) isinstance(call, list)

View File

@@ -10,7 +10,7 @@ import subprocess
class TestGenerateDefaultApplications(unittest.TestCase): class TestGenerateDefaultApplications(unittest.TestCase):
def setUp(self): def setUp(self):
# Path to the generator script under test # Path to the generator script under test
self.script_path = Path(__file__).resolve().parents[5] / "cli" / "build" / "defaults" / "applications.py" self.script_path = Path(__file__).resolve().parents[4] / "cli" / "setup" / "applications.py"
# Create temp role structure # Create temp role structure
self.temp_dir = Path(tempfile.mkdtemp()) self.temp_dir = Path(tempfile.mkdtemp())
self.roles_dir = self.temp_dir / "roles" self.roles_dir = self.temp_dir / "roles"
@@ -32,7 +32,7 @@ class TestGenerateDefaultApplications(unittest.TestCase):
shutil.rmtree(self.temp_dir) shutil.rmtree(self.temp_dir)
def test_script_generates_expected_yaml(self): def test_script_generates_expected_yaml(self):
script_path = Path(__file__).resolve().parent.parent.parent.parent.parent.parent / "cli/build/defaults/applications.py" script_path = Path(__file__).resolve().parent.parent.parent.parent.parent.parent / "cli/setup/applications.py"
result = subprocess.run( result = subprocess.run(
[ [

View File

@@ -45,7 +45,7 @@ class TestGenerateDefaultApplicationsUsers(unittest.TestCase):
When a users.yml exists with defined users, the script should inject a 'users' When a users.yml exists with defined users, the script should inject a 'users'
mapping in the generated YAML, mapping each username to a Jinja2 reference. mapping in the generated YAML, mapping each username to a Jinja2 reference.
""" """
script_path = Path(__file__).resolve().parents[5] / "cli" / "build/defaults/applications.py" script_path = Path(__file__).resolve().parents[4] / "cli" / "setup/applications.py"
result = subprocess.run([ result = subprocess.run([
"python3", str(script_path), "python3", str(script_path),
"--roles-dir", str(self.roles_dir), "--roles-dir", str(self.roles_dir),

View File

@@ -7,7 +7,7 @@ import yaml
from collections import OrderedDict from collections import OrderedDict
# Add cli/ to import path # Add cli/ to import path
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../..", "cli/build/defaults/"))) sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../..", "cli/setup/")))
import users import users
@@ -159,7 +159,7 @@ class TestGenerateUsers(unittest.TestCase):
out_file = tmpdir / "users.yml" out_file = tmpdir / "users.yml"
# Resolve script path like in other tests (relative to repo root) # Resolve script path like in other tests (relative to repo root)
script_path = Path(__file__).resolve().parents[5] / "cli" / "build" / "defaults" / "users.py" script_path = Path(__file__).resolve().parents[4] / "cli" / "setup" / "users.py"
# Run generator # Run generator
result = subprocess.run( result = subprocess.run(
@@ -215,7 +215,7 @@ class TestGenerateUsers(unittest.TestCase):
yaml.safe_dump({"users": users_map}, f) yaml.safe_dump({"users": users_map}, f)
out_file = tmpdir / "users.yml" out_file = tmpdir / "users.yml"
script_path = Path(__file__).resolve().parents[5] / "cli" / "build" / "defaults" / "users.py" script_path = Path(__file__).resolve().parents[5] / "cli" / "setup" / "users.py"
# First run # First run
r1 = subprocess.run( r1 = subprocess.run(
@@ -303,7 +303,7 @@ class TestGenerateUsers(unittest.TestCase):
) )
out_file = tmpdir / "users.yml" out_file = tmpdir / "users.yml"
script_path = Path(__file__).resolve().parents[5] / "cli" / "build" / "defaults" / "users.py" script_path = Path(__file__).resolve().parents[5] / "cli" / "setup" / "users.py"
result = subprocess.run( result = subprocess.run(
[ [

View File

@@ -69,7 +69,7 @@ class TestMainHelpers(unittest.TestCase):
""" """
available = [ available = [
(None, "deploy"), (None, "deploy"),
("build/defaults", "users"), ("setup", "users"),
] ]
main.show_full_help_for_all("/fake/cli", available) main.show_full_help_for_all("/fake/cli", available)