Make standalone connector config deployable

This commit is contained in:
Meta Vens 2026-08-24 19:36:27 +02:00
commit 548ee73b01
3 changed files with 150 additions and 8 deletions

View file

@ -61,6 +61,31 @@ pub struct CodexDaemonConfig {
}
impl CodexDaemonConfig {
pub fn single_caller(
bind: impl Into<String>,
codex_executable: PathBuf,
codex_executable_sha256: [u8; 32],
codex_home: PathBuf,
replay_store: PathBuf,
caller: CodexCallerConfig,
) -> Self {
Self {
epoch: CONFIG_EPOCH,
bind: bind.into(),
codex_executable,
codex_executable_sha256,
codex_home,
max_frame_bytes: caller
.max_payload_bytes
.saturating_add(FRAME_OVERHEAD_BUDGET),
max_connections: caller.max_concurrent_requests.max(8),
socket_timeout_ms: 10_000,
max_expiry_skew_ms: 300_000,
callers: vec![caller],
replay_store,
}
}
pub fn validate(&self) -> Result<SocketAddr, CodexDaemonError> {
if self.epoch != CONFIG_EPOCH {
return Err(CodexDaemonError::InvalidConfig("epoch"));
@ -387,6 +412,34 @@ mod tests {
fs::remove_dir_all(root).unwrap();
}
#[test]
fn single_caller_initializer_derives_daemon_bounds_without_reowning_policy() {
let root = Path::new("/srv/codex-connector");
let caller = CodexCallerConfig {
caller_runtime_id: "ghostlight-dungeon-yggdrasil".to_string(),
connection_key_file: root.join("ghostlight.key"),
connection_key_epoch: 3,
allowed_models: vec!["gpt-5.4".to_string()],
max_concurrent_requests: 12,
max_payload_bytes: 1_048_576,
max_output_tokens: 32_768,
};
let config = CodexDaemonConfig::single_caller(
"127.0.0.1:4103",
root.join("codex"),
[7; 32],
root.join("codex-home"),
root.join("replay.cc"),
caller.clone(),
);
assert_eq!(config.epoch, CONFIG_EPOCH);
assert_eq!(config.max_frame_bytes, 1_052_672);
assert_eq!(config.max_connections, 12);
assert_eq!(config.callers, vec![caller]);
config.validate().unwrap();
}
#[test]
fn direct_pipe_frame_is_big_endian_and_refuses_oversize_before_allocation() {
let mut encoded = Vec::new();

View file

@ -1,11 +1,27 @@
use std::ffi::OsString;
use std::path::PathBuf;
use codex_connector::{CodexCallerConfig, CodexDaemonConfig};
const USAGE: &str = "usage:\n codex-connector --config PATH.cc\n codex-connector --initialize-single-caller-config PATH.cc BIND CODEX_EXECUTABLE CODEX_SHA256 CODEX_HOME REPLAY_STORE CALLER_RUNTIME_ID CONNECTION_KEY_FILE CONNECTION_KEY_EPOCH ALLOWED_MODEL MAX_CONCURRENT_REQUESTS MAX_PAYLOAD_BYTES MAX_OUTPUT_TOKENS";
fn main() {
let mut args = std::env::args_os().skip(1);
let config = match (args.next(), args.next(), args.next()) {
(Some(flag), Some(path), None) if flag == "--config" => PathBuf::from(path),
let args = std::env::args_os().skip(1).collect::<Vec<_>>();
if args
.first()
.is_some_and(|value| value == "--initialize-single-caller-config")
{
if let Err(error) = initialize_single_caller_config(&args[1..]) {
eprintln!("codex-connector config initialization failed: {error}");
eprintln!("{USAGE}");
std::process::exit(2);
}
return;
}
let config = match args.as_slice() {
[flag, path] if flag == "--config" => PathBuf::from(path),
_ => {
eprintln!("usage: codex-connector --config PATH.cc");
eprintln!("{USAGE}");
std::process::exit(2);
}
};
@ -14,3 +30,75 @@ fn main() {
std::process::exit(1);
}
}
fn initialize_single_caller_config(args: &[OsString]) -> Result<(), String> {
let [
path,
bind,
executable,
executable_sha256,
codex_home,
replay_store,
caller_runtime_id,
connection_key_file,
connection_key_epoch,
allowed_model,
max_concurrent_requests,
max_payload_bytes,
max_output_tokens,
] = args
else {
return Err("expected exactly thirteen configuration values".to_string());
};
let config = CodexDaemonConfig::single_caller(
os_text(bind, "bind")?,
PathBuf::from(executable),
parse_sha256(executable_sha256)?,
PathBuf::from(codex_home),
PathBuf::from(replay_store),
CodexCallerConfig {
caller_runtime_id: os_text(caller_runtime_id, "caller runtime")?,
connection_key_file: PathBuf::from(connection_key_file),
connection_key_epoch: parse_number(connection_key_epoch, "connection key epoch")?,
allowed_models: vec![os_text(allowed_model, "allowed model")?],
max_concurrent_requests: parse_number(
max_concurrent_requests,
"max concurrent requests",
)?,
max_payload_bytes: parse_number(max_payload_bytes, "max payload bytes")?,
max_output_tokens: parse_number(max_output_tokens, "max output tokens")?,
},
);
codex_connector::write_daemon_config(&PathBuf::from(path), &config)
.map_err(|error| error.to_string())
}
fn os_text(value: &OsString, field: &str) -> Result<String, String> {
value
.to_str()
.filter(|value| !value.is_empty())
.map(str::to_string)
.ok_or_else(|| format!("{field} must be non-empty UTF-8"))
}
fn parse_number<T>(value: &OsString, field: &str) -> Result<T, String>
where
T: std::str::FromStr,
{
os_text(value, field)?
.parse()
.map_err(|_| format!("{field} is not a valid integer"))
}
fn parse_sha256(value: &OsString) -> Result<[u8; 32], String> {
let value = os_text(value, "Codex SHA-256")?;
if value.len() != 64 || !value.bytes().all(|byte| byte.is_ascii_hexdigit()) {
return Err("Codex SHA-256 must be exactly 64 hexadecimal characters".to_string());
}
let mut digest = [0_u8; 32];
for (index, output) in digest.iter_mut().enumerate() {
*output = u8::from_str_radix(&value[index * 2..index * 2 + 2], 16)
.map_err(|_| "Codex SHA-256 is malformed".to_string())?;
}
Ok(digest)
}