ref:main

Bake in https://anvil.fangorn.io as the default server URL #50

merged colechristensen cole.christensen@gmail.com wants to merge fix/47-default-server-url into main

Closes #47

Why

A runner host reported this, with the daemon itself running fine:

$ /home/pi/.local/bin/anvil update
Error: not logged in — run `anvil auth login` first

Two config files, one of which nobody ever writes on a runner box. The daemon reads what you hand it via --config ~/.anvil-runner/config.json; anvil update reads the user config at ~/.config/anvil/config.json, which only auth login creates. And update sends no credentials at all — /runner/version, /runner/download, /runner/checksums are plain GETs with no auth header. It needed a server URL, asked for it through a getter whose only error is NotLoggedIn, and told the host to log in for a public binary. Self-update on runners was broken by construction.

The underlying assumption is stale. On-prem is no longer the plan; https://anvil.fangorn.io is the server in ~99% of invocations.

What changed

  • DEFAULT_SERVER_URL = "https://anvil.fangorn.io" in src/config.rs.
  • Config::server_url() is infallible (-> &str) and falls back to that default. token() keeps returning NotLoggedIn — the auth gate lives there, and only there.
  • Precedence unchanged in spirit: ANVIL_SERVER_URL > config file server_url > baked-in default. Staging and local installs keep working through the env var, which is already how the test suite points the binary at wiremock.
  • The default resolves per invocation and is never written to disk, so a host follows the default rather than pinning today’s value.
  • auth status bases logged_in on token presence alone and reports the resolved server either way — knowing where the CLI would talk is the useful half of the answer on an unconfigured host.
  • The four commands that print a web link (pr, issue, repo, release) now always print it instead of silently skipping when no URL was configured.

Tests

tests/default_server_url.rs, 10 tests, TDD — 6 failed against the old code, all 10 green now. They cover the full precedence chain, that resolving the default doesn’t persist it, that update --check needs no token, that an authenticated command still refuses without one, and both auth status states.

No test talks to production. The default is asserted through auth status, which resolves and reports the URL without a request; anything needing a round-trip is pointed at a wiremock.

Manual verification

Release binary, scratch ANVIL_CONFIG pointing at an empty directory, both env vars unset — i.e. the reported host state, against real production:

$ anvil auth status
Server https://anvil.fangorn.io
Not logged in. Run `anvil auth login` to authenticate.
exit=0
$ anvil update --check
Current d8b808e
Latest 2026.07.11
· Update available: d8b808e → 2026.07.11
exit=0

No config file was created. Auth gate confirmed intact — anvil issue list fangorn/anvil-cli with no token still exits 1 with not logged in.

Full suite passes (162 unit + all integration), cargo clippy --all-targets -- -D warnings clean, cargo fmt --check clean.

Requirements

REQ-CFG-001 … REQ-CFG-005, all covered; anvil requirement status exits 0.

Note for deployers

Existing hosts are unaffected — a configured server_url still wins. On the runner box, anvil update will now work, but the swap is a rename, so pid 241591 keeps the old inode until the service is restarted.

Created Jul 24, 2026 at 18:19 UTC | Merged Jul 24, 2026 at 19:16 UTC by colechristensen cole.christensen@gmail.com