cubesys: add cube systemd daemon (cube-server) + socket client (cubec)
Implements the requested cube service: a long-lived daemon that holds ONE CubeStore for its whole lifetime and serves the cube command language over a Unix-domain socket, plus cubec to talk to it. - cubesys::commands: factored the single command interpreter (Session::exec) so cube REPL, cubec client, and the daemon run identical logic - cubesys::net: dependency-free length-framed AF_UNIX transport - cubesys::persist: dependency-free JSON snapshot (atomic tmp+rename) so the store -- including sealed/encrypted records -- survives daemon restarts - cube-server: listens on $XDG_RUNTIME_DIR/cube/cube.sock, snapshots to $XDG_STATE_HOME/cube/cube-store.json, replays on startup - cubec: one-shot + REPL client over the socket - cube.rs trimmed to a thin REPL/script/demo driver (help text updated) - /etc/systemd/system/cube.service: runs as luulu, ProtectSystem=strict, RestrictAddressFamilies=AF_UNIX, Restart=on-failure; enabled + active - integration.md documents the daemon + caveat (open rewrites plaintext) Verified: ./check (fmt+tests+clippy -D warnings) green; ./check mount (27 FUSE e2e) green; socket CLI round-trips; sealed record survived a full service restart and reopened+r with original value.
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
//! `cubec` — the CUBELinux-2 system client.
|
||||
//!
|
||||
//! Talks to `cube-server` over its Unix-domain socket. Two modes:
|
||||
//!
|
||||
//! cubec <command...> one-shot: send a single command, print the reply
|
||||
//! cubec REPL: read command lines from stdin, one per
|
||||
//! connection, printing each reply (like `cube repl`)
|
||||
//!
|
||||
//! The socket defaults to /run/cube/cube.sock (or $XDG_RUNTIME_DIR/cube/cube.sock)
|
||||
//! and can be overridden with `--socket PATH`.
|
||||
|
||||
use std::io::{BufRead, Write};
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
use cubesys::net::{read_frame, write_frame};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let socket_path = socket_from_args(&args);
|
||||
|
||||
// Build the command from everything that isn't `--socket PATH`.
|
||||
let mut rest: Vec<String> = Vec::new();
|
||||
let mut i = 1;
|
||||
while i < args.len() {
|
||||
if args[i] == "--socket" {
|
||||
i += 2; // skip the flag and its value
|
||||
continue;
|
||||
}
|
||||
rest.push(args[i].clone());
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if rest.is_empty() {
|
||||
repl(&socket_path);
|
||||
} else {
|
||||
let cmd = rest.join(" ");
|
||||
match request(&socket_path, &cmd) {
|
||||
Ok(reply) => println!("{reply}"),
|
||||
Err(e) => {
|
||||
eprintln!("cubec: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn repl(socket_path: &str) {
|
||||
let stdin = std::io::stdin();
|
||||
for line in stdin.lock().lines() {
|
||||
let line = match line {
|
||||
Ok(l) => l,
|
||||
Err(_) => break,
|
||||
};
|
||||
if line.trim().is_empty() || line.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
match request(socket_path, &line) {
|
||||
Ok(reply) => println!("{reply}"),
|
||||
Err(e) => eprintln!("error: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Open a connection, send one command frame, return the reply frame.
|
||||
fn request(socket_path: &str, command: &str) -> Result<String, String> {
|
||||
let mut stream = UnixStream::connect(socket_path)
|
||||
.map_err(|e| format!("cannot connect to {socket_path}: {e}"))?;
|
||||
write_frame(&mut stream, command).map_err(|e| format!("write: {e}"))?;
|
||||
read_frame(&mut stream).map_err(|e| format!("read: {e}"))
|
||||
}
|
||||
|
||||
fn socket_from_args(args: &[String]) -> String {
|
||||
let mut i = 0;
|
||||
while i < args.len() {
|
||||
if args[i] == "--socket" {
|
||||
if let Some(v) = args.get(i + 1) {
|
||||
return v.clone();
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
if let Ok(runtime) = std::env::var("XDG_RUNTIME_DIR") {
|
||||
return format!("{runtime}/cube/cube.sock");
|
||||
}
|
||||
"/run/cube/cube.sock".to_string()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn _flush() {
|
||||
let _ = std::io::stdout().flush();
|
||||
}
|
||||
Reference in New Issue
Block a user