Skip to main content

ServerSettings

Struct ServerSettings 

Source
pub struct ServerSettings {
    pub agent_prune_days: Option<u32>,
    pub controller_group: Option<String>,
    pub mail: Option<MailSection>,
    pub agent_install: Option<AgentInstallSection>,
    pub collect_retention_days: Option<u32>,
    pub result_output_retention_days: Option<u32>,
    pub session_ttl_hours: Option<u32>,
    pub check_status_stale_days: Option<u32>,
    pub object_store_caps: Option<ObjectStoreCaps>,
    pub support_codes: Vec<SupportCode>,
}
Expand description

Value stored in the server_settings KV bucket under the single key crate::kv::KEY_SERVER_SETTINGS. Operator-editable, backend-side server configuration that isn’t per-agent (so it doesn’t belong in agent_config’s layered scopes) and isn’t a fleet-wide switch every agent watches (so it doesn’t belong in fleet_config). Managed via the SPA Settings page’s “server settings” tab.

Every field is Option<_>: None (the default / the JSON value null / the field simply absent) means unset — fall back to the built-in default (ServerSettings::defaults), exactly like the agent layered-config scopes. The SPA renders the built-in default as a faint placeholder so a blank field shows what it resolves to, and when a real default is introduced here it appears in the UI (and takes effect for already-deployed-but-unset fleets) for free.

#[serde(default)] on the container keeps the document backward/forward compatible: a freshly-created or missing key decodes to all-None (pre-feature behaviour), an older backend reading a newer document ignores unknown fields, and a newer backend reading an older document fills the missing field with None. Keep that invariant — never add a field whose None doesn’t mean “behave as before”.

Fields§

§agent_prune_days: Option<u32>

Days a dead agent (one whose heartbeat stopped arriving) may linger in the agents registry before the backend cleanup task prunes its row.

None (unset) falls back to the built-in default; with no default configured that resolves to pruning disabled (see ServerSettings::effective_agent_prune_days). A positive value makes the cleanup sweep delete rows whose last_heartbeat is older than that many days. The agents table is a projection of the heartbeat stream, so a machine that’s merely offline (not gone) reappears on its next heartbeat (~30s cadence); only genuinely-retired machines stay gone.

§controller_group: Option<String>

Agent group whose members are the trusted controller-tier runners. A job with tier: controller (e.g. a feed: job that fetches an external URL) is dispatched ONLY to members of this group; None (unset) means controller-tier jobs run nowhere (fail-safe, so an external fetch never lands on an employee endpoint by accident). See crate::manifest::Tier. None ⇒ no controller runners configured, which is the safe default for a fresh deployment.

§mail: Option<MailSection>

