mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2026-03-08 17:37:26 -04:00
feat: refactor Dockerfile and supervisord configuration to remove cron and add periodic sync script
This commit is contained in:
@@ -41,7 +41,7 @@ ENV PYTHONDONTWRITEBYTECODE=1
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install runtime dependencies (including GDAL and cron)
|
||||
# Install runtime dependencies (including GDAL)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
postgresql-client \
|
||||
gdal-bin \
|
||||
@@ -49,7 +49,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
nginx \
|
||||
memcached \
|
||||
supervisor \
|
||||
cron \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy Python packages from builder
|
||||
@@ -61,12 +60,8 @@ COPY ./server /code/
|
||||
COPY ./nginx.conf /etc/nginx/nginx.conf
|
||||
COPY ./supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
COPY ./entrypoint.sh /code/entrypoint.sh
|
||||
COPY ./crontab /etc/cron.d/adventurelog-cron
|
||||
RUN chmod +x /code/entrypoint.sh \
|
||||
&& mkdir -p /code/static /code/media \
|
||||
&& chmod 0644 /etc/cron.d/adventurelog-cron \
|
||||
&& crontab /etc/cron.d/adventurelog-cron \
|
||||
&& touch /var/log/cron.log
|
||||
&& mkdir -p /code/static /code/media
|
||||
|
||||
# Collect static files
|
||||
RUN python3 manage.py collectstatic --noinput --verbosity 2
|
||||
@@ -74,5 +69,8 @@ RUN python3 manage.py collectstatic --noinput --verbosity 2
|
||||
# Expose ports
|
||||
EXPOSE 80 8000
|
||||
|
||||
# Start Supervisor
|
||||
# Start with an entrypoint that runs init tasks then starts supervisord
|
||||
ENTRYPOINT ["/code/entrypoint.sh"]
|
||||
|
||||
# Start supervisord to manage processes
|
||||
CMD ["supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# AdventureLog cron jobs
|
||||
SHELL=/bin/sh
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
# Run sync_visited_regions daily at midnight
|
||||
0 0 * * * cd /code && /usr/local/bin/python3 manage.py sync_visited_regions >> /var/log/cron.log 2>&1
|
||||
|
||||
# Empty line required at end of cron file
|
||||
@@ -84,8 +84,4 @@ fi
|
||||
|
||||
cat /code/adventurelog.txt
|
||||
|
||||
# Start Gunicorn in foreground
|
||||
exec gunicorn main.wsgi:application \
|
||||
--bind [::]:8000 \
|
||||
--workers 2 \
|
||||
--timeout 120
|
||||
exec "$@"
|
||||
105
backend/server/run_periodic_sync.py
Normal file
105
backend/server/run_periodic_sync.py
Normal file
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Periodic sync runner for AdventureLog.
|
||||
Runs sync_visited_regions management command every 60 seconds.
|
||||
Managed by supervisord to ensure it inherits container environment variables.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import signal
|
||||
import threading
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
# Setup Django
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'main.settings')
|
||||
|
||||
import django
|
||||
django.setup()
|
||||
|
||||
from django.core.management import call_command
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [%(levelname)s] %(message)s',
|
||||
handlers=[logging.StreamHandler(sys.stdout)]
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
INTERVAL_SECONDS = 60
|
||||
|
||||
# Event used to signal shutdown from signal handlers
|
||||
_stop_event = threading.Event()
|
||||
|
||||
|
||||
def _seconds_until_next_midnight() -> float:
|
||||
"""Return number of seconds until the next local midnight."""
|
||||
now = datetime.now()
|
||||
next_midnight = (now + timedelta(days=1)).replace(
|
||||
hour=0, minute=0, second=0, microsecond=0
|
||||
)
|
||||
return (next_midnight - now).total_seconds()
|
||||
|
||||
|
||||
def _handle_termination(signum, frame):
|
||||
"""Signal handler for SIGTERM and SIGINT: request graceful shutdown."""
|
||||
logger.info(f"Received signal {signum}; shutting down gracefully...")
|
||||
_stop_event.set()
|
||||
|
||||
|
||||
def run_sync():
|
||||
"""Run the sync_visited_regions command."""
|
||||
try:
|
||||
logger.info("Running sync_visited_regions...")
|
||||
call_command('sync_visited_regions')
|
||||
logger.info("Sync completed successfully")
|
||||
except Exception as e:
|
||||
logger.error(f"Sync failed: {e}", exc_info=True)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main loop - run sync every INTERVAL_SECONDS."""
|
||||
logger.info(f"Starting periodic sync (interval: {INTERVAL_SECONDS}s)")
|
||||
|
||||
# Install signal handlers so supervisord (or other process managers)
|
||||
# can request a clean shutdown using SIGTERM/SIGINT.
|
||||
signal.signal(signal.SIGTERM, _handle_termination)
|
||||
signal.signal(signal.SIGINT, _handle_termination)
|
||||
|
||||
try:
|
||||
while not _stop_event.is_set():
|
||||
# Wait until the next local midnight (or until shutdown)
|
||||
wait_seconds = _seconds_until_next_midnight()
|
||||
hours = wait_seconds / 3600.0
|
||||
logger.info(
|
||||
f"Next sync scheduled in {wait_seconds:.0f}s (~{hours:.2f}h) at local midnight"
|
||||
)
|
||||
# Sleep until midnight or until stop event is set
|
||||
if _stop_event.wait(wait_seconds):
|
||||
break
|
||||
|
||||
# It's midnight (or we woke up), run the sync once
|
||||
run_sync()
|
||||
|
||||
# After running at midnight, loop continues to compute next midnight
|
||||
except Exception:
|
||||
logger.exception("Unexpected error in periodic sync loop")
|
||||
finally:
|
||||
logger.info("Periodic sync worker exiting")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
# Fallback in case the signal is delivered as KeyboardInterrupt
|
||||
logger.info("KeyboardInterrupt received — exiting")
|
||||
_stop_event.set()
|
||||
except SystemExit:
|
||||
logger.info("SystemExit received — exiting")
|
||||
finally:
|
||||
logger.info("run_periodic_sync terminated")
|
||||
@@ -8,7 +8,8 @@ stdout_logfile=/dev/stdout
|
||||
stderr_logfile=/dev/stderr
|
||||
|
||||
[program:gunicorn]
|
||||
command=/code/entrypoint.sh
|
||||
command=/usr/local/bin/gunicorn main.wsgi:application --bind [::]:8000 --workers 2 --timeout 120
|
||||
directory=/code
|
||||
autorestart=true
|
||||
stdout_logfile=/dev/stdout
|
||||
stderr_logfile=/dev/stderr
|
||||
@@ -23,8 +24,9 @@ stderr_logfile=/dev/stderr
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:cron]
|
||||
command=cron -f
|
||||
[program:sync_visited_regions]
|
||||
command=/usr/local/bin/python3 /code/run_periodic_sync.py
|
||||
directory=/code
|
||||
autorestart=true
|
||||
stdout_logfile=/dev/stdout
|
||||
stderr_logfile=/dev/stderr
|
||||
|
||||
Reference in New Issue
Block a user