7.3 KiB
Plan: Interactive multi-transport selection at startup
Status: design / ready for review.
Related:
README.md§7 Transport, §8.3 Qubes OS qube, §9 Usagesrc/main.c— argument parsing, main loop, TUIsrc/server.c—server_ctx_t,server_start,server_handle_oneplans/qrexec_persistent_bridge.md— bridge design
1. Motivation
The current CLI has many transport flags (--listen, --socket-name, --bridge-source-trusted, --auth, --allow-all) that the user must know in advance. For interactive use, this is unfriendly — the user just wants to answer "how should other programs reach this signer?"
Additionally, the user may want multiple transports active simultaneously (e.g. unix socket for local clients + TCP for FIPS mesh clients + qrexec bridge for other qubes). Today only one --listen mode is supported at a time.
2. Design
2.1 Interactive transport menu at startup
After mnemonic entry and before the running phase, if no --listen flag was given (i.e. interactive mode), present a multi-select menu:
Transport — how should other programs reach this signer?
Select one or more (space to toggle, enter to confirm):
[x] 1. Local Unix socket (same machine/qube)
[ ] 2. Qubes qrexec bridge (other qubes via qrexec, no network)
[ ] 3. TCP listener (FIPS mesh or local network)
[ ] 4. Qrexec one-shot (legacy, one request per invocation)
[a] select all
(at least one must be selected)
Each selected transport gets configured with sensible defaults:
| Choice | What it starts | Defaults |
|---|---|---|
| 1. Unix socket | --listen unix |
socket name nsigner (or random if collision) |
| 2. Qrexec bridge | --listen unix --bridge-source-trusted |
socket name nsigner, accepts qrexec preamble |
| 3. TCP | --listen tcp:[::]:8080 |
bind all interfaces, port 8080 |
| 4. Qrexec one-shot | --listen qrexec |
one request via stdin, then exit |
Choices 1 and 2 can coexist — they're both unix listeners, just with different identity handling. Choice 2 implies choice 1's socket. If both are selected, the bridge-source-trusted flag is set on the single unix listener (it handles both local and bridge connections).
Choice 3 (TCP) can coexist with 1+2 — it's a separate listener on a different fd.
Choice 4 (qrexec one-shot) is mutually exclusive with all others — it uses stdin/stdout and exits after one request. If selected alone, it runs the one-shot path. If selected with others, it's ignored with a warning (or: the user is told it can't combine).
2.2 Multi-listener architecture
Currently main() creates one server_ctx_t and polls server.listen_fd + STDIN_FILENO. To support multiple listeners:
- Create an array of
server_ctx_t(up to 3: unix, tcp, and optionally a second unix for bridge — though 1+2 collapse into one). - Each
server_ctx_tgets its ownserver_start(). - The poll loop expands to poll all listener fds + STDIN_FILENO.
- When a listener fd has activity, call
server_handle_one()on that context. - The TUI status display shows all active transports.
Connections
listen: unix @nsigner (bridge-source-trusted)
listen: tcp [::]:8080
client: nsigner --socket-name nsigner client '<json>'
2.3 Coexistence with CLI flags
- If
--listenis given on the command line, skip the interactive menu (automation/scripting path unchanged). - If no
--listenis given and stdin is a TTY, show the interactive menu. - If no
--listenis given and stdin is NOT a TTY, default to unix socket (current behavior, for--mnemonic-stdin/--mnemonic-fdsupervised launches).
This preserves backward compatibility: all existing flags and scripts work unchanged. The menu is purely an interactive convenience.
2.4 TUI implementation
The menu uses the existing TUI rendering helpers (tui_render_content_screen, tui_print). It appears after mnemonic acceptance and before the status display. Navigation:
1-4or space: toggle a choicea: select all (except 4, which is mutually exclusive)a: select all (except 4, which is mutually exclusive)enter: confirm and proceed to running phase (at least one must be selected)
After confirmation, the selected listeners are started and the status display renders.
2.5 Security considerations
- The menu is purely a UX layer — it doesn't change the security model. Each transport still goes through the same policy/approval pipeline.
--bridge-source-trustedis only set if the user explicitly selects the qrexec bridge option. It's never silently enabled.- TCP listener still requires auth envelopes (existing behavior).
- The menu doesn't offer
--allow-all; that remains a CLI flag for users who want it.
3. Implementation checklist
3.1 Multi-listener support in main.c
- Replace single
server_ctx_t serverwith an array (e.g.server_ctx_t servers[3]). - Add a
server_countvariable. - Expand the poll loop to poll all
servers[i].listen_fd+ STDIN_FILENO. - On activity, call
server_handle_one(&servers[i], ...)for the matching fd. server_stop()all on shutdown.
3.2 Interactive menu
- Add
prompt_transport_selection()function inmain.c(after mnemonic load, before server start). - Returns a bitmask of selected transports.
- Only called when
listen_modewas not set by CLI and stdin is a TTY. - Uses
tui_render_content_screen+read_line_stdinfor input.
3.3 Transport setup from menu selection
- Unix:
server_init+server_startwithNSIGNER_LISTEN_UNIX, socket namensigner. - Qrexec bridge: same as unix +
server_set_bridge_source_trusted(&server, 1). - TCP:
server_init+server_startwithNSIGNER_LISTEN_TCP, targettcp:[::]:8080. - Qrexec one-shot: existing
NSIGNER_LISTEN_QREXECpath (stdin, one request, exit).
3.4 TUI status display
- Update
render_status()to show multiple active listeners. - Show
(bridge-source-trusted)annotation on unix listeners that have it.
3.5 Testing
- Interactive: start nsigner with no
--listen, select unix+TCP, verify both listeners work. - Interactive: select qrexec bridge, verify bridge connections get
qubes:<vm>identity. - CLI:
--listen tcp:[::]:8080still works (skips menu). - CLI:
--listen unix --bridge-source-trustedstill works (skips menu). - Non-TTY:
--mnemonic-stdinwithout--listendefaults to unix (no menu).
4. Open questions
-
Should the menu remember the last selection? n_signer has no config files (by design). We could store the preference in an env var or a
/tmpfile, but that violates the zero-filesystem-footprint principle. Decision: no persistence — the menu appears fresh each time. -
Should there be a hotkey to add/remove transports at runtime? E.g. press
tin the TUI to bring up the transport menu again. This is a nice-to-have but adds complexity. Decision: defer — the menu is startup-only for now. -
TCP port selection in the menu? The menu could ask for a port number if TCP is selected. Decision: default to 8080, let the user override with
--listen tcp:...if they need a different port. Keep the menu simple. -
Socket name selection? Same — default to
nsigner, override with--socket-nameif needed. Decision: default, no prompt.