GitOps repository for personal infrastructure management
  • Nix 32.5%
  • Jinja 21.5%
  • Python 17.9%
  • Shell 11.8%
  • Go 8.1%
  • Other 8.2%
Find a file
Erich Blume 3c7967e445 C1: deploy shower v1.1.0 (phases + guest memories) (#354)
## Summary

Deploys `adelaide-baby-shower-app` **v1.1.0** to ringtail k3s.

### App changes (since v1.0.2)

- **Four-phase `ShowerState`** replaces the boolean `locked` flag — `pre_event` → `party` → `prizes_locked` → `event_locked` — with a backfill migration that maps `locked=True → pre_event`, `locked=False → party`.
- **Guest memories**: append-only photos + comments panel where guests can leave notes for the baby. Adds `GuestPhoto` + `GuestComment` models with file-extension validators and a max-size validator; new `shower.imaging` module for thumbnail generation.
- **Admin + QR polish**: configurable host link, fixed "View Site" URL, guest-facing QR copy improvements, contest tweaks.

Three Django migrations run automatically in the entrypoint against the SQLite PV:
- `0009_shower_phase`
- `0010_guest_memories`
- `0011_book_description`

No ConfigMap / env-var changes. The deploy uses `strategy: Recreate` with a single replica, so the old pod releases the data PVC before the new one mounts it and runs migrations.

### Container build changes

The v1.1.0 tag exposed a latent issue with the Forgejo PyPI install path:

- The recent commit [2d38418e](2d38418e) closed the forge package leak at the Fly edge by blocking `/api/packages/*` publicly.
- Forgejo's PyPI simple index returns absolute file URLs hardcoded to its public `ROOT_URL` (`forge.eblu.me`), so pip-installing from the tailnet index URL still tries to download from `forge.eblu.me` → 403.
- Previous shower builds escaped this because their FOD outputs were already in the nix store; bumping to a new version forced a fresh pip run that hit the block.

Fix mirrors what we already do for the sdist: both wheel and sdist are pulled via direct `fetchurl` against `forge.ops.eblu.me`, then the wheel is copied to TMPDIR under its clean filename (nix store path's hash prefix breaks pip's wheel-filename parser) and handed to pip as a local path. The forge `--extra-index-url` is no longer needed.

FOD outputHash pinned to `sha256-kTNOswobtkgyQmmqbQM8XO4vvaGg57nCuuZGbNXb0NM=` from run 547. Image: `registry.ops.eblu.me/blumeops/shower:v1.1.0-444ff91-nix`.

### Adjacent finding (already handled)

The ringtail `gitea-runner-nix_container_builder` systemd unit was left `inactive` after the recent `provision-ringtail` (matches the known `sshd-restart-hangs-mux` lesson — the rebuild changed the unit's PATH closure + config.yaml, systemd stopped it, then the playbook hung before the activation could restart it). Manually started; the existing memory `lesson_provision_ringtail_ssh_hang.md` was extended to mention the runner as the canary service to check after provisions.

## Test plan

- [ ] `argocd app diff shower --revision shower-v1.1.0` — review the manifest change
- [ ] `argocd app set shower --revision shower-v1.1.0 && argocd app sync shower`
- [ ] `kubectl --context=k3s-ringtail logs -n shower deploy/shower` — confirm migrations 0009/0010/0011 applied, no errors
- [ ] Hit `https://shower.ops.eblu.me/` (tailnet) — splash page renders, phase indicator visible
- [ ] Hit `https://shower.ops.eblu.me/host/` — host console loads, phase dropdown shows the four states
- [ ] Hit `https://shower.eblu.me/` (public via Fly) — splash page still served
- [ ] After merge: `argocd app set shower --revision main && argocd app sync shower`

Reviewed-on: #354
2026-05-11 20:08:03 -07:00
.claude Remove doc-reviewer agent 2026-03-30 16:12:48 -07:00
.forgejo/workflows C1: migrate cv + docs from minikube to indri-native (#342) 2026-04-29 14:55:11 -07:00
.github Switch git hooks from pre-commit to prek (#276) 2026-03-02 18:15:23 -08:00
ansible C1: deploy adelaide-baby-shower-app to ringtail k3s (#349) 2026-05-11 13:47:18 -07:00
argocd C1: deploy shower v1.1.0 (phases + guest memories) (#354) 2026-05-11 20:08:03 -07:00
containers C1: deploy shower v1.1.0 (phases + guest memories) (#354) 2026-05-11 20:08:03 -07:00
docs C1: deploy shower v1.1.0 (phases + guest memories) (#354) 2026-05-11 20:08:03 -07:00
fly C1: deploy adelaide-baby-shower-app to ringtail k3s (#349) 2026-05-11 13:47:18 -07:00
mise-tasks C1: deploy adelaide-baby-shower-app to ringtail k3s (#349) 2026-05-11 13:47:18 -07:00
nixos/ringtail C1: ringtail — weekly flake.lock update (#352) 2026-05-11 16:13:07 -07:00
pulumi C1: deploy adelaide-baby-shower-app to ringtail k3s (#349) 2026-05-11 13:47:18 -07:00
src/blumeops Refactor Dagger go_build() helper and standardize Alpine 3.23 2026-04-16 10:10:46 -07:00
utils/qart Add QArt Tuner: QR code art generator with interactive web UI 2026-03-27 15:33:36 -07:00
.ansible-lint Add pre-commit hooks for code quality (#19) 2026-01-16 19:33:02 -08:00
.gitattributes Native Dagger container builds + Navidrome v0.61.1 (#330) 2026-04-11 17:11:56 -07:00
.gitignore C0: gitignore .claude/scheduled_tasks.lock 2026-05-11 18:37:29 -07:00
.yamllint.yaml Allow implicit octals in yamllint and normalize k8s mode values 2026-03-03 13:10:44 -08:00
AGENTS.md C0: docs — default argocd login to --sso; drop extraneous --grpc-web 2026-04-21 10:43:21 -07:00
Brewfile Add op-backup mise task for encrypted 1Password disaster recovery (#136) 2026-02-09 20:37:39 -08:00
CHANGELOG.md Update docs release to v1.16.0 2026-04-18 10:00:54 -07:00
CLAUDE.md C0: CLAUDE.md — import AGENTS.md instead of redirecting to it 2026-04-27 11:41:13 -07:00
compensating-controls.yaml C1: review CC observability-stack-audit (extend to k3s) (#353) 2026-05-11 16:10:39 -07:00
dagger.json Bump Dagger to 0.20.6 and migrate runner-job-image to Alpine container.py 2026-04-21 08:28:18 -07:00
LICENSE Adopt Dagger CI for container builds (Phase 1) (#156) 2026-02-11 15:38:31 -08:00
mise.toml Bump Dagger to 0.20.6 and migrate runner-job-image to Alpine container.py 2026-04-21 08:28:18 -07:00
prek.toml C1: SHA-pin tooling dependencies (2026-04 cycle) (#344) 2026-04-30 16:51:43 -07:00
pyproject.toml Miniflux 2.2.19 + container.py migration + ty typechecker (#331) 2026-04-12 08:54:32 -07:00
README.md C0: adopt AGENTS.md as canonical agent config 2026-04-18 20:15:30 -07:00
service-versions.yaml C1: deploy shower v1.1.0 (phases + guest memories) (#354) 2026-05-11 20:08:03 -07:00
towncrier.toml Fix Quartz build to preserve git history for accurate file dates (#105) 2026-02-04 08:25:46 -08:00
uv.lock Add uv.lock for version pinning of dagger pipeline 2026-04-13 08:35:01 -07:00

blumeops

aka "Blue Mops"

Tools and configuration for Erich Blume's personal infrastructure, orchestrated across a Tailscale tailnet.

This is a homelab, but it's also a testing ground for AI-assisted infrastructure development. Much of this codebase was initially co-authored with Claude Code, and the repo places heavy emphasis on documentation, process, and change classification to make that collaboration work well. I don't know entirely how I feel about LLMs in our current era (there are real concerns about how training data is sourced and energy subsidy) but it felt important to learn how to work with these tools.

The full documentation is published at docs.eblu.me and lives in the docs/ directory, structured around the Diataxis framework and designed to be compatible with Obsidian/Obsidian.nvim.

What runs here

Services are a mix of Kubernetes pods (managed by ArgoCD), macOS LaunchAgent services (managed by Ansible), and NixOS systemd services (managed by Nix flakes), all connected via Tailscale:

  • Indri (Mac Mini M1) - primary server. Most services run in Minikube via ArgoCD; Forgejo, Caddy, and others run natively as LaunchAgent services via Ansible.
  • Ringtail (NixOS desktop, RTX 4080) - GPU workloads (Frigate NVR, Authentik SSO) on k3s, plus NixOS systemd services.
  • Sifaka (Synology NAS) - backup target and bulk storage.

Notable services include Grafana/Prometheus/Loki observability, Immich photos, Jellyfin media, Forgejo git forge, a Zot container registry, and more. Public access is routed through a Fly.io proxy; everything else is tailnet-only.

Project structure

ansible/            Ansible playbooks and roles (indri, sifaka)
argocd/apps/        ArgoCD Application definitions
argocd/manifests/   Kubernetes manifests per service
containers/         Custom container builds (Dockerfile + Nix)
docs/               Diataxis documentation (published at docs.eblu.me)
fly/                Fly.io public proxy configuration
mise-tasks/         Operational scripts run via mise
nixos/              NixOS configuration for ringtail
pulumi/             Pulumi IaC (Tailscale ACLs, Gandi DNS)
.dagger/            Dagger CI pipelines
.forgejo/           Forgejo Actions CI/CD workflows

Getting started

You'll need Homebrew and mise:

brew bundle                    # install CLI tools (argocd, tea, flyctl, etc.)
mise install                   # install managed toolchains (ansible, pulumi, dagger, etc.)
prek install                    # set up git hooks

Git hooks (via prek) enforce secret scanning (TruffleHog), linting, formatting, and custom checks like doc link validation and the Mikado branch invariant. They run automatically on git commit.

Operational tasks are driven through mise. Run mise tasks to see what's available. Key examples:

mise run provision-indri       # deploy to indri via Ansible
mise run services-check        # verify service health
mise run container-list        # list tracked container images

AI-assisted development

This repo is designed to be worked on by both humans and AI agents. The AGENTS.md file provides shared instructions for agentic tools, and the docs/tutorials/ai-assistance-guide.md explains the full workflow.

Changes are classified before starting work:

  • C0 - quick fixes, committed directly to main
  • C1 - feature branch + PR, documentation written before code
  • C2 - multi-phase work using the Mikado method for dependency tracking

See the agent change process for details.

License

GPLv3