diff --git a/.env.example b/.env.example index 5d3c8ad..664a6cd 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,7 @@ # Copy to .env (which is gitignored) — `make env` generates one with fresh secrets. -# Both the systemd service and the reminder cron job read this file. -# -# Values must stay shell-safe and unquoted: the cron wrapper sources this file, so -# a secret containing ')' or '$' would break it. `make env` uses a URL-safe alphabet. +# Read by the systemd service (EnvironmentFile) and the reminder cron job +# (uv run --env-file), both of which want plain unquoted KEY=value lines. +# `make env` uses a URL-safe alphabet for the generated secrets. DJANGO_SECRET_KEY=change-me DJANGO_DEBUG=0 diff --git a/Makefile b/Makefile index 6622337..5a269cf 100644 --- a/Makefile +++ b/Makefile @@ -6,10 +6,13 @@ USER := $(shell id -un) PORT ?= 8000 SERVICE := osiris UNIT_FILE := /etc/systemd/system/$(SERVICE).service -REMINDER_SH := $(PROJECT_DIR)/deploy/send-reminders.sh CRON_SCHED ?= * * * * * CRON_LOG ?= $(PROJECT_DIR)/reminders.log MANAGE := $(UV) run python manage.py +# The reminder job as one line: cron's environment is nearly empty, so uv is +# called by absolute path and loads .env (VAPID keys) itself via --env-file. +# --no-sync: don't resolve dependencies (needs network) on a cron tick. +REMINDER_CMD := cd $(PROJECT_DIR) && $(UV) run --no-sync --env-file .env python manage.py send_reminders .PHONY: help help: ## Show this help @@ -88,24 +91,23 @@ service-uninstall: ## Stop and remove the systemd service # --- cron ------------------------------------------------------------------ -# The script path is the marker: it identifies our line without needing a literal -# '#' comment, which make would strip as a comment of its own. +# 'send[-_]reminders' is the marker identifying our line, so reinstalling stays +# idempotent and also cleans up entries from the old wrapper-script era. .PHONY: cron -cron: ## Install the reminder cron job (every 10 minutes, idempotent) - @chmod +x $(REMINDER_SH) - @( crontab -l 2>/dev/null | grep -vF '$(REMINDER_SH)' ; \ - echo "$(CRON_SCHED) $(REMINDER_SH) >> $(CRON_LOG) 2>&1" ) | crontab - +cron: ## Install the reminder cron job (idempotent) + @( crontab -l 2>/dev/null | grep -vE 'send[-_]reminders' ; \ + echo "$(CRON_SCHED) $(REMINDER_CMD) >> $(CRON_LOG) 2>&1" ) | crontab - @echo "Installed:" - @crontab -l | grep -F '$(REMINDER_SH)' + @crontab -l | grep -F 'send_reminders' .PHONY: cron-remove cron-remove: ## Remove the reminder cron job - @crontab -l 2>/dev/null | grep -vF '$(REMINDER_SH)' | crontab - + @crontab -l 2>/dev/null | grep -vE 'send[-_]reminders' | crontab - @echo "Removed." .PHONY: cron-test cron-test: ## Run the reminder job once, reporting what it would send - $(REMINDER_SH) --dry-run + @$(REMINDER_CMD) --dry-run # --- Frontend -------------------------------------------------------------- diff --git a/README.md b/README.md index 305de44..d8e0a00 100644 --- a/README.md +++ b/README.md @@ -122,10 +122,12 @@ and so on. A dose with no reminder time never notifies. **3. Turn reminders on** in the Reminders card on the Today page, and use **Send test** there to confirm the whole chain works before trusting it with the cat. -**4. Run the sender from cron**, every ten minutes: +**4. Run the sender from cron** — `make cron` installs the line below. Cron's +environment is nearly empty, so `uv` is called by absolute path and loads +`.env` (the VAPID keys) itself: ```cron -*/10 * * * * cd /path/to/Osiris && uv run python manage.py send_reminders >> /var/log/osiris-reminders.log 2>&1 +*/10 * * * * cd /path/to/Osiris && /path/to/uv run --no-sync --env-file .env python manage.py send_reminders >> reminders.log 2>&1 ``` The command only pushes a dose that is due and not already ticked off, and diff --git a/deploy/send-reminders.sh b/deploy/send-reminders.sh deleted file mode 100755 index 1b3b0c7..0000000 --- a/deploy/send-reminders.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -# Wrapper for the reminder cron job. Cron runs with almost no environment, so the -# settings (and the VAPID keys) have to be loaded explicitly. -set -euo pipefail - -PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -cd "$PROJECT_DIR" - -if [ -f .env ]; then - set -a - # shellcheck disable=SC1091 - . ./.env - set +a -fi - -exec "${UV:-uv}" run --no-sync python manage.py send_reminders "$@"