.PHONY: build deploy sign download-model download-pdfjs compress-assets convert-images pdf-thumbs pdfs watch clean dev # Source .env for deploy / GitHub config if it exists. # .env format: KEY=value (one per line, no `export` prefix, no quotes needed). # Only the variables explicitly listed below are exported to recipe # subprocesses — bare `export` would leak every .env key (including any # future GITHUB_TOKEN) into every child process. -include .env export VPS_USER VPS_HOST VPS_PATH GITHUB_REPO build: # Auto-snapshot any uncommitted content/ changes BEFORE the build # so the stability heuristic in build/Stability.hs sees a stable # git history. If a subsequent step fails, the snapshot remains in # the history — that's intentional. The next successful build # either reuses it (no new content/ changes) or appends another # snapshot on top, so failures don't disappear from the log. # # Pathspec is explicit (not `git add content/`) so a stray .env, # credential file, or other non-content artifact dropped under # content/ is NOT auto-staged. The :(glob) magic prefix makes `**` # match across path components (git default fnmatch does not). # Add new extensions here if a new asset type is introduced. @git add ':(glob)content/**/*.md' ':(glob)content/**/*.html' ':(glob)content/**/*.bib' \ ':(glob)content/**/*.png' ':(glob)content/**/*.jpg' ':(glob)content/**/*.jpeg' \ ':(glob)content/**/*.svg' ':(glob)content/**/*.gif' ':(glob)content/**/*.pdf' \ ':(glob)content/**/*.mp3' ':(glob)content/**/*.ogg' ':(glob)content/**/*.flac' \ ':(glob)content/**/*.yaml' ':(glob)content/**/*.yml' ':(glob)content/**/*.json' \ ':(glob)content/**/*.css' ':(glob)content/**/*.tex' @git diff --cached --quiet || git commit -m "auto: $$(date -u +%Y-%m-%dT%H:%M:%SZ) [skip ci]" @mkdir -p data @date +%s > data/build-start.txt @./tools/convert-images.sh @$(MAKE) -s pdf-thumbs @./tools/download-pdfjs.sh cabal run site -- build pagefind --site _site @if [ -d .venv ]; then \ uv run python tools/embed.py || echo "Warning: embedding failed — data/similar-links.json not updated (build continues)"; \ else \ echo "Embedding skipped: run 'uv sync' to enable similar-links (build continues)"; \ fi @./tools/compress-assets.sh _site > IGNORE.txt @BUILD_END=$$(date +%s); \ BUILD_START=$$(cat data/build-start.txt); \ echo $$((BUILD_END - BUILD_START)) > data/last-build-seconds.txt.tmp && \ mv data/last-build-seconds.txt.tmp data/last-build-seconds.txt sign: @./tools/sign-site.sh # Download the quantized ONNX model for client-side semantic search. # Run once; files are gitignored. Safe to re-run (skips existing files). download-model: @./tools/download-model.sh # Vendor Mozilla's prebuilt PDF.js viewer into static/pdfjs/. # Runs automatically as part of `build` (skips when already present). # Files are gitignored; sha256-verified against tools/pdfjs-checksums.sha256. download-pdfjs: @./tools/download-pdfjs.sh # Generate .gz and .br sidecars for compressible text assets in _site/. # Runs automatically as part of `build`. Pairs with `gzip_static` / # `brotli_static` in the nginx vhost (see nginx/static-assets.conf). compress-assets: @./tools/compress-assets.sh _site # Convert JPEG/PNG images to WebP companions (also runs automatically in build). # Requires cwebp: pacman -S libwebp / apt install webp convert-images: @./tools/convert-images.sh # Generate first-page thumbnails for PDFs in static/papers/ (also runs in build). # Requires pdftoppm: pacman -S poppler / apt install poppler-utils # Thumbnails are written as static/papers/foo.thumb.png alongside each PDF. # Skipped silently when pdftoppm is not installed or static/papers/ is empty. pdf-thumbs: @if command -v pdftoppm >/dev/null 2>&1; then \ find static/papers -name '*.pdf' 2>/dev/null | while read pdf; do \ thumb="$${pdf%.pdf}.thumb"; \ if [ ! -f "$${thumb}.png" ] || [ "$$pdf" -nt "$${thumb}.png" ]; then \ echo " pdf-thumb $$pdf"; \ pdftoppm -r 100 -f 1 -l 1 -png -singlefile "$$pdf" "$$thumb"; \ fi; \ done; \ else \ echo "pdf-thumbs: pdftoppm not found — install poppler (skipping)"; \ fi # Rebuild the CV + website résumé from yaml-source/ and refresh static/. # Standalone helper — NOT a dependency of `build` or `deploy`. Run manually # after editing a YAML under yaml-source/data/. The site build copies # static/*.pdf through unchanged, so a subsequent `make build` picks them up. # # The ATS variant (yaml-source/output/resume_ats.pdf) is intentionally not # copied to static/ — it's a submission artifact, not a website asset. To # regenerate it too, run `make -C yaml-source ats` directly. # # Silently skipped on hosts without the pipeline (e.g., the VPS): yaml-source/ # is gitignored, so it's absent on a fresh clone, and that's the expected # state wherever the LaTeX toolchain isn't installed. pdfs: @if [ ! -d yaml-source ]; then \ echo "pdfs: yaml-source/ not present — skipping (pipeline is local-only)"; \ exit 0; \ fi @$(MAKE) -C yaml-source all @cp yaml-source/output/cv.pdf static/cv.pdf @cp yaml-source/output/resume.pdf static/resume.pdf @echo "pdfs: static/cv.pdf and static/resume.pdf refreshed." deploy: clean build sign @test -n "$(VPS_USER)" || (echo "deploy: VPS_USER not set in .env" >&2; exit 1) @test -n "$(VPS_HOST)" || (echo "deploy: VPS_HOST not set in .env" >&2; exit 1) @test -n "$(VPS_PATH)" || (echo "deploy: VPS_PATH not set in .env" >&2; exit 1) # Refuse to deploy a manifestly broken build. _site/index.html must # exist and be non-empty before we run rsync --delete on the VPS. @test -s _site/index.html || { echo "deploy: _site/index.html is missing or empty — refusing to rsync" >&2; exit 1; } # Defense-in-depth: refuse rsync --delete to obviously dangerous # parents in case VPS_PATH was typo'd (e.g. trailing-slash mistake). @case "$(VPS_PATH)" in /|/srv|/srv/http|/var|/var/www|/home|/root|"") echo "deploy: VPS_PATH=$(VPS_PATH) looks unsafe — refusing" >&2; exit 1 ;; esac @command -v notify-send >/dev/null 2>&1 && notify-send "make deploy" "Ready to push & rsync — waiting for auth" || true # Push first: a successful push is cheap to roll back, while a # half-completed rsync is harder to recover from. If the push # fails (auth, branch protection, network), abort before touching # the VPS so the public source repo and the live site stay in sync. git push -u origin main rsync -avz --delete _site/ $(VPS_USER)@$(VPS_HOST):$(VPS_PATH)/ watch: export SITE_ENV = dev watch: cabal run site -- watch clean: cabal run site -- clean # Dev build includes any in-progress drafts under content/drafts/essays/. # SITE_ENV=dev is read by build/Site.hs; drafts are otherwise invisible to # every build (make build / make deploy / cabal run site -- build directly). dev: export SITE_ENV = dev dev: cabal run site -- clean cabal run site -- build python3 -m http.server 8000 --bind 127.0.0.1 --directory _site