Non-secret SMTP relay settings for outbound email (compliance-alert notifications, account setup links, …). Lives here (#884) rather than in backend.toml so an operator can edit it from the SPA without shell access to the host.

None (unset) ⇒ no relay configured, email is a no-op — the in-app / NATS notification path is unaffected. The SMTP password is deliberately not here (KV is readable over NATS): it stays sourced from the MailPassword registry secret / $KANADE_MAIL_PASSWORD and is combined with these settings when the backend builds its Mailer. Changes take effect on the next backend restart (the backend builds the Mailer once at startup — no live rebuild), which is acceptable per the #884 discussion.

§agent_install: Option<AgentInstallSection>

Self-service agent-installer knobs — see AgentInstallSection. None (unset) ⇒ the installer falls back to this backend’s own [nats] url and embeds no token. The nats_token inside is write-only: ServerSettings::redacted strips it from every response and the PUT merge preserves a stored token across a url-only edit (the same support-code trap: a form round-trip must never silently clear a secret).

§collect_retention_days: Option<u32>

Retention window (days) for collected file bundles — the collect: job archives uploaded to the collections Object Store (#219).

Unlike the other fields, this one has a real built-in default (DEFAULT_COLLECT_RETENTION_DAYS, 30 d): None (unset) falls back to it, so a blank field preserves the historical behaviour rather than disabling retention. A positive value tells the backend to reconcile the Object Store’s max_age to that many days (see bootstrap::reconcile_collect_retention), applied at boot and again whenever this document is saved from the SPA. Clamped to MAX_COLLECT_RETENTION_DAYS. The bucket’s max_bytes cap is untouched, so extending the window can’t make the store grow unbounded.

§result_output_retention_days: Option<u32>

How long OBJECT_RESULT_OUTPUT keeps an overflowed stdout/stderr blob, in days. Absent / null falls back to DEFAULT_RESULT_OUTPUT_RETENTION_DAYS.

Unlike collect_retention_days, this window is NOT how long the data survives — the results projector derefs each blob into the row before the INSERT, so the text lives in SQLite and the SPA reads it from there regardless. What it bounds is how long a -WipeDb re-projection can still recover that text: replay re-derefs, and an aged-out object comes back as empty stdout on the rebuilt row.

So this trades recovery window against standing volume, and volume is what filled the bucket into its cap (#1321 — a hard wall, not eviction) and stopped result delivery fleet-wide. Clamped to MAX_RESULT_OUTPUT_RETENTION_DAYS, which is STREAM_RESULTS’s own window: past it there is no message left to replay.

§session_ttl_hours: Option<u32>

How many hours a freshly-minted login token (SPA / CLI) stays valid before the caller must re-authenticate. Read backend-side by the login handler when it mints the JWT exp; changing it affects tokens minted after the change, not already-issued ones.

Like collect_retention_days this carries a real built-in default (DEFAULT_SESSION_TTL_HOURS, 24h): None (unset) falls back to it, so a blank field resolves to a usable window rather than an instantly-expired token. Clamped to 1..=MAX_SESSION_TTL_HOURS. The SPA renders 24 as a faint placeholder on a blank field.

§check_status_stale_days: Option<u32>

Days a check_status row may go without a fresh result before the Compliance page treats it as stale and hides it (#1032②). A PC that stops running a check — excluded from its target via a dynamic group, decommissioned, or its schedule removed — stops refreshing its row’s recorded_at; once that timestamp is older than this window the row is omitted from the default /api/checks view and its counts, so a machine no longer in scope stops showing as permanently failing a check it never runs.

Like collect_retention_days this carries a real built-in default (DEFAULT_CHECK_STATUS_STALE_DAYS, 30d), so the feature works with no configuration. 0 disables staleness (every row shown, the pre-feature behaviour); a positive value is the cutoff, clamped to MAX_CHECK_STATUS_STALE_DAYS. The row is never deleted — hiding is non-destructive so history and the compliance-alert prior-status are preserved.

§object_store_caps: Option<ObjectStoreCaps>

Per-bucket disk caps (MiB) for the five NATS Object Stores (#1247) — see ObjectStoreCaps. None (unset) ⇒ every bucket resolves to its built-in default, so a blank field preserves the out-of-box budget. Applied to the backing OBJ_* streams at backend boot and whenever this document is saved from the SPA (bootstrap::reconcile_object_store_max_bytes), which is also what finally delivers caps to buckets created before the caps existed.

§support_codes: Vec<SupportCode>

Operator-issued support codes — the helpdesk “裏コマンド” that reveals client.unlock-scoped jobs in the Client App. See SupportCode.

The one non-Option field in this document, because a list has an honest empty state: [] (the default) means no codes configured, so every client.unlock job stays hidden from everyone — the same fail-closed “behave as before” the Nones give the other fields.

Unlike the rest of the document this is read by agents as well as the backend (each agent verifies a typed code against these hashes locally, so an unlock still works during a backend outage), and it is not editable through the generic PUT /api/server-settings merge: a secret gets its own set-and-forget endpoint so a redacted document round-tripping through the SPA form can never blank a live code.

Implementations§

Source§

impl ServerSettings

Source

pub fn defaults() -> Self

Built-in defaults applied when a field is unset (None) in the stored document. Most fields default to None (no fleet-meaningful default — a blank prune window means “disabled” rather than some arbitrary number of days). The exceptions carry real defaults so a blank field still resolves to a sensible value: collect_retention_days (DEFAULT_COLLECT_RETENTION_DAYS, preserving the historical 30-day retention), result_output_retention_days (DEFAULT_RESULT_OUTPUT_RETENTION_DAYS, 7 days — deliberately NOT the historical 30, see that constant) and session_ttl_hours (DEFAULT_SESSION_TTL_HOURS, 24h).

Exposed via GET /api/server-settings/defaults so the SPA renders these as faint placeholders (mirroring the agent layered-config page’s built-in floor). Introducing a real default is a one-line change here that automatically shows up in the UI and applies to every deployment that hasn’t overridden the field.

Source

pub fn effective_object_store_caps(&self) -> ObjectStoreCaps

The caps document with every bucket resolved: stored values where set, built-in defaults elsewhere, each clamped to 1..=MAX_OBJECT_STORE_CAP_MIB so an out-of-band KV write can’t reach the broker unsanitised.

Source

pub fn support_code(&self, scope: &str) -> Option<&SupportCode>

The live code for scope, or None when the scope has no code, its code is disabled, or the hash was blanked (an API-redacted document). Every caller of this is a gate, so all three cases fail closed.

Source

pub fn redacted(self) -> Self

The same document with every support-code hash blanked — what an HTTP response is allowed to contain. Scope / label / TTL stay visible so the SPA can list and manage the codes; the secret material never leaves the backend, not even to an operator-authenticated caller. (GET /api/server-settings is viewer+, so an unredacted document would hand every read-only account an offline-crackable hash.)

Same treatment for agent_install.nats_token: it is a live broker credential baked into installer ZIPs, so it never leaves the backend either — the response carries only nats_token_set (computed HERE, never accepted from a client) so the SPA can render “configured”.

Source

pub fn effective_controller_group(&self) -> Option<&str>

The configured controller-tier runner group, trimmed, or None when unset / blank. None ⇒ controller-tier jobs run nowhere (fail-safe).

Source

pub fn effective_agent_prune_days(&self) -> u32

The effective dead-agent prune window in days: the stored value if set, else the built-in default, else 0 (= pruning disabled). The final unwrap_or(0) is the absent-everywhere floor, not a user-facing default — the cleanup task treats 0 as “don’t prune”.

Clamped to MAX_AGENT_PRUNE_DAYS so the cleanup task’s now - Duration::days(n) can never overflow DateTime (and panic the task), even if a value larger than the PUT handler allows was written to the KV out-of-band.

Source

pub fn effective_collect_retention_days(&self) -> u32

The effective collect-bundle retention window in days: the stored value if set, else the built-in default (DEFAULT_COLLECT_RETENTION_DAYS). Floored at 1 and clamped to MAX_COLLECT_RETENTION_DAYS so an out-of-band KV write can’t reach the Object Store max_age unsanitised. The floor matters specifically because NATS treats max_age: 0 as unlimited retention, not “evict immediately”: a stray 0 would silently make bundles never expire (defeating the auto-expire intent), so we coerce it to the shortest real window (1 day) instead. The PUT handler already rejects 0 / over-cap, so this is the belt-and-braces path for a hand-edited KV value.

Source

pub fn effective_result_output_retention_days(&self) -> u32

The effective result_output retention window in days: the stored value if set, else the built-in default. Floored at 1 and clamped to MAX_RESULT_OUTPUT_RETENTION_DAYS so a hand-written KV value can neither disable retention outright nor outlive the stream it exists to serve.

Source

pub fn effective_session_ttl_hours(&self) -> u32

The effective login-token lifetime in hours: the stored value if set, else the built-in DEFAULT_SESSION_TTL_HOURS. Floored at 1 and clamped to MAX_SESSION_TTL_HOURS so a zero/absent/out-of-band value can never mint an already-expired or overflow-inducing token. The PUT handler already rejects 0 / over-cap, so this is the belt-and-braces path for a hand-edited KV value.

Source

pub fn effective_check_status_stale_days(&self) -> u32

The effective check-staleness window in days: the stored value if set, else the built-in DEFAULT_CHECK_STATUS_STALE_DAYS. 0 means disabled (no row is ever hidden as stale) — deliberately NOT floored to 1 (unlike collect/session), because 0 is a meaningful “show everything” value here, the same convention as agent_prune_days. Clamped to MAX_CHECK_STATUS_STALE_DAYS so an out-of-band KV write can’t overflow the now - Duration::days(n) cutoff math.

Trait Implementations§

Source§

impl Clone for ServerSettings

Source§

fn clone(&self) -> ServerSettings

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 ServerSettings

Source§

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

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

impl Default for ServerSettings

Source§

fn default() -> ServerSettings

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

impl<'de> Deserialize<'de> for ServerSettings

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 ServerSettings

Source§

impl PartialEq for ServerSettings

Source§

fn eq(&self, other: &ServerSettings) -> 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 ServerSettings

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 ServerSettings

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<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

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> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
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> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
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 = Infallible

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.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more