Files
MyAiDesk/nomifun-tauri/docs/guides/web-server-deployment.md
T
freedak f7a720204a Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
2026-07-04 19:20:46 +08:00

16 KiB
Raw Blame History

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).

First-run admin setup screen

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.lockone 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: 332 chars, [a-zA-Z0-9_-], must not start or end with - / _.
  • Password: 8128 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.

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:

  1. Builds the SPA with Bun.
  2. Compiles nomifun-web from the workspace.
  3. Assembles a slim debian:bookworm-slim runtime that includes bun, git, and ripgrep.

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:

  1. Edit Caddyfile and replace your.domain.com with your real domain.
  2. Set NOMIFUN_HTTPS=true in the nomifun service env (so cookies get the Secure flag).
  3. Replace ports: ["8787:8787"] with expose: ["8787"] so only Caddy is published.
  4. Uncomment the caddy: service and the caddy-data / caddy-config volumes.
  5. 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, then sudo 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:8787 by default. Change NOMIFUN_WEB_HOST to 0.0.0.0 only after first-run setup is complete or NOMIFUN_ADMIN_PASSWORD is configured.
  • Sets NOMIFUN_DATA_DIR=/var/lib/nomifun to match the systemd-managed StateDirectory=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 nomifun user (User=nomifun, Group=nomifun).
  • Restarts on failure with a 3 s backoff.
  • Applies moderate hardening (NoNewPrivileges=yes, PrivateTmp=yes). Do not add ProtectHome=yes or strict ProtectSystem — 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=true so the session cookie is flagged Secure.
  • 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_PASSWORD for any host that becomes reachable before you are ready to interactively complete setup. Alternatively keep the service on 127.0.0.1 until setup is finished, then intentionally bind 0.0.0.0.
  • --insecure-no-auth is 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.