Files
Lucas BergerandClaude Opus 5 dad8774ed9 Document verified odin access path and Unraid API
Replace the assumed/unconfirmed environment notes with facts verified from
this box, and add docs/odin-access.md as the access reference.

Verified:
- Tailscale is the only path from dev to odin. dev (100.94.16.46) is a VPS
  with a public IP, not a LAN machine; odin is 100.101.253.105, and a subnet
  route puts 192.168.90.0/24 over tailscale0 at the same ~34ms RTT.
- The myunraid.net host is the canonical nginx vhost, not a cloud relay --
  DNS resolves it to the private 192.168.90.103, so traffic stays on the
  tailnet. The bare IP 404s on /graphql, and plain HTTP 302-redirects to the
  myunraid host while stripping the x-api-key header.
- GraphQL is live: unauthenticated POSTs return HTTP 200 with an
  UNAUTHENTICATED error body, so 200 must never be read as success.

Corrects unraid-docker-manager's claim that no SSL ignore is needed: TLS
verification fails from dev because ca-certificates 20250419 cannot chain
Let's Encrypt intermediate YR1. The cert is genuine and the claim likely
holds on odin itself, but curl needs -k here.

Container-polling field behaviours (UPPERCASE state, /-prefixed names,
PrefixedID ids, no isUpdateAvailable in 7.2) are carried over from
unraid-docker-manager, which is credited as the authoritative source.

No API key is present on this box; .env.unraid-api is gitignored there and
absent from the clone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 20:33:29 -04:00

5.8 KiB
Raw Permalink Blame History

Accessing odin

Reference for reaching the Unraid host (odin) from this workstation (dev) and driving its API. Everything here was verified from dev on 2026-08-15; the "Verified" column says how.

Related repo: luckberg/unraid-docker-manager on git.bergerhouse.net — an n8n Telegram bot that manages odin's containers. It is the origin of most of the API detail below and is worth reading before writing anything new against the Unraid API.


Network path: Tailscale

dev and odin are both on the tailnet. dev is a VPS (public IP 51.222.234.162), not a machine on the home LAN — Tailscale is the only path to odin.

Node Tailscale IP Notes
dev 100.94.16.46 this workstation (linux)
odin 100.101.253.105 the Unraid host (linux)

Other tailnet nodes seen: duplicati, experience (windows), fedora, nusgwvm, salt, google-pixel-8-pro.

A subnet route is advertised for the home LAN. odin's LAN address 192.168.90.103 is reachable from dev and routes over tailscale0:

$ ip route get 192.168.90.103
192.168.90.103 dev tailscale0 table 52 src 100.94.16.46

Both the tailnet IP and the LAN IP answer at ~34 ms RTT — the same path. This matters: the myunraid.net URL below looks like a cloud relay, but DNS resolves it to the private 192.168.90.103, so traffic goes direct over Tailscale and never leaves the tailnet. If Tailscale is down, none of it is reachable.


WebGUI / API endpoint

https://192-168-90-103.87c90a69c53e7197560c778a63f483e675130f2a.myunraid.net:8443/

The hostname encodes odin's LAN IP with dashes, under a per-server hash on myunraid.net. This is the canonical vhost — not merely a convenience alias. Use it for everything.

Why the plain LAN IP does not work

Both failure modes are real and confirmed:

Attempt Result Verified
https://192.168.90.103:8443/graphql 404 from nginx curl
http://192.168.90.103/graphql 302 → the myunraid.net URL curl
https://<myunraid-host>:8443/graphql 200, GraphQL responds curl

nginx only serves /graphql on the myunraid.net server_name; the raw IP vhost returns 404. Plain HTTP redirects to that hostname — and the redirect strips the x-api-key header, so a client that follows redirects authenticates as nobody. Always call the myunraid.net URL directly rather than relying on a redirect.

TLS caveat — correcting the other repo

unraid-docker-manager states "Valid certs via myunraid.net — no SSL ignore needed." That is not true from dev. Verification fails here:

depth=1 C=US, O=Let's Encrypt, CN=YR1
verify error:num=20:unable to get local issuer certificate
depth=0 CN=*.87c90a69c53e7197560c778a63f483e675130f2a.myunraid.net

The leaf is a genuine Let's Encrypt wildcard, but the chain presents an intermediate (YR1) that this box's CA bundle (ca-certificates 20250419) cannot chain to a trusted root — either the server omits the intermediate or the bundle predates it. The cert is not forged; the chain is incomplete for this client.

Practical consequence: curl needs -k from dev, and any script that talks to odin must either pass the insecure flag or pin/supply the issuer. n8n running on odin may well verify fine, which is likely why the other repo's claim held there. Prefer supplying the missing intermediate over making -k a permanent habit — -k disables verification entirely, which on a tailnet is a tolerable but real weakening.


Unraid GraphQL API

  • Endpoint: {UNRAID_HOST}/graphql, POST, where UNRAID_HOST is the myunraid.net base URL without the /graphql suffix.
  • Auth: x-api-key: <key> header.
  • Availability: native in Unraid 7.2+ (odin is on 7.2); 6.97.1 needs the Connect plugin.

Unauthenticated requests return HTTP 200 with a GraphQL error body — not an HTTP 401:

{"errors":[{"message":"Invalid CSRF token","extensions":{"code":"UNAUTHENTICATED",
 "originalError":{"error":"Unauthorized","statusCode":401}}}],"data":null}

Never treat HTTP 200 as success. Always inspect response.errors[].

Polling running containers

curl -sS -k -X POST "${UNRAID_HOST}/graphql" \
  -H 'Content-Type: application/json' \
  -H "x-api-key: ${UNRAID_API_KEY}" \
  -d '{"query":"query { docker { containers { id names state } } }"}'
{"data":{"docker":{"containers":[
  {"id":"<server_hash>:<container_hash>","names":["/n8n"],"state":"RUNNING"}
]}}}

Field behaviours that bite:

  • state is UPPERCASERUNNING, not running.
  • names is an array and entries are /-prefixed/n8n, not n8n.
  • id is a PrefixedID: {server_hash}:{container_hash}, two 64-char SHA-256 hex strings joined by a colon. The server half is identical for every container on odin.
  • isUpdateAvailable does not exist in the 7.2 schema, despite appearing in that repo's earlier research. Introspect before relying on any field.

API keys

Not present on this box — .env.unraid-api is gitignored and absent from the clone. Create a key scoped to what the script actually needs:

unraid-api apikey --create --name "<name>" --permissions "DOCKER:UPDATE_ANY" --json

Or WebGUI → Settings → Management Access → API Keys. Store it in the collection's own .env, never in git.


Gotchas carried over from unraid-docker-manager

  • Each Bash tool call is a fresh shell. Source the env file in the same command chain as the request: . .env.unraid-api; curl .... A separate source call is lost.
  • Large API responses: save to a temp file before parsing; piping a 400 KB body straight into python3 -c fails silently.
  • Docker API 204 No Content means success with an empty body.