Infernet Protocol

Documentation

Run a node. Submit jobs. Self-host.

Everything an operator or client needs to use the network. If you're looking for protocol-level design decisions, see the IPIPs on GitHub.

Workload note: Infernet targets single-GPU inference (7B–70B), embarrassingly parallel batch, LoRA fine-tunes, and async / federated distributed training. Tight-sync NVLink-bound training of 100B+ models stays on hyperscaler fleets — by design, not by accident. See the homepage positioning section for the full reasoning.

#Quick start

On Linux, macOS, or Windows (WSL2), one line gets you the infernet CLI plus everything it needs:

curl -fsSL https://infernetprotocol.com/install.sh | sh

Then bootstrap your environment (Ollama, model, firewall, identity, register with the control plane, start the daemon — all in one interactive walk-through):

infernet setup

Once setup is green, try inference end-to-end:

infernet "what is 2+2?"
# or pipe input
echo "summarize: water is wet" | infernet
Note · The CLI defaults to chat. infernet "..." is shorthand for infernet chat "...". Subcommands like setup, model, tui, etc. take precedence when present.

Windows (WSL2)

The installer is POSIX shell, so Windows operators run it inside WSL2 and the daemon picks up the GPU through Microsoft's WSL CUDA path:

# 1. From PowerShell, install WSL + Ubuntu (reboot when prompted)
wsl --install -d Ubuntu

# 2. Install the Windows NVIDIA driver — gives WSL CUDA without
#    needing a separate driver inside Ubuntu.
#    https://www.nvidia.com/Download/index.aspx

# 3. Open Ubuntu, then run the same one-liner:
curl -fsSL https://infernetprotocol.com/install.sh | sh
infernet setup
Note · Outbound paths (chat, remote model commands) work out of the box. Direct P2P inbound on :46337 needs a netsh interface portproxy rule on the Windows host — skip it if you only care about earning via routed jobs.

How Node gets installed

The installer uses mise to install Node 20 — single static binary under $HOME/.local/share/mise, no apt-repo dance, no conflict with whatever Node a base image already shipped (e.g. RunPod's vllm-workspace ships Node 12). The wrapper at ~/.local/bin/infernetprepends mise's shims dir so the right Node is picked regardless of whether your shell has activated mise yet.

#CLI overview

The infernet binary groups commands into four buckets:

GroupCommandsPurpose
Setupsetup, modelBootstrap the runtime + manage what models you serve
Node lifecycleinit, login, register, update, removeConfigure node identity + (de)register with a control plane
Daemonstart, stop, status, stats, logs, serviceRun the long-lived process and inspect it
Diagnosticsgpu, firewall, chat, tui, doctorProbe the local box + interact with the network
Paymentspayout, paymentsConfigure your payout addresses + view earnings

Run infernet help for the live list. Every subcommand accepts --help.

#infernet setup

The full bootstrap. Idempotent — re-run anytime to fix or update. Walks through each step and asks before doing anything privileged (sudo prompts use your terminal directly).

infernet setup

[1/8] Node.js                ✓ v22.21.1
[2/8] Ollama                 ✗ Ollama not installed
                              Will run: curl -fsSL https://ollama.com/install.sh | sh
                              Install Ollama now (will sudo)? [Y/n] y
                              ...
                              ✓ done
[3/8] Model                  Which model should this node serve?
                                1) qwen2.5:0.5b   ≈400 MB  smoke test, runs on CPU
                                3) qwen2.5:7b     ≈4.4 GB  fits 8 GB+ GPU — recommended
                              Choice [3]:
                              → pulling qwen2.5:7b via the ollama CLI...
                              ✓ verified: ollama can serve qwen2.5:7b
[4/8] Firewall (port 46337)  ✓ rule applied via ufw
[5/8] Config                 ✓ saved to ~/.config/infernet/config.json
[6/8] Identity & control plane
                              ! no identity yet — running init
                              ...
                              ✓ identity: 5d0de683…
[7/8] Provider registration  ✓ registered
[8/8] Daemon                 ✓ daemon running

✓ setup complete

Useful flags:

infernet setup --confirm                # auto-yes every prompt
infernet setup --model qwen2.5:7b       # preselect the model
infernet setup --no-firewall            # skip firewall step (e.g. inside containers)
infernet setup --skip-pull              # leave models alone
infernet setup --backend stub           # use canned tokens (no real GPU)

