Skip to content

feat(linbo-remote): append a JSON record per finished run - #184

Merged
HappyBasher merged 2 commits into
7.4from
feat/linbo-remote-run-record
Oct 1, 2026
Merged

HappyBasher merged 2 commits into
7.4from
feat/linbo-remote-run-record

Conversation

@HappyBasher

Copy link
Copy Markdown
Member

Part 2 of #179. Stacked on #183 (base is that branch; retarget to main once #183 is merged).

Every -c run appends one JSON line to $LINBOLOGDIR/linbo-remote_runs.jsonl:

{"hostname":"r100-pc01","mode":"direct","commands":["sync:1"],"dry_run":false,"start":"2026-10-01T11:36:43+0200","log":"/var/log/linuxmuster/linbo/r100-pc01_linbo-remote_20261001113643.log","end":"2026-10-01T11:41:02+0200","rc":0}
  • Written by the remote script via an EXIT trap, since only it knows end time and exit status. A trap also leaves a record for aborted runs (e.g. killed tmux session).
  • Appends are serialized with flock (parallel hosts share the file).
  • Tests run a rendered script for real with bash (linbo-ssh replaced by true/false) and check the record on success and failure.

Not covered: onboot (-p) runs. The server only queues them and never learns end time or status, so there is nothing to record on its side. Say if you want a "queued" record for those too.

Refs #179

🤖 Generated with Claude Code

Nothing persisted that a -c run had happened once its tmux session was
gone, so consumers (linuxmuster-api, webui) could not list finished runs.

The remote script now installs an EXIT trap that appends one JSON line
to LINBOLOGDIR/linbo-remote_runs.jsonl with hostname, mode, commands,
dry_run, start, end, rc and the path of the run's log (see the per-run
logfile). The static part is built by buildRunRecordPrefix(); only the
script knows the end time and exit status, so it completes the line. A
trap also covers aborted runs, and the append is serialized with flock
because parallel runs share the file.

Part 2 of #179. Onboot (-p) runs are not recorded: the server only
queues them, end time and status are unknown to it.

Signed-off-by: thomas@linuxmuster.net
Assisted-by: Claude
After an unhandled fatal signal bash reports $?=0 inside the EXIT trap,
so a run whose tmux session was killed was recorded with "rc":0, i.e. as
a success. Found while testing the run record live.

Turn HUP/INT/TERM into `exit 129/130/143` before the EXIT trap fires, so
the record carries the 128+signal status. The record of an aborted run
appears once the in-flight linbo-ssh child has finished.

Refs #179

Signed-off-by: thomas@linuxmuster.net
Assisted-by: Claude
@HappyBasher
HappyBasher force-pushed the feat/linbo-remote-per-run-log branch from f9c8868 to 5b325b2 Compare October 1, 2026 10:20
@HappyBasher
HappyBasher force-pushed the feat/linbo-remote-run-record branch from 352dbcc to 6f13ee0 Compare October 1, 2026 10:20
@HappyBasher
HappyBasher changed the base branch from feat/linbo-remote-per-run-log to 7.4 October 1, 2026 10:21
@HappyBasher
HappyBasher merged commit 7cb9818 into 7.4 Oct 1, 2026
9 checks passed
@TomlDev

TomlDev commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Thanks for this. A review of the code, with the points a consumer (linuxmuster-api, webui7, edulution) runs into:

Line references are to the files on the PR branches (linbo_remote_lib.py = src/serverfs/usr/share/linuxmuster/linbo/linbo_remote_lib.py).

1. start/end use %z without a colon (linbo_remote_lib.py buildRunRecordPrefix, time.strftime('%Y-%m-%dT%H:%M:%S%z', ...), and date +%Y-%m-%dT%H:%M:%S%z in writeRunRecord). 2026-10-01T11:36:43+0200 is not RFC 3339/ISO 8601 extended; Date.parse in Safari/Firefox, Go's time.RFC3339, Java OffsetDateTime and Python <= 3.10 fromisoformat reject it.
Suggestion: date -Iseconds in the trap and datetime.fromtimestamp(time.mktime(started)).astimezone().isoformat(timespec='seconds') (or time.strftime(...) plus an inserted colon) in Python, giving +02:00.

2. A run that is still in progress, or ended by SIGKILL/OOM/server reboot, leaves no record at all (writeRunRecord is the only writer; the line carries start and end together). A client cannot list "running" or "lost" runs from the file alone and has to join with tmux ls.
Suggestion: document it in the README, or write the start line at launch (same flock append, "event":"start") and the end line from the trap, joined by a run id.

3. No field links the per-host lines of one invocation, and skipped hosts leave no trace. linbo-remote -g r101 -c ... writes N unrelated lines; an offline host (Not online, host skipped. in sendCmds) writes none. Consumers (linuxmuster-api #46) want "run on room R101 finished on 18 of 20 hosts".
Suggestion: add "run_id" (one per invocation, e.g. <pid>-<epoch> or uuid4, generated once before the host loop) and "user" (SUDO_USER/USER) to the prefix; optionally append a "rc":null,"skipped":"offline" line for skipped hosts.

4. rc is 0 for start*/reboot/halt regardless of outcome (renderRemoteScript: [ $RC = 0 ] && ssh ... & followed by sleep 10, the background job's status is never read). For all other commands it is 0/1 without saying which command failed.
Suggestion: state in the README that rc 0 after a backgrounded command only means "dispatched"; optionally add "failed_cmd":"<n>" from the loop.

5. Schema is undocumented beyond the field names. Add a "v":1 field to the prefix and document in the README: types, start/end format, log is an absolute path, rc meaning (0, 1, 129/130/143), dry_run records exist and carry the dry-run exit status, commands is the normalized list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants