feat: journal access notice, 1.60.0, install script, changelog, riscv protoc fallback

- Journal pane now distinguishes 'no entries' from 'no journal access':
  journalctl exits 0 with empty output when the agent's user simply can't
  see the target's entries (demo mode / user-run agents), explaining
  itself only on stderr. The agent forwards that hint as an additive
  JournalResponse.notice and the client renders it with practical advice.
  Verified E2E via a stub journalctl emulating the unprivileged case.
- Version 1.60.0 across all crates (1.51 would read fine, but the repo's
  scheme is 1.40/1.50/…, and a literal 1.6.0 would sort BELOW 1.50.0 in
  semver). All user-facing version strings already come from
  CARGO_PKG_VERSION — a stale binary was the only way to see an old one.
- scripts/install.sh: build-from-source install/upgrade for the test
  fleet (Linux + macOS). Detects in-repo checkouts, installs rustup when
  missing, replaces a systemd socktop-agent service binary in place and
  restarts it, requires system protoc on riscv64.
- build.rs (agent + connector): fall back to $PROTOC / PATH when
  protoc-bin-vendored has no binary for the host (riscv64) — native SBC
  builds previously panicked in the build script.
- CHANGELOG.md covering v1.50.0 -> 1.60.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jasonwitty
2026-08-19 20:24:56 -07:00
parent 746ca4cf58
commit d653cbcadc
14 changed files with 249 additions and 21 deletions
+20
View File
@@ -1412,6 +1412,25 @@ pub async fn collect_journal_entries(pid: u32) -> Result<JournalResponse, String
// Sort by timestamp (newest first)
entries.sort_by_key(|e| std::cmp::Reverse(e.timestamp_us));
// journalctl exits 0 with no output when the invoking user simply cannot
// SEE the process's entries (e.g. a user-run agent asking about a system
// service) — but it explains itself on stderr ("You are currently not
// seeing messages from other users and the system…"). Pass that hint
// along so the client can distinguish "no logs" from "no access".
let notice = if entries.is_empty() {
let err = String::from_utf8_lossy(&output.stderr);
let hint: String = err
.lines()
.map(str::trim)
.filter(|l| !l.is_empty())
.take(2)
.collect::<Vec<_>>()
.join(" ");
if hint.is_empty() { None } else { Some(hint) }
} else {
None
};
let response_timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|e| format!("Time error: {e}"))?
@@ -1424,6 +1443,7 @@ pub async fn collect_journal_entries(pid: u32) -> Result<JournalResponse, String
entries,
total_count,
truncated,
notice,
cached_at: response_timestamp,
})
}
+1 -1
View File
@@ -24,7 +24,7 @@ pub fn cert_paths() -> (PathBuf, PathBuf) {
pub fn ensure_self_signed_cert() -> anyhow::Result<(PathBuf, PathBuf)> {
let (cert_path, key_path) = cert_paths();
if cert_path.exists() && key_path.exists() {
// Keys generated by agents older than 1.51 were written with the
// Keys generated by agents older than 1.60 were written with the
// default umask (typically 0644): tighten them on startup.
#[cfg(unix)]
{
+4
View File
@@ -126,5 +126,9 @@ pub struct JournalResponse {
pub entries: Vec<JournalEntry>,
pub total_count: u32,
pub truncated: bool,
/// journalctl's own explanation when the result is empty because of
/// journal ACCESS (not absence of logs) — e.g. a user-run agent asking
/// about a system service. None when entries exist or nothing to say.
pub notice: Option<String>,
pub cached_at: u64, // Unix timestamp when this data was cached
}