pub struct ServerSettings {
pub agent_prune_days: Option<u32>,
pub controller_group: Option<String>,
pub mail: Option<MailSection>,
pub collect_retention_days: Option<u32>,
pub session_ttl_hours: Option<u32>,
}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.
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.
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.
Implementations§
Source§impl ServerSettings
impl ServerSettings
Sourcepub fn defaults() -> Self
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) 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.
Sourcepub fn effective_controller_group(&self) -> Option<&str>
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).
Sourcepub fn effective_agent_prune_days(&self) -> u32
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.
Sourcepub fn effective_collect_retention_days(&self) -> u32
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.
Sourcepub fn effective_session_ttl_hours(&self) -> u32
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.
Trait Implementations§
Source§impl Clone for ServerSettings
impl Clone for ServerSettings
Source§fn clone(&self) -> ServerSettings
fn clone(&self) -> ServerSettings
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for ServerSettings
impl Debug for ServerSettings
Source§impl Default for ServerSettings
impl Default for ServerSettings
Source§fn default() -> ServerSettings
fn default() -> ServerSettings
Source§impl<'de> Deserialize<'de> for ServerSettingswhere
ServerSettings: Default,
impl<'de> Deserialize<'de> for ServerSettingswhere
ServerSettings: Default,
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
impl Eq for ServerSettings
Source§impl PartialEq for ServerSettings
impl PartialEq for ServerSettings
Source§impl Serialize for ServerSettings
impl Serialize for ServerSettings
impl StructuralPartialEq for ServerSettings
Auto Trait Implementations§
impl Freeze for ServerSettings
impl RefUnwindSafe for ServerSettings
impl Send for ServerSettings
impl Sync for ServerSettings
impl Unpin for ServerSettings
impl UnsafeUnpin for ServerSettings
impl UnwindSafe for ServerSettings
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.