news-digest: per-instance cron schedule via env
Hardcoded crontab → render at container start from DIGEST_CRON_AM + DIGEST_CRON_PM. Defaults match the original 0800 / 2000 so existing deploys are no-ops. scripts/add-digest-user.sh learns --am and --pm flags so each teammate's stack can fire on their hours: scripts/add-digest-user.sh bob --am "0 6 * * *" --pm "0 17 * * *" scripts/add-digest-user.sh carol --pm "30 18 * * 1-5" # weekdays only Standard 5-field cron syntax; busybox crond honors the container's \$TZ. Removed the now-unused stacks/news-digest/crontab file and the matching COPY in the Dockerfile.
This commit is contained in:
@@ -23,8 +23,46 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
USER_ARG="${1:?usage: $0 <username> [password]}"
|
usage() {
|
||||||
USER_PASS="${2:-}"
|
cat <<EOF
|
||||||
|
usage: $0 <username> [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
|
HOST=ana-docker
|
||||||
WORKSTATION_STACK="$(dirname "$0")/../stacks/news-digest"
|
WORKSTATION_STACK="$(dirname "$0")/../stacks/news-digest"
|
||||||
PROJECT="digest-$USER_ARG"
|
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
|
# Also need the build context (Dockerfile, *.py, templates/) so the
|
||||||
# image can build if it's not already cached. Use the existing tree.
|
# 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/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"
|
ssh "$HOST" "mkdir -p $HOST_COMPOSE_DIR/templates"
|
||||||
scp -q "$WORKSTATION_STACK"/templates/* "$HOST:$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_LOCAL_CATEGORY=Local
|
||||||
DIGEST_MINIFLUX_HEADLINES_HOURS=8
|
DIGEST_MINIFLUX_HEADLINES_HOURS=8
|
||||||
DIGEST_MINIFLUX_HEADLINES_MAX=15
|
DIGEST_MINIFLUX_HEADLINES_MAX=15
|
||||||
|
DIGEST_CRON_AM=$CRON_AM
|
||||||
|
DIGEST_CRON_PM=$CRON_PM
|
||||||
NEWS_DIGEST_OUTPUT_DIR=$HOST_DATA_DIR
|
NEWS_DIGEST_OUTPUT_DIR=$HOST_DATA_DIR
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@@ -166,6 +206,7 @@ bold "✓ provisioned digest for $USER_ARG"
|
|||||||
echo
|
echo
|
||||||
echo " digest URL : http://10.250.50.70:$new_port/"
|
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 " 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 " compose dir : $HOST:$HOST_COMPOSE_DIR/"
|
||||||
echo " output dir : $HOST:$HOST_DATA_DIR/"
|
echo " output dir : $HOST:$HOST_DATA_DIR/"
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -60,6 +60,12 @@ DIGEST_MINIFLUX_LOCAL_CATEGORY=Local
|
|||||||
DIGEST_MINIFLUX_HEADLINES_HOURS=8
|
DIGEST_MINIFLUX_HEADLINES_HOURS=8
|
||||||
DIGEST_MINIFLUX_HEADLINES_MAX=15
|
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) ────────────────────────────────
|
# ── output dir on host (bind-mounted) ────────────────────────────────
|
||||||
# Separate from /opt/docker/conf/<stack>/ to keep generated content
|
# Separate from /opt/docker/conf/<stack>/ to keep generated content
|
||||||
# distinct from config. Owned by container UID; writes are atomic.
|
# distinct from config. Owned by container UID; writes are atomic.
|
||||||
|
|||||||
@@ -40,7 +40,10 @@ COPY web.py /app/web.py
|
|||||||
COPY templates /app/templates
|
COPY templates /app/templates
|
||||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
COPY run-digest.sh /usr/local/bin/run-digest.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
|
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/run-digest.sh
|
||||||
|
|
||||||
# Sentinel + first-run output dir
|
# Sentinel + first-run output dir
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ services:
|
|||||||
- DIGEST_MINIFLUX_LOCAL_CATEGORY=${DIGEST_MINIFLUX_LOCAL_CATEGORY:-Local}
|
- DIGEST_MINIFLUX_LOCAL_CATEGORY=${DIGEST_MINIFLUX_LOCAL_CATEGORY:-Local}
|
||||||
- DIGEST_MINIFLUX_HEADLINES_HOURS=${DIGEST_MINIFLUX_HEADLINES_HOURS:-8}
|
- DIGEST_MINIFLUX_HEADLINES_HOURS=${DIGEST_MINIFLUX_HEADLINES_HOURS:-8}
|
||||||
- DIGEST_MINIFLUX_HEADLINES_MAX=${DIGEST_MINIFLUX_HEADLINES_MAX:-15}
|
- 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:
|
volumes:
|
||||||
- ${NEWS_DIGEST_OUTPUT_DIR}:/output
|
- ${NEWS_DIGEST_OUTPUT_DIR}:/output
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -29,8 +29,21 @@ if [ ! -f /output/index.html ]; then
|
|||||||
echo "[entrypoint] first run failed; cron will retry on schedule"
|
echo "[entrypoint] first run failed; cron will retry on schedule"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Tee crontab into the place busybox expects (already done in image
|
# Render the crontab from env so each per-user instance can fire on
|
||||||
# via COPY) and run crond in foreground. -L /dev/stdout sends cron
|
# its own schedule. Defaults match the original singleton (0800 / 2000
|
||||||
# stdout/stderr to docker logs.
|
# local). Two separate vars (AM / PM) instead of one combined string
|
||||||
echo "[entrypoint] starting crond (fires per /etc/crontabs/root)"
|
# 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
|
exec crond -f -L /dev/stdout -l 8
|
||||||
|
|||||||
Reference in New Issue
Block a user