|
| 1 | +#!/bin/sh |
| 2 | +# cron-entrypoint.sh — container entrypoint for the odek image. |
| 3 | +# |
| 4 | +# If a crontab is mounted, start supercronic in the background, then hand the |
| 5 | +# container over to the real odek command (serve / telegram / run / …) via |
| 6 | +# `exec` so odek remains the main process: signals, graceful restart, and the |
| 7 | +# Telegram singleton lock all behave exactly as they did before this wrapper. |
| 8 | +# |
| 9 | +# supercronic inherits THIS process's environment and passes it to every cron |
| 10 | +# job, so a scheduled `odek run --deliver` sees the same env_file vars |
| 11 | +# (ODEK_API_KEY, ODEK_TELEGRAM_BOT_TOKEN, ODEK_TELEGRAM_DEFAULT_CHAT_ID, …) |
| 12 | +# that the bot does. That is the whole reason for using supercronic over the |
| 13 | +# classic crond, which scrubs the environment from its jobs. |
| 14 | +set -eu |
| 15 | + |
| 16 | +# Path to the crontab. Overridable so an operator can relocate the mount. |
| 17 | +CRONTAB="${ODEK_CRONTAB:-/home/odek/.crontabs/crontab}" |
| 18 | + |
| 19 | +# Graceful-restart support: odek's /restart command re-execs via this script |
| 20 | +# (see ODEK_ENTRYPOINT below). Kill any supercronic from the previous run so we |
| 21 | +# never end up with two instances scheduling the same crontab concurrently. |
| 22 | +if [ -n "${ODEK_SUPERCRONIC_PID:-}" ]; then |
| 23 | + kill "$ODEK_SUPERCRONIC_PID" 2>/dev/null || true |
| 24 | + unset ODEK_SUPERCRONIC_PID |
| 25 | +fi |
| 26 | + |
| 27 | +if [ -d "$CRONTAB" ]; then |
| 28 | + # Docker creates a directory when the bind-mount source doesn't exist on the |
| 29 | + # host. This is almost always a misconfiguration — warn loudly rather than |
| 30 | + # silently skipping so the operator knows why reminders aren't firing. |
| 31 | + echo "cron-entrypoint: WARNING: $CRONTAB is a directory, not a file" >&2 |
| 32 | + echo "cron-entrypoint: Docker created it because the host path was missing." >&2 |
| 33 | + echo "cron-entrypoint: Fix: remove the directory on the host and create the file." >&2 |
| 34 | + echo "cron-entrypoint: Skipping supercronic — cron jobs will NOT run." >&2 |
| 35 | +elif [ -f "$CRONTAB" ]; then |
| 36 | + echo "cron-entrypoint: starting supercronic for $CRONTAB" >&2 |
| 37 | + # -passthrough-logs keeps each job's own stdout/stderr intact in the |
| 38 | + # container log alongside supercronic's scheduling lines. |
| 39 | + supercronic -passthrough-logs "$CRONTAB" & |
| 40 | + ODEK_SUPERCRONIC_PID=$! |
| 41 | + export ODEK_SUPERCRONIC_PID |
| 42 | + # Brief liveness check: supercronic parses the crontab at startup and exits |
| 43 | + # immediately on a syntax error or missing binary. Neither is recoverable at |
| 44 | + # runtime, so catching it here produces a clear warning rather than silent |
| 45 | + # non-delivery. set -e does not cover backgrounded processes, so we check |
| 46 | + # explicitly after a short window. |
| 47 | + sleep 1 |
| 48 | + if ! kill -0 "$ODEK_SUPERCRONIC_PID" 2>/dev/null; then |
| 49 | + echo "cron-entrypoint: WARNING: supercronic exited immediately — cron jobs will NOT run" >&2 |
| 50 | + unset ODEK_SUPERCRONIC_PID |
| 51 | + fi |
| 52 | +else |
| 53 | + echo "cron-entrypoint: no crontab at $CRONTAB — skipping supercronic" >&2 |
| 54 | +fi |
| 55 | + |
| 56 | +# Advertise this script's own path so spawnChild (odek's /restart handler) can |
| 57 | +# re-exec through the wrapper instead of the bare binary. Without this, a |
| 58 | +# graceful restart would skip supercronic entirely. |
| 59 | +export ODEK_ENTRYPOINT="$0" |
| 60 | + |
| 61 | +# Default to printing usage if no command was provided (matches the previous |
| 62 | +# `ENTRYPOINT ["odek"]` behaviour for a bare `docker run`). |
| 63 | +exec odek "$@" |
0 commit comments