webui: replace nautctl web server with a loadable plugin

Drop the web UI that was compiled into nautctl and serve the
torrent-ui front end (../torrent-ui/public) from a native plugin
(plugins/webui) loaded via `nautd --plugin`. The plugin talks to the
engine only through the host call_rpc ABI and adapts the daemon's RPC
surface to the qBittorrent-style contract the UI expects (snapshot/SSE,
torrent detail tabs, add/delete, cookie auth).

Also folds in the daemon refactor that owns per-torrent worker threads
and the swarm engine (naut_swarm) used by the plugin's data source.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ookami125 2026-06-17 00:21:48 -04:00
parent 50a357968a
commit 8dde48c05a
27 changed files with 2706 additions and 403 deletions

113
README.md
View file

@ -25,6 +25,8 @@ src/peer/ wire protocol, MSE/RC4, BEP-10, ut_metadata, and PEX
apps/echo/ Phase 1 gate: io_uring echo server on the buffer pool
apps/leech/ Phase 3 gate: verified single-peer download
apps/swarm/ tracker/DHT discovery, magnets, and concurrent peers
apps/nautctl/ thin CLI frontend over daemon RPC
plugins/webui/ daemon plugin that serves ../torrent-ui as the web panel
tests/unit/ unit + concurrency tests
```
@ -35,6 +37,13 @@ cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
ninja -C build
ctest --test-dir build --output-on-failure
# portable daemon/client build with Jansson and Lua embedded
cmake -S . -B build-standalone -G Ninja \
-DCMAKE_BUILD_TYPE=Release -DNAUT_STANDALONE=ON
ninja -C build-standalone nautd nautctl
ldd build-standalone/nautd
ldd build-standalone/nautctl
# sanitizer build (address|thread|undefined)
cmake -S . -B build-tsan -G Ninja -DCMAKE_BUILD_TYPE=Debug -DNAUT_SAN=thread
ninja -C build-tsan && ./build-tsan/test_buf
@ -42,13 +51,16 @@ ninja -C build-tsan && ./build-tsan/test_buf
# run the Phase 1 echo gate
./build/naut_echo 9000
# download from explicit peers, or omit them to use the torrent's trackers
./build/naut_swarm file.torrent output/ 192.0.2.10:6881 192.0.2.11:6881
./build/naut_swarm file.torrent output/
# start the engine, then add and inspect downloads through its RPC frontend
./build/nautd
./build/nautctl add file.torrent output/
./build/nautctl list
./build/nautctl show 1
./build/nautctl events
# trackerless magnet start through DHT (override bootstraps when needed)
./build/naut_swarm 'magnet:?xt=urn:btih:...' output/
NAUT_DHT_BOOTSTRAP=127.0.0.1:6881 ./build/naut_swarm 'magnet:?xt=urn:btih:...' output/
# explicit peers and trackerless magnets use the same daemon workflow
./build/nautctl add file.torrent output/ 192.0.2.10:6881
./build/nautctl add 'magnet:?xt=urn:btih:...' output/
# force an encrypted single-peer MSE/RC4 connection
./build/naut_leech --mse file.torrent output/ 192.0.2.10 6881
@ -59,41 +71,86 @@ NAUT_DHT_BOOTSTRAP=127.0.0.1:6881 ./build/naut_swarm 'magnet:?xt=urn:btih:...' o
bash tests/integration/run_echo_scale.sh ./build/naut_echo
# optional data-path tuning
NAUT_DIRECT_IO=1 NAUT_WORKERS=8 ./build/naut_swarm file.torrent output/
NAUT_DIRECT_IO=1 NAUT_WORKERS=8 ./build/nautd
NAUT_CPU=2 NAUT_SQPOLL=1 NAUT_HUGEPAGES=1 NAUT_NUMA_NODE=0 ./build/naut_echo 9000
```
Requirements: Linux ≥ 6.0, `liburing` (≥ 2.x), OpenSSL `libcrypto`, Jansson,
Lua, CMake ≥ 3.20, gcc/clang, Ninja.
`NAUT_STANDALONE=ON` downloads hash-pinned Jansson 2.14.1 and Lua 5.4.8
sources at configure time and statically embeds them in `nautd` and `nautctl`.
The resulting executables still use the host's glibc/ELF loader intentionally:
fully static glibc breaks normal DNS/NSS behavior and native `.so` plugins.
Release builds target a portable CPU baseline. Use `-DNAUT_NATIVE=ON` only for
a local build that will run on the same CPU family as the build machine.
## Daemon, RPC, plugins, and scripts
Phase 7 adds a headless control process and thin CLI over a versioned,
length-prefixed JSON protocol on a Unix socket:
`nautd` is the application engine: it owns torrent workers, storage, scripts,
plugins, progress, and lifecycle. `nautctl` is one thin frontend over a
versioned, length-prefixed JSON protocol on a Unix socket; a desktop or web
panel can use the same RPC surface.
```sh
./build/nautd \
--socket /tmp/nautd.sock \
--plugin ./build/naut_example.so \
--script ./tests/fixtures/phase7.lua
--plugin ./build/naut_example.so
./build/nautctl ping
./build/nautctl plugins
./build/nautctl status
./build/nautctl script ./examples/anime_sort.lua
./build/nautctl add show.torrent /downloads/show
./build/nautctl list
./build/nautctl events
```
`nautctl` accepts an optional JSON value after the method:
The convenience commands cover normal operation:
```sh
# register a torrent's storage so a move command can resolve + relocate its files
./build/nautctl add_torrent \
'{"torrent_id":7,"torrent":"file.torrent","root":"output/"}'
./build/nautctl emit \
'{"type":"torrent_finished","torrent_id":7}'
./build/nautctl add file.torrent output/ [IP:PORT ...]
./build/nautctl list
./build/nautctl show 1
./build/nautctl remove 1
./build/nautctl script rules.lua
./build/nautctl unscript
./build/nautctl shutdown
```
For tooling and plugin methods, the generic form remains
`nautctl METHOD [PARAMS_JSON]`.
### Web panel
The web panel is a daemon plugin, not part of `nautctl`. It serves the static
frontend from `../torrent-ui/public` by default and adapts that UI's `/api/*`
contract to Naut's daemon RPC surface:
```sh
NAUT_WEBUI_ROOT=../torrent-ui/public \
NAUT_AUTH_PASSWORD='change-me' \
./build/nautd --socket /tmp/nautd.sock --plugin ./build/naut_webui.so
# open http://127.0.0.1:8080
```
Configuration:
```sh
NAUT_WEBUI_HOST=127.0.0.1 # default
NAUT_WEBUI_PORT=8080 # default
NAUT_WEBUI_ROOT=../torrent-ui/public
NAUT_AUTH_USER=admin # default
NAUT_AUTH_PASSWORD=change-me # generated and logged if omitted
NAUT_WEBUI_SAVE_PATH=/downloads # default add-torrent destination
```
The plugin implements the stable `torrent-ui` API surface: cookie login,
`/api/snapshot`, `/api/stream` Server-Sent Events, `/api/meta`, torrent detail
tabs, add/remove, and `/api/plugins` loading ES modules from
`public/plugins/plugins.json`. Some advanced qBittorrent-style controls in the
UI are accepted as no-ops until Naut grows matching daemon RPC methods.
The native ABI is declared in `include/naut/naut_plugin.h`. Plugins export
`naut_plugin_register()`, receive the versioned host API, and may register RPC
methods, storage backends, and event handlers. `plugins/example/example.c`
@ -105,12 +162,11 @@ hooks are `on_torrent_added`, `on_piece_complete`, `on_file_complete`,
filesystem, process, package-loading, debug, and raw chunk-loading globals
(`os`, `io`, `package`/`require`, `debug`, `dofile`/`loadfile`, and
`load`/`loadstring` — the bytecode loaders are denied so a crafted binary chunk
can't escape the VM). `naut.move_file()` submits a bounded command from the
script thread to the daemon owner thread; the owner resolves it through the
torrent registry (`naut_session`) and performs the relocate with
`naut_storage_relocate()`. Register a torrent's storage first with the
`add_torrent` RPC so the id resolves. `phase7_extensibility` drives this
end to end and asserts the file actually moves on disk.
can't escape the VM). `naut.move_file()` submits a bounded command to the
worker that owns the torrent. That worker performs
`naut_storage_relocate()` and keeps tracking the file at its new path.
`phase7_extensibility` drives a real daemon-owned download end to end and
asserts the moved file byte-for-byte.
The full script-visible surface — every event hook, the `event` object's
fields, and the `naut` API table — is documented in
@ -211,9 +267,8 @@ embedded Anitomy-style filename parser ([`examples/`](examples/)).
piece verifies — before the torrent finishes — and `naut_storage_relocate()`
moves that file out safely (even mid-download, while other files' pieces are
still arriving). `test_filemove` proves a file is relocated mid-download with
no corruption. The scripting layer (Phase 7) forwards the event to an
`on_file_complete` hook and exposes `move_file`; the daemon resolves the
command through the `naut_session` torrent registry (`src/session/session.c`)
and calls `naut_storage_relocate()` on its owner thread. `phase7_extensibility`
exercises the whole chain — script thread → bounded queue → owner thread →
storage — and asserts the file moves on disk.
no corruption. The scripting layer forwards the event to an
`on_file_complete` hook and exposes `move_file`; the daemon queues the command
back to the worker that owns the torrent's storage.
`phase7_extensibility` exercises the whole chain — download worker → script
thread → bounded command queue → download worker — and checks the moved bytes.