Skip to main content

AppConfig

Struct AppConfig 

Source
pub struct AppConfig {
Show 40 fields pub name: String, pub script: String, pub args: Vec<String>, pub cwd: Option<String>, pub interpreter: Option<String>, pub env: BTreeMap<String, String>, pub instances: u32, pub autorestart: bool, pub autostart: bool, pub stop_exit_codes: Vec<i32>, pub min_uptime: UpDuration, pub max_restarts: u32, pub restart_delay: Option<UpDuration>, pub exp_backoff_restart_delay: Option<UpDuration>, pub kill_signal: Option<String>, pub kill_timeout: UpDuration, pub shutdown_with_message: bool, pub listen_timeout: UpDuration, pub graceful_timeout: UpDuration, pub action_timeout: UpDuration, pub max_memory: Option<MemSize>, pub watch: bool, pub ignore_watch: Vec<String>, pub watch_delay: Option<UpDuration>, pub cron_restart: Option<String>, pub fold: Option<String>, pub user: Option<String>, pub group: Option<String>, pub out_file: Option<String>, pub err_file: Option<String>, pub merge_logs: bool, pub channel: bool, pub stdin: bool, pub wait_ready: bool, pub reuse_port: bool, pub readiness_probe: Option<ProbeConfig>, pub liveness_probe: Option<ProbeConfig>, pub watch_options: Vec<String>, pub cron_timezone: Option<String>, pub increment_var: Option<String>,
}
Expand description

Per-app configuration — one sheep’s entry in a Flockfile

Field names are the Flockfile contract (sheep-native; pm2 spellings are rejected — the importer translates them). Unknown fields are errors so typos fail loudly at parse time.

§Example

use shep_core::config::AppConfig;

let app: AppConfig = toml::from_str("name = \"web\"\nscript = \"./srv\"").unwrap();
assert!(app.autorestart); // spec default

Fields§

§name: String

Unique sheep name (required)

§script: String

Executable or script path (required)

§args: Vec<String>

Arguments passed to the script

§cwd: Option<String>

Working directory (default: daemon’s cwd at spawn registration)

§interpreter: Option<String>

Interpreter override ("none" = run script directly)

§env: BTreeMap<String, String>

Environment for the sheep (merged over the daemon’s filtered env)

§instances: u32

Instance count (“cluster” = N fork instances; spec §4)

§autorestart: bool

Restart on unexpected exit

§autostart: bool

Start when the daemon starts / on shep muster

§stop_exit_codes: Vec<i32>

Exit codes treated as clean stop (no restart)

§min_uptime: UpDuration

Uptime below this marks an exit as unstable

§max_restarts: u32

Consecutive unstable exits before errored

§restart_delay: Option<UpDuration>

Fixed delay before every restart (alternative to backoff)

§exp_backoff_restart_delay: Option<UpDuration>

Initial backoff delay; grows ×1.5 capped at 15s (spec §4)

Defaults to 100ms, not unset. An unstable exit (sooner than min_uptime) with neither this nor restart_delay configured would otherwise restart with no delay at all, so an app that can never start (a missing dependency, a bad config) would burn its whole max_restarts budget inside a second, logging the same failure dozens of times.

All of the above assumes restart_delay is unset. A fixed restart_delay takes precedence over this field on every exit, stable or not, so a stable exit restarts immediately only while restart_delay stays unset, and setting this field to "0" disables the backoff without producing an immediate restart if a nonzero restart_delay is also configured.

§kill_signal: Option<String>

Stop signal, one of SIGTERM/SIGINT/SIGQUIT/SIGUSR2 (the SIG prefix and the case are both optional). Unset means SIGTERM.

A String rather than a KillSignal so the Flockfile schema and this struct’s wire form stay plain text; normalize is what refuses a name outside that set, the same split cron_restart and the watch globs already use.

§kill_timeout: UpDuration

