Compare commits

..
4 Commits
Author SHA1 Message Date
mathias f50dd6be6a chore(release): plugin.json version 0.6.1
Skip-Release: true
2026-08-25 05:04:23 +00:00
mathiasandClaude Opus 5 1906a774b3 fix(gitea-ci): teach buildctl, not buildah (infra#241)
release / tag (push) Successful in 1s
This skill was the source of the buildah build step that infra#241 has
spent a sprint removing from nine repos: rootless buildah cannot run in
the containerized gitea-runner (drop ALL capabilities,
allowPrivilegeEscalation false, no OCI runtime), and the failure is
latent -- warm base-image layers hide it until the first cold pull.

Two templates were migrated for exactly this reason (stop new repos
inheriting the trap); this skill is the third factory and was still
handing out the broken pattern.

Also drops the `sudo k3s ctr` smoke test it recommended: written for the
Arch host runner, impossible in a pod (`sudo: command not found`). The
deploy job's rollout verification covers the same ground.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011PL5XcG68SvQqz3vGVbWbf
2026-08-25 07:00:50 +02:00
mathias 38f905e705 chore(release): plugin.json version 0.6.0
Skip-Release: true
2026-08-24 19:16:00 +00:00
mathiasandClaude Opus 5 7db7e0779b feat(mcp): serve brain and infra over Tailscale Serve with real TLS
release / tag (push) Successful in 1s
Replaces the raw NodePort URLs with Tailscale Serve endpoints on koala:

  brain  https://koala.tail30472e.ts.net:8443/mcp  -> 127.0.0.1:30330
  infra  https://koala.tail30472e.ts.net:8444/mcp  -> 127.0.0.1:30342

Why this beats both alternatives that were tried:

The public d-ma.be path exits to Cloudflare and NPM to reach a k3s service on
the machine the session is running on, and Cloudflare rate-limits and
size-caps tool results — the original complaint. The raw NodePort path fixed
that but only worked from koala: every namespace carries default-deny-ingress
and an allow policy naming the traefik namespace, so kube-proxy's
KUBE-EXTERNAL-SERVICES chain RSTs external callers. Admitting tailnet clients
there would need externalTrafficPolicy: Local on each service plus an ipBlock
per policy, because the default Cluster policy SNATs the source and an ipBlock
can never match.

Serve sidesteps all of it. tailscaled terminates TLS with a genuine cert and
proxies over loopback, so the request reaches the NodePort from koala itself —
the case the NetworkPolicy already permits. No policy change, no
externalTrafficPolicy change, no firewall rule, no cert-manager, no DNS record.

Port-per-service rather than path-per-service: the mapping is 1:1 with no
prefix rewriting, so what the client requests and what the server sees cannot
desync.

Verified: 200 authenticated in 7.6ms from koala, and `claude mcp list` reports
Connected against the Serve URL. An earlier apparent OAuth resource-metadata
mismatch turned out to be an artifact of testing without an Authorization
header — with a valid bearer the client never enters OAuth discovery.

Client requirement: `tailscale set --accept-dns=true`. Confirmed on iguana that
100.100.100.100 resolves the MagicDNS name correctly while the system resolver
does not, so the name fails to resolve until Tailscale is accepted as a DNS
provider. Tracked in #24 as the Mac prerequisite. Note Serve matches on SNI, so
substituting the raw tailnet IP is not a workaround.

Bump-Type: minor

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BeAp5LTscnz2W7Ubt4eJ6m
2026-08-24 21:11:49 +02:00
3 changed files with 46 additions and 30 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "skills", "name": "skills",
"description": "Mathias's engineering skills library, plus the hooks and MCP servers that make it enforceable: TDD, clean code, debugging, specs, discovery, CI, and secret hygiene.", "description": "Mathias's engineering skills library, plus the hooks and MCP servers that make it enforceable: TDD, clean code, debugging, specs, discovery, CI, and secret hygiene.",
"version": "0.5.0", "version": "0.6.1",
"author": { "author": {
"name": "Mathias", "name": "Mathias",
"url": "https://git.d-ma.be/mathias" "url": "https://git.d-ma.be/mathias"
+4 -4
View File
@@ -1,16 +1,16 @@
{ {
"_comment": "Tailnet-direct NodePorts, not the public d-ma.be hostnames. The public path goes out through Cloudflare and NPM to reach a k3s service on koala, which imposes rate limits and message-size caps on tool results and adds a full internet round trip - for a service on the same machine most sessions run on. Requires koala to allow these ports on the Tailscale interface; see mathias/skills#24.", "_comment": "Tailscale Serve on koala, not the public d-ma.be hostnames. The public path exits to Cloudflare and NPM to reach a k3s service on the same machine most sessions run on, which imposes rate limits and message-size caps on tool results. Serve terminates TLS with a real Tailscale cert and proxies to the NodePort over loopback, so traffic reaches the service from koala itself and satisfies the default-deny-ingress NetworkPolicy with no cluster change. Clients need 'tailscale set --accept-dns=true' for the MagicDNS name to resolve; see mathias/skills#24.",
"mcpServers": { "mcpServers": {
"brain": { "brain": {
"type": "http", "type": "http",
"url": "http://koala:30330/mcp", "url": "https://koala.tail30472e.ts.net:8443/mcp",
"headers": { "headers": {
"Authorization": "Bearer ${BRAIN_MCP_TOKEN}" "Authorization": "Bearer ${BRAIN_MCP_TOKEN}"
} }
}, },
"infra": { "infra": {
"type": "http", "type": "http",
"url": "http://koala:30342/mcp" "url": "https://koala.tail30472e.ts.net:8444/mcp"
} }
} }
} }
+41 -25
View File
@@ -1,6 +1,6 @@
--- ---
name: gitea-ci name: gitea-ci
description: End-to-end Gitea CI/CD setup with self-hosted act_runner, quality gate pipeline, buildah image build, k3s deploy, and GitHub mirror. Covers act_runner-specific gotchas that are not documented anywhere. description: End-to-end Gitea CI/CD setup with self-hosted act_runner, quality gate pipeline, buildctl image build, k3s deploy, and GitHub mirror. Covers act_runner-specific gotchas that are not documented anywhere.
--- ---
# Gitea CI/CD Pipeline Setup # Gitea CI/CD Pipeline Setup
@@ -31,7 +31,7 @@ Before writing any YAML, collect the following. Use AskUserQuestion for anything
| Container registry | Ask or check existing config | `localhost:5000` on koala, or external | | Container registry | Ask or check existing config | `localhost:5000` on koala, or external |
| k3s namespace | k8s/ manifests or ask | Deploy target | | k3s namespace | k8s/ manifests or ask | Deploy target |
| GitHub mirror repo | Ask | Target for the mirror job | | GitHub mirror repo | Ask | Target for the mirror job |
| Image name | Ask or derive from repo name | Used in buildah + k3s steps | | Image name | Ask or derive from repo name | Used in buildctl + k3s steps |
Check the project's `CLAUDE.md` for infra notes first — often this answers most questions without needing to ask. Check the project's `CLAUDE.md` for infra notes first — often this answers most questions without needing to ask.
@@ -135,33 +135,49 @@ jobs:
- name: Build and push to local registry - name: Build and push to local registry
run: | run: |
set -e
# buildctl, NOT buildah (infra#241). The runner is a pod with
# `capabilities: drop [ALL]` and `allowPrivilegeEscalation: false`,
# so rootless buildah cannot remap UID/GID for a fresh base-image
# pull and the pod has no OCI runtime -- every `RUN` step fails.
# buildctl is a thin client: it hands the build to the host's
# buildkitd over a socket, so the privileged work happens on the
# host and the pod's securityContext is not in the picture.
# The failure is LATENT -- warm base-image layers mask it, so a
# buildah step can look fine for weeks and then fail on a cold pull.
BUILDKIT_HOST="unix:///run/buildkit/buildkitd.sock"
REGISTRY="localhost:5000" REGISTRY="localhost:5000"
REF="${REGISTRY}/${{ env.IMAGE }}:${{ steps.meta.outputs.sha-tag }}" REF="${REGISTRY}/${{ env.IMAGE }}:${{ steps.meta.outputs.sha-tag }}"
buildah build \ TAR_FILE="$(mktemp)"
--label "org.opencontainers.image.revision=${{ github.sha }}" \ trap 'rm -f "$TAR_FILE"' EXIT
--label "org.opencontainers.image.source=${{ github.repositoryUrl }}" \
-t ${REF} \ buildctl --addr "$BUILDKIT_HOST" build \
-t ${REGISTRY}/${{ env.IMAGE }}:latest \ --frontend dockerfile.v0 \
. --local context=. \
buildah push --tls-verify=false ${REF} --local dockerfile=. \
buildah push --tls-verify=false ${REGISTRY}/${{ env.IMAGE }}:latest --opt label:org.opencontainers.image.revision="${{ github.sha }}" \
[[ -n "${{ steps.meta.outputs.version-tag }}" ]] && \ --opt label:org.opencontainers.image.source="${{ github.repositoryUrl }}" \
buildah push --tls-verify=false \ --output type=oci,dest="$TAR_FILE"
${REF} \
${REGISTRY}/${{ env.IMAGE }}:${{ steps.meta.outputs.version-tag }} || true # buildctl has no registry-push equivalent to `buildah push`; skopeo
# copies the OCI archive up, once per tag.
skopeo copy --dest-tls-verify=false \
oci-archive:"$TAR_FILE" docker://${REF}
skopeo copy --dest-tls-verify=false \
oci-archive:"$TAR_FILE" docker://${REGISTRY}/${{ env.IMAGE }}:latest
if [ -n "${{ steps.meta.outputs.version-tag }}" ]; then
skopeo copy --dest-tls-verify=false \
oci-archive:"$TAR_FILE" \
docker://${REGISTRY}/${{ env.IMAGE }}:${{ steps.meta.outputs.version-tag }}
fi
echo "✓ Image pushed to ${REF}" echo "✓ Image pushed to ${REF}"
- name: Smoke test # No smoke test step. The one this skill used to recommend ran
run: | # `sudo k3s ctr run` against the host's containerd -- that worked when
REGISTRY="localhost:5000" # the runner was the Arch host, and is impossible now that it is a pod:
REF="${REGISTRY}/${{ env.IMAGE }}:${{ steps.meta.outputs.sha-tag }}" # no sudo, no ctr, no containerd socket (`sudo: command not found`).
CNAME="smoke-${{ steps.meta.outputs.sha-tag }}" # The deploy job's `Verify rollout` + `Confirm pod running new image`
sudo k3s ctr images pull --plain-http ${REF} # below assert the same property against the real workload.
OUTPUT=$(timeout 5 sudo k3s ctr run --rm ${REF} ${CNAME} /<binary-name> 2>&1 || true)
sudo k3s ctr containers delete ${CNAME} 2>/dev/null || true
echo "$OUTPUT" | grep -q "<project-name>" \
&& echo "✓ Smoke test passed" \
|| echo "⚠ Smoke test inconclusive: $OUTPUT"
# ── 3. Deploy to k3s ──────────────────────────────────────────────────────── # ── 3. Deploy to k3s ────────────────────────────────────────────────────────
deploy: deploy: