duckeighty: doctor
Run the checks below in order. For each, run the exact command, judge the result, and note the single fix command if it fails. At the end, produce a compact table (check / status / fix) and point the user to the single next action.
This skill only diagnoses. Do not run setup-ca, setup-resolver, or
up from here — direct the user to the duckeighty-init skill or the manual command.
When a fix command is ./duckeighty.sh …, the user needs to run it from
their duckeighty checkout. Resolve the path in this order, no guessing:
$DUCKEIGHTY_CHECKOUTenv var if set.If the ingress is running, ask Docker directly:
docker compose ls --format json \ | python3 -c "import json,sys,os; p=next((x for x in json.load(sys.stdin) if x['Name']=='duckeighty'),None); print(os.path.dirname(os.path.dirname(p['ConfigFiles'])) if p else '')"This returns the checkout root (parent of
infra/compose.yaml) when theduckeightyproject is running.Otherwise ask the user for the path.
If the user has no checkout at all, tell them to run the duckeighty-init skill first instead of printing a fix command.
1. Docker is usable
docker version --format '{{.Server.Version}}'
Fails → "Start Docker Desktop (or your Docker daemon) and re-run."
2. shared_ingress network exists
docker network inspect shared_ingress >/dev/null 2>&1 && echo ok || echo missing
missing → "Run ./duckeighty.sh up from the duckeighty checkout. It
creates the network before compose up."
3. duckeighty compose project is up
docker compose ls --format json | python3 -c "import json,sys; print(next((p['Status'] for p in json.load(sys.stdin) if p['Name']=='duckeighty'),'missing'))"
Expect something like running(2). missing → ./duckeighty.sh up.
4. Both ingress containers exist and are on shared_ingress
docker ps --filter 'label=com.docker.compose.project=duckeighty' --format '{{.Names}}\t{{.Networks}}\t{{.Status}}'
Expect two rows: duckeighty-traefik-1 and duckeighty-dnsmasq-1, both
Up. The traefik row's Networks column must contain shared_ingress; if
not, the ingress compose diverged from upstream.
5. macOS resolver entry
test -f /etc/resolver/duckeighty.test && cat /etc/resolver/duckeighty.test
Expect:
nameserver 127.0.0.1
port 5354
Missing or wrong → "Run ./duckeighty.sh setup-resolver (sudo required,
one-time)."
6. DNS actually answers
dig +short @127.0.0.1 -p 5354 probe.duckeighty.test
Expect 127.0.0.1. Empty or timeout → dnsmasq is not listening on
5354/udp on 127.0.0.1. Check docker logs <dnsmasq container>; confirm
the published ports in infra/compose.yaml are intact.
7. Local CA is trusted
mkcert -CAROOT
ls "$(mkcert -CAROOT)/rootCA.pem" 2>/dev/null
Both must succeed. Missing → ./duckeighty.sh setup-ca.
If mkcert itself is missing, ask the user to install it:
brew install mkcert nss.
8. HTTPS path reaches Traefik
curl -sI https://no-such-app.duckeighty.test/ | head -1
A HTTP/2 404 response from Traefik is the happy path: DNS + TLS both
work, and the hostname just has no matching router yet. Other failures:
curl: (6) Could not resolve host→ step 5 or 6 failed- TLS / certificate errors → step 7 failed, or the user did not restart the browser / shell after installing the CA
curl: (7) Failed to connect→ step 3 failed, or ports 80/443 are occupied by another process
Report format
check status fix
1. docker daemon OK —
2. shared_ingress network OK —
3. duckeighty compose project OK —
4. traefik + dnsmasq on net OK —
5. /etc/resolver/… MISSING ./duckeighty.sh setup-resolver
6. DNS resolution FAIL (after step 5)
7. mkcert local CA OK —
8. HTTPS reaches traefik FAIL (after step 5)
End with the single first command the user should run. Do not dump all per-step fixes when one upstream fix unblocks the rest.