Skip to main content

systemprompt_models/profile/
server.rs

1//! Server configuration.
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
6#[serde(deny_unknown_fields)]
7pub struct ServerConfig {
8    pub host: String,
9
10    pub port: u16,
11
12    pub api_server_url: String,
13
14    pub api_internal_url: String,
15
16    pub api_external_url: String,
17
18    #[serde(default)]
19    pub use_https: bool,
20
21    #[serde(default)]
22    pub cors_allowed_origins: Vec<String>,
23
24    #[serde(default)]
25    pub content_negotiation: ContentNegotiationConfig,
26
27    #[serde(default)]
28    pub security_headers: SecurityHeadersConfig,
29
30    /// Stable identifier for this replica. Empty/unset resolves to the
31    /// OS hostname (or a generated short id) at config build time.
32    #[serde(default)]
33    pub instance_id: Option<String>,
34
35    /// Global cap on concurrent A2A SSE streams for this replica.
36    #[serde(default = "default_max_concurrent_streams")]
37    pub max_concurrent_streams: usize,
38
39    /// CIDR ranges whose immediate-peer requests are allowed to set
40    /// `X-Forwarded-For`, `X-Real-IP`, and `CF-Connecting-IP`. Empty
41    /// means the platform treats every connection as direct and ignores
42    /// those headers — the only safe default behind no proxy. Each entry
43    /// is a CIDR string (e.g. `10.0.0.0/8`, `192.168.1.0/24`,
44    /// `2001:db8::/32`). Single addresses without a `/` are accepted as
45    /// `/32` (IPv4) or `/128` (IPv6).
46    #[serde(default)]
47    pub trusted_proxies: Vec<String>,
48}
49
50const fn default_max_concurrent_streams() -> usize {
51    crate::config::DEFAULT_MAX_CONCURRENT_STREAMS
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
55#[serde(deny_unknown_fields)]
56pub struct ContentNegotiationConfig {
57    #[serde(default)]
58    pub enabled: bool,
59
60    #[serde(default = "default_markdown_suffix")]
61    pub markdown_suffix: String,
62}
63
64impl Default for ContentNegotiationConfig {
65    fn default() -> Self {
66        Self {
67            enabled: false,
68            markdown_suffix: default_markdown_suffix(),
69        }
70    }
71}
72
73fn default_markdown_suffix() -> String {
74    ".md".to_owned()
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
78#[serde(deny_unknown_fields)]
79pub struct SecurityHeadersConfig {
80    #[serde(default = "default_enabled")]
81    pub enabled: bool,
82
83    #[serde(default = "default_hsts")]
84    pub hsts: String,
85
86    #[serde(default = "default_frame_options")]
87    pub frame_options: String,
88
89    #[serde(default = "default_content_type_options")]
90    pub content_type_options: String,
91
92    #[serde(default = "default_referrer_policy")]
93    pub referrer_policy: String,
94
95    #[serde(default = "default_permissions_policy")]
96    pub permissions_policy: String,
97
98    #[serde(default)]
99    pub content_security_policy: Option<String>,
100}
101
102impl Default for SecurityHeadersConfig {
103    fn default() -> Self {
104        Self {
105            enabled: true,
106            hsts: default_hsts(),
107            frame_options: default_frame_options(),
108            content_type_options: default_content_type_options(),
109            referrer_policy: default_referrer_policy(),
110            permissions_policy: default_permissions_policy(),
111            content_security_policy: None,
112        }
113    }
114}
115
116const fn default_enabled() -> bool {
117    true
118}
119
120fn default_hsts() -> String {
121    "max-age=63072000; includeSubDomains; preload".to_owned()
122}
123
124fn default_frame_options() -> String {
125    "DENY".to_owned()
126}
127
128fn default_content_type_options() -> String {
129    "nosniff".to_owned()
130}
131
132fn default_referrer_policy() -> String {
133    "strict-origin-when-cross-origin".to_owned()
134}
135
136fn default_permissions_policy() -> String {
137    "camera=(), microphone=(), geolocation=()".to_owned()
138}