Skip to content

Commit 76122fe

Browse files
authored
Add devbox deployment runtime (#14)
1 parent fa75011 commit 76122fe

15 files changed

Lines changed: 3254 additions & 57 deletions

Cargo.lock

Lines changed: 629 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ tracing = "0.1"
2727
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
2828
uuid = { version = "1", features = ["serde", "v4"] }
2929
jsonwebtoken = { version = "9", default-features = false }
30+
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
3031

3132
[profile.release]
3233
opt-level = "z"

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,23 @@ Gateway-owned settings use the `CODEX_GATEWAY_` prefix for better discoverabilit
166166
- `CODEX_GATEWAY_OPENAI_API_KEY`: API key used at startup to run `codex login --with-api-key`.
167167
- `CODEX_GATEWAY_OPENAI_BASE_URL`: upstream OpenAI-compatible base URL. When set, the gateway configures Codex to use a custom provider with `supports_websockets = false`.
168168
- `CODEX_GATEWAY_MAX_SESSIONS`: maximum live sessions. Defaults to `12`.
169+
- `CODEX_GATEWAY_MAX_DEPLOYMENTS`: maximum active deployment tasks. Defaults to `4`.
169170
- `CODEX_GATEWAY_SESSION_TTL_MS`: idle session TTL. Defaults to `1800000`.
171+
- `CODEX_GATEWAY_DEPLOYMENT_TIMEOUT_MS`: deployment task timeout and deployment session keepalive window. Defaults to `3600000`.
170172
- `CODEX_GATEWAY_SESSION_SWEEP_INTERVAL_MS`: cleanup sweep interval. Defaults to `60000`.
171173
- `CODEX_GATEWAY_CODEX_HOME`: Codex runtime home for auth cache, logs, history, and config. In Docker this defaults to `/codex-home`.
172174
- `CODEX_GATEWAY_DEBUG`: enables raw bridge message debugging when set to `1`.
173175
- `CODEX_GATEWAY_JWT_SECRET`: optional HS256 JWT secret. When set, the gateway requires a valid bearer token for all routes except `/healthz` and `/readyz`.
176+
- `CODEX_GATEWAY_SESSION_RUNTIME`: session runtime backend. Defaults to `local`; set to `devbox` to create a Devbox runtime before each session.
177+
- `CODEX_GATEWAY_DEVBOX_BASE_URL`: Devbox API base URL. If omitted in devbox mode, the gateway derives `https://devbox-server.${SEALOS_HOST}` from `SEALOS_HOST`.
178+
- `CODEX_GATEWAY_DEVBOX_TOKEN`: Devbox API bearer token. `DEVBOX_TOKEN` is also accepted.
179+
- `CODEX_GATEWAY_DEVBOX_JWT_SIGNING_KEY`: HS256 signing key used when no Devbox token is configured. `DEVBOX_JWT_SIGNING_KEY` is also accepted.
180+
- `CODEX_GATEWAY_DEVBOX_NAMESPACE`: Devbox namespace. Defaults to `ns-test`.
181+
- `CODEX_GATEWAY_DEVBOX_RUNTIME_IMAGE`: optional Devbox runtime image override.
182+
- `CODEX_GATEWAY_DEVBOX_ARCHIVE_AFTER_PAUSE_TIME`: Devbox archive delay after pause. Defaults to `24h`.
183+
- `CODEX_GATEWAY_DEVBOX_WAIT_TIMEOUT_SECONDS`: timeout while waiting for a new Devbox to become `Running`. Defaults to `60`.
184+
- `CODEX_GATEWAY_DEVBOX_GATEWAY_READY_TIMEOUT_SECONDS`: timeout while waiting for the Codex Gateway inside Devbox to pass health and readiness checks. Defaults to `60`.
185+
- `CODEX_GATEWAY_DEVBOX_BOOTSTRAP_TIMEOUT_SECONDS`: timeout for the bootstrap command. Defaults to `300`.
174186

175187
## Docker
176188

rust-src/config.rs

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@ use std::path::PathBuf;
22
use std::time::Duration;
33

44
use crate::env_config::{
5-
BRIDGE_CWD_ENV, CODEX_BIN_ENV, DEBUG_ENV, DEFAULT_MODEL_ENV, HOST_ENV, JWT_SECRET_ENV,
6-
MAX_SESSIONS_ENV, PORT_ENV, SESSION_SWEEP_INTERVAL_MS_ENV, SESSION_TTL_MS_ENV, read_bool_flag,
7-
read_env, read_u16, read_u64, read_usize,
5+
BRIDGE_CWD_ENV, CODEX_BIN_ENV, DEBUG_ENV, DEFAULT_MODEL_ENV, DEPLOYMENT_TIMEOUT_MS_ENV,
6+
DEVBOX_ARCHIVE_AFTER_PAUSE_TIME_ENV, DEVBOX_BASE_URL_ENV, DEVBOX_BOOTSTRAP_TIMEOUT_SECONDS_ENV,
7+
DEVBOX_GATEWAY_READY_TIMEOUT_SECONDS_ENV, DEVBOX_JWT_SIGNING_KEY_ENV,
8+
DEVBOX_JWT_TTL_SECONDS_ENV, DEVBOX_NAMESPACE_ENV, DEVBOX_RUNTIME_IMAGE_ENV, DEVBOX_TOKEN_ENV,
9+
DEVBOX_WAIT_TIMEOUT_SECONDS_ENV, HOST_ENV, JWT_SECRET_ENV, MAX_DEPLOYMENTS_ENV,
10+
MAX_SESSIONS_ENV, PORT_ENV, SEALOS_HOST_ENV, SESSION_RUNTIME_ENV,
11+
SESSION_SWEEP_INTERVAL_MS_ENV, SESSION_TTL_MS_ENV, read_bool_flag, read_env, read_u16,
12+
read_u64, read_usize,
813
};
914

1015
#[derive(Debug, Clone)]
@@ -19,6 +24,26 @@ pub struct AuthConfig {
1924
pub jwt_secret: String,
2025
}
2126

27+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28+
pub enum SessionRuntimeMode {
29+
Local,
30+
Devbox,
31+
}
32+
33+
#[derive(Debug, Clone)]
34+
pub struct DevboxConfig {
35+
pub base_url: String,
36+
pub namespace: String,
37+
pub token: Option<String>,
38+
pub jwt_signing_key: Option<String>,
39+
pub jwt_ttl_seconds: u64,
40+
pub runtime_image: Option<String>,
41+
pub archive_after_pause_time: String,
42+
pub wait_timeout: Duration,
43+
pub gateway_ready_timeout: Duration,
44+
pub bootstrap_timeout: Duration,
45+
}
46+
2247
#[derive(Debug, Clone)]
2348
pub struct AppConfig {
2449
pub host: String,
@@ -30,16 +55,22 @@ pub struct AppConfig {
3055
pub debug: bool,
3156
pub default_model: Option<String>,
3257
pub max_sessions: usize,
58+
pub max_deployments: usize,
3359
pub session_ttl: Duration,
60+
pub deployment_timeout: Duration,
3461
pub session_sweep_interval: Duration,
3562
pub client_info: ClientInfo,
3663
pub auth: Option<AuthConfig>,
64+
pub session_runtime: SessionRuntimeMode,
65+
pub devbox: Option<DevboxConfig>,
3766
}
3867

3968
impl AppConfig {
4069
pub fn from_env(root_dir: PathBuf) -> Self {
4170
let public_dir = root_dir.join("public");
4271
let lab_dir = root_dir.join("lab");
72+
let session_runtime = read_session_runtime();
73+
let devbox = read_devbox_config(session_runtime);
4374

4475
Self {
4576
host: read_env(HOST_ENV).unwrap_or_else(|| "0.0.0.0".to_string()),
@@ -53,9 +84,13 @@ impl AppConfig {
5384
debug: read_bool_flag(DEBUG_ENV),
5485
default_model: read_env(DEFAULT_MODEL_ENV),
5586
max_sessions: read_usize(MAX_SESSIONS_ENV).unwrap_or(12),
87+
max_deployments: read_usize(MAX_DEPLOYMENTS_ENV).unwrap_or(4),
5688
session_ttl: Duration::from_millis(
5789
read_u64(SESSION_TTL_MS_ENV).unwrap_or(30 * 60 * 1000),
5890
),
91+
deployment_timeout: Duration::from_millis(
92+
read_u64(DEPLOYMENT_TIMEOUT_MS_ENV).unwrap_or(60 * 60 * 1000),
93+
),
5994
session_sweep_interval: Duration::from_millis(
6095
read_u64(SESSION_SWEEP_INTERVAL_MS_ENV).unwrap_or(60 * 1000),
6196
),
@@ -65,6 +100,55 @@ impl AppConfig {
65100
version: env!("CARGO_PKG_VERSION").to_string(),
66101
},
67102
auth: read_env(JWT_SECRET_ENV).map(|jwt_secret| AuthConfig { jwt_secret }),
103+
session_runtime,
104+
devbox,
68105
}
69106
}
70107
}
108+
109+
fn read_session_runtime() -> SessionRuntimeMode {
110+
match read_env(SESSION_RUNTIME_ENV)
111+
.unwrap_or_else(|| "local".to_string())
112+
.to_ascii_lowercase()
113+
.as_str()
114+
{
115+
"devbox" => SessionRuntimeMode::Devbox,
116+
_ => SessionRuntimeMode::Local,
117+
}
118+
}
119+
120+
fn read_devbox_config(session_runtime: SessionRuntimeMode) -> Option<DevboxConfig> {
121+
if session_runtime != SessionRuntimeMode::Devbox {
122+
return None;
123+
}
124+
125+
let base_url = read_env(DEVBOX_BASE_URL_ENV).or_else(|| {
126+
read_env(SEALOS_HOST_ENV).map(|host| {
127+
let normalized = host
128+
.trim()
129+
.trim_end_matches('/')
130+
.trim_start_matches("https://")
131+
.trim_start_matches("http://")
132+
.to_string();
133+
format!("https://devbox-server.{normalized}")
134+
})
135+
});
136+
137+
Some(DevboxConfig {
138+
base_url: base_url.unwrap_or_default(),
139+
namespace: read_env(DEVBOX_NAMESPACE_ENV).unwrap_or_else(|| "ns-test".to_string()),
140+
token: read_env(DEVBOX_TOKEN_ENV),
141+
jwt_signing_key: read_env(DEVBOX_JWT_SIGNING_KEY_ENV),
142+
jwt_ttl_seconds: read_u64(DEVBOX_JWT_TTL_SECONDS_ENV).unwrap_or(4 * 60 * 60),
143+
runtime_image: read_env(DEVBOX_RUNTIME_IMAGE_ENV),
144+
archive_after_pause_time: read_env(DEVBOX_ARCHIVE_AFTER_PAUSE_TIME_ENV)
145+
.unwrap_or_else(|| "24h".to_string()),
146+
wait_timeout: Duration::from_secs(read_u64(DEVBOX_WAIT_TIMEOUT_SECONDS_ENV).unwrap_or(60)),
147+
gateway_ready_timeout: Duration::from_secs(
148+
read_u64(DEVBOX_GATEWAY_READY_TIMEOUT_SECONDS_ENV).unwrap_or(60),
149+
),
150+
bootstrap_timeout: Duration::from_secs(
151+
read_u64(DEVBOX_BOOTSTRAP_TIMEOUT_SECONDS_ENV).unwrap_or(300),
152+
),
153+
})
154+
}

0 commit comments

Comments
 (0)