diff options
| -rw-r--r-- | clients/bokftui.c | 162 | ||||
| -rw-r--r-- | docs/DEPLOY.md | 25 | ||||
| -rwxr-xr-x | scripts/bokftui-sudo | 21 |
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 "$@" |
