v0.0.27 - Refine TUI login flow, fix menu hotkey underlining, and pin connection info header

This commit is contained in:
Laan Tungir
2026-05-08 11:36:26 -04:00
parent 5c214f3614
commit 44372c0108
2 changed files with 247 additions and 15 deletions

33
run.sh Executable file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUILD_SCRIPT="$SCRIPT_DIR/build_static.sh"
BUILD_DIR="$SCRIPT_DIR/build"
HOST_UNAME="$(uname -m)"
case "$HOST_UNAME" in
x86_64)
OUTPUT_NAME="nsigner_static_x86_64"
;;
aarch64|arm64)
OUTPUT_NAME="nsigner_static_arm64"
;;
armv7l|armv7)
OUTPUT_NAME="nsigner_static_armv7"
;;
*)
echo "ERROR: Unsupported host architecture '$HOST_UNAME'"
exit 1
;;
esac
"$BUILD_SCRIPT"
BINARY_PATH="$BUILD_DIR/$OUTPUT_NAME"
if [[ ! -x "$BINARY_PATH" ]]; then
echo "ERROR: Compiled binary not found or not executable: $BINARY_PATH"
exit 1
fi
exec "$BINARY_PATH" "$@"

View File

@@ -491,8 +491,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 26
#define NSIGNER_VERSION "v0.0.26"
#define NSIGNER_VERSION_PATCH 27
#define NSIGNER_VERSION "v0.0.27"
/* NSIGNER_HEADERLESS_DECLS_END */
@@ -509,6 +509,7 @@ int transport_recv_framed(int fd, char **out_payload, size_t max_size);
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -524,6 +525,7 @@ int transport_recv_framed(int fd, char **out_payload, size_t max_size);
#define NSIGNER_DEFAULT_SOCKET_NAME "nsigner"
#define ACTIVITY_LOG_CAP 16
#define CONNECTION_INFO_CAP 8
typedef struct {
char lines[ACTIVITY_LOG_CAP][256];
@@ -546,6 +548,8 @@ typedef struct {
} mnemonic_source_t;
static mnemonic_source_kind_t g_startup_mnemonic_source_kind = MNEMONIC_SOURCE_TUI;
static char g_connection_info[CONNECTION_INFO_CAP][192];
static int g_connection_info_count = 0;
static const TuiMenuItem g_main_menu_items[] = {
{"^_q^:/x quit", 'q'},
@@ -570,6 +574,28 @@ static void render_status(const role_table_t *role_table,
int derived_count,
const char *socket_name);
static void connection_info_clear(void) {
g_connection_info_count = 0;
memset(g_connection_info, 0, sizeof(g_connection_info));
}
static void connection_info_add(const char *fmt, ...) {
va_list ap;
if (fmt == NULL || g_connection_info_count >= CONNECTION_INFO_CAP) {
return;
}
va_start(ap, fmt);
(void)vsnprintf(g_connection_info[g_connection_info_count],
sizeof(g_connection_info[g_connection_info_count]),
fmt,
ap);
va_end(ap);
g_connection_info_count++;
}
static void handle_signal(int sig) {
(void)sig;
g_running = 0;
@@ -596,6 +622,126 @@ static int read_line_stdin(char *buf, size_t buf_sz) {
}
static int read_cmd_output_local(const char *cmd, char **out_buf) {
FILE *fp;
char chunk[512];
char *buf = NULL;
size_t used = 0;
size_t cap = 0;
if (cmd == NULL || out_buf == NULL) {
return -1;
}
*out_buf = NULL;
fp = popen(cmd, "r");
if (fp == NULL) {
return -1;
}
while (fgets(chunk, sizeof(chunk), fp) != NULL) {
size_t n = strlen(chunk);
if (used + n + 1 > cap) {
size_t new_cap = (cap == 0) ? 2048 : cap * 2;
while (new_cap < used + n + 1) {
new_cap *= 2;
}
{
char *tmp = (char *)realloc(buf, new_cap);
if (tmp == NULL) {
free(buf);
(void)pclose(fp);
return -1;
}
buf = tmp;
cap = new_cap;
}
}
memcpy(buf + used, chunk, n);
used += n;
}
(void)pclose(fp);
if (buf == NULL) {
return -1;
}
buf[used] = '\0';
*out_buf = buf;
return 0;
}
static int lookup_local_fips_identity(char *out_ipv6,
size_t out_ipv6_sz,
char *out_npub,
size_t out_npub_sz) {
char *json = NULL;
cJSON *root = NULL;
cJSON *ipv6_item;
cJSON *npub_item;
if (out_ipv6 == NULL || out_ipv6_sz == 0 || out_npub == NULL || out_npub_sz == 0) {
return -1;
}
out_ipv6[0] = '\0';
out_npub[0] = '\0';
if (read_cmd_output_local("fipsctl show status 2>/dev/null", &json) != 0) {
return -1;
}
root = cJSON_Parse(json);
free(json);
if (root == NULL) {
return -1;
}
ipv6_item = cJSON_GetObjectItemCaseSensitive(root, "ipv6_addr");
npub_item = cJSON_GetObjectItemCaseSensitive(root, "npub");
if (cJSON_IsString(ipv6_item) && ipv6_item->valuestring != NULL) {
strncpy(out_ipv6, ipv6_item->valuestring, out_ipv6_sz - 1);
out_ipv6[out_ipv6_sz - 1] = '\0';
}
if (cJSON_IsString(npub_item) && npub_item->valuestring != NULL) {
strncpy(out_npub, npub_item->valuestring, out_npub_sz - 1);
out_npub[out_npub_sz - 1] = '\0';
}
cJSON_Delete(root);
return (out_ipv6[0] != '\0' && out_npub[0] != '\0') ? 0 : -1;
}
static int extract_listen_port(const char *listen_target, char *out_port, size_t out_port_sz) {
const char *port;
size_t i;
if (listen_target == NULL || out_port == NULL || out_port_sz == 0) {
return -1;
}
out_port[0] = '\0';
port = strrchr(listen_target, ':');
if (port == NULL || *(port + 1) == '\0') {
return -1;
}
port++;
for (i = 0; port[i] != '\0'; ++i) {
if (!isdigit((unsigned char)port[i])) {
return -1;
}
}
strncpy(out_port, port, out_port_sz - 1);
out_port[out_port_sz - 1] = '\0';
return 0;
}
static int connect_abstract_socket(const char *name) {
int fd;
struct sockaddr_un addr;
@@ -895,8 +1041,25 @@ static void render_status(const role_table_t *role_table,
const char *socket_name) {
TuiFrame frame = { "n_signer", NSIGNER_VERSION, "> Main Menu" };
TuiMenu menu = { g_main_menu_items, (int)(sizeof(g_main_menu_items) / sizeof(g_main_menu_items[0])) };
TuiSize size = tui_terminal_size();
int left_col = tui_menu_left_col(&frame, size.width);
char status_buf[256];
TuiStatus status;
tui_clear_continuous(size.height);
tui_render_top_frame(&frame, size.width);
tui_print("^*Connections^:");
if (g_connection_info_count == 0) {
tui_print("(none)");
} else {
for (int i = 0; i < g_connection_info_count; ++i) {
tui_print("%s", g_connection_info[i]);
}
}
tui_print("");
tui_render_menu(&menu, left_col);
tui_print("");
(void)snprintf(status_buf,
sizeof(status_buf),
@@ -906,9 +1069,7 @@ static void render_status(const role_table_t *role_table,
(socket_name != NULL) ? socket_name : "(none)",
derived_count,
g_auto_approve ? "ON" : "OFF");
status.text = status_buf;
tui_render_screen(&frame, &menu, &status);
tui_print("%s", status_buf);
tui_print("^*Roles^:");
if (role_table == NULL || role_table->count == 0) {
@@ -946,7 +1107,7 @@ static void render_status(const role_table_t *role_table,
}
}
tui_anchor_prompt(0, tui_menu_left_col(&frame, tui_terminal_size().width));
tui_anchor_prompt(0, left_col);
fflush(stdout);
}
@@ -1085,6 +1246,16 @@ static int prompt_load_mnemonic_tui(mnemonic_state_t *mnemonic) {
idx++;
word = strtok_r(NULL, " ", &ctx);
}
tui_print("");
tui_print("Press Enter after writing down your mnemonic to continue.");
printf("> ");
fflush(stdout);
if (read_line_stdin(mode, sizeof(mode)) != 0) {
fprintf(stderr, "Failed to read continue input\n");
memset(phrase, 0, sizeof(phrase));
memset(phrase_copy, 0, sizeof(phrase_copy));
return -1;
}
if (mnemonic_load(mnemonic, phrase) != 0) {
memset(phrase, 0, sizeof(phrase));
@@ -1603,15 +1774,43 @@ int main(int argc, char *argv[]) {
(void)signal(SIGINT, handle_signal);
(void)signal(SIGTERM, handle_signal);
{
char fips_ipv6[128];
char fips_npub[256];
char listen_port[16];
int have_fips_identity =
(lookup_local_fips_identity(fips_ipv6, sizeof(fips_ipv6), fips_npub, sizeof(fips_npub)) == 0);
connection_info_clear();
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);
} else if (listen_mode == NSIGNER_LISTEN_TCP) {
connection_info_add("listen: %s", listen_target);
printf("System is ready and waiting for connections on %s.\n", listen_target);
} else if (listen_mode == NSIGNER_LISTEN_QREXEC) {
connection_info_add("listen: qrexec");
printf("System is ready and waiting for a qrexec request.\n");
} else {
connection_info_add("listen: stdio");
printf("System is ready and waiting for a stdio request.\n");
}
if (have_fips_identity) {
connection_info_add("fips ipv6: %s", fips_ipv6);
connection_info_add("fips npub: %s", fips_npub);
printf("fips ipv6: %s\n", fips_ipv6);
printf("fips npub: %s\n", fips_npub);
if (listen_mode == NSIGNER_LISTEN_TCP &&
extract_listen_port(listen_target, listen_port, sizeof(listen_port)) == 0) {
connection_info_add("fips address: http://%s.fips:%s", fips_npub, listen_port);
printf("fips address: http://%s.fips:%s\n", fips_npub, listen_port);
}
}
}
fflush(stdout);
if (listen_mode == NSIGNER_LISTEN_STDIO || listen_mode == NSIGNER_LISTEN_QREXEC) {