#infernet (chat)

Default verb. infernet "<prompt>" sends a chat job; if a control plane is configured, it routes through the P2P network, otherwise falls back to your local engine.

# Network (default when controlPlane.url is set)
infernet "what is 2+2?"

# Force the local engine — useful for smoke testing without a network
infernet --local "ping"

# Force network — error if no control plane configured
infernet --remote "what is the capital of France?"

# Pipe input
echo "summarize this paragraph" | infernet

# Pin a specific model (overrides config)
infernet --model qwen2.5:7b "tell me a joke"

# JSON event stream — one event per line (meta, token, done)
infernet --json "ping"

Common flags:

FlagEffect
--remote / --localForce routing path
--url <url>Override control-plane URL (network mode)
--model <name>Pin a model id (e.g. qwen2.5:7b)
--system <text>Prepend a system message
--temperature <n>Sampling temperature
--max-tokens <n>Cap on generated tokens
--jsonEmit raw NDJSON events instead of token stream
--backend <kind>Local: ollama | mojo | stub

#infernet model

Manage the models your node has on disk and which one is the active default.

infernet model list                  # show pulled models + which is active
infernet model pull qwen2.5:7b       # pull (Ollama progress bar inline)
infernet model use qwen2.5:7b        # set as engine.model in config
infernet model show                  # current backend, host, active model
infernet model remove qwen2.5:0.5b   # delete from disk (clears active if it was)
Note · Distinct from infernet update / remove, which are about your node's registration on the control plane — not about local models on disk.

#infernet init

Configure node identity (Nostr keypair) and the control plane to talk to. Idempotent — re-run to finish a partial config without losing engine settings written by setup.

infernet init

Control-plane URL [https://infernetprotocol.com]:        ← Enter to accept
Node role (provider|aggregator|client) [provider]:        ← Enter to accept
Human-readable node name [provider@hostname]: my-vps-1
... auto-generates Nostr keypair, detects address, asks about firewall ...
Wrote /home/ubuntu/.config/infernet/config.json
Pubkey: 5d0de683a5f22aa1d5a8927a431d86601277aad61fc7cdce126ac8c012e2c84d

Non-interactive flags:

infernet init --url https://infernetprotocol.com \
              --role provider \
              --name my-vps-1 \
              --p2p-port 46337 \
              --no-advertise          # outbound-only mode (Tor-friendly)

#infernet start

Boot the long-running daemon. Detaches by default; use --foreground for systemd / process supervisors.

infernet start                    # detach to background, return shell prompt
infernet start --foreground       # for systemd (Type=simple) or PM2

# Inspect state
infernet status                   # remote row + local stats
infernet stats                    # live in-memory snapshot via IPC
infernet logs                     # tail ~/.config/infernet/daemon.log
infernet stop                     # graceful shutdown via IPC, signal fallback
Note · For boot-persistence with auto-restart, prefer infernet service install (systemd userland unit) — see the dedicated section below.

#infernet doctor

Six independent checks across local config, engine, control plane, daemon, registration row, and an end-to-end test inference. Useful on a fresh box and after every config change.

infernet doctor

[1/6] Local config           ✓ provider/my-vps-1, key=5d0de683a5f2…
[2/6] Engine                 ✓ Ollama up @ http://localhost:11434, model qwen2.5:7b pulled
[3/6] Control plane          ✓ https://infernetprotocol.com (HTTP 200, 124ms)
[4/6] Daemon                 ✓ pid=12345, last heartbeat 4s ago, jobs 12/13
[5/6] Provider row           ✓ registered as 5d0de683…, status=available, gpu=A100
[6/6] End-to-end             ✓ routed via=p2p, provider=self, first token 740ms, total 1280ms

All checks passed.

Skip the e2e test (which submits a real tiny job):

infernet doctor --skip-e2e

#infernet payout

Configure where you want to be paid for served jobs. CoinPayPortal sends payouts to whatever addresses you set here. Operators who already have wallets paste their addresses; operators who don't can have a non-custodial BIP39 wallet generated and the encrypted seed phrase delivered to their PGP key.

# Bring your own wallet (recommended — non-custodial)
infernet payout set BTC mainnet bc1q9h6zcv4fp9kx5n0jq5qf3w2yxz4z3y2w5
infernet payout set ETH mainnet 0x742d35Cc6634C0532925a3b844Bc9e7595f8B59f
infernet payout set SOL mainnet 5RaBwVPRBfpz9MRBnGRcQAPfvKxcUVtxL2ANe9XJ
infernet payout set USDC solana 5RaBwVPRBfpz9MRBnGRcQAPfvKxcUVtxL2ANe9XJ
infernet payout set USDC polygon 0x742d35Cc6634C0532925a3b844Bc9e7595f8B59f

# Lightning — paste your bolt12 (recommended) or LNURL-pay address
infernet payout set BTC ln lno1qsgqmqvgm96frzdg8m0gc6nz...

# Don't have an LN node? Opt-in to a CoinPay-custodial channel:
infernet payout set BTC ln --provision

# Inspect
infernet payout list
infernet payout remove ETH mainnet
infernet payments                    # recent transactions (signed read)
Heads up · Lightning is the single custodial exception. All other coins are fully non-custodial — CoinPay generates the BIP39 seed if you ask it to, delivers it GPG-encrypted, and never holds the keys.

#infernet tui

Live terminal dashboard. Polls the daemon IPC + local Ollama; renders system / jobs / engine / peers panels. q to quit, r to refresh, --refresh <ms> to tune.

infernet tui                # default 2s poll
infernet tui --refresh 1000 # 1s — for live debugging

#infernet service (systemd)

Optional boot-persistent runner via a userland systemd unit. No sudo, no system-wide changes, lives under ~/.config/systemd/user/. Linux only.

# Install the unit (writes ~/.config/systemd/user/infernet.service)
infernet service install
infernet service enable          # systemctl --user enable --now infernet

# Inspect
infernet service status
infernet service logs            # journalctl --user -u infernet -f

# (Optional) make it survive logout / reboot when not logged in:
loginctl enable-linger $USER

# Tear down
infernet service disable
infernet service uninstall

Preview the unit file without writing anything:

infernet service unit

#Sign up / sign in / reset password

Browser-side auth lives at /auth/* on the control plane. All flows are server-side cookie-based via @supabase/ssr — the browser never sees a Supabase URL or key directly.

PageWhat it does
/auth/signupEmail + password. Sends a confirmation link.
/auth/loginEmail + password, OR leave password blank for a magic link.
/auth/reset-passwordEmail a recovery link.
/auth/update-passwordSet a new password (you land here from the recovery email).
/auth/check-email"We sent you a link" success screen.

SDK / CLI consumers use the same routes with Content-Type: application/json:

# Send a magic link
curl -sS -X POST https://infernetprotocol.com/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com"}'

# Or password
curl -sS -X POST https://infernetprotocol.com/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"hunter22"}' \
  -c cookies.txt

# Use the resulting cookie for /api/admin/* (when those land — IPIP-0003 phase 4)
Note · Branded transactional emails ship from the Supabase SMTP setup with the HTML templates in docs/AUTH_EMAIL_TEMPLATES.md. Operators self-hosting paste those into their own Supabase dashboard.

#Dashboard

Signing in lands you on /dashboard. It's your private, per-user view of the network: only resources linked to your account show up here. Network-wide stats live on /status instead.

The dashboard surfaces, all queried server-side:

  • Earnings (last 30d / all-time / # confirmed payments) — sum of inbound payment_transactions joined to your providers.
  • Spend on the same shape, joined to your clients.
  • GPUs in use + models served — totals across the providers.specs.gpus / .models jsonb of every provider you operate.
  • Provider-node table with status pills + last-seen timestamps.
  • CPU pool — host CPU groups across your nodes.
  • Interconnect summary — NVLink / xGMI / InfiniBand / EFA / RDMA capability across your providers (see the interconnects section below for what gets detected).
  • Linked Nostr identities— which pubkeys you've claimed for which roles (provider, client,aggregator).
  • Recent jobs you submitted (best-effort match by client label until the jobs table grows a client_id FK).

Empty cards print the exact CLI command that would populate them — e.g. infernet register for a node, infernet pubkey link for an identity. No fake numbers.

The user-scoped joins go through pubkey_links (per IPIP-0003): auth.users.id → pubkey_links.user_id → providers.public_key / clients.public_key. Routes that need the current user import getCurrentUser() from apps/web/lib/supabase/auth-server.js; unauth'd hits redirect to /auth/login?next=/dashboard.

#Interconnects (NVLink / xGMI / InfiniBand / EFA)

Infernet auto-detects GPU-to-GPU and host-to-host fabric on every infernet setup and infernet gpu run, advertises a coarse capability summary at registration time, and emits the right environment variables when spawning an engine subprocess so NCCL / RCCL / libfabric actually use the fabric instead of silently falling back to TCP.

What gets detected

FabricHardwareDetection source
NVLinkNVIDIA datacenter + consumer multi-GPUnvidia-smi topo -m
xGMI / Infinity FabricAMD MI200 / MI300 seriesrocm-smi --showtopo
InfiniBand / RoCEMellanox / Nvidia ConnectX, RoCE NICs/sys/class/infiniband/* (Linux)
AWS EFAAWS p4d / p5 / Trainium instanceslspci -d 1d0f: (Linux)

Detection is best-effort: any vendor command that's missing or fails is silently skipped — running on a CPU-only laptop or an AMD-only host doesn't error, the relevant detector just returns an empty result. Topology is classified pair, mesh, or all-to-all by counting deduped GPU-pair edges against the complete-graph upper bound.

See it on your hardware:

infernet gpu                           # human summary
infernet gpu --json | jq .interconnects # raw detected shape

What gets advertised

infernet register includes a coarsened capability block in specs.interconnects — no device names, no board IDs, no PCI BDFs. Just enough for the matchmaker to route, not enough to fingerprint your hardware:

{
  "specs": {
    "interconnects": {
      "nvlink":     { "available": true,  "topology": "all-to-all", "link_count": 6 },
      "xgmi":       { "available": false, "topology": "none",       "link_count": 0 },
      "infiniband": { "available": true,  "active_port_count": 2 },
      "efa":        { "available": false, "adapter_count": 0 },
      "rdma_capable": true
    }
  }
}

Engine-spawn env vars

When the daemon launches an engine for a job, it merges the output of interconnectEnv(capability)into the child's environment. These are the standards downstream toolkits already honor — setting them is the difference between NCCL using IB at 200 Gbit/s and NCCL falling back to TCP at 10 Gbit/s.

WhenEnv vars set
NVLink availableINFERNET_NVLINK=1, INFERNET_NVLINK_TOPOLOGY=<topo>
xGMI availableINFERNET_XGMI=1, INFERNET_XGMI_TOPOLOGY=<topo>
≥1 active IB port with a nameNCCL_IB_DISABLE=0, NCCL_IB_HCA=mlx5_0:1,mlx5_1:1,…
EFA adapter presentFI_PROVIDER=efa, NCCL_PROTO=simple, INFERNET_EFA=1
RDMA-capable (any of IB / EFA active)INFERNET_RDMA=1

The INFERNET_* markers are advisory — useful for our adapters that branch on fabric (e.g. opting into tensor-parallel inference modes). The NCCL_* and FI_*are the load-bearing standards every distributed-training framework already reads. Job-level launch profiles can override any of these; the daemon won't clobber an explicit caller value.

Note · Full normative spec, including the client-side requires.interconnects matchmaking shape, lives in IPIP-0008.

#Public API endpoints

These are the wire surfaces. Two auth tiers — the protocol (Nostr-signed) and the dashboard (cookie session) — plus a small public-read surface that's IP-rate-limited.

EndpointMethodAuthWhat
/api/healthGETnoneLiveness probe (no DB)
/api/peers?limit=NGETnoneBootstrap seed peers (IPIP-0006)
/.well-known/did.jsonGETnonePlatform DID document (IPIP-0007)
/api/overviewGETnoneAggregate network stats
/api/chatPOSTnone + IP-rateSubmit a chat job (legacy alias for /api/jobs/submit)
/api/chat/stream/[jobId]GET (SSE)noneStream tokens + events
/api/v1/node/registerPOSTX-Infernet-AuthProvider registration (signed)
/api/v1/node/heartbeatPOSTX-Infernet-AuthLiveness ping
/api/v1/node/jobs/pollPOSTX-Infernet-AuthPull assigned jobs
/api/v1/node/jobs/[id]/eventsPOSTX-Infernet-AuthStream tokens back
/api/v1/node/jobs/[id]/completePOSTX-Infernet-AuthMark completed; triggers receipt
/api/auth/{signup,login,callback,logout,reset-password,update-password}POST/GETcookie sessionDashboard auth flows

Submit a chat job (anonymous, IP-rate-limited):

curl -sS -X POST https://infernetprotocol.com/api/chat \
  -H "Content-Type: application/json" \
  -d '{
    "messages":[{"role":"user","content":"hello"}],
    "modelName":"qwen2.5:7b",
    "maxTokens":256
  }'

# → { "jobId":"...", "streamUrl":"/api/chat/stream/..." }
# Then SSE-tail streamUrl for tokens.

Bootstrap seed peers:

curl -sS https://infernetprotocol.com/api/peers?limit=10 | jq .

# → {
#   "data":[
#     {
#       "pubkey":"5d0de683…",
#       "multiaddr":"/ip4/162.250.189.114/tcp/46337",
#       "last_seen":"2026-04-26T12:00:00Z",
#       "served_models":["qwen2.5:7b"],
#       "gpu_model":"A100"
#     }
#   ]
# }

Verify a CPR Receipt issued by the platform:

curl -sS https://infernetprotocol.com/.well-known/did.json | jq .verificationMethod

# Use the publicKeyMultibase to verify any Receipt signed by
# did:web:infernetprotocol.com.

#Self-host the control plane

Infernet's control plane is open-source (MIT) and Docker-shippable. Self-hosting means you run your own Next.js app + Supabase (cloud or self-hosted) and point your operators at your URL via infernet init --url https://your-infernet.example. The protocol itself is permissionless — your operators can keep speaking to other control planes too.

When to self-host

  • You run a private GPU pool for one organization and don't want jobs leaving it.
  • You need data residency in a specific region.
  • You want to fork the dashboard, ship a different model catalog, or run an internal billing layer.
  • You're building on top of the protocol and want a sandbox to break.

1. Clone, configure, build

git clone https://github.com/profullstack/infernet-protocol.git
cd infernet-protocol
cp sample.env .env
node tooling/generate-secrets.mjs >> .env       # CLI session, cron, DID keys
$EDITOR .env                                    # fill SUPABASE_*, RESEND, CoinPay, etc.

pnpm install
pnpm --filter @infernetprotocol/web build

2. Provision Supabase

Either flavor works:

  • Supabase Cloud — fastest path. Create a project, copy the URL + anon key + service-role key into .env, then run supabase db push from this repo to apply migrations.
  • Self-hosted Supabase — boot the official Docker stack, then in this repo run supabase start && supabase db reset for local Postgres + Auth + Realtime + migrations applied.

3. Required environment variables

# --- Supabase ---------------------------------------------------------------
NEXT_PUBLIC_SUPABASE_URL=https://<project-ref>.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
SUPABASE_SERVICE_ROLE_KEY=eyJ...                  # server-only, never browser

# --- Public-facing URL (used by every redirect, OG card, install.sh) -------
NEXT_PUBLIC_APP_URL=https://your-infernet.example

# --- Auth (CLI bearer JWTs, cron auth, DID:web platform identity) ----------
INFERNET_CLI_SESSION_SECRET=...                   # generate-secrets.mjs fills these
INFERNET_CRON_SECRET=...
INFERNET_PLATFORM_DID_PRIVATE_KEY=...

# --- Email (sign-up confirmation, password reset, /contact) ----------------
RESEND_API_KEY=re_...

# --- Optional: NIM fallback for the public /chat playground ----------------
NVIDIA_NIM_API_KEY=nvapi-...

# --- Payments (CoinPayPortal — non-custodial except Lightning) -------------
COINPAY_ISSUER_API_KEY=cprt_...

# --- Web server -------------------------------------------------------------
PORT=8080

4. Run it

Pick a hosting target:

Docker

# Multi-stage build — preserves the pnpm workspace topology.
docker build -t infernet-control-plane -f docker/Dockerfile .

# Run with your .env mounted in
docker run --env-file .env -p 8080:8080 infernet-control-plane

Railway / Fly / Render

Point the platform at docker/Dockerfile as the builder and copy the env vars from your .envinto the platform's secret manager. Railway specifically: set the build command to nothing (Dockerfile handles it), healthcheck path to /api/health, and PORT to 8080 (or leave $PORT auto-injection alone — next start honors it). Add your custom domain at the platform; in your DNS, the apex record points at the platform's edge.

Bare Linux + systemd

pnpm --filter @infernetprotocol/web start       # http://localhost:8080
# Front it with nginx/Caddy + Let's Encrypt for TLS, then bind a systemd
# unit so it restarts on crash. apps/cli/commands/service.js generates a
# matching unit for the GPU node side.

5. Verify the deploy

curl -fsS https://your-infernet.example/api/health
# {"ok":true,"uptime_s":...,"node_env":"production","commit":"<sha>"}

# Round-trip the device-code login from a workstation:
infernet init --url https://your-infernet.example
infernet login                                  # opens browser → polls
infernet "ping"                                 # default verb is chat

If /api/healthreturns 502, the container is down — most often a missing env var at boot. Check the platform's deploy logs first, before debugging anything in the app.

6. Operators on your deployment

infernet init --url https://your-infernet.example
infernet setup                                  # installs Ollama, pulls a model, opens firewall
infernet register                               # advertises the node on your control plane
infernet start                                  # daemon takes paying jobs

#Architecture

Three components, two trust tiers:

┌────────────────────────────────────────────────────────┐
│  Control plane (Next.js + Supabase)                    │
│   /api/v1/node/*      provider tier  (Nostr-signed)    │
│   /api/admin/*        dashboard tier (cookie session)  │
│   /api/chat, /api/peers, /.well-known/did.json         │
│                                                        │
│   Writes CPR Receipts to coinpayportal.com on every    │
│   completed job — operator reputation accumulates      │
│   automatically.                                       │
└─────────────────────────────────────┬──────────────────┘
                                      │  (server-side only)
                                      ▼
                              Supabase / Postgres
                                      ▲
        Nostr-signed HTTP             │           anon SSE / cookie
        (BIP-340 Schnorr)             │
   ┌────────────┐   ┌──────────┐   ┌────────────────────┐
   │ infernet   │…  │ Browsers │   │ NVIDIA NIM         │
   │ daemon     │   │ /chat    │   │ (fallback only)    │
   │ Ollama     │   │ /status  │   │                    │
   │ TCP 46337  │   │ /docs    │   └────────────────────┘
   └────────────┘   └──────────┘

Read the IPIPs for the locked-in design decisions:

  • IPIP-0001 — v1.0 launch criteria
  • IPIP-0002 — operator P2P chat (Nostr DMs + rooms)
  • IPIP-0003 — auth + account model (two-tier, pubkey linking)
  • IPIP-0004 — multi-currency payments via CoinPayPortal
  • IPIP-0005 — data access architecture (no DB clients in browsers/CLI/SDK)
  • IPIP-0006 — peer discovery + bootstrap (Nostr capability + libp2p Kad)
  • IPIP-0007 — CoinPay Reputation Protocol integration

#Troubleshooting

`infernet doctor` reports Ollama not reachable

Cause — Ollama not installed, or installed but not running.

Fix — Run infernet setup — it'll detect both states and offer to fix. If you prefer manual: curl -fsSL https://ollama.com/install.sh | sh then sudo systemctl start ollama.

`infernet doctor` reports control plane unreachable

Cause — Wrong URL in config, or the control plane is genuinely down.

Fix — Check with curl https://infernetprotocol.com/api/health. If the public site is up but yours doesn't reach it, your network may be filtering outbound. Run infernet login --url <new-url> if you need to repoint.

"job not assigned to this provider" on complete

Cause — Daemon picked up a job but its public_key on the control plane doesn't match. Usually means you regenerated the keypair without re-registering.

Fix — Run infernet register to re-upsert; then restart the daemon.

Inference is much slower than expected

Cause — Likely running on CPU — Ollama installs without CUDA on a fresh box.

Fix — Run nvidia-smi while a job is in flight. If VRAM doesn't move, Ollama isn't using the GPU. Reinstall after installing CUDA, OR switch to a smaller model (qwen2.5:0.5b runs reasonably on CPU).

`infernet chat` returns error about missing model

Cause — config.engine.model unset or pointing at a model not pulled.

Fix — Pull and select: infernet model pull qwen2.5:7b && infernet model use qwen2.5:7b. infernet model show confirms.

Email confirmation never arrives

Cause — Supabase SMTP not configured, or sender domain not warmed up.

Fix — Check Supabase Dashboard → Authentication → SMTP settings. Verify your sender domain has SPF + DKIM. Most "missing email" issues are quarantine — check spam, Promotions, Updates folders.

HTTP 502 from infernetprotocol.com

Cause — Control plane container is down or env vars are missing at boot.

Fix — Check /api/health first — a 502 there means the container is fully dead; missing-env-var crashes are the most common cause. SSH (or Railway dashboard) and check the deploy log.