luhproc 0.1.2

A lightweight background process manager
Documentation
# luhproc

[<img alt="github" src="https://img.shields.io/badge/github-calizoots/luhproc-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/calizoots/luhproc)
[<img alt="crates.io" src="https://img.shields.io/crates/v/luhproc.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/luhproc)
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-luhproc-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/luhproc)

A lightweight background process manager
<br>
> made with love s.c

`luhproc` provides a simple system for spawning, tracking, and stopping
background worker processes in Rust applications.

It is built around two core ideas:
- **Workers are identified by environment variables** (e.g. `MY_TASK=1`)
- **Workers run in background mode when started from the same binary**

This makes it easy to run small persistent tasks (indexers, watchers,
refreshers, schedulers, etc.) without needing systemd, Docker, or any
external supervisor.

## Quick Start

Add `luhtwin` to your `Cargo.toml`:
```toml
[dependencies]
luhproc = "0.1"
```

### Example

```rust

static PM: OnceLock<ProcessManager> = OnceLock::new();

fn child_work() -> LuhTwin<()> {
    info!("hello from the child thread");
    Ok(())
}

fn main() -> LuhTwin<() {
    PM.set(process_manager!("MOTHAPP" => start_moth)
           .encase(|| "failed to make process_manager")?)
        .unwrap();

    PM.get().unwrap().check()
        .encase(|| "failed to run child process")?;

    PM.get().unwrap().start("MOTHAPP", "app", None)
    .encase(|| "failed to start moth app")?;
}
```

---

## Basic Concept

You define tasks using the [`process_manager!`] macro:

```rust
process_manager! {
    "MY_TASK" => my_background_function,
    "REFRESH_CACHE" => refresh_cache_worker,
};
```

A task is triggered when the binary starts **with that environment variable set**.

For example:

```sh
MY_TASK=1 ./myapp
```

The main process will immediately run the function associated with the task,
**then exit after finishing**.

The task can be launched in the background through the API:

```rust
pm.start("MY_TASK", "unique-id", None)?;
```

Each running worker is stored inside a temporary directory containing:

```txt
<tmp>/luhproc/my-task-<hash>/
├── out.log   # stdout
├── err.log   # stderr
└── pid       # process ID
```

---

# Architecture

## `ChildTask`

Represents one runnable background task.

```rust
pub struct ChildTask {
    pub id: String,
    pub env_var: &'static str,
    pub work: fn() -> LuhTwin<()>,
}
```

- `id` — unique instance identifier (affects directory hashing)
- `env_var` — environment variable that triggers the worker
- `work` — the task function itself

### Generated Directory Name

Each task instance gets its own hashed directory:

```txt
my-task-3fa92k19cd12
```

This allows multiple instances of the same task type.

---

## `ProcessManager`

Main API for controlling worker tasks.

```rust
pub struct ProcessManager {
    pub tasks: Vec<ChildTask>,
}
```

### Registering Tasks

```rust
let mut pm = ProcessManager::new()?;
pm.register_task("MY_TASK", my_worker_fn);
```

Or with the macro:

```rust
let pm = process_manager! {
    "MY_TASK" => my_worker_fn,
    "SYNC" => sync_worker_fn,
}?;
```

### Starting a Worker

Spawns a background process by re-invoking the binary with an env var:

```rust
pm.start("MY_TASK", "session42", None)?;
```

Internally:
- creates temp directory  
- forks current executable  
- writes PID file  
- redirects stdout/stderr  

### Stopping a Worker

```rust
pm.stop("MY_TASK", Some("session42"))?;
```

Or stop *all* workers of that type:

```rust
pm.stop("MY_TASK", None)?;
```

### Checking Worker Status

```rust
let details = pm.info("MY_TASK", "session42")?;
println!("{details}");
```

Example output:

```txt
task: MY_TASK (id: session42)
pid: 39241
directory: /tmp/luhproc/my-task-a8fd93c2e1a3
log file: /tmp/luhproc/.../out.log
error file: /tmp/luhproc/.../err.log
```

---

# File Layout

```
/tmp/luhproc/
/
/my-task-ae92f139ab23/
/    pid        # process ID
/    out.log    # stdout of worker
/    err.log    # stderr of worker
```

If `LUHPROC_TMP_DIR` is set, that directory is used instead of `/tmp` or dev temp.

---

#  Environment Trigger Check

At the start of your application, call:

```rust
pm.check()?;
```

If the process was started as a worker, the task runs inline and the parent
process exits afterwards.

Your `main` typically looks like:

```rust
fn main() -> LuhTwin<()> {
    let pm = process_manager! {
        "MY_TASK" => run_worker,
    }?;

    // check for background-worker mode
    pm.check()?;

    // normal application logic
    run_cli()?;
    Ok(())
}
```

---

# Behavior Notes

- Workers are single-process, not threadpools.  
- Stopping uses `SIGTERM` (Unix-only via `nix`).  
- If a PID file becomes invalid, `stop()` will report an error but still clean up.  
- If your worker loops forever, ensure it handles SIGTERM gracefully.  

---

# Example Worker

```rust
fn ping_worker() -> LuhTwin<()> {
    loop {
        println!("ping!");
        std::thread::sleep(std::time::Duration::from_secs(1));
    }
}
```

And launching it:

```rust
let pm = process_manager! {
    "PING_WORKER" => ping_worker,
}?;

pm.check()?;


pm.start("PING_WORKER", "main", None)?;
```