Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Step 1: nexusd Skeleton — Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: A Rust daemon that starts via systemd, handles signals, logs to journald, and serves a health check endpoint.

Architecture: Single binary (nexusd) in a Cargo workspace with a shared library crate (nexus-lib). Uses tokio for async, axum for HTTP, tracing for structured logging. Config loaded from YAML with sensible defaults.

Tech Stack:

  • tokio 1.x — async runtime
  • axum — HTTP server
  • clap 4.x — CLI argument parsing
  • tracing + tracing-subscriber — structured logging
  • serde + serde_norway — config deserialization (serde_yaml is deprecated/archived)
  • serde_json — API responses
  • dirs — XDG Base Directory paths

XDG Directory Layout:

  • Config: $XDG_CONFIG_HOME/nexus/nexus.yaml (default: ~/.config/nexus/nexus.yaml)
  • Data (workspaces, images): $XDG_DATA_HOME/nexus/ (default: ~/.local/share/nexus/)
  • State (database): $XDG_STATE_HOME/nexus/ (default: ~/.local/state/nexus/)
  • Runtime (sockets): $XDG_RUNTIME_DIR/nexus/ (default: /run/user/$UID/nexus/)

Task 1: Create Rust Workspace

Files:

  • Create: nexus/Cargo.toml
  • Create: nexus/nexusd/Cargo.toml
  • Create: nexus/nexusd/src/main.rs
  • Create: nexus/nexus-lib/Cargo.toml
  • Create: nexus/nexus-lib/src/lib.rs

Step 1: Create directory structure

mkdir -p nexus/nexusd/src nexus/nexus-lib/src

Step 2: Write workspace Cargo.toml

# nexus/Cargo.toml
[workspace]
members = ["nexusd", "nexus-lib"]
resolver = "2"

Step 3: Write nexus-lib Cargo.toml

# nexus/nexus-lib/Cargo.toml
[package]
name = "nexus-lib"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_norway = "0.9"
dirs = "6"

Step 4: Write nexus-lib stub

#![allow(unused)]
fn main() {
// nexus/nexus-lib/src/lib.rs
pub mod config;
}
#![allow(unused)]
fn main() {
// nexus/nexus-lib/src/config.rs
// Filled in Task 2
}

Step 5: Write nexusd Cargo.toml

# nexus/nexusd/Cargo.toml
[package]
name = "nexusd"
version = "0.1.0"
edition = "2021"

[dependencies]
nexus-lib = { path = "../nexus-lib" }
tokio = { version = "1", features = ["full"] }
axum = "0.8"
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

[dev-dependencies]
tower = { version = "0.5", features = ["util"] }

Step 6: Write minimal main.rs

// nexus/nexusd/src/main.rs
fn main() {
    println!("nexusd");
}

Step 7: Verify build

Run: cd nexus && cargo build

Expected: Compiles with no errors.

Step 8: Commit

git add nexus/
git commit -m "feat: create nexus Rust workspace with nexusd and nexus-lib crates"

Task 2: Configuration Types and Loading

Files:

  • Create: nexus/nexus-lib/src/config.rs
  • Modify: nexus/nexus-lib/src/lib.rs

Step 1: Write the failing test

#![allow(unused)]
fn main() {
// nexus/nexus-lib/src/config.rs

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn deserialize_minimal_config() {
        let yaml = r#"
api:
  listen: "127.0.0.1:8080"
"#;
        let config: Config = serde_norway::from_str(yaml).unwrap();
        assert_eq!(config.api.listen, "127.0.0.1:8080");
    }

    #[test]
    fn default_config_values() {
        let config = Config::default();
        assert_eq!(config.api.listen, "127.0.0.1:9600");
    }

    #[test]
    fn partial_yaml_uses_defaults() {
        let yaml = "{}";
        let config: Config = serde_norway::from_str(yaml).unwrap();
        assert_eq!(config.api.listen, "127.0.0.1:9600");
    }

    #[test]
    fn load_nonexistent_file_returns_not_found() {
        let result = Config::load("/nonexistent/path/config.yaml");
        assert!(result.is_err());
        assert!(result.unwrap_err().is_not_found());
    }

    #[test]
    fn load_invalid_yaml_returns_invalid() {
        let dir = std::env::temp_dir();
        let path = dir.join("nexus-test-bad-config.yaml");
        std::fs::write(&path, "{{invalid yaml").unwrap();
        let result = Config::load(&path);
        assert!(result.is_err());
        assert!(!result.unwrap_err().is_not_found());
        std::fs::remove_file(&path).ok();
    }
}
}

Step 2: Run tests to verify they fail

Run: cd nexus && cargo test -p nexus-lib

Expected: FAIL — Config type does not exist yet.

Step 3: Implement Config

#![allow(unused)]
fn main() {
// nexus/nexus-lib/src/config.rs
use serde::Deserialize;
use std::path::{Path, PathBuf};

#[derive(Debug)]
pub enum ConfigError {
    NotFound(std::io::Error),
    Invalid(String),
}

impl std::fmt::Display for ConfigError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ConfigError::NotFound(e) => write!(f, "config file not found: {e}"),
            ConfigError::Invalid(e) => write!(f, "invalid config: {e}"),
        }
    }
}

impl std::error::Error for ConfigError {}

impl ConfigError {
    pub fn is_not_found(&self) -> bool {
        matches!(self, ConfigError::NotFound(_))
    }
}

#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct Config {
    pub api: ApiConfig,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct ApiConfig {
    pub listen: String,
}

impl Default for Config {
    fn default() -> Self {
        Config {
            api: ApiConfig::default(),
        }
    }
}

impl Default for ApiConfig {
    fn default() -> Self {
        ApiConfig {
            listen: "127.0.0.1:9600".to_string(),
        }
    }
}

/// Returns the default config file path: $XDG_CONFIG_HOME/nexus/nexus.yaml
pub fn default_config_path() -> PathBuf {
    let config_dir = dirs::config_dir()
        .expect("cannot determine XDG_CONFIG_HOME")
        .join("nexus");
    config_dir.join("nexus.yaml")
}

impl Config {
    pub fn load(path: impl AsRef<Path>) -> Result<Self, ConfigError> {
        let content = std::fs::read_to_string(path).map_err(|e| {
            if e.kind() == std::io::ErrorKind::NotFound {
                ConfigError::NotFound(e)
            } else {
                ConfigError::Invalid(e.to_string())
            }
        })?;
        let config: Config =
            serde_norway::from_str(&content).map_err(|e| ConfigError::Invalid(e.to_string()))?;
        Ok(config)
    }
}
}

Step 4: Run tests to verify they pass

Run: cd nexus && cargo test -p nexus-lib

Expected: All 4 tests PASS.

Step 5: Commit

git add nexus/nexus-lib/
git commit -m "feat(nexus-lib): add Config type with YAML loading and defaults"

Task 3: Health Endpoint

Files:

  • Create: nexus/nexusd/src/api.rs
  • Modify: nexus/nexusd/src/main.rs

Step 1: Write the failing test

#![allow(unused)]
fn main() {
// nexus/nexusd/src/api.rs
use axum::{Json, Router, routing::get};
use serde::Serialize;

#[derive(Serialize)]
struct HealthResponse {
    status: String,
}

async fn health() -> Json<HealthResponse> {
    todo!()
}

pub fn router() -> Router {
    Router::new().route("/v1/health", get(health))
}

#[cfg(test)]
mod tests {
    use super::*;
    use axum::http::StatusCode;
    use axum::body::Body;
    use axum::http::Request;
    use tower::ServiceExt;

    #[tokio::test]
    async fn health_returns_ok() {
        let app = router();

        let response = app
            .oneshot(Request::get("/v1/health").body(Body::empty()).unwrap())
            .await
            .unwrap();

        assert_eq!(response.status(), StatusCode::OK);

        let body = axum::body::to_bytes(response.into_body(), usize::MAX)
            .await
            .unwrap();
        let json: serde_json::Value = serde_json::from_slice(&body).unwrap();

        assert_eq!(json["status"], "ok");
    }
}
}

Step 2: Run test to verify it fails

Run: cd nexus && cargo test -p nexusd api::tests::health_returns_ok

Expected: FAIL — todo!() panics.

Step 3: Implement the handler

Replace todo!() with the real implementation:

#![allow(unused)]
fn main() {
async fn health() -> Json<HealthResponse> {
    Json(HealthResponse {
        status: "ok".to_string(),
    })
}
}

Step 4: Add module to main.rs

// nexus/nexusd/src/main.rs
mod api;

fn main() {
    println!("nexusd");
}

Step 5: Run test to verify it passes

Run: cd nexus && cargo test -p nexusd api::tests::health_returns_ok

Expected: PASS.

Step 6: Commit

git add nexus/nexusd/
git commit -m "feat(nexusd): add GET /v1/health endpoint"

Task 4: Signal Handling and Graceful Shutdown

Files:

  • Create: nexus/nexusd/src/server.rs

This task creates the server startup and shutdown logic. Signal handling is difficult to unit test in isolation, so it will be verified in the integration test (Task 7).

