blumeops/docs/reference/services/zot.md
Erich Blume 4f0476a851
All checks were successful
Build Container / detect (push) Successful in 3s
Build Container (Nix) / detect (push) Successful in 1s
Build Container (Nix) / build (quartz) (push) Successful in 1s
Build Container / build (quartz) (push) Successful in 10s
Fix spider trap: disable SPA mode, remove index files, relax wiki-links (#290)
## Summary

Fixes the Facebook crawler spider trap that's been generating infinite recursive URLs like `/how-to/tutorials/tutorials/how-to/explanation/...` for several days.

**Root cause:** Quartz SPA mode + nginx `try_files` fallback to `index.html` meant any fabricated URL returned the root HTML shell with HTTP 200. Crawlers followed relative links from those fake URLs, creating infinite recursion.

**Fix:**
- Disable Quartz SPA mode (`enableSPA: false`) — all pages are now fully static HTML
- Replace nginx SPA fallback with `=404` + Quartz's static `404.html`
- Remove `robots.txt` exclusions (no longer needed)

**Docs cleanup (Obsidian.nvim compat no longer needed):**
- Delete hand-curated category index files (`tutorials.md`, `reference.md`, `how-to.md`, `explanation.md`) — Quartz auto-generates folder pages
- Delete `postgresql-storage.md` (redirect stub) and `migrate-forgejo-from-brew.md` (stale history)
- Drop `docs-check-index` and `docs-check-filenames` prek hooks
- Rewrite `docs-check-links` to allow path-based wiki-links (`[[path/to/file]]`) and only error on true ambiguity
- Add `ai-docs` doc tree listing to replace index files for AI context
- Add natural cross-links from reference cards to fix orphan docs

## Deployment and Testing

- [ ] Merge and let the build pipeline run
- [ ] Verify docs.eblu.me serves pages correctly with full page loads
- [ ] Verify non-existent URLs return 404
- [ ] Monitor crawler traffic — should drop to near zero for fabricated URLs

Reviewed-on: #290
2026-03-09 11:59:43 -07:00

2.3 KiB

title modified tags
Zot 2026-02-21
service
registry

Zot

OCI-native container registry providing pull-through cache and private image storage.

Quick Reference

Property Value
URL https://registry.ops.eblu.me
Local Port 5050
Data ~/zot
Config ~/.config/zot/config.json
LaunchAgent mcquack

Namespace Convention

Path Source
registry.ops.eblu.me/docker.io/* Cached from Docker Hub
registry.ops.eblu.me/ghcr.io/* Cached from GHCR
registry.ops.eblu.me/quay.io/* Cached from Quay
registry.ops.eblu.me/blumeops/* Private images

Pull-Through Cache

When cluster pulls an image, containerd checks zot first. If cached, returns immediately. If not, zot fetches from upstream, caches it, then returns.

Security Model

OIDC authentication via authentik, with API key support for CI. Three-tier access control:

Role Permissions Use case
Anonymous read Pull images without auth
artifact-workloads group read, create CI push (new tags only, no overwrite/delete)
admins group read, create, update, delete Break-glass admin access

CI authenticates with a zot API key generated from the zot-ci service account's OIDC session. The key is stored in the Forgejo Secrets 1Password item (field zot-ci-api) and synced to Forgejo Actions secrets via ansible.

API Key Rotation

The zot-ci API key expires every 90 days. To rotate:

  1. In Authentik admin UI, impersonate the zot-ci user
  2. Visit https://registry.ops.eblu.me — you'll land on the login page
  3. Click "SIGN IN WITH OIDC" to authenticate as zot-ci
  4. Navigate to https://registry.ops.eblu.me/user/apikey
  5. Generate a new API key, copy it to clipboard
  6. Update 1Password:
    pbpaste | op item edit "Forgejo Secrets" --vault blumeops "zot-ci-api[password]=-"
    
  7. Sync to Forgejo: mise run provision-indri -- --tags forgejo_actions_secrets