Use the n0 preset by default and accept custom relay URLs. Send relay settings from the control plane to each endpoint. Prepare version 0.6.1.
- commit
- 3298c4aec224ee2579c84439d68f38ad99c0df1d
- parent
- 36e521e55a7b0913414f62e1d48b7d355b37757b
- author
- Dami <dami.etoile@gmail.com>
| M | .github/ | 28 | +--- |
| M | Cargo.lock | 14 | +- |
| M | Cargo.toml | 2 | +- |
| M | README.md | 10 | +- |
| M | crates/ | 1 | + |
| M | crates/ | 22 | +-- |
| M | crates/ | 6 | +- |
| M | crates/ | 6 | +- |
| M | crates/ | 1 | + |
| M | crates/ | 201 | +++++++++++++++++++++++------- |
| M | crates/ | 34 | +++-- |
| M | crates/ | 4 | +- |
| M | deploy/ | 3 | +- |
| M | fly.toml | 3 | + |
| M | plugins/ | 2 | +- |
@@ -1,29 +1,19 @@
1-dev.site v0.6.0 gives static profile pages a small server-rendered update path.1+dev.site v0.6.1 makes relay selection part of the control-plane configuration.22 33 ## Highlights44 5-- The control plane renders profile HTML for each viewer.6-- Fixi starts profile requests and handles incoming share actions.7-- SSEXi receives the initial profile and later profile changes.8-- Paxi morphs each profile fragment while the page keeps local folder state.9-- Strict TypeScript and Biome now check the browser code.10-- The control plane filters profile updates by affected account.11-12-## Identity file change13-14-Version 0.6.0 removes the legacy identity file migration promised in version 0.5.1.15-16-The CLI and daemon use `devsite-endpoint.key` and `devsite-endpoint.pub`. They do not read17-or move `identity.key` or `identity.pub`.18-19-If these legacy files remain, start version 0.5.1 once before you install version 0.6.0.5+- The control plane uses the n0 relay preset by default.6+- `IROH_SERVICES_API_SECRET` adds endpoint-scoped relay tokens.7+- `DEVSITE_RELAY_URLS` selects a comma-separated custom relay list.8+- The control plane sends the relay list and scoped token to each endpoint.9+- The dev.site deployment keeps its custom relay list in `fly.toml`.2010 2111 ## Upgrade notes2212 2313 - Database migrations are not required.24-- Existing profiles and themes continue to work.25-- Existing `devsite-endpoint.key` and `devsite-endpoint.pub` files continue to work.26-- The release workflow now checks Rust, TypeScript, Biome, and browser tests.14+- Existing endpoint identities continue to work.15+- Self-hosted deployments can remove `DEVSITE_RELAY_URLS` to use the n0 preset.16+- Self-hosted deployments can replace `DEVSITE_RELAY_URLS` with their Iroh Services relays.2717 2818 Homebrew users can upgrade with:2919 @@ -922,13 +922,14 @@ dependencies = [
922922 923923 [[package]]924924 name = "devsite-cli"925-version = "0.6.0"925+version = "0.6.1"926926 dependencies = [927927 "anyhow",928928 "clap",929929 "data-encoding",930930 "devsite-client",931931 "devsite-daemon",932+ "devsite-iroh",932933 "devsite-proto",933934 "getrandom 0.3.4",934935 "iroh",@@ -943,7 +944,7 @@ dependencies = [
943944 944945 [[package]]945946 name = "devsite-client"946-version = "0.6.0"947+version = "0.6.1"947948 dependencies = [948949 "anyhow",949950 "devsite-iroh",@@ -956,7 +957,7 @@ dependencies = [
956957 957958 [[package]]958959 name = "devsite-daemon"959-version = "0.6.0"960+version = "0.6.1"960961 dependencies = [961962 "anyhow",962963 "data-encoding",@@ -976,17 +977,18 @@ dependencies = [
976977 977978 [[package]]978979 name = "devsite-iroh"979-version = "0.6.0"980+version = "0.6.1"980981 dependencies = [981982 "anyhow",982983 "data-encoding",983984 "iroh",984985 "iroh-services",986+ "serde",985987 ]986988 987989 [[package]]988990 name = "devsite-proto"989-version = "0.6.0"991+version = "0.6.1"990992 dependencies = [991993 "data-encoding",992994 "ed25519-dalek 2.2.0",@@ -998,7 +1000,7 @@ dependencies = [
9981000 9991001 [[package]]10001002 name = "devsite-server"1001-version = "0.6.0"1003+version = "0.6.1"10021004 dependencies = [10031005 "anyhow",10041006 "axum",@@ -248,8 +248,10 @@ reader's folder state during each morph.
248248 249249 Prerequisites: stable Rust and Node.js 24.250250 251-Set `IROH_SERVICES_API_SECRET` before you start the control plane.252-The control plane uses it to make endpoint-scoped relay tokens.251+The control plane uses the n0 relay preset by default.252+Set `IROH_SERVICES_API_SECRET` to add endpoint-scoped Iroh Services tokens.253+Set `DEVSITE_RELAY_URLS` to a comma-separated list to use custom relays.254+The control plane sends the configured URLs and scoped token to each endpoint.253255 254256 ```bash255257 npm ci@@ -306,7 +308,7 @@ the configured issuer, client, and subjects.
306308 | Path | Responsibility |307309 | --- | --- |308310 | `crates/devsite-proto` | Opaque ids, signed capabilities, and the TCP stream handshake. |309-| `crates/devsite-iroh` | Shared Iroh Services relay configuration. |311+| `crates/devsite-iroh` | Shared n0 and custom relay configuration. |310312 | `crates/devsite-client` | Native Iroh client endpoint and authorized service streams. |311313 | `crates/devsite-daemon` | Capability verification and fixed-target TCP forwarding. |312314 | `crates/devsite-cli` | Login, links, service hosting/connect, themes, and daemon lifecycle. |@@ -323,7 +325,7 @@ the configured issuer, client, and subjects.
323325 memory, while the control plane stores its hash until expiry or disconnect.324326 - Capabilities are bound to the authenticated Iroh client endpoint and cannot be replayed325327 by another endpoint.326-- The control plane keeps the Iroh Services API key and gives each endpoint a scoped relay token.328+- When configured, the control plane keeps the Iroh Services key and gives each endpoint a scoped relay token.327329 - Each capability opens one stream and its nonce is consumed once.328330 - Unknown resources, invalid signatures, wrong audiences, wrong clients, expiry, and replay329331 all produce the same denial.@@ -388,7 +388,8 @@ struct CapabilityResponse {
388388 389389 struct RedeemTicketResponse {390390 session_token: String,391- relay_token: String,391+ 392+ relay_access: devsite_iroh::RelayAccess,392393 resource_id: String,393394 name: String,394395 expires_at: u64,@@ -442,16 +443,12 @@ struct TunnelSessionInfo {
442443 resource_id: String,443444 name: String,444445 requester_endpoint_id: String,445- relay_token: String,446+ 447+ relay_access: devsite_iroh::RelayAccess,446448 expires_at: u64,447449 brokered: bool,448450 }449451 450-451-struct RegisterDaemonResponse {452- relay_token: String,453-}454-455452 456453 struct AuthorizationListing {457454 authorizations: Vec<ServiceAuthorization>,@@ -1183,7 +1180,7 @@ async fn connect(server: &str, ticket: &str, listen: SocketAddr, output: Output)
11831180 .await11841181 .context("redeeming connection ticket")?;11851182 let client =1186- Arc::new(ClientEndpoint::create_with_relay(secret_key, redeemed.relay_token).await?);1183+ Arc::new(ClientEndpoint::create_with_relay(secret_key, redeemed.relay_access).await?);11871184 let api = Arc::new(ControlPlane::new(server, Some(redeemed.session_token)));11881185 run_tunnel(11891186 "connect",@@ -1479,7 +1476,7 @@ async fn connect_grant(
14791476 bail!("the grant is not bound to the supplied requester endpoint key");14801477 }14811478 let client =1482- Arc::new(ClientEndpoint::create_with_relay(secret_key, session.relay_token).await?);1479+ Arc::new(ClientEndpoint::create_with_relay(secret_key, session.relay_access).await?);14831480 run_tunnel(14841481 "access.connect",14851482 server,@@ -2850,14 +2847,13 @@ async fn run_daemon(paths: &Paths, server: &str, output: Output) -> Result<()> {
28502847 28512848 // The endpoint id is the public half of the key in DEVSITE_HOME/identity, so28522849 // it is the same on every run. Register it before the endpoint binds so the2853- // control plane can make its scoped relay token.2850+ // control plane can return its relay settings.28542851 let api = ControlPlane::new(&server_url, token);2855- let registration: RegisterDaemonResponse = api2852+ let relay_access: devsite_iroh::RelayAccess = api28562853 .put("/api/daemon", &serde_json::json!({ "proof": proof }))28572854 .await28582855 .context("registering this daemon with the control plane")?;2859- let daemon =2860- Arc::new(Daemon::bind_with_relay(secret_key, config, registration.relay_token).await?);2856+ let daemon = Arc::new(Daemon::bind_with_relay(secret_key, config, relay_access).await?);28612857 28622858 let relay = daemon28632859 .endpoint()@@ -94,12 +94,12 @@ impl ClientEndpoint {
9494 })9595 }9696 97- /// Bind an endpoint to the dev.site relay network.97+ /// Bind an endpoint with relay settings from the control plane.9898 pub async fn create_with_relay(9999 secret_key: iroh::SecretKey,100- relay_token: impl Into<String>,100+ relay_access: devsite_iroh::RelayAccess,101101 ) -> Result<Self> {102- let endpoint = Endpoint::builder(devsite_iroh::preset(secret_key, relay_token)?)102+ let endpoint = Endpoint::builder(devsite_iroh::preset(secret_key, relay_access)?)103103 .bind()104104 .await105105 .context("binding client endpoint")?;@@ -61,14 +61,14 @@ impl Daemon {
6161 .await6262 }6363 64- /// Bind the endpoint to the dev.site relay network.64+ /// Bind the endpoint with relay settings from the control plane.6565 pub async fn bind_with_relay(6666 secret_key: SecretKey,6767 config: DaemonConfig,68- relay_token: impl Into<String>,68+ relay_access: devsite_iroh::RelayAccess,6969 ) -> Result<Self> {7070 Self::bind_builder(71- Endpoint::builder(devsite_iroh::preset(secret_key, relay_token)?),71+ Endpoint::builder(devsite_iroh::preset(secret_key, relay_access)?),7272 config,7373 )7474 .await@@ -9,3 +9,4 @@ anyhow.workspace = true
99 data-encoding.workspace = true1010 iroh = "1"1111 iroh-services = "1"12+serde.workspace = true@@ -1,3 +1,4 @@
1+use std::str::FromStr;12 use std::time::Duration;23 34 use anyhow::{Context, Result};@@ -5,71 +6,142 @@ use iroh::endpoint::presets::Preset;
56 use iroh::{RelayMap, RelayMode, RelayUrl, SecretKey};67 use iroh_services::caps::{Cap, Caps, RelayCap};78 use iroh_services::{ApiSecret, API_SECRET_ENV_VAR_NAME};9+use serde::{Deserialize, Serialize};810 9-const RELAYS: [&str; 4] = [10- "https://e67jyfby7vj7ak9h9.euc1.relay.iroh-svc.com/",11- "https://e67jyfby7vj7ak9h9.use1.relay.iroh-svc.com/",12- "https://e67jyfby7vj7ak9h9.usw1.relay.iroh-svc.com/",13- "https://e67jyfby7vj7ak9h9.aps1.relay.iroh-svc.com/",14-];11+pub const RELAY_URLS_ENV_VAR_NAME: &str = "DEVSITE_RELAY_URLS";1512 1613 const RELAY_TOKEN_LIFETIME: Duration = Duration::from_secs(30 * 24 * 60 * 60);1714 18-/// The server-side key that grants relay access to one endpoint.19-pub struct RelayIssuer {20- api_secret: ApiSecret,15+/// The relay settings that the control plane gives to an endpoint.16+17+pub struct RelayAccess {18+ /// The endpoint-scoped Iroh Services token, when the server has an API secret.19+ 20+ pub relay_token: Option<String>,21+ /// Custom relay URLs. An empty list selects the n0 relay preset.22+ 23+ pub relay_urls: Vec<String>,2124 }2225 23-impl RelayIssuer {24- /// Load the Iroh Services API secret from its standard environment variable.26+/// The control-plane relay configuration.27+pub struct RelayConfig {28+ api_secret: Option<ApiSecret>,29+ relay_urls: Vec<RelayUrl>,30+}31+32+impl RelayConfig {33+ /// Load the optional Iroh Services key and custom relay URLs.2534 pub fn from_env() -> Result<Self> {26- let api_secret = ApiSecret::from_env_var(API_SECRET_ENV_VAR_NAME)27- .context("reading IROH_SERVICES_API_SECRET")?;28- Ok(Self { api_secret })35+ let api_secret = if std::env::var_os(API_SECRET_ENV_VAR_NAME).is_some() {36+ Some(37+ ApiSecret::from_env_var(API_SECRET_ENV_VAR_NAME)38+ .context("reading IROH_SERVICES_API_SECRET")?,39+ )40+ } else {41+ None42+ };43+ let relay_urls = std::env::var(RELAY_URLS_ENV_VAR_NAME)44+ .ok()45+ .map(|value| parse_relay_urls(&value))46+ .transpose()?47+ .unwrap_or_default();48+ Ok(Self {49+ api_secret,50+ relay_urls,51+ })2952 }3053 31- /// Make a relay-only token for one endpoint.32- pub fn token(&self, endpoint_key: &[u8; 32]) -> Result<String> {33- let endpoint_id = iroh::EndpointId::from_bytes(endpoint_key)34- .context("the relay endpoint key is invalid")?;35- let token = iroh_services::caps::create_api_token_from_secret_key(36- self.api_secret.secret.clone(),37- endpoint_id,38- RELAY_TOKEN_LIFETIME,39- Caps::new([Cap::Relay(RelayCap::Use)]),40- )?;41- let mut encoded = data_encoding::BASE32_NOPAD.encode(&token.encode());42- encoded.make_ascii_lowercase();43- Ok(encoded)54+ /// Make the relay settings for one endpoint.55+ pub fn access(&self, endpoint_key: &[u8; 32]) -> Result<RelayAccess> {56+ let relay_token = self57+ .api_secret58+ .as_ref()59+ .map(|api_secret| relay_token(api_secret, endpoint_key))60+ .transpose()?;61+ let relay_urls = self.relay_urls.iter().map(ToString::to_string).collect();62+ Ok(RelayAccess {63+ relay_token,64+ relay_urls,65+ })4466 }4567 }4668 47-/// The endpoint preset for the dev.site relay network.69+fn parse_relay_urls(value: &str) -> Result<Vec<RelayUrl>> {70+ value71+ .split(',')72+ .map(str::trim)73+ .filter(|url| !url.is_empty())74+ .map(|url| {75+ RelayUrl::from_str(url)76+ .with_context(|| format!("invalid relay URL in {RELAY_URLS_ENV_VAR_NAME}: {url}"))77+ })78+ .collect()79+}80+81+fn relay_token(api_secret: &ApiSecret, endpoint_key: &[u8; 32]) -> Result<String> {82+ let endpoint_id =83+ iroh::EndpointId::from_bytes(endpoint_key).context("the relay endpoint key is invalid")?;84+ let token = iroh_services::caps::create_api_token_from_secret_key(85+ api_secret.secret.clone(),86+ endpoint_id,87+ RELAY_TOKEN_LIFETIME,88+ Caps::new([Cap::Relay(RelayCap::Use)]),89+ )?;90+ let mut encoded = data_encoding::BASE32_NOPAD.encode(&token.encode());91+ encoded.make_ascii_lowercase();92+ Ok(encoded)93+}94+95+/// The endpoint preset for the configured relay network.4896 pub struct DevsitePreset {4997 secret_key: SecretKey,50- relays: RelayMap,98+ relay_mode: Option<RelayMode>,5199 }52100 53101 impl Preset for DevsitePreset {54102 fn apply(self, builder: iroh::endpoint::Builder) -> iroh::endpoint::Builder {55103 let builder = iroh::endpoint::presets::N0.apply(builder);56- builder57- .relay_mode(RelayMode::Custom(self.relays))58- .secret_key(self.secret_key)104+ let builder = match self.relay_mode {105+ Some(relay_mode) => builder.relay_mode(relay_mode),106+ None => builder,107+ };108+ builder.secret_key(self.secret_key)59109 }60110 }61111 62-/// Make the Iroh preset for the dev.site relay network.63-pub fn preset(secret_key: SecretKey, relay_token: impl Into<String>) -> Result<DevsitePreset> {64- let relays = RELAYS65- .into_iter()66- .map(|url| {67- url.parse::<RelayUrl>()68- .with_context(|| format!("invalid dev.site relay URL {url}"))69- })70- .collect::<Result<Vec<_>>>()?;71- let relays = RelayMap::from_iter(relays).with_auth_token(relay_token);72- Ok(DevsitePreset { secret_key, relays })112+/// Make an Iroh preset from relay settings supplied by the control plane.113+pub fn preset(secret_key: SecretKey, access: RelayAccess) -> Result<DevsitePreset> {114+ let relay_mode = relay_mode(&access)?;115+ Ok(DevsitePreset {116+ secret_key,117+ relay_mode,118+ })119+}120+121+fn relay_mode(access: &RelayAccess) -> Result<Option<RelayMode>> {122+ if access.relay_urls.is_empty() && access.relay_token.is_none() {123+ return Ok(None);124+ }125+126+ let relays = if access.relay_urls.is_empty() {127+ iroh::endpoint::default_relay_mode().relay_map()128+ } else {129+ RelayMap::from_iter(130+ access131+ .relay_urls132+ .iter()133+ .map(|url| {134+ url.parse::<RelayUrl>()135+ .with_context(|| format!("invalid relay URL from the control plane: {url}"))136+ })137+ .collect::<Result<Vec<_>>>()?,138+ )139+ };140+ let relays = match &access.relay_token {141+ Some(token) => relays.with_auth_token(token.clone()),142+ None => relays,143+ };144+ Ok(Some(RelayMode::Custom(relays)))73145 }74146 75147 @@ -79,16 +151,53 @@ mod tests {
79151 80152 fn relay_tokens_are_scoped_to_endpoint_keys() {81153 let api_secret = ApiSecret::new(SecretKey::generate(), SecretKey::generate().public());82- let issuer = RelayIssuer { api_secret };83154 let first = SecretKey::generate().public();84155 let second = SecretKey::generate().public();85156 86- let first_token = issuer.token(first.as_bytes()).unwrap();87- let second_token = issuer.token(second.as_bytes()).unwrap();157+ let first_token = relay_token(&api_secret, first.as_bytes()).unwrap();158+ let second_token = relay_token(&api_secret, second.as_bytes()).unwrap();88159 89160 assert_ne!(first_token, second_token);90161 assert!(first_token91162 .bytes()92163 .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit()));93164 }165+166+ 167+ fn relay_urls_are_configured_as_a_comma_separated_list() {168+ let urls =169+ parse_relay_urls("https://use1.example.test/, https://euc1.example.test/").unwrap();170+171+ assert_eq!(urls.len(), 2);172+ assert_eq!(urls[0].to_string(), "https://use1.example.test/");173+ assert_eq!(urls[1].to_string(), "https://euc1.example.test/");174+ }175+176+ 177+ fn empty_access_keeps_the_n0_preset() {178+ assert!(relay_mode(&RelayAccess::default()).unwrap().is_none());179+ }180+181+ 182+ fn a_token_uses_the_n0_relay_map_when_urls_are_not_configured() {183+ let access = RelayAccess {184+ relay_token: Some("token".to_string()),185+ relay_urls: Vec::new(),186+ };187+188+ assert!(matches!(189+ relay_mode(&access).unwrap(),190+ Some(RelayMode::Custom(_))191+ ));192+ }193+194+ 195+ fn invalid_custom_relay_urls_fail_before_binding() {196+ let access = RelayAccess {197+ relay_token: None,198+ relay_urls: vec!["not a URL".to_string()],199+ };200+201+ assert!(relay_mode(&access).is_err());202+ }94203 }@@ -35,7 +35,7 @@ pub struct AppState {
3535 pub issuer: Issuer,3636 pub identity_namespace: String,3737 pub public_origin: String,38- pub relay_issuer: devsite_iroh::RelayIssuer,38+ pub relay_config: devsite_iroh::RelayConfig,3939 pub profile_changes: broadcast::Sender<ProfileChange>,4040 pub profile_revision: AtomicU64,4141 }@@ -431,7 +431,8 @@ pub struct RedeemTicketRequest {
431431 432432 pub struct RedeemTicketResponse {433433 pub session_token: String,434- pub relay_token: String,434+ 435+ pub relay_access: devsite_iroh::RelayAccess,435436 pub resource_id: String,436437 pub name: String,437438 pub expires_at: u64,@@ -1293,11 +1294,11 @@ async fn register_daemon(
12931294 12941295 let endpoint_key = parse_endpoint_id(&machine.endpoint_id)12951296 .ok_or_else(|| ApiError::internal(anyhow::anyhow!("stored endpoint id is invalid")))?;1296- let relay_token = state1297- .relay_issuer1298- .token(&endpoint_key)1297+ let relay_access = state1298+ .relay_config1299+ .access(&endpoint_key)12991300 .map_err(ApiError::internal)?;1300- Ok(Json(serde_json::json!({ "relay_token": relay_token })))1301+ Ok(Json(relay_access))13011302 }13021303 13031304 /// Report endpoint identities without implying daemon liveness. Registration is@@ -2336,9 +2337,9 @@ async fn redeem_connection_ticket(
23362337 23372338 Ok(Json(RedeemTicketResponse {23382339 session_token,2339- relay_token: state2340- .relay_issuer2341- .token(&client_key)2340+ relay_access: state2341+ .relay_config2342+ .access(&client_key)23422343 .map_err(ApiError::internal)?,23432344 resource_id: resource.id.to_string(),23442345 name: resource.name,@@ -2459,15 +2460,20 @@ async fn tunnel_session_info(
24592460 {24602461 return Err(invalid_tunnel_session());24612462 }2463+ let relay_access = state2464+ .relay_config2465+ .access(2466+ &parse_endpoint_id(&session.client_endpoint_id).ok_or_else(|| {2467+ ApiError::internal(anyhow::anyhow!("stored client endpoint id is invalid"))2468+ })?,2469+ )2470+ .map_err(ApiError::internal)?;24622471 Ok(Json(serde_json::json!({24632472 "resource_id": resource.id.to_string(),24642473 "name": resource.name,24652474 "requester_endpoint_id": session.client_endpoint_id,2466- "relay_token": state.relay_issuer.token(2467- &parse_endpoint_id(&session.client_endpoint_id).ok_or_else(||2468- ApiError::internal(anyhow::anyhow!("stored client endpoint id is invalid"))2469- )?2470- ).map_err(ApiError::internal)?,2475+ "relay_token": relay_access.relay_token,2476+ "relay_urls": relay_access.relay_urls,24712477 "expires_at": session.expires_at,24722478 "brokered": session.issuer_credential_id.is_some(),24732479 })))@@ -103,7 +103,7 @@ async fn main() -> Result<()> {
103103 104104 let db = Db::open(&config.database)?;105105 let issuer = config.issuer()?;106- let relay_issuer = devsite_iroh::RelayIssuer::from_env()?;106+ let relay_config = devsite_iroh::RelayConfig::from_env()?;107107 let oidc = OidcConfig::from_env(&config.public_origin)?;108108 109109 tracing::info!(@@ -121,7 +121,7 @@ async fn main() -> Result<()> {
121121 issuer,122122 identity_namespace: oidc.issuer().to_string(),123123 public_origin: config.public_origin.clone(),124- relay_issuer,124+ relay_config,125125 profile_changes,126126 profile_revision: std::sync::atomic::AtomicU64::new(0),127127 });@@ -112,7 +112,8 @@ non-OIDC adapter.
112112 | `DEVSITE_OIDC_SCOPES` | optional | Space-separated; defaults to `openid` and must include it. |113113 | `DEVSITE_OIDC_ALGORITHMS` | optional | Allowed asymmetric JWT algorithms; defaults to Shoo's `ES256`. |114114 | `DEVSITE_SIGNING_KEY` | `fly secrets` | Hex. Never in the repository. |115-| `IROH_SERVICES_API_SECRET` | `fly secrets` | Iroh Services API key. Never in the repository. |115+| `IROH_SERVICES_API_SECRET` | optional secret | Iroh Services API key for scoped relay tokens. |116+| `DEVSITE_RELAY_URLS` | optional | Comma-separated custom relay URLs. The n0 preset is the default. |116117 | `DEVSITE_BIND` | `Dockerfile` | `0.0.0.0:8080`, matching `internal_port`. |117118 | `DEVSITE_DB` | `Dockerfile` | `/data/devsite.db`, on the volume. |118119 | `DEVSITE_STATE_DIR` | `Dockerfile` | Unused while the secret is set. |@@ -20,6 +20,9 @@ primary_region = "sjc"
2020 # default client id and pairwise subjects from it, so changing it after anyone2121 # has signed in is disruptive. It must match the hostname browsers load.2222 DEVSITE_PUBLIC_ORIGIN = "https://dev.site"23+ # This deployment uses the relays from its Iroh Services project. A fork can24+ # replace this list or remove it to use the n0 relay preset.25+ DEVSITE_RELAY_URLS = "https://e67jyfby7vj7ak9h9.euc1.relay.iroh-svc.com/,https://e67jyfby7vj7ak9h9.use1.relay.iroh-svc.com/,https://e67jyfby7vj7ak9h9.usw1.relay.iroh-svc.com/,https://e67jyfby7vj7ak9h9.aps1.relay.iroh-svc.com/"2326 RUST_LOG = "devsite_server=info,tower_http=warn,warn"2427 2528 [[mounts]]