Run Claude Code in CI on your subscription, not the metered API — — Running Claude Code headless in a pipeline almost always reaches for an ANTHROPIC_API_KEY — which bills pay-per-token, separate from the Max plan you're already paying for. But Claude Code has a second auth mode that draws on the subscription instead. It's one env var and a token you generate once. Here's the switch, and the edges that come with drawing CI usage from a personal plan.
Email on a custom domain without running a mail server — — I wanted me@awkto.dev to just work — receive into an inbox I already read, and reply from it too — without standing up Postfix, paying for a mailbox, or babysitting a spam reputation. It turned out to be a forwarding service for inbound and a relay bolted onto Gmail for outbound: two free tiers, zero servers. Here's exactly how the stack fits together and where its edges are.
Three ways to build an agent (and why I picked the middle one) — — I built an AI teammate that lives inside my GitLab. The hard question was never 'which model' — it was 'who runs the loop.' There are exactly three answers, and the difference between them is mostly about what leaves your network.
GitLab with Object Storage on DigitalOcean or Cloudflare — — GitLab's object-storage docs lean on AWS S3 and GCS examples — fine for a company, but for a home lab those clouds are expensive and genuinely dangerous (egress fees and runaway costs). DO Spaces and Cloudflare R2 are flat-rate, S3-compatible, and boring in the best way. Here's the gitlab.rb that actually works on both, including the R2 quirks the docs don't mention.
Making Bitwarden non-interactive for an AI agent — — Both Bitwarden CLIs assume a human is sitting there to type the master password, the API client_secret, and the 2FA code. For an AI agent doing bulk vault edits, every prompt is a deadlock. The fix is one specific insight: `rbw` asks for every secret through pinentry, and pinentry is a protocol I can implement. A 30-line bash script that routes each prompt to OpenBao turns rbw into a fully unattended tool.
Mattermost Desktop shows a generic cog in the GNOME dock on Wayland — — Installed Mattermost from the tarball on Ubuntu 26.04. The launcher icon was correct, but once the app was running, the dock showed a generic cog instead of the Mattermost logo. On Wayland the dock matches the running window's xdg_toplevel app_id against the .desktop file basename — and Mattermost's app_id is Mattermost.Desktop, with the dot.
GitLab Geo as an HA story: the seams that show — — GitLab Geo is excellent at what it was designed for — disaster recovery across regions. People keep reaching for it as a high-availability solution, and that's where the seams show. Where the design assumes a one-way ticket from primary to promoted secondary, an HA pattern wants seamless, cheap, identity-preserving failover. Here's a tour of the friction, what GitLab could do to mature it, and what you can do today.
Why GitLab has a separate secrets file — and why Geo doesn't sync it — — After failing over a GitLab Geo pair, my Personal Access Token stopped working. The root cause was the one file that PostgreSQL streaming replication politely refuses to touch. Here's what gitlab-secrets.json actually contains, what it protects, why GitLab keeps it separate from the database, and the practical implications for any Geo or HA setup.
Planned GitLab Geo failover: nine things the docs glossed over — — Stood up a GitLab Geo secondary, did a planned failover, and immediately set up backward replication so the old primary became the new secondary. The promotion itself took 90 seconds. Everything around it — the cert dance, the auth quirks, the silent reconfigure failures, the wrapper that drops your env vars — turned a one-hour task into half a day. Writing it down before the bruises heal.
Standing up GitLab EE on DigitalOcean: the gotchas I'd avoid next time — — Built a fresh GitLab EE 18.11 lab on DO droplets — Ultimate-NFR license, top-level group, owner-access user accounts, 9 docker-executor runners. Worked, eventually. A dozen pitfalls along the way turned a one-hour task into four hours. Writing them down so the next build is forty-five minutes.
UpNote (and any Electron app) won't show a window on NVIDIA + Wayland — — Installed UpNote on Ubuntu 26.04 with an NVIDIA card. Process runs, tray icon registers, no window ever appears. Two stacked failures: the Chromium GPU process segfaults on the nvidia-drm node, and Electron's native Wayland backend silently fails to map the toplevel. Fix: --disable-gpu --ozone-platform=x11 in the .desktop launcher.
The '?' on the WiFi icon that won't go away across Ubuntu 24, 25, 26 — — Three Ubuntu releases in a row, the WiFi indicator paints a question mark while the network works fine. It's NetworkManager's connectivity check failing against the default Ubuntu endpoint. Pointing it at GNOME's has fixed it for me on every release.
Cutting musicmod's CI from 12 minutes to 2 — — musicmod's release builds were burning ~12 minutes apiece — long enough that a busy day of tagging blew through the GitHub Actions Pro budget twice. Three changes (a dropped matrix entry, a registry-backed Docker cache, and a .dockerignore) took every subsequent build down to about 1m50s.
WireGuard import is still broken in Ubuntu 26.04 — — The GUI VPN importer silently drops the [Peer] section on WireGuard configs. The connection appears, activates, and does absolutely nothing. Here's the nmcli workaround that actually works — and why the GUI is still where I want to live.
Making nginx more resilient: runtime DNS resolution and auto-restart — — An internal site silently disappeared for three days after nginx failed a reload because DNS was briefly unavailable. Two tiny config changes — runtime upstream resolution and a systemd Restart drop-in — make that failure mode impossible.
Adding HA to a single-container SQLite web app, without moving to Postgres — — Three options for SQLite HA. The first is overkill. The second is the obvious answer that didn't work — we burned ~600 lines of glue trying to make Litestream and an embedded SFTP server cooperate. The third is 80 lines of Python that POSTs the DB over HTTPS every thirty seconds. Guess which one we shipped.
Why my duplicate-detection was 100× slower than it should have been — — Audio fingerprints aren't hashes. They're short time-series, and comparing them in pure Python means paying the interpreter tax on every single sample. Here's the cliff I walked off, and the one-line numpy fix.
Home, office, and a bastion in the cloud — one WireGuard config — — Neither my home nor my office has a public IP. I still wanted every device I own to reach both LANs as if it were sitting in either building. A small cloud bastion and one well-crafted WireGuard config solved it.
Running GitLab MCP from my couch (via Cloudflare Tunnel) — — I wanted Claude to open MRs and triage issues in my self-hosted GitLab from my phone. Claude web runs on Anthropic's infrastructure, not my LAN — which meant rethinking how the MCP connects.
acme.sh quietly runs my entire lab's TLS — — Certbot is fine. But if you're issuing certs across a home lab — wildcards, multiple CAs, weird DNS providers — acme.sh is the tool I keep coming back to. One shell script, zero plugin installs, a wildcard cert in about fifteen seconds.
How awkto.dev ships itself — — From a Lovable-generated Vite app to a fully wired awkto.dev — Cloudflare Workers, GitHub Actions, a dev preview branch, a new domain and two-way email, without standing up a single server.