Deploy to a Fresh Server From Scratch
Who is this page for?
You just got hold of a brand-new Ubuntu / Debian server (cloud VM, local VM, internal box — all fine) and want to bring up anet hub + a node or two + Telegram from zero, end-to-end.
Every step has a verify command, and the end of the page has a common-error lookup (troubleshooting table).
It is not for users who already have anet installed; that's Getting Started or Upgrade Guide. To move an existing Hub to another machine, follow Moving the Hub; a fresh deployment does not restore its data.
0. Prerequisites
Install Node.js ≥ 22.13 and Bun ≥ 1.2 as described in Install (you need both; the Hub runs on Bun). If systemd, cron or another user will start the Hub and nodes later, read the PATH note on that page first.
1. Install the anet CLI
npm install -g @sleep2agi/agent-network
anet -vanet -v shows whether agent-node and commhub-server are in place (they are fetched on first use) and which optional runtime CLIs are installed. Whenever something fails to start later, look here first.
2. Start the Hub (recommended: under tmux)
CommHub is a long-running process: closing the terminal stops it. For production you'd configure systemd (§8 Persistence); for a quick verify, use tmux:
# Install tmux (if not present)
sudo apt install tmux -y # Ubuntu / Debian
# Start a tmux session named anet-hub
tmux new -s anet-hub
anet hub start --host 0.0.0.0 --port 9200
# Look for output like this — that's success:
# CommHub MCP Server <version>
# REST: http://0.0.0.0:9200/api
# ✅ Admin account created
# username: admin
# password: anet-<22 random hex chars> ← printed only this once; save itPress Ctrl+B then D to detach the tmux session while keeping it alive. Come back with tmux a -t anet-hub.
Verify the hub is up (from another terminal):
curl -s http://127.0.0.1:9200/health | head -5
# Expect something like {"ok":true,"version":"<version>",...}Public-internet deploy: change the password immediately
The random password from the first start is shown only once. Any --host 0.0.0.0 public-internet deploy must immediately change it to your own strong password:
anet login --username admin --password '<password printed at startup>' --hub http://127.0.0.1:9200
anet passwd # Interactive password change (≥ 8 chars, not in the weak-password dict)Or set credentials when starting the hub: anet hub start --host 0.0.0.0 --username <your-admin> --password '<strong-pass>'.
Port already in use? Change ports or stop the old hub
# Who's listening on the port
ss -tlnp | grep 9200
# Change to a different port
anet hub start --port 9201
# Or gracefully stop the old hub (preferred)
anet hub stop # default port 9200, SIGTERM → 3s grace → SIGKILL fallback
anet hub stop --port 9201 # non-default port
anet hub status # show current PID / port / commhub-server version
# Fallback if the above somehow doesn't work
HUB_PID=$(ss -tlnp | grep ':9200' | sed -E 's/.*pid=([0-9]+).*/\1/' | head -1)
kill "$HUB_PID"
# Or if your hub runs under tmux, attach and Ctrl+C
tmux a -t anet-hub # Then Ctrl+C to stop, Ctrl+B D to keep the tmux sessionOn older versions (such as 2.2.12) anet hub --help does not list stop / status, but the commands work: anet hub status returns hub running / vX.Y.Z / pid N.
3. CLI Login
Open a third terminal (leave the hub's tmux alone):
anet login --hub http://127.0.0.1:9200 --username admin --password '<password printed at startup>'After login, the token lives at ~/.anet/config.json and is auto-attached to every subsequent anet node ... command.
anet whoami # Confirm your identity4. Create an Agent Node (chosen by runtime)
anet node create my-botThe wizard asks the following, in order:
node-name → runtime → (only if claude-agent-sdk) vendor → model → API key / authRuntime is the first fork: it decides whether you'll be asked for a vendor and what dependencies you'll need. The picker lists all 7 runtimes; the table below covers the common ones. For npm packages, default models and the full wizard behavior, see runtimes — canonical table:
| Runtime | Complexity | Best for | Wizard follow-up | Extra dependencies |
|---|---|---|---|---|
claude-code-cli (recommended for first-time users) | ⭐ | You already use Claude Code, want to reuse the subscription | Skips vendor + model, uses your local claude login | claude auth login already done |
claude-agent-sdk | ⭐⭐ | Programmatic access to any Anthropic-compatible API (MiniMax / 书生 / 小米 MiMo / domestic models go here) | Pops a vendor submenu → pick vendor → pick model → enter API key | API key |
codex-sdk | ⭐⭐⭐ | Writing code / running commands via OpenAI Codex | Skips vendor, uses codex's own auth | agent-node + codex CLI + codex login |
grok-build-acp | ⭐⭐⭐ | Running tasks via xAI Grok Build | Skips vendor, uses grok's own auth + GROK_CODE_XAI_API_KEY | grok CLI + grok login + GROK_CODE_XAI_API_KEY |
opencode-cli (preview) | ⭐⭐⭐ | Use the public sst/opencode CLI as a multi-vendor front-end (preview; in the menu on both latest and preview) | Pick a vendor preset (anthropic / openai), key read from env | opencode CLI + Anthropic/OpenAI env key |
Watch out for the default runtime
The wizard defaults to the first option (currently claude-agent-sdk); a new user pressing Enter all the way lands on the vendor + API-key path. Manually pick claude-code-cli for the smoothest first-time experience.
The wizard does not ask about Telegram
Once the steps above finish, the wizard ends; it never asks for a Telegram bot token / allow-list UID. Telegram is attached after node creation with a separate command, anet channel add telegram (see §6).
If you see the line "optional Telegram channel" at the top of anet create's output, don't assume the wizard will configure it for you; it won't.
After completion, the node config is written under cwd:
.anet/nodes/my-bot/config.jsonVerify:
anet node ls # should list my-bot5. (codex-sdk only) Install agent-node + codex CLI
If you picked codex-sdk or claude-agent-sdk above, the node needs the @sleep2agi/agent-node package to run. It is lazily fetched via npx on first use; if that fetch doesn't happen, the node fails on start with agent-node is not installed or cannot report a version. Run: anet upgrade.
The reliable fallback: install it once manually:
npm i -g @sleep2agi/agent-node
# Verify
agent-node --version # should print a versionThe codex-sdk runtime additionally needs the codex CLI installed + logged in:
# Note: this is @openai/codex (the CLI tool), distinct from @openai/codex-sdk (the SDK library that the codex-sdk runtime depends on) — two separate npm packages
npm i -g @openai/codex
codex login # Browser OAuth
# Verify
codex --versionThe claude-agent-sdk runtime does not need either codex / claude CLI: as long as your node config carries the right vendor's API key (or the matching env vars ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN), you're good.
6. Configure the Telegram channel (optional)
If you want to dispatch tasks to the agent from Telegram:
6.1 Get a bot token + your Telegram user ID
- In Telegram, search @BotFather →
/newbot→ follow the prompts; the last reply contains yourbot-token(looks like1234567:ABCDef...) - Get your Telegram user ID (a number): message @userinfobot anything; it replies with your UID
6.2 Attach the Telegram channel to the node
anet channel add telegram my-bot \
--bot-token <bot-token> \
--allow <uid>Parameters:
<bot-token>: the string BotFather gave you<uid>: the Telegram user ID(s) allowed to dispatch tasks to this bot (comma-separate multiples:--allow 11111,22222)
Verify it landed:
anet channel ls
# Should list: my-bot (my-bot) telegram allow: <uid>Want to add more allowed users?
anet channel add telegram <node> --allow <new-uid-list> overwrites the allowlist, it doesn't append. To keep existing entries, repeat the full list (--allow 11111,22222,33333). More known limits: guide/channels.md.
7. Start the Node
# Recommended: tmux (same as the hub — avoids stopping when the terminal closes)
tmux new -s anet-my-bot
anet node start my-botclaude-code-cli first launch pops the "dev-channels" confirmation prompt
On its first launch, a claude-code-cli node will pop Claude Code's --dangerously-load-development-channels confirmation prompt:
❯ 1. I am using this for local development
2. ExitPress 1 + Enter. In scripted / detached launches nobody's there to answer, so the node hangs and stays offline.
Workaround: run it once in a foreground tmux and answer the prompt manually; subsequent launches don't pop it (the answer is session-scoped). You can then layer systemd on top.
When you see SSE connected, the node is online; back on the hub side, anet status should list my-bot as online.
Ctrl+B D to detach the tmux session.
8. Persistence (systemd / tmux)
Two options below: tmux for quick / dev use, systemd units for production / autostart.
8.1 tmux (dev / quick verify)
A machine reboot drops everything; you restart by hand:
- hub:
tmux new -s anet-hub+anet hub start --host 0.0.0.0 - each node:
tmux new -s anet-<alias>+anet node start <alias> - An
@rebootcrontab line can replace the manual step, but you'll need to solve the non-interactive-shell PATH / nvm problem first (see §0)
Confirm the node really came up; don't trust the stdout ✅
After starting each node, run tmux has-session -t "=<alias>"; echo $? and expect 0. The = is required: a bare name is a prefix match and can go green on another session. Versions before 2.3.0-preview.40 can print ✅ started detached (tmux session live) and exit 0 on the detached path while nothing runs in tmux. For bulk launches use anet project up; its exit code is trustworthy on ≥ 2.3.0-preview.40.
8.2 systemd units (production / autostart)
The unit files below are not officially tested. Swap in your real anet path (from which anet) and the user you run anet under, then systemctl daemon-reload + enable --now.
Hub /etc/systemd/system/anet-hub.service:
[Unit]
Description=Agent Network Hub
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=youruser
WorkingDirectory=/home/youruser
# Replace with the absolute path from `which anet` ↓
ExecStart=/home/youruser/.nvm/versions/node/v22.13.0/bin/anet hub start --host 0.0.0.0 --port 9200
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.targetNode (templated — one unit runs N nodes) /etc/systemd/system/anet-node@.service:
[Unit]
Description=Agent Network Node %i
After=anet-hub.service
Requires=anet-hub.service
[Service]
Type=simple
User=youruser
WorkingDirectory=/home/youruser
ExecStart=/home/youruser/.nvm/versions/node/v22.13.0/bin/anet node start %i
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.targetEnable:
sudo systemctl daemon-reload
sudo systemctl enable --now anet-hub
sudo systemctl enable --now anet-node@my-bot
sudo systemctl status anet-hub anet-node@my-botGotchas:
ExecStartmust be the absolute path toanet(which anet); systemd does not read nvm's~/.bashrcUser=should be the everyday user that owns the anet install; don't useroot- The
claude-code-cliruntime pops a dev-channels confirmation on first run (see §7 Start the Node); answer it once under a foreground tmux before handing off to systemd - Want an official unit / improvements to the template above? PRs welcome, or open a thread on GitHub Discussions
Running in containers
There is no maintained production compose bundle yet; the Dockerfiles and compose files in the repository are for tests or specific integrations. In a container, treat it as "Ubuntu with Node.js and Bun": every command above works the same. tests/Dockerfile (the minimal Node.js + Bun + anet set) is a starting point; for production add multi-stage builds, pinned image hashes and a non-root user yourself.
- Same start order: Hub → Dashboard →
anet login→anet node create→anet project up; containerizing only splits them into services. - Slow first start: the first
anet hub startfetches a pinnedcommhub-serverfrom npm and caches it in$HOME/.bun/install/cache; pre-warming that directory speeds things up a lot. - Container networking: the Dashboard calls the Hub on
:9200over REST + SSE, so both must share a docker network, or publish the Hub port to the host. Inside a container,localhostmeans that container. - Persistence: node state lives in
.anet/nodes/<alias>/under the working directory and Hub data in~/.commhub/; mount both as volumes. - Do not run the node process as PID 1: wrap it in an init such as
tini, otherwise SIGTERM skips the node's offline notification.
Troubleshooting table
In the order of the setup steps — symptom → cause → fix:
| # | Symptom | Cause | Fix |
|---|---|---|---|
| 1 | anet hub start → spawn bunx ENOENT + node-stack crash | Bun not installed; commhub-server is Bun-shebang TS | npm i -g bun or curl -fsSL https://bun.sh/install | bash; afterwards bun --version should produce output |
| 2 | anet node create → after picking runtime → FATAL: TypeError: fetch failed | Node creation needs to register with the local hub, but the hub isn't up (usually because of row 1) | Open another terminal, anet hub start, then retry |
| 3 | Pressing Enter through the wizard lands you on the vendor + API-key path | Runtime menu defaults to claude-agent-sdk, not the simpler claude-code-cli | Manually pick claude-code-cli when creating the node (with claude auth login already done, it reuses the subscription directly). If a Ctrl-C during vendor selection left a half-baked node behind, clean up with anet node delete <alias> and retry |
| 4 | Telegram doesn't work after node creation, even though the wizard mentioned "optional Telegram channel" | The wizard never asks about Telegram; that line is misleading copy | Use anet channel add telegram <node> --bot-token <tok> --allow <uid> separately to attach it (see §6) |
| 5 | anet node start (codex-sdk / claude-agent-sdk) → agent-node is not installed or cannot report a version | npx lazy-load didn't actually pull @sleep2agi/agent-node; builds ≤ 2.3.0-preview.37 (including 2.2.x) have this problem | Short-term: npm i -g @sleep2agi/agent-node to put the binary in place; long-term: anet upgrade to ≥ 2.3.0-preview.38 (check what each channel points to with npm view @sleep2agi/agent-network dist-tags) |
| 5.5 | anet node start prints ✅ started detached (tmux session live) and exit 0, but tmux ls has no such session and the process is gone | False success on the detached path; versions before 2.3.0-preview.40 have this problem | Real check: tmux has-session -t "=<alias>"; echo $? should print 0 (the = is required). For bulk launches use anet project up (its exit code is trustworthy on ≥ 2.3.0-preview.40). Until you upgrade, confirm every start with has-session |
| 6 | claude-code-cli node starts but stays offline / pane is stuck on the confirmation prompt | Claude Code's --dangerously-load-development-channels prompt is waiting for an Enter | Run it once in foreground tmux and press 1 + Enter; subsequent launches don't pop it |
| 7 | systemd / cron / a different user launch produces a string of command not found errors | nvm + Bun each install per user; non-interactive shells don't load them | Symlink node/npm/bun into /usr/local/bin/, or have the launch script explicitly source ~/.nvm/nvm.sh |
| 8 | Machine reboot drops everything | Hub + nodes all rely on manual tmux sessions | Configure autostart via systemd; see the template in §8.2 systemd units |
Next
- Getting Started: end-to-end walkthrough for an already-installed laptop setup
- One-shot installer retirement: the old script is disabled; use this page's step-by-step flow
- Production / public-internet deployment: TLS / firewall / backup / public-internet risk surface
- Channel integration: current channel status and Telegram / Feishu setup; WeChat has not shipped
- Node runtime: detailed comparison of the runtimes
If you hit a problem not covered here, please open a GitHub issue with the step that failed, the exact error, and the anet -v output.