summaryrefslogtreecommitdiff
path: root/crates/config
diff options
context:
space:
mode:
authorDoan Luu Duc Tai <doan@luuduct.ai>2026-10-31 07:39:18 -0500
committerDoan Luu Duc Tai <doan@luuduct.ai>2026-10-31 07:39:18 -0500
commitd809d86182ab802472ae7d5e74cab66ba7bfa397 (patch)
tree36718e8d91b13b2714cbfe7877353ac7625f2f44 /crates/config
parentdd1d5863ef865ebe29dc93d03ca6abc0aaf48bee (diff)
Package private chat for HTTPS and verify portable encrypted recovery
Diffstat (limited to 'crates/config')
-rw-r--r--crates/config/src/lib.rs45
1 files changed, 43 insertions, 2 deletions
diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs
index 0a648a2..ce509b6 100644
--- a/crates/config/src/lib.rs
+++ b/crates/config/src/lib.rs
@@ -32,6 +32,7 @@ const HOSTS: &[&str] = &[
"SEARCH_HOST",
"WKD_HOST",
"CHAT_HOST",
+ "CHAT_LOCAL_HOST",
];
const PUBLIC: &[&str] = &[
"BASE_DOMAIN",
@@ -40,6 +41,9 @@ const PUBLIC: &[&str] = &[
"SEARCH_HOST",
"WKD_HOST",
"CHAT_HOST",
+ "CHAT_LOCAL_HOST",
+ "WEB_BIND",
+ "CHAT_UPSTREAM",
"HTTP_PORT",
"HTTPS_PORT",
"LOCAL_HTTP_PORT",
@@ -103,6 +107,32 @@ impl Config {
} else {
BTreeMap::new()
};
+ Self::from_values(explicit)
+ }
+
+ #[doc = "Loads only declared public settings and the chat token from a container environment."]
+ #[doc = "# Errors"]
+ #[doc = "Rejects invalid configuration without importing unrelated host secrets."]
+ pub fn from_environment() -> Result<Self> {
+ let mut explicit = BTreeMap::new();
+ for key in PUBLIC
+ .iter()
+ .copied()
+ .chain(std::iter::once("CHAT_SETUP_TOKEN"))
+ {
+ if let Some(value) = std::env::var_os(key) {
+ explicit.insert(
+ key.to_owned(),
+ value
+ .into_string()
+ .map_err(|_| anyhow::anyhow!("Invalid {key} environment value"))?,
+ );
+ }
+ }
+ Self::from_values(explicit)
+ }
+
+ fn from_values(explicit: BTreeMap<String, String>) -> Result<Self> {
let mut values = defaults()?;
values.retain(|key, _| PUBLIC.contains(&key.as_str()));
values.extend(explicit.clone());
@@ -160,9 +190,16 @@ impl Config {
(1024..=65_534).contains(&self.port("LOCAL_HTTP_PORT")?),
"LOCAL_HTTP_PORT must be between 1024 and 65534"
);
- for key in ["CGIT_UPSTREAM", "SEARXNG_UPSTREAM"] {
+ for key in ["CGIT_UPSTREAM", "SEARXNG_UPSTREAM", "CHAT_UPSTREAM"] {
ensure!(endpoint(self.value(key)?), "Invalid {key} in root .env");
}
+ self.value("WEB_BIND")?
+ .parse::<std::net::IpAddr>()
+ .context("Invalid WEB_BIND")?;
+ ensure!(
+ self.value("CHAT_LOCAL_HOST")?.ends_with(".localhost"),
+ "CHAT_LOCAL_HOST must be a dedicated .localhost name"
+ );
self.chat_bind()?;
let origin = self.value("CHAT_ORIGIN")?;
ensure!(
@@ -222,9 +259,10 @@ impl Config {
values.insert("GIT_ORIGIN".into(), "/git".into());
values.insert("SEARCH_ORIGIN".into(), "/search".into());
values.insert("WKD_ORIGIN".into(), String::new());
+ values.insert("LOCAL_HTTP_PORT".into(), port.to_string());
values.insert(
"CHAT_PUBLIC_ORIGIN".into(),
- self.value("CHAT_ORIGIN")?.into(),
+ format!("http://{}:{port}", self.value("CHAT_LOCAL_HOST")?),
);
}
Ok(values)
@@ -271,6 +309,9 @@ pub fn initialize_public(root: &Path) -> Result<()> {
writeln!(appended, "{key}={value}")?;
}
}
+ if path.exists() {
+ fs::set_permissions(&path, fs::Permissions::from_mode(0o600))?;
+ }
if appended.is_empty() {
return Ok(());
}