Step 1: Implement the server module

#![allow(unused)]
fn main() {
// nexus/nexusd/src/server.rs
use crate::api;
use nexus_lib::config::Config;
use tokio::net::TcpListener;
use tracing::info;

pub async fn run(config: &Config) -> Result<(), Box<dyn std::error::Error>> {
    let app = api::router();

    let listener = TcpListener::bind(&config.api.listen).await?;
    info!(listen = %config.api.listen, "HTTP API ready");

    axum::serve(listener, app)
        .with_graceful_shutdown(shutdown_signal())
        .await?;

    info!("nexusd stopped");
    Ok(())
}

async fn shutdown_signal() {
    use tokio::signal::unix::{signal, SignalKind};

    let mut sigterm = signal(SignalKind::terminate())
        .expect("failed to install SIGTERM handler");
    let mut sigint = signal(SignalKind::interrupt())
        .expect("failed to install SIGINT handler");

    tokio::select! {
        _ = sigterm.recv() => info!("received SIGTERM, shutting down"),
        _ = sigint.recv() => info!("received SIGINT, shutting down"),
    }
}
}

Step 2: Commit

git add nexus/nexusd/src/server.rs
git commit -m "feat(nexusd): add server startup with graceful shutdown on SIGTERM/SIGINT"

Task 5: Logging

Files:

  • Create: nexus/nexusd/src/logging.rs

Step 1: Implement logging setup

#![allow(unused)]
fn main() {
// nexus/nexusd/src/logging.rs
use tracing_subscriber::{fmt, EnvFilter};

pub fn init() {
    let filter = EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| EnvFilter::new("info"));

    fmt()
        .with_env_filter(filter)
        .init();
}
}

Uses RUST_LOG env var when set, defaults to info. systemd captures stdout to journald automatically — no special journald integration needed.

Step 2: Commit

git add nexus/nexusd/src/logging.rs
git commit -m "feat(nexusd): add tracing-based logging with env filter"

Task 6: CLI Arguments and main() Wiring

Files:

  • Modify: nexus/nexusd/src/main.rs

Step 1: Wire everything together

// nexus/nexusd/src/main.rs
use clap::Parser;
use nexus_lib::config::{self, Config};
use tracing::{error, info};

mod api;
mod logging;
mod server;

#[derive(Parser)]
#[command(name = "nexusd", about = "WorkFort Nexus daemon")]
struct Cli {
    /// Path to configuration file
    /// [default: $XDG_CONFIG_HOME/nexus/nexus.yaml]
    #[arg(long)]
    config: Option<String>,
}

#[tokio::main]
async fn main() {
    let cli = Cli::parse();

    logging::init();

    let config_path = cli.config
        .map(std::path::PathBuf::from)
        .unwrap_or_else(config::default_config_path);

    let config = match Config::load(&config_path) {
        Ok(config) => {
            info!(config_path = %config_path.display(), "loaded configuration");
            config
        }
        Err(e) if e.is_not_found() => {
            info!("no config file found, using defaults");
            Config::default()
        }
        Err(e) => {
            error!(error = %e, path = %config_path.display(), "invalid configuration file");
            std::process::exit(1);
        }
    };

    info!("nexusd starting");

    if let Err(e) = server::run(&config).await {
        error!(error = %e, "daemon failed");
        std::process::exit(1);
    }
}

Step 2: Verify build

Run: cd nexus && cargo build

Expected: Compiles with no errors.

Step 3: Verify --help

Run: cd nexus && cargo run -p nexusd -- --help

Expected:

WorkFort Nexus daemon

Usage: nexusd [OPTIONS]

Options:
      --config <CONFIG>  Path to configuration file [default: $XDG_CONFIG_HOME/nexus/nexus.yaml]
  -h, --help             Print help

Step 4: Quick manual smoke test

Run: cd nexus && cargo run -p nexusd

Expected: Daemon starts, logs “HTTP API ready” with listen=127.0.0.1:9600. In another terminal:

Run: curl -s http://127.0.0.1:9600/v1/health | python -m json.tool

Expected:

{
    "status": "ok"
}

Kill the daemon with Ctrl-C. Expected: logs “received SIGINT, shutting down” and “nexusd stopped”, then exits cleanly.

Step 5: Commit

git add nexus/nexusd/
git commit -m "feat(nexusd): wire CLI args, config loading, logging, and server into main"

Task 7: Systemd Unit File

Files:

  • Create: nexus/dist/nexus.service

Step 1: Write the unit file

# nexus/dist/nexus.service
[Unit]
Description=WorkFort Nexus Daemon