Grace period between stop signal and SIGKILL

§shutdown_with_message: bool

Send {"kind":"shutdown"} on the shepherd channel instead of a signal

§listen_timeout: UpDuration

Readiness fallback window when no ready signal/probe configured

§graceful_timeout: UpDuration

Drain window for the old instance during reload

§action_timeout: UpDuration

How long a triggered action gets to answer on the shepherd channel before its row becomes ActionOutcome::TimedOut.

Defaults to 3s — comfortably under the 5s an RPC caller gets when it sends no deadline of its own (shep-client’s DEFAULT_DEADLINE, mirrored daemon-side as rpc’s DEFAULT_DEADLINE_MS). The margin matters more than the number: push this past that budget and a caller using the plain default gives up with DeadlineExceeded before the daemon’s own honest TimedOut row ever reaches it. A legitimately slow action (a cache flush, say) can still ask for longer, but its caller has to ask for a longer deadline in step — Client::request_with_deadline, the way shep logs -f already asks for LOG_PLANE_DEADLINE rather than the client’s default. normalize refuses a value no caller could ever satisfy, however long a deadline it asks for; a value merely above the default budget is a caller’s choice to widen its own deadline, not a config error this crate can see.

§max_memory: Option<MemSize>

Memory ceiling — polling enforcer restarts above this

§watch: bool

Watch files and restart on change

§ignore_watch: Vec<String>

Watch ignore globs (defaults added daemon-side: dot-entries, node_modules)

§watch_delay: Option<UpDuration>

Watch debounce window (default 500ms, applied daemon-side)

§cron_restart: Option<String>

Cron pattern for scheduled restarts (croner dialect)

§fold: Option<String>

Fold (group) this sheep belongs to

§user: Option<String>

Run as this user (unix)

§group: Option<String>

Run as this group (unix)

§out_file: Option<String>

Stdout log file (default: $SHEP_HOME/logs/<name>-<instance>-out.log; merge_logs collapses to <name>-out.log)

§err_file: Option<String>

Stderr log file (default: $SHEP_HOME/logs/<name>-<instance>-err.log; merge_logs collapses to <name>-err.log)

§merge_logs: bool

Merge instance logs into one file pair

§channel: bool

Open the shepherd channel on fd 3 for this app on its own, without needing wait_ready or shutdown_with_message to imply it.

Defaults to false: a socketpair plus two pump tasks per sheep is real cost weighed against spec §14.11’s single-digit-MB idle-RSS goal, so a channel is opened only when something asks for one.

§stdin: bool

Open a pipe on this sheep’s stdin, so shep whisper can write to it.

Defaults to false, and the default is the decision rather than a convenience. Without it a sheep gets /dev/null on fd 0, which is what every sheep has had until now, and three things argue for keeping it that way unless an app asks otherwise:

  • Flipping it for the whole flock is a behaviour change to processes nobody asked to change.
  • Programs detect stdin. A closed or null fd 0 is how a great many programs decide they are non-interactive — no prompt, no pager, no readline, no colour. Handing them a pipe silently moves them to the other branch.
  • It costs a descriptor and a pump task per sheep for the whole life of the process, against spec §14.11’s single-digit-MB idle-RSS goal — the same budget Self::channel’s own default is protecting.

Unlike channel, nothing implies this: wait_ready and shutdown_with_message both need fd 3 and so turn channel on for you, while nothing in shep needs a sheep’s stdin except an operator typing shep whisper. A sheep without it answers a no_stdin row and names this field.

The pipe’s write end lives as long as the sheep does, so the app sees EOF on stdin when the process is on its way out, never before.

§wait_ready: bool

Expect {"kind":"ready"} on the shepherd channel

§reuse_port: bool

Asserts that the app itself sets SO_REUSEPORT before it binds — shep binds nothing, so it cannot set the option on the app’s behalf. The child process owns the mechanism (Node ≥22’s reusePort, Go’s net.ListenConfig.Control, nginx’s reuseport); shep’s contribution is permission for the old and new instance to overlap during reload, not the socket option itself.

