liminal_server/services/templates/
configuration.rs1use super::TemplatePolicy;
4use crate::config::types::ServiceTemplateConfig;
5use serde::{Deserialize, Deserializer};
6
7#[derive(Clone, Debug, PartialEq, Eq, Default)]
9pub struct TemplateSources {
10 configured: Vec<ServiceTemplateConfig>,
11 pub policy: Option<TemplatePolicy>,
13}
14impl TemplateSources {
15 #[must_use]
17 pub fn is_empty(&self) -> bool {
18 self.configured.is_empty() && self.policy.is_none()
19 }
20}
21
22impl<'de> Deserialize<'de> for TemplateSources {
23 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
24 #[derive(Deserialize)]
25 #[serde(deny_unknown_fields)]
26 struct Table {
27 #[serde(default)]
28 configured: Vec<ServiceTemplateConfig>,
29 roots: Vec<std::path::PathBuf>,
30 command_bytes: std::num::NonZeroU64,
31 records: std::num::NonZeroUsize,
32 arguments: std::num::NonZeroUsize,
33 environment_names: std::num::NonZeroUsize,
34 text_bytes: std::num::NonZeroUsize,
35 stop_timeout_ms: std::num::NonZeroU64,
36 }
37 #[derive(Deserialize)]
38 #[serde(untagged)]
39 enum Wire {
40 Legacy(Vec<ServiceTemplateConfig>),
41 Table(Table),
42 }
43 Ok(match Wire::deserialize(deserializer)? {
44 Wire::Legacy(configured) => Self {
45 configured,
46 policy: None,
47 },
48 Wire::Table(table) => Self {
49 configured: table.configured,
50 policy: Some(TemplatePolicy {
51 roots: table.roots,
52 command_bytes: table.command_bytes,
53 records: table.records,
54 arguments: table.arguments,
55 environment_names: table.environment_names,
56 text_bytes: table.text_bytes,
57 stop_timeout_ms: table.stop_timeout_ms,
58 }),
59 },
60 })
61 }
62}
63impl std::ops::Deref for TemplateSources {
64 type Target = [ServiceTemplateConfig];
65 fn deref(&self) -> &Self::Target {
66 &self.configured
67 }
68}
69impl<'a> IntoIterator for &'a TemplateSources {
70 type Item = &'a ServiceTemplateConfig;
71 type IntoIter = std::slice::Iter<'a, ServiceTemplateConfig>;
72 fn into_iter(self) -> Self::IntoIter {
73 self.configured.iter()
74 }
75}
76impl From<Vec<ServiceTemplateConfig>> for TemplateSources {
77 fn from(configured: Vec<ServiceTemplateConfig>) -> Self {
78 Self {
79 configured,
80 policy: None,
81 }
82 }
83}
84
85pub fn collect_errors(config: &crate::config::types::ServerConfig, errors: &mut Vec<String>) {
87 let Some(lys) = config.auth.as_ref().and_then(|auth| auth.lys.as_ref()) else {
88 return;
89 };
90 if let Some(policy) = &lys.templates.policy {
91 if policy.roots.is_empty()
92 || policy
93 .roots
94 .iter()
95 .any(|root| !root.is_absolute() || !root.is_dir())
96 {
97 errors
98 .push("[auth.lys.templates] roots must name existing absolute directories".into());
99 }
100 if lys.construction.is_none() {
101 errors.push("[auth.lys.templates] roots require [auth.lys.construction]".into());
102 }
103 }
104 if let Some(section) = &lys.construction {
105 if lys.agents.is_none() {
106 errors.push("[auth.lys.construction] requires [auth.lys.agents]".into());
107 }
108 if lys.machine.is_none() {
109 errors.push("[auth.lys.construction] requires [auth.lys.machine]".into());
110 }
111 if !lys.live {
112 errors.push(
113 "[auth.lys.construction] requires auth.lys.live for deliberate drafts".into(),
114 );
115 }
116 if config.persistence_path.is_none() || lys.app_credential_path.is_none() {
117 errors.push(
118 "[auth.lys.construction] requires persistence_path and app_credential_path".into(),
119 );
120 }
121 if section.limits.snapshot_bytes.get() < section.limits.request_bytes.get() {
122 errors
123 .push("auth.lys.construction.limits.snapshot_bytes is below request_bytes".into());
124 }
125 for source in [§ion.grants.plan, §ion.grants.serve] {
126 if source.is_empty()
127 || source.len() > section.limits.text_bytes.get()
128 || source.chars().any(char::is_control)
129 {
130 errors.push(
131 "auth.lys.construction.grants must name the machine's source grants".into(),
132 );
133 }
134 }
135 }
136}
137
138impl std::fmt::Debug for ServiceTemplateConfig {
139 fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140 out.debug_struct("ServiceTemplateConfig")
141 .field("subjects", &self.subjects.len())
142 .field("stop_timeout_ms", &self.stop_timeout_ms)
143 .finish_non_exhaustive()
144 }
145}