This commit integrates support for pushing and pulling Docker images from a container registry and enhances the Makefile for a more flexible development workflow.
Key changes include:
- **Container Registry Configuration:** Added and variables to the Makefile and updated and targets to correctly tag and push images to the configured registry.
- **Docker Compose Registry Integration:** Modified to use the image from the registry by default instead of building locally.
- **Makefile Workflow Enhancements:**
- Introduced a target (Pulling image git.wlkns.org/stephan/audio-engine-hub:latest from registry...
docker pull git.wlkns.org/stephan/audio-engine-hub:latest) for explicitly downloading images from the registry.
- Modified the target (Starting development environment by pulling image from registry...
export IMAGE_NAME=audio-engine-hub && \
export REGISTRY=git.wlkns.org && \
export USERNAME=stephan && \
export TAG=latest && \
docker compose up -d) to use the registry image.
- Added a target (Starting development environment (local build) on port 8000...) for local development, which explicitly builds the image from source () before starting the service.
- Updated and GNU bash, Version 5.1.16(1)-release (x86_64-pc-linux-gnu)
Diese Shellkommandos sind intern definiert. Geben Sie »help« ein, um diese
Liste zu sehen. Geben Sie »help Name« ein, um die Beschreibung der Funktion
»Name« zu sehen. Geben Sie »info bash« ein, um die vollständige Dokumentation
zu sehen. Geben Sie »man -k« oder »info« ein, um detaillierte Beschreibungen
der Shellkommandos zu sehen.
Ein Stern (*) neben dem Namen kennzeichnet deaktivierte Kommandos.
Jobbezeichnung [&] history [-c] [-d Offset] [n] oder history -anrw [Dateiname] oder histor>
(( Ausdruck )) if Kommandos; then Kommandos; [ elif Kommandos; then Kommandos; ]... [ >
. Dateiname [Argumente] jobs [-lnprs] [Jobbezeichnung ...] or jobs -x Kommando [Arg]
: kill [-s Signalname | -n Signalnummer | -Signalname] pid | jobspec ... >
[ Argument... ] let Argument [Argument ...]
[[ Ausdruck ]] local [Option] Name[=Wert] ...
alias [-p] [Name[=Wert] ... ] logout [n]
bg [Jobbezeichnung ...] mapfile [-d Begrenzer] [-n Anzahl] [-O Quelle] [-s Anzahl] [-t] [-u fd]>
bind [-lpsvPSVX] [-m Tastaturtabelle] [-f Dateiname] [-q Name] [-u Name]> popd [-n] [+N | -N]
break [n] printf [-v var] Format [Argumente]
builtin [Shell-Kommando [Argument ...]] pushd [-n] [+N | -N | Verzeichnis]
caller [Ausdruck] pwd [-LP]
case Wort in [Muster [| Muster]...) Kommandos ;;]... esac read [-ers] [-a Feld] [-d Begrenzer] [-i Text] [-n Zeichenanzahl] [-N Z>
cd [-L|[-P [-e]] [-@]] [Verzeichnis] readarray [-d Begrenzer] [-n Anzahl] [-O Quelle] [-s Anzahl] [-t] >
command [-pVv] Kommando [Argument ...] readonly [-aAf] [Name[=Wert] ...] oder readonly -p
compgen [-abcdefgjksuv] [-o option] [-A action] [-G globpat] [-W wordlis> return [n]
complete [-abcdefgjksuv] [-pr] [-DEI] [-o option] [-A action] [-G globpa> select Name [in Wortliste ... ;] do Kommandos; done
compopt [-o|+o Option] [-DEI] [Name ...] set [-abefhkmnptuvxBCHP] [-o Option] [--] [Argument ...]
continue [n] shift [n]
coproc [Name] Kommando [Umleitungen] shopt [-pqsu] [-o] [Optionsname ...]
declare [-aAfFgiIlnrtux] [-p] [Name[=Wert] …] source Dateiname [Argumente]
dirs [-clpv] [+N] [-N] suspend [-f]
disown [-h] [-ar] [Jobbezeichnung ... | pid ...] test [Ausdruck]
echo [-neE] [Argument ...] time [-p] Pipeline
enable [-a] [-dnps] [-f Dateiname] [Name ...] times
eval [Argument ...] trap [-lp] [[Argument] Signalbezeichnung ...]
exec [-cl] [-a Name] [Befehl [Argument …]] [Umleitung …] true
exit [n] type [-afptP] Name [Name ...]
export [-fn] [Name[=Wert] ...] oder export -p typeset [-aAfFgiIlnrtux] [-p] Name[=Wert] …
false ulimit [-SHabcdefiklmnpqrstuvxPT] [Grenze]
fc [-e Editor] [-lnr] [Anfang] [Ende] oder fc -s [Muster=Ersetzung] [Kom> umask [-p] [-S] [Modus]
fg [Jobbezeichnung] unalias [-a] Name [Name ...]
for Name [in Wortliste ... ] ; do Kommandos; done unset [-f] [-v] [-n] [NAME ...]
for (( Ausdruck1; Ausdruck2; Ausdruck3 )); do Kommandos; done until Kommandos; do Kommandos; done
function Name { Kommandos ; } oder Name () { Kommandos ; } variables - Namen und Bedeutung einiger Shell-Variablen
getopts optstring name [arg ...] wait [-fn] [-p var] [id ...]
hash [-lr] [-p Pfadname] [-dt] [Name ...] while Kommandos; do Kommandos; done
help [-dms] [Muster ...] { Kommandos ; } targets accordingly.
- **Session Resume Update:** Updated to document the container registry integration.
9.7 KiB
Session Resumee - AudioEngineHub Project Refactoring
Date: Donnerstag, 4. Dezember 2025
Objective: Refactor the AudioEngineHub project to follow best practices for containerization, error handling, and project structure, based on a provided guide.md document.
Initial Project State & Overview
The project was a modular FastAPI-based TTS server with a TTSEngineBase interface. Key initial findings:
piperwas functional.stylettsandchatttswere dummy implementations.f5_ttswas implemented but inactive.- Configuration was scattered and hardcoded.
- Error handling was basic.
- Tests (
pytestandunittest) existed but were limited and inconsistent. - No containerization strategy was in place, leading to potential dependency hell.
Refactoring Phase 1: Robustness & Configuration
-
Centralized Configuration:
- Replaced hardcoded values with
pydantic-settingsfor.envfile management. config.pycreated (later moved toapp/config.py).IMAGE_NAMEinMakefilewas also user-configurable.requirements.txtwas updated withpydantic-settings.docker-compose.ymlwas updated to use.env.
- Replaced hardcoded values with
-
Configurable Engines Feature:
- Implemented dynamic
ENGINE_REGISTRYloading based onACTIVE_ENGINESsetting in.env. - Allows easy activation/deactivation of TTS engines.
- Implemented dynamic
-
Robust Error Handling:
- Implemented comprehensive input validation in the
/ttsendpoint (checking engine, model, speaker existence). - Added dependency checks (e.g.,
ffmpeg,piperexecutables) to engines, reportingHTTP 503for unavailable engines. - Secured
tempfile.mktempusage by replacing it withtempfile.NamedTemporaryFile. - Wrapped synthesis logic in
try-exceptblocks to catch and propagate engine-specific errors asHTTP 500.
- Implemented comprehensive input validation in the
-
Asynchronous Operations:
- Changed
TTSEngineBase.synthesizeandselftesttoasync. - Refactored all concrete engine implementations (
piper,f5_tts,styletts,chattts) to useasync defmethods. - Updated
app/main.py's/ttsendpoint to beasyncand useawaitfor engine calls andasyncio.gatherfor concurrent chunk synthesis. - Wrapped blocking I/O (file ops,
ffmpeg) and CPU-bound tasks inasyncio.to_thread. - Updated
tests/test_f5_tts.pyto correctlyawaitasync calls.
- Changed
Refactoring Phase 2: Containerization & Workflow (Based on guide.md)
-
Integrated
Makefile:- Created a
Makefilewith targets forbuild,run,stop,logs,shell,up,down,test,tag,push,clean,help. - Included robust shell functions for port checking (
check_traefik_ports_free,check_app_port_free). - Set
SHELL := /bin/bashinMakefileto ensure correct shell interpretation. - Ensured
PYTHONPATH=$(PWD)is set formake test.
- Created a
-
Adopted Multi-Stage
Dockerfile:- Implemented a multi-stage
Dockerfile(builder/runner stages). builderstage creates a Python virtual environment and installsrequirements.txt(includinggunicorn).runnerstage usespython:3.11-slim, installs runtime system dependencies (ffmpeg), creates an unprivilegedappuser, and sets the productionCMDtogunicornwithuvicornworkers.
- Implemented a multi-stage
-
Refactored Project Structure (
app/package):- Created an
app/directory. - Moved
main.py,config.py,engines/,models/,utils/intoapp/. - Created
app/__init__.py. - Updated all Python import paths (
from app.config import settings,from app.engines.piper import PiperEngine, etc.). - Updated internal references in engine files (e.g.,
model_dir,voices_dir).
- Created an
-
Refined
docker-compose.ymlwith Traefik:- Integrated
traefikservice for dynamic reverse proxying during local development. - Modified
appservice with Traefiklabelsand connected both services to awebnetwork. - Adjusted Docker
volumesmounts to match the newapp/structure (e.g.,./models:/home/appuser/app/models). - Updated
appservicecommandfor Uvicorn hot-reloading in dev.
- Integrated
-
Centralized Testing Workflow:
- Removed
test_run.sh. - Integrated
make testfor runningpytest --cov=. app/ tests/.
- Removed
-
Dedicated Documentation:
- Created
docs/directory. - Moved
guide.mdtodocs/guide.md.
- Created
Verification & Troubleshooting
- Tests: All unit/integration tests (
make test) are passing. - Local Run (Virtual Env): Initial local runs (
python app/main.py) failed due toModuleNotFoundError(fixed bypython -m app.main) andPermissionError(fixed by needing to mock or redirectsettings.AUDIO_CACHE_DIRfor local direct execution, but not strictly needed for successful app execution throughuvicorn). The current approach is to verify in Docker. - Docker Compose:
- Initial
make upfailures were due to an outdateddocker-composeclient (1.29.2) and later,Makefilesyntax issues (fixed by settingSHELL := /bin/bashand fixing macros). docker-composewas eventually updated to thedocker composeCLI plugin (v5.0.0).make upcommand finally succeeded in bringing up containers.- The
curl http://localhost/healthcommand returned404 page not found. This indicates a potential routing issue with Traefik or the application not being responsive on the expected path within the container. (This is the last unresolved issue).
- Initial
Final Debugging & Resolution (Post-Refactoring)
After the major refactoring, the service was unable to start and was returning 404 errors. The final debugging session addressed these issues.
-
FastAPI App Startup Fix:
- Problem: The Uvicorn server was failing with
Attribute "app" not found in module "app.main". - Solution: The
appinstance was only created inside theif __name__ == "__main__"block. Movedapp = create_app()to the module's global scope inapp/main.pyso Uvicorn could find it.
- Problem: The Uvicorn server was failing with
-
Traefik & Docker Compose Issues:
- Problem: The initial
docker-compose.ymlincluded a Traefik service for reverse proxying, which was causing multiple issues (invalid container names due to missing.envvariables, Docker client API version errors). The user also clarified that they use an existing reverse proxy and did not want a new Traefik container deployed. - Solution: Removed the
traefikservice entirely fromdocker-compose.yml. Theappservice's port was exposed directly to the host.
- Problem: The initial
-
Model Loading Failure:
- Problem: The
piperengine was not loading any models, returning an empty list and causing/ttsrequests to fail with a422 Unprocessable Entityerror. - Diagnosis: The root cause was an incorrect volume mount. The
docker-compose.ymlwas attempting to mount a non-existent, empty./modelsdirectory from the host. The actual models were located in./app/models. - Solution:
- Corrected the
docker-compose.ymlvolume mount to point to the correct source directory:- ./app/models:/models. - Updated the
app/engines/piper.pycode to use the absolute path/models/piper/to look for models inside the container, making the configuration more robust and independent of the working directory.
- Corrected the
- Problem: The
-
Developer Experience (DX) Improvements:
- Automatic Port Finding: The
Makefilewas enhanced. Themake upandmake runcommands now automatically find a free port starting from 8000, preventing port conflicts. - Health Check Command: A
make health-checktarget was added. It runs a sanity check against the deployed container's/healthendpoint to verify that the service is up and all engines are reporting an "ok" status. - Documentation:
- The
README.mdwas completely rewritten to provide a clear and up-to-date guide for getting started, usage, and availablemakecommands. - A note was added to
docs/guide.mdto clarify that it describes an older, more advanced setup and to point readers to the newREADME.md.
- The
- Automatic Port Finding: The
Final Status: The service is now fully deployable via make up and passes make health-check. All identified startup issues and bugs have been resolved.
Container Registry Integration
To facilitate pushing and pulling Docker images from a remote registry (e.g., git.wlkns.org), the Makefile and docker-compose.yml were updated:
-
Makefile Configuration:
- Added
REGISTRY := git.wlkns.organdUSERNAME := stephanvariables. - Modified the
tagtarget to tag images in the format$(REGISTRY)/$(USERNAME)/$(IMAGE_NAME):$(TAG). - Modified the
pushtarget to depend onbuildandtag, then push the image to the configured registry.
- Added
-
docker-compose.ymlIntegration:- Changed the
appservice definition to useimage: ${REGISTRY}/${USERNAME}/${IMAGE_NAME}:${TAG}instead ofbuild: ., making it pull from the registry by default.
- Changed the
-
Makefile Workflow Enhancements:
- Added a
pulltarget (make pull) to explicitly download the image from the registry. - The
uptarget (make up) was modified to start the service using the image specified indocker-compose.yml(which now points to the registry). - A new
dev-uptarget (make dev-up) was introduced for local development, which explicitly builds the image from source (docker compose up --build -d) before starting the service. - The
downandhelptargets were updated accordingly.
- Added a
This allows for flexible deployment, supporting both local development with on-demand building and production-like environments pulling from a registry.