Repository navigation
The desktop app
nerve-node-desktopis the node with a window, for Linux x86_64 (GNOME or KDE Plasma, Wayland or X11; glibc 2.35 or newer) and Windows x86_64 (#10). It is one program: started with arguments it isnerve-node, started without any it opens the window. The window runs the CLI for every action, so it has the CLI's checks and audit records. Seedocs/user/desktop.md.- Its pages: overview (the node's state, panic and unlock, and the service's start, stop and restart); centers with pairing (also from a
nerve://link), pause, resume and key rotation; relay; permissions (mode, folders, rules, capability switches, sudo); audit; log; settings (config, proxy, backup and restore); help; and the About dialog. A first-run setup names the node and pairs its first center. Light and dark follow the system, or the choice saved in the app. - The audit page has allow, allow as wildcard, deny and deny as wildcard on each entry, which write the same rule as
nerve-node rule add --action(#23). - The downloaded file installs itself: the program, the node as a service under its own user (as
nerve-node install), the menu or Start menu entry with its icon, and thenerve://handler, after the system's question for the rights (pkexec, UAC). Started again, it offers to open, update or uninstall Nerve. Settings update it (from the latest release, checked againstSHA256SUMS, or a file) and uninstall it, keeping or deleting the node's data. On Windows it is in Apps & features, whose Uninstall opens the app's uninstall. - A node installed with
nerve-nodeis taken over: the service runs the desktop app's program, so there is one program to update. On Linux/usr/local/bin/nerve-nodebecomes a link to it, so the terminal command keeps working (--versionthrough it saysnerve-node-desktop), and uninstall removes the link. On Windows the oldnerve-node.exeis removed, as the app has no console; for a terminal, downloadnerve-node-x86_64-windows.exefrom the release. A plainnerve-node uninstallafter the takeover removes the app too. - A change the running service takes only when it starts again (on Windows every change but a folder's, a pairing too) makes the app restart the service, two seconds after the last one or as the app closes, and say so. A restore stops the service and starts it again.
- On Windows a standard user can install and use the app with an administrator's name and password in the UAC prompt; the commands still run as that user.
- On Linux the app says what the desktop lacks instead of failing silently: no display, no
pkexecor polkit agent, no desktop portal for the file dialogs, noupdate-desktop-database; without OpenGL it draws in software.docs/user/desktop.mdlists what each desktop needs. - The release has
nerve-node-desktop-x86_64-linuxandnerve-node-desktop-x86_64-windows.exe, with their checksums inSHA256SUMS. - The
NOTICEfile holds the licence of the Lucide icons, which the app and the center's web UI use. The app is built with Slint under its Royalty-free license 2.0. - Rights once, like an installed program: the user who installs becomes the node's owner, and opens the app, changes settings, pairs, edits rules and starts, stops and restarts the service without a question. The administrator's password is asked only for update, uninstall, sudo on and the
machine.*settings that pick an account or program (also a restore that would change them), each time for that action. - The look: colour with a meaning (the node's state, the page's icon, the open page), text that reads well in light and dark (WCAG AA), short texts. A busy button shows a spinner in place of its icon and keeps its size. Light and dark are chosen at the foot of the sidebar, and a change comes in one step, with no light frame between two dark ones. Keys: Ctrl+1 … Ctrl+8 for the pages, Ctrl+R and F5 read the page again, Esc and Enter in dialogs, Tab through every control. A click on the open page's tab reads nothing again.
- Settings has tabs (General, Keys, Network, Limits, Backup, App, Advanced), with a control for each setting an owner changes, the limits per command too; Any setting is under Advanced. Network keeps the proxy: the relay has a page of its own.
- The Relay page has both sides of a relay: this node through one, from one pasted link, and this machine as one, with its nodes, their links, centers, limits and tunnels now. Pairing checks the link first (the center answers, its pin, the code still valid) and offers Pair only when it can work. The first-run steps have Back.
- Permissions puts the rules that count in the center's mode first (in allowlist mode the deny rules as its exceptions), keeps the sudo list with sudo, and lets every rule be edited in place. It says who runs the commands, offers Run them as you in allowlist mode, lists
nerve-run's built-in folders, and shows a folder grant that failed. A one-line warning says when commands run without their limits. - A list longer than some eight rows (rules, folders, capabilities, a relay's nodes and tunnels, the Overview's centers) scrolls in a box of its own with a scroll bar in sight. Help has the keys and the command reference, by topic or A to Z and filtered, each command opening on its arguments, an example and the place in the app; each part, topic and command opens and closes on its own. Clear on Audit and Log asks first. Audit filters by decision (allowed, refused, settings changes), center, action and words in the target and reason, combined and counted, newest or oldest first.
- The window has the app's icon in GNOME's dash, KDE's task manager and the Windows taskbar. With System, the app is dark when GNOME (also Zorin OS, by its GTK theme) or KDE is. The downloaded file opened while the app is open brings the window up and offers its update.
- Animations of 100 to 200 ms: pages and settings tabs fade in, dialogs rise in and their veil lifts as they close, the note in the corner comes and goes, Help's sections slide open, the open page's mark grows, buttons and switches change smoothly. None delays a click or a key. They are off, and the loading dots and spinners still, when the desktop asks for less motion: GNOME's Reduce Animation, KDE's animation speed Instant, Windows' Animation effects off, or the desktop portal's
reduced-motion(#136).
The guard against changing the node (#143)
- A new pairing starts with deny rules, the guard, that keep its commands off the node itself: a command that runs the node's programs (not one that only names them, like
cargo build -p nerve-node), one that names its socket or its config and state folders, and a write into those folders.rule guardadds them to a pairing that lacks them, a pairing made before 0.0.6 included, and the desktop app offers that on Permissions. They catch an agent's mistake, not a name built at run time; the real protection isnerve-run, the separate account for commands on Linux. - The guard also refuses a command that stops, restarts, disables or kills the node's service or process by its name:
systemctl stop,restart,disable,killormask nerve-node,service nerve-node stoporrestart, andpkillorkillallonnerve-nodeon Linux, andsc stop,configordelete,net stop,Stop-Service,Restart-Service,Set-Service,Remove-Service,Stop-Process -Nameandtaskkill /im nerve-node.exe(ornerve-node-desktop.exe) on Windows. The Windows commands match in any case, a line break after a command or a name ends it like a space, andsudocounts at a command's start only, so a quoted mention such asgit commit -m "run sudo systemctl stop nerve-node"runs. Reading stays allowed (systemctl status,journalctl -u nerve-node,pgrep,sc query,Get-Service nerve-node). A command afterthen,do,else,{,!,timeornohupcounts as one that starts a line, and an indent of up to 16 characters is read. A refusal's reason and the settings log write the rule's tabs and line breaks as\t,\rand\n, andconfig addand the app refuse the listed text of such a rule as a new rule. The rules that name the node's programs, sockets and folders end a name at a tab or line break too, and see a command after one. A rule with a tab or line break is listed with\t,\rand\nwritten out (rule ls, the app's rule lists), andrule rmandrule edittake it so.
Folder mode as an account of its own (#142)
- On the node installed as a service on Linux,
foldermode commands, andallowlistmode commands unlessmode set allowlist --as-owner, run asnerve-run, an accountinstallmakes with a home of its own (/var/lib/nerve-run), no login and no other groups;uninstallremoves it. In 0.0.5 the commands of every mode ran as one account thatinstalltook orsetupchose (machine.user).nerve-runis not the owner: the owner socket refuses it, and the owner's session programs (service manager, session bus, tmux) take no requests from it, on every kernel.usermode runs as the owner, the account that raninstall. There is no other account to choose:setup --create-user, setup's question for the account andmachine.useron Linux (it stays for Windows) are gone. - The folders you give a pairing become POSIX ACL entries for
nerve-run(the folder and everything below it, default entries so that what it makes stays yours, a pass-through entry on your closed folders above it), taken off again when the grant goes and byuninstall(which also takes them off whatnerve-runmade there, and gives what it made to the owner before its account goes, but for a file with more than one name, which may lie outside the folders and which it counts). A default ACL of the owner's own is left as it was, with an entry the owner gave itself there, and a folder that had none has none again. An inner grant that goes is counted off by a walk of the grant around it only where that walk saw all of it (no other file system, no folder closed to the owner); else it stays stale until its own walk goes through. Only pairings whose commands run asnerve-rungive it folders. The node walks the folders by descriptor and follows no link, also one a command puts in place of a folder meanwhile./tmp,/var/tmpand its home are always its own;HOMEand theXDG_*folders point there, and git trusts the repositories it reaches. Landlock holds each command to its own pairing's folders on top. A folder grant that failed is named byfolder add,folder setandstatus, also after a restart, and tried again. Accessnerve-runshould have lost but keeps stops all its commands until it is off;folder rm,status(grant_stalewith--jsonon the folder commands) and the app (nerve-run keeps access it should have lost) say so on every pairing ofnerve-run. - A new pairing starts in
foldermode. In 0.0.5 it started inallowlistmode with no rule, so every call was refused until you allowed something. - Where the kernel has no Landlock, the installed node runs
folderandallowlistmode commands asnerve-runall the same, held back by its account alone;statussaysas nerve-run, without Landlock, and the app says Without Landlock. In 0.0.5foldermode ran no command there, and a node you run yourself still runs none. - Without Landlock,
install,status(home_warningin--json) and the app warn thatnerve-runcan read every file in the owner's home that other accounts may read: when the home is open to them (mode 755, 711), or when a folder given to it lies in the home, which it passes through. For an open home the warning lasts until the owner closes it (chmod 750 ~); for a folder in the home it lasts as long as that folder is given, and only folders outside the home or a kernel with Landlock end it, whileumask 027only narrows whatnerve-runcan read. With Landlock nothing is said. - A sudo command the sudo list allows runs as root in every mode, with sudo on: for a pairing whose commands run as the owner through the owner's sudo and password, as in 0.0.5; for one whose commands run as
nerve-runthe node starts it as root itself, without the owner's password and without the sudo program, so the sudo list isnerve-run's only way to root. It runs in/bin/shwith the allowed line alone (a leading-nor--dropped, any other option of sudo refused with the reason) and a fixed environment, in its working folder, which the node opens as the owner without following a link and enters before the command becomes root.sudo onasks for the owner's own sudo password, which shows that the owner, who lets the sudo list run as root, may run commands as root here; in 0.0.5 it asked for the password of the account the commands ran as. - The node's keys may be in the keyring on every Linux kernel (#135). In 0.0.5 they stayed in files below Landlock ABI 9 (Linux 7.1):
config set machine.secret_storage keyringwas refused andsetupdid not ask. On the installed node the keyring isnerve's and no command runs asnerve, so none reaches it. A node you run yourself runs nofoldermode command while its keys are in the keyring, on every kernel; 0.0.5 ran them on Linux 7.1 or newer, with Landlock keeping them off the session bus, which no longer holds them. installnames the owner in the unit and suggestscenter addandfolder add +rwxnext.updateleaves the unit as it was, so a node installed with 0.0.5 getsnerve-run, its owner and the new unit fromsudo nerve-node installrun again from the owner's own account: afterupdatealone it runs no command for a center.- On Linux the owner runs
setupwithoutsudo: its questions take the owner's input through the owner socket, and its one step that needs root, installing a keyring for the node's user, takes root by the service's own rights, only after a yes at a terminal. That step runs the package manager andloginctlby their full paths from the system's folders, with a fixed environment, in/, without input, and installs GNOME Keyring without what it only recommends. Setup's folder step gives+rwxon Enter. - The keyring
setupinstalls for the node's user is asked in that user's own session: the probe runs with a fixed environment (HOME,USER,XDG_RUNTIME_DIR=/run/user/<uid>, the bus in it, the system'sPATH) and nothing of the caller's, also when the owner'ssetupruns it as the node's user, andsetupwaits until the user's service manager answers. A session whose service manager got another user'sXDG_RUNTIME_DIR, bus or XDG folders from the system-wide environment of the login (/etc/environment, as on a GitHub runner), which kept GNOME Keyring from starting ("Failed to activate service 'org.freedesktop.secrets': timed out") or from finding its keyrings, is set right (the user's own folders are set, not unset), and its bus and keyring started again, whether the keyring's unit isgnome-keyring-daemon.serviceor, in GNOME Keyring 40 (Ubuntu 22.04),gnome-keyring.service. It stays right:setupwrites/etc/systemd/user-environment-generators/99-nerve-session, which gives the session of the node's user its own folders at every start of its manager (uninstallremoves it), and the installed node does the same repair when it starts with its keys in the keyring (both only for the node's own usernerve, never for a node you run as yourself or a session with a desktop) (#135).
Root as the owner (#151)
installrun by root itself (a server where you log in as root) asks at a terminal whether root is to be the node's owner, after saying what it means; the answer is no unless you typeyoryes, and without a terminal (piped input included) nothing is asked.--owner=root --accept-root-ownersays yes without a terminal; either flag alone, or the flags throughsudoby another account, is refused, and on Windows both are. The question is asked only where input and output are a terminal and not with--json; elsewhere the answer is no at once, and install says so on stderr. A later install by root keeps root as the owner without asking;install --owner=noneleaves the node without an owner;updatekeeps it. In 0.0.5 aninstallrun by root made root the account commands run as, with a warning.- When
installchanges the owner (root to none, alice to root, root to alice), it first stops the service and takesnerve-run's ACL entries off every folder the earlier owner gave it, asuninstalldoes, once nothing else in the install can refuse; the node grants them again for the new owner, and without an owner (--owner=none) it givesnerve-runno folders, which install says. Where not all come off, the owner stays, its node walks every grant again, and the service runs again; so it does after any later failure. Entries an earlier owner's grants left are kept in the record and walked again at each sync, anduninstalltakes them off. The question and the install that makes root the owner name the centers that have the settings grant already. - With root as the owner,
usermode andallowlistmode with--as-ownerrun commands as root, which can do everything on the machine, Nerve and its keys included;foldermode andallowlistmode run asnerve-run, as on any node.nerve,nerve-run, another account with uid 0 and an account that reaches the node's folders are never the owner. statussays so ("owner_is_root"with--json), and so do the desktop app's Overview, Permissions (user mode and the chip say as root, a banner Its commands run as root) and Settings. Root gets no polkit files, and no center turns sudo on while root is the owner (sudo -vchecks no password of root's). A center with the settings grant can still set its own mode touserand so run as root:center grant settings writeand the desktop app warn of that while root is the owner.
Commands
--jsonon the read commands (status,center ls,folder ls,rule ls,cap ls,config get,proxy ls,relay ls,sudo status,service status,audit,log), and{"messages": [...]}or{"error": ...}from every other command (docs/user/commands.md).nerve-node proxy set --password-stdin --headers-stdintakes the proxy's password and headers from stdin, so neither shows in the process list.rule addandrule rmtake--action, the rule of an audit entry.audit --last Nshows the newest entries only.nerve-node log [--problems] [--last N] [--follow]shows the node's log, with--problemsits errors and warnings only.nerve-node audit clearstarts the audit log afresh: the entries so far becomeaudit.jsonl.1(as at 10 MiB) and the new log begins with "audit cleared by ";auditshows the entries from the clearing on.nerve-node log clearempties the log and its older file. Owner or root only, refused below a command a center runs.- With
--jsona change says"next_start": truewhen the node takes it only at its next start;relay addandrelay tokengive the token as data ("token"),backupwhat it wrote ("backup"),center rotate-keythe new key ("public_key"), andrestorewhat it did ("restored"). nerve-node service start|stop|restart|statuscontrols the installed service;statusneeds no root.- On the installed node its owner needs no
sudo(on Windows no administrator's terminal): on Linux the account that raninstall, which the unit names (on Windows the SIDsinstallrecords). The running node runs the owner's command asnerveover its owner socket (/run/nerve-node/owner.sock; a pipe on Windows) with the CLI's checks, letting in the owner and root only, by the caller's account;install,update,uninstall,sudo onand themachine.*settings that pick an account or program stay root's, and on Windowssetup.backupandrestorework for the owner too: the owner's ownnerve-nodeopens the file and hands it to the node (on Linux the open file over the socket, on Windows its bytes), so the node never opens a path in the owner's folders; for a restore the node stops and starts again by itself, and a backup that would change thosemachine.*settings is refused to the owner. A polkit rule (Linux) and the service's DACL (Windows) let the owner start, stop and restart the service; on Linux through a helper that does only that (/usr/local/lib/nerve-node/service-control, its own polkit action), so polkit 0.105 (Ubuntu 22.04), which takes no rules, lets the owner too, by a.pklafile.uninstallremoves the rule and the rights. Settings changes in the audit name the account that made them ("user", textby local (alice));status --jsonnames the owner ("owner") and the account each pairing's commands run as. A change the owner makes is in the node's log (alice runsrule add …as an owner); the reads the app makes every few seconds are at debug. cap ls --jsonlists the switchable capabilities (known).center addchecks the link before it asks: the center answers, its certificate is the pinned one, and the code is still valid (POST /nerve/v0/pair/checkon the center, which neither uses the code nor counts a try); it asks only when the pairing can work.center add --jsonwithout--yessays so in"check".relay addandrelay tokenshow anerve://relaylink with all the other node needs (the relay's address, which--addresssets, the node's name and token, the pin), andproxy settakes that link whole.rule edit <list> <entry> <new>changes one rule in place, as one change in the audit.installrun throughpkexectakes the user who ran it as the owner, as withsudo.- On Windows,
statusasks the running service through its control pipe, as it does on Linux (in 0.0.5 it saiddaemon: not knownthere). config get machine.secret_storage --jsonsays whether the keyring can hold the keys here ("keyring").- A node's name (
node.name, a relay's nodes), a provider's (providers.allowed) and a center's name on the node start and end with a letter or digit, so that none reads as a flag: the node refuses a config with another, as dosetup,center add --nameandrelay add, and the center refuses such a node name in a pairing and a rename (#146); the published schemas say the same of node names. - Changed: on the installed node,
sudo nerve-node backupandrestoreopen the backup file as the user who ransudo(orpkexec), where 0.0.5 opened it asnerve, so a backup in your own folders works and is yours; the command itself still runs asnerve. Where that user may not open the file, the owner of its folder does: a root shell (sudo -i) writes to root's folders as root, andsudo nerve-node backup /var/lib/nerve/state/…writes asnerve. Throughpkexec(the desktop app) a file its user may not open is refused, and so is an asker without an account.
The center
- The web UI is redrawn in the desktop app's design (#137): its colours in light and dark, type scale, spaces and controls, and its animations of 100 to 200 ms (0.0.5's ran up to 350 ms), none when the system asks for less motion. The choice of System, Light and Dark has its names at the foot of the menu, and Help sits there too. A busy button shows a spinner. Help is built as the app's: the keyboard shortcuts and the command reference, by topic or A to Z, filtered, each part opening on its own, and a User guide link at the top that opens these pages on GitHub. The center has the app's icon.
- The Action checker's switches in the web UI save again: a click sent nothing and showed "Cannot read properties of undefined". The page no longer prints the word "null" in its Local model part. A save that the center refuses no longer leaves its change in the page: the switch goes back, and a later save does not send it. The Action checker saves one change at a time: its buttons and switches are off until a save has settled, so a refused change is never saved by a second one. A page that is drawn anew (by a save or by the page's refresh) keeps the keyboard focus on the same control, found by what it is and not by where it was; when that item is gone, the focus goes to the heading of its list, a text field that gets the focus back keeps what was typed into it, and a save that is still running does not pull the focus back from where you moved it. An off switch, in the web UI and in the desktop app, has a track with at least 3:1 contrast against its surface (WCAG 1.4.11).
- The center no longer stops answering on its web UI port under quick use (#150): two requests that read the node list and the session list at the same time (the overview the UI polls) could lock each other out for good, and every request after them waited too, until the center was restarted.
- The center's ports are no longer held by slow peers (#150): a peer that reads nothing of an answer, or sends its request a byte now and then, is dropped after 15 s plus a second per 16 KiB, where before it kept one of the port's 256 connections as long as it liked.
- A slow disk no longer holds up the center's ports: the state file is written after the state is unlocked, so a request that only reads does not wait for it; changes that come together are written once, and a save that waits for the disk holds none of the threads that serve the ports. On Linux the state file's folder is synced after each write, so a saved change is still there after a power cut.
- The pairing card of the web UI offers the command and the bare link (for the desktop app), each with Copy; 0.0.5 showed the command and, apart from it, the center, the code and the pin.
POST /nerve/v0/pair/checktells a node whether a pairing code is still open without using it or counting a try; checks with codes that are not open have a budget of their own (10 in 10 minutes), after which the center answersbusy.
The action checker
- New built-in blocklist entries keep an agent off the node it runs on: running
nerve-nodeornerve-node-desktop, a command that names the node's socket, pipe, config or state folder, and a write into those folders (idsnerve-node,nerve-node-desktop,nerve-node-socket,nerve-node-folders,fs-nerve-…). They catch mistakes, not commands written to get past them.
No migrations before 0.1.0 (#144)
- Until the first 0.1.0 release nobody runs Nerve, so the code that read or upgraded what older versions wrote is gone; from 0.1.0 on, migrations come as they are needed.
- The node's control socket refuses
statuswithout its token too (0.0.5 answered it, forupdateof 0.0.2). updateon Windows replaces the program and restarts the service only: it no longer registers the Event Log source, whichinstalldoes.
Static Linux binaries (x86_64, aarch64) and Windows x86_64 programs of nerve-center and nerve-node, and the desktop app nerve-node-desktop for Linux x86_64 (glibc 2.35 or newer) and Windows x86_64, built from 39e9f2a. SHA256SUMS lists their checksums. They are not signed; nerve-node update and the desktop app's update check the one they download against SHA256SUMS (docs/user/service.md, docs/user/desktop.md).