[Service]
Type=exec
ExecStart=%h/.cargo/bin/nexusd
Restart=on-failure
RestartSec=5
Environment=RUST_LOG=info

[Install]
WantedBy=default.target

Notes:

  • Type=exec waits for the binary to launch successfully, catching missing binary errors (better than Type=simple).
  • %h expands to the user’s home directory. Binary path will be adjusted once packaging is set up.
  • StandardOutput=journal and SyslogIdentifier are omitted as they are systemd defaults.

Step 2: Test with systemd

# Install the unit file
mkdir -p ~/.config/systemd/user
cp nexus/dist/nexus.service ~/.config/systemd/user/
systemctl --user daemon-reload

# First, build and install the binary somewhere on PATH
cd nexus && cargo build --release
cp target/release/nexusd ~/.cargo/bin/

# Start and verify
systemctl --user start nexus
systemctl --user status nexus
curl -s http://127.0.0.1:9600/v1/health

# Check logs
journalctl --user -u nexus -n 20

# Stop
systemctl --user stop nexus

Expected: Service starts, health endpoint responds, logs appear in journald, service stops cleanly on stop.

Step 3: Commit

git add nexus/dist/
git commit -m "feat(nexusd): add systemd user service unit file"

Task 8: Integration Test

Files:

  • Create: nexus/nexusd/tests/daemon.rs
  • Modify: nexus/nexusd/Cargo.toml (add dev-dependencies)

Step 1: Add dev-dependencies

Add to nexus/nexusd/Cargo.toml:

Merge into the existing [dev-dependencies] section:

[dev-dependencies]
tower = { version = "0.5", features = ["util"] }
reqwest = { version = "0.13", features = ["json"] }
nix = { version = "0.30", features = ["signal"] }
serde_json = "1"

Step 2: Write the integration test

#![allow(unused)]
fn main() {
// nexus/nexusd/tests/daemon.rs
use std::process::{Command, Child};
use std::time::Duration;
use nix::sys::signal::{self, Signal};
use nix::unistd::Pid;

fn start_daemon() -> Child {
    let binary = env!("CARGO_BIN_EXE_nexusd");
    Command::new(binary)
        .env("RUST_LOG", "info")
        .spawn()
        .expect("failed to start nexusd")
}

fn stop_daemon(child: &Child) {
    signal::kill(Pid::from_raw(child.id() as i32), Signal::SIGTERM)
        .expect("failed to send SIGTERM");
}

#[tokio::test]
async fn daemon_starts_serves_health_and_stops() {
    let mut child = start_daemon();

    // Wait for the daemon to be ready
    let client = reqwest::Client::new();
    let mut ready = false;
    for _ in 0..50 {
        tokio::time::sleep(Duration::from_millis(100)).await;
        if client.get("http://127.0.0.1:9600/v1/health")
            .send()
            .await
            .is_ok()
        {
            ready = true;
            break;
        }
    }
    assert!(ready, "daemon did not become ready within 5 seconds");

    // Verify health endpoint
    let resp = client.get("http://127.0.0.1:9600/v1/health")
        .send()
        .await
        .expect("health request failed");
    assert_eq!(resp.status(), 200);

    let body: serde_json::Value = resp.json().await.unwrap();
    assert_eq!(body["status"], "ok");

    // Graceful shutdown
    stop_daemon(&child);
    let status = child.wait().expect("failed to wait on daemon");
    assert!(status.success(), "daemon exited with non-zero status: {}", status);
}
}

Step 3: Run the integration test

Run: cd nexus && cargo test -p nexusd --test daemon

Expected: PASS — daemon starts, health endpoint returns {"status":"ok"}, SIGTERM causes clean exit with code 0.

Note: This test uses a hardcoded port (9600). If the port is in use, the test will fail. For now this is acceptable — a single integration test doesn’t need port randomization. Address this when adding more integration tests.

Step 4: Commit

git add nexus/nexusd/
git commit -m "test(nexusd): add integration test for daemon lifecycle"

Verification Checklist

After all tasks are complete, verify the following:

  • cargo build succeeds with no warnings
  • cargo test --workspace — all tests pass
  • cargo run -p nexusd -- --help — prints usage
  • curl localhost:9600/v1/health — returns {"status":"ok"}
  • systemctl --user start nexus — daemon starts
  • journalctl --user -u nexus — shows structured log output
  • systemctl --user stop nexus — daemon stops cleanly (exit 0)
  • Sending SIGTERM to the process causes graceful shutdown
  • Sending SIGINT (Ctrl-C) to the process causes graceful shutdown
  • No config file present — daemon starts with defaults
  • Config file present — daemon uses configured values