From e0bc04770b3433b8eb5074e9af29cf6a8bbef25f Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 01:27:56 +0200 Subject: [PATCH 01/25] feat: manual reload --- src/karl/api/v1.py | 9 ++++++++- src/karl/model/vcs.py | 7 +++++++ src/karl/model/webhook.py | 3 +++ src/karl/services/reload.py | 29 +++++++++++++++++++++++++++++ src/karl/services/vcs.py | 7 +++++++ 5 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 src/karl/model/vcs.py create mode 100644 src/karl/services/reload.py diff --git a/src/karl/api/v1.py b/src/karl/api/v1.py index de85719..2a88c18 100644 --- a/src/karl/api/v1.py +++ b/src/karl/api/v1.py @@ -9,7 +9,7 @@ from starlette.responses import JSONResponse, Response from karl.api.models import Request from karl.core.injects import AutowireSupport from karl.core.woodpecker import Woodpecker -from karl.model.webhook import WoodpeckerEvent +from karl.model.webhook import WoodpeckerEvent, ReloadEvent router = APIRouter() logger = logging.getLogger(__name__) @@ -39,3 +39,10 @@ class APIv1: async def ci(self, request: Request): await self.bus.dispatch(mapper.map(request)) return Response(status_code=201) + + @router.post("/reload", summary="Manual service reload") + async def reload(self, service: str = None): + if service is None: + return Response(status_code=400) + await self.bus.dispatch(ReloadEvent(service=service)) + return Response(status_code=201) diff --git a/src/karl/model/vcs.py b/src/karl/model/vcs.py new file mode 100644 index 0000000..80800bc --- /dev/null +++ b/src/karl/model/vcs.py @@ -0,0 +1,7 @@ +from dataclasses import dataclass + + +@dataclass +class Head: + sha: str + branch: str diff --git a/src/karl/model/webhook.py b/src/karl/model/webhook.py index 6c941ae..5b44d46 100644 --- a/src/karl/model/webhook.py +++ b/src/karl/model/webhook.py @@ -10,3 +10,6 @@ class WoodpeckerEvent(BaseEvent): message: str started: int files: List[str] + +class ReloadEvent(BaseEvent): + service: str diff --git a/src/karl/services/reload.py b/src/karl/services/reload.py new file mode 100644 index 0000000..28997aa --- /dev/null +++ b/src/karl/services/reload.py @@ -0,0 +1,29 @@ +from datetime import datetime +from typing import Annotated + +from bubus import EventBus +from injectable import injectable, autowired, Autowired + +from model.webhook import ReloadEvent, WoodpeckerEvent +from services import GitService + + +@injectable(singleton=True) +class ReloadService: + + @autowired + def __init__(self, bus: Annotated[EventBus, Autowired]): + self._bus = bus + self._bus.on(ReloadEvent, self.on_reload) + self._git = GitService() + + def on_reload(self, event: ReloadEvent): + head = self._git.get_head() + self._bus.dispatch(WoodpeckerEvent( + _id=-1, + commit=head.sha, + ref=head.branch, + message=f"Manual reload of {event.service}", + started=int(datetime.now().timestamp()), + files=[f"compose/{event.service}/docker-compose.yml"] + )) diff --git a/src/karl/services/vcs.py b/src/karl/services/vcs.py index e89836d..8f63c19 100644 --- a/src/karl/services/vcs.py +++ b/src/karl/services/vcs.py @@ -2,6 +2,7 @@ from git import Repo, Remote from injectable import injectable from karl.config import GitConfig, get_settings +from model.vcs import Head @injectable(singleton=True) @@ -27,3 +28,9 @@ class GitService: def checkout(self, sha: str): self._origin.fetch() self._repo.git.checkout(sha) + + def get_head(self) -> Head: + return Head( + self._repo.active_branch.commit.hexsha, + self._repo.active_branch.name + ) From 0e19df5c3e2763b13c37ee327b8e88f457f69244 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 01:40:34 +0200 Subject: [PATCH 02/25] fix: settings location --- .gitignore | 2 +- src/karl/config/settings.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 52364cd..06c7873 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,6 @@ uv.lock **/*.kdbx* .compose_repository -__pycache__/ +deployment/ **/dist/ **/*.log diff --git a/src/karl/config/settings.py b/src/karl/config/settings.py index 79e66c9..8e20b51 100644 --- a/src/karl/config/settings.py +++ b/src/karl/config/settings.py @@ -51,4 +51,8 @@ class Settings(BaseSettings): @lru_cache def get_settings() -> Settings: - return Settings.from_yaml() + paths = ['deployment/config.yaml', 'config/config.yaml'] + for path in paths: + if Path(path).exists(): + return Settings.from_yaml(path) + raise Exception("Config file not found") From 604381348abc23d2498cb7f813cc941ae539dc4b Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 01:48:42 +0200 Subject: [PATCH 03/25] fix: logging in reload --- src/karl/services/reload.py | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/karl/services/reload.py b/src/karl/services/reload.py index 28997aa..c28b8be 100644 --- a/src/karl/services/reload.py +++ b/src/karl/services/reload.py @@ -1,3 +1,4 @@ +import logging from datetime import datetime from typing import Annotated @@ -7,23 +8,26 @@ from injectable import injectable, autowired, Autowired from model.webhook import ReloadEvent, WoodpeckerEvent from services import GitService +logger = logging.getLogger(__name__) + @injectable(singleton=True) class ReloadService: - @autowired - def __init__(self, bus: Annotated[EventBus, Autowired]): - self._bus = bus - self._bus.on(ReloadEvent, self.on_reload) - self._git = GitService() + @autowired + def __init__(self, bus: Annotated[EventBus, Autowired]): + self._bus = bus + self._bus.on(ReloadEvent, self.on_reload) + self._git = GitService() - def on_reload(self, event: ReloadEvent): - head = self._git.get_head() - self._bus.dispatch(WoodpeckerEvent( - _id=-1, - commit=head.sha, - ref=head.branch, - message=f"Manual reload of {event.service}", - started=int(datetime.now().timestamp()), - files=[f"compose/{event.service}/docker-compose.yml"] - )) + async def on_reload(self, event: ReloadEvent): + logger.info(f"Received ReloadEvent: {event.service}") + head = self._git.get_head() + await self._bus.dispatch(WoodpeckerEvent( + _id=-1, + commit=head.sha, + ref=head.branch, + message=f"Manual reload of {event.service}", + started=int(datetime.now().timestamp()), + files=[f"compose/{event.service}/docker-compose.yml"] + )) From 8f4dc486ac469c067a58f62e104b5b572d4fdb18 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 01:54:43 +0200 Subject: [PATCH 04/25] fix: inject ReloadService to API class --- src/karl/api/v1.py | 2 ++ src/karl/core/injects.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/karl/api/v1.py b/src/karl/api/v1.py index 2a88c18..60e9f1c 100644 --- a/src/karl/api/v1.py +++ b/src/karl/api/v1.py @@ -10,6 +10,7 @@ from karl.api.models import Request from karl.core.injects import AutowireSupport from karl.core.woodpecker import Woodpecker from karl.model.webhook import WoodpeckerEvent, ReloadEvent +from services.reload import ReloadService router = APIRouter() logger = logging.getLogger(__name__) @@ -21,6 +22,7 @@ async def root(): @cbv(router) class APIv1: + reload: ReloadService = Depends(AutowireSupport.reload) woodpecker: Woodpecker = Depends(AutowireSupport.woodpecker) bus: EventBus = Depends(AutowireSupport.bus) diff --git a/src/karl/core/injects.py b/src/karl/core/injects.py index 34a6280..dd23516 100644 --- a/src/karl/core/injects.py +++ b/src/karl/core/injects.py @@ -2,6 +2,7 @@ from bubus import EventBus from injectable import inject from karl.core.woodpecker import Woodpecker +from services.reload import ReloadService class AutowireSupport: @@ -10,6 +11,10 @@ class AutowireSupport: def woodpecker(): return inject(Woodpecker) + @staticmethod + def reload(): + return inject(ReloadService) + @staticmethod def bus(): return inject(EventBus) From 6ee6341a5e2539555b4f21e5bb3c25017f027221 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 02:03:31 +0200 Subject: [PATCH 05/25] fix: annotations --- src/karl/api/v1.py | 4 ++-- src/karl/services/reload.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/karl/api/v1.py b/src/karl/api/v1.py index 60e9f1c..28d8515 100644 --- a/src/karl/api/v1.py +++ b/src/karl/api/v1.py @@ -42,8 +42,8 @@ class APIv1: await self.bus.dispatch(mapper.map(request)) return Response(status_code=201) - @router.post("/reload", summary="Manual service reload") - async def reload(self, service: str = None): + @router.get("/reload", summary="Manual service reload") + async def reload(self, service: str = None) -> Response: if service is None: return Response(status_code=400) await self.bus.dispatch(ReloadEvent(service=service)) diff --git a/src/karl/services/reload.py b/src/karl/services/reload.py index c28b8be..6c3b28d 100644 --- a/src/karl/services/reload.py +++ b/src/karl/services/reload.py @@ -17,8 +17,8 @@ class ReloadService: @autowired def __init__(self, bus: Annotated[EventBus, Autowired]): self._bus = bus - self._bus.on(ReloadEvent, self.on_reload) self._git = GitService() + bus.on(ReloadEvent, self.on_reload) async def on_reload(self, event: ReloadEvent): logger.info(f"Received ReloadEvent: {event.service}") From 05931329a30a360edeb743e45e2631840bd40d27 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 02:06:23 +0200 Subject: [PATCH 06/25] fix: moved ReloadService to core, fixed import --- src/karl/api/v1.py | 4 ++-- src/karl/{services => core}/reload.py | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename src/karl/{services => core}/reload.py (100%) diff --git a/src/karl/api/v1.py b/src/karl/api/v1.py index 28d8515..e32db0b 100644 --- a/src/karl/api/v1.py +++ b/src/karl/api/v1.py @@ -8,9 +8,9 @@ from starlette.responses import JSONResponse, Response from karl.api.models import Request from karl.core.injects import AutowireSupport +from karl.core.reload import ReloadService from karl.core.woodpecker import Woodpecker from karl.model.webhook import WoodpeckerEvent, ReloadEvent -from services.reload import ReloadService router = APIRouter() logger = logging.getLogger(__name__) @@ -22,8 +22,8 @@ async def root(): @cbv(router) class APIv1: - reload: ReloadService = Depends(AutowireSupport.reload) woodpecker: Woodpecker = Depends(AutowireSupport.woodpecker) + reload: ReloadService = Depends(AutowireSupport.reload) bus: EventBus = Depends(AutowireSupport.bus) def __init__(self): diff --git a/src/karl/services/reload.py b/src/karl/core/reload.py similarity index 100% rename from src/karl/services/reload.py rename to src/karl/core/reload.py From dcba20ee944933e47a8f264b29033b19b7de179e Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 02:07:16 +0200 Subject: [PATCH 07/25] fix: fixed import in injects --- src/karl/core/injects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/karl/core/injects.py b/src/karl/core/injects.py index dd23516..b52451b 100644 --- a/src/karl/core/injects.py +++ b/src/karl/core/injects.py @@ -1,8 +1,8 @@ from bubus import EventBus from injectable import inject +from karl.core.reload import ReloadService from karl.core.woodpecker import Woodpecker -from services.reload import ReloadService class AutowireSupport: From cd62e256cfb93007cdf57099606cfc9a950badd2 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 02:11:12 +0200 Subject: [PATCH 08/25] fix: api annotations --- src/karl/api/v1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/karl/api/v1.py b/src/karl/api/v1.py index e32db0b..65139ff 100644 --- a/src/karl/api/v1.py +++ b/src/karl/api/v1.py @@ -42,7 +42,7 @@ class APIv1: await self.bus.dispatch(mapper.map(request)) return Response(status_code=201) - @router.get("/reload", summary="Manual service reload") + @router.get("/reload", summary="Manual service reload", response_model=Response) async def reload(self, service: str = None) -> Response: if service is None: return Response(status_code=400) From 59c5844f5b61eeef5adb5b30a689535abba37c1a Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 02:12:22 +0200 Subject: [PATCH 09/25] fix: api annotations again --- src/karl/api/v1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/karl/api/v1.py b/src/karl/api/v1.py index 65139ff..39123de 100644 --- a/src/karl/api/v1.py +++ b/src/karl/api/v1.py @@ -42,7 +42,7 @@ class APIv1: await self.bus.dispatch(mapper.map(request)) return Response(status_code=201) - @router.get("/reload", summary="Manual service reload", response_model=Response) + @router.get("/reload", summary="Manual service reload", response_model=None) async def reload(self, service: str = None) -> Response: if service is None: return Response(status_code=400) From a50df466d01f53e4892bf4795f3228447e973576 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 02:27:31 +0200 Subject: [PATCH 10/25] fix: moved services initialization to main --- src/karl/api/v1.py | 6 +----- src/karl/core/reload.py | 1 + src/karl/main.py | 8 +++++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/karl/api/v1.py b/src/karl/api/v1.py index 39123de..8a529ab 100644 --- a/src/karl/api/v1.py +++ b/src/karl/api/v1.py @@ -8,8 +8,6 @@ from starlette.responses import JSONResponse, Response from karl.api.models import Request from karl.core.injects import AutowireSupport -from karl.core.reload import ReloadService -from karl.core.woodpecker import Woodpecker from karl.model.webhook import WoodpeckerEvent, ReloadEvent router = APIRouter() @@ -22,8 +20,6 @@ async def root(): @cbv(router) class APIv1: - woodpecker: Woodpecker = Depends(AutowireSupport.woodpecker) - reload: ReloadService = Depends(AutowireSupport.reload) bus: EventBus = Depends(AutowireSupport.bus) def __init__(self): @@ -42,7 +38,7 @@ class APIv1: await self.bus.dispatch(mapper.map(request)) return Response(status_code=201) - @router.get("/reload", summary="Manual service reload", response_model=None) + @router.get("/reload", summary="Manual service reload") async def reload(self, service: str = None) -> Response: if service is None: return Response(status_code=400) diff --git a/src/karl/core/reload.py b/src/karl/core/reload.py index 6c3b28d..4a8bcbd 100644 --- a/src/karl/core/reload.py +++ b/src/karl/core/reload.py @@ -19,6 +19,7 @@ class ReloadService: self._bus = bus self._git = GitService() bus.on(ReloadEvent, self.on_reload) + logger.info("ReloadService initialized.") async def on_reload(self, event: ReloadEvent): logger.info(f"Received ReloadEvent: {event.service}") diff --git a/src/karl/main.py b/src/karl/main.py index fc75ec3..25029ac 100644 --- a/src/karl/main.py +++ b/src/karl/main.py @@ -3,6 +3,7 @@ import logging from fastapi import FastAPI from injectable import load_injection_container +from core.injects import AutowireSupport from karl.config import get_settings from karl.util.logging import HandlerFactory @@ -15,7 +16,7 @@ class KarlApplication: _app = FastAPI(title="Karl", version="0.1.0") self._set_middlewares(_app) self._set_routes(_app) - self._set_events(_app) + self._init_services(_app) self._app = _app @@ -55,8 +56,9 @@ class KarlApplication: app.include_router(api_v1_router, prefix="/api/v1", tags=["v1"]) pass - def _set_events(self, app: FastAPI): - pass + def _init_services(self, app: FastAPI): + AutowireSupport.reload() + AutowireSupport.woodpecker() def run(): From da1292a45f9346c5bd164c177a69b392b9d94dc9 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 02:46:15 +0200 Subject: [PATCH 11/25] fix: detached head check --- src/karl/services/vcs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/karl/services/vcs.py b/src/karl/services/vcs.py index 8f63c19..0d70139 100644 --- a/src/karl/services/vcs.py +++ b/src/karl/services/vcs.py @@ -30,6 +30,8 @@ class GitService: self._repo.git.checkout(sha) def get_head(self) -> Head: + if self._repo.head.is_detached: + return Head(self._repo.head.object.hexsha, "detached") return Head( self._repo.active_branch.commit.hexsha, self._repo.active_branch.name From 4b8ea49c0ca7292fd4a97e885302cadadca9c48d Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 03:12:31 +0200 Subject: [PATCH 12/25] fix: force .mo. reload in reload --- src/karl/core/reload.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/karl/core/reload.py b/src/karl/core/reload.py index 4a8bcbd..5af7151 100644 --- a/src/karl/core/reload.py +++ b/src/karl/core/reload.py @@ -1,5 +1,6 @@ import logging from datetime import datetime +from pathlib import Path from typing import Annotated from bubus import EventBus @@ -24,11 +25,12 @@ class ReloadService: async def on_reload(self, event: ReloadEvent): logger.info(f"Received ReloadEvent: {event.service}") head = self._git.get_head() + mos = Path('files/forge').glob('*.mo.*') await self._bus.dispatch(WoodpeckerEvent( _id=-1, commit=head.sha, ref=head.branch, message=f"Manual reload of {event.service}", started=int(datetime.now().timestamp()), - files=[f"compose/{event.service}/docker-compose.yml"] + files=[f"compose/{event.service}/docker-compose.yml"] + [str(pp) for pp in list(mos)] )) From 9a8bc44e0996aba065ad01dd230a3b79da3ebf14 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 03:16:26 +0200 Subject: [PATCH 13/25] fix: verbosity in ReloadService --- src/karl/core/reload.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/karl/core/reload.py b/src/karl/core/reload.py index 5af7151..c8c9785 100644 --- a/src/karl/core/reload.py +++ b/src/karl/core/reload.py @@ -25,12 +25,14 @@ class ReloadService: async def on_reload(self, event: ReloadEvent): logger.info(f"Received ReloadEvent: {event.service}") head = self._git.get_head() - mos = Path('files/forge').glob('*.mo.*') - await self._bus.dispatch(WoodpeckerEvent( + mos = Path(f"files/{event.service}").glob('*.mo.*') + event = WoodpeckerEvent( _id=-1, commit=head.sha, ref=head.branch, message=f"Manual reload of {event.service}", started=int(datetime.now().timestamp()), files=[f"compose/{event.service}/docker-compose.yml"] + [str(pp) for pp in list(mos)] - )) + ) + logger.trace(f"Sending WoodpeckerEvent: {event}") + await self._bus.dispatch(event) From 7fa243a8d4276595572fa546690c1179805669e9 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 03:19:07 +0200 Subject: [PATCH 14/25] fix: repr WoodpeckerEvent --- src/karl/core/reload.py | 4 ++-- src/karl/model/webhook.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/karl/core/reload.py b/src/karl/core/reload.py index c8c9785..58e119a 100644 --- a/src/karl/core/reload.py +++ b/src/karl/core/reload.py @@ -26,7 +26,7 @@ class ReloadService: logger.info(f"Received ReloadEvent: {event.service}") head = self._git.get_head() mos = Path(f"files/{event.service}").glob('*.mo.*') - event = WoodpeckerEvent( + we = WoodpeckerEvent( _id=-1, commit=head.sha, ref=head.branch, @@ -34,5 +34,5 @@ class ReloadService: started=int(datetime.now().timestamp()), files=[f"compose/{event.service}/docker-compose.yml"] + [str(pp) for pp in list(mos)] ) - logger.trace(f"Sending WoodpeckerEvent: {event}") + logger.trace(f"Sending WoodpeckerEvent: {we}") await self._bus.dispatch(event) diff --git a/src/karl/model/webhook.py b/src/karl/model/webhook.py index 5b44d46..abfea20 100644 --- a/src/karl/model/webhook.py +++ b/src/karl/model/webhook.py @@ -11,5 +11,8 @@ class WoodpeckerEvent(BaseEvent): started: int files: List[str] + def __repr__(self): + return f"" + class ReloadEvent(BaseEvent): service: str From 8da4bc4aaf44c586e4515e10897ba3bd51aff89e Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 03:21:49 +0200 Subject: [PATCH 15/25] fix: repr WoodpeckerEvent? --- src/karl/core/reload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/karl/core/reload.py b/src/karl/core/reload.py index 58e119a..43628be 100644 --- a/src/karl/core/reload.py +++ b/src/karl/core/reload.py @@ -34,5 +34,5 @@ class ReloadService: started=int(datetime.now().timestamp()), files=[f"compose/{event.service}/docker-compose.yml"] + [str(pp) for pp in list(mos)] ) - logger.trace(f"Sending WoodpeckerEvent: {we}") + logger.trace(f"Sending WoodpeckerEvent: {str(we)}") await self._bus.dispatch(event) From 7234560d1639b57158f91927cfeeeffe08a2ee44 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 03:24:04 +0200 Subject: [PATCH 16/25] fix: repr WoodpeckerEvent! --- src/karl/core/reload.py | 4 ++-- src/karl/model/webhook.py | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/karl/core/reload.py b/src/karl/core/reload.py index 43628be..e14e2ea 100644 --- a/src/karl/core/reload.py +++ b/src/karl/core/reload.py @@ -34,5 +34,5 @@ class ReloadService: started=int(datetime.now().timestamp()), files=[f"compose/{event.service}/docker-compose.yml"] + [str(pp) for pp in list(mos)] ) - logger.trace(f"Sending WoodpeckerEvent: {str(we)}") - await self._bus.dispatch(event) + logger.trace(f"Sending " - class ReloadEvent(BaseEvent): service: str From 239e51f0f8c099d7f3f1d4cf87ad30c8452b4668 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 03:26:17 +0200 Subject: [PATCH 17/25] fix: repr WoodpeckerEvent?! --- src/karl/core/reload.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/karl/core/reload.py b/src/karl/core/reload.py index e14e2ea..9880bad 100644 --- a/src/karl/core/reload.py +++ b/src/karl/core/reload.py @@ -23,16 +23,19 @@ class ReloadService: logger.info("ReloadService initialized.") async def on_reload(self, event: ReloadEvent): - logger.info(f"Received ReloadEvent: {event.service}") - head = self._git.get_head() - mos = Path(f"files/{event.service}").glob('*.mo.*') - we = WoodpeckerEvent( - _id=-1, - commit=head.sha, - ref=head.branch, - message=f"Manual reload of {event.service}", - started=int(datetime.now().timestamp()), - files=[f"compose/{event.service}/docker-compose.yml"] + [str(pp) for pp in list(mos)] - ) - logger.trace(f"Sending Date: Tue, 14 Apr 2026 03:27:44 +0200 Subject: [PATCH 18/25] fix: repr WoodpeckerEvent?? --- src/karl/core/reload.py | 2 +- src/karl/model/webhook.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/karl/core/reload.py b/src/karl/core/reload.py index 9880bad..4b5b047 100644 --- a/src/karl/core/reload.py +++ b/src/karl/core/reload.py @@ -35,7 +35,7 @@ class ReloadService: started=int(datetime.now().timestamp()), files=[f"compose/{event.service}/docker-compose.yml"] + [str(pp) for pp in list(mos)] ) - logger.debug(f"Sending Date: Tue, 14 Apr 2026 03:29:06 +0200 Subject: [PATCH 19/25] fix: repr WoodpeckerEvent??!! --- src/karl/core/reload.py | 2 +- src/karl/model/webhook.py | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/karl/core/reload.py b/src/karl/core/reload.py index 4b5b047..ea6b32a 100644 --- a/src/karl/core/reload.py +++ b/src/karl/core/reload.py @@ -35,7 +35,7 @@ class ReloadService: started=int(datetime.now().timestamp()), files=[f"compose/{event.service}/docker-compose.yml"] + [str(pp) for pp in list(mos)] ) - logger.debug(f"Sending Date: Tue, 14 Apr 2026 03:32:50 +0200 Subject: [PATCH 20/25] fix: repr WoodpeckerEvent?!?! --- src/karl/core/reload.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/karl/core/reload.py b/src/karl/core/reload.py index ea6b32a..5e0e36c 100644 --- a/src/karl/core/reload.py +++ b/src/karl/core/reload.py @@ -26,14 +26,15 @@ class ReloadService: try: logger.info(f"Received ReloadEvent: {event.service}") head = self._git.get_head() - mos = Path(f"files/{event.service}").glob('*.mo.*') + mos = list(Path(f"files/{event.service}").glob('*.mo.*')) + logger.debug(f"Found {len(mos)} .mo files") we = WoodpeckerEvent( _id=-1, commit=head.sha, ref=head.branch, message=f"Manual reload of {event.service}", started=int(datetime.now().timestamp()), - files=[f"compose/{event.service}/docker-compose.yml"] + [str(pp) for pp in list(mos)] + files=[f"compose/{event.service}/docker-compose.yml"] + [str(pp) for pp in mos] ) logger.debug(f"Sending Date: Tue, 14 Apr 2026 03:37:16 +0200 Subject: [PATCH 21/25] fix: Just another verbosity improvement --- src/karl/core/reload.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/karl/core/reload.py b/src/karl/core/reload.py index 5e0e36c..19d87cf 100644 --- a/src/karl/core/reload.py +++ b/src/karl/core/reload.py @@ -26,7 +26,11 @@ class ReloadService: try: logger.info(f"Received ReloadEvent: {event.service}") head = self._git.get_head() - mos = list(Path(f"files/{event.service}").glob('*.mo.*')) + file_path = Path(f"files/{event.service}") + if not file_path.exists(): + raise Exception(f"Service {event.service} not found.") + logger.debug(f"Found service files at {file_path}: {', '.join([str(f) for f in list(file_path.iterdir())])}") + mos = list(file_path.glob('*.mo.*')) logger.debug(f"Found {len(mos)} .mo files") we = WoodpeckerEvent( _id=-1, From bc592a2c6c73312fce2bf2a79c95ff18b6c1c4fe Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 03:38:18 +0200 Subject: [PATCH 22/25] fix: Just another verbosity improvement --- src/karl/core/reload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/karl/core/reload.py b/src/karl/core/reload.py index 19d87cf..a8c0838 100644 --- a/src/karl/core/reload.py +++ b/src/karl/core/reload.py @@ -28,7 +28,7 @@ class ReloadService: head = self._git.get_head() file_path = Path(f"files/{event.service}") if not file_path.exists(): - raise Exception(f"Service {event.service} not found.") + raise Exception(f"Service {event.service} not found: {file_path.absolute()} does not exist.") logger.debug(f"Found service files at {file_path}: {', '.join([str(f) for f in list(file_path.iterdir())])}") mos = list(file_path.glob('*.mo.*')) logger.debug(f"Found {len(mos)} .mo files") From 3224f8cfca61046bdab8f106071cb7d018a04671 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 03:41:44 +0200 Subject: [PATCH 23/25] fix: Absolute path fix --- src/karl/core/reload.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/karl/core/reload.py b/src/karl/core/reload.py index a8c0838..9478150 100644 --- a/src/karl/core/reload.py +++ b/src/karl/core/reload.py @@ -6,6 +6,7 @@ from typing import Annotated from bubus import EventBus from injectable import injectable, autowired, Autowired +from karl import get_settings from model.webhook import ReloadEvent, WoodpeckerEvent from services import GitService @@ -20,13 +21,14 @@ class ReloadService: self._bus = bus self._git = GitService() bus.on(ReloadEvent, self.on_reload) + self.root_path = get_settings().git.path logger.info("ReloadService initialized.") async def on_reload(self, event: ReloadEvent): try: logger.info(f"Received ReloadEvent: {event.service}") head = self._git.get_head() - file_path = Path(f"files/{event.service}") + file_path = Path(self.root_path) / f"files/{event.service}" if not file_path.exists(): raise Exception(f"Service {event.service} not found: {file_path.absolute()} does not exist.") logger.debug(f"Found service files at {file_path}: {', '.join([str(f) for f in list(file_path.iterdir())])}") From ce74860ca37b5fb95609e7940d2764cdf357c857 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 14 Apr 2026 03:44:13 +0200 Subject: [PATCH 24/25] fix: Another path fix --- src/karl/core/reload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/karl/core/reload.py b/src/karl/core/reload.py index 9478150..f3975c4 100644 --- a/src/karl/core/reload.py +++ b/src/karl/core/reload.py @@ -40,7 +40,7 @@ class ReloadService: ref=head.branch, message=f"Manual reload of {event.service}", started=int(datetime.now().timestamp()), - files=[f"compose/{event.service}/docker-compose.yml"] + [str(pp) for pp in mos] + files=[f"compose/{event.service}/docker-compose.yml"] + [str(pp).replace(str(self.root_path), '') for pp in mos] ) logger.debug(f"Sending Date: Tue, 14 Apr 2026 03:46:05 +0200 Subject: [PATCH 25/25] fix: Detach composes --- src/karl/services/containers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/karl/services/containers.py b/src/karl/services/containers.py index cc7cf56..96b2c45 100644 --- a/src/karl/services/containers.py +++ b/src/karl/services/containers.py @@ -17,4 +17,4 @@ class DockerService: os.chdir(compose_path.parent) self._client.compose.ps() self._client.compose.down(remove_orphans=True) - self._client.compose.up() + self._client.compose.up(detach=True)