Lines
7.14 %
Functions
5 %
Branches
100 %
use server::config::{ConfigError, system_config};
/// Public-facing SSH endpoint advertised in the
/// `/account/ssh-key` "key added" snippet. All three values are
/// optional — operators set them via env (`SSH_HOSTNAME`,
/// `SSH_PORT`, `SSH_HOST_FINGERPRINT`) on the deployment ConfigMap.
#[derive(Debug, Clone)]
pub struct SshConnectInfo {
pub hostname: String,
pub port: u16,
pub host_fingerprint: String,
}
impl SshConnectInfo {
fn from_env() -> Self {
Self {
hostname: std::env::var("SSH_HOSTNAME")
.unwrap_or_else(|_| "ssh.example.invalid".to_string()),
port: std::env::var("SSH_PORT")
.ok()
.and_then(|raw| raw.parse::<u16>().ok())
.unwrap_or(2222),
host_fingerprint: std::env::var("SSH_HOST_FINGERPRINT")
.unwrap_or_else(|_| "(not configured)".to_string()),
/// An environment value, if the deployment actually supplied one.
///
/// A key set to nothing counts as unset: a ConfigMap can carry an empty
/// string, and taking that literally would override good configuration with a
/// URL that cannot parse.
/// Takes the looked-up value rather than reading the environment so it is
/// testable without mutating process-global state.
pub fn env_override(raw: Option<String>) -> Option<String> {
raw.filter(|value| !value.trim().is_empty())
pub struct Config {
pub site_url: String,
pub redis_url: String,
// Token lifetimes only. Signing keys are no longer global config — each user
// signs with their own keypair (see `crate::auth_keys` / `server::auth_keys`).
pub access_token_max_age: i64,
pub refresh_token_max_age: i64,
pub ssh: SshConnectInfo,
impl Config {
pub async fn init() -> Result<Config, ConfigError> {
let site_url = system_config("site_url")
.await?
.ok_or(ConfigError::NoConfig("site_url".to_string()))?
.to_string();
// The environment wins for the session store: it is a deployment
// endpoint, like DATABASE_URL, not something an operator edits in the
// running site. The seeded row is redis://127.0.0.1:6379/, so without
// this every fresh install crash-loops on any host where Redis is not
// on loopback, and the only cure is rewriting the row by hand after
// the first boot.
let redis_url = match env_override(std::env::var("REDIS_URL").ok()) {
Some(url) => url,
None => system_config("redis_url")
.ok_or(ConfigError::NoConfig("redis_url".to_string()))?
.to_string(),
};
let access_token_max_age = system_config("access_token_maxage")
.ok_or(ConfigError::NoConfig("access_token_maxage".to_string()))?
let refresh_token_max_age = system_config("refresh_token_maxage")
.ok_or(ConfigError::NoConfig("refresh_token_maxage".to_string()))?
let parse_age = |raw: String, field: &str| {
raw.parse::<i64>()
.map_err(|_| ConfigError::NoConfig(field.to_string()))
Ok(Config {
site_url,
redis_url,
access_token_max_age: parse_age(access_token_max_age, "access_token_maxage")?,
refresh_token_max_age: parse_age(refresh_token_max_age, "refresh_token_maxage")?,
ssh: SshConnectInfo::from_env(),
})