.DEFAULT_GOAL := help UV := $(shell command -v uv 2>/dev/null) PROJECT_DIR := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST))))) 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 .PHONY: help help: ## Show this help @grep -hE '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) \ | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-16s\033[0m %s\n", $$1, $$2}' # --- Setup ----------------------------------------------------------------- .PHONY: install install: check-uv frontend ## Install dependencies, build the frontend, migrate, collect static files $(UV) sync @$(MAKE) --no-print-directory env # after sync: it needs Django importable $(MANAGE) migrate $(MANAGE) collectstatic --noinput @echo "Installed. Next: 'make superuser' (once), then 'make service' and 'make cron'." .PHONY: check-uv check-uv: @test -n "$(UV)" || { echo "uv is not installed: https://docs.astral.sh/uv/"; exit 1; } .PHONY: env env: ## Create .env with a fresh SECRET_KEY and VAPID keys (keeps an existing one) @if [ -f .env ]; then \ echo ".env already exists — leaving it alone."; \ else \ cp .env.example .env; \ secret=$$($(UV) run python -c "import secrets; print(secrets.token_urlsafe(64))"); \ sed -i "s|^DJANGO_SECRET_KEY=.*|DJANGO_SECRET_KEY=$$secret|" .env; \ $(MAKE) --no-print-directory vapid; \ echo "Wrote .env — review DJANGO_ALLOWED_HOSTS before going live."; \ fi .PHONY: vapid vapid: ## Generate VAPID keys and write them into .env @test -f .env || cp .env.example .env @keys=$$($(MANAGE) generate_vapid_keys 2>/dev/null | grep '^VAPID_'); \ pub=$$(echo "$$keys" | grep '^VAPID_PUBLIC_KEY=' | cut -d= -f2); \ priv=$$(echo "$$keys" | grep '^VAPID_PRIVATE_KEY=' | cut -d= -f2); \ test -n "$$pub" || { echo "Key generation failed."; exit 1; }; \ sed -i "s|^VAPID_PUBLIC_KEY=.*|VAPID_PUBLIC_KEY=$$pub|" .env; \ sed -i "s|^VAPID_PRIVATE_KEY=.*|VAPID_PRIVATE_KEY=$$priv|" .env; \ echo "VAPID keys written to .env. Restart the service, then re-enable reminders on each device." .PHONY: superuser superuser: ## Create the login account (there is no registration) $(MANAGE) createsuperuser # --- systemd --------------------------------------------------------------- .PHONY: service service: ## Install, enable and start the systemd service (needs sudo) @sed -e 's|__USER__|$(USER)|g' \ -e 's|__PROJECT_DIR__|$(PROJECT_DIR)|g' \ -e 's|__UV__|$(UV)|g' \ -e 's|__PORT__|$(PORT)|g' \ deploy/osiris.service.template > /tmp/$(SERVICE).service sudo install -m 644 /tmp/$(SERVICE).service $(UNIT_FILE) @rm -f /tmp/$(SERVICE).service sudo systemctl daemon-reload sudo systemctl enable --now $(SERVICE) @sudo systemctl --no-pager --lines=0 status $(SERVICE) || true .PHONY: restart restart: ## Restart the service (do this after changing .env) sudo systemctl restart $(SERVICE) .PHONY: logs logs: ## Follow the service log journalctl -u $(SERVICE) -f .PHONY: service-uninstall service-uninstall: ## Stop and remove the systemd service -sudo systemctl disable --now $(SERVICE) sudo rm -f $(UNIT_FILE) sudo systemctl daemon-reload # --- 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. .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 - @echo "Installed:" @crontab -l | grep -F '$(REMINDER_SH)' .PHONY: cron-remove cron-remove: ## Remove the reminder cron job @crontab -l 2>/dev/null | grep -vF '$(REMINDER_SH)' | crontab - @echo "Removed." .PHONY: cron-test cron-test: ## Run the reminder job once, reporting what it would send $(REMINDER_SH) --dry-run # --- Frontend -------------------------------------------------------------- .PHONY: frontend frontend: ## Install frontend dependencies (pnpm) and build the Vue frontend cd frontend && pnpm install && pnpm run build # Only build when no bundle exists yet: tests render the shell template, which # needs the Vite manifest, but should not pay for a rebuild on every run. frontend/dist/.vite/manifest.json: @$(MAKE) --no-print-directory frontend .PHONY: front-dev front-dev: ## Run the Vite dev server with hot reload (pair with: DJANGO_VITE_DEV=1 make run) cd frontend && pnpm run dev .PHONY: api-client api-client: ## Regenerate the typed TS API client (needs 'make run' in another terminal) cd frontend && pnpm run generate:api # --- Development ----------------------------------------------------------- .PHONY: run run: ## Run the development server $(MANAGE) runserver $(PORT) .PHONY: test test: frontend/dist/.vite/manifest.json ## Run the test suite $(MANAGE) test .PHONY: lint lint: ## Check formatting and lints $(UV) run ruff format --check . $(UV) run ruff check . .PHONY: format format: ## Reformat and autofix $(UV) run ruff format . $(UV) run ruff check --fix . .PHONY: deploy deploy: install restart ## Pull dependencies, migrate, collect static, restart @echo "Deployed."