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
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), 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.
Sourcepub fn effective_object_store_caps(&self) -> ObjectStoreCaps
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.
Sourcepub fn support_code(&self, scope: &str) -> Option<&SupportCode>
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.
Sourcepub fn redacted(self) -> Self
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”.
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_result_output_retention_days(&self) -> u32
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.
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.
Sourcepub fn effective_check_status_stale_days(&self) -> u32
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
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.