while(motivation <= 0)

Back to the blog...
Back to basics

This weekend, I started off strong by going to the range with an old friend and then catching up over lunch. As far as the tech side of things, I broke down my serverless setup as it’s more expensive than a Wonderbox hosting containers. With that said I dusted off my nginx load balancer container, killed my ELB, and elastic ips, and retooled dns back to it’s humble roots. The following is Claude’s summary of the new features we implemented and the things we troubleshooted.

Dev work done this weekend

This weekend was a productive infrastructure and tooling sprint on the blog2/vacuumflask project. Here's a rundown of what got built and fixed.

Media Expiration System

Added a full lifecycle management system for media files. You can now set an expiration date on any file in the media library. After that date, a cleanup job removes it from S3 automatically. The expiration is stored in SQLite, visible in the media library UI with an edit button on each card, and also available at upload time. When a file is deleted manually, its expiration record is cleaned up too.

Headless API Authentication

Built a /api/login endpoint that issues a short-lived Bearer token using VACUUMAPIKEYSALT + TOTP — no plaintext password required. This lets automated scripts authenticate without storing credentials, using AWS Secrets Manager for the secret values and pyotp for one-time passwords.

Lambda Cleanup Cron

Created cron/cleanup_expired.py — a Lambda-compatible handler that logs in via the headless API and calls /admin/cleanup_expired. It logs structured output to CloudWatch under the cron log group via watchtower, with each run getting its own stream. Also ships as a local cron.sh that can be called from the system crontab, scheduled for 12:01 AM daily.

MCP Server

Made the blog discoverable to AI assistants via the Model Context Protocol. A FastMCP server exposes blog posts, tags, and search as resources and tools. It runs as a Docker container with SSE transport proxied through nginx at /mcp/, and is advertised to clients via /.well-known/mcp.json and a tag in the page header.

Blog Styling Modernization

Rewrote style.css with CSS variables, a sticky header, card-style post layout, and a modernized tag cloud panel. Fixed a specificity bug where tags were rendering white-on-white, and another where tag size weighting was overridden by the admin nav styles — so the tag cloud now correctly reflects content volume.

Nginx + Certbot Infrastructure

Rebuilt the load balancer container to manage its own TLS certificates. On startup it generates self-signed certs so nginx can start, then immediately replaces them with Let's Encrypt certificates via the HTTP-01 webroot challenge. A cron job inside the container handles daily renewal at 3 AM and 3 PM. Certificates persist across restarts via Docker named volumes.

Redis Connection Fix

Tracked down a TimeoutError in the Redis client caused by a stale EC2 internal IP in the server's .env file. The Flask container runs with --network=host but Valkey runs in bridge mode, so Docker's loopback forwarding doesn't apply. The fix was to use Valkey's Docker bridge IP (172.17.0.2) directly.