v0.0.37 - Add interactive multi-transport selection menu at startup — users can select one or more transports (unix, qrexec bridge, TCP) from a menu instead of memorizing CLI flags
This commit is contained in:
143
plans/interactive_transport_selection.md
Normal file
143
plans/interactive_transport_selection.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Plan: Interactive multi-transport selection at startup
|
||||
|
||||
Status: design / ready for review.
|
||||
|
||||
Related:
|
||||
- [`README.md`](../README.md) §7 Transport, §8.3 Qubes OS qube, §9 Usage
|
||||
- [`src/main.c`](../src/main.c) — argument parsing, main loop, TUI
|
||||
- [`src/server.c`](../src/server.c) — `server_ctx_t`, `server_start`, `server_handle_one`
|
||||
- [`plans/qrexec_persistent_bridge.md`](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:
|
||||
|
||||
```text
|
||||
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_t` gets its own `server_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.
|
||||
|
||||
```text
|
||||
Connections
|
||||
listen: unix @nsigner (bridge-source-trusted)
|
||||
listen: tcp [::]:8080
|
||||
client: nsigner --socket-name nsigner client '<json>'
|
||||
```
|
||||
|
||||
### 2.3 Coexistence with CLI flags
|
||||
|
||||
- If `--listen` is given on the command line, **skip the interactive menu** (automation/scripting path unchanged).
|
||||
- If no `--listen` is given and stdin is a TTY, **show the interactive menu**.
|
||||
- If no `--listen` is given and stdin is NOT a TTY, **default to unix socket** (current behavior, for `--mnemonic-stdin` / `--mnemonic-fd` supervised 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`-`4` or space: toggle a choice
|
||||
- `a`: 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-trusted` is 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 server` with an array (e.g. `server_ctx_t servers[3]`).
|
||||
- [ ] Add a `server_count` variable.
|
||||
- [ ] 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 in `main.c` (after mnemonic load, before server start).
|
||||
- [ ] Returns a bitmask of selected transports.
|
||||
- [ ] Only called when `listen_mode` was not set by CLI and stdin is a TTY.
|
||||
- [ ] Uses `tui_render_content_screen` + `read_line_stdin` for input.
|
||||
|
||||
### 3.3 Transport setup from menu selection
|
||||
- [ ] Unix: `server_init` + `server_start` with `NSIGNER_LISTEN_UNIX`, socket name `nsigner`.
|
||||
- [ ] Qrexec bridge: same as unix + `server_set_bridge_source_trusted(&server, 1)`.
|
||||
- [ ] TCP: `server_init` + `server_start` with `NSIGNER_LISTEN_TCP`, target `tcp:[::]:8080`.
|
||||
- [ ] Qrexec one-shot: existing `NSIGNER_LISTEN_QREXEC` path (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:[::]:8080` still works (skips menu).
|
||||
- [ ] CLI: `--listen unix --bridge-source-trusted` still works (skips menu).
|
||||
- [ ] Non-TTY: `--mnemonic-stdin` without `--listen` defaults to unix (no menu).
|
||||
|
||||
---
|
||||
|
||||
## 4. Open questions
|
||||
|
||||
1. **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 `/tmp` file, but that violates the zero-filesystem-footprint principle. **Decision: no persistence — the menu appears fresh each time.**
|
||||
|
||||
2. **Should there be a hotkey to add/remove transports at runtime?** E.g. press `t` in 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.**
|
||||
|
||||
3. **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.**
|
||||
|
||||
4. **Socket name selection?** Same — default to `nsigner`, override with `--socket-name` if needed. **Decision: default, no prompt.**
|
||||
252
src/main.c
252
src/main.c
@@ -494,8 +494,8 @@ int socket_name_random(char *out, size_t out_len);
|
||||
/* Version information (auto-updated by build/version tooling) */
|
||||
#define NSIGNER_VERSION_MAJOR 0
|
||||
#define NSIGNER_VERSION_MINOR 0
|
||||
#define NSIGNER_VERSION_PATCH 36
|
||||
#define NSIGNER_VERSION "v0.0.36"
|
||||
#define NSIGNER_VERSION_PATCH 37
|
||||
#define NSIGNER_VERSION "v0.0.37"
|
||||
|
||||
|
||||
/* NSIGNER_HEADERLESS_DECLS_END */
|
||||
@@ -1591,6 +1591,88 @@ static void apply_test_overrides(policy_table_t *policy) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Transport selection bitmask for interactive menu */
|
||||
#define TRANSPORT_UNIX 0x01
|
||||
#define TRANSPORT_QREXEC_BRIDGE 0x02
|
||||
#define TRANSPORT_TCP 0x04
|
||||
#define TRANSPORT_QREXEC_ONESHOT 0x08
|
||||
|
||||
/*
|
||||
* Interactive transport selection menu.
|
||||
* Shown after mnemonic entry when no --listen flag was given and stdin is a TTY.
|
||||
* Returns a bitmask of selected transports, or 0 on error/no selection.
|
||||
*/
|
||||
static int prompt_transport_selection(void) {
|
||||
int selected = TRANSPORT_UNIX; /* default: local unix socket */
|
||||
char input[32];
|
||||
|
||||
for (;;) {
|
||||
tui_render_content_screen(NULL, "Transport — how should other programs reach this signer?");
|
||||
printf("Select one or more (type a number to toggle, 'a' for all, Enter to confirm):\n\n");
|
||||
printf(" [%s] 1. Local Unix socket (same machine/qube)\n",
|
||||
(selected & TRANSPORT_UNIX) ? "x" : " ");
|
||||
printf(" [%s] 2. Qubes qrexec bridge (other qubes via qrexec, no network)\n",
|
||||
(selected & TRANSPORT_QREXEC_BRIDGE) ? "x" : " ");
|
||||
printf(" [%s] 3. TCP listener (FIPS mesh or local network)\n",
|
||||
(selected & TRANSPORT_TCP) ? "x" : " ");
|
||||
printf(" [%s] 4. Qrexec one-shot (legacy, one request per invocation)\n",
|
||||
(selected & TRANSPORT_QREXEC_ONESHOT) ? "x" : " ");
|
||||
printf("\n [a] select all (except 4) Enter = confirm\n");
|
||||
printf("> ");
|
||||
fflush(stdout);
|
||||
|
||||
if (read_line_stdin(input, sizeof(input)) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Trim trailing whitespace */
|
||||
{
|
||||
size_t len = strlen(input);
|
||||
while (len > 0 && (input[len-1] == '\n' || input[len-1] == '\r' ||
|
||||
input[len-1] == ' ' || input[len-1] == '\t')) {
|
||||
input[--len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
if (input[0] == '\0') {
|
||||
/* Enter pressed — confirm current selection */
|
||||
if (selected == 0) {
|
||||
printf("At least one transport must be selected.\n");
|
||||
continue;
|
||||
}
|
||||
/* Qrexec one-shot is mutually exclusive with persistent listeners */
|
||||
if ((selected & TRANSPORT_QREXEC_ONESHOT) && (selected & ~TRANSPORT_QREXEC_ONESHOT)) {
|
||||
printf("Qrexec one-shot cannot combine with other transports. Deselecting it.\n");
|
||||
selected &= ~TRANSPORT_QREXEC_ONESHOT;
|
||||
if (selected == 0) {
|
||||
selected = TRANSPORT_UNIX;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (input[0] == 'a' || input[0] == 'A') {
|
||||
selected = TRANSPORT_UNIX | TRANSPORT_QREXEC_BRIDGE | TRANSPORT_TCP;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input[0] == '1') {
|
||||
selected ^= TRANSPORT_UNIX;
|
||||
} else if (input[0] == '2') {
|
||||
selected ^= TRANSPORT_QREXEC_BRIDGE;
|
||||
} else if (input[0] == '3') {
|
||||
selected ^= TRANSPORT_TCP;
|
||||
} else if (input[0] == '4') {
|
||||
selected ^= TRANSPORT_QREXEC_ONESHOT;
|
||||
} else {
|
||||
printf("Invalid input. Type 1-4, 'a', or Enter to confirm.\n");
|
||||
}
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
mnemonic_state_t mnemonic;
|
||||
role_table_t role_table;
|
||||
@@ -1598,13 +1680,14 @@ int main(int argc, char *argv[]) {
|
||||
policy_table_t policy;
|
||||
server_ctx_t server;
|
||||
key_store_t key_store;
|
||||
struct pollfd pfds[2];
|
||||
struct pollfd pfds[3]; /* max: 2 listeners (unix+tcp) + stdin */
|
||||
uid_t owner_uid;
|
||||
int derived_count;
|
||||
const char *socket_name = NSIGNER_DEFAULT_SOCKET_NAME;
|
||||
char generated_socket_name[SERVER_SOCKET_NAME_MAX];
|
||||
int socket_name_explicit = 0;
|
||||
int listen_mode = NSIGNER_LISTEN_UNIX;
|
||||
int listen_mode_explicit = 0;
|
||||
const char *listen_target = NSIGNER_DEFAULT_SOCKET_NAME;
|
||||
int argi = 1;
|
||||
const char *preapprove_specs[POLICY_MAX_ENTRIES];
|
||||
@@ -1633,6 +1716,7 @@ int main(int argc, char *argv[]) {
|
||||
fprintf(stderr, "Missing value for %s\n", argv[argi]);
|
||||
return 1;
|
||||
}
|
||||
listen_mode_explicit = 1;
|
||||
if (strcmp(argv[argi + 1], "unix") == 0) {
|
||||
listen_mode = NSIGNER_LISTEN_UNIX;
|
||||
} else if (strcmp(argv[argi + 1], "stdio") == 0) {
|
||||
@@ -1853,6 +1937,57 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
dispatcher_init(&dispatcher, &role_table, &mnemonic, &key_store);
|
||||
|
||||
/*
|
||||
* Interactive transport selection: if no --listen flag was given and
|
||||
* stdin is a TTY, show the multi-select transport menu. This lets the
|
||||
* user choose how other programs reach the signer without memorizing
|
||||
* CLI flags. CLI flags skip the menu (backward compatible).
|
||||
*/
|
||||
int transport_mask = 0;
|
||||
int multi_listen = 0; /* set when multiple persistent listeners are active */
|
||||
server_ctx_t servers[2]; /* max: unix + tcp */
|
||||
int server_count = 0;
|
||||
int unix_server_idx = -1;
|
||||
int tcp_server_idx = -1;
|
||||
|
||||
if (!listen_mode_explicit && isatty(STDIN_FILENO) &&
|
||||
mnemonic_source.kind == MNEMONIC_SOURCE_TUI) {
|
||||
transport_mask = prompt_transport_selection();
|
||||
if (transport_mask == 0) {
|
||||
fprintf(stderr, "No transport selected. Exiting.\n");
|
||||
crypto_wipe(&key_store);
|
||||
nostr_cleanup();
|
||||
mnemonic_unload(&mnemonic);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Qrexec one-shot: use existing single-listener path */
|
||||
if (transport_mask & TRANSPORT_QREXEC_ONESHOT) {
|
||||
listen_mode = NSIGNER_LISTEN_QREXEC;
|
||||
listen_mode_explicit = 1;
|
||||
} else {
|
||||
/* Persistent listeners — configure from menu selection */
|
||||
multi_listen = 1;
|
||||
|
||||
/* Unix socket (with or without bridge-source-trusted) */
|
||||
if (transport_mask & (TRANSPORT_UNIX | TRANSPORT_QREXEC_BRIDGE)) {
|
||||
/* Use fixed name "nsigner" for scripted access */
|
||||
socket_name = "nsigner";
|
||||
socket_name_explicit = 1;
|
||||
listen_mode = NSIGNER_LISTEN_UNIX;
|
||||
listen_target = socket_name;
|
||||
if (transport_mask & TRANSPORT_QREXEC_BRIDGE) {
|
||||
bridge_source_trusted = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* TCP listener */
|
||||
if (transport_mask & TRANSPORT_TCP) {
|
||||
/* Will be started as a second listener */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (listen_mode == NSIGNER_LISTEN_UNIX && !socket_name_explicit) {
|
||||
if (socket_name_random(generated_socket_name, sizeof(generated_socket_name)) != 0) {
|
||||
fprintf(stderr, "Failed to generate random socket name\n");
|
||||
@@ -1914,6 +2049,41 @@ int main(int argc, char *argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Multi-listener: if interactive menu selected TCP in addition to unix,
|
||||
* start a second server context for TCP. The primary server (unix) is
|
||||
* already running. Both share the same dispatcher and policy.
|
||||
*/
|
||||
if (multi_listen && (transport_mask & TRANSPORT_TCP)) {
|
||||
const char *tcp_target = "tcp:[::]:8080";
|
||||
tcp_server_idx = server_count;
|
||||
server_init(&servers[tcp_server_idx],
|
||||
tcp_target,
|
||||
0, /* socket_name_explicit = 0 for TCP */
|
||||
NSIGNER_LISTEN_TCP,
|
||||
NSIGNER_AUTH_REQUIRED,
|
||||
auth_skew_seconds,
|
||||
&dispatcher,
|
||||
&policy);
|
||||
if (server_start(&servers[tcp_server_idx]) != 0) {
|
||||
fprintf(stderr, "Failed to start TCP server on %s: %s\n",
|
||||
tcp_target, server_last_error(&servers[tcp_server_idx]));
|
||||
server_stop(&server);
|
||||
crypto_wipe(&key_store);
|
||||
nostr_cleanup();
|
||||
mnemonic_unload(&mnemonic);
|
||||
return 1;
|
||||
}
|
||||
server_count++;
|
||||
}
|
||||
|
||||
/* Copy the primary (unix) server into the array for unified polling */
|
||||
if (multi_listen) {
|
||||
unix_server_idx = server_count;
|
||||
servers[unix_server_idx] = server; /* struct copy */
|
||||
server_count++;
|
||||
}
|
||||
|
||||
(void)signal(SIGINT, handle_signal);
|
||||
(void)signal(SIGTERM, handle_signal);
|
||||
/*
|
||||
@@ -1935,7 +2105,22 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
connection_info_clear();
|
||||
|
||||
if (listen_mode == NSIGNER_LISTEN_UNIX) {
|
||||
if (multi_listen) {
|
||||
/* Multi-listener mode: show all active transports */
|
||||
if (transport_mask & (TRANSPORT_UNIX | TRANSPORT_QREXEC_BRIDGE)) {
|
||||
if (bridge_source_trusted) {
|
||||
connection_info_add("listen: unix @%s (bridge-source-trusted)", socket_name);
|
||||
} else {
|
||||
connection_info_add("listen: unix @%s", socket_name);
|
||||
}
|
||||
connection_info_add("client: nsigner --socket-name %s client '<json>'", socket_name);
|
||||
printf("System is ready and waiting for connections on @%s.\n", socket_name);
|
||||
}
|
||||
if (transport_mask & TRANSPORT_TCP) {
|
||||
connection_info_add("listen: tcp [::]:8080");
|
||||
printf("System is ready and waiting for connections on tcp:[::]:8080.\n");
|
||||
}
|
||||
} else if (listen_mode == NSIGNER_LISTEN_UNIX) {
|
||||
connection_info_add("listen: unix @%s", socket_name);
|
||||
connection_info_add("client: nsigner --socket-name %s client '<json>'", socket_name);
|
||||
printf("System is ready and waiting for connections on @%s.\n", socket_name);
|
||||
@@ -2013,13 +2198,26 @@ int main(int argc, char *argv[]) {
|
||||
tui_install_resize_handler();
|
||||
render_status(&role_table, &mnemonic, derived_count, socket_name);
|
||||
|
||||
pfds[0].fd = server.listen_fd;
|
||||
pfds[0].events = POLLIN;
|
||||
pfds[1].fd = STDIN_FILENO;
|
||||
pfds[1].events = POLLIN;
|
||||
/* Set up poll fds: listener(s) + stdin */
|
||||
int npfds = 0;
|
||||
if (multi_listen) {
|
||||
for (int si = 0; si < server_count; si++) {
|
||||
pfds[npfds].fd = servers[si].listen_fd;
|
||||
pfds[npfds].events = POLLIN;
|
||||
npfds++;
|
||||
}
|
||||
} else {
|
||||
pfds[npfds].fd = server.listen_fd;
|
||||
pfds[npfds].events = POLLIN;
|
||||
npfds++;
|
||||
}
|
||||
pfds[npfds].fd = STDIN_FILENO;
|
||||
pfds[npfds].events = POLLIN;
|
||||
npfds++;
|
||||
int stdin_pfd_idx = npfds - 1;
|
||||
|
||||
while (g_running && server.running) {
|
||||
int prc = poll(pfds, 2, 200);
|
||||
int prc = poll(pfds, npfds, 200);
|
||||
if (prc < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
@@ -2031,15 +2229,31 @@ int main(int argc, char *argv[]) {
|
||||
render_status(&role_table, &mnemonic, derived_count, socket_name);
|
||||
}
|
||||
|
||||
if (prc > 0 && (pfds[0].revents & POLLIN)) {
|
||||
int hrc = server_handle_one(&server, activity_log_cb, NULL);
|
||||
if (hrc < 0) {
|
||||
break;
|
||||
/* Check all listener fds for activity */
|
||||
if (prc > 0) {
|
||||
if (multi_listen) {
|
||||
for (int si = 0; si < server_count; si++) {
|
||||
if (pfds[si].revents & POLLIN) {
|
||||
int hrc = server_handle_one(&servers[si], activity_log_cb, NULL);
|
||||
if (hrc < 0) {
|
||||
g_running = 0;
|
||||
break;
|
||||
}
|
||||
render_status(&role_table, &mnemonic, derived_count, socket_name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (pfds[0].revents & POLLIN) {
|
||||
int hrc = server_handle_one(&server, activity_log_cb, NULL);
|
||||
if (hrc < 0) {
|
||||
break;
|
||||
}
|
||||
render_status(&role_table, &mnemonic, derived_count, socket_name);
|
||||
}
|
||||
}
|
||||
render_status(&role_table, &mnemonic, derived_count, socket_name);
|
||||
}
|
||||
|
||||
if (prc > 0 && (pfds[1].revents & POLLIN)) {
|
||||
if (prc > 0 && (pfds[stdin_pfd_idx].revents & POLLIN)) {
|
||||
char ch = '\0';
|
||||
if (read(STDIN_FILENO, &ch, 1) > 0) {
|
||||
char lower = (char)tolower((unsigned char)ch);
|
||||
@@ -2083,7 +2297,13 @@ int main(int argc, char *argv[]) {
|
||||
server_set_approval_cb(NULL, NULL);
|
||||
}
|
||||
|
||||
server_stop(&server);
|
||||
if (multi_listen) {
|
||||
for (int si = 0; si < server_count; si++) {
|
||||
server_stop(&servers[si]);
|
||||
}
|
||||
} else {
|
||||
server_stop(&server);
|
||||
}
|
||||
crypto_wipe(&key_store);
|
||||
nostr_cleanup();
|
||||
mnemonic_unload(&mnemonic);
|
||||
|
||||
Reference in New Issue
Block a user