110 lines
3.6 KiB
Python
110 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Watch for code changes and restart a target container.
|
|
|
|
This script watches one or more directories (set via the `WATCH_PATHS`
|
|
environment variable, comma-separated) and restarts the container named by
|
|
`TARGET_CONTAINER` (defaults to `ur_gia`) when a filesystem change is detected.
|
|
|
|
Typical usage in this repo (examples are provided in `docker-compose.yml`):
|
|
|
|
- To restart the runtime router (UR) when core code changes set:
|
|
WATCH_PATHS=/code/core
|
|
TARGET_CONTAINER=ur_gia
|
|
|
|
- To restart the scheduling command when app code changes set:
|
|
WATCH_PATHS=/code/app
|
|
TARGET_CONTAINER=scheduling_gia
|
|
|
|
If you need to force a restart manually (for example to refresh a running
|
|
management command), you can "touch" any file under the watched path:
|
|
|
|
docker compose exec <service> sh -c 'touch /code/core/__restart__'
|
|
|
|
The watcher ignores `__pycache__`, `.pyc` files and `.git` paths.
|
|
"""
|
|
import os
|
|
import subprocess
|
|
import time
|
|
|
|
from watchdog.events import FileSystemEventHandler
|
|
from watchdog.observers import Observer
|
|
|
|
|
|
class ChangeHandler(FileSystemEventHandler):
|
|
def __init__(self):
|
|
self.last_restart = 0
|
|
self.restart_debounce = 2 # seconds
|
|
|
|
def on_modified(self, event):
|
|
self._check_and_restart(event.src_path)
|
|
|
|
def on_created(self, event):
|
|
self._check_and_restart(event.src_path)
|
|
|
|
def on_deleted(self, event):
|
|
self._check_and_restart(event.src_path)
|
|
|
|
def _check_and_restart(self, path):
|
|
# Ignore pycache and compiled files
|
|
if "__pycache__" in path or ".pyc" in path or ".git" in path:
|
|
return
|
|
|
|
now = time.time()
|
|
if now - self.last_restart < self.restart_debounce:
|
|
return
|
|
|
|
self.last_restart = now
|
|
print(f'[{time.strftime("%H:%M:%S")}] Change detected: {path}', flush=True)
|
|
time.sleep(1)
|
|
self._restart_ur()
|
|
|
|
def _restart_ur(self):
|
|
# Determine target container from environment (default `ur_gia`)
|
|
target = os.environ.get("TARGET_CONTAINER", "ur_gia")
|
|
print(f'[{time.strftime("%H:%M:%S")}] Restarting {target}...', flush=True)
|
|
# Try podman first (preferred in this setup), then docker
|
|
cmd = f"podman restart {target} 2>/dev/null || docker restart {target} 2>/dev/null"
|
|
result = subprocess.run(cmd, shell=True, capture_output=True)
|
|
if result.returncode == 0:
|
|
print(
|
|
f'[{time.strftime("%H:%M:%S")}] {target} restarted successfully',
|
|
flush=True,
|
|
)
|
|
else:
|
|
print(f'[{time.strftime("%H:%M:%S")}] {target} restart failed', flush=True)
|
|
time.sleep(1)
|
|
|
|
|
|
def main():
|
|
handler = ChangeHandler()
|
|
observer = Observer()
|
|
|
|
# Allow overriding watched paths via environment variable `WATCH_PATHS`.
|
|
# Default is `/code/core,/code/app` but you can set e.g. `WATCH_PATHS=/code/core`
|
|
watch_paths_env = os.environ.get("WATCH_PATHS", "/code/core,/code/app")
|
|
watch_paths = [p.strip() for p in watch_paths_env.split(",") if p.strip()]
|
|
for path in watch_paths:
|
|
if os.path.exists(path):
|
|
observer.schedule(handler, path, recursive=True)
|
|
print(f"Watching: {path}", flush=True)
|
|
else:
|
|
print(f"Not found (will not watch): {path}", flush=True)
|
|
|
|
observer.start()
|
|
print(
|
|
f'[{time.strftime("%H:%M:%S")}] File watcher started. Monitoring for changes...',
|
|
flush=True,
|
|
)
|
|
|
|
try:
|
|
while True:
|
|
time.sleep(1)
|
|
except KeyboardInterrupt:
|
|
observer.stop()
|
|
observer.join()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|