diff options
| -rw-r--r-- | clients/bokftui.c | 1 | ||||
| -rw-r--r-- | clients/client.c | 3 | ||||
| -rw-r--r-- | clients/screens_bokslut.c | 2 | ||||
| -rw-r--r-- | clients/screens_ib.c | 2 | ||||
| -rw-r--r-- | clients/screens_invoices.c | 2 | ||||
| -rw-r--r-- | clients/screens_payroll.c | 5 | ||||
| -rw-r--r-- | clients/screens_templates.c | 2 | ||||
| -rw-r--r-- | clients/screens_vouchers.c | 2 | ||||
| -rw-r--r-- | clients/tui.c | 35 | ||||
| -rw-r--r-- | docs/STATE.md | 17 | ||||
| -rw-r--r-- | docs/TUI-GUIDELINES.md | 2 | ||||
| -rwxr-xr-x | scripts/tui-golden.py | 60 | ||||
| -rw-r--r-- | src/smtp.c | 3 | ||||
| -rw-r--r-- | src/tax_table.c | 3 | ||||
| -rw-r--r-- | src/tls_ca.h | 31 |
15 files changed, 147 insertions, 23 deletions
diff --git a/clients/bokftui.c b/clients/bokftui.c index 2dc23ba..69d6690 100644 --- a/clients/bokftui.c +++ b/clients/bokftui.c @@ -346,6 +346,7 @@ static void usage(FILE *f) static void reset_modify_keys(void) { + putp("\033[<u"); putp("\033[>4m"); fflush(stdout); } diff --git a/clients/client.c b/clients/client.c index fd119f1..30f2a67 100644 --- a/clients/client.c +++ b/clients/client.c @@ -1,4 +1,5 @@ #include "client.h" +#include "tls_ca.h" #include <arpa/inet.h> #include <errno.h> @@ -126,7 +127,7 @@ static int tls_connect_addr(const char *addrport, struct client_conn *out) } SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); - SSL_CTX_set_default_verify_paths(ctx); + tls_load_default_cas(ctx); const char *ca = getenv("BOKFD_TLS_CA"); if (ca && *ca && SSL_CTX_load_verify_locations(ctx, ca, NULL) != 1) { set_tls_error("TLS CA file"); diff --git a/clients/screens_bokslut.c b/clients/screens_bokslut.c index df47196..3f69982 100644 --- a/clients/screens_bokslut.c +++ b/clients/screens_bokslut.c @@ -242,7 +242,7 @@ void bokslut_screen(struct app *a) ff[7].cap = sizeof rate; ff[7].kind = TUI_F_TEXT; tui_form_hint("upp/ned/Tab = flytta Enter = ändra/utför F5 = visa" - " bokslutsplan ^Enter = bokför planen (frågar" + " bokslutsplan ^Enter/F9 = bokför planen (frågar" " först) Esc/q = tillbaka ^C = avsluta"); int r = tui_form_run_actions("Bokslut", ff, nf + 2, can_edit, acts, 5, NULL, &sel); diff --git a/clients/screens_ib.c b/clients/screens_ib.c index ca35562..8950667 100644 --- a/clients/screens_ib.c +++ b/clients/screens_ib.c @@ -272,7 +272,7 @@ static int ib_form(struct app *a, char **old_acc, int64_t *old_amt, int nold) for (;;) { int rr = tui_rt_run("Ingående balans", &ib.rt, "Tab = byta fält F5 = validera ^X = rensa rad" - " ^Enter = spara Esc = avbryt"); + " ^Enter/F9 = spara Esc = avbryt"); if (rr == -1) return 0; char msg[256]; diff --git a/clients/screens_invoices.c b/clients/screens_invoices.c index b8cc8dd..ca59eef 100644 --- a/clients/screens_invoices.c +++ b/clients/screens_invoices.c @@ -1092,7 +1092,7 @@ static int64_t invoices_new(struct app *a) tui_rt_set_fields(&f.rt, ff, 7); const char *hint = "Enter = välj/ändra Tab = byta fält F5 = förhandsvisa" - " ^Enter = utfärda Esc = avbryt"; + " ^Enter/F9 = utfärda Esc = avbryt"; int64_t out = 0; for (;;) { int rr = tui_rt_run("Ny faktura", &f.rt, hint); diff --git a/clients/screens_payroll.c b/clients/screens_payroll.c index 9bfaf38..23d765f 100644 --- a/clients/screens_payroll.c +++ b/clients/screens_payroll.c @@ -1101,7 +1101,8 @@ static int64_t payroll_run_screen(struct app *a, int64_t id) { status, -1, NULL }, { totals, -1, NULL }, { "Förhandsvisa (F5)", 1, NULL }, - { "Bokför körning (^Enter)", post_reason ? 0 : 1, post_reason }, + { "Bokför körning (^Enter/F9)", post_reason ? 0 : 1, + post_reason }, { "Lönebesked (PDF)", payslip_reason ? 0 : 1, payslip_reason }, { "AGI-underlag", agi_reason ? 0 : 1, agi_reason }, { "Betala skatt & avgifter", pay_reason ? 0 : 1, pay_reason }, @@ -1123,7 +1124,7 @@ static int64_t payroll_run_screen(struct app *a, int64_t id) snprintf(title, sizeof title, "Ny lönekörning"); int editable = can_write && !posted; tui_form_hint("upp/ned/Tab = flytta Enter = ändra/utför" - " F5 = förhandsvisa ^Enter = bokför" + " F5 = förhandsvisa ^Enter/F9 = bokför" " Esc/q = tillbaka ^C = avsluta"); int ret = tui_form_run_actions(title, ff, 2, editable, acts, 7, NULL, &focus); diff --git a/clients/screens_templates.c b/clients/screens_templates.c index b2cd7ab..5777742 100644 --- a/clients/screens_templates.c +++ b/clients/screens_templates.c @@ -234,7 +234,7 @@ static int template_form(struct app *a, const char *load_name) tui_rt_set_fields(&tf.rt, ff, 3); const char *title = load_name ? "Redigera mall" : "Ny mall"; const char *hint = "Enter = ändra fält Tab = byta fält F5 = validera" - " ^X = rensa rad ^Enter = spara Esc = avbryt"; + " ^X = rensa rad ^Enter/F9 = spara Esc = avbryt"; for (;;) { int rr = tui_rt_run(title, &tf.rt, hint); if (rr == -1) diff --git a/clients/screens_vouchers.c b/clients/screens_vouchers.c index fedffe9..5e1b006 100644 --- a/clients/screens_vouchers.c +++ b/clients/screens_vouchers.c @@ -836,7 +836,7 @@ int64_t vouchers_new_prefill(struct app *a, const struct voucher_prefill *p) tui_rt_normalize(&vf.rt); const char *hint = "Enter = ändra fält Tab = byta fält F4 = mall" " ^F = bifoga fil F5 = validera ^X = rensa rad" - " ^Enter = bokför Esc = avbryt"; + " ^Enter/F9 = bokför Esc = avbryt"; for (;;) { int rr = tui_rt_run("Nytt verifikat", &vf.rt, hint); if (rr == -1) diff --git a/clients/tui.c b/clients/tui.c index 031fad0..30761a8 100644 --- a/clients/tui.c +++ b/clients/tui.c @@ -159,9 +159,34 @@ int tui_sticky_split(char **lines, int n, char **head, int *nhead) void tui_keys_setup(void) { - /* ^Enter has no control code; enable xterm modifyOtherKeys and bind the - sequence it produces. Terminals without it ignore the request. */ + /* ^Enter has no control code; enable xterm modifyOtherKeys level 2 (level + 1 leaves control+Enter alone) and the Kitty keyboard protocol, then bind + the sequences they produce. Terminals with neither keep F9 working. */ + putp("\033[>4;2m"); + putp("\033[>1u"); + fflush(stdout); define_key("\033[27;5;13~", TUI_KEY_CTRL_ENTER); + define_key("\033[13;5u", TUI_KEY_CTRL_ENTER); + define_key("\033[27;2;9~", KEY_BTAB); + define_key("\033[9;2u", KEY_BTAB); + define_key("\033[27u", 27); + define_key("\033[27;1u", 27); + define_key("\033[27;5;97~", 1); + define_key("\033[97;5u", 1); + define_key("\033[27;5;99~", 3); + define_key("\033[99;5u", 3); + define_key("\033[27;5;101~", 5); + define_key("\033[101;5u", 5); + define_key("\033[27;5;102~", 6); + define_key("\033[102;5u", 6); + define_key("\033[27;5;110~", 14); + define_key("\033[110;5u", 14); + define_key("\033[27;5;114~", 18); + define_key("\033[114;5u", 18); + define_key("\033[27;5;117~", 21); + define_key("\033[117;5u", 21); + define_key("\033[27;5;120~", 24); + define_key("\033[120;5u", 24); /* Home/End arrive as CSI or SS3 depending on the terminal */ define_key("\033[1~", KEY_HOME); define_key("\033OH", KEY_HOME); @@ -184,8 +209,6 @@ void tui_keys_setup(void) init_pair(TUI_CP_RULE, COLOR_BLUE, -1); init_pair(TUI_CP_STATUS, COLOR_CYAN, -1); } - putp("\033[>4;1m"); - fflush(stdout); } @@ -1592,7 +1615,7 @@ static int form_run(const char *title, struct tui_form_field *f, int nf, h = na > 0 ? (can_edit ? "upp/ned/Tab = flytta Enter = ändra/utför" - " F5 = uppdatera ^Enter = spara" + " F5 = uppdatera ^Enter/F9 = spara" " Esc/q = tillbaka ^C = avsluta" : "upp/ned/Tab = flytta Enter = utför" " F5 = uppdatera Esc/q = tillbaka" @@ -1600,7 +1623,7 @@ static int form_run(const char *title, struct tui_form_field *f, int nf, " (endast behöriga kan ändra)") : (can_edit ? "upp/ned/Home/End Enter = ändra" - " F5 = uppdatera ^Enter = spara" + " F5 = uppdatera ^Enter/F9 = spara" " Esc/q = tillbaka ^C = avsluta" : "upp/ned/Home/End F5 = uppdatera" " Esc/q = tillbaka ^C = avsluta" diff --git a/docs/STATE.md b/docs/STATE.md index 39fa44e..929ddf1 100644 --- a/docs/STATE.md +++ b/docs/STATE.md @@ -14,14 +14,19 @@ unit tests and the docs consistency check. ## Resume here (2026-09-21) -- **Deployed**: `v0.1.56`, healthy on `nas` (Alpine runtime, static aarch64 +- **Deployed**: `v0.1.59`, healthy on `nas` (Alpine runtime, static aarch64 binaries cross-compiled on this machine — ~20 s, the host only assembles the image; image 33.7 MB, no `libssl3`). `v0.1.55` brought the payroll server waves (schema v11: employees, tax tables, lönebesked) and the - payroll TUI; `v0.1.56` brought the **Bolaget** hub (the Företag section is - gone, the registers live under Bolaget). The live database migrated - v9 → v11 on the v0.1.55 startup with the automatic pre-migration snapshot - in `var/db/backup/`. `main` and the tags are pushed to `nas`. + payroll TUI; `v0.1.56` the **Bolaget** hub; `v0.1.57` the flat main menu + without section headings, the **System** hub (Skattetabeller, Revision) + and the client-side remembered attachment directory; `v0.1.58` loads the + system CA bundle explicitly so the static binaries can verify TLS (the + Skatteverket fetch, SMTP, static clients); `v0.1.59` fixes form saving on + modern terminals (modifyOtherKeys level 2, Kitty keyboard protocol, and + `^Enter/F9` in the hints). The live database migrated v9 → v11 on the + v0.1.55 startup with the automatic pre-migration snapshot in + `var/db/backup/`. `main` and the tags are pushed to `nas`. - **Next tasks (payroll follow-ups)**: fold `payroll.settings_get/set` into `settings.get/set` (cmd_settings.c was busy during wave 1); settle the over-80k % rule (SKV 433 leaves it ambiguous — currently a clear @@ -86,7 +91,7 @@ Original entries for the struck items are in `docs/DECISIONS.md`. ## Environment / how to run -- **Deployed**: `scripts/deploy.sh` (latest `v0.1.56`, healthy on nas). +- **Deployed**: `scripts/deploy.sh` (latest `v0.1.59`, healthy on nas). Live daemon `tls:bokf.makandra.eu:8788`, token `~/.config/bokf/migration-token` (scopes `read,write`; owner-only actions like closing years must be done by the human in the TUI). Git remote diff --git a/docs/TUI-GUIDELINES.md b/docs/TUI-GUIDELINES.md index 94c3b5c..190a5c5 100644 --- a/docs/TUI-GUIDELINES.md +++ b/docs/TUI-GUIDELINES.md @@ -33,7 +33,7 @@ there. | `Ctrl+F` | Attach a file via the file browser (voucher form and voucher detail) | | `k` | Underlag: link the highlighted attachment to a voucher picked from a list | | `Ctrl+X` | Clear the current row — only inside row editors (never "new") | -| `Ctrl+Enter` | Save/post the current form. Enabled via xterm `modifyOtherKeys`; terminals that cannot send it keep `F9` working | +| `Ctrl+Enter` | Save/post the current form. Enabled via xterm `modifyOtherKeys` level 2 or the Kitty keyboard protocol; terminals that send neither keep `F9` working, and the hints show `^Enter/F9` | Every screen prints its keys in the footer via `hints()`. If a key exists, the footer shows it; if the footer shows it, the key works. Control keys are diff --git a/scripts/tui-golden.py b/scripts/tui-golden.py index 9df5ed4..f5d5b7f 100755 --- a/scripts/tui-golden.py +++ b/scripts/tui-golden.py @@ -56,7 +56,9 @@ KEYS = { "ctrlc": "\x03", "ctrln": "\x0e", "ctrlenter": "\x1b[27;5;13~", + "ctrlenter-kitty": "\x1b[13;5u", "f5": "\x1b[15~", + "f9": "\x1b[20~", } # {org_name} {org_nr} {fy_label} {fy_start} {fy_end} are substituted at run @@ -234,6 +236,58 @@ SCENARIOS = [ "expect": ["Anställda", "Testanställd", "Ny anställd"], }, { + "name": "employee-new-f9", + "screen": "employees", + "steps": [ + { + "keys": ["ctrln"], + "expect": ["Ny anställd", "Personnummer"], + }, + { + "keys": ["enter", "Testperson", "enter"], + "expect": ["Ny anställd", "Testperson"], + }, + { + "keys": ["down", "enter", "19900101-1234", "enter"], + "expect": ["19900101-1234"], + }, + { + "keys": ["f9"], + "expect": ["Anställd skapad."], + }, + { + "keys": ["enter"], + "expect": ["Anställda", "Testperson"], + }, + ], + }, + { + "name": "employee-new-kitty", + "screen": "employees", + "steps": [ + { + "keys": ["ctrln"], + "expect": ["Ny anställd", "Personnummer"], + }, + { + "keys": ["enter", "Kitty Person", "enter"], + "expect": ["Kitty Person"], + }, + { + "keys": ["down", "enter", "19900202-5678", "enter"], + "expect": ["19900202-5678"], + }, + { + "keys": ["ctrlenter-kitty"], + "expect": ["Anställd skapad."], + }, + { + "keys": ["enter"], + "expect": ["Anställda", "Kitty Person"], + }, + ], + }, + { "name": "company", "screen": "company", "expect": ["Bolaget", "Företagsuppgifter", "Fakturauppgifter", @@ -947,6 +1001,12 @@ def main(argv): report_failure(name, missing, "", before) failures += 1 continue + if (b"\x1b[>4;2m" not in app.raw or + b"\x1b[>1u" not in app.raw): + report_failure(name, ["keyboard protocol enable " + "sequence"], "", before) + failures += 1 + continue current = before for step in sc.get("steps", []): keys = resolve_keys(step["keys"]) @@ -18,6 +18,7 @@ #include <openssl/ssl.h> #include <openssl/x509.h> +#include "tls_ca.h" #include "util.h" #define SMTP_TIMEOUT_SEC 30 @@ -345,7 +346,7 @@ static int tls_start(struct smtp_conn *c, const char *host, char *err, SSL_CTX_set_options(c->ctx, SSL_OP_NO_COMPRESSION | SSL_OP_NO_RENEGOTIATION); SSL_CTX_set_verify(c->ctx, SSL_VERIFY_PEER, NULL); - if (SSL_CTX_set_default_verify_paths(c->ctx) != 1) { + if (tls_load_default_cas(c->ctx) != 1) { set_err(err, errlen, "smtp: cannot load system CA certificates"); return -1; } diff --git a/src/tax_table.c b/src/tax_table.c index 1cf40c1..6405a70 100644 --- a/src/tax_table.c +++ b/src/tax_table.c @@ -18,6 +18,7 @@ #include <openssl/x509.h> #include "db.h" +#include "tls_ca.h" #include "version.h" #define TT_LINE_LEN 49 @@ -478,7 +479,7 @@ static int tt_get_once(const struct tt_url *u, struct buf *body, int *status, SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION | SSL_OP_NO_RENEGOTIATION); SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); - if (SSL_CTX_set_default_verify_paths(ctx) != 1) { + if (tls_load_default_cas(ctx) != 1) { set_err(err, "cannot load system CA certificates"); goto done; } diff --git a/src/tls_ca.h b/src/tls_ca.h new file mode 100644 index 0000000..3b31345 --- /dev/null +++ b/src/tls_ca.h @@ -0,0 +1,31 @@ +#ifndef BOKF_TLS_CA_H +#define BOKF_TLS_CA_H + +#include <openssl/ssl.h> + +/* Load the system trust store. A statically linked OpenSSL keeps the build + machine's compiled-in directory (e.g. Debian's /usr/lib/ssl), which may + not exist where the binary runs, so the common bundle locations are also + tried explicitly. Returns 1 when any store was loaded. */ +static inline int tls_load_default_cas(SSL_CTX *ctx) +{ + int ok = SSL_CTX_set_default_verify_paths(ctx) == 1; + static const char *const files[] = { + "/etc/ssl/certs/ca-certificates.crt", + "/etc/pki/tls/certs/ca-bundle.crt", + NULL, + }; + static const char *const dirs[] = { + "/etc/ssl/certs", + NULL, + }; + for (int i = 0; files[i]; i++) + if (SSL_CTX_load_verify_locations(ctx, files[i], NULL) == 1) + ok = 1; + for (int i = 0; dirs[i]; i++) + if (SSL_CTX_load_verify_locations(ctx, NULL, dirs[i]) == 1) + ok = 1; + return ok; +} + +#endif |
