@mmmbuto/nexuscrew 0.9.16 → 0.9.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +132 -3
- package/docs/ALIBABA_TOKEN_PLAN.md +70 -0
- package/docs/AUDIO_SHARE.md +119 -0
- package/docs/CELL_PANEL.md +156 -0
- package/docs/CONFIGURATION.md +122 -0
- package/docs/FLEET.md +222 -0
- package/docs/INSTALLATION.md +128 -0
- package/docs/LIVE_PROMPT.md +110 -0
- package/docs/MCP.md +151 -0
- package/docs/NODES.md +214 -0
- package/docs/NOTIFICATIONS.md +101 -0
- package/docs/OPERATIONS.md +114 -0
- package/docs/README.md +55 -0
- package/docs/SECURITY.md +185 -0
- package/docs/VL_MICRO_NODES.md +107 -0
- package/docs/img/fleet-deck-desktop.png +0 -0
- package/docs/img/session-mobile.png +0 -0
- package/docs/live-prompt-templates/LIVE_PROMPT.en.md +67 -0
- package/docs/live-prompt-templates/LIVE_PROMPT.es.md +70 -0
- package/docs/live-prompt-templates/LIVE_PROMPT.it.md +67 -0
- package/frontend/dist/assets/{index-CwsGpQwK.js → index-jBYBPXhB.js} +1 -1
- package/frontend/dist/index.html +1 -1
- package/frontend/dist/version.json +1 -1
- package/lib/auth/middleware.js +1 -1
- package/lib/cells/scope-guard.js +2 -2
- package/lib/cli/commands.js +7 -7
- package/lib/cli/fleet-service.js +2 -2
- package/lib/cli/pidfile.js +15 -15
- package/lib/cli/service.js +2 -2
- package/lib/cli/stable-alias.js +1 -1
- package/lib/files/routes.js +2 -2
- package/lib/fleet/builtin.js +6 -6
- package/lib/fleet/cell-exec.js +4 -4
- package/lib/fleet/cell-lease-server.js +17 -17
- package/lib/fleet/definitions.js +5 -5
- package/lib/fleet/launch-broker.js +3 -3
- package/lib/fleet/lease-client.js +1 -1
- package/lib/fleet/managed.js +11 -11
- package/lib/fleet/prompt-delivery.js +1 -1
- package/lib/fleet/runtime.js +3 -3
- package/lib/live-host/bridge.js +3 -3
- package/lib/live-host/routes.js +1 -1
- package/lib/mcp/server.js +7 -8
- package/lib/mcp/tools.js +1 -1
- package/lib/nodes/commands.js +2 -2
- package/lib/nodes/health.js +1 -1
- package/lib/nodes/identity.js +10 -13
- package/lib/nodes/store.js +2 -2
- package/lib/nodes/tunnel-supervisor.js +7 -7
- package/lib/nodes/tunnel.js +10 -11
- package/lib/notify/asks.js +4 -4
- package/lib/notify/persist.js +1 -1
- package/lib/notify/push.js +4 -4
- package/lib/notify/routes.js +12 -12
- package/lib/proxy/node-proxy.js +4 -4
- package/lib/proxy/panel-proxy.js +2 -2
- package/lib/pty/attach.js +1 -1
- package/lib/server.js +3 -3
- package/lib/settings/pairing-coordinator.js +1 -1
- package/lib/settings/routes.js +4 -4
- package/lib/tmux/lifecycle.js +4 -4
- package/lib/tmux/preview.js +1 -1
- package/lib/update/runner.js +9 -9
- package/lib/vl-nodes/broker.js +1 -1
- package/lib/ws/bridge.js +1 -1
- package/package.json +20 -2
- package/frontend/index.html +0 -20
package/docs/NODES.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Connect nodes
|
|
2
|
+
|
|
3
|
+
[← Documentation index](README.md)
|
|
4
|
+
|
|
5
|
+
Every NexusCrew installation starts as a local node. Connected nodes use
|
|
6
|
+
supervised OpenSSH links; NexusCrew does not replace SSH, generate SSH keys or
|
|
7
|
+
edit `authorized_keys`.
|
|
8
|
+
|
|
9
|
+
## Pair a node
|
|
10
|
+
|
|
11
|
+
1. On the installation that will host the new node, open
|
|
12
|
+
**Settings → Nodes → Invite a node**.
|
|
13
|
+
2. Provide the OpenSSH target the joining device can use, such as `user@host`
|
|
14
|
+
or a local SSH config alias.
|
|
15
|
+
3. On the joining device, paste the complete pairing link in
|
|
16
|
+
**Settings → Nodes** and choose **Test and connect**.
|
|
17
|
+
4. If the portable address cannot select the correct key, open
|
|
18
|
+
**Advanced / edit** and use the SSH alias that already works on that device.
|
|
19
|
+
|
|
20
|
+
An invite belongs to the installation that will host the node, so it is issued
|
|
21
|
+
there and nowhere else. Minting one is not a federated operation: a node you are
|
|
22
|
+
connected to cannot be asked to admit a further node on your behalf. If you are
|
|
23
|
+
on a client and the interface refuses, open NexusCrew on the hub itself.
|
|
24
|
+
|
|
25
|
+
The pairing payload is not a browser URL. It contains a short-lived, one-time
|
|
26
|
+
invite and routing fields, but no SSH private key, provider key or PWA token.
|
|
27
|
+
|
|
28
|
+
For headless installations, use:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
nexuscrew nodes invite --ssh user@host
|
|
32
|
+
nexuscrew nodes pair
|
|
33
|
+
nexuscrew nodes join
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Pair/join reads the one-time payload from stdin so it does not need to appear
|
|
37
|
+
in process arguments.
|
|
38
|
+
|
|
39
|
+
## Where a node's controls live
|
|
40
|
+
|
|
41
|
+
**Settings → Nodes** lists nodes one per row: name, how it is reached, and what
|
|
42
|
+
it sees of the network. Selecting a row opens that node's sheet — a panel at the
|
|
43
|
+
side on a wide screen, a sheet rising from the bottom on a narrow one — and
|
|
44
|
+
every per-node control lives there: reachability and transport, visibility and
|
|
45
|
+
its grants, edit, test, connect or disconnect, remove.
|
|
46
|
+
|
|
47
|
+
Nothing in the list itself mutates a node. Publishing *this* device, and
|
|
48
|
+
inviting a new one, stay outside the list because they are not properties of a
|
|
49
|
+
node you are looking at.
|
|
50
|
+
|
|
51
|
+
## Labels and routes
|
|
52
|
+
|
|
53
|
+
The human-readable display label is separate from the local route handle. A
|
|
54
|
+
route defaults to a readable slug plus a stable node-ID suffix, so multiple
|
|
55
|
+
devices reporting `localhost` still receive distinct identities.
|
|
56
|
+
|
|
57
|
+
Rename an owned node from Settings or a roster; the same canonical label then
|
|
58
|
+
appears everywhere without changing route, identity, credentials, Share state
|
|
59
|
+
or deck identity.
|
|
60
|
+
|
|
61
|
+
For a routed node the current installation does not own, Settings offers a
|
|
62
|
+
local alias. That alias stays private to the viewing installation and follows
|
|
63
|
+
the stable instance identity.
|
|
64
|
+
|
|
65
|
+
## Private and shared state
|
|
66
|
+
|
|
67
|
+
Newly joined devices are private by default. Private pairing is administrative
|
|
68
|
+
inventory, not operational publication: a private client can remain listed in
|
|
69
|
+
Settings while staying absent from routable topology, deck bars and MCP
|
|
70
|
+
discovery.
|
|
71
|
+
|
|
72
|
+
Enabling **Share this device through the selected hub** adds a verified reverse
|
|
73
|
+
channel to the existing SSH process. The hub decides whether authorized peers
|
|
74
|
+
see the whole network, only the hub or an explicit subset.
|
|
75
|
+
|
|
76
|
+
Share is stored as desired state:
|
|
77
|
+
|
|
78
|
+
- Failed activation rolls back to private.
|
|
79
|
+
- Deactivation saves private intent first.
|
|
80
|
+
- The hub must acknowledge withdrawal before the reverse channel is removed.
|
|
81
|
+
- If acknowledgement fails, bounded boot retries reconcile the saved state
|
|
82
|
+
without claiming remote removal completed.
|
|
83
|
+
|
|
84
|
+
Temporary loss of reachability does not revoke consent. An authorized node
|
|
85
|
+
remains visible as stale/offline until an authoritative refresh restores it or
|
|
86
|
+
confirms withdrawal.
|
|
87
|
+
|
|
88
|
+
## Which cells a node may see
|
|
89
|
+
|
|
90
|
+
Sharing decides whether a node is reachable. A **cell scope** decides how much
|
|
91
|
+
of this installation it reaches once it is:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
nexuscrew nodes cells <name|nodeId> all # default
|
|
95
|
+
nexuscrew nodes cells <name|nodeId> none
|
|
96
|
+
nexuscrew nodes cells <name|nodeId> Research,Dev
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The scope is set here, on the installation that owns the cells — never by the
|
|
100
|
+
node being scoped, and never over the federation. It applies to what that node
|
|
101
|
+
lists (cells, fleet status, sessions) and to what it can act on, including the
|
|
102
|
+
terminal attach; a session belonging to no cell is outside every scope.
|
|
103
|
+
Switching away from the explicit list clears it, so a cell revoked today does
|
|
104
|
+
not come back the next time a list is set.
|
|
105
|
+
|
|
106
|
+
Within its scope a node keeps full authority. Read the cell scope section of
|
|
107
|
+
[Security model](SECURITY.md) before using it as a boundary against anything
|
|
108
|
+
you do not trust.
|
|
109
|
+
|
|
110
|
+
## Tunnel behavior
|
|
111
|
+
|
|
112
|
+
NexusCrew creates one supervised private `ssh -L` process for a hub connection
|
|
113
|
+
and proves the forwarded TCP endpoint before reporting success. It does not use
|
|
114
|
+
`autossh` as a hidden second supervisor. A shared peer with a verified
|
|
115
|
+
rotatable pool may additionally run a short-lived, per-slot reverse supervisor;
|
|
116
|
+
each one has a distinct local target so the hub can prove the exact reverse
|
|
117
|
+
slot it reached.
|
|
118
|
+
|
|
119
|
+
Reverse ports are reserved across active and pending pairings, probed before
|
|
120
|
+
use and protected by a persistent uniqueness check. A stale same-name peer or
|
|
121
|
+
late collision returns an actionable conflict instead of silently consuming
|
|
122
|
+
the invitation.
|
|
123
|
+
|
|
124
|
+
OpenSSH restrictions still apply. A shared client may need its accepted hub key
|
|
125
|
+
to allow the negotiated reverse listener:
|
|
126
|
+
|
|
127
|
+
```text
|
|
128
|
+
permitlisten="127.0.0.1:44002"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Use the exact port printed by the tunnel diagnostic.
|
|
132
|
+
|
|
133
|
+
## Rotatable reverse-port pools
|
|
134
|
+
|
|
135
|
+
A new shared peer receives a three-port pool with the same base and offsets
|
|
136
|
+
`+100` and `+200`. Read the base this installation actually assigned — do not
|
|
137
|
+
copy the one below, it is a placeholder — and grant exactly those three ports
|
|
138
|
+
**on the hub**, in the `~/.ssh/authorized_keys` entry that the hub accepts from
|
|
139
|
+
that device:
|
|
140
|
+
|
|
141
|
+
```text
|
|
142
|
+
permitlisten="127.0.0.1:<BASE>",permitlisten="127.0.0.1:<BASE+100>",permitlisten="127.0.0.1:<BASE+200>"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Add those options when you install or update the key. NexusCrew never writes,
|
|
146
|
+
widens, removes, or otherwise edits `authorized_keys`. A legacy peer with only
|
|
147
|
+
one `permitlisten` remains usable, but is reported as not rotatable until the
|
|
148
|
+
operator installs its full pool.
|
|
149
|
+
|
|
150
|
+
The direction is worth stating plainly, because getting it backwards sends you
|
|
151
|
+
looking where nothing is wrong: **the device asks for the reverse bind, the
|
|
152
|
+
hub's sshd grants or refuses it.** Nothing needs changing on the device — the
|
|
153
|
+
grant lives on the hub, in the entry carrying that device's key.
|
|
154
|
+
|
|
155
|
+
**Re-pairing a device changes its base.** Pool bases are monotonic and a
|
|
156
|
+
removed peer retires its own, so a device paired again is assigned a new one
|
|
157
|
+
while its key still carries the previous grant. Nothing warns you: the pairing
|
|
158
|
+
succeeds, the private `-L` works, the device looks connected — and Share alone
|
|
159
|
+
fails, because it is the only operation that needs the reverse channel. The
|
|
160
|
+
symptom is `share-channel-not-ready` with HTTP 409, and no amount of re-pairing
|
|
161
|
+
fixes it, because pairing is not what is broken.
|
|
162
|
+
|
|
163
|
+
The hub records every refusal it issues as a `SHARE_CHANNEL_REFUSED`
|
|
164
|
+
diagnostic, naming the peer, the typed failure code and **the port it probed**.
|
|
165
|
+
Read it in the Diagnostics view of the PWA. Confirm the cause on the hub with
|
|
166
|
+
`journalctl -u ssh` or `auth.log`:
|
|
167
|
+
|
|
168
|
+
```text
|
|
169
|
+
Received request ... to remote forward to host 127.0.0.1 port <BASE>, but the request was denied
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
That line names the exact port SSH policy is refusing. Grant that port on that
|
|
173
|
+
key, and remove any grant pointing at a base that now belongs to another peer:
|
|
174
|
+
one key must never hold a listen right on another peer's pool.
|
|
175
|
+
|
|
176
|
+
Rotation is deliberately narrow:
|
|
177
|
+
|
|
178
|
+
- The peer proposes a pre-authorized slot over its existing private `-L`; the
|
|
179
|
+
hub alone assigns the lease and generation.
|
|
180
|
+
- Every slot is proven over a slot-specific local endpoint before automatic
|
|
181
|
+
rotation is allowed. A generic SSH failure or a missing grant stops the
|
|
182
|
+
episode after one candidate instead of consuming the pool.
|
|
183
|
+
- A verified collision can switch to one ready slot at most once every ten
|
|
184
|
+
minutes. The old slot drains briefly; an unproven or unattributable listener
|
|
185
|
+
is quarantined and reported, never terminated automatically.
|
|
186
|
+
- Pool bases are monotonic. Removing a peer retires its base so a key that
|
|
187
|
+
still has old `permitlisten` rights cannot claim a later peer's port.
|
|
188
|
+
|
|
189
|
+
If the pool is exhausted, degraded, unverified, or not configured, NexusCrew
|
|
190
|
+
does not guess a new port or change SSH policy. Correct the displayed
|
|
191
|
+
`permitlisten` line, then retry Share from the device.
|
|
192
|
+
|
|
193
|
+
## CLI
|
|
194
|
+
|
|
195
|
+
```text
|
|
196
|
+
nexuscrew nodes list [--json]
|
|
197
|
+
nexuscrew nodes inspect <name|nodeId>
|
|
198
|
+
nexuscrew nodes edit <name|nodeId> ...
|
|
199
|
+
nexuscrew nodes test # every peer, plus what is only claimed
|
|
200
|
+
nexuscrew nodes up|down|connect|disconnect <name|nodeId>
|
|
201
|
+
nexuscrew nodes restart|reconnect <name|nodeId>
|
|
202
|
+
nexuscrew nodes share <name|nodeId> on|off
|
|
203
|
+
nexuscrew nodes cells <name|nodeId> all|none|Cell1,Cell2
|
|
204
|
+
nexuscrew nodes remove <name|nodeId> --yes
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Node and deck identities remain owner-qualified across the network. Routed
|
|
208
|
+
HTTP and WebSocket requests recheck authorization, hop count and cycle rules.
|
|
209
|
+
|
|
210
|
+
## Related guides
|
|
211
|
+
|
|
212
|
+
- [Installation](INSTALLATION.md)
|
|
213
|
+
- [Fleet and terminals](FLEET.md)
|
|
214
|
+
- [Security](SECURITY.md)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Notifications
|
|
2
|
+
|
|
3
|
+
[← Documentation index](README.md)
|
|
4
|
+
|
|
5
|
+
NexusCrew can deliver live cell notifications through three independent
|
|
6
|
+
surfaces:
|
|
7
|
+
|
|
8
|
+
1. In-app toasts over the live event stream.
|
|
9
|
+
2. Best-effort Web Push when the browser and operating system support it.
|
|
10
|
+
3. Optional on-device spoken alerts while the PWA is visible and focused.
|
|
11
|
+
|
|
12
|
+
## In-app and push delivery
|
|
13
|
+
|
|
14
|
+
Toasts are the primary live UI signal. Web Push requires browser permission;
|
|
15
|
+
on iOS it also requires an installed PWA.
|
|
16
|
+
|
|
17
|
+
The service worker cannot synthesize speech. A hidden or closed PWA therefore
|
|
18
|
+
uses only its normal system-notification path.
|
|
19
|
+
|
|
20
|
+
Depending on the operating system, a visible toast, Web Push notification and
|
|
21
|
+
spoken alert may coexist.
|
|
22
|
+
|
|
23
|
+
## Spoken alerts
|
|
24
|
+
|
|
25
|
+
Enable **Settings → System → Read notifications aloud**.
|
|
26
|
+
|
|
27
|
+
Spoken alerts are:
|
|
28
|
+
|
|
29
|
+
- off by default
|
|
30
|
+
- stored as a browser-local, per-device preference
|
|
31
|
+
- available even when server settings are read-only
|
|
32
|
+
- synthesized by the device's browser speech engine
|
|
33
|
+
- spoken in the notification's declared content language when available
|
|
34
|
+
- never sent to a remote speech service
|
|
35
|
+
|
|
36
|
+
For backend-delivered speech on a shared Mac, Linux host, or Termux device,
|
|
37
|
+
use [Audio Share and native TTS](AUDIO_SHARE.md). It has a separate local
|
|
38
|
+
consent gate and does not depend on the browser remaining foregrounded.
|
|
39
|
+
|
|
40
|
+
`nc_notify` accepts an optional `lang` value for Italian, English or Spanish,
|
|
41
|
+
using either a base code (`it`) or an equivalent BCP-47 locale (`it-IT`). The
|
|
42
|
+
live PWA selects a matching installed voice when the browser exposes one.
|
|
43
|
+
Browsers commonly report an empty voice list during startup, so NexusCrew
|
|
44
|
+
refreshes its cache on `voiceschanged` and lets the browser choose until a
|
|
45
|
+
matching voice is available.
|
|
46
|
+
|
|
47
|
+
Notifications from older clients have no language field. For compatibility,
|
|
48
|
+
NexusCrew uses a language detected from the body only when the signal is
|
|
49
|
+
strong; short or mixed technical text falls back to the current UI language.
|
|
50
|
+
Unknown languages leave the utterance language empty so the browser uses its
|
|
51
|
+
own default rather than being forced to English.
|
|
52
|
+
|
|
53
|
+
Enabling speech runs an audible preview. NexusCrew reports success only after
|
|
54
|
+
the browser emits both speech start and speech end. A silent, missing or failed
|
|
55
|
+
native voice reports **voice unavailable** rather than a false success.
|
|
56
|
+
|
|
57
|
+
The preview must succeed once per page session, including after a reload.
|
|
58
|
+
|
|
59
|
+
## Focus and privacy
|
|
60
|
+
|
|
61
|
+
Speech runs only while the document is visible and has operating-system focus.
|
|
62
|
+
Blur, background, opt-out and unmount cancel the current utterance and clear
|
|
63
|
+
pending work. Skipped notifications are not replayed when focus returns.
|
|
64
|
+
|
|
65
|
+
Focus provides one speaker among NexusCrew windows on the same device. Two
|
|
66
|
+
separately opted-in devices may both speak when each PWA is visible and
|
|
67
|
+
focused; this is intentional.
|
|
68
|
+
|
|
69
|
+
Credential-shaped values and private home paths are redacted before speech.
|
|
70
|
+
Spoken alerts do not replace the toast's accessibility status, so screen-reader
|
|
71
|
+
users can keep the feature disabled if their assistive technology already
|
|
72
|
+
announces notifications.
|
|
73
|
+
|
|
74
|
+
## Queue behavior
|
|
75
|
+
|
|
76
|
+
- Duplicate live frames are suppressed for 60 seconds.
|
|
77
|
+
- At most two normal alerts remain pending.
|
|
78
|
+
- A high-urgency alert interrupts the current queue.
|
|
79
|
+
- A 30-second watchdog cancels a stuck browser utterance and advances.
|
|
80
|
+
- Failures are isolated from the event stream and visual toast delivery.
|
|
81
|
+
|
|
82
|
+
Only new live notification frames are spoken. Persisted questions and previous
|
|
83
|
+
events are not replayed.
|
|
84
|
+
|
|
85
|
+
## Platform notes
|
|
86
|
+
|
|
87
|
+
| Platform | Expected behavior |
|
|
88
|
+
|---|---|
|
|
89
|
+
| Chrome / Edge desktop | System voice while the focused PWA is visible |
|
|
90
|
+
| Firefox desktop | System voice where Web Speech synthesis is available |
|
|
91
|
+
| Safari desktop | System voice while the page is focused |
|
|
92
|
+
| Android Chrome | Visible/focused PWA can speak; background uses normal push |
|
|
93
|
+
| iOS installed PWA | Visible/focused PWA can speak; lock screen relies on normal push |
|
|
94
|
+
|
|
95
|
+
Browser and operating-system voice availability remains device-dependent.
|
|
96
|
+
|
|
97
|
+
## Related guides
|
|
98
|
+
|
|
99
|
+
- [Configuration](CONFIGURATION.md)
|
|
100
|
+
- [Audio Share and native TTS](AUDIO_SHARE.md)
|
|
101
|
+
- [Security](SECURITY.md)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Operations
|
|
2
|
+
|
|
3
|
+
[← Documentation index](README.md)
|
|
4
|
+
|
|
5
|
+
## CLI
|
|
6
|
+
|
|
7
|
+
| Command | Purpose |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `nexuscrew` | Start or reuse the background runtime and print status |
|
|
10
|
+
| `nexuscrew show` | Open the authenticated PWA |
|
|
11
|
+
| `nexuscrew show token` | Print the authenticated link |
|
|
12
|
+
| `nexuscrew status` | Show service, port, role and node status |
|
|
13
|
+
| `nexuscrew stop` | Stop NexusCrew and managed tunnels, preserving tmux |
|
|
14
|
+
| `nexuscrew restart` | Restart NexusCrew and autostart links, preserving tmux |
|
|
15
|
+
| `nexuscrew boot` | Enable startup persistence |
|
|
16
|
+
| `nexuscrew boot off` | Disable persistence without stopping the runtime |
|
|
17
|
+
| `nexuscrew doctor` | Check Node, PTY, tmux, SSH, service and platform integration |
|
|
18
|
+
| `nexuscrew nodes ...` | Inspect and manage connected nodes |
|
|
19
|
+
| `nexuscrew help` | Show public command help |
|
|
20
|
+
| `nexuscrew version` | Show the installed version |
|
|
21
|
+
|
|
22
|
+
`init`, `serve`, `fleet-boot` and `mcp` are internal entry points rather than
|
|
23
|
+
the normal interactive workflow.
|
|
24
|
+
|
|
25
|
+
## Boot integration
|
|
26
|
+
|
|
27
|
+
| Platform | Integration |
|
|
28
|
+
|---|---|
|
|
29
|
+
| Linux | systemd user service |
|
|
30
|
+
| macOS | per-user LaunchAgent |
|
|
31
|
+
| Android / Termux | detached runtime and optional Termux:Boot script |
|
|
32
|
+
|
|
33
|
+
Boot starts only cells marked `boot:true`. It is startup persistence, not a
|
|
34
|
+
watchdog for the tmux server itself.
|
|
35
|
+
|
|
36
|
+
Linux services use `KillMode=process` so restarting NexusCrew does not stop the
|
|
37
|
+
shared tmux server. Lifecycle commands fail closed when this protection cannot
|
|
38
|
+
be verified.
|
|
39
|
+
|
|
40
|
+
Termux pidfiles include process identity data. If Android reuses a PID under
|
|
41
|
+
another app UID, NexusCrew removes only the stale pidfile, never signals the
|
|
42
|
+
foreign process, and restarts only the configured OpenSSH supervisor.
|
|
43
|
+
|
|
44
|
+
## Backup and restore
|
|
45
|
+
|
|
46
|
+
**Settings → Fleet** can export and restore selected cells, system prompts and
|
|
47
|
+
reusable engines.
|
|
48
|
+
|
|
49
|
+
Restore:
|
|
50
|
+
|
|
51
|
+
- previews conflicts
|
|
52
|
+
- supports per-item selection
|
|
53
|
+
- reports active cells requiring restart
|
|
54
|
+
- stores working directories relative to the target user's home
|
|
55
|
+
- requires explicit repair for legacy or foreign absolute paths
|
|
56
|
+
|
|
57
|
+
Archives contain credential variable names, never credential values, browser
|
|
58
|
+
tokens or live tmux state.
|
|
59
|
+
|
|
60
|
+
## Updates
|
|
61
|
+
|
|
62
|
+
Global npm installs can follow stable `latest` automatically. NexusCrew:
|
|
63
|
+
|
|
64
|
+
1. Serializes update attempts.
|
|
65
|
+
2. Installs the selected stable version.
|
|
66
|
+
3. Verifies the CLI and same-port runtime.
|
|
67
|
+
4. Rolls back once to the exact previous version if health checks fail.
|
|
68
|
+
|
|
69
|
+
It never installs prereleases from `latest` or silently downgrades. Use
|
|
70
|
+
`NEXUSCREW_AUTO_UPDATE=0` to disable the scheduler.
|
|
71
|
+
|
|
72
|
+
## Diagnostics
|
|
73
|
+
|
|
74
|
+
**Settings → System → Diagnostics** shows a bounded in-memory event buffer for the
|
|
75
|
+
local installation or an authorized routed node.
|
|
76
|
+
|
|
77
|
+
Verbose collection is explicit and expires after 5, 15, 30 or 60 minutes.
|
|
78
|
+
Operational warnings and errors remain available while verbose mode is off.
|
|
79
|
+
The view supports filtering, pause, autoscroll, copy, JSON export and clear.
|
|
80
|
+
|
|
81
|
+
Records are structured and redacted before storage. Raw terminal content,
|
|
82
|
+
prompts, command lines, environment values, credentials and filesystem paths
|
|
83
|
+
are not accepted as diagnostic metadata. Fleet launch failures expose closed
|
|
84
|
+
`code` and `phase` values rather than raw stderr.
|
|
85
|
+
|
|
86
|
+
## Troubleshooting
|
|
87
|
+
|
|
88
|
+
- **Node older than 18** — upgrade Node before initialization.
|
|
89
|
+
- **tmux missing** — install tmux and rerun `nexuscrew`.
|
|
90
|
+
- **OpenSSH missing** — install `ssh`; `autossh` remains optional.
|
|
91
|
+
- **systemd user service unavailable** — consider
|
|
92
|
+
`loginctl enable-linger "$USER"`.
|
|
93
|
+
- **LaunchAgent failure** — inspect `nexuscrew doctor` and the user
|
|
94
|
+
`~/Library/LaunchAgents` permissions.
|
|
95
|
+
- **Termux:Boot does not start** — install and open the Android app once.
|
|
96
|
+
- **Voice unavailable** — distinguish browser synthesis from optional server
|
|
97
|
+
speech-to-text; see [Notifications](NOTIFICATIONS.md).
|
|
98
|
+
|
|
99
|
+
## Development
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npm test
|
|
103
|
+
npm run build
|
|
104
|
+
node bin/nexuscrew.js serve
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Tests that exercise tmux use private sockets and must never attach to or stop
|
|
108
|
+
the operator's tmux server.
|
|
109
|
+
|
|
110
|
+
## Related guides
|
|
111
|
+
|
|
112
|
+
- [Installation](INSTALLATION.md)
|
|
113
|
+
- [Configuration](CONFIGURATION.md)
|
|
114
|
+
- [Security](SECURITY.md)
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# NexusCrew documentation
|
|
2
|
+
|
|
3
|
+
[← Main README](../README.md)
|
|
4
|
+
|
|
5
|
+
NexusCrew is designed around a small public entry point and focused technical
|
|
6
|
+
guides. Start with installation, then open only the area you need.
|
|
7
|
+
|
|
8
|
+
| Guide | Use it for |
|
|
9
|
+
|---|---|
|
|
10
|
+
| [Installation](INSTALLATION.md) | Platform prerequisites, first run, verification and upgrades |
|
|
11
|
+
| [Fleet and terminals](FLEET.md) | Cells, engines, providers, decks, terminal input and mobile behavior |
|
|
12
|
+
| [The Live per-cell prompt](LIVE_PROMPT.md) | Where `LIVE_PROMPT.md` goes, what the bridge does with it, and the ready-to-copy IT/EN/ES templates |
|
|
13
|
+
| [The cell panel](CELL_PANEL.md) | A web interface next to the terminal: configuration, the loopback rule, how the frame authenticates, and granting it across nodes |
|
|
14
|
+
| [Connect nodes](NODES.md) | Pairing, SSH routes, private/share state and routed aliases |
|
|
15
|
+
| [Notifications](NOTIFICATIONS.md) | In-app toasts, Web Push and on-device spoken alerts |
|
|
16
|
+
| [Audio Share and native TTS](AUDIO_SHARE.md) | Node-native TTS, consent, exact targets, groups and MCP commands |
|
|
17
|
+
| [MCP bridge](MCP.md) | Operator tools, client configuration and cell-to-cell delivery |
|
|
18
|
+
| [VL micro-device nodes](VL_MICRO_NODES.md) | Pairing, exact health, bounded management and ACK semantics |
|
|
19
|
+
| [Configuration](CONFIGURATION.md) | Local paths, environment overrides and browser-local settings |
|
|
20
|
+
| [Operations](OPERATIONS.md) | CLI, boot, backups, updates, diagnostics and development |
|
|
21
|
+
| [Security](SECURITY.md) | Network, token, credential, file and federation boundaries |
|
|
22
|
+
| [Alibaba Token Plan](ALIBABA_TOKEN_PLAN.md) | Managed Claude Code, Codex-VL and Pi profiles |
|
|
23
|
+
|
|
24
|
+
Additional public references:
|
|
25
|
+
|
|
26
|
+
- [MCP companions](../MCP_COMPANIONS.md)
|
|
27
|
+
- [Machine-readable companion catalog](../mcp-companions.json)
|
|
28
|
+
- [Changelog](../CHANGELOG.md)
|
|
29
|
+
- [License](../LICENSE)
|
|
30
|
+
|
|
31
|
+
## Fast paths
|
|
32
|
+
|
|
33
|
+
### I want to use NexusCrew on this machine
|
|
34
|
+
|
|
35
|
+
Read [Installation](INSTALLATION.md), run `nexuscrew doctor`, then open the PWA
|
|
36
|
+
with `nexuscrew show`.
|
|
37
|
+
|
|
38
|
+
### I want to reach another machine
|
|
39
|
+
|
|
40
|
+
Read [Connect nodes](NODES.md). NexusCrew supervises OpenSSH; it does not
|
|
41
|
+
replace your SSH configuration or create SSH keys.
|
|
42
|
+
|
|
43
|
+
### I want to run AI CLI workers
|
|
44
|
+
|
|
45
|
+
Read [Fleet and terminals](FLEET.md), then configure cells and provider
|
|
46
|
+
presence in **Settings → Fleet**.
|
|
47
|
+
|
|
48
|
+
### I want an AI session to contact the operator
|
|
49
|
+
|
|
50
|
+
Read [MCP bridge](MCP.md) and register `nexuscrew mcp` in the client.
|
|
51
|
+
|
|
52
|
+
### I want to expose NexusCrew publicly
|
|
53
|
+
|
|
54
|
+
Do not. Read [Security](SECURITY.md): the supported model is loopback plus an
|
|
55
|
+
SSH tunnel or VPN you control.
|
package/docs/SECURITY.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Security model
|
|
2
|
+
|
|
3
|
+
[← Documentation index](README.md)
|
|
4
|
+
|
|
5
|
+
NexusCrew is local-first and fail-closed. It has no hosted control plane,
|
|
6
|
+
required account or supported public-listener mode.
|
|
7
|
+
|
|
8
|
+
## Network boundary
|
|
9
|
+
|
|
10
|
+
NexusCrew binds only to `127.0.0.1`. Non-loopback binds are rejected.
|
|
11
|
+
|
|
12
|
+
To reach a remote installation, carry its loopback port through SSH or a VPN
|
|
13
|
+
you control:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
ssh -L 41820:127.0.0.1:41820 user@your-host
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Direct public exposure through a reverse proxy, public port forward or network
|
|
20
|
+
bind is not supported.
|
|
21
|
+
|
|
22
|
+
## Browser authentication
|
|
23
|
+
|
|
24
|
+
Every API and WebSocket connection requires the local bearer token. The token
|
|
25
|
+
is stored in a user-only file and passed to the browser in the URL fragment:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
http://127.0.0.1:41820/#token=...
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Fragments are not sent in the initial HTTP request or written to server access
|
|
32
|
+
logs. Treat the complete link as a credential and do not open it on a shared
|
|
33
|
+
device.
|
|
34
|
+
|
|
35
|
+
## Session and transport authority
|
|
36
|
+
|
|
37
|
+
- tmux remains the session authority.
|
|
38
|
+
- OpenSSH remains the network and identity authority.
|
|
39
|
+
- NexusCrew supervises SSH but does not create keys or edit
|
|
40
|
+
`authorized_keys`.
|
|
41
|
+
- Node and deck identities remain owner-qualified.
|
|
42
|
+
- Routed HTTP and WebSocket requests recheck ACL, hop count and cycle rules.
|
|
43
|
+
|
|
44
|
+
### Reverse-port recovery
|
|
45
|
+
|
|
46
|
+
For a shared peer, a reverse-port pool is a fixed SSH policy boundary, not a
|
|
47
|
+
permission NexusCrew can expand. The application never modifies
|
|
48
|
+
`authorized_keys`; a hub operator explicitly grants the three loopback
|
|
49
|
+
`permitlisten` entries for a rotatable peer.
|
|
50
|
+
|
|
51
|
+
Before accepting a pool slot, the hub sends a fresh HMAC challenge to the
|
|
52
|
+
specific loopback listener and verifies its instance, generation and port. The
|
|
53
|
+
probe never sends the peer bearer credential to an unknown listener. A failed
|
|
54
|
+
or unknown listener is quarantined for diagnosis: NexusCrew does not kill SSH
|
|
55
|
+
processes or release ports it cannot prove it owns. Removed pools remain
|
|
56
|
+
retired, preventing an old SSH key from binding a port reassigned to another
|
|
57
|
+
peer.
|
|
58
|
+
|
|
59
|
+
## Provider credentials
|
|
60
|
+
|
|
61
|
+
Provider keys are resolved only on the node launching the process. NexusCrew
|
|
62
|
+
can use its service environment, compatible user-owned provider files or an
|
|
63
|
+
optional node-local write-only store.
|
|
64
|
+
|
|
65
|
+
Credential values are excluded from:
|
|
66
|
+
|
|
67
|
+
- Fleet cell and engine definitions
|
|
68
|
+
- backups
|
|
69
|
+
- API and status responses
|
|
70
|
+
- tmux state
|
|
71
|
+
- process arguments
|
|
72
|
+
- temporary files
|
|
73
|
+
- diagnostics and logs
|
|
74
|
+
|
|
75
|
+
The PWA reports only whether a required variable is configured. Replacement
|
|
76
|
+
values are transient in the browser and are written only to the selected
|
|
77
|
+
node's credential store.
|
|
78
|
+
|
|
79
|
+
## Files
|
|
80
|
+
|
|
81
|
+
Per-session file exchange is scoped under `~/NexusFiles/<session>`. Upload and
|
|
82
|
+
download operations reject traversal and symlink escapes.
|
|
83
|
+
|
|
84
|
+
Clipboard images and dropped files are stored in the selected session inbox;
|
|
85
|
+
their path is inserted into the terminal without automatically pressing Enter.
|
|
86
|
+
|
|
87
|
+
## Pairing and sharing
|
|
88
|
+
|
|
89
|
+
**Pairing grants owner-equivalent authority. Pair only devices you own.**
|
|
90
|
+
|
|
91
|
+
A paired node can create tmux sessions — including a shell — attach to them over
|
|
92
|
+
the federated WebSocket and write into them as the user running NexusCrew. It
|
|
93
|
+
can define engines and cells, and a custom engine may name an existing
|
|
94
|
+
executable with arguments of its choosing: the trust gate checks the binary's
|
|
95
|
+
ownership, mode and path, not the intent of its arguments.
|
|
96
|
+
|
|
97
|
+
Admission is the exception. Minting a pairing invite is not federated: an invite
|
|
98
|
+
belongs to the installation that will host the new node, so it is issued there,
|
|
99
|
+
locally. A paired node cannot admit a third party on your behalf.
|
|
100
|
+
|
|
101
|
+
This is a deliberate boundary, not a defect list. NexusCrew was designed to put
|
|
102
|
+
one person's machines on one control plane, and it treats a paired node the way
|
|
103
|
+
it treats you.
|
|
104
|
+
|
|
105
|
+
### Cell scope
|
|
106
|
+
|
|
107
|
+
A per-node cell scope narrows what one paired node sees and can act on. It is
|
|
108
|
+
set locally, on the installation that owns the cells:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
nexuscrew nodes cells <node> all # default: every cell
|
|
112
|
+
nexuscrew nodes cells <node> none # no cell at all
|
|
113
|
+
nexuscrew nodes cells <node> Research,Dev # exactly these
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The scope is enforced on the federated request, in one place in front of the
|
|
117
|
+
API router, so a listing and an action pass the same predicate. Route targets
|
|
118
|
+
are declared in an explicit table rather than inferred, and a federated request
|
|
119
|
+
that names a cell or a session on a route missing from that table is refused
|
|
120
|
+
rather than allowed — a route added later starts closed, not open. Defining,
|
|
121
|
+
editing or importing a cell is refused outright for a scoped node: it must not
|
|
122
|
+
be able to create the cell it was not granted. The terminal attach is gated
|
|
123
|
+
with the same predicate, and a tmux session that maps to no cell is outside
|
|
124
|
+
every scope.
|
|
125
|
+
|
|
126
|
+
What the scope does **not** change: the pairing itself is still
|
|
127
|
+
owner-equivalent for everything it governs, and the granted cells are granted
|
|
128
|
+
fully — inside its scope a node still creates sessions, attaches to them and
|
|
129
|
+
writes into them as the user running NexusCrew. It attenuates reach, not the
|
|
130
|
+
authority held within that reach. A capability model with proof of possession —
|
|
131
|
+
authority granted, attenuated and revoked as a token rather than as
|
|
132
|
+
configuration — remains on the roadmap and is not implemented.
|
|
133
|
+
|
|
134
|
+
So: do not accept a pairing invite from an installation you do not own, and do
|
|
135
|
+
not treat a cell scope as a sandbox for code you do not trust. It is a
|
|
136
|
+
reduction of surface between machines that are already yours.
|
|
137
|
+
|
|
138
|
+
Pairing links contain a short-lived one-time invite and routing data, but no
|
|
139
|
+
SSH private key, provider key or PWA token.
|
|
140
|
+
|
|
141
|
+
Newly paired nodes are private by default. Sharing is explicit desired state
|
|
142
|
+
and uses a verified reverse channel in the supervised SSH process. Revocation
|
|
143
|
+
is saved locally before the hub is asked to withdraw the node; the UI does not
|
|
144
|
+
claim remote removal until acknowledgement.
|
|
145
|
+
|
|
146
|
+
## Diagnostics and speech
|
|
147
|
+
|
|
148
|
+
Diagnostics accept structured, bounded metadata and reject raw terminal
|
|
149
|
+
content, prompts, command lines, environment values, credentials and private
|
|
150
|
+
paths.
|
|
151
|
+
|
|
152
|
+
Optional spoken notifications use the browser's device-local speech engine.
|
|
153
|
+
Credential-shaped values and private home paths are redacted before speech;
|
|
154
|
+
notification text is not sent to a speech service.
|
|
155
|
+
|
|
156
|
+
Audio Share is a separate backend path for a node with a real speaker. Its
|
|
157
|
+
local consent defaults to off and is independent of node Share and visibility;
|
|
158
|
+
neither routing control grants permission to make sound. Calls use an
|
|
159
|
+
HMAC-proven active Fleet origin, exact node IDs, target-side ACL and rate
|
|
160
|
+
limits. Audio groups are local delivery preferences, never an authorization.
|
|
161
|
+
See [Audio Share and native TTS](AUDIO_SHARE.md).
|
|
162
|
+
|
|
163
|
+
## Updates and process safety
|
|
164
|
+
|
|
165
|
+
Stable updates verify the new CLI and same-port runtime, and roll back once to
|
|
166
|
+
the exact previous version on failure. tmux sessions remain outside the
|
|
167
|
+
service process group.
|
|
168
|
+
|
|
169
|
+
Termux process handling verifies process identity before sending a signal.
|
|
170
|
+
Android PID reuse under another app UID is treated as a stale pidfile, never as
|
|
171
|
+
permission to signal the foreign process.
|
|
172
|
+
|
|
173
|
+
## Report a security issue
|
|
174
|
+
|
|
175
|
+
Do not include live tokens, credentials, private keys or complete authenticated
|
|
176
|
+
links in a public issue. Use the repository's private security-reporting
|
|
177
|
+
channel when available.
|
|
178
|
+
|
|
179
|
+
## Related guides
|
|
180
|
+
|
|
181
|
+
- [Connect nodes](NODES.md)
|
|
182
|
+
- [Configuration](CONFIGURATION.md)
|
|
183
|
+
- [Notifications](NOTIFICATIONS.md)
|
|
184
|
+
- [Audio Share and native TTS](AUDIO_SHARE.md)
|
|
185
|
+
- [Operations](OPERATIONS.md)
|