That permission is what the field buys, and it is read by exactly one thing: which reload the daemon runs for the app.

  • Unset, and the app has a readiness_probe: reload is SERIAL. The instance being replaced is drained first and its replacement is spawned into the empty slot, so the app is down for the length of the drain. That is the cost of an honest answer — while both instances are up, a probe against an address cannot say which of them answered, and shep would take the outgoing instance’s reply as proof the incoming one is ready.
  • Set: reload OVERLAPS. The replacement is spawned alongside the instance it replaces and takes over without a gap — if the app really does set SO_REUSEPORT. If it does not, the replacement takes EADDRINUSE and the reload fails, which is the failure this field exists to keep opt-in.

An app with no readiness_probe overlaps either way: with nothing probing an address, there is no answer for the wrong instance to give. So does one using wait_ready, because the shepherd channel a replacement reports on is its own — the instance being replaced has no way to answer it. Both of those need SO_REUSEPORT as much as a reuse_port app does if they bind an address, since they are overlapped too; what this field changes is which apps get overlapped, not what an overlap costs.

Setting this on an app that does NOT set the socket option is the one way to get it wrong, and shep cannot check it: the option is set inside the child, after the fork, on a socket shep never sees.

§readiness_probe: Option<ProbeConfig>

Readiness probe — gates reload’s AwaitReady (spec §7)

§liveness_probe: Option<ProbeConfig>

Liveness probe — failures feed the restart policy (spec §7)

§watch_options: Vec<String>

Watch include globs (empty = watch cwd)

§cron_timezone: Option<String>

Timezone for cron_restart (IANA name)

§increment_var: Option<String>

Removed. Set your own variable to {{instance}} in env instead.

Kept only so normalize can reject it with that instruction: a deny_unknown_fields serde error would name no replacement. Remove in 0.2.

Implementations§

Source§

impl AppConfig

Source

pub fn minimal(name: &str, script: &str) -> Self

A minimal config with spec defaults — the programmatic entry point

Source

pub fn drifted_fields(&self, other: &Self) -> Vec<String>

The names of the fields whose values differ between self and other, in field-name order.

Names only, never values. The one caller sends this list across the wire to be printed at an operator, and AppConfig::env carries secrets, so a differing env reports "env" and stops there (IR-41).

Compare configs that have both been through normalize. Two configs differing only in what normalization would have filled in are not a difference an operator can act on, and reporting them would make the caller noisy about nothing.

§Example
use shep_core::config::AppConfig;

let stored = AppConfig::minimal("web", "./srv");
let mut edited = stored.clone();
edited.cwd = Some("/srv".to_string());

assert_eq!(stored.drifted_fields(&edited), vec!["cwd".to_string()]);
assert!(stored.drifted_fields(&stored).is_empty());

Trait Implementations§

Source§

impl Clone for AppConfig

Source§

fn clone(&self) -> AppConfig

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for AppConfig

Debug implementation does not leak env values (IR-41)

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for AppConfig

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for AppConfig

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Eq for AppConfig

Source§

impl JsonSchema for AppConfig

Source§

fn schema_name() -> Cow<'static, str>

The name of the generated JSON Schema. Read more
Source§

fn schema_id() -> Cow<'static, str>

Returns a string that uniquely identifies the schema produced by this type. Read more
Source§

fn json_schema(generator: &mut SchemaGenerator) -> Schema

Generates a JSON Schema for this type. Read more
Source§

fn inline_schema() -> bool

Whether JSON Schemas generated for this type should be included directly in parent schemas, rather than being re-used where possible using the $ref keyword. Read more
Source§

impl PartialEq for AppConfig

Source§

fn eq(&self, other: &AppConfig) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for AppConfig

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for AppConfig

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.