diff --git a/scripts/add-digest-user.sh b/scripts/add-digest-user.sh index fbf906d..7750c5d 100755 --- a/scripts/add-digest-user.sh +++ b/scripts/add-digest-user.sh @@ -23,8 +23,46 @@ set -euo pipefail -USER_ARG="${1:?usage: $0 [password]}" -USER_PASS="${2:-}" +usage() { + cat < [password] [--am "MIN HR * * *"] [--pm "MIN HR * * *"] + +Defaults: AM = "0 8 * * *", PM = "0 20 * * *". Cron syntax is +standard 5-field; busybox crond honors the container's \$TZ. + +Examples: + $0 alice + $0 bob --am "0 6 * * *" --pm "0 17 * * *" + $0 carol s3cret --pm "30 18 * * 1-5" # weekdays only PM run +EOF + exit 1 +} + +USER_ARG="" +USER_PASS="" +CRON_AM="0 8 * * *" +CRON_PM="0 20 * * *" + +while [ "$#" -gt 0 ]; do + case "$1" in + --am) CRON_AM="$2"; shift 2 ;; + --pm) CRON_PM="$2"; shift 2 ;; + --help|-h) usage ;; + --*) echo "unknown flag: $1" >&2; usage ;; + *) + if [ -z "$USER_ARG" ]; then + USER_ARG="$1" + elif [ -z "$USER_PASS" ]; then + USER_PASS="$1" + else + echo "unexpected positional arg: $1" >&2; usage + fi + shift + ;; + esac +done +[ -n "$USER_ARG" ] || usage + HOST=ana-docker WORKSTATION_STACK="$(dirname "$0")/../stacks/news-digest" PROJECT="digest-$USER_ARG" @@ -102,7 +140,7 @@ scp -q "$WORKSTATION_STACK/compose.yaml" "$HOST:$HOST_COMPOSE_DIR/compose.yaml" # Also need the build context (Dockerfile, *.py, templates/) so the # image can build if it's not already cached. Use the existing tree. scp -q "$WORKSTATION_STACK/Dockerfile" "$HOST:$HOST_COMPOSE_DIR/Dockerfile" -scp -q "$WORKSTATION_STACK"/{digest.py,seed-headlines.py,web.py,entrypoint.sh,run-digest.sh,crontab} "$HOST:$HOST_COMPOSE_DIR/" +scp -q "$WORKSTATION_STACK"/{digest.py,seed-headlines.py,web.py,entrypoint.sh,run-digest.sh} "$HOST:$HOST_COMPOSE_DIR/" ssh "$HOST" "mkdir -p $HOST_COMPOSE_DIR/templates" scp -q "$WORKSTATION_STACK"/templates/* "$HOST:$HOST_COMPOSE_DIR/templates/" @@ -139,6 +177,8 @@ DIGEST_MINIFLUX_WORLD_CATEGORY=World DIGEST_MINIFLUX_LOCAL_CATEGORY=Local DIGEST_MINIFLUX_HEADLINES_HOURS=8 DIGEST_MINIFLUX_HEADLINES_MAX=15 +DIGEST_CRON_AM=$CRON_AM +DIGEST_CRON_PM=$CRON_PM NEWS_DIGEST_OUTPUT_DIR=$HOST_DATA_DIR EOF @@ -166,6 +206,7 @@ bold "✓ provisioned digest for $USER_ARG" echo echo " digest URL : http://10.250.50.70:$new_port/" echo " miniflux UI : http://10.250.50.70:8080/ (login: $USER_ARG / $USER_PASS)" +echo " schedule : AM '$CRON_AM' / PM '$CRON_PM' (TZ from \$NEWS_DIGEST_TZ)" echo " compose dir : $HOST:$HOST_COMPOSE_DIR/" echo " output dir : $HOST:$HOST_DATA_DIR/" echo diff --git a/stacks/news-digest/.env.example b/stacks/news-digest/.env.example index d5f46b0..0df5307 100644 --- a/stacks/news-digest/.env.example +++ b/stacks/news-digest/.env.example @@ -60,6 +60,12 @@ DIGEST_MINIFLUX_LOCAL_CATEGORY=Local DIGEST_MINIFLUX_HEADLINES_HOURS=8 DIGEST_MINIFLUX_HEADLINES_MAX=15 +# Cron schedule (standard 5-field). busybox crond honors $TZ above. +# Two fires per day by convention (morning / evening); change times +# per-user to match working hours. +DIGEST_CRON_AM=0 8 * * * +DIGEST_CRON_PM=0 20 * * * + # ── output dir on host (bind-mounted) ──────────────────────────────── # Separate from /opt/docker/conf// to keep generated content # distinct from config. Owned by container UID; writes are atomic. diff --git a/stacks/news-digest/Dockerfile b/stacks/news-digest/Dockerfile index 20dd153..72253b8 100644 --- a/stacks/news-digest/Dockerfile +++ b/stacks/news-digest/Dockerfile @@ -40,7 +40,10 @@ COPY web.py /app/web.py COPY templates /app/templates COPY entrypoint.sh /usr/local/bin/entrypoint.sh COPY run-digest.sh /usr/local/bin/run-digest.sh -COPY crontab /etc/crontabs/root +# /etc/crontabs/root is written by entrypoint.sh from +# DIGEST_CRON_AM/DIGEST_CRON_PM env at container start, so each +# per-user instance gets its own schedule. Image no longer ships a +# baked-in crontab. RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/run-digest.sh # Sentinel + first-run output dir diff --git a/stacks/news-digest/compose.yaml b/stacks/news-digest/compose.yaml index ed7f61d..6ee9df0 100644 --- a/stacks/news-digest/compose.yaml +++ b/stacks/news-digest/compose.yaml @@ -48,6 +48,10 @@ services: - DIGEST_MINIFLUX_LOCAL_CATEGORY=${DIGEST_MINIFLUX_LOCAL_CATEGORY:-Local} - DIGEST_MINIFLUX_HEADLINES_HOURS=${DIGEST_MINIFLUX_HEADLINES_HOURS:-8} - DIGEST_MINIFLUX_HEADLINES_MAX=${DIGEST_MINIFLUX_HEADLINES_MAX:-15} + # Per-instance cron schedule. Standard 5-field syntax. busybox + # crond honors $TZ. Defaults preserve the original 0800 / 2000. + - DIGEST_CRON_AM=${DIGEST_CRON_AM:-0 8 * * *} + - DIGEST_CRON_PM=${DIGEST_CRON_PM:-0 20 * * *} volumes: - ${NEWS_DIGEST_OUTPUT_DIR}:/output networks: diff --git a/stacks/news-digest/crontab b/stacks/news-digest/crontab deleted file mode 100644 index a30dce8..0000000 --- a/stacks/news-digest/crontab +++ /dev/null @@ -1,4 +0,0 @@ -# news-digest fires twice a day at 0800 and 2000 local (TZ from env). -# Output written atomically to /output/index.html — nginx serves it -# without restart. -0 8,20 * * * /usr/local/bin/run-digest.sh diff --git a/stacks/news-digest/entrypoint.sh b/stacks/news-digest/entrypoint.sh index c666632..8e80bf8 100644 --- a/stacks/news-digest/entrypoint.sh +++ b/stacks/news-digest/entrypoint.sh @@ -29,8 +29,21 @@ if [ ! -f /output/index.html ]; then echo "[entrypoint] first run failed; cron will retry on schedule" fi -# Tee crontab into the place busybox expects (already done in image -# via COPY) and run crond in foreground. -L /dev/stdout sends cron -# stdout/stderr to docker logs. -echo "[entrypoint] starting crond (fires per /etc/crontabs/root)" +# Render the crontab from env so each per-user instance can fire on +# its own schedule. Defaults match the original singleton (0800 / 2000 +# local). Two separate vars (AM / PM) instead of one combined string +# so users can tweak one fire without re-deriving the other. +CRON_AM="${DIGEST_CRON_AM:-0 8 * * *}" +CRON_PM="${DIGEST_CRON_PM:-0 20 * * *}" +mkdir -p /etc/crontabs +{ + echo "# news-digest cron — generated at container start from env." + echo "$CRON_AM /usr/local/bin/run-digest.sh" + echo "$CRON_PM /usr/local/bin/run-digest.sh" +} > /etc/crontabs/root +echo "[entrypoint] crontab:" +sed 's/^/ /' /etc/crontabs/root + +# Foreground crond. -L /dev/stdout sends cron stdout/stderr to docker logs. +echo "[entrypoint] starting crond" exec crond -f -L /dev/stdout -l 8