summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-17 22:30:33 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-17 22:30:33 +0200
commit804b31cc0c8415ebc79ccddf534dd09c5e6484a0 (patch)
tree4e5222126e358d0ea34a63b2fab45102e8c03c24
parent91f2af4f5ce2f295c5d0a8bfd22b8c3bcc5f6966 (diff)
downloadbokf-3e87afa8ba2aaf679c781e55d43424fd9dbd5b38.tar.gz
bokf-3e87afa8ba2aaf679c781e55d43424fd9dbd5b38.zip
bokftui: remember server/user, BOKFD_TOKEN auto-login, sudo credential launcherv0.1.4
-rw-r--r--clients/bokftui.c162
-rw-r--r--docs/DEPLOY.md25
-rwxr-xr-xscripts/bokftui-sudo21
3 files changed, 189 insertions, 19 deletions
diff --git a/clients/bokftui.c b/clients/bokftui.c
index c26d188..16c9941 100644
--- a/clients/bokftui.c
+++ b/clients/bokftui.c
@@ -3837,13 +3837,116 @@ static void dashboard(struct app *a)
/* login */
/* ------------------------------------------------------------------ */
+/* The login screen remembers server and user (never the password) in
+ $XDG_CONFIG_HOME/bokf/tui.conf, defaulting to ~/.config/bokf/tui.conf. */
+
+static void config_path(char *buf, size_t n)
+{
+ const char *xdg = getenv("XDG_CONFIG_HOME");
+ if (xdg && *xdg) {
+ snprintf(buf, n, "%s/bokf/tui.conf", xdg);
+ return;
+ }
+ const char *home = getenv("HOME");
+ snprintf(buf, n, "%s/.config/bokf/tui.conf",
+ home && *home ? home : ".");
+}
+
+static void config_mkdirs(const char *path)
+{
+ char tmp[512];
+ snprintf(tmp, sizeof tmp, "%s", path);
+ if (strlen(tmp) >= sizeof tmp)
+ return;
+ for (char *p = tmp + 1; *p; p++) {
+ if (*p != '/')
+ continue;
+ *p = '\0';
+ mkdir(tmp, 0700);
+ *p = '/';
+ }
+}
+
+static void config_load(struct app *a)
+{
+ char path[512];
+ config_path(path, sizeof path);
+ FILE *f = fopen(path, "r");
+ if (!f)
+ return;
+ char line[512];
+ while (fgets(line, sizeof line, f)) {
+ char *nl = strchr(line, '\n');
+ if (nl)
+ *nl = '\0';
+ char *eq = strchr(line, '=');
+ if (!eq)
+ continue;
+ *eq = '\0';
+ const char *val = util_str_trim(eq + 1);
+ if (strcmp(line, "server") == 0 && !a->socket[0])
+ snprintf(a->socket, sizeof a->socket, "%s", val);
+ else if (strcmp(line, "user") == 0 && !a->username[0])
+ snprintf(a->username, sizeof a->username, "%s", val);
+ }
+ fclose(f);
+}
+
+static void config_save(const struct app *a)
+{
+ char path[512];
+ config_path(path, sizeof path);
+ config_mkdirs(path);
+ FILE *f = fopen(path, "w");
+ if (!f)
+ return;
+ fprintf(f, "server=%s\nuser=%s\n", a->socket, a->username);
+ fclose(f);
+ chmod(path, 0600);
+}
+
+/* Connects and opens a session. Returns 0 on success, -2 when the
+ connection failed and -1 when the server rejected the login. */
+static int try_login(struct app *a, const char *user, const char *pass,
+ const char *token, char **err_out)
+{
+ *err_out = NULL;
+ if (client_connect(a->socket, &a->conn) != 0) {
+ *err_out = xstrdup(client_last_error());
+ return -2;
+ }
+ char *session = NULL;
+ int rc;
+ if (token && *token)
+ rc = client_token_login(&a->conn, token, &session, err_out);
+ else
+ rc = client_login(&a->conn, user, pass, &session, err_out);
+ if (rc != 0) {
+ client_close(&a->conn);
+ return -1;
+ }
+ snprintf(a->session, sizeof a->session, "%s", session);
+ if (user && *user)
+ snprintf(a->username, sizeof a->username, "%s", user);
+ free(session);
+ if (token && *token) {
+ char *resp =
+ client_rpc(&a->conn, "session.whoami", a->session, 0, NULL);
+ char *u = jstr_dup(resp, "result.user.username");
+ if (u && *u)
+ snprintf(a->username, sizeof a->username, "%s", u);
+ free(u);
+ free(resp);
+ }
+ return 0;
+}
+
static int login_screen(struct app *a)
{
char socket_path[256], user[64], pass[128];
snprintf(socket_path, sizeof socket_path, "%s", a->socket);
- const char *env_user = getenv("BOKFD_USER");
const char *env_pass = getenv("BOKFD_PASSWORD");
- snprintf(user, sizeof user, "%s", env_user ? env_user : "");
+ snprintf(user, sizeof user, "%s", a->username);
snprintf(pass, sizeof pass, "%s", env_pass ? env_pass : "");
int field = 3; /* 0 server, 1 user, 2 password, 3 = Logga in button */
@@ -3936,13 +4039,15 @@ static int login_screen(struct app *a)
/* attempt login */
snprintf(a->socket, sizeof a->socket, "%s", socket_path);
- if (client_connect(a->socket, &a->conn) != 0) {
+ char *err = NULL;
+ int rc = try_login(a, user, pass, NULL, &err);
+ if (rc == -2) {
message("Fel", "Kunde inte ansluta till %s: %s", a->socket,
- client_last_error());
+ err ? err : "okänt fel");
+ free(err);
continue;
}
- char *err = NULL, *session = NULL;
- if (client_login(&a->conn, user, pass, &session, &err) != 0) {
+ if (rc != 0) {
if (err && err[0] == '{') {
char *code = jstr_dup(err, "error.code");
char *msg = jstr_dup(err, "error.message");
@@ -3955,12 +4060,9 @@ static int login_screen(struct app *a)
err ? err : "transportfel");
}
free(err);
- client_close(&a->conn);
continue;
}
- snprintf(a->session, sizeof a->session, "%s", session);
- snprintf(a->username, sizeof a->username, "%s", user);
- free(session);
+ config_save(a);
return 0;
}
}
@@ -4025,16 +4127,14 @@ int main(int argc, char **argv)
struct app app;
memset(&app, 0, sizeof app);
app.conn.fd = -1;
- const char *socket = getenv("BOKFD_SOCKET");
- if (!socket)
- socket = "/run/bokfd/bokfd.sock";
+ const char *cli_socket = NULL, *cli_user = NULL;
int64_t want_org = 0;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--socket") == 0 && i + 1 < argc)
- socket = argv[++i];
- else if (strcmp(argv[i], "--user") == 0 && i + 1 < argc) {
- (void)argv[++i];
- } else if (strcmp(argv[i], "--org") == 0 && i + 1 < argc) {
+ cli_socket = argv[++i];
+ else if (strcmp(argv[i], "--user") == 0 && i + 1 < argc)
+ cli_user = argv[++i];
+ else if (strcmp(argv[i], "--org") == 0 && i + 1 < argc) {
want_org = strtoll(argv[++i], NULL, 10);
} else if (strcmp(argv[i], "--version") == 0) {
printf("bokftui %s\n", BOKF_VERSION);
@@ -4047,7 +4147,20 @@ int main(int argc, char **argv)
return 2;
}
}
- snprintf(app.socket, sizeof app.socket, "%s", socket);
+ const char *env_socket = getenv("BOKFD_SOCKET");
+ const char *env_user = getenv("BOKFD_USER");
+ if (cli_socket)
+ snprintf(app.socket, sizeof app.socket, "%s", cli_socket);
+ else if (env_socket)
+ snprintf(app.socket, sizeof app.socket, "%s", env_socket);
+ if (cli_user)
+ snprintf(app.username, sizeof app.username, "%s", cli_user);
+ else if (env_user)
+ snprintf(app.username, sizeof app.username, "%s", env_user);
+ config_load(&app);
+ if (!app.socket[0])
+ snprintf(app.socket, sizeof app.socket, "%s",
+ "/run/bokfd/bokfd.sock");
setlocale(LC_ALL, "");
initscr();
@@ -4057,7 +4170,18 @@ int main(int argc, char **argv)
keypad(stdscr, TRUE);
curs_set(1);
- if (login_screen(&app) != 0) {
+ const char *token = getenv("BOKFD_TOKEN");
+ if (token && *token) {
+ char *err = NULL;
+ if (try_login(&app, NULL, NULL, token, &err) == 0) {
+ config_save(&app);
+ } else {
+ message("Token", "Token-inloggning misslyckades: %s",
+ err ? err : "okänt fel");
+ free(err);
+ }
+ }
+ if (!app.session[0] && login_screen(&app) != 0) {
endwin();
return 0;
}
diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md
index 74f3953..5281fb6 100644
--- a/docs/DEPLOY.md
+++ b/docs/DEPLOY.md
@@ -116,6 +116,31 @@ The clients honor `BOKFD_SOCKET`; a host-installed client can also point at
`var/run/bokfd.sock`, but that file is owned by uid 10001, so the host user
must be in that group (or use `sudo`).
+The TUI remembers the server and user (never the password) in
+`$XDG_CONFIG_HOME/bokf/tui.conf` (`~/.config/bokf/tui.conf`), written after
+a successful login. Precedence: `--socket`/`--user`, then `BOKFD_SOCKET`/
+`BOKFD_USER`, then that file.
+
+For a credential read from a root-only file, unlocked with `sudo`, create an
+admin-scoped token on the host and store it on the client machine:
+
+```sh
+docker compose exec -e BOKFD_PASSWORD='<pw>' bokfd \
+ bokfctl --user admin --org 1 token.create \
+ '{"label":"tui-laptop","scopes":["read","write","admin"]}'
+
+# on the client machine:
+sudo install -d -m 700 /etc/bokf
+sudo sh -c 'umask 077; printf "%s\n" "bokf_..." > /etc/bokf/tui-token'
+install -m 755 scripts/bokftui-sudo ~/bin/bokftui-sudo
+bokftui-sudo
+```
+
+`bokftui-sudo` runs `sudo -v`, reads `/etc/bokf/tui-token` (falling back to
+`/etc/bokf/tui-password`), exports the credential and execs `bokftui` as
+your user; with `BOKFD_TOKEN` set the TUI logs in without showing the login
+screen. Revoke the token with `token.revoke` when a machine goes away.
+
## TLS and external users
Put the ACME DNS credentials in the host's `.env` once (they are never
diff --git a/scripts/bokftui-sudo b/scripts/bokftui-sudo
new file mode 100755
index 0000000..fc300ae
--- /dev/null
+++ b/scripts/bokftui-sudo
@@ -0,0 +1,21 @@
+#!/bin/sh
+# Start bokftui with a credential from a root-only file, unlocked with sudo.
+# Looks for /etc/bokf/tui-token (preferred) then /etc/bokf/tui-password.
+# The server and user are remembered by bokftui itself in
+# ~/.config/bokf/tui.conf.
+set -eu
+
+sudo -v
+
+if token=$(sudo cat /etc/bokf/tui-token 2>/dev/null) && [ -n "$token" ]; then
+ BOKFD_TOKEN="$token"
+ export BOKFD_TOKEN
+elif pass=$(sudo cat /etc/bokf/tui-password 2>/dev/null) && [ -n "$pass" ]; then
+ BOKFD_PASSWORD="$pass"
+ export BOKFD_PASSWORD
+else
+ echo "bokftui-sudo: no /etc/bokf/tui-token or /etc/bokf/tui-password" >&2
+ exit 1
+fi
+
+exec bokftui "$@"