Files
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

487 lines
21 KiB
Markdown

---
title: Remote Access
description: Access AgentsView from other devices on your network
---
AgentsView binds to `127.0.0.1` by default, so only your local machine can reach
it. To make the UI available from other devices, you need to do two things:
- listen on a non-loopback address or put the server behind a proxy
- require a bearer token for API access
In current releases, bearer-token auth is controlled by `require_auth`.
## Quick Setup
For a one-off background server, enable auth in `~/.agentsview/config.toml`:
```toml
require_auth = true
```
Then pass a one-off non-loopback bind:
```bash
agentsview serve --background --host 0.0.0.0
```
Omit `--background` when you want that `serve` invocation to stay in the
foreground. The explicit `--host` flag also permits a one-off unauthenticated
non-loopback bind, though authenticated access is strongly recommended.
For a persistent, config-driven node — so restarts and auto-started daemons keep
the server reachable — set both values in `config.toml` instead of passing a
flag each time:
```toml
host = "0.0.0.0"
require_auth = true
```
Then use the canonical writable-daemon lifecycle:
```bash
agentsview daemon start
agentsview daemon status
# After configuration changes:
agentsview daemon restart
```
A non-loopback `host` in `config.toml` requires `require_auth = true`; the
server refuses to start rather than persistently exposing an unauthenticated
API. The `--host` flag remains available for one-off unauthenticated binds.
When the server runs inside WSL, AgentsView advertises the WSL `eth0` address
instead of `127.0.0.1` so the printed URL is usable from the Windows host and
nearby LAN clients. An explicit `--public-url` still takes precedence.
When auth is enabled, AgentsView generates a token if needed and stores it in
`~/.agentsview/config.toml`. Open `http://<your-ip>:8080` from another device
and enter the configured token in the frontend, or send it in an `Authorization`
header.
!!! note
Older configs may still use `remote_access = true`. AgentsView still reads
that legacy key, but new setups should use `require_auth = true`.
## Authentication
When `require_auth` is enabled, all `/api/` requests must include:
```
Authorization: Bearer <token>
```
The token is stored in `config.toml` as `auth_token`. It is generated
automatically the first time auth is enabled, so you do not need to mint one
manually. For supervised services or temporary overrides, set
`AGENTSVIEW_AUTH_TOKEN`; it takes precedence over the config file.
Auth is enforced on API routes, not on static assets. That means a browser can
load the HTML shell, but API requests fail with `401` until the correct token is
supplied.
When `require_auth` is disabled, normal local loopback use remains ungated.
## HTTP Remote Sync
Configured `[[remote_hosts]]` entries can use `transport = "http"` when the
remote machine is already running an AgentsView daemon:
```toml
[[remote_hosts]]
host = "devbox1"
transport = "http"
url = "http://devbox1.tailnet.ts.net:8080"
token = "remote-token"
interval = "5m" # optional: sync periodically while the collector daemon runs
```
Treat `host` as the remote machine's stable, unique identity. AgentsView uses it
to namespace imported session IDs, the database skip cache, and the mirror
directory. Changing it creates a new namespace and can duplicate sessions from
the same machine; reusing it for a different machine can reuse stale cached
state. Changing only `url` is fine when the same logical machine moves.
The daemon on the remote machine must bind a non-loopback interface (set
`host = "0.0.0.0"` with `require_auth = true` in its config.toml) or every sync
fails with a connection-refused error. See
[Remote Hosts](/configuration/#remote-hosts) for the full remote-side setup,
including keeping detached daemons alive.
Use `require_auth = true` on remote nodes when practical, or at minimum keep
their generated `auth_token` configured. The remote archive endpoints always
require `Authorization: Bearer <token>`, even when the rest of the daemon API is
unauthenticated. The `token` in the collector's `remote_hosts` entry must match
the remote daemon's `auth_token` and is required for HTTP remote sync. Do not
reuse the collector daemon's own `auth_token` for untrusted remote endpoints.
The HTTP transport is intended for private networking such as Tailscale or an
equivalent restricted overlay. Do not expose raw archive endpoints directly to
the public internet.
HTTP remote sync failures are summarized without echoing remote-controlled URLs
or response bodies. Common summaries point at the specific fix: a rejected token
means the collector's per-host `token` does not match the remote daemon's
`auth_token`; a missing endpoint means the remote host needs a newer AgentsView;
connection refusal usually means the daemon is not running, is still bound to
loopback, or the URL port is wrong; DNS and timeout messages point back to the
configured `url`.
For always-available fleet nodes launched with `agentsview daemon start`, set:
```toml
daemon_idle_timeout = "0s"
```
That setting controls detached writable daemons, including config-driven and
`serve --background` launches. Supervised daemons run under systemd, launchd,
Docker, or a foreground shell never create the idle tracker and already stay
alive until their supervisor stops them.
Run `agentsview sync` on the collector to sync local sessions and every
configured host. Set `interval` on a `[[remote_hosts]]` entry when a running
collector daemon should sync that host periodically; omit it or set it to `0s`
for manual sync only. See [`agentsview sync`](/commands/#agentsview-sync) for
single-host selection and failure behavior.
## Incremental Sync
Starting in 0.37.4, HTTP remote sync keeps a persistent mirror of each remote
machine's syncable source files under:
```text
<data_dir>/remote-mirrors/<sanitized-host>-<hash>/
```
For the default data directory, that parent is `~/.agentsview/remote-mirrors/`.
The readable host component is followed by a hash so names that sanitize to the
same directory name do not collide. A lock file next to each mirror serializes
concurrent syncs of that host from the same data directory.
The mirror adds an on-disk copy of the remote session sources to the collector,
in addition to the indexed database. Budget roughly the size of each remote
host's syncable source corpus for it. Incremental transfer applies only to the
HTTP transport; SSH remote sync continues to copy a full session tree on each
run.
### How A Sync Works
1. The collector asks the remote daemon for its resolved agent roots. The remote
controls this allowlist; the collector cannot name arbitrary roots.
1. The collector locks the host mirror and requests a gzip-compressed manifest
of regular, non-symlink files. Each entry carries its absolute remote path,
size, and modification time.
1. The collector walks the mirror and compares file size and modification time
at microsecond precision. It schedules missing or changed files for fetch and
removes mirror files that disappeared from the manifest.
1. When fewer than half of the manifest's files need fetching, the collector
requests only that delta. At half or more, it requests a full archive instead
of sending a large file list. The current collector advertises gzip support
for both archive modes.
1. The archive is extracted into the mirror with remote modification times
preserved. AgentsView then imports from the complete mirror, so parsers that
read sibling files behave the same way they do against a full source tree.
The separate database skip cache avoids unnecessary parsing of unchanged
sessions during normal syncs.
For a configured full sync that includes local sources, mirror preparation
finishes before database work begins. The collector then ingests local sources
and every prepared HTTP mirror through the same batched temporary-database path,
with FTS maintenance suspended during ingest and rebuilt once before an atomic
swap. Configured SSH hosts run afterward. A preparation, parser, batch-write, or
FTS failure leaves the active archive unchanged and prevents the SSH phase.
Remote-only syncs, including
`agentsview sync --host <configured-http-host> --full`, continue to import into
the active archive and do not use the combined rebuild path.
If no directory-scoped files changed, the collector skips the archive request
and imports directly from the existing mirror. Files that disappear remotely are
deleted from the mirror, but remote import is intentionally non-destructive:
sessions already stored in the AgentsView database remain available.
Windsurf is a special case. Its state database is sanitized into a curated
export for every transfer, so the raw tree cannot safely participate in the
manifest. AgentsView fetches that small export as a separate full archive on
each sync while the rest of the host remains eligible for delta transfer. The
Windsurf content in the mirror is the sanitized export, not a byte-for-byte copy
of the remote state database.
### When AgentsView Downloads A Full Archive
A full HTTP transfer occurs in these cases:
- the per-host mirror is new or was removed
- at least half of the manifest files are missing or changed
- a delta request is rejected after a manifest succeeded; the collector retries
once with a full archive
- the remote daemon does not support manifests, in which case the collector uses
the legacy full-transfer path on every sync
Windsurf's curated export is also fetched in full on every sync, independently
of the directory-scoped archive decision.
`--full` reparses every discovered remote session but still uses the manifest
comparison to decide which mirror bytes need transferring. It does not delete
the local database or turn remote sync into a destructive reconciliation.
### Compatibility And Recovery
A current collector works with older remote daemons that already expose the
HTTP remote-sync target and archive endpoints. A missing manifest route —
including an old daemon's HTML app shell answering that route — makes the
collector report that incremental transfer is unavailable and use the legacy
full-archive flow. That flow extracts to a temporary directory and does not
create or update the persistent mirror. During a configured full local sync,
the temporary source still uses the collector's new batched ingest path.
Therefore the collector can be upgraded and tested before its spokes. Upgrading
only the collector provides the database-ingest speedup; upgrading each spoke
adds manifest-delta transfer and avoids downloading its complete archive. A
spoke old enough to lack the target or archive endpoints was not compatible
with HTTP remote sync before this change either. Older collectors also continue
to use the full-archive endpoint on a current remote.
The normal mirror comparison detects interrupted extraction when the resulting
file size or modification time differs from the manifest, and the next sync
fetches that file again. It also repairs file-versus-directory conflicts left by
an interrupted extraction.
The comparison does not hash file content. A remote rewrite that preserves both
size and modification time, or local mirror corruption with the same metadata,
can therefore look unchanged. Because `--full` now separates reparsing from
mirror transfer, it does not repair same-stat corruption by itself.
The mirror is a disposable transfer cache. When no sync is running, deleting a
host's mirror directory is the repair procedure: the next compatible sync
bootstraps it again. Leave the adjacent `.lock` file in place. Removing mirror
files never removes imported sessions from the database.
### Transfer Safety
The remote daemon recomputes its allowed sync targets for each request and
rejects paths outside them. Manifest walks omit symlinks and special files;
delta requests must either match an allowed extra file exactly or use an
absolute path in the same POSIX, drive-letter, or UNC dialect as the allowed
root. Symlinked roots or intermediate components are refused. Mirror deletions
and type-conflict cleanup are separately confined to the per-host mirror root.
These checks limit what authenticated sync requests can read, but they do not
replace network isolation or bearer-token security. Keep the daemon on a private
network and protect its `auth_token` as described above.
## SSE Endpoints
The SSE endpoints also accept `?token=<token>` because browser `EventSource`
cannot set custom headers:
```text
http://<host>:8080/api/v1/events?token=<token>
http://<host>:8080/api/v1/sessions/<id>/watch?token=<token>
```
Use header-based auth for normal API calls whenever possible.
## Public URL And Trusted Origins
When you access AgentsView through a hostname or reverse proxy, tell the server
about the public URL:
```bash
agentsview serve --public-url https://agents.example.com
```
For additional trusted origins, use `--public-origin`:
```bash
agentsview serve \
--public-origin https://agents.example.com \
--public-origin https://internal.example.com
```
Flags can also be comma-separated:
```bash
agentsview serve --public-origin https://a.example.com,https://b.example.com
```
These can also be persisted:
```toml
public_url = "https://agents.example.com"
public_origins = [
"https://agents.example.com",
"https://internal.example.com",
]
```
### Forwarded Dev Environments
AgentsView validates the request `Host` header before serving API requests. That
protects local loopback servers from DNS-rebinding attacks, but it also means
SSH port forwards, reverse proxies, WSL2, Codespaces, Coder, and similar remote
development environments must use a trusted public URL or origin.
Static assets can still load in these environments because the HTML shell is
intentionally less strict than `/api/` routes. The dashboard may appear briefly,
then `/api/v1/settings` or another API request can fail with `403 Forbidden` if
the browser `Host` is the forwarded hostname rather than one of the local
allowed hosts.
Restart the server with the exact origin you open in the browser:
```bash
# exe.dev browser URL: https://<vm>.exe.xyz
agentsview serve --public-url https://<vm>.exe.xyz
# ssh -L 18080:127.0.0.1:8080 host
# Browser opens http://127.0.0.1:18080
agentsview serve --public-url http://127.0.0.1:18080
```
`--public-url` must match the browser origin exactly, including the scheme,
hostname, and non-default port. When the browser sends an untrusted `Host`,
current releases return `403 Forbidden` with a response body that explains the
rejected host, the allowed set, and the `--public-url` fix. The Settings page
shows the same actionable message instead of prompting for an auth token. A
short breadcrumb is also written to the server debug log.
### Troubleshooting Forwarded Access
If the UI flashes and then the dashboard or settings page fails, check the
`/api/v1/settings` request in browser devtools:
| Symptom or check | Meaning | Fix |
| --------------------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------- |
| `/api/v1/settings` returns `401 Unauthorized` | Bearer-token auth is enabled | Use the token printed by the server or stored in `~/.agentsview/config.toml` |
| `/api/v1/settings` returns `403 Forbidden` | The browser host or origin was rejected | Restart with `agentsview serve --public-url <exact-browser-origin>` |
| `/api/v1/settings` goes to an unexpected host | The frontend has a saved remote server URL | Clear `localStorage.getItem("agentsview-server-url")` for this site |
To clear an unintended saved server URL, run this in the browser console and
reload:
```js
localStorage.removeItem("agentsview-server-url")
location.reload()
```
## Managed Caddy Mode
AgentsView can manage a [Caddy](https://caddyserver.com) reverse proxy for
TLS-terminated access. The AgentsView backend stays on loopback while Caddy
handles the public socket.
!!! warning "Enable auth first"
Managed Caddy exposes a public endpoint. Set `require_auth = true` in
`~/.agentsview/config.toml` before starting the server, or the proxy will
front an unauthenticated API. See [Quick Setup](#quick-setup).
```bash
agentsview serve \
--public-url https://agents.example.com \
--proxy caddy \
--tls-cert /path/to/cert.pem \
--tls-key /path/to/key.pem
```
| Flag | Default | Description |
| ------------------- | ----------- | ----------------------------------------------------- |
| `--proxy` | | Proxy mode — currently `caddy` |
| `--caddy-bin` | `caddy` | Path to the Caddy binary |
| `--proxy-bind-host` | `127.0.0.1` | Interface for Caddy to bind |
| `--public-port` | `8443` | External port for the public URL |
| `--tls-cert` | | TLS certificate file path |
| `--tls-key` | | TLS key file path |
| `--allowed-subnet` | | Client CIDR allowlist (repeatable or comma-separated) |
Caddy must already be installed and available on `PATH` unless you override it
with `--caddy-bin`.
### Subnet Allowlists
When using a non-loopback bind host for Caddy, restrict access to specific
networks. Subnet allowlists complement bearer-token auth rather than replacing
it — `require_auth = true` should still be set before binding Caddy off
loopback.
```bash
agentsview serve \
--proxy caddy \
--proxy-bind-host 0.0.0.0 \
--allowed-subnet 192.168.1.0/24 \
--allowed-subnet 10.0.0.0/8
```
Requests from outside the allowed subnets receive `403`.
## Settings Page
Remote access can also be configured from the Settings page. The **Remote
Access** section lets you:
- toggle `require_auth`
- view the auto-generated auth token
- connect the frontend to another AgentsView server by URL and token
![Settings remote access](/assets/generated/screenshots/settings-remote.png)
Changes that affect bind or auth behavior may require a server restart.
## CLI Flags Reference
| Flag | Default | Description |
| ------------------- | ----------- | --------------------------------------------------- |
| `--host` | `127.0.0.1` | Interface to bind |
| `--require-auth` | `false` | Require a bearer token for API requests |
| `--public-url` | | Public URL for hostname or proxy access |
| `--public-origin` | | Trusted browser origin (repeatable/comma-separated) |
| `--proxy` | | Managed proxy mode (`caddy`) |
| `--caddy-bin` | `caddy` | Caddy binary path |
| `--proxy-bind-host` | `127.0.0.1` | Interface for managed proxy |
| `--public-port` | `8443` | External port for managed proxy |
| `--tls-cert` | | TLS certificate path |
| `--tls-key` | | TLS key path |
| `--allowed-subnet` | | Client CIDR allowlist (repeatable/comma-separated) |
## Config File Reference
Remote-access-related fields in `~/.agentsview/config.toml`:
```toml
require_auth = true
auth_token = "auto-generated-base64-token"
public_url = "https://agents.example.com"
public_origins = ["https://agents.example.com"]
[proxy]
mode = "caddy"
bin = "caddy"
bind_host = "127.0.0.1"
public_port = 8443
tls_cert = "/path/to/cert.pem"
tls_key = "/path/to/key.pem"
allowed_subnets = ["192.168.1.0/24"]
```
| Field | Description |
| ----------------------- | -------------------------------------------------------------------------- |
| `host` | Server bind interface; non-loopback values require `require_auth = true` |
| `require_auth` | Require bearer-token authentication for API access |
| `auth_token` | Auto-generated 256-bit bearer token; overridden by `AGENTSVIEW_AUTH_TOKEN` |
| `public_url` | Public URL for host/origin validation |
| `public_origins` | Additional trusted CORS origins |
| `proxy.mode` | Managed proxy mode (`caddy`) |
| `proxy.bin` | Path to proxy binary |
| `proxy.bind_host` | Interface for proxy to bind |
| `proxy.public_port` | External port for proxy |
| `proxy.tls_cert` | TLS certificate path |
| `proxy.tls_key` | TLS key path |
| `proxy.allowed_subnets` | CIDR allowlist for proxy connections |
For LAN access you still need a non-loopback bind such as
`agentsview serve --host 0.0.0.0`. `require_auth` controls API auth; it does not
change the bind address by itself.