diff --git a/includes/nips b/includes/nips
new file mode 160000
index 00000000..05d3f198
--- /dev/null
+++ b/includes/nips
@@ -0,0 +1 @@
+Subproject commit 05d3f198c61c2732ccf15ba8005299365dabb8e0
diff --git a/iso/config/hooks/live/0020-enable-services.hook.chroot b/iso/config/hooks/live/0020-enable-services.hook.chroot
index a8492406..3ef0860d 100755
--- a/iso/config/hooks/live/0020-enable-services.hook.chroot
+++ b/iso/config/hooks/live/0020-enable-services.hook.chroot
@@ -1,26 +1,35 @@
#!/bin/sh
-# Enable n-OS-tr services so they start automatically on boot.
-set -e
+# Configure boot-time enable/disable posture for n-OS-tr services.
+set -eu
# Disable the Debian default site so it doesn't conflict with ours.
rm -f /etc/nginx/sites-enabled/default
-# Enable our site.
+# Enable our site symlink only. Runtime nginx startup is user-selected in TUI.
ln -sf /etc/nginx/sites-available/n-os-tr.conf /etc/nginx/sites-enabled/n-os-tr.conf
-systemctl enable nginx.service
-
-# Slice A
-systemctl enable ginxsom.service
-systemctl enable n-os-tr-firstboot.service
-
-# Slice B
-systemctl enable fips.service || true
-systemctl enable fips-dns.service || true
-
-# Slice C
-systemctl enable c-relay.service
-
-# TUI on tty1
+# TTY handoff: identity TUI owns tty1 before getty.
systemctl disable getty@tty1.service || true
+
+# Always-on boot pipeline.
+systemctl enable n-os-tr-firstboot.service
+systemctl enable nostr-id.service || true
systemctl enable nostr-id-tui.service
+
+# Explicitly keep optional services disabled at boot.
+for unit in \
+ c-relay.service \
+ ginxsom.service \
+ fips.service \
+ fips-dns.service \
+ nginx.service \
+ nostr-config-loader.service \
+ nostr-config-writer.service \
+ ssh.service \
+ tor.service
+ do
+ systemctl disable "$unit" 2>/dev/null || true
+ done
+
+# Apply preset policy for any additional units.
+systemctl preset-all --preset-mode=enable-only 2>/dev/null || true
diff --git a/iso/config/includes.chroot/etc/n-os-tr/services.conf b/iso/config/includes.chroot/etc/n-os-tr/services.conf
new file mode 100644
index 00000000..6432609c
--- /dev/null
+++ b/iso/config/includes.chroot/etc/n-os-tr/services.conf
@@ -0,0 +1,9 @@
+# key\tunit\tlabel\tdefault\tdeps
+r c-relay.service c-relay off nginx.service
+g ginxsom.service ginxsom off nginx.service
+f fips.service fips off -
+n nginx.service nginx off -
+l nostr-config-loader.service config-loader off -
+w nostr-config-writer.service config-writer off nostr-config-loader.service
+s ssh.service ssh off -
+t tor.service tor off -
diff --git a/iso/config/includes.chroot/etc/systemd/system-preset/50-n-os-tr.preset b/iso/config/includes.chroot/etc/systemd/system-preset/50-n-os-tr.preset
new file mode 100644
index 00000000..38adab14
--- /dev/null
+++ b/iso/config/includes.chroot/etc/systemd/system-preset/50-n-os-tr.preset
@@ -0,0 +1,16 @@
+# n-OS-tr service posture: only identity + firstboot + TUI start automatically.
+# Optional services are loaded/installed but disabled by default.
+
+enable n-os-tr-firstboot.service
+enable nostr-id.service
+enable nostr-id-tui.service
+
+disable c-relay.service
+disable ginxsom.service
+disable fips.service
+disable fips-dns.service
+disable nginx.service
+disable nostr-config-loader.service
+disable nostr-config-writer.service
+disable ssh.service
+disable tor.service
diff --git a/iso/config/package-lists/base.list.chroot b/iso/config/package-lists/base.list.chroot
index e6b5b6aa..32090112 100644
--- a/iso/config/package-lists/base.list.chroot
+++ b/iso/config/package-lists/base.list.chroot
@@ -15,3 +15,6 @@ nftables
# TUI runtime dependency
libncursesw6
+
+# Service-menu optional stack
+tor
diff --git a/n_os_tr.code-workspace b/n_os_tr.code-workspace
new file mode 100644
index 00000000..22619dfb
--- /dev/null
+++ b/n_os_tr.code-workspace
@@ -0,0 +1,13 @@
+{
+ "folders": [
+ {
+ "path": "."
+ },
+ {
+ "path": "../aesthetics"
+ }
+ ],
+ "settings": {
+ "git.ignoreLimitWarning": true
+ }
+}
\ No newline at end of file
diff --git a/plans/iso_architecture.md b/plans/iso_architecture.md
index 5d4f0720..87ee158e 100644
--- a/plans/iso_architecture.md
+++ b/plans/iso_architecture.md
@@ -7,12 +7,13 @@ survive individual work sessions.
## 1. Big-picture goal
A single bootable/installable Debian Live ISO that, the moment it finishes
-booting, is running:
+booting, has all n-OS-tr services installed and configured, then presents a
+boot-time identity + service-selection TUI before login:
-- [`fips`](../includes/fips) — Rust mesh-routing daemon, npub-addressed IPv6 overlay
-- [`c-relay`](../includes/c-relay) — C Nostr relay with event-based config and embedded admin
-- [`ginxsom`](../includes/ginxsom) — C Blossom (blob) server as FastCGI behind nginx
-- `nginx` — TLS front door, static blob serving, reverse proxy
+- [`fips`](../includes/fips) — Rust mesh-routing daemon, npub-addressed IPv6 overlay (optional start)
+- [`c-relay`](../includes/c-relay) — C Nostr relay with event-based config and embedded admin (optional start)
+- [`ginxsom`](../includes/ginxsom) — C Blossom (blob) server as FastCGI behind nginx (optional start)
+- `nginx` — TLS front door, static blob serving, reverse proxy (optional start)
No Docker. No `curl | bash` provisioning. All three apps run as native
systemd services directly on the host.
@@ -116,28 +117,46 @@ sequenceDiagram
participant LB as live-boot / initramfs
participant Sd as systemd PID 1
participant Fb as n-os-tr-firstboot.service
+ participant IA as nostr-id.service
+ participant TUI as nostr-id-tui.service
participant Fp as fips.service
participant Cr as c-relay.service
participant Gx as ginxsom.service
participant Ng as nginx.service
+ participant GT as getty@tty1.service
LB->>Sd: hand off
- Sd->>Fb: start oneshot (Before= the app units)
+ Sd->>Fb: start oneshot (Before= identity + app units)
alt First boot
Fb->>Fb: mkdir /var/lib/n-os-tr
generate ginxsom key
generate fips key
write EnvironmentFile
touch .provisioned
else Already provisioned
Fb-->>Sd: exit 0 (ConditionPathExists trip)
end
Fb-->>Sd: done
- par
- Sd->>Fp: start (reads /etc/fips/fips.yaml)
- and
- Sd->>Cr: start (self-provisions admin key to /var/lib/c-relay/)
- and
- Sd->>Gx: start (spawn-fcgi, reads env file for privkey)
- and
- Sd->>Ng: start (serves /var/www/blobs, proxies to c-relay and ginxsom)
+
+ Sd->>IA: start identity-agent
+ IA-->>Sd: active (control socket ready)
+
+ Sd->>TUI: start boot-time identity + services flow on tty1
+ TUI->>IA: load_mnemonic / load_bunker / load_amnesia
+ IA-->>TUI: identity loaded
+ TUI->>Sd: start/stop selected services via systemctl
+
+ opt User enabled nginx
+ Sd->>Ng: start
end
+ opt User enabled c-relay
+ Sd->>Cr: start
+ end
+ opt User enabled ginxsom
+ Sd->>Gx: start
+ end
+ opt User enabled fips
+ Sd->>Fp: start
+ end
+
+ TUI->>Sd: exit 0
+ Sd->>GT: start (after TUI exits)
```
## 5. Phase 0.A — Gitea artifact fetch
diff --git a/plans/tui_login.md b/plans/tui_login.md
index 16bc0b98..0c61941f 100644
--- a/plans/tui_login.md
+++ b/plans/tui_login.md
@@ -185,6 +185,10 @@ stack/nostr-id-tui/ # new subtree
│ ├── screen_generate.c # generate + display + 3-position confirm
│ ├── screen_import.c # /media/*/mnemonic.txt discovery + wipe
│ ├── screen_bunker.c # NIP-46 bunker URL entry
+│ ├── screen_services.c # post-identity service selection menu
+│ ├── screen_services_confirm.c # commit/apply status + failure handling
+│ ├── services_conf.c # /etc/n-os-tr/services.conf parser
+│ ├── systemd_client.c # wrapper over /bin/systemctl and journalctl
│ ├── screen_error.c # fatal error screen
│ ├── curses_helpers.c # small ncurses wrappers (input w/o history)
│ ├── agent_client.c # speak nostr-id control socket (see §5)
@@ -237,9 +241,12 @@ stateDiagram-v2
Amnesia --> SendAgentAmnesia
- SendAgent --> Exit: agent returns ok
+ SendAgent --> Services: agent returns ok
SendAgent --> FatalError: agent returns error
- SendAgentAmnesia --> Exit
+ SendAgentAmnesia --> Services
+
+ Services --> Exit: commit/skip complete
+ Services --> FatalError: unrecoverable apply error
FatalError --> [*]
Exit --> [*]
@@ -261,7 +268,9 @@ stateDiagram-v2
**Amnesia.** No persistence, no Nostr config projection, ephemeral main key generated via [`nostr_generate_keypair`](../includes/nostr_core_lib/nostr_core/nip006.h:20). Big red banner: "AMNESIA MODE. This identity dies at shutdown."
-**FatalError.** A single full-screen error. No recovery — require the user to reboot. This is only reached if ncurses init failed, the agent control socket is unavailable, or memory locking failed (which means the threat-model invariants cannot be enforced).
+**Services.** After identity load succeeds, the same TUI presents a service-selection screen (see [`plans/tui_service_menu.md`](tui_service_menu.md)). The user toggles optional services (c-relay, ginxsom, fips, nginx, config-loader, config-writer, ssh, tor) and either commits changes or skips with all services off for that session.
+
+**FatalError.** A single full-screen error. No recovery — require the user to reboot. This is only reached if ncurses init failed, the agent control socket is unavailable, memory locking failed (which means the threat-model invariants cannot be enforced), or service-apply hits a non-recoverable system state.
### 3.3 Anti-shoulder-surf tradeoffs
@@ -408,6 +417,9 @@ sequenceDiagram
Note over TUI: user interacts
TUI->>IA: load_mnemonic / load_bunker / load_amnesia
IA-->>TUI: ok
+ Note over TUI: service selection menu (same binary)
+ TUI->>SD: start/stop selected units
+ SD-->>TUI: unit results
TUI->>SD: exit 0
SD->>GT: start (now allowed by ordering)
GT->>User: "n-os-tr login:"
diff --git a/plans/tui_service_menu.md b/plans/tui_service_menu.md
new file mode 100644
index 00000000..de105406
--- /dev/null
+++ b/plans/tui_service_menu.md
@@ -0,0 +1,538 @@
+# TUI Service Menu — Post-Identity Service Selection
+
+> **Status:** Design. This document extends [`plans/tui_login.md`](tui_login.md) with a second screen inside the same `nostr-id-tui` binary: after the user has successfully loaded an identity (mnemonic / generated / imported / bunker / amnesia), the TUI presents a menu of **optional services** and lets the user toggle each one on or off **before** `getty@tty1` is allowed to start.
+>
+> Depends on: [`plans/tui_login.md`](tui_login.md), [`plans/identity_subsystem.md`](identity_subsystem.md), [`plans/iso_architecture.md`](iso_architecture.md), [`plans/nostr_config_projection.md`](nostr_config_projection.md), [`/home/user/lt/aesthetics/TUI.md`](/home/user/lt/aesthetics/TUI.md).
+
+## 0. Policy
+
+1. **n-OS-tr always installs and sets up every service.** Binaries, unit files, config templates, data directories — all present on the image.
+2. **No service starts automatically at boot except the identity/boot pipeline itself.** Exactly these three are enabled image-wide: [`n-os-tr-firstboot.service`](../iso/config/includes.chroot/etc/systemd/system/n-os-tr-firstboot.service), `nostr-id.service` (identity-agent), [`nostr-id-tui.service`](../iso/config/includes.chroot/etc/systemd/system/nostr-id-tui.service). Everything else ships `disabled` via the live-build preset policy.
+3. **The user decides what turns on.** After the user authenticates via `nostr-id-tui`, the TUI presents a service-toggle screen. The user picks what they want running this session.
+4. **Selections are session-local in v1.** No persistence across reboots. Every boot starts with the same defaults. A future revision may project selections to a Nostr config slot (§10).
+5. **One binary, one ceremony.** The service menu is a new screen inside `nostr-id-tui`, not a separate unit. Rationale discussed in the architect session: the user is already in one authenticated terminal flow; we do not want two back-to-back ceremonies before login.
+6. **Default-off by default.** Every toggleable service starts the session in the "off" state. The user must explicitly enable each one they want running. This matches the privacy-forward posture of the project.
+7. **Style:** strict adherence to [`/home/user/lt/aesthetics/TUI.md`](/home/user/lt/aesthetics/TUI.md) — single-key actions, terminal-width headers, ANSI underline for hotkeys, canonical menu template (§12 of that doc).
+
+## 1. Scope
+
+### 1.1 Services in the menu (v1)
+
+| Key | Label | systemd unit | Default | What it does |
+|-----|-----------------|---------------------------|---------|--------------------------------------------------------|
+| `r` | `c-relay` | `c-relay.service` | off | Local Nostr relay (NIP-01/42). Admin key = index 2. |
+| `g` | `ginxsom` | `ginxsom.service` | off | Blossom blob server behind nginx. Server key = index 3.|
+| `f` | `fips` | `fips.service` | off | Mesh-routing daemon; npub-IPv6 overlay. Key = index 4. |
+| `n` | `nginx` | `nginx.service` | off | TLS front door / reverse proxy. Needed by c-relay ws, ginxsom uploads. |
+| `l` | `config-loader` | `nostr-config-loader.service` | off | Fetch + materialize user config slots (§ [`nostr_config_projection.md`](nostr_config_projection.md#3-boot-flow)). |
+| `w` | `config-writer` | `nostr-config-writer.service` | off | Save tracked home paths back to Nostr on shutdown. |
+| `s` | `ssh` | `ssh.service` | off | OpenSSH server. Keys regenerated at firstboot. |
+| `t` | `tor` | `tor.service` | off | Tor daemon. Default posture = SOCKS-only (client), not exit/relay. |
+
+### 1.2 Services explicitly NOT in the menu
+
+- `nostr-id.service` — identity-agent is always required; it's the thing the TUI talks to.
+- `n-os-tr-firstboot.service` — oneshot, runs before the TUI, not user-controlled.
+- `nostr-id-tui.service` — the TUI itself.
+- `systemd-networkd`/`NetworkManager`/`dbus` — core system plumbing; managed by distro defaults.
+
+### 1.3 Service dependencies the TUI must respect
+
+Some services only make sense together. The TUI enforces these dependencies at toggle time (not at the systemd level — systemd's own `Requires=`/`Wants=` handles the actual startup ordering):
+
+| Service | Implicit deps (auto-enabled when this is turned on) |
+|---------------|-------------------------------------------------------------|
+| `c-relay` | `nginx` (wss termination and cert) |
+| `ginxsom` | `nginx` (serves blobs, proxies uploads) |
+| `config-writer` | `config-loader` (must have run at boot to know the slots) |
+
+If the user toggles `c-relay` on and `nginx` is off, the TUI:
+- Shows a one-line prompt: `c-relay needs nginx. Turn nginx on too? [Y/n]`.
+- Default `y` on Enter. If `n`, the toggle is cancelled (we do not start a service that won't be reachable).
+
+Reverse direction (turning `nginx` off while `c-relay` is on): prompt `c-relay will become unreachable without nginx. Stop c-relay too? [Y/n]`.
+
+## 2. Placement in the existing TUI flow
+
+### 2.1 Updated state diagram
+
+```mermaid
+stateDiagram-v2
+ [*] --> Menu
+
+ Menu --> Enter: 1 / E
+ Menu --> Generate: 2 / G
+ Menu --> Import: 3 / I
+ Menu --> Bunker: 4 / B
+ Menu --> Amnesia: 5 / A
+
+ Enter --> SendAgent: BIP-39 ok
+ Generate --> SendAgent: confirm words ok
+ Import --> SendAgent: file read + wiped
+ Bunker --> SendAgent: NIP-46 connected
+ Amnesia --> SendAgent: ephemeral key
+
+ SendAgent --> Services: agent returns ok
+ SendAgent --> FatalError: agent returns error
+
+ Services --> Services: user toggles
+ Services --> Confirm: user presses C (commit)
+ Services --> Services: user presses X (skip, all off)
+
+ Confirm --> Apply: user presses Y
+ Confirm --> Services: user presses N
+
+ Apply --> Apply: start/stop each selected unit
+ Apply --> Exit: all units reached terminal state
+ Apply --> ApplyError: any unit failed
+
+ ApplyError --> Services: user reviews, retries
+
+ FatalError --> [*]
+ Exit --> [*]
+```
+
+The only new states are `Services`, `Confirm`, `Apply`, and `ApplyError`. Everything before `SendAgent` is unchanged from [`plans/tui_login.md §3.1`](tui_login.md#31-state-diagram).
+
+### 2.2 Updated boot sequence
+
+```mermaid
+sequenceDiagram
+ participant SD as systemd
+ participant IA as nostr-id.service
+ participant TUI as nostr-id-tui.service
+ participant Svc as toggleable services
+ participant GT as getty@tty1.service
+
+ SD->>IA: start
+ IA-->>SD: listening on control.sock
+ SD->>TUI: start (owns tty1)
+ Note over TUI: identity screens
+ TUI->>IA: load_mnemonic/bunker/amnesia
+ IA-->>TUI: ok, main_npub
+ Note over TUI: service menu
+ TUI->>TUI: user toggles, presses Commit
+ TUI->>SD: StartUnit/StopUnit via dbus
+ SD->>Svc: start selected services
+ Svc-->>SD: active
+ SD-->>TUI: job results
+ TUI->>SD: exit 0
+ SD->>GT: start (ordering allows)
+ GT->>User: n-os-tr login:
+```
+
+## 3. Screen design
+
+Strict compliance with [`/home/user/lt/aesthetics/TUI.md`](/home/user/lt/aesthetics/TUI.md). Every screen follows the canonical template in §12 of that doc.
+
+### 3.1 Main services screen
+
+```text
+===================== SERVICES =====================
+ (expands to terminal width)
+
+Identity: npub1abcd…wxyz (main, index 0)
+Session: live (in-memory, no persistence)
+
+ Key Service State Default Depends on
+ --- ------------- --------- ------- -----------
+ R c-relay [ off ] off nginx
+ G ginxsom [ off ] off nginx
+ F fips [ off ] off —
+ N nginx [ off ] off —
+ L config-loader [ off ] off —
+ W config-writer [ off ] off config-loader
+ S ssh [ off ] off —
+ T tor [ off ] off —
+
+Actions:
+ R/G/F/N/L/W/S/T) Toggle service
+ A) Turn All on
+ Z) Turn all off (reset)
+ C) Commit and continue
+ X) Skip (all off, continue)
+
+Select action: >
+```
+
+Formatting rules (all from [TUI.md](/home/user/lt/aesthetics/TUI.md)):
+
+- Header expands to full terminal width using the `=== SERVICES ===` pattern from [`ui.c:print_centered_header`](/home/user/lt/aesthetics/TUI.md:88).
+- Column widths are fixed; when terminal is narrower than 60 cols, drop the "Default" and "Depends on" columns (the state is what matters).
+- State column is either `[ off ]` or `[ on ]`. A third transient state `[ ... ]` appears during the Apply phase.
+- The first letter of each service name in the Key column is rendered with ANSI underline via `\033[4m\033[0m`, matching [TUI.md §7](/home/user/lt/aesthetics/TUI.md).
+- Prompt at bottom is `Select action: >` per the canonical template.
+
+### 3.2 Confirm screen
+
+Before actually doing anything, we show what will happen:
+
+```text
+===================== CONFIRM =====================
+
+The following changes will be applied:
+
+ START nginx.service
+ START c-relay.service
+ START fips.service
+
+ (5 services remain off)
+
+Proceed? [Y/n] >
+```
+
+Only services whose session state differs from their default appear. Pure-default sessions (no changes) go straight from the services screen's `C`/`X` directly to `Apply` with an empty plan and exit.
+
+### 3.3 Apply screen
+
+Live progress. We iterate the plan serially (not concurrently — systemd handles that anyway, and serial output is easier to read):
+
+```text
+===================== APPLYING =====================
+
+ [ OK ] nginx.service started in 0.42s
+ [ OK ] c-relay.service started in 1.14s
+ [ ... ] fips.service starting...
+ [ ] (waiting)
+
+Press Ctrl-C to abort remaining jobs (already-started services stay up).
+```
+
+Each row shows one of:
+- `[ ]` queued
+- `[ ... ]` in progress (animated dot after 250 ms; ANSI cursor save/restore to re-render only the affected row)
+- `[ OK ]` finished successfully
+- `[FAIL ]` finished with an error
+- `[SKIP ]` not applied (e.g. dependency refused earlier)
+
+After all jobs complete:
+
+- All green → advance to Exit immediately (print `All services started. Handing control to login...` for 1 second, then the TUI exits and getty takes over).
+- Any failures → advance to **ApplyError** screen.
+
+### 3.4 Apply-error screen
+
+```text
+===================== PARTIAL FAILURE =====================
+
+Some services did not start cleanly:
+
+ [FAIL ] ginxsom.service
+ SubState: failed
+ ExecMainStatus: 1
+ Last log line: "/usr/local/bin/ginxsom/ginxsom-fcgi: socket bind failed"
+
+Actions:
+ V) View journal for failed services
+ R) Retry failed services only
+ B) Back to service menu
+ I) Ignore and continue to login anyway
+
+Select action: >
+```
+
+The journal view (`V`) shells out to `journalctl -u -n 50 --no-pager` via a pipe (we do not embed a log viewer). `less`-style paging is acceptable if available; otherwise we just print and prompt to continue.
+
+## 4. Service manifest
+
+The TUI does not hard-code the service list in C. It reads `/etc/n-os-tr/services.conf` at startup. This lets us add/remove services (especially tor, which lands later) without rebuilding the binary.
+
+### 4.1 File format
+
+A small line-based format, one record per service, fields tab-separated:
+
+```
+# key unit label default deps
+r c-relay.service c-relay off nginx.service
+g ginxsom.service ginxsom off nginx.service
+f fips.service fips off -
+n nginx.service nginx off -
+l nostr-config-loader.service config-loader off -
+w nostr-config-writer.service config-writer off nostr-config-loader.service
+s ssh.service ssh off -
+t tor.service tor off -
+```
+
+Comments start with `#`. Blank lines ignored. Unknown fields ignored for forward compatibility. Multiple deps separated by `,`. A literal `-` means no deps.
+
+Parsing happens in `stack/nostr-id-tui/src/services_conf.c`. On parse error the TUI falls back to a hardcoded minimal list (c-relay, ginxsom, fips, nginx) and logs the parse error to stderr (captured by journald, per the service unit's `StandardError=journal`).
+
+### 4.2 Install location
+
+Shipped in the chroot at [`iso/config/includes.chroot/etc/n-os-tr/services.conf`](../iso/config/includes.chroot/etc/n-os-tr/services.conf). Mode `0644`, owner `root:root`.
+
+## 5. Control path: talking to systemd
+
+### 5.1 Transport choice
+
+The TUI needs to:
+1. Query each unit's current state (`LoadState`, `ActiveState`, `SubState`).
+2. Start and stop units.
+3. Receive job-completion signals.
+
+Two realistic options in a static musl C binary with no glibc:
+
+| Option | Notes | Verdict |
+|----------------------------------------------------------------|---------------------------------------------------------------------------------------------|---------------------|
+| **Shell out to `systemctl`** via `fork` + `execve` | Simple. Exit codes are well-defined. Parse `show --property=ActiveState=`. No linking. | **v1 default.** |
+| **Speak the D-Bus wire protocol directly** to `/run/dbus/system_bus_socket` | Avoids forking. Enables real-time `JobRemoved` signals. Needs a tiny D-Bus marshaller (~1 KLOC) or the vendored cjson-style lib. | Later optimization. |
+| **Link `libsystemd.a`** | Would pull in a chunk of systemd source into Alpine build, and libsystemd isn't available as `-static` in Alpine. | Rejected. |
+
+v1 uses `systemctl`. The binary path is hardcoded to `/bin/systemctl` (present on every Debian live image — `systemctl` itself is part of `systemd` which is in base). The wrapper module is `stack/nostr-id-tui/src/systemd_client.c` and exposes:
+
+```c
+typedef enum {
+ SVC_INACTIVE = 0,
+ SVC_ACTIVATING,
+ SVC_ACTIVE,
+ SVC_DEACTIVATING,
+ SVC_FAILED,
+ SVC_UNKNOWN
+} svc_state_t;
+
+svc_state_t systemd_active_state(const char *unit); /* systemctl show -p ActiveState */
+int systemd_start(const char *unit, int timeout_ms, char *err_out, size_t err_len);
+int systemd_stop (const char *unit, int timeout_ms, char *err_out, size_t err_len);
+int systemd_journal_tail(const char *unit, int n_lines, FILE *out); /* exec journalctl */
+```
+
+Each call spawns `systemctl` / `journalctl` with `posix_spawn` into a pipe, waits with a timeout, captures stdout/stderr, and returns.
+
+### 5.2 Privilege
+
+`nostr-id-tui.service` currently runs as root on `tty1` (see the service unit). That gives it direct `systemctl` privilege — no polkit rule needed. This matches the existing threat-model stance in [`plans/tui_login.md §6.1`](tui_login.md#61-etcsystemdsystemnostr-id-tuiservice): the TUI is already privileged because it owns the tty and talks to identity-agent over a root-only socket.
+
+If we ever demote the TUI to run as the `nostr-id` user, we'll need `/etc/polkit-1/rules.d/50-n-os-tr-services.rules` granting that user the `org.freedesktop.systemd1.manage-units` action **only for the whitelisted units listed in `services.conf`**. That's future work.
+
+### 5.3 Timeouts and cancellation
+
+- Per-unit start/stop timeout: 30 s (covers nginx startup, fips mesh ready, etc.). Larger than any real service needs.
+- TUI-level overall budget: 2 minutes. If exceeded, Apply falls through to ApplyError with any unfinished jobs marked `[TIME]` and the user is prompted.
+- Ctrl-C in the Apply screen sends SIGINT to the current `systemctl` child, marks the remaining jobs `[SKIP ]`, and goes to ApplyError.
+
+## 6. Source tree changes
+
+Extending the layout declared in [`plans/tui_login.md §2.2`](tui_login.md#22-code-that-lives-in-this-repo-n_os_tr):
+
+```
+stack/nostr-id-tui/
+├── src/
+│ ├── main.c # now dispatches identity→services→exit
+│ ├── screen_menu.c # unchanged
+│ ├── screen_enter.c # unchanged
+│ ├── screen_generate.c # unchanged
+│ ├── screen_import.c # unchanged
+│ ├── screen_bunker.c # unchanged
+│ ├── screen_services.c # NEW: main service toggle screen
+│ ├── screen_services_confirm.c # NEW: confirm + apply screens
+│ ├── services_conf.c # NEW: parser for /etc/n-os-tr/services.conf
+│ ├── systemd_client.c # NEW: fork/exec systemctl + journalctl
+│ ├── curses_helpers.c # unchanged
+│ ├── agent_client.c # unchanged
+│ └── memhygiene.c # unchanged
+├── include/
+│ └── n_os_tr_tui.h # adds svc_* decls + ui_backend hooks
+└── tests/
+ ├── test_screen_services.c # NEW: state-machine unit test
+ ├── test_services_conf.c # NEW: parser fuzz/table tests
+ ├── test_systemd_client.c # NEW: runs against a fake systemctl shim
+ └── test_integration.sh # extended to toggle a service in QEMU
+```
+
+No ncurses API leaks into the new state machines. All rendering goes through the same `ui_backend_t` abstraction established in [`plans/tui_login.md §7`](tui_login.md#7-per-flavor-abstraction). The Pi LCD port will get a stripped service menu for free.
+
+## 7. Systemd preset policy
+
+### 7.1 What "installed but not enabled" means
+
+For each toggleable unit we ship:
+- The unit file in `iso/config/includes.chroot/etc/systemd/system/` or (for distro-packaged units like `ssh`, `tor`, `nginx`) in the package itself.
+- **No symlink in `multi-user.target.wants/`** — i.e. no `systemctl enable`.
+- A preset file that explicitly forbids enabling at image-build time.
+
+### 7.2 Preset file
+
+New file: [`iso/config/includes.chroot/etc/systemd/system-preset/50-n-os-tr.preset`](../iso/config/includes.chroot/etc/systemd/system-preset/50-n-os-tr.preset):
+
+```
+# n-OS-tr service posture: only the identity/boot pipeline auto-starts.
+# Everything else is explicitly disabled. The TUI turns them on at the
+# user's request after identity load.
+enable nostr-id.service
+enable nostr-id-tui.service
+enable n-os-tr-firstboot.service
+
+disable c-relay.service
+disable ginxsom.service
+disable fips.service
+disable nginx.service
+disable nostr-config-loader.service
+disable nostr-config-writer.service
+disable ssh.service
+disable tor.service
+```
+
+The `50-` prefix lands before Debian defaults (typically `90-*.preset`), so our `disable` lines override e.g. nginx's upstream preset-enable.
+
+### 7.3 Rework of the enable-services hook
+
+Current hook [`iso/config/hooks/live/0020-enable-services.hook.chroot`](../iso/config/hooks/live/0020-enable-services.hook.chroot) enables the services directly. Rework it to be explicit about the new posture:
+
+```sh
+#!/bin/sh
+set -eu
+
+# Enable the always-on trio
+systemctl enable n-os-tr-firstboot.service
+systemctl enable nostr-id.service
+systemctl enable nostr-id-tui.service
+
+# Explicitly disable everything else in the service manifest.
+# Idempotent; safe to re-run. `systemctl disable` is a no-op for units
+# that aren't enabled yet, which is what we want.
+for unit in \
+ c-relay.service \
+ ginxsom.service \
+ fips.service \
+ nginx.service \
+ nostr-config-loader.service \
+ nostr-config-writer.service \
+ ssh.service \
+ tor.service ; do
+ systemctl disable "$unit" 2>/dev/null || true
+done
+
+# Run the preset policy for anything we didn't list above (defensive).
+systemctl preset-all --preset-mode=enable-only 2>/dev/null || true
+```
+
+### 7.4 Package-list additions
+
+`tor` is new to the image. Add to [`iso/config/package-lists/base.list.chroot`](../iso/config/package-lists/base.list.chroot):
+
+```
+tor
+```
+
+No other new packages: nginx, openssh-server, ca-certificates, systemd, etc. are already pulled.
+
+## 8. Interaction with identity modes
+
+### 8.1 Normal, Bunker, and Amnesia modes
+
+- **Normal (mnemonic loaded)**: full menu, full defaults as in §1.1.
+- **Bunker mode**: services that require a raw local nsec are **disabled in the menu** (greyed out with `[ n/a ]` in the state column). Those are:
+ - `c-relay` — admin key comes from identity-agent via NIP-46 signing for event-based config only; for the admin nsec path the service fails. The menu shows `[ n/a ]` with a tooltip line "requires local key; bunker mode cannot provide".
+ - `ginxsom` — same. Key material is needed at process start (`--server-privkey` in [`ginxsom.service`](../iso/config/includes.chroot/etc/systemd/system/ginxsom.service)).
+ - `fips` — same.
+ - `nginx`, `config-loader`, `config-writer`, `ssh`, `tor` — available in bunker mode.
+- **Amnesia mode**: all services available, but `config-loader`/`config-writer` are explicitly disabled with label "amnesia session — config not persisted" (we don't pull anything from Nostr for an ephemeral identity).
+
+The rules live in `services_conf.c` as a small matrix; the file format in §4.1 can be extended later with `modes=normal,bunker,amnesia` but v1 hardcodes the matrix in C.
+
+### 8.2 Re-entry
+
+A future option from the Services menu: `Q) Reset identity and return to identity menu`. Deferred — not in v1. For v1 the only path out of Services is `C` (commit) or `X` (skip), both of which lead to Exit.
+
+## 9. Testing
+
+### 9.1 Unit (in Alpine/musl builder)
+
+- `test_services_conf` — feeds valid and malformed manifests, asserts parser produces correct structs and correct fallback on bad input.
+- `test_systemd_client` — sets `$PATH` to a directory with a shim `systemctl` that records its argv and emits canned output; asserts our client parses `ActiveState=active|inactive|failed` correctly and times out when the shim sleeps.
+- `test_screen_services` — drives the services state machine through a mock `ui_backend_t`; asserts:
+ - Default state is all-off.
+ - Toggling `R` then `G` then `R` ends with `G` on and `R` off.
+ - Toggling `R` prompts for `N` enable; answering `y` enables both.
+ - `Z` (reset) returns to defaults.
+ - `X` (skip) goes directly to Apply with an empty plan.
+ - `C` (commit) goes to Confirm.
+
+### 9.2 Integration (QEMU)
+
+Extend [`stack/nostr-id-tui/tests/test_integration.sh`](../stack/nostr-id-tui/tests/test_integration.sh) with a new scripted run:
+1. Boot the ISO.
+2. At the identity screen, feed the Trezor test-vector mnemonic.
+3. At the services screen, press `N` (enable nginx), then `C`, then `Y`.
+4. After the TUI exits and getty appears, log in as the default user.
+5. Assert `systemctl is-active nginx` = `active` and `systemctl is-active c-relay` = `inactive`.
+
+### 9.3 Smoketest extensions
+
+Extend [`iso/config/includes.chroot/usr/local/bin/n-os-tr-smoketest`](../iso/config/includes.chroot/usr/local/bin/n-os-tr-smoketest) to also verify:
+
+- Preset posture at **image build time**: for each toggleable unit, `systemctl is-enabled ` returns `disabled`.
+- The manifest file `/etc/n-os-tr/services.conf` exists and parses (re-invoke the parser from a test helper).
+- `/bin/systemctl` is present (the TUI depends on it).
+
+## 10. Non-persistence and the future Nostr slot
+
+v1: selections are session-local. Reboot → all services back to off, user re-selects.
+
+Future: one new entry in the manifest scheme from [`plans/nostr_config_projection.md §2.1`](nostr_config_projection.md#21-slots):
+
+```json
+{
+ "name": "n-os-tr/services",
+ "path": "/run/nostr-id/services.state.json",
+ "mode": "0600",
+ "encrypted": true
+}
+```
+
+Content schema (draft):
+
+```json
+{
+ "version": 1,
+ "enabled": ["nginx.service", "c-relay.service", "fips.service"]
+}
+```
+
+When a session starts, if a services slot exists, the TUI pre-loads the toggle state from it. User still has to press `C` to commit — we don't auto-apply without the user seeing the menu. This preserves the "the user decides when to turn them on" rule.
+
+Out of scope for v1 implementation.
+
+## 11. Failure modes
+
+| Failure | Behavior |
+|-------------------------------------------------|--------------------------------------------------------------------------|
+| `/etc/n-os-tr/services.conf` missing or unparseable | Log to stderr, fall back to hardcoded minimal list (c-relay, ginxsom, fips, nginx). |
+| `/bin/systemctl` missing | FatalError (as in [`plans/tui_login.md §9`](tui_login.md#9-failure-modes-summary)). The ISO is malformed. |
+| Unit start returns non-zero | Row shows `[FAIL ]`, ApplyError screen is reached after all jobs settle. |
+| Unit start hangs past 30 s | Timeout, row shows `[TIME]`, ApplyError. |
+| User hits Ctrl-C during Apply | SIGINT to current `systemctl`, remaining jobs `[SKIP ]`, ApplyError. |
+| Bunker mode, user tries to toggle `c-relay` | Toggle is ignored; status line shows "requires local key; bunker mode". |
+| User toggles all off and presses Commit | Apply plan is empty; TUI prints `No services requested. Handing control to login...` and exits. |
+| Identity agent dies during service menu | We're not calling the agent during this phase — selections are in TUI memory only. User is unaffected until they finish service menu and the TUI exits; if the agent is dead when services start, those services will fail on their own dependency chain. |
+
+## 12. Docs to update
+
+- [`plans/tui_login.md`](tui_login.md) — add a one-line reference in §3.1 to the new `Services` state after `SendAgent`; add `screen_services.c`, `screen_services_confirm.c`, `services_conf.c`, `systemd_client.c` to the source tree listing in §2.2; update §13 implementation order with the service-menu steps.
+- [`plans/iso_architecture.md`](iso_architecture.md) — update §4 boot sequence to show the identity+services TUI phase before the parallel `fips/c-relay/ginxsom/nginx` block, and note that those services are started by the TUI, not auto-started by systemd.
+- [`iso/config/includes.chroot/usr/share/doc/n-os-tr/README`](../iso/config/includes.chroot/usr/share/doc/n-os-tr/README) — describe the new login→services flow for end users.
+- [`docs/RUNNING.md`](../docs/RUNNING.md) — add a section showing the service menu and how to toggle.
+
+## 13. Implementation order (Phase 3a.2)
+
+Small, independently verifiable steps. Each one leaves the repo in a runnable state.
+
+1. **Land the preset file and rework the enable hook.** After this commit, a fresh ISO boots to a system where only `nostr-id`, `nostr-id-tui`, and `firstboot` are enabled; every other service is present on disk but inactive. Verify with `systemctl list-unit-files --state=disabled` in a QEMU boot.
+2. **Add the service manifest file.** Ship `/etc/n-os-tr/services.conf` with the v1 table from §1.1. No TUI change yet; verifies that the file reaches the image.
+3. **Add `tor` to the base package list.** Confirm the built ISO includes `tor.service` as a disabled unit.
+4. **Implement `services_conf.c` + tests.** Parser plus a 40-line sanity test in the Alpine builder.
+5. **Implement `systemd_client.c` + tests.** Fork/exec + pipe capture + shim-based test.
+6. **Implement `screen_services.c` state machine + `test_screen_services`.** No rendering yet — verifies transitions against a mock backend.
+7. **Render the services screen in `ui_backend_curses.c`.** First visible new screen.
+8. **Implement `screen_services_confirm.c` — confirm/apply/apply-error screens.** Wire them to `systemd_client`.
+9. **Hook into `main.c`.** After `agent_client` returns from `load_mnemonic`/`load_bunker`/`load_amnesia` with ok, transition to the services screen. Skip the screen entirely if `/etc/n-os-tr/services.conf` is missing *and* fallback is disabled via a `--no-services` flag (useful for tests that want only the identity flow).
+10. **Extend the QEMU integration test.** Script a toggle of one service, assert post-login that the chosen service is active and the others are not.
+11. **Extend the smoketest.** Add the preset-state check.
+12. **Doc passes.** Update [`plans/tui_login.md`](tui_login.md), [`plans/iso_architecture.md`](iso_architecture.md), [`docs/RUNNING.md`](../docs/RUNNING.md), and the in-ISO README.
+
+## 14. Open questions
+
+- **`tor.service` posture out of the box.** We ship the distro default `torrc` (SOCKS 9050 only, no relay). Do we want a second unit drop-in that enables the hidden-service onion for nginx when the user turns both on? Probably yes, as a follow-up; keep v1 minimal.
+- **GUI flavor.** Image flavor with a GUI will run the TUI on a text VT during early boot. Does it also allow the user to re-run the services menu later from the GUI? Probably yes via a GTK wrapper around the same `systemd_client` module, but that's post-v1.
+- **Per-user vs system.** All services here are system-level. "Which user's `ssh/authorized_keys` does `ssh` serve" is answered by the config-loader slot scheme. No per-user service toggles in v1.
+- **Should toggling the menu's state affect *default* behavior for future reboots?** Today: no; v1 is stateless. A future Nostr slot (§10) could record the last committed state and let the TUI show it as a "remembered" default, but that's explicit future work.
diff --git a/stack/nostr-id-tui/CMakeLists.txt b/stack/nostr-id-tui/CMakeLists.txt
index 5040e42b..3d2c61ef 100644
--- a/stack/nostr-id-tui/CMakeLists.txt
+++ b/stack/nostr-id-tui/CMakeLists.txt
@@ -18,6 +18,8 @@ endif()
add_executable(nostr-id-tui
src/main.c
+ src/services_conf.c
+ src/systemd_client.c
)
target_include_directories(nostr-id-tui PRIVATE
diff --git a/stack/nostr-id-tui/build-host/CMakeFiles/Makefile2 b/stack/nostr-id-tui/build-host/CMakeFiles/Makefile2
index b47d7281..b92caa74 100644
--- a/stack/nostr-id-tui/build-host/CMakeFiles/Makefile2
+++ b/stack/nostr-id-tui/build-host/CMakeFiles/Makefile2
@@ -85,12 +85,12 @@ clean: CMakeFiles/nostr-id-tui.dir/clean
CMakeFiles/nostr-id-tui.dir/all:
$(MAKE) $(MAKESILENT) -f CMakeFiles/nostr-id-tui.dir/build.make CMakeFiles/nostr-id-tui.dir/depend
$(MAKE) $(MAKESILENT) -f CMakeFiles/nostr-id-tui.dir/build.make CMakeFiles/nostr-id-tui.dir/build
- @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/home/user/lt/n_os_tr/stack/nostr-id-tui/build-host/CMakeFiles --progress-num=1,2 "Built target nostr-id-tui"
+ @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/home/user/lt/n_os_tr/stack/nostr-id-tui/build-host/CMakeFiles --progress-num=1,2,3,4 "Built target nostr-id-tui"
.PHONY : CMakeFiles/nostr-id-tui.dir/all
# Build rule for subdir invocation for target.
CMakeFiles/nostr-id-tui.dir/rule: cmake_check_build_system
- $(CMAKE_COMMAND) -E cmake_progress_start /home/user/lt/n_os_tr/stack/nostr-id-tui/build-host/CMakeFiles 2
+ $(CMAKE_COMMAND) -E cmake_progress_start /home/user/lt/n_os_tr/stack/nostr-id-tui/build-host/CMakeFiles 4
$(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 CMakeFiles/nostr-id-tui.dir/all
$(CMAKE_COMMAND) -E cmake_progress_start /home/user/lt/n_os_tr/stack/nostr-id-tui/build-host/CMakeFiles 0
.PHONY : CMakeFiles/nostr-id-tui.dir/rule
@@ -102,7 +102,7 @@ nostr-id-tui: CMakeFiles/nostr-id-tui.dir/rule
# codegen rule for target.
CMakeFiles/nostr-id-tui.dir/codegen:
$(MAKE) $(MAKESILENT) -f CMakeFiles/nostr-id-tui.dir/build.make CMakeFiles/nostr-id-tui.dir/codegen
- @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/home/user/lt/n_os_tr/stack/nostr-id-tui/build-host/CMakeFiles --progress-num=1,2 "Finished codegen for target nostr-id-tui"
+ @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/home/user/lt/n_os_tr/stack/nostr-id-tui/build-host/CMakeFiles --progress-num=1,2,3,4 "Finished codegen for target nostr-id-tui"
.PHONY : CMakeFiles/nostr-id-tui.dir/codegen
# clean rule for target.
diff --git a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/DependInfo.cmake b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/DependInfo.cmake
index 08ff9f6e..30e2f271 100644
--- a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/DependInfo.cmake
+++ b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/DependInfo.cmake
@@ -9,6 +9,8 @@ set(CMAKE_DEPENDS_LANGUAGES
# The set of dependency files which are needed:
set(CMAKE_DEPENDS_DEPENDENCY_FILES
"/home/user/lt/n_os_tr/stack/nostr-id-tui/src/main.c" "CMakeFiles/nostr-id-tui.dir/src/main.c.o" "gcc" "CMakeFiles/nostr-id-tui.dir/src/main.c.o.d"
+ "/home/user/lt/n_os_tr/stack/nostr-id-tui/src/services_conf.c" "CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o" "gcc" "CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o.d"
+ "/home/user/lt/n_os_tr/stack/nostr-id-tui/src/systemd_client.c" "CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o" "gcc" "CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o.d"
"" "nostr-id-tui" "gcc" "CMakeFiles/nostr-id-tui.dir/link.d"
)
diff --git a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/build.make b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/build.make
index 8bc426e5..901e6df6 100644
--- a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/build.make
+++ b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/build.make
@@ -86,21 +86,53 @@ CMakeFiles/nostr-id-tui.dir/src/main.c.s: cmake_force
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Compiling C source to assembly CMakeFiles/nostr-id-tui.dir/src/main.c.s"
/usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -S /home/user/lt/n_os_tr/stack/nostr-id-tui/src/main.c -o CMakeFiles/nostr-id-tui.dir/src/main.c.s
+CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o: CMakeFiles/nostr-id-tui.dir/flags.make
+CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o: /home/user/lt/n_os_tr/stack/nostr-id-tui/src/services_conf.c
+CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o: CMakeFiles/nostr-id-tui.dir/compiler_depend.ts
+ @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --progress-dir=/home/user/lt/n_os_tr/stack/nostr-id-tui/build-host/CMakeFiles --progress-num=$(CMAKE_PROGRESS_2) "Building C object CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o"
+ /usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -MD -MT CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o -MF CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o.d -o CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o -c /home/user/lt/n_os_tr/stack/nostr-id-tui/src/services_conf.c
+
+CMakeFiles/nostr-id-tui.dir/src/services_conf.c.i: cmake_force
+ @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Preprocessing C source to CMakeFiles/nostr-id-tui.dir/src/services_conf.c.i"
+ /usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -E /home/user/lt/n_os_tr/stack/nostr-id-tui/src/services_conf.c > CMakeFiles/nostr-id-tui.dir/src/services_conf.c.i
+
+CMakeFiles/nostr-id-tui.dir/src/services_conf.c.s: cmake_force
+ @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Compiling C source to assembly CMakeFiles/nostr-id-tui.dir/src/services_conf.c.s"
+ /usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -S /home/user/lt/n_os_tr/stack/nostr-id-tui/src/services_conf.c -o CMakeFiles/nostr-id-tui.dir/src/services_conf.c.s
+
+CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o: CMakeFiles/nostr-id-tui.dir/flags.make
+CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o: /home/user/lt/n_os_tr/stack/nostr-id-tui/src/systemd_client.c
+CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o: CMakeFiles/nostr-id-tui.dir/compiler_depend.ts
+ @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --progress-dir=/home/user/lt/n_os_tr/stack/nostr-id-tui/build-host/CMakeFiles --progress-num=$(CMAKE_PROGRESS_3) "Building C object CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o"
+ /usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -MD -MT CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o -MF CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o.d -o CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o -c /home/user/lt/n_os_tr/stack/nostr-id-tui/src/systemd_client.c
+
+CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.i: cmake_force
+ @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Preprocessing C source to CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.i"
+ /usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -E /home/user/lt/n_os_tr/stack/nostr-id-tui/src/systemd_client.c > CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.i
+
+CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.s: cmake_force
+ @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Compiling C source to assembly CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.s"
+ /usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -S /home/user/lt/n_os_tr/stack/nostr-id-tui/src/systemd_client.c -o CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.s
+
# Object files for target nostr-id-tui
nostr__id__tui_OBJECTS = \
-"CMakeFiles/nostr-id-tui.dir/src/main.c.o"
+"CMakeFiles/nostr-id-tui.dir/src/main.c.o" \
+"CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o" \
+"CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o"
# External object files for target nostr-id-tui
nostr__id__tui_EXTERNAL_OBJECTS =
nostr-id-tui: CMakeFiles/nostr-id-tui.dir/src/main.c.o
+nostr-id-tui: CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o
+nostr-id-tui: CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o
nostr-id-tui: CMakeFiles/nostr-id-tui.dir/build.make
nostr-id-tui: CMakeFiles/nostr-id-tui.dir/compiler_depend.ts
nostr-id-tui: /home/user/lt/n_os_tr/stack/nostr-id-tui/../../includes/nostr_core_lib/libnostr_core_x64.a
nostr-id-tui: /usr/lib/x86_64-linux-gnu/libcurses.so
nostr-id-tui: /usr/lib/x86_64-linux-gnu/libform.so
nostr-id-tui: CMakeFiles/nostr-id-tui.dir/link.txt
- @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --bold --progress-dir=/home/user/lt/n_os_tr/stack/nostr-id-tui/build-host/CMakeFiles --progress-num=$(CMAKE_PROGRESS_2) "Linking C executable nostr-id-tui"
+ @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --bold --progress-dir=/home/user/lt/n_os_tr/stack/nostr-id-tui/build-host/CMakeFiles --progress-num=$(CMAKE_PROGRESS_4) "Linking C executable nostr-id-tui"
$(CMAKE_COMMAND) -E cmake_link_script CMakeFiles/nostr-id-tui.dir/link.txt --verbose=$(VERBOSE)
# Rule to build all files generated by this target.
diff --git a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/cmake_clean.cmake b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/cmake_clean.cmake
index 2f33a374..a34ab38d 100644
--- a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/cmake_clean.cmake
+++ b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/cmake_clean.cmake
@@ -2,6 +2,10 @@ file(REMOVE_RECURSE
"CMakeFiles/nostr-id-tui.dir/link.d"
"CMakeFiles/nostr-id-tui.dir/src/main.c.o"
"CMakeFiles/nostr-id-tui.dir/src/main.c.o.d"
+ "CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o"
+ "CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o.d"
+ "CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o"
+ "CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o.d"
"nostr-id-tui"
"nostr-id-tui.pdb"
)
diff --git a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/compiler_depend.internal b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/compiler_depend.internal
index d9f3dac4..369bea3e 100644
--- a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/compiler_depend.internal
+++ b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/compiler_depend.internal
@@ -52,12 +52,11 @@ CMakeFiles/nostr-id-tui.dir/src/main.c.o
/usr/include/x86_64-linux-gnu/bits/floatn.h
/usr/include/x86_64-linux-gnu/bits/libc-header-start.h
/usr/include/x86_64-linux-gnu/bits/long-double.h
+ /usr/include/x86_64-linux-gnu/bits/select.h
/usr/include/x86_64-linux-gnu/bits/stdint-intn.h
/usr/include/x86_64-linux-gnu/bits/stdint-least.h
/usr/include/x86_64-linux-gnu/bits/stdint-uintn.h
- /usr/include/x86_64-linux-gnu/bits/stdio.h
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h
- /usr/include/x86_64-linux-gnu/bits/stdlib-bsearch.h
/usr/include/x86_64-linux-gnu/bits/stdlib-float.h
/usr/include/x86_64-linux-gnu/bits/time.h
/usr/include/x86_64-linux-gnu/bits/time64.h
@@ -68,8 +67,11 @@ CMakeFiles/nostr-id-tui.dir/src/main.c.o
/usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h
/usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h
/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h
+ /usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h
/usr/include/x86_64-linux-gnu/bits/types/clock_t.h
+ /usr/include/x86_64-linux-gnu/bits/types/sigset_t.h
/usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h
+ /usr/include/x86_64-linux-gnu/bits/types/struct_timeval.h
/usr/include/x86_64-linux-gnu/bits/types/struct_tm.h
/usr/include/x86_64-linux-gnu/bits/types/time_t.h
/usr/include/x86_64-linux-gnu/bits/typesizes.h
@@ -78,11 +80,93 @@ CMakeFiles/nostr-id-tui.dir/src/main.c.o
/usr/include/x86_64-linux-gnu/gnu/stubs-64.h
/usr/include/x86_64-linux-gnu/gnu/stubs.h
/usr/include/x86_64-linux-gnu/sys/cdefs.h
+ /usr/include/x86_64-linux-gnu/sys/select.h
+ /usr/include/x86_64-linux-gnu/sys/time.h
/usr/lib/gcc/x86_64-linux-gnu/14/include/stdarg.h
/usr/lib/gcc/x86_64-linux-gnu/14/include/stdbool.h
/usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h
/usr/lib/gcc/x86_64-linux-gnu/14/include/stdint.h
+CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o
+ /home/user/lt/n_os_tr/stack/nostr-id-tui/src/services_conf.c
+ /home/user/lt/n_os_tr/stack/nostr-id-tui/include/n_os_tr_tui.h
+ /usr/include/ctype.h
+ /usr/include/features-time64.h
+ /usr/include/features.h
+ /usr/include/stdc-predef.h
+ /usr/include/stdio.h
+ /usr/include/stdlib.h
+ /usr/include/string.h
+ /usr/include/x86_64-linux-gnu/bits/endian.h
+ /usr/include/x86_64-linux-gnu/bits/endianness.h
+ /usr/include/x86_64-linux-gnu/bits/floatn-common.h
+ /usr/include/x86_64-linux-gnu/bits/floatn.h
+ /usr/include/x86_64-linux-gnu/bits/libc-header-start.h
+ /usr/include/x86_64-linux-gnu/bits/long-double.h
+ /usr/include/x86_64-linux-gnu/bits/stdio_lim.h
+ /usr/include/x86_64-linux-gnu/bits/stdlib-float.h
+ /usr/include/x86_64-linux-gnu/bits/time64.h
+ /usr/include/x86_64-linux-gnu/bits/timesize.h
+ /usr/include/x86_64-linux-gnu/bits/types.h
+ /usr/include/x86_64-linux-gnu/bits/types/FILE.h
+ /usr/include/x86_64-linux-gnu/bits/types/__FILE.h
+ /usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h
+ /usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h
+ /usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h
+ /usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h
+ /usr/include/x86_64-linux-gnu/bits/typesizes.h
+ /usr/include/x86_64-linux-gnu/bits/wordsize.h
+ /usr/include/x86_64-linux-gnu/gnu/stubs-64.h
+ /usr/include/x86_64-linux-gnu/gnu/stubs.h
+ /usr/include/x86_64-linux-gnu/sys/cdefs.h
+ /usr/lib/gcc/x86_64-linux-gnu/14/include/stdarg.h
+ /usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h
+
+CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o
+ /home/user/lt/n_os_tr/stack/nostr-id-tui/src/systemd_client.c
+ /home/user/lt/n_os_tr/stack/nostr-id-tui/include/n_os_tr_tui.h
+ /usr/include/asm-generic/errno-base.h
+ /usr/include/asm-generic/errno.h
+ /usr/include/errno.h
+ /usr/include/features-time64.h
+ /usr/include/features.h
+ /usr/include/linux/errno.h
+ /usr/include/stdc-predef.h
+ /usr/include/stdio.h
+ /usr/include/stdlib.h
+ /usr/include/string.h
+ /usr/include/unistd.h
+ /usr/include/x86_64-linux-gnu/bits/confname.h
+ /usr/include/x86_64-linux-gnu/bits/errno.h
+ /usr/include/x86_64-linux-gnu/bits/floatn-common.h
+ /usr/include/x86_64-linux-gnu/bits/floatn.h
+ /usr/include/x86_64-linux-gnu/bits/libc-header-start.h
+ /usr/include/x86_64-linux-gnu/bits/long-double.h
+ /usr/include/x86_64-linux-gnu/bits/posix_opt.h
+ /usr/include/x86_64-linux-gnu/bits/stdio_lim.h
+ /usr/include/x86_64-linux-gnu/bits/stdlib-float.h
+ /usr/include/x86_64-linux-gnu/bits/time64.h
+ /usr/include/x86_64-linux-gnu/bits/timesize.h
+ /usr/include/x86_64-linux-gnu/bits/types.h
+ /usr/include/x86_64-linux-gnu/bits/types/FILE.h
+ /usr/include/x86_64-linux-gnu/bits/types/__FILE.h
+ /usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h
+ /usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h
+ /usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h
+ /usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h
+ /usr/include/x86_64-linux-gnu/bits/typesizes.h
+ /usr/include/x86_64-linux-gnu/bits/unistd_ext.h
+ /usr/include/x86_64-linux-gnu/bits/waitflags.h
+ /usr/include/x86_64-linux-gnu/bits/waitstatus.h
+ /usr/include/x86_64-linux-gnu/bits/wordsize.h
+ /usr/include/x86_64-linux-gnu/gnu/stubs-64.h
+ /usr/include/x86_64-linux-gnu/gnu/stubs.h
+ /usr/include/x86_64-linux-gnu/sys/cdefs.h
+ /usr/include/x86_64-linux-gnu/sys/wait.h
+ /usr/lib/gcc/x86_64-linux-gnu/14/include/stdarg.h
+ /usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h
+ /usr/lib/linux/uapi/x86/asm/errno.h
+
nostr-id-tui
/home/user/lt/n_os_tr/includes/nostr_core_lib/libnostr_core_x64.a
/lib/x86_64-linux-gnu/libc.so.6
@@ -112,4 +196,6 @@ nostr-id-tui
/usr/lib/x86_64-linux-gnu/libc_nonshared.a
/usr/lib/x86_64-linux-gnu/libform.so
/home/user/lt/n_os_tr/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/main.c.o
+ /home/user/lt/n_os_tr/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o
+ /home/user/lt/n_os_tr/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o
diff --git a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/compiler_depend.make b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/compiler_depend.make
index cfd084e6..dac911f3 100644
--- a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/compiler_depend.make
+++ b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/compiler_depend.make
@@ -51,12 +51,11 @@ CMakeFiles/nostr-id-tui.dir/src/main.c.o: /home/user/lt/n_os_tr/stack/nostr-id-t
/usr/include/x86_64-linux-gnu/bits/floatn.h \
/usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
/usr/include/x86_64-linux-gnu/bits/long-double.h \
+ /usr/include/x86_64-linux-gnu/bits/select.h \
/usr/include/x86_64-linux-gnu/bits/stdint-intn.h \
/usr/include/x86_64-linux-gnu/bits/stdint-least.h \
/usr/include/x86_64-linux-gnu/bits/stdint-uintn.h \
- /usr/include/x86_64-linux-gnu/bits/stdio.h \
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
- /usr/include/x86_64-linux-gnu/bits/stdlib-bsearch.h \
/usr/include/x86_64-linux-gnu/bits/stdlib-float.h \
/usr/include/x86_64-linux-gnu/bits/time.h \
/usr/include/x86_64-linux-gnu/bits/time64.h \
@@ -67,8 +66,11 @@ CMakeFiles/nostr-id-tui.dir/src/main.c.o: /home/user/lt/n_os_tr/stack/nostr-id-t
/usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h \
/usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h \
/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h \
/usr/include/x86_64-linux-gnu/bits/types/clock_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/sigset_t.h \
/usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h \
+ /usr/include/x86_64-linux-gnu/bits/types/struct_timeval.h \
/usr/include/x86_64-linux-gnu/bits/types/struct_tm.h \
/usr/include/x86_64-linux-gnu/bits/types/time_t.h \
/usr/include/x86_64-linux-gnu/bits/typesizes.h \
@@ -77,11 +79,91 @@ CMakeFiles/nostr-id-tui.dir/src/main.c.o: /home/user/lt/n_os_tr/stack/nostr-id-t
/usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
/usr/include/x86_64-linux-gnu/gnu/stubs.h \
/usr/include/x86_64-linux-gnu/sys/cdefs.h \
+ /usr/include/x86_64-linux-gnu/sys/select.h \
+ /usr/include/x86_64-linux-gnu/sys/time.h \
/usr/lib/gcc/x86_64-linux-gnu/14/include/stdarg.h \
/usr/lib/gcc/x86_64-linux-gnu/14/include/stdbool.h \
/usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h \
/usr/lib/gcc/x86_64-linux-gnu/14/include/stdint.h
+CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o: /home/user/lt/n_os_tr/stack/nostr-id-tui/src/services_conf.c \
+ /home/user/lt/n_os_tr/stack/nostr-id-tui/include/n_os_tr_tui.h \
+ /usr/include/ctype.h \
+ /usr/include/features-time64.h \
+ /usr/include/features.h \
+ /usr/include/stdc-predef.h \
+ /usr/include/stdio.h \
+ /usr/include/stdlib.h \
+ /usr/include/string.h \
+ /usr/include/x86_64-linux-gnu/bits/endian.h \
+ /usr/include/x86_64-linux-gnu/bits/endianness.h \
+ /usr/include/x86_64-linux-gnu/bits/floatn-common.h \
+ /usr/include/x86_64-linux-gnu/bits/floatn.h \
+ /usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
+ /usr/include/x86_64-linux-gnu/bits/long-double.h \
+ /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
+ /usr/include/x86_64-linux-gnu/bits/stdlib-float.h \
+ /usr/include/x86_64-linux-gnu/bits/time64.h \
+ /usr/include/x86_64-linux-gnu/bits/timesize.h \
+ /usr/include/x86_64-linux-gnu/bits/types.h \
+ /usr/include/x86_64-linux-gnu/bits/types/FILE.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__FILE.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h \
+ /usr/include/x86_64-linux-gnu/bits/typesizes.h \
+ /usr/include/x86_64-linux-gnu/bits/wordsize.h \
+ /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
+ /usr/include/x86_64-linux-gnu/gnu/stubs.h \
+ /usr/include/x86_64-linux-gnu/sys/cdefs.h \
+ /usr/lib/gcc/x86_64-linux-gnu/14/include/stdarg.h \
+ /usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h
+
+CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o: /home/user/lt/n_os_tr/stack/nostr-id-tui/src/systemd_client.c \
+ /home/user/lt/n_os_tr/stack/nostr-id-tui/include/n_os_tr_tui.h \
+ /usr/include/asm-generic/errno-base.h \
+ /usr/include/asm-generic/errno.h \
+ /usr/include/errno.h \
+ /usr/include/features-time64.h \
+ /usr/include/features.h \
+ /usr/include/linux/errno.h \
+ /usr/include/stdc-predef.h \
+ /usr/include/stdio.h \
+ /usr/include/stdlib.h \
+ /usr/include/string.h \
+ /usr/include/unistd.h \
+ /usr/include/x86_64-linux-gnu/bits/confname.h \
+ /usr/include/x86_64-linux-gnu/bits/errno.h \
+ /usr/include/x86_64-linux-gnu/bits/floatn-common.h \
+ /usr/include/x86_64-linux-gnu/bits/floatn.h \
+ /usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
+ /usr/include/x86_64-linux-gnu/bits/long-double.h \
+ /usr/include/x86_64-linux-gnu/bits/posix_opt.h \
+ /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
+ /usr/include/x86_64-linux-gnu/bits/stdlib-float.h \
+ /usr/include/x86_64-linux-gnu/bits/time64.h \
+ /usr/include/x86_64-linux-gnu/bits/timesize.h \
+ /usr/include/x86_64-linux-gnu/bits/types.h \
+ /usr/include/x86_64-linux-gnu/bits/types/FILE.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__FILE.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h \
+ /usr/include/x86_64-linux-gnu/bits/typesizes.h \
+ /usr/include/x86_64-linux-gnu/bits/unistd_ext.h \
+ /usr/include/x86_64-linux-gnu/bits/waitflags.h \
+ /usr/include/x86_64-linux-gnu/bits/waitstatus.h \
+ /usr/include/x86_64-linux-gnu/bits/wordsize.h \
+ /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
+ /usr/include/x86_64-linux-gnu/gnu/stubs.h \
+ /usr/include/x86_64-linux-gnu/sys/cdefs.h \
+ /usr/include/x86_64-linux-gnu/sys/wait.h \
+ /usr/lib/gcc/x86_64-linux-gnu/14/include/stdarg.h \
+ /usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h \
+ /usr/lib/linux/uapi/x86/asm/errno.h
+
nostr-id-tui: /home/user/lt/n_os_tr/includes/nostr_core_lib/libnostr_core_x64.a \
/lib/x86_64-linux-gnu/libc.so.6 \
/lib/x86_64-linux-gnu/libm.so.6 \
@@ -109,219 +191,263 @@ nostr-id-tui: /home/user/lt/n_os_tr/includes/nostr_core_lib/libnostr_core_x64.a
/usr/lib/gcc/x86_64-linux-gnu/14/libgcc_s.so \
/usr/lib/x86_64-linux-gnu/libc_nonshared.a \
/usr/lib/x86_64-linux-gnu/libform.so \
- CMakeFiles/nostr-id-tui.dir/src/main.c.o
+ CMakeFiles/nostr-id-tui.dir/src/main.c.o \
+ CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o \
+ CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o
-/usr/lib/x86_64-linux-gnu/libform.so:
-
-/usr/lib/x86_64-linux-gnu/libc_nonshared.a:
-
-/usr/lib/gcc/x86_64-linux-gnu/14/libgcc.a:
-
-/usr/lib/gcc/x86_64-linux-gnu/14/crtendS.o:
-
-/usr/lib/x86_64-linux-gnu/libz.so:
-
-/usr/lib/x86_64-linux-gnu/libtinfo.so:
-
-/usr/lib/x86_64-linux-gnu/libssl.so:
-
-/usr/lib/x86_64-linux-gnu/libpthread.a:
-
-/usr/lib/x86_64-linux-gnu/libncurses.so.6:
-
-/usr/lib/x86_64-linux-gnu/libdl.a:
-
-/usr/lib/x86_64-linux-gnu/libcrypto.so:
-
-/usr/lib/x86_64-linux-gnu/libc.so:
-
-/usr/lib/x86_64-linux-gnu/crti.o:
-
-/usr/lib/x86_64-linux-gnu/Scrt1.o:
-
-/lib/x86_64-linux-gnu/libmvec.so.1:
-
-/usr/lib/gcc/x86_64-linux-gnu/14/include/stdint.h:
-
-/usr/lib/gcc/x86_64-linux-gnu/14/include/stdbool.h:
-
-/usr/include/x86_64-linux-gnu/sys/cdefs.h:
-
-/usr/include/x86_64-linux-gnu/gnu/stubs-64.h:
-
-/usr/include/x86_64-linux-gnu/bits/wordsize.h:
-
-/usr/include/x86_64-linux-gnu/bits/typesizes.h:
-
-CMakeFiles/nostr-id-tui.dir/src/main.c.o:
-
-/usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h:
-
-/usr/lib/x86_64-linux-gnu/libm.so:
-
-/lib/x86_64-linux-gnu/libc.so.6:
-
-/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h:
-
-/usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h:
-
-/usr/include/x86_64-linux-gnu/bits/types.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nostr_secure_memory.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nostr_fips_ipv6.h:
-
-/usr/lib/gcc/x86_64-linux-gnu/14/include/stdarg.h:
-
-/usr/include/ctype.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nostr_bip39_ui.h:
-
-/usr/include/curses.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip060.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/cjson/cJSON.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip021.h:
-
-/usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip013.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/request_validator.h:
-
-/usr/include/x86_64-linux-gnu/bits/libc-header-start.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip004.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nostr_core.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip061.h:
-
-/usr/include/x86_64-linux-gnu/bits/stdint-least.h:
-
-/usr/lib/x86_64-linux-gnu/libcurses.so:
-
-/usr/include/x86_64-linux-gnu/bits/time.h:
-
-/usr/include/x86_64-linux-gnu/gnu/stubs.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/blossom_client.h:
-
-/usr/lib/x86_64-linux-gnu/libsecp256k1.so:
-
-/usr/include/x86_64-linux-gnu/bits/floatn-common.h:
-
-/usr/include/x86_64-linux-gnu/bits/wchar.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nostr_common.h:
-
-/home/user/lt/n_os_tr/stack/nostr-id-tui/src/main.c:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nostr_http.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip046.h:
-
-/lib/x86_64-linux-gnu/libm.so.6:
-
-/usr/include/x86_64-linux-gnu/bits/types/time_t.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip006.h:
-
-/usr/include/x86_64-linux-gnu/bits/types/clock_t.h:
-
-/usr/include/x86_64-linux-gnu/bits/types/__FILE.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/cashu_mint.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip011.h:
-
-/lib64/ld-linux-x86-64.so.2:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip042.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip005.h:
-
-/usr/include/x86_64-linux-gnu/bits/long-double.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip019.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip003.h:
-
-/usr/include/x86_64-linux-gnu/bits/floatn.h:
-
-/home/user/lt/n_os_tr/stack/nostr-id-tui/include/n_os_tr_tui.h:
-
-/usr/lib/x86_64-linux-gnu/crtn.o:
-
-/usr/include/unctrl.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/utils.h:
-
-/usr/include/x86_64-linux-gnu/bits/time64.h:
-
-/usr/include/features-time64.h:
-
-/usr/include/features.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip017.h:
-
-/usr/include/ncurses_dll.h:
-
-/usr/lib/gcc/x86_64-linux-gnu/14/libgcc_s.so:
-
-/usr/include/stdint.h:
-
-/usr/include/x86_64-linux-gnu/bits/timesize.h:
-
-/usr/include/stdio.h:
-
-/usr/include/stdlib.h:
-
-/usr/lib/x86_64-linux-gnu/libgcc_s.so.1:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nostr_log.h:
-
-/usr/include/x86_64-linux-gnu/bits/endian.h:
-
-/usr/include/string.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/libnostr_core_x64.a:
-
-/usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip044.h:
-
-/usr/include/x86_64-linux-gnu/bits/stdint-uintn.h:
-
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip001.h:
-
-/usr/include/time.h:
-
-/usr/include/x86_64-linux-gnu/bits/stdio_lim.h:
-
-/usr/include/sqlite3.h:
+CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o:
/usr/include/x86_64-linux-gnu/bits/endianness.h:
+/usr/include/sqlite3.h:
+
/usr/include/x86_64-linux-gnu/bits/stdint-intn.h:
-/usr/include/x86_64-linux-gnu/bits/types/struct_tm.h:
+/usr/include/x86_64-linux-gnu/bits/types.h:
-/usr/include/x86_64-linux-gnu/bits/types/FILE.h:
+/usr/include/time.h:
-/usr/include/x86_64-linux-gnu/bits/stdio.h:
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip001.h:
-/usr/lib/gcc/x86_64-linux-gnu/14/crtbeginS.o:
+/usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h:
-/usr/lib/x86_64-linux-gnu/libcurl.so:
+/home/user/lt/n_os_tr/includes/nostr_core_lib/libnostr_core_x64.a:
-/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip059.h:
+/usr/include/string.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nostr_log.h:
+
+/usr/lib/x86_64-linux-gnu/libgcc_s.so.1:
+
+/usr/include/x86_64-linux-gnu/bits/timesize.h:
+
+/usr/include/stdint.h:
+
+/usr/lib/gcc/x86_64-linux-gnu/14/libgcc_s.so:
/usr/include/x86_64-linux-gnu/bits/stdlib-float.h:
/usr/include/stdc-predef.h:
-/usr/include/x86_64-linux-gnu/bits/stdlib-bsearch.h:
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip059.h:
+
+/usr/lib/x86_64-linux-gnu/libcurl.so:
+
+/usr/include/ncurses_dll.h:
+
+/usr/include/features.h:
+
+/usr/include/features-time64.h:
+
+/usr/include/x86_64-linux-gnu/bits/select.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/utils.h:
+
+/usr/include/x86_64-linux-gnu/bits/types/sigset_t.h:
+
+/usr/include/unctrl.h:
+
+/usr/lib/x86_64-linux-gnu/crtn.o:
+
+/home/user/lt/n_os_tr/stack/nostr-id-tui/include/n_os_tr_tui.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip017.h:
+
+/usr/include/x86_64-linux-gnu/bits/floatn.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip003.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip019.h:
+
+/usr/include/x86_64-linux-gnu/bits/long-double.h:
+
+/usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip005.h:
+
+/usr/include/x86_64-linux-gnu/bits/errno.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip011.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/cashu_mint.h:
+
+/usr/include/x86_64-linux-gnu/bits/types/__FILE.h:
+
+/usr/include/x86_64-linux-gnu/bits/types/clock_t.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip006.h:
+
+/lib64/ld-linux-x86-64.so.2:
+
+/usr/include/x86_64-linux-gnu/bits/types/time_t.h:
+
+/lib/x86_64-linux-gnu/libm.so.6:
+
+/home/user/lt/n_os_tr/stack/nostr-id-tui/src/main.c:
+
+/usr/include/x86_64-linux-gnu/bits/wchar.h:
+
+/usr/include/x86_64-linux-gnu/bits/floatn-common.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nostr_http.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/blossom_client.h:
+
+/usr/include/x86_64-linux-gnu/bits/endian.h:
+
+/usr/include/x86_64-linux-gnu/gnu/stubs.h:
+
+/usr/include/x86_64-linux-gnu/bits/time.h:
+
+/usr/lib/x86_64-linux-gnu/libcurses.so:
+
+/usr/include/x86_64-linux-gnu/bits/stdint-least.h:
+
+/usr/include/x86_64-linux-gnu/bits/time64.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip004.h:
+
+/usr/include/stdio.h:
+
+/home/user/lt/n_os_tr/stack/nostr-id-tui/src/services_conf.c:
+
+/usr/include/x86_64-linux-gnu/bits/libc-header-start.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/request_validator.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip013.h:
+
+/usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip021.h:
+
+/usr/include/x86_64-linux-gnu/bits/waitstatus.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/cjson/cJSON.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip060.h:
+
+/usr/include/curses.h:
+
+/usr/lib/gcc/x86_64-linux-gnu/14/crtbeginS.o:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nostr_bip39_ui.h:
+
+/usr/include/ctype.h:
+
+/usr/lib/gcc/x86_64-linux-gnu/14/include/stdarg.h:
+
+/usr/include/stdlib.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nostr_fips_ipv6.h:
+
+/home/user/lt/n_os_tr/stack/nostr-id-tui/src/systemd_client.c:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip044.h:
+
+/usr/include/x86_64-linux-gnu/bits/types/struct_tm.h:
+
+/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h:
+
+/lib/x86_64-linux-gnu/libc.so.6:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nostr_common.h:
+
+/usr/lib/x86_64-linux-gnu/libm.so:
+
+/usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h:
+
+/usr/include/x86_64-linux-gnu/sys/cdefs.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip046.h:
+
+/usr/include/x86_64-linux-gnu/bits/typesizes.h:
+
+/usr/include/x86_64-linux-gnu/bits/wordsize.h:
+
+/usr/include/x86_64-linux-gnu/gnu/stubs-64.h:
+
+/usr/include/x86_64-linux-gnu/sys/time.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip042.h:
+
+/usr/lib/gcc/x86_64-linux-gnu/14/include/stdbool.h:
+
+/usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h:
+
+/usr/include/x86_64-linux-gnu/sys/wait.h:
+
+/usr/lib/gcc/x86_64-linux-gnu/14/include/stdint.h:
+
+/usr/include/asm-generic/errno-base.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nip061.h:
+
+/usr/lib/x86_64-linux-gnu/libncurses.so.6:
+
+/usr/lib/x86_64-linux-gnu/libsecp256k1.so:
+
+/usr/include/asm-generic/errno.h:
+
+/usr/include/linux/errno.h:
+
+/usr/include/x86_64-linux-gnu/bits/stdint-uintn.h:
+
+/usr/include/x86_64-linux-gnu/sys/select.h:
+
+/usr/include/unistd.h:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nostr_secure_memory.h:
+
+/usr/include/x86_64-linux-gnu/bits/confname.h:
+
+/usr/include/x86_64-linux-gnu/bits/posix_opt.h:
+
+/usr/include/x86_64-linux-gnu/bits/waitflags.h:
+
+/usr/include/x86_64-linux-gnu/bits/types/struct_timeval.h:
+
+/usr/lib/linux/uapi/x86/asm/errno.h:
+
+/lib/x86_64-linux-gnu/libmvec.so.1:
+
+/usr/lib/x86_64-linux-gnu/Scrt1.o:
+
+/usr/lib/x86_64-linux-gnu/libc.so:
+
+CMakeFiles/nostr-id-tui.dir/src/main.c.o:
+
+/usr/lib/x86_64-linux-gnu/libcrypto.so:
+
+/usr/lib/x86_64-linux-gnu/libdl.a:
+
+/usr/lib/x86_64-linux-gnu/libpthread.a:
+
+/usr/lib/x86_64-linux-gnu/libssl.so:
+
+/home/user/lt/n_os_tr/includes/nostr_core_lib/nostr_core/nostr_core.h:
+
+/usr/include/errno.h:
+
+/usr/lib/x86_64-linux-gnu/libtinfo.so:
+
+/usr/lib/x86_64-linux-gnu/crti.o:
+
+/usr/lib/x86_64-linux-gnu/libz.so:
+
+/usr/include/x86_64-linux-gnu/bits/stdio_lim.h:
+
+/usr/include/x86_64-linux-gnu/bits/types/FILE.h:
+
+/usr/lib/gcc/x86_64-linux-gnu/14/crtendS.o:
+
+/usr/lib/gcc/x86_64-linux-gnu/14/libgcc.a:
+
+/usr/include/x86_64-linux-gnu/bits/unistd_ext.h:
+
+/usr/lib/x86_64-linux-gnu/libc_nonshared.a:
+
+/usr/lib/x86_64-linux-gnu/libform.so:
+
+CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o:
diff --git a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/link.d b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/link.d
index 17eaf57c..bd824f80 100644
--- a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/link.d
+++ b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/link.d
@@ -3,6 +3,8 @@ nostr-id-tui: \
/usr/lib/gcc/x86_64-linux-gnu/14/../../../x86_64-linux-gnu/crti.o \
/usr/lib/gcc/x86_64-linux-gnu/14/crtbeginS.o \
CMakeFiles/nostr-id-tui.dir/src/main.c.o \
+ CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o \
+ CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o \
/home/user/lt/n_os_tr/stack/nostr-id-tui/../../includes/nostr_core_lib/libnostr_core_x64.a \
/usr/lib/gcc/x86_64-linux-gnu/14/../../../x86_64-linux-gnu/libcurses.so \
/usr/lib/gcc/x86_64-linux-gnu/14/../../../x86_64-linux-gnu/libcurses.so \
@@ -52,6 +54,10 @@ nostr-id-tui: \
CMakeFiles/nostr-id-tui.dir/src/main.c.o:
+CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o:
+
+CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o:
+
/home/user/lt/n_os_tr/stack/nostr-id-tui/../../includes/nostr_core_lib/libnostr_core_x64.a:
/usr/lib/gcc/x86_64-linux-gnu/14/../../../x86_64-linux-gnu/libcurses.so:
diff --git a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/link.txt b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/link.txt
index 569f51d2..248577d5 100644
--- a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/link.txt
+++ b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/link.txt
@@ -1 +1 @@
-/usr/bin/cc -g -Wl,--dependency-file=CMakeFiles/nostr-id-tui.dir/link.d "CMakeFiles/nostr-id-tui.dir/src/main.c.o" -o nostr-id-tui /home/user/lt/n_os_tr/stack/nostr-id-tui/../../includes/nostr_core_lib/libnostr_core_x64.a -lcurses /usr/lib/x86_64-linux-gnu/libform.so -lsecp256k1 -lssl -lcrypto -lcurl -lz -lpthread -lm -ldl
+/usr/bin/cc -g -Wl,--dependency-file=CMakeFiles/nostr-id-tui.dir/link.d "CMakeFiles/nostr-id-tui.dir/src/main.c.o" "CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o" "CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o" -o nostr-id-tui /home/user/lt/n_os_tr/stack/nostr-id-tui/../../includes/nostr_core_lib/libnostr_core_x64.a -lcurses /usr/lib/x86_64-linux-gnu/libform.so -lsecp256k1 -lssl -lcrypto -lcurl -lz -lpthread -lm -ldl
diff --git a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/progress.make b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/progress.make
index abadeb0c..a69a57e8 100644
--- a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/progress.make
+++ b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/progress.make
@@ -1,3 +1,5 @@
CMAKE_PROGRESS_1 = 1
CMAKE_PROGRESS_2 = 2
+CMAKE_PROGRESS_3 = 3
+CMAKE_PROGRESS_4 = 4
diff --git a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/main.c.o b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/main.c.o
index 2d119fc9..7f3b5af8 100644
Binary files a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/main.c.o and b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/main.c.o differ
diff --git a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/main.c.o.d b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/main.c.o.d
index 99146bb6..0e77d3a1 100644
--- a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/main.c.o.d
+++ b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/main.c.o.d
@@ -34,6 +34,13 @@ CMakeFiles/nostr-id-tui.dir/src/main.c.o: \
/usr/lib/gcc/x86_64-linux-gnu/14/include/stdbool.h /usr/include/unctrl.h \
/usr/include/curses.h /usr/include/stdlib.h \
/usr/include/x86_64-linux-gnu/bits/stdlib-float.h /usr/include/string.h \
+ /usr/include/x86_64-linux-gnu/sys/time.h \
+ /usr/include/x86_64-linux-gnu/bits/types/time_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/struct_timeval.h \
+ /usr/include/x86_64-linux-gnu/sys/select.h \
+ /usr/include/x86_64-linux-gnu/bits/select.h \
+ /usr/include/x86_64-linux-gnu/bits/types/sigset_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h \
/home/user/lt/n_os_tr/stack/nostr-id-tui/include/n_os_tr_tui.h \
/home/user/lt/n_os_tr/stack/nostr-id-tui/../../includes/nostr_core_lib/nostr_core/nostr_core.h \
/home/user/lt/n_os_tr/stack/nostr-id-tui/../../includes/nostr_core_lib/nostr_core/nostr_common.h \
@@ -44,7 +51,6 @@ CMakeFiles/nostr-id-tui.dir/src/main.c.o: \
/home/user/lt/n_os_tr/stack/nostr-id-tui/../../includes/nostr_core_lib/nostr_core/nip001.h \
/usr/include/time.h /usr/include/x86_64-linux-gnu/bits/time.h \
/usr/include/x86_64-linux-gnu/bits/types/clock_t.h \
- /usr/include/x86_64-linux-gnu/bits/types/time_t.h \
/usr/include/x86_64-linux-gnu/bits/types/struct_tm.h \
/home/user/lt/n_os_tr/stack/nostr-id-tui/../../includes/nostr_core_lib/nostr_core/../cjson/cJSON.h \
/home/user/lt/n_os_tr/stack/nostr-id-tui/../../includes/nostr_core_lib/nostr_core/nip003.h \
diff --git a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o
new file mode 100644
index 00000000..6519e394
Binary files /dev/null and b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o differ
diff --git a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o.d b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o.d
new file mode 100644
index 00000000..2e51fcc7
--- /dev/null
+++ b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o.d
@@ -0,0 +1,29 @@
+CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o: \
+ /home/user/lt/n_os_tr/stack/nostr-id-tui/src/services_conf.c \
+ /usr/include/stdc-predef.h /usr/include/ctype.h /usr/include/features.h \
+ /usr/include/features-time64.h \
+ /usr/include/x86_64-linux-gnu/bits/wordsize.h \
+ /usr/include/x86_64-linux-gnu/bits/timesize.h \
+ /usr/include/x86_64-linux-gnu/sys/cdefs.h \
+ /usr/include/x86_64-linux-gnu/bits/long-double.h \
+ /usr/include/x86_64-linux-gnu/gnu/stubs.h \
+ /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
+ /usr/include/x86_64-linux-gnu/bits/types.h \
+ /usr/include/x86_64-linux-gnu/bits/typesizes.h \
+ /usr/include/x86_64-linux-gnu/bits/time64.h \
+ /usr/include/x86_64-linux-gnu/bits/endian.h \
+ /usr/include/x86_64-linux-gnu/bits/endianness.h /usr/include/stdio.h \
+ /usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
+ /usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h \
+ /usr/lib/gcc/x86_64-linux-gnu/14/include/stdarg.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__FILE.h \
+ /usr/include/x86_64-linux-gnu/bits/types/FILE.h \
+ /usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h \
+ /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
+ /usr/include/x86_64-linux-gnu/bits/floatn.h \
+ /usr/include/x86_64-linux-gnu/bits/floatn-common.h /usr/include/stdlib.h \
+ /usr/include/x86_64-linux-gnu/bits/stdlib-float.h /usr/include/string.h \
+ /home/user/lt/n_os_tr/stack/nostr-id-tui/include/n_os_tr_tui.h
diff --git a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o
new file mode 100644
index 00000000..56e3dc8b
Binary files /dev/null and b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o differ
diff --git a/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o.d b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o.d
new file mode 100644
index 00000000..b2a24994
--- /dev/null
+++ b/stack/nostr-id-tui/build-host/CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o.d
@@ -0,0 +1,36 @@
+CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o: \
+ /home/user/lt/n_os_tr/stack/nostr-id-tui/src/systemd_client.c \
+ /usr/include/stdc-predef.h /usr/include/errno.h /usr/include/features.h \
+ /usr/include/features-time64.h \
+ /usr/include/x86_64-linux-gnu/bits/wordsize.h \
+ /usr/include/x86_64-linux-gnu/bits/timesize.h \
+ /usr/include/x86_64-linux-gnu/sys/cdefs.h \
+ /usr/include/x86_64-linux-gnu/bits/long-double.h \
+ /usr/include/x86_64-linux-gnu/gnu/stubs.h \
+ /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
+ /usr/include/x86_64-linux-gnu/bits/errno.h /usr/include/linux/errno.h \
+ /usr/lib/linux/uapi/x86/asm/errno.h /usr/include/asm-generic/errno.h \
+ /usr/include/asm-generic/errno-base.h /usr/include/stdio.h \
+ /usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
+ /usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h \
+ /usr/lib/gcc/x86_64-linux-gnu/14/include/stdarg.h \
+ /usr/include/x86_64-linux-gnu/bits/types.h \
+ /usr/include/x86_64-linux-gnu/bits/typesizes.h \
+ /usr/include/x86_64-linux-gnu/bits/time64.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h \
+ /usr/include/x86_64-linux-gnu/bits/types/__FILE.h \
+ /usr/include/x86_64-linux-gnu/bits/types/FILE.h \
+ /usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h \
+ /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
+ /usr/include/x86_64-linux-gnu/bits/floatn.h \
+ /usr/include/x86_64-linux-gnu/bits/floatn-common.h /usr/include/stdlib.h \
+ /usr/include/x86_64-linux-gnu/bits/stdlib-float.h /usr/include/string.h \
+ /usr/include/x86_64-linux-gnu/sys/wait.h \
+ /usr/include/x86_64-linux-gnu/bits/waitflags.h \
+ /usr/include/x86_64-linux-gnu/bits/waitstatus.h /usr/include/unistd.h \
+ /usr/include/x86_64-linux-gnu/bits/posix_opt.h \
+ /usr/include/x86_64-linux-gnu/bits/confname.h \
+ /usr/include/x86_64-linux-gnu/bits/unistd_ext.h \
+ /home/user/lt/n_os_tr/stack/nostr-id-tui/include/n_os_tr_tui.h
diff --git a/stack/nostr-id-tui/build-host/CMakeFiles/progress.marks b/stack/nostr-id-tui/build-host/CMakeFiles/progress.marks
index 0cfbf088..b8626c4c 100644
--- a/stack/nostr-id-tui/build-host/CMakeFiles/progress.marks
+++ b/stack/nostr-id-tui/build-host/CMakeFiles/progress.marks
@@ -1 +1 @@
-2
+4
diff --git a/stack/nostr-id-tui/build-host/Makefile b/stack/nostr-id-tui/build-host/Makefile
index 249f5817..73cb8ca6 100644
--- a/stack/nostr-id-tui/build-host/Makefile
+++ b/stack/nostr-id-tui/build-host/Makefile
@@ -153,6 +153,54 @@ src/main.c.s:
$(MAKE) $(MAKESILENT) -f CMakeFiles/nostr-id-tui.dir/build.make CMakeFiles/nostr-id-tui.dir/src/main.c.s
.PHONY : src/main.c.s
+src/services_conf.o: src/services_conf.c.o
+.PHONY : src/services_conf.o
+
+# target to build an object file
+src/services_conf.c.o:
+ $(MAKE) $(MAKESILENT) -f CMakeFiles/nostr-id-tui.dir/build.make CMakeFiles/nostr-id-tui.dir/src/services_conf.c.o
+.PHONY : src/services_conf.c.o
+
+src/services_conf.i: src/services_conf.c.i
+.PHONY : src/services_conf.i
+
+# target to preprocess a source file
+src/services_conf.c.i:
+ $(MAKE) $(MAKESILENT) -f CMakeFiles/nostr-id-tui.dir/build.make CMakeFiles/nostr-id-tui.dir/src/services_conf.c.i
+.PHONY : src/services_conf.c.i
+
+src/services_conf.s: src/services_conf.c.s
+.PHONY : src/services_conf.s
+
+# target to generate assembly for a file
+src/services_conf.c.s:
+ $(MAKE) $(MAKESILENT) -f CMakeFiles/nostr-id-tui.dir/build.make CMakeFiles/nostr-id-tui.dir/src/services_conf.c.s
+.PHONY : src/services_conf.c.s
+
+src/systemd_client.o: src/systemd_client.c.o
+.PHONY : src/systemd_client.o
+
+# target to build an object file
+src/systemd_client.c.o:
+ $(MAKE) $(MAKESILENT) -f CMakeFiles/nostr-id-tui.dir/build.make CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.o
+.PHONY : src/systemd_client.c.o
+
+src/systemd_client.i: src/systemd_client.c.i
+.PHONY : src/systemd_client.i
+
+# target to preprocess a source file
+src/systemd_client.c.i:
+ $(MAKE) $(MAKESILENT) -f CMakeFiles/nostr-id-tui.dir/build.make CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.i
+.PHONY : src/systemd_client.c.i
+
+src/systemd_client.s: src/systemd_client.c.s
+.PHONY : src/systemd_client.s
+
+# target to generate assembly for a file
+src/systemd_client.c.s:
+ $(MAKE) $(MAKESILENT) -f CMakeFiles/nostr-id-tui.dir/build.make CMakeFiles/nostr-id-tui.dir/src/systemd_client.c.s
+.PHONY : src/systemd_client.c.s
+
# Help Target
help:
@echo "The following are some of the valid targets for this Makefile:"
@@ -165,6 +213,12 @@ help:
@echo "... src/main.o"
@echo "... src/main.i"
@echo "... src/main.s"
+ @echo "... src/services_conf.o"
+ @echo "... src/services_conf.i"
+ @echo "... src/services_conf.s"
+ @echo "... src/systemd_client.o"
+ @echo "... src/systemd_client.i"
+ @echo "... src/systemd_client.s"
.PHONY : help
diff --git a/stack/nostr-id-tui/build-host/nostr-id-tui b/stack/nostr-id-tui/build-host/nostr-id-tui
index 93e52c3f..dd6affcb 100755
Binary files a/stack/nostr-id-tui/build-host/nostr-id-tui and b/stack/nostr-id-tui/build-host/nostr-id-tui differ
diff --git a/stack/nostr-id-tui/build-host/test-services-sim.out b/stack/nostr-id-tui/build-host/test-services-sim.out
new file mode 100644
index 00000000..04351b45
--- /dev/null
+++ b/stack/nostr-id-tui/build-host/test-services-sim.out
@@ -0,0 +1,2 @@
+[?1049h[22;0;0t[1;24r(B[m[4l[?7h[?1h=[?25l[H[2J=[30b n-OS-tr Identity =[29b=[3;3HTUI version: 0.2.0[5;3H^[[4m1^[[0m) Enter existing mnemonic[6;3H^[[4m2^[[0m) Generate new mnemonic[7;3HE^[[4mx^[[0mit[9;3HSelect option: > [H[2J=[30b n-OS-tr Identity =[29b=[3;3HTUI version: 0.2.0[5;3H^[[4m1^[[0m) Enter existing mnemonic[6;3H^[[4m2^[[0m) Generate new mnemonic[7;3HE^[[4mx^[[0mit[9;3HSelect option: >
+Session terminated, killing shell... ...killed.
diff --git a/stack/nostr-id-tui/include/n_os_tr_tui.h b/stack/nostr-id-tui/include/n_os_tr_tui.h
index 45e932fc..3acec662 100644
--- a/stack/nostr-id-tui/include/n_os_tr_tui.h
+++ b/stack/nostr-id-tui/include/n_os_tr_tui.h
@@ -1,9 +1,32 @@
#ifndef N_OS_TR_TUI_H
#define N_OS_TR_TUI_H
-#define N_OS_TR_TUI_VERSION "0.1.0"
+#include
+
+#define N_OS_TR_TUI_VERSION "0.2.0"
#define NOSTR_TUI_MAX_MNEMONIC 256
#define NOSTR_TUI_MAX_NPUB 128
+#define N_OS_TR_TUI_MAX_SERVICES 16
+
+#define N_OS_TR_TUI_OK 0
+#define N_OS_TR_TUI_ERR 1
+
+typedef struct {
+ char key;
+ char unit[64];
+ char label[64];
+ int default_on;
+ char deps[128];
+ int desired_on;
+} service_entry_t;
+
+int services_conf_load(const char* path, service_entry_t* out, size_t max, size_t* out_count);
+int services_conf_load_fallback(service_entry_t* out, size_t max, size_t* out_count);
+
+int systemd_is_simulation_mode(void);
+int systemd_unit_is_active(const char* unit, int* is_active);
+int systemd_unit_start(const char* unit, char* err_out, size_t err_out_len);
+int systemd_unit_stop(const char* unit, char* err_out, size_t err_out_len);
#endif /* N_OS_TR_TUI_H */
diff --git a/stack/nostr-id-tui/src/main.c b/stack/nostr-id-tui/src/main.c
index f570ee37..4ab0f7a5 100644
--- a/stack/nostr-id-tui/src/main.c
+++ b/stack/nostr-id-tui/src/main.c
@@ -4,6 +4,8 @@
#include
#include
+#include
+
#include "n_os_tr_tui.h"
#include "nostr_core/nostr_core.h"
@@ -11,29 +13,180 @@ typedef enum {
SCREEN_MENU = 0,
SCREEN_ENTER = 1,
SCREEN_GENERATE = 2,
- SCREEN_EXIT = 3
+ SCREEN_SERVICES = 3,
+ SCREEN_EXIT = 4
} screen_t;
-static void clear_and_title(const char* title) {
- clear();
- mvprintw(0, 2, "n-OS-tr nostr-id-tui v%s", N_OS_TR_TUI_VERSION);
- mvprintw(1, 2, "%s", title);
- mvhline(2, 1, ACS_HLINE, 76);
+typedef struct {
+ int identity_loaded;
+ char main_npub[NOSTR_TUI_MAX_NPUB];
+ service_entry_t services[N_OS_TR_TUI_MAX_SERVICES];
+ size_t service_count;
+} app_state_t;
+
+#define BANNER_HEIGHT 9
+#define BANNER_IN_MS 3000LL
+#define BANNER_HOLD_MS 3000LL
+#define BANNER_OUT_MS 3000LL
+#define BANNER_PAUSE_MS 3000LL
+#define BANNER_CYCLE_MS (BANNER_IN_MS + BANNER_HOLD_MS + BANNER_OUT_MS + BANNER_PAUSE_MS)
+
+
+
+
+static const char* BANNER_ART[BANNER_HEIGHT] = {
+ "____________________________/\\\\\\\\\\__________/\\\\\\\\\\\\\\\\\\\\\\______________________________________________",
+ "___________________________/\\\\\\///\\\\\\______/\\\\\\/////////\\\\\\___________________________________________",
+ "__________________________/\\\\\\/__\\///\\\\\\___\\//\\\\\\______\\///_________________/\\\\\\______________________",
+ "_/\\\\/\\\\\\\\\\\\_______________/\\\\\\______\\//\\\\\\___\\////\\\\\\_____________________/\\\\\\\\\\\\\\\\\\\\\\__/\\\\/\\\\\\\\\\\\\\___",
+ "_\\/\\\\\\////\\\\\\_____________\\/\\\\\\_______\\/\\\\\\______\\////\\\\\\_________________\\////\\\\\\////__\\/\\\\\\/////\\\\\\_",
+ "__\\/\\\\\\__\\//\\\\\\____________\\//\\\\\\______/\\\\\\__________\\////\\\\\\_________________\\/\\\\\\______\\/\\\\\\___\\///_",
+ "___\\/\\\\\\___\\/\\\\\\_____________\\///\\\\\\__/\\\\\\_____/\\\\\\______\\//\\\\\\________________\\/\\\\\\_/\\\\__\\/\\\\\\_______",
+ "____\\/\\\\\\___\\/\\\\\\__/\\\\\\\\\\\\\\\\____\\///\\\\\\\\\\/_____\\///\\\\\\\\\\\\\\\\\\\\\\/____/\\\\\\\\\\\\\\\\____\\//\\\\\\\\\\___\\/\\\\\\______",
+ "_____\\///____\\///__\\////////_______\\/////_________\\///////////_____\\////////______\\/////____\\///______"
+};
+
+// Unescaped editable banner reference (do not compile directly):
+//
+// ____________________________/\\\\\__________/\\\\\\\\\\\______________________________________________
+// ___________________________/\\\///\\\______/\\\/////////\\\___________________________________________
+// __________________________/\\\/__\///\\\___\//\\\______\///_________________/\\\______________________
+// _/\\/\\\\\\_______________/\\\______\//\\\___\////\\\_____________________/\\\\\\\\\\\__/\\/\\\\\\\___
+// _\/\\\////\\\_____________\/\\\_______\/\\\______\////\\\_________________\////\\\////__\/\\\/////\\\_
+//___\/\\\__\//\\\____________\//\\\______/\\\__________\////\\\_________________\/\\\______\/\\\___\///_
+// ___\/\\\___\/\\\_____________\///\\\__/\\\_____/\\\______\//\\\________________\/\\\_/\\__\/\\\_______
+// ____\/\\\___\/\\\__/\\\\\\\\____\///\\\\\/_____\///\\\\\\\\\\\/____/\\\\\\\\____\//\\\\\___\/\\\______
+// _____\///____\///__\////////_______\/////_________\///////////_____\////////______\/////____\///______
+
+
+
+
+
+
+
+
+
+static int ui_row(int y) {
+ return y + BANNER_HEIGHT + 1;
+}
+
+static long long monotonic_ms(void) {
+ struct timeval tv;
+ if (gettimeofday(&tv, NULL) != 0) {
+ return 0;
+ }
+ return ((long long)tv.tv_sec * 1000LL) + ((long long)tv.tv_usec / 1000LL);
+}
+
+static int banner_art_width(void) {
+ int i;
+ int max_width = 0;
+
+ for (i = 0; i < BANNER_HEIGHT; ++i) {
+ int w = (int)strlen(BANNER_ART[i]);
+ if (w > max_width) {
+ max_width = w;
+ }
+ }
+
+ return max_width;
+}
+
+static void render_banner_frame(void) {
+ int row;
+ int col;
+ int screen_w = COLS;
+ int art_w = banner_art_width();
+ int center_x = (screen_w - art_w) / 2;
+ long long t = monotonic_ms() % BANNER_CYCLE_MS;
+ int show_art = 1;
+ int art_x = center_x;
+
+ for (row = 0; row < BANNER_HEIGHT; ++row) {
+ move(row, 0);
+ for (col = 0; col < screen_w; ++col) {
+ addch('_');
+ }
+ }
+
+ if (t < BANNER_IN_MS) {
+ int delta = center_x + art_w;
+ art_x = -art_w + (int)((delta * t) / BANNER_IN_MS);
+ } else if (t < (BANNER_IN_MS + BANNER_HOLD_MS)) {
+ art_x = center_x;
+ } else if (t < (BANNER_IN_MS + BANNER_HOLD_MS + BANNER_OUT_MS)) {
+ long long out_t = t - (BANNER_IN_MS + BANNER_HOLD_MS);
+ int delta = (screen_w + 1) - center_x;
+ art_x = center_x + (int)((delta * out_t) / BANNER_OUT_MS);
+ } else {
+ show_art = 0;
+ }
+
+ if (!show_art) {
+ return;
+ }
+
+ for (row = 0; row < BANNER_HEIGHT; ++row) {
+ const char* line = BANNER_ART[row];
+ int len = (int)strlen(line);
+ for (col = 0; col < len; ++col) {
+ int x = art_x + col;
+ char ch = line[col];
+ if (x < 0 || x >= screen_w) continue;
+ mvaddch(row, x, ch);
+ }
+ }
}
static void trim_line(char* s) {
size_t len;
if (!s) return;
+
len = strlen(s);
while (len > 0 && (s[len - 1] == '\n' || s[len - 1] == '\r' || isspace((unsigned char)s[len - 1]))) {
s[len - 1] = '\0';
len--;
}
+
while (*s && isspace((unsigned char)*s)) {
memmove(s, s + 1, strlen(s));
}
}
+static void draw_centered_header(const char* title) {
+ int width = COLS;
+ int title_len = (int)strlen(title);
+ int min_required = title_len + 4;
+ int left_pad, right_pad, i;
+ int r;
+
+ /* Clear UI body when entering/redrawing a screen to avoid stale text
+ from previous screens (menu/generate/services overlays). */
+ for (r = ui_row(1); r < LINES; ++r) {
+ move(r, 0);
+ clrtoeol();
+ }
+
+ /* Header line stays independently drawn below the banner. */
+ move(ui_row(0), 0);
+ for (i = 0; i < COLS; ++i) addch(' ');
+
+ if (width < min_required) {
+ mvprintw(ui_row(0), 0, "=== %s ===", title);
+ return;
+ }
+
+ left_pad = (width - title_len - 2) / 2;
+ right_pad = width - title_len - 2 - left_pad;
+
+ move(ui_row(0), 0);
+ for (i = 0; i < left_pad; ++i) addch('=');
+ addch(' ');
+ addstr(title);
+ addch(' ');
+ for (i = 0; i < right_pad; ++i) addch('=');
+}
+
static int read_line_at(int y, int x, char* out, size_t out_size) {
int rc;
if (!out || out_size < 2) return NOSTR_ERROR_INVALID_INPUT;
@@ -52,8 +205,15 @@ static int read_line_at(int y, int x, char* out, size_t out_size) {
}
static void wait_key(void) {
+ timeout(60);
mvprintw(LINES - 2, 2, "Press any key to continue...");
- getch();
+ refresh();
+ while (getch() == ERR) {
+ render_banner_frame();
+ mvprintw(LINES - 2, 2, "Press any key to continue...");
+ refresh();
+ }
+ timeout(-1);
}
static int derive_from_mnemonic(const char* mnemonic, char* npub_out, size_t npub_size) {
@@ -73,9 +233,7 @@ static int derive_from_mnemonic(const char* mnemonic, char* npub_out, size_t npu
memset(priv, 0, sizeof(priv));
memset(pub, 0, sizeof(pub));
- if (rc != NOSTR_SUCCESS) {
- return rc;
- }
+ if (rc != NOSTR_SUCCESS) return rc;
npub_out[npub_size - 1] = '\0';
return NOSTR_SUCCESS;
@@ -91,49 +249,193 @@ static void render_prefix_hint(const char* word_prefix) {
return;
}
- mvprintw(11, 2, "Suggestions for '%s':", word_prefix);
+ mvprintw(ui_row(11), 2, "Suggestions for '%s':", word_prefix);
for (size_t i = 0; i < count && i < 6; ++i) {
- mvprintw(12 + (int)i, 4, "- %s", matches[i]);
+ mvprintw(ui_row(12 + (int)i), 4, "- %s", matches[i]);
}
}
+static void load_services_if_needed(app_state_t* app) {
+ const char* path;
+ if (!app || app->service_count > 0) return;
+
+ path = getenv("NOSTR_TUI_SERVICES_CONF");
+ if (!path || path[0] == '\0') {
+ path = "/etc/n-os-tr/services.conf";
+ }
+
+ if (services_conf_load(path, app->services, N_OS_TR_TUI_MAX_SERVICES, &app->service_count) != N_OS_TR_TUI_OK) {
+ (void)services_conf_load_fallback(app->services, N_OS_TR_TUI_MAX_SERVICES, &app->service_count);
+ }
+}
+
+static void refresh_service_states(app_state_t* app, int* active_states) {
+ size_t i;
+ int active = 0;
+
+ for (i = 0; i < app->service_count; ++i) {
+ if (systemd_unit_is_active(app->services[i].unit, &active) != N_OS_TR_TUI_OK) {
+ active = 0;
+ }
+ active_states[i] = active;
+ }
+}
+
+static void apply_dependency_rules(app_state_t* app, size_t idx) {
+ size_t i;
+ if (!app || idx >= app->service_count) return;
+
+ if (strcmp(app->services[idx].deps, "-") == 0 || app->services[idx].deps[0] == '\0') {
+ return;
+ }
+
+ if (!app->services[idx].desired_on) return;
+
+ for (i = 0; i < app->service_count; ++i) {
+ if (strcmp(app->services[i].unit, app->services[idx].deps) == 0) {
+ app->services[i].desired_on = 1;
+ }
+ }
+}
+
+static int apply_services(app_state_t* app) {
+ int y = ui_row(9);
+ size_t i;
+ int active_states[N_OS_TR_TUI_MAX_SERVICES];
+ char err[256] = {0};
+ int had_error = 0;
+
+ refresh_service_states(app, active_states);
+
+ mvprintw(ui_row(7), 2, "Applying selection...");
+ for (i = 0; i < app->service_count; ++i) {
+ int rc;
+ const char* action = NULL;
+
+ if (app->services[i].desired_on && !active_states[i]) {
+ action = "START";
+ rc = systemd_unit_start(app->services[i].unit, err, sizeof(err));
+ } else if (!app->services[i].desired_on && active_states[i]) {
+ action = "STOP ";
+ rc = systemd_unit_stop(app->services[i].unit, err, sizeof(err));
+ } else {
+ action = "SKIP ";
+ rc = N_OS_TR_TUI_OK;
+ }
+
+ if (rc == N_OS_TR_TUI_OK) {
+ mvprintw(y++, 4, "[ OK ] %s %s", action, app->services[i].unit);
+ } else {
+ mvprintw(y++, 4, "[FAIL] %s %s", action, app->services[i].unit);
+ mvprintw(y++, 6, "%s", err[0] ? err : "systemctl error");
+ had_error = 1;
+ }
+
+ if (y >= LINES - 4) break;
+ }
+
+ if (had_error) {
+ mvprintw(LINES - 4, 2, "One or more unit operations failed.");
+ return N_OS_TR_TUI_ERR;
+ }
+
+ mvprintw(LINES - 4, 2, "Service selection applied.");
+ return N_OS_TR_TUI_OK;
+}
+
+static void render_menu_static(void) {
+ draw_centered_header("n_OS_tr");
+
+ move(ui_row(4), 2);
+ attron(A_UNDERLINE);
+ addch('E');
+ attroff(A_UNDERLINE);
+ addstr("nter existing mnemonic");
+
+ move(ui_row(5), 2);
+ attron(A_UNDERLINE);
+ addch('G');
+ attroff(A_UNDERLINE);
+ addstr("enerate new mnemonic");
+
+ move(ui_row(6), 2);
+ addch('E');
+ attron(A_UNDERLINE);
+ addch('x');
+ attroff(A_UNDERLINE);
+ addstr("it");
+
+ mvprintw(ui_row(8), 2, "Select action: > ");
+}
+
static screen_t screen_menu(void) {
int ch;
- clear_and_title("Main Menu");
- mvprintw(4, 2, "1) Enter existing mnemonic");
- mvprintw(5, 2, "2) Generate new mnemonic");
- mvprintw(6, 2, "q) Quit");
- mvprintw(8, 2, "Select option: ");
+ int last_cols = COLS;
+ int last_lines = LINES;
+
+ render_menu_static();
+ render_banner_frame();
refresh();
- ch = getch();
- if (ch == '1') return SCREEN_ENTER;
- if (ch == '2') return SCREEN_GENERATE;
- if (ch == 'q' || ch == 'Q') return SCREEN_EXIT;
- return SCREEN_MENU;
+ timeout(60);
+ while (1) {
+ ch = getch();
+ if (ch == ERR) {
+ if (COLS != last_cols || LINES != last_lines) {
+ last_cols = COLS;
+ last_lines = LINES;
+ render_menu_static();
+ }
+ render_banner_frame();
+ refresh();
+ continue;
+ }
+
+ if (ch == KEY_RESIZE) {
+ last_cols = COLS;
+ last_lines = LINES;
+ render_menu_static();
+ render_banner_frame();
+ refresh();
+ continue;
+ }
+
+ ch = tolower(ch);
+ if (ch == 'e') {
+ timeout(-1);
+ return SCREEN_ENTER;
+ }
+ if (ch == 'g') {
+ timeout(-1);
+ return SCREEN_GENERATE;
+ }
+ if (ch == 'x' || ch == 'q') {
+ timeout(-1);
+ return SCREEN_EXIT;
+ }
+ }
}
-static screen_t screen_enter(void) {
+static screen_t screen_enter(app_state_t* app) {
char line[NOSTR_TUI_MAX_MNEMONIC] = {0};
- char npub[NOSTR_TUI_MAX_NPUB] = {0};
nostr_secure_buf_t secure = {0};
int rc;
- clear_and_title("Enter mnemonic");
- mvprintw(4, 2, "Paste your 12/15/18/21/24-word mnemonic and press Enter.");
- mvprintw(5, 2, "Mnemonic: ");
+ draw_centered_header("Enter Mnemonic");
+ mvprintw(ui_row(3), 2, "Paste your 12/15/18/21/24-word mnemonic and press Enter.");
+ mvprintw(ui_row(5), 2, "Mnemonic: ");
refresh();
rc = nostr_secure_buf_init(&secure, NOSTR_TUI_MAX_MNEMONIC);
if (rc != NOSTR_SUCCESS) {
- mvprintw(7, 2, "secure memory init failed: %d", rc);
+ mvprintw(ui_row(7), 2, "secure memory init failed: %d", rc);
wait_key();
return SCREEN_MENU;
}
- rc = read_line_at(5, 12, line, sizeof(line));
+ rc = read_line_at(ui_row(5), 12, line, sizeof(line));
if (rc != NOSTR_SUCCESS) {
- mvprintw(7, 2, "input error");
+ mvprintw(ui_row(7), 2, "input error");
nostr_secure_buf_destroy(&secure);
wait_key();
return SCREEN_MENU;
@@ -141,98 +443,261 @@ static screen_t screen_enter(void) {
rc = nostr_secure_buf_append(&secure, line, strlen(line));
if (rc != NOSTR_SUCCESS) {
- mvprintw(7, 2, "secure buffer append failed: %d", rc);
+ mvprintw(ui_row(7), 2, "secure buffer append failed: %d", rc);
nostr_secure_buf_destroy(&secure);
wait_key();
return SCREEN_MENU;
}
if (nostr_bip39_mnemonic_validate((const char*)secure.bytes) != NOSTR_SUCCESS) {
- mvprintw(7, 2, "Invalid mnemonic.");
char first_word[32] = {0};
size_t i = 0;
+ mvprintw(ui_row(7), 2, "Invalid mnemonic.");
+
while (i < sizeof(first_word) - 1 && line[i] && !isspace((unsigned char)line[i])) {
first_word[i] = line[i];
i++;
}
first_word[i] = '\0';
+
render_prefix_hint(first_word);
nostr_secure_buf_destroy(&secure);
wait_key();
return SCREEN_MENU;
}
- rc = derive_from_mnemonic((const char*)secure.bytes, npub, sizeof(npub));
- if (rc != NOSTR_SUCCESS) {
- mvprintw(7, 2, "Key derivation failed: %d", rc);
- nostr_secure_buf_destroy(&secure);
- wait_key();
- return SCREEN_MENU;
- }
-
- mvprintw(7, 2, "Mnemonic accepted.");
- mvprintw(8, 2, "Derived main npub:");
- mvprintw(9, 2, "%s", npub);
-
+ rc = derive_from_mnemonic((const char*)secure.bytes, app->main_npub, sizeof(app->main_npub));
nostr_secure_buf_destroy(&secure);
memset(line, 0, sizeof(line));
- wait_key();
- return SCREEN_MENU;
+ if (rc != NOSTR_SUCCESS) {
+ mvprintw(ui_row(7), 2, "Key derivation failed: %d", rc);
+ wait_key();
+ return SCREEN_MENU;
+ }
+
+ app->identity_loaded = 1;
+ return SCREEN_SERVICES;
}
-static screen_t screen_generate(void) {
+static screen_t screen_generate(app_state_t* app) {
char mnemonic[NOSTR_TUI_MAX_MNEMONIC] = {0};
- char npub[NOSTR_TUI_MAX_NPUB] = {0};
+ char main_nsec[NOSTR_TUI_MAX_NPUB] = {0};
unsigned char priv[32] = {0};
unsigned char pub[32] = {0};
- uint8_t picks[NOSTR_BIP39_CONFIRM_PICK_COUNT] = {0};
int rc;
- clear_and_title("Generate mnemonic");
+ draw_centered_header("Generate Mnemonic");
rc = nostr_generate_mnemonic_and_keys(mnemonic, sizeof(mnemonic), 0, priv, pub);
if (rc != NOSTR_SUCCESS) {
- mvprintw(4, 2, "Generation failed: %d", rc);
+ mvprintw(ui_row(4), 2, "Generation failed: %d", rc);
wait_key();
return SCREEN_MENU;
}
- rc = nostr_key_to_bech32(pub, "npub", npub);
+ rc = nostr_key_to_bech32(pub, "npub", app->main_npub);
if (rc != NOSTR_SUCCESS) {
- mvprintw(4, 2, "npub conversion failed: %d", rc);
+ mvprintw(ui_row(4), 2, "npub conversion failed: %d", rc);
memset(priv, 0, sizeof(priv));
memset(pub, 0, sizeof(pub));
wait_key();
return SCREEN_MENU;
}
- rc = nostr_bip39_ui_random_confirm_positions(12, picks);
+ rc = nostr_key_to_bech32(priv, "nsec", main_nsec);
if (rc != NOSTR_SUCCESS) {
- mvprintw(4, 2, "confirm-position generation failed: %d", rc);
+ mvprintw(ui_row(4), 2, "nsec conversion failed: %d", rc);
memset(priv, 0, sizeof(priv));
memset(pub, 0, sizeof(pub));
wait_key();
return SCREEN_MENU;
}
- mvprintw(4, 2, "Write this mnemonic down now:");
- mvprintw(6, 2, "%s", mnemonic);
- mvprintw(8, 2, "Derived main npub:");
- mvprintw(9, 2, "%s", npub);
- mvprintw(11, 2, "Confirmation check words: #%u, #%u, #%u",
- (unsigned)picks[0], (unsigned)picks[1], (unsigned)picks[2]);
+ mvprintw(ui_row(4), 2, "Write this mnemonic down now:");
+ mvprintw(ui_row(5), 2, "Mnemonic (index 0):");
+ mvprintw(ui_row(6), 2, "%s", mnemonic);
+ mvprintw(ui_row(8), 2, "Derived keys (mnemonic index 0):");
+ mvprintw(ui_row(9), 2, "npub: %s", app->main_npub);
+ mvprintw(ui_row(10), 2, "nsec: %s", main_nsec);
memset(priv, 0, sizeof(priv));
memset(pub, 0, sizeof(pub));
memset(mnemonic, 0, sizeof(mnemonic));
+ app->identity_loaded = 1;
wait_key();
- return SCREEN_MENU;
+ return SCREEN_SERVICES;
+}
+
+static void render_underlined_hotkey_service_row(int y,
+ const service_entry_t* svc,
+ int active_state) {
+ int x = 3;
+ const char* label;
+ const char* key_pos;
+ size_t prefix_len;
+
+ if (!svc) return;
+
+ label = svc->label;
+ key_pos = strchr(label, (int)tolower((unsigned char)svc->key));
+
+ if (!key_pos) {
+ key_pos = strchr(label, (int)toupper((unsigned char)svc->key));
+ }
+
+ if (!key_pos) {
+ mvprintw(y, x, "%-16s [ %s ] [ %s ] %s",
+ label,
+ svc->desired_on ? "on " : "off",
+ active_state ? "on " : "off",
+ svc->unit);
+ return;
+ }
+
+ prefix_len = (size_t)(key_pos - label);
+
+ move(y, x);
+ addnstr(label, (int)prefix_len);
+ x += (int)prefix_len;
+
+ move(y, x);
+ attron(A_UNDERLINE);
+ addch(*key_pos);
+ attroff(A_UNDERLINE);
+ x += 1;
+
+ move(y, x);
+ addstr(key_pos + 1);
+
+ mvprintw(y, 22, "[ %s ] [ %s ] %s",
+ svc->desired_on ? "on " : "off",
+ active_state ? "on " : "off",
+ svc->unit);
+}
+
+static void render_services_table(app_state_t* app, const int* active_states) {
+ size_t i;
+ int y = ui_row(6);
+
+ mvprintw(ui_row(4), 2, "Service Desired Active Unit");
+ mvprintw(ui_row(5), 2, "----------------- ------- ------ ------------------------------");
+
+ for (i = 0; i < app->service_count && y < LINES - 8; ++i) {
+ render_underlined_hotkey_service_row(y++, &app->services[i], active_states[i]);
+ }
+}
+
+static screen_t screen_services(app_state_t* app) {
+ int ch;
+ int active_states[N_OS_TR_TUI_MAX_SERVICES] = {0};
+ size_t i;
+ int needs_redraw = 1;
+ int last_cols = COLS;
+ int last_lines = LINES;
+
+ if (!app || !app->identity_loaded) {
+ return SCREEN_MENU;
+ }
+
+ load_services_if_needed(app);
+ for (i = 0; i < app->service_count; ++i) {
+ app->services[i].desired_on = app->services[i].default_on;
+ }
+
+ timeout(60);
+ while (1) {
+ if (needs_redraw) {
+ draw_centered_header("Services");
+ refresh_service_states(app, active_states);
+
+ mvprintw(ui_row(2), 2, "Identity: %.22s...", app->main_npub[0] ? app->main_npub : "(unknown)");
+ mvprintw(ui_row(3), 2, "Simulation mode: %s", systemd_is_simulation_mode() ? "ON" : "OFF");
+
+ render_services_table(app, active_states);
+
+ mvprintw(LINES - 6, 2, "Actions:");
+ mvprintw(LINES - 5, 2, " R/G/F/N/L/W/S/T toggle | A all on | Z all off");
+ mvprintw(LINES - 4, 2, " C commit/apply | X skip (leave unchanged) | Q quit");
+ mvprintw(LINES - 2, 2, "Select action: > ");
+ render_banner_frame();
+ refresh();
+ needs_redraw = 0;
+ }
+
+ ch = getch();
+ if (ch == ERR) {
+ if (COLS != last_cols || LINES != last_lines) {
+ last_cols = COLS;
+ last_lines = LINES;
+ needs_redraw = 1;
+ }
+ render_banner_frame();
+ refresh();
+ continue;
+ }
+
+ if (ch == KEY_RESIZE) {
+ last_cols = COLS;
+ last_lines = LINES;
+ needs_redraw = 1;
+ continue;
+ }
+
+ ch = tolower(ch);
+ if (ch == 'q') {
+ timeout(-1);
+ return SCREEN_EXIT;
+ }
+
+ if (ch == 'x') {
+ timeout(-1);
+ return SCREEN_EXIT;
+ }
+
+ if (ch == 'a') {
+ for (i = 0; i < app->service_count; ++i) app->services[i].desired_on = 1;
+ needs_redraw = 1;
+ continue;
+ }
+
+ if (ch == 'z') {
+ for (i = 0; i < app->service_count; ++i) app->services[i].desired_on = 0;
+ needs_redraw = 1;
+ continue;
+ }
+
+ if (ch == 'c') {
+ draw_centered_header("Applying Services");
+ render_banner_frame();
+ mvprintw(ui_row(2), 2, "Committing selected service states...");
+ if (apply_services(app) != N_OS_TR_TUI_OK) {
+ wait_key();
+ needs_redraw = 1;
+ continue;
+ }
+ wait_key();
+ timeout(-1);
+ return SCREEN_EXIT;
+ }
+
+ for (i = 0; i < app->service_count; ++i) {
+ if (ch == tolower((unsigned char)app->services[i].key)) {
+ app->services[i].desired_on = !app->services[i].desired_on;
+ apply_dependency_rules(app, i);
+ needs_redraw = 1;
+ break;
+ }
+ }
+ }
}
int main(void) {
screen_t screen = SCREEN_MENU;
+ app_state_t app;
+
+ memset(&app, 0, sizeof(app));
if (nostr_init() != NOSTR_SUCCESS) {
fprintf(stderr, "nostr_init failed\n");
@@ -254,10 +719,13 @@ int main(void) {
screen = screen_menu();
break;
case SCREEN_ENTER:
- screen = screen_enter();
+ screen = screen_enter(&app);
break;
case SCREEN_GENERATE:
- screen = screen_generate();
+ screen = screen_generate(&app);
+ break;
+ case SCREEN_SERVICES:
+ screen = screen_services(&app);
break;
case SCREEN_EXIT:
default:
diff --git a/stack/nostr-id-tui/src/services_conf.c b/stack/nostr-id-tui/src/services_conf.c
new file mode 100644
index 00000000..8060c7fa
--- /dev/null
+++ b/stack/nostr-id-tui/src/services_conf.c
@@ -0,0 +1,131 @@
+#include
+#include
+#include
+#include
+
+#include "n_os_tr_tui.h"
+
+static void copy_str(char* dst, size_t dst_len, const char* src) {
+ if (!dst || dst_len == 0) return;
+ if (!src) {
+ dst[0] = '\0';
+ return;
+ }
+ snprintf(dst, dst_len, "%s", src);
+}
+
+static void trim(char* s) {
+ size_t len;
+ if (!s) return;
+
+ while (*s && isspace((unsigned char)*s)) {
+ memmove(s, s + 1, strlen(s));
+ }
+
+ len = strlen(s);
+ while (len > 0 && isspace((unsigned char)s[len - 1])) {
+ s[len - 1] = '\0';
+ len--;
+ }
+}
+
+static int parse_default(const char* v) {
+ if (!v) return 0;
+ if (strcmp(v, "on") == 0 || strcmp(v, "1") == 0 || strcmp(v, "true") == 0) return 1;
+ return 0;
+}
+
+static void set_entry(service_entry_t* e,
+ char key,
+ const char* unit,
+ const char* label,
+ int default_on,
+ const char* deps) {
+ if (!e) return;
+ memset(e, 0, sizeof(*e));
+ e->key = key;
+ copy_str(e->unit, sizeof(e->unit), unit);
+ copy_str(e->label, sizeof(e->label), label);
+ e->default_on = default_on;
+ copy_str(e->deps, sizeof(e->deps), deps ? deps : "-");
+ e->desired_on = default_on;
+}
+
+int services_conf_load_fallback(service_entry_t* out, size_t max, size_t* out_count) {
+ size_t i = 0;
+ if (!out || !out_count || max < 8) return N_OS_TR_TUI_ERR;
+
+ set_entry(&out[i++], 'r', "c-relay.service", "c-relay", 0, "nginx.service");
+ set_entry(&out[i++], 'g', "ginxsom.service", "ginxsom", 0, "nginx.service");
+ set_entry(&out[i++], 'f', "fips.service", "fips", 0, "-");
+ set_entry(&out[i++], 'n', "nginx.service", "nginx", 0, "-");
+ set_entry(&out[i++], 'l', "nostr-config-loader.service", "config-loader", 0, "-");
+ set_entry(&out[i++], 'w', "nostr-config-writer.service", "config-writer", 0, "nostr-config-loader.service");
+ set_entry(&out[i++], 's', "ssh.service", "ssh", 0, "-");
+ set_entry(&out[i++], 't', "tor.service", "tor", 0, "-");
+
+ *out_count = i;
+ return N_OS_TR_TUI_OK;
+}
+
+int services_conf_load(const char* path, service_entry_t* out, size_t max, size_t* out_count) {
+ FILE* fp;
+ char line[512];
+ size_t n = 0;
+
+ if (!path || !out || !out_count || max == 0) return N_OS_TR_TUI_ERR;
+
+ fp = fopen(path, "r");
+ if (!fp) return N_OS_TR_TUI_ERR;
+
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ char* tok;
+ char local[512];
+ char* key_s;
+ char* unit_s;
+ char* label_s;
+ char* def_s;
+ char* deps_s;
+ char key;
+
+ copy_str(local, sizeof(local), line);
+ trim(local);
+ if (local[0] == '\0' || local[0] == '#') continue;
+
+ tok = strtok(local, "\t ");
+ if (!tok) continue;
+ key_s = tok;
+
+ tok = strtok(NULL, "\t ");
+ if (!tok) continue;
+ unit_s = tok;
+
+ tok = strtok(NULL, "\t ");
+ if (!tok) continue;
+ label_s = tok;
+
+ tok = strtok(NULL, "\t ");
+ if (!tok) continue;
+ def_s = tok;
+
+ tok = strtok(NULL, "\t ");
+ deps_s = tok ? tok : "-";
+
+ key = (char)tolower((unsigned char)key_s[0]);
+ if (!isprint((unsigned char)key)) continue;
+
+ if (n >= max) {
+ fclose(fp);
+ return N_OS_TR_TUI_ERR;
+ }
+
+ set_entry(&out[n], key, unit_s, label_s, parse_default(def_s), deps_s);
+ n++;
+ }
+
+ fclose(fp);
+
+ if (n == 0) return N_OS_TR_TUI_ERR;
+ *out_count = n;
+ return N_OS_TR_TUI_OK;
+}
diff --git a/stack/nostr-id-tui/src/systemd_client.c b/stack/nostr-id-tui/src/systemd_client.c
new file mode 100644
index 00000000..f9e18edc
--- /dev/null
+++ b/stack/nostr-id-tui/src/systemd_client.c
@@ -0,0 +1,100 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "n_os_tr_tui.h"
+
+static int run_systemctl(char* const argv[]) {
+ pid_t pid;
+ int status = 0;
+
+ if (systemd_is_simulation_mode()) {
+ return 0;
+ }
+
+ if (access("/bin/systemctl", X_OK) != 0) {
+ return -1;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ return -1;
+ }
+
+ if (pid == 0) {
+ execv("/bin/systemctl", argv);
+ _exit(127);
+ }
+
+ if (waitpid(pid, &status, 0) < 0) {
+ return -1;
+ }
+
+ if (WIFEXITED(status)) {
+ return WEXITSTATUS(status);
+ }
+
+ return -1;
+}
+
+int systemd_is_simulation_mode(void) {
+ const char* v = getenv("NOSTR_TUI_SIMULATE_SYSTEMCTL");
+ return (v && strcmp(v, "1") == 0) ? 1 : 0;
+}
+
+int systemd_unit_is_active(const char* unit, int* is_active) {
+ char* const argv[] = {
+ "/bin/systemctl", "is-active", "--quiet", (char*)unit, NULL
+ };
+ int rc;
+
+ if (!unit || !is_active) return N_OS_TR_TUI_ERR;
+
+ if (systemd_is_simulation_mode()) {
+ *is_active = 0;
+ return N_OS_TR_TUI_OK;
+ }
+
+ rc = run_systemctl(argv);
+ if (rc < 0) return N_OS_TR_TUI_ERR;
+
+ *is_active = (rc == 0) ? 1 : 0;
+ return N_OS_TR_TUI_OK;
+}
+
+int systemd_unit_start(const char* unit, char* err_out, size_t err_out_len) {
+ char* const argv[] = {
+ "/bin/systemctl", "start", (char*)unit, NULL
+ };
+ int rc;
+
+ if (!unit) return N_OS_TR_TUI_ERR;
+
+ rc = run_systemctl(argv);
+ if (rc == 0) return N_OS_TR_TUI_OK;
+
+ if (err_out && err_out_len > 0) {
+ snprintf(err_out, err_out_len, "systemctl start failed for %s (rc=%d, errno=%d)", unit, rc, errno);
+ }
+ return N_OS_TR_TUI_ERR;
+}
+
+int systemd_unit_stop(const char* unit, char* err_out, size_t err_out_len) {
+ char* const argv[] = {
+ "/bin/systemctl", "stop", (char*)unit, NULL
+ };
+ int rc;
+
+ if (!unit) return N_OS_TR_TUI_ERR;
+
+ rc = run_systemctl(argv);
+ if (rc == 0) return N_OS_TR_TUI_OK;
+
+ if (err_out && err_out_len > 0) {
+ snprintf(err_out, err_out_len, "systemctl stop failed for %s (rc=%d, errno=%d)", unit, rc, errno);
+ }
+ return N_OS_TR_TUI_ERR;
+}
diff --git a/stack/nostr-id-tui/tests/test_services_sim.sh b/stack/nostr-id-tui/tests/test_services_sim.sh
new file mode 100755
index 00000000..d397710e
--- /dev/null
+++ b/stack/nostr-id-tui/tests/test_services_sim.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
+BIN="$ROOT_DIR/stack/nostr-id-tui/build-host/nostr-id-tui"
+SERVICES_CONF="$ROOT_DIR/iso/config/includes.chroot/etc/n-os-tr/services.conf"
+
+if [[ ! -x "$BIN" ]]; then
+ echo "error: missing binary $BIN"
+ echo "build first: cmake -S stack/nostr-id-tui -B stack/nostr-id-tui/build-host && cmake --build stack/nostr-id-tui/build-host -j"
+ exit 1
+fi
+
+if [[ ! -f "$SERVICES_CONF" ]]; then
+ echo "error: missing services config $SERVICES_CONF"
+ exit 1
+fi
+
+OUT="$ROOT_DIR/stack/nostr-id-tui/build-host/test-services-sim.out"
+
+# Simulated run (no real systemctl operations). We just verify the TUI launches and renders.
+timeout 2s env TERM=xterm-256color \
+ NOSTR_TUI_SIMULATE_SYSTEMCTL=1 \
+ NOSTR_TUI_SERVICES_CONF="$SERVICES_CONF" \
+ script -q -c "$BIN" /dev/null > "$OUT" 2>&1 || true
+
+if grep -q "n-OS-tr Identity" "$OUT"; then
+ echo "ok: TUI launched and rendered header"
+else
+ echo "error: expected TUI header not found in output"
+ echo "see $OUT"
+ exit 1
+fi
+
+echo "ok: simulated services smoke test passed"