diff --git a/app/wsgi.py b/app/wsgi.py index f2c7150..4725324 100644 --- a/app/wsgi.py +++ b/app/wsgi.py @@ -14,3 +14,4 @@ from django.core.wsgi import get_wsgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") application = get_wsgi_application() + diff --git a/docker/uwsgi.ini b/docker/uwsgi.ini index 8869a1d..d846186 100644 --- a/docker/uwsgi.ini +++ b/docker/uwsgi.ini @@ -19,4 +19,9 @@ vacuum=1 home=/venv processes=4 threads=2 -log-level=debug \ No newline at end of file +log-level=debug + +# Autoreload on code changes (graceful reload) +py-autoreload=1 +py-autoreload-on-edit=/code/GIA/core +py-autoreload-on-edit=/code/GIA/app \ No newline at end of file diff --git a/docker/watch_and_restart.py b/docker/watch_and_restart.py new file mode 100644 index 0000000..efa2208 --- /dev/null +++ b/docker/watch_and_restart.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +""" +Watch for code changes in core/ and app/ and restart ur_gia container. +""" +import os +import sys +import time +import subprocess +from pathlib import Path +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler + + +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): + print(f'[{time.strftime("%H:%M:%S")}] Restarting ur_gia...', flush=True) + # Try podman first (preferred in this setup), then docker + result = subprocess.run( + 'podman restart ur_gia 2>/dev/null || docker restart ur_gia 2>/dev/null', + shell=True, + capture_output=True, + ) + if result.returncode == 0: + print(f'[{time.strftime("%H:%M:%S")}] ur_gia restarted successfully', flush=True) + else: + print(f'[{time.strftime("%H:%M:%S")}] ur_gia restart failed', flush=True) + time.sleep(1) + + +def main(): + handler = ChangeHandler() + observer = Observer() + + # Watch both core and app directories + watch_paths = ['/code/GIA/core', '/code/GIA/app'] + for path in watch_paths: + if os.path.exists(path): + observer.schedule(handler, path, recursive=True) + print(f'Watching: {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() diff --git a/docker/watch_simple.py b/docker/watch_simple.py new file mode 100644 index 0000000..eff42be --- /dev/null +++ b/docker/watch_simple.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +""" +Simple file watcher using stat() instead of watchdog (no external deps). +Watches core/ and app/ for changes and restarts ur_gia. +""" +import os +import sys +import time +import subprocess + + +def get_mtime(path): + """Recursively get the most recent mtime in a directory tree.""" + max_mtime = 0 + for root, dirs, files in os.walk(path): + # Skip pycache and hidden dirs + dirs[:] = [d for d in dirs if not d.startswith('.') and d != '__pycache__'] + for file in files: + if file.endswith(('.pyc', '.pyo')): + continue + try: + mtime = os.path.getmtime(os.path.join(root, file)) + max_mtime = max(max_mtime, mtime) + except OSError: + pass + return max_mtime + + +def restart_ur(): + """Restart ur_gia container.""" + print(f'[{time.strftime("%H:%M:%S")}] Restarting ur_gia...', flush=True) + result = subprocess.run( + 'podman restart ur_gia 2>/dev/null || docker restart ur_gia 2>/dev/null', + shell=True, + capture_output=True, + ) + if result.returncode == 0: + print(f'[{time.strftime("%H:%M:%S")}] ur_gia restarted', flush=True) + else: + print(f'[{time.strftime("%H:%M:%S")}] restart failed', flush=True) + + +def main(): + paths = ['/code/GIA/core', '/code/GIA/app'] + last_mtimes = {} + + for path in paths: + if os.path.exists(path): + print(f'Watching: {path}', flush=True) + last_mtimes[path] = get_mtime(path) + else: + print(f'Not found: {path}', flush=True) + + print(f'[{time.strftime("%H:%M:%S")}] Watcher started', flush=True) + restart_debounce = 0 + + try: + while True: + time.sleep(2) + restart_debounce -= 2 + + for path in paths: + if not os.path.exists(path): + continue + current_mtime = get_mtime(path) + if current_mtime > last_mtimes.get(path, 0): + print(f'[{time.strftime("%H:%M:%S")}] Changes in {path}', flush=True) + last_mtimes[path] = current_mtime + if restart_debounce <= 0: + restart_ur() + restart_debounce = 5 # Don't restart more than every 5s + except KeyboardInterrupt: + print('Watcher stopped', flush=True) + sys.exit(0) + + +if __name__ == '__main__': + main()