- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用 - 添加所有子项目的完整源代码 - 保留原始 .git 为 .git.bak 备份
16 KiB
Web Server Deployment
nomifun-web is the headless, self-host way to run NomiFun. It is the same Rust backend that the desktop app embeds, but built as a standalone binary that also serves the SPA (ui/dist) on the same port. There is no GUI, no WebView, no DISPLAY requirement — it runs anywhere a Linux/macOS/Windows server will run a static binary.
Unlike the desktop shell, nomifun-web requires authentication by default. The first browser visitor either creates the admin account interactively (first-run setup), or you pre-seed credentials with NOMIFUN_ADMIN_PASSWORD.
If you want to expose an existing desktop install for remote access without setting up a server, see WebUI Remote Access. That is a per-instance feature; this guide is for a dedicated server.
Browser / phone / LAN nomifun-web (one process, one port)
┌──────────────────┐ ┌───────────────────────────────────────┐
│ SPA + login │ HTTP / WS │ axum router │
│ (ui/dist) │ ────────────► │ ├─ / → SPA (ui/dist) │
└──────────────────┘ │ ├─ /api/* → REST handlers │
│ ├─ /ws → WebSocket events │
│ └─ /login … → auth (on by default)│
│ │
│ embedded backend (nomifun-app) │
│ └─ SQLite · agents · cron · channels │
└───────────────────────────────────────┘
Quick start
Run the binary directly
cargo build --release -p nomifun-web
./target/release/nomifun-web --host 127.0.0.1 --port 8787 \
--data-dir ./data --dist ./ui/dist
Then open http://127.0.0.1:8787 and the first visit lets you create the admin account. After that, the setup endpoint returns 409 Conflict and the only way in is via the login form (or NOMIFUN_ADMIN_PASSWORD).
Or via Cargo, from the repo
bun install
bun run build:ui # produces ui/dist
cargo run -p nomifun-web # picks up the default --dist=../../ui/dist
CLI flags and environment variables
All flags below are read by apps/web/src/main.rs. Each has an environment-variable counterpart for systemd / Docker / orchestrators.
| Flag | Env var | Default | Purpose |
|---|---|---|---|
--host |
NOMIFUN_WEB_HOST |
127.0.0.1 |
IP to bind on. 0.0.0.0 accepts LAN/VPN/public traffic; pre-seed the admin or complete first-run setup before broad exposure. |
--port |
NOMIFUN_WEB_PORT |
8787 |
TCP port. Serves the API, the WebSocket at /ws, and the SPA. |
--data-dir |
NOMIFUN_DATA_DIR |
per-user dir | Backend data dir (SQLite database, agent state, logs, Bun cache). Defaults to the per-user location shared with the desktop app (%LOCALAPPDATA%\NomiFun\Nomi, ~/Library/Application Support/NomiFun/Nomi, $XDG_DATA_HOME/NomiFun/Nomi). Still set an explicit absolute path in production. |
--dist |
NOMIFUN_WEB_DIST |
../../ui/dist |
Directory containing the built SPA. Set this explicitly when deploying. |
--admin-user |
NOMIFUN_ADMIN_USERNAME |
admin |
Username used when pre-seeding the first admin. Ignored once an admin exists. |
--admin-password |
NOMIFUN_ADMIN_PASSWORD |
— | Pre-seed the first admin password at boot, skipping interactive setup. Ignored once an admin exists. |
--insecure-no-auth |
NOMIFUN_WEB_INSECURE_NO_AUTH |
false |
DANGER. Disables authentication entirely (desktop-style local mode). Only use on loopback or a fully trusted private network. |
| — | NOMIFUN_HTTPS |
false |
When true, session and CSRF cookies are flagged Secure. Set this whenever the app is reached over HTTPS (e.g. behind a TLS reverse proxy). |
| — | SHELL |
platform default | Shell used by the agent engine when spawning processes. Set to /bin/bash on Linux servers if $SHELL is unset. |
Boolean envs accept 1, true, yes, on (case-insensitive).
A bad --host (anything that does not parse as an IP) fails fast at startup with a clear error rather than a cryptic socket error.
At startup the backend takes an OS-level exclusive lock on {data_dir}/server.lock — one backend instance per data dir. A second process pointed at the same directory fails fast with an error naming the current holder (pid + exe); to deploy multiple instances, give each its own NOMIFUN_DATA_DIR / --data-dir. The OS releases the lock on exit or crash, so a leftover server.lock file is harmless.
Password and username rules
When the admin account is created (interactively or via pre-seed), values are validated server-side:
- Username: 3–32 chars,
[a-zA-Z0-9_-], must not start or end with-/_. - Password: 8–128 chars, rejected if it appears in a small common-passwords list (
password,12345678,qwertyui, …).
A weak NOMIFUN_ADMIN_PASSWORD will refuse to boot. A weak interactively-typed password will return 400 with the validation message.
First-run admin provisioning
There are two supported paths.
Interactive (default)
Leave NOMIFUN_ADMIN_PASSWORD unset. On a fresh data dir the install is "uninitialised": GET /api/auth/status reports needs_setup: true, the SPA shows the first-run form, and the first browser visitor's chosen username + password become the admin via an atomic POST /api/auth/setup. The write is a conditional UPDATE — even two concurrent first-run requests cannot both win; the loser receives 409 Conflict.
Security note — the first-run window. Between the moment the server is reachable and the moment you complete setup, anyone who can reach the port can claim the admin account. On a non-loopback bind the server logs a loud warning. Mitigate by completing setup over a trusted tunnel/VPN first, or pre-seed (next section) so the install is initialised before it goes live.
Pre-seeded (recommended for automation)
Provide NOMIFUN_ADMIN_PASSWORD (and optionally NOMIFUN_ADMIN_USERNAME, default admin) before first boot. The bootstrap routine hashes and stores the credentials atomically, the first-run setup endpoint returns 409 from the very first start, and there is no window for someone else to claim the account.
NOMIFUN_ADMIN_USERNAME=alice \
NOMIFUN_ADMIN_PASSWORD='change-me-to-something-strong' \
nomifun-web --host 0.0.0.0 --port 8787 \
--data-dir /var/lib/nomifun --dist /opt/nomifun/web
The pre-seed is idempotent — once an admin exists, the env vars are ignored on subsequent boots. To rotate credentials, use the in-app change-password / change-username flow rather than the env vars.
Docker
The repo ships a multi-stage Dockerfile and a docker-compose.yml. The image:
- Builds the SPA with Bun.
- Compiles
nomifun-webfrom the workspace. - Assembles a slim
debian:bookworm-slimruntime that includesbun,git, andripgrep.
It exposes port 8787 and uses /data as the data volume.
Compose
docker compose up -d --build
# then open http://<server-ip>:8787 and create the first admin
restart: unless-stopped makes the service start on host boot — installing it is enabling it. The default ports block publishes 8787:8787 directly; pre-seed the admin or complete setup on a trusted network before exposing it broadly. Add TLS (next section) before exposing to the internet.
Verify readiness:
docker compose logs -f nomifun
# look for: "nomifun-web: embedded backend + SPA on one port"
The compose file mounts a named volume nomifun-data:/data which holds the SQLite DB, logs, the Bun runtime cache, and per-agent state. Back this up with the same care as any other database.
Pre-seeding the admin in Compose
Uncomment the environment: block:
environment:
NOMIFUN_ADMIN_USERNAME: admin
NOMIFUN_ADMIN_PASSWORD: "change-me-to-something-strong"
NOMIFUN_HTTPS: "true" # when fronted by Caddy / nginx with TLS
Building behind a slow registry
The Rust stage accepts a CARGO_REGISTRY_MIRROR build arg for cargo registry mirroring (e.g. on a network where crates.io is slow):
docker build --build-arg CARGO_REGISTRY_MIRROR=https://rsproxy.cn/index/ -t nomifun-web:local .
$ docker compose up -d
[+] Running 2/2
✔ Network nomifun_default Created
✔ Container nomifun-web Started
$ docker compose logs -f web
nomifun-web | listening on 0.0.0.0:8787 (auth: enabled)
TLS via Caddy reverse proxy
A Caddyfile is included for Caddy 2. Caddy auto-provisions HTTPS certificates (Let's Encrypt or ZeroSSL by default) and proxies to the app. The WebSocket upgrade at /ws passes through automatically, no extra config required.
your.domain.com {
encode zstd gzip
reverse_proxy nomifun:8787
}
To enable the Caddy service in docker-compose.yml:
- Edit
Caddyfileand replaceyour.domain.comwith your real domain. - Set
NOMIFUN_HTTPS=truein thenomifunservice env (so cookies get theSecureflag). - Replace
ports: ["8787:8787"]withexpose: ["8787"]so only Caddy is published. - Uncomment the
caddy:service and thecaddy-data/caddy-configvolumes. docker compose up -d.
The app already provides its own login screen, so do not configure HTTP basic auth in Caddy — Caddy's job is purely TLS termination and proxying.
For a LAN-only host without a public domain you can use an internal name with tls internal, or just publish port 8787 directly without Caddy (the in-app login still protects it).
systemd (Linux server, no Docker)
The repo includes packaging/linux/nomifun-web.service and a long-form Linux deployment guide at packaging/linux/README.md.
Build artifacts
You need a Linux build host (cross-compiling the C dependencies from Windows is painful — the easiest workaround is to extract the binary from the Docker image with docker cp). On Linux:
bun install
bun run build:ui # → ui/dist (~21MB)
cargo build --release -p nomifun-web # → target/release/nomifun-web
Layout
/opt/nomifun/nomifun-web # the binary
/opt/nomifun/web/ # contents of ui/dist
/var/lib/nomifun/ # data dir (created by systemd's StateDirectory)
sudo useradd --system --home /var/lib/nomifun --shell /usr/sbin/nologin nomifun
sudo mkdir -p /opt/nomifun/web
sudo cp target/release/nomifun-web /opt/nomifun/
sudo cp -r ui/dist/. /opt/nomifun/web/
Bun must be on the system PATH
The agent engine requires bun ≥ 1.3.13 as a runtime dependency. Because the service runs under a nologin system account, an install in someone's ~/.bun/bin/ is invisible to it. Pick one:
- System install:
curl -fsSL https://bun.sh/install | bash, thensudo install ~/.bun/bin/bun /usr/local/bin/bun. - Embed in the binary: build with
NOMIFUN_EMBED_BUN=1 cargo build --release -p nomifun-web. Bun is bundled into the binary and self-extracts into the data dir on first run.
Verify: sudo -u nomifun -s -- which bun must return a path. Otherwise the first agent spawn will fail with an opaque error.
Install the unit
sudo cp packaging/linux/nomifun-web.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now nomifun-web
sudo systemctl status nomifun-web
The shipped unit:
- Binds
127.0.0.1:8787by default. ChangeNOMIFUN_WEB_HOSTto0.0.0.0only after first-run setup is complete orNOMIFUN_ADMIN_PASSWORDis configured. - Sets
NOMIFUN_DATA_DIR=/var/lib/nomifunto match the systemd-managedStateDirectory=nomifun. Keep these two in sync — if you drop the env line, the data dir silently falls back to the service user's per-user directory ($XDG_DATA_HOME/NomiFun/Nomi, typically~nomifun/.local/share/NomiFun/Nomi), decoupled from systemd state. - Runs as a dedicated
nomifunuser (User=nomifun,Group=nomifun). - Restarts on failure with a 3 s backoff.
- Applies moderate hardening (
NoNewPrivileges=yes,PrivateTmp=yes). Do not addProtectHome=yesor strictProtectSystem— the agent engine reads/writes operator-directed files and over-sandboxing breaks core features.
To enable HTTPS cookies behind a TLS proxy, uncomment:
Environment=NOMIFUN_HTTPS=true
To pre-seed the admin instead of interactive setup:
Environment=NOMIFUN_ADMIN_USERNAME=admin
Environment=NOMIFUN_ADMIN_PASSWORD=change-me-to-something-strong
$ sudo systemctl status nomifun-web
● nomifun-web.service - NomiFun web host
Loaded: loaded (/etc/systemd/system/nomifun-web.service; enabled; preset: enabled)
Active: active (running) since Tue 2026-06-25 09:12:03 UTC
Main PID: 12345 (nomifun-web)
CGroup: /system.slice/nomifun-web.service
└─12345 /usr/local/bin/nomifun-web --host 127.0.0.1 --port 8787 …
nomifun-web[12345]: listening on 127.0.0.1:8787 (auth: enabled)
Linux runtime dependencies
| Dependency | Required? | Notes |
|---|---|---|
glibc + ca-certificates |
Yes | sqlite is statically linked, TLS uses rustls — no openssl, no libsqlite needed. |
bun ≥ 1.3.13 |
Yes | Agent execution runtime. 1.1.38 has an stdin bug; do not use. Already inside the Docker image. |
node / npm / npx |
Recommended | Many user-configured MCP stdio servers launch via npx -y …. |
git |
Recommended | Skill discovery and a few built-in tools. |
ripgrep (rg) |
Recommended | Code-search backend. Falls back to grep if missing. |
DISPLAY / X11 / WebView |
No | nomifun-web is fully headless. |
Security checklist
- Use TLS for any public deployment. Cookies and login credentials over plain HTTP can be sniffed. Behind a TLS proxy, set
NOMIFUN_HTTPS=trueso the session cookie is flaggedSecure. - Strong admin password. The validator rejects passwords below 8 chars and a few obvious dictionary entries, but it does not enforce a strength score — pick something long and random. Change it from the in-app flow whenever you suspect compromise; the change-password endpoint rotates the JWT secret, invalidating every existing session.
- Close the first-run window with
NOMIFUN_ADMIN_PASSWORDfor any host that becomes reachable before you are ready to interactively complete setup. Alternatively keep the service on127.0.0.1until setup is finished, then intentionally bind0.0.0.0. --insecure-no-authis hostile by default. It disables authentication completely; anyone who can reach the port becomes a privileged user with shell, file, and agent access. Only use on a loopback bind or a fully trusted private network. The server logs a warning when it is enabled on a non-loopback address.- The backend has terminal, filesystem, and agent execution capabilities — running it remotely is, by design, equivalent to giving yourself remote code execution on the host. Auth + TLS are the floor, not the ceiling. Treat the data dir and the admin password the same way you would treat root credentials.
Troubleshooting
invalid --host '<value>'. Pass an IP literal (127.0.0.1, 0.0.0.0, an explicit interface IP). Hostnames are not parsed.
Cookies don't stick over HTTPS. Set NOMIFUN_HTTPS=true so the Secure flag is added. Without it, browsers reject the cookie on HTTPS responses.
Agent commands fail with bun: command not found under systemd. Install bun system-wide (see the bun-on-PATH section above) or rebuild with NOMIFUN_EMBED_BUN=1.
Healthcheck. Use GET /health for process liveness. Use
GET /api/auth/status only when the caller also needs setup/auth state.
See also
- Running NomiFun as a Desktop App
- WebUI Remote Access — turn an existing desktop install into a remotely-accessible server (without provisioning a separate machine).
packaging/linux/README.md— deeper Linux notes (mostly Chinese; this guide subsumes the English content).apps/web/src/main.rs— the source of truth for flags, env vars, and bootstrapping order.
