Skip to main content

cloudillo_core/
core_settings.rs

1// SPDX-FileCopyrightText: Szilárd Hajba
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4//! Core server settings registration
5//!
6//! Registers global server-level settings for logging, features, etc.
7
8use crate::prelude::*;
9use crate::scheduler::CronSchedule;
10use crate::settings::{
11	PermissionLevel, SettingDefinition, SettingScope, SettingValue, SettingsRegistry,
12};
13
14/// Validator for any setting whose value is handed to `TaskSchedulerBuilder::cron`.
15/// Exported for the other crates that register `*_cron` settings.
16///
17/// Uses the scheduler's own parser, so the two cannot disagree about what is accepted.
18/// Without it, an expression like `"every night"` is stored happily and only fails at
19/// the next boot, where the builder silently degrades the task from recurring to
20/// one-shot: it runs once, the finish handler retires the row, and the schedule is
21/// gone for good.
22pub fn cron_validator(v: &SettingValue) -> ClResult<()> {
23	let SettingValue::String(s) = v else {
24		return Err(Error::ValidationError("Cron expression must be a string".into()));
25	};
26	CronSchedule::parse(s).map(|_| ())
27}
28
29/// Register all core settings
30pub fn register_settings(registry: &mut SettingsRegistry) -> ClResult<()> {
31	// Server registration enabled
32	registry.register(
33		SettingDefinition::builder("server.registration_enabled")
34			.description("Allow new user registrations")
35			.default(SettingValue::Bool(true))
36			.scope(SettingScope::Global)
37			.permission(PermissionLevel::Admin)
38			.build()?,
39	)?;
40
41	// Nightly meta-database maintenance (FTS merge + WAL checkpoint + VACUUM)
42	registry.register(
43		SettingDefinition::builder("core.db_maintenance_cron")
44			.description(
45				"Cron expression for the nightly database maintenance schedule (5-field: \
46				 'minute hour day month weekday')",
47			)
48			.default(SettingValue::String("20 4 * * *".into()))
49			.scope(SettingScope::Global)
50			.permission(PermissionLevel::Admin)
51			.validator(cron_validator)
52			.build()?,
53	)?;
54	// An integer percent because `SettingValue` has no float variant.
55	registry.register(
56		SettingDefinition::builder("core.vacuum_min_free_pct")
57			.description(
58				"Percentage of the metadata database's pages that must be free before the \
59				 nightly maintenance rewrites it to return the space to the filesystem. The \
60				 rewrite blocks every other writer while it runs, so a low value trades \
61				 availability for disk.",
62			)
63			.default(SettingValue::Int(20))
64			.scope(SettingScope::Global)
65			.permission(PermissionLevel::Admin)
66			// `reclaim_space` compares `free_pct >= min_free_pct` without clamping, so a
67			// negative value rewrites the whole database every night. Above 100 is the
68			// harmless converse — never vacuum — but just as certainly a typo.
69			.validator(|v| match v {
70				SettingValue::Int(n) if (0..=100).contains(n) => Ok(()),
71				_ => Err(Error::ValidationError(
72					"Vacuum free-page threshold must be an integer percent between 0 and 100"
73						.into(),
74				)),
75			})
76			.build()?,
77	)?;
78
79	// Wildcard pattern for UI settings - allows storing arbitrary UI preferences
80	registry.register(
81		SettingDefinition::builder("ui.*")
82			.description("User interface settings and preferences")
83			.scope(SettingScope::Tenant)
84			.permission(PermissionLevel::User)
85			.optional(true)
86			.build()?,
87	)?;
88
89	// Wildcard pattern for application settings - allows storing arbitrary app state
90	registry.register(
91		SettingDefinition::builder("app.*")
92			.description("Application-specific settings and state")
93			.scope(SettingScope::Tenant)
94			.permission(PermissionLevel::User)
95			.optional(true)
96			.build()?,
97	)?;
98
99	Ok(())
100}
101
102// vim: ts=4