Add Iroh relay mode config
This commit is contained in:
parent
8334cc5e00
commit
0f2ad755d9
13 changed files with 210 additions and 21 deletions
|
|
@ -102,10 +102,10 @@ Roadmap items should be actionable and checkable:
|
||||||
## Current Feature Boundaries
|
## Current Feature Boundaries
|
||||||
|
|
||||||
- Local daemon, local control socket, local identity, local store, local CAS,
|
- Local daemon, local control socket, local identity, local store, local CAS,
|
||||||
daemon-owned Iroh endpoint startup, SSH certificate metadata, revocation
|
daemon-owned Iroh endpoint startup, built-in relay-mode config, SSH
|
||||||
metadata, user service definitions, and a pinned `geth-iroh` endpoint wrapper
|
certificate metadata, revocation metadata, user service definitions, and a
|
||||||
exist.
|
pinned `geth-iroh` endpoint wrapper exist.
|
||||||
- Relay policy, mDNS discovery, peer auth over Iroh, cr-sqlite, iroh-docs,
|
- Custom relay maps, mDNS discovery, peer auth over Iroh, cr-sqlite, iroh-docs,
|
||||||
iroh-gossip, iroh-blobs, Automerge sync, real auth enforcement, OpenSSH KRL
|
iroh-gossip, iroh-blobs, Automerge sync, real auth enforcement, OpenSSH KRL
|
||||||
generation, and Keyhive/BeeKEM-style authorization are future roadmap items
|
generation, and Keyhive/BeeKEM-style authorization are future roadmap items
|
||||||
unless implemented later.
|
unless implemented later.
|
||||||
|
|
|
||||||
8
Cargo.lock
generated
8
Cargo.lock
generated
|
|
@ -1090,6 +1090,7 @@ dependencies = [
|
||||||
"directories",
|
"directories",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
|
"toml_edit",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3860,6 +3861,7 @@ dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
"toml_parser",
|
"toml_parser",
|
||||||
|
"toml_writer",
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -3872,6 +3874,12 @@ dependencies = [
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_writer"
|
||||||
|
version = "1.1.1+spec-1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower"
|
name = "tower"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ tempfile = "3"
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
time = { version = "0.3", features = ["formatting", "serde"] }
|
time = { version = "0.3", features = ["formatting", "serde"] }
|
||||||
tokio = { version = "1", features = ["fs", "io-util", "macros", "net", "rt-multi-thread", "signal", "time"] }
|
tokio = { version = "1", features = ["fs", "io-util", "macros", "net", "rt-multi-thread", "signal", "time"] }
|
||||||
|
toml_edit = "0.25.11"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ geth resource list
|
||||||
geth cas add ./file
|
geth cas add ./file
|
||||||
```
|
```
|
||||||
|
|
||||||
The daemon owns local identity, the future Iroh endpoint, trust state, resource
|
The daemon owns local identity, the Iroh endpoint, trust state, resource
|
||||||
registry, module router, local metadata store, and synchronized data structures.
|
registry, module router, local metadata store, and synchronized data structures.
|
||||||
Most non-daemon commands talk to the daemon through a local Unix socket at
|
Most non-daemon commands talk to the daemon through a local Unix socket at
|
||||||
`$GETH_HOME/run/geth.sock`.
|
`$GETH_HOME/run/geth.sock`.
|
||||||
|
|
@ -43,6 +43,9 @@ services, not system services.
|
||||||
|
|
||||||
All remote node-to-node geth communication is designed to happen over Iroh only.
|
All remote node-to-node geth communication is designed to happen over Iroh only.
|
||||||
SSH is not a geth transport backend, and there is no SSH fallback transport.
|
SSH is not a geth transport backend, and there is no SSH fallback transport.
|
||||||
|
The default node config uses Iroh's default relay policy for practical
|
||||||
|
connectivity; set `[iroh].relay_mode = "disabled"` for local-only/offline
|
||||||
|
development.
|
||||||
|
|
||||||
SSH keys are used as admin trust anchors and ecosystem integration points.
|
SSH keys are used as admin trust anchors and ecosystem integration points.
|
||||||
OpenSSH, FIDO, and YubiKey-backed keys can sign geth trust objects through
|
OpenSSH, FIDO, and YubiKey-backed keys can sign geth trust objects through
|
||||||
|
|
@ -110,6 +113,7 @@ $GETH_HOME/
|
||||||
geth.sqlite
|
geth.sqlite
|
||||||
config.toml
|
config.toml
|
||||||
identity/agent.ed25519
|
identity/agent.ed25519
|
||||||
|
identity/iroh.ed25519
|
||||||
cas/blobs/
|
cas/blobs/
|
||||||
run/geth.sock
|
run/geth.sock
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -507,6 +507,7 @@ fn print_response(response: ControlResponse, json: bool) -> Result<()> {
|
||||||
"endpoint: {}",
|
"endpoint: {}",
|
||||||
status.endpoint_id.as_deref().unwrap_or("not started")
|
status.endpoint_id.as_deref().unwrap_or("not started")
|
||||||
);
|
);
|
||||||
|
println!("iroh relay: {}", status.iroh_relay_mode);
|
||||||
println!("iroh: {}", status.iroh);
|
println!("iroh: {}", status.iroh);
|
||||||
}
|
}
|
||||||
ControlResponse::NodeId(node) => {
|
ControlResponse::NodeId(node) => {
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,4 @@ license.workspace = true
|
||||||
directories.workspace = true
|
directories.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
toml_edit.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,14 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
pub const DEFAULT_CONFIG_TOML: &str = r#"# geth local node config
|
||||||
|
# Remote node-to-node communication is Iroh-only.
|
||||||
|
#
|
||||||
|
# relay_mode controls Iroh relay use for practical internet connectivity.
|
||||||
|
# Values: "default", "staging", "disabled".
|
||||||
|
[iroh]
|
||||||
|
relay_mode = "default"
|
||||||
|
"#;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct GethPaths {
|
pub struct GethPaths {
|
||||||
home: PathBuf,
|
home: PathBuf,
|
||||||
|
|
@ -83,4 +92,106 @@ pub enum ConfigError {
|
||||||
NoDataDirectory,
|
NoDataDirectory,
|
||||||
#[error("io error: {0}")]
|
#[error("io error: {0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("toml parse error: {0}")]
|
||||||
|
TomlParse(#[from] toml_edit::TomlError),
|
||||||
|
#[error("invalid iroh relay_mode: {0}")]
|
||||||
|
InvalidRelayMode(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub struct GethConfig {
|
||||||
|
pub iroh: IrohConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GethConfig {
|
||||||
|
#[must_use]
|
||||||
|
pub fn default_toml() -> &'static str {
|
||||||
|
DEFAULT_CONFIG_TOML
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(path: &Path) -> Result<Self, ConfigError> {
|
||||||
|
if !path.exists() {
|
||||||
|
return Ok(Self::default());
|
||||||
|
}
|
||||||
|
Self::parse(&std::fs::read_to_string(path)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(text: &str) -> Result<Self, ConfigError> {
|
||||||
|
let document = text.parse::<toml_edit::DocumentMut>()?;
|
||||||
|
let relay_mode = match document.get("iroh").and_then(|iroh| iroh.get("relay_mode")) {
|
||||||
|
Some(item) => {
|
||||||
|
let value = item
|
||||||
|
.as_str()
|
||||||
|
.ok_or_else(|| ConfigError::InvalidRelayMode(item.to_string()))?;
|
||||||
|
RelayMode::parse(value)?
|
||||||
|
}
|
||||||
|
None => RelayMode::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
iroh: IrohConfig { relay_mode },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub struct IrohConfig {
|
||||||
|
pub relay_mode: RelayMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub enum RelayMode {
|
||||||
|
Disabled,
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
Staging,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RelayMode {
|
||||||
|
pub fn parse(value: &str) -> Result<Self, ConfigError> {
|
||||||
|
match value {
|
||||||
|
"disabled" => Ok(Self::Disabled),
|
||||||
|
"default" => Ok(Self::Default),
|
||||||
|
"staging" => Ok(Self::Staging),
|
||||||
|
other => Err(ConfigError::InvalidRelayMode(other.to_owned())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Disabled => "disabled",
|
||||||
|
Self::Default => "default",
|
||||||
|
Self::Staging => "staging",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_config_uses_iroh_relays() {
|
||||||
|
let config = GethConfig::parse(GethConfig::default_toml()).expect("parse config");
|
||||||
|
assert_eq!(config.iroh.relay_mode, RelayMode::Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_parses_disabled_relay_mode() {
|
||||||
|
let config = GethConfig::parse("[iroh]\nrelay_mode = \"disabled\"\n").expect("parse");
|
||||||
|
assert_eq!(config.iroh.relay_mode, RelayMode::Disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_rejects_unknown_relay_mode() {
|
||||||
|
let error = GethConfig::parse("[iroh]\nrelay_mode = \"magic\"\n").expect_err("error");
|
||||||
|
assert!(matches!(error, ConfigError::InvalidRelayMode(value) if value == "magic"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_rejects_non_string_relay_mode() {
|
||||||
|
let error = GethConfig::parse("[iroh]\nrelay_mode = true\n").expect_err("error");
|
||||||
|
assert!(matches!(error, ConfigError::InvalidRelayMode(_)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,7 @@ pub struct StatusResponse {
|
||||||
pub node_id: String,
|
pub node_id: String,
|
||||||
pub iroh_enabled: bool,
|
pub iroh_enabled: bool,
|
||||||
pub endpoint_id: Option<String>,
|
pub endpoint_id: Option<String>,
|
||||||
|
pub iroh_relay_mode: String,
|
||||||
pub iroh: String,
|
pub iroh: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,17 @@ pub struct GethIrohConfig {
|
||||||
impl GethIrohConfig {
|
impl GethIrohConfig {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn local(secret_key_path: impl Into<PathBuf>) -> Self {
|
pub fn local(secret_key_path: impl Into<PathBuf>) -> Self {
|
||||||
|
Self::local_with_relay(secret_key_path, GethRelayMode::Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn local_with_relay(
|
||||||
|
secret_key_path: impl Into<PathBuf>,
|
||||||
|
relay_mode: GethRelayMode,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
secret_key_path: secret_key_path.into(),
|
secret_key_path: secret_key_path.into(),
|
||||||
relay_mode: GethRelayMode::Disabled,
|
relay_mode,
|
||||||
bind_ipv4: None,
|
bind_ipv4: None,
|
||||||
bind_ipv6: None,
|
bind_ipv6: None,
|
||||||
alpns: all_alpns(),
|
alpns: all_alpns(),
|
||||||
|
|
@ -49,6 +57,15 @@ impl GethRelayMode {
|
||||||
Self::Staging => iroh::RelayMode::Staging,
|
Self::Staging => iroh::RelayMode::Staging,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Disabled => "disabled",
|
||||||
|
Self::Default => "default",
|
||||||
|
Self::Staging => "staging",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GethIrohEndpoint {
|
pub struct GethIrohEndpoint {
|
||||||
|
|
@ -90,6 +107,7 @@ pub async fn start_endpoint(config: &GethIrohConfig) -> Result<GethIrohEndpoint,
|
||||||
let status = EndpointStatus {
|
let status = EndpointStatus {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
endpoint_id: Some(endpoint.node_id().to_string()),
|
endpoint_id: Some(endpoint.node_id().to_string()),
|
||||||
|
relay_mode: config.relay_mode.as_str().to_owned(),
|
||||||
note: format!(
|
note: format!(
|
||||||
"Iroh endpoint started with iroh 0.90.0; relay mode: {:?}",
|
"Iroh endpoint started with iroh 0.90.0; relay mode: {:?}",
|
||||||
config.relay_mode
|
config.relay_mode
|
||||||
|
|
@ -143,6 +161,7 @@ pub fn all_alpns() -> Vec<Vec<u8>> {
|
||||||
pub struct EndpointStatus {
|
pub struct EndpointStatus {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub endpoint_id: Option<String>,
|
pub endpoint_id: Option<String>,
|
||||||
|
pub relay_mode: String,
|
||||||
pub note: String,
|
pub note: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -152,6 +171,7 @@ impl EndpointStatus {
|
||||||
Self {
|
Self {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
endpoint_id: None,
|
endpoint_id: None,
|
||||||
|
relay_mode: "not-started".to_owned(),
|
||||||
note: "Iroh endpoint is not running outside daemon mode".to_owned(),
|
note: "Iroh endpoint is not running outside daemon mode".to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -197,15 +217,29 @@ mod tests {
|
||||||
assert_eq!(first.public(), second.public());
|
assert_eq!(first.public(), second.public());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn local_config_selects_relay_mode() {
|
||||||
|
let config = GethIrohConfig::local_with_relay("iroh.ed25519", GethRelayMode::Staging);
|
||||||
|
assert_eq!(config.relay_mode, GethRelayMode::Staging);
|
||||||
|
assert!(matches!(
|
||||||
|
config.relay_mode.to_iroh(),
|
||||||
|
iroh::RelayMode::Staging
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn endpoint_status_tracks_node_id_when_bind_is_available() {
|
async fn endpoint_status_tracks_node_id_when_bind_is_available() {
|
||||||
let dir = tempfile::tempdir().expect("tempdir");
|
let dir = tempfile::tempdir().expect("tempdir");
|
||||||
let config = GethIrohConfig::local(dir.path().join("iroh.ed25519"));
|
let config = GethIrohConfig::local_with_relay(
|
||||||
|
dir.path().join("iroh.ed25519"),
|
||||||
|
GethRelayMode::Disabled,
|
||||||
|
);
|
||||||
match start_endpoint(&config).await {
|
match start_endpoint(&config).await {
|
||||||
Ok(endpoint) => {
|
Ok(endpoint) => {
|
||||||
let status = endpoint.status();
|
let status = endpoint.status();
|
||||||
assert!(status.enabled);
|
assert!(status.enabled);
|
||||||
assert_eq!(status.endpoint_id, Some(endpoint.node_id()));
|
assert_eq!(status.endpoint_id, Some(endpoint.node_id()));
|
||||||
|
assert_eq!(status.relay_mode, "disabled");
|
||||||
endpoint.shutdown().await;
|
endpoint.shutdown().await;
|
||||||
}
|
}
|
||||||
Err(IrohError::Bind(error)) => {
|
Err(IrohError::Bind(error)) => {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@ pub mod service;
|
||||||
|
|
||||||
use geth_auth::AuthExplanation;
|
use geth_auth::AuthExplanation;
|
||||||
use geth_cas::{LocalCas, hash_path};
|
use geth_cas::{LocalCas, hash_path};
|
||||||
use geth_config::GethPaths;
|
use geth_config::{GethConfig, GethPaths, RelayMode};
|
||||||
use geth_control::{
|
use geth_control::{
|
||||||
CasBlob, ControlRequest, ControlResponse, KeychainStatusResponse, NodeIdResponse,
|
CasBlob, ControlRequest, ControlResponse, KeychainStatusResponse, NodeIdResponse,
|
||||||
StatusResponse,
|
StatusResponse,
|
||||||
};
|
};
|
||||||
use geth_crypto::AgentKey;
|
use geth_crypto::AgentKey;
|
||||||
use geth_iroh::{EndpointStatus, GethIrohConfig, GethIrohEndpoint};
|
use geth_iroh::{EndpointStatus, GethIrohConfig, GethIrohEndpoint, GethRelayMode};
|
||||||
use geth_resource::ResourceDescriptor;
|
use geth_resource::ResourceDescriptor;
|
||||||
use geth_ssh_identity::{
|
use geth_ssh_identity::{
|
||||||
SshCertApproval, SshCertKind, SshCertRequest, SshCertRequestStatus, SshCertificateRecord,
|
SshCertApproval, SshCertKind, SshCertRequest, SshCertRequestStatus, SshCertificateRecord,
|
||||||
|
|
@ -68,10 +68,7 @@ pub struct LocalNode {
|
||||||
pub fn init_node(paths: &GethPaths) -> Result<LocalNode, NodeError> {
|
pub fn init_node(paths: &GethPaths) -> Result<LocalNode, NodeError> {
|
||||||
paths.ensure_base_dirs()?;
|
paths.ensure_base_dirs()?;
|
||||||
if !paths.config_file().exists() {
|
if !paths.config_file().exists() {
|
||||||
std::fs::write(
|
std::fs::write(paths.config_file(), GethConfig::default_toml())?;
|
||||||
paths.config_file(),
|
|
||||||
"# geth local node config\n# Remote node-to-node communication is Iroh-only.\n",
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
let key = AgentKey::load_or_create(&paths.agent_key())?;
|
let key = AgentKey::load_or_create(&paths.agent_key())?;
|
||||||
let agent_id = key.agent_id().to_string();
|
let agent_id = key.agent_id().to_string();
|
||||||
|
|
@ -163,6 +160,7 @@ pub fn handle_request(
|
||||||
node_id: node.node_id.clone(),
|
node_id: node.node_id.clone(),
|
||||||
iroh_enabled: node.iroh_status.enabled,
|
iroh_enabled: node.iroh_status.enabled,
|
||||||
endpoint_id: node.iroh_status.endpoint_id.clone(),
|
endpoint_id: node.iroh_status.endpoint_id.clone(),
|
||||||
|
iroh_relay_mode: node.iroh_status.relay_mode.clone(),
|
||||||
iroh: node.iroh_status.note.clone(),
|
iroh: node.iroh_status.note.clone(),
|
||||||
})),
|
})),
|
||||||
ControlRequest::NodeId => Ok(ControlResponse::NodeId(NodeIdResponse {
|
ControlRequest::NodeId => Ok(ControlResponse::NodeId(NodeIdResponse {
|
||||||
|
|
@ -447,7 +445,10 @@ fn stable_node_id(agent_id: &str) -> String {
|
||||||
async fn start_daemon_iroh_endpoint(
|
async fn start_daemon_iroh_endpoint(
|
||||||
node: &mut LocalNode,
|
node: &mut LocalNode,
|
||||||
) -> Result<Option<GethIrohEndpoint>, NodeError> {
|
) -> Result<Option<GethIrohEndpoint>, NodeError> {
|
||||||
let config = GethIrohConfig::local(node.paths.iroh_key());
|
let node_config = GethConfig::load(&node.paths.config_file())?;
|
||||||
|
let relay_mode = node_config.iroh.relay_mode;
|
||||||
|
let iroh_relay_mode = config_relay_mode_to_iroh(relay_mode);
|
||||||
|
let config = GethIrohConfig::local_with_relay(node.paths.iroh_key(), iroh_relay_mode);
|
||||||
match geth_iroh::start_endpoint(&config).await {
|
match geth_iroh::start_endpoint(&config).await {
|
||||||
Ok(endpoint) => {
|
Ok(endpoint) => {
|
||||||
let status = endpoint.status();
|
let status = endpoint.status();
|
||||||
|
|
@ -462,6 +463,7 @@ async fn start_daemon_iroh_endpoint(
|
||||||
node.iroh_status = EndpointStatus {
|
node.iroh_status = EndpointStatus {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
endpoint_id: None,
|
endpoint_id: None,
|
||||||
|
relay_mode: relay_mode.as_str().to_owned(),
|
||||||
note: format!("Iroh endpoint failed to start: {error}"),
|
note: format!("Iroh endpoint failed to start: {error}"),
|
||||||
};
|
};
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|
@ -469,6 +471,14 @@ async fn start_daemon_iroh_endpoint(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn config_relay_mode_to_iroh(mode: RelayMode) -> GethRelayMode {
|
||||||
|
match mode {
|
||||||
|
RelayMode::Disabled => GethRelayMode::Disabled,
|
||||||
|
RelayMode::Default => GethRelayMode::Default,
|
||||||
|
RelayMode::Staging => GethRelayMode::Staging,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn expected_openssh_cert_path(public_key_path: &Path) -> String {
|
fn expected_openssh_cert_path(public_key_path: &Path) -> String {
|
||||||
let text = public_key_path.display().to_string();
|
let text = public_key_path.display().to_string();
|
||||||
if let Some(prefix) = text.strip_suffix(".pub") {
|
if let Some(prefix) = text.strip_suffix(".pub") {
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,11 @@ fn geth_init_in_temp_home() {
|
||||||
assert!(home.path().join("geth.sqlite").exists());
|
assert!(home.path().join("geth.sqlite").exists());
|
||||||
assert!(home.path().join("identity/agent.ed25519").exists());
|
assert!(home.path().join("identity/agent.ed25519").exists());
|
||||||
assert!(home.path().join("config.toml").exists());
|
assert!(home.path().join("config.toml").exists());
|
||||||
|
assert!(
|
||||||
|
std::fs::read_to_string(home.path().join("config.toml"))
|
||||||
|
.expect("read config")
|
||||||
|
.contains("relay_mode = \"default\"")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -68,6 +73,11 @@ fn geth_status_against_running_daemon() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
assert!(run_geth(home.path(), &["init"]).status.success());
|
assert!(run_geth(home.path(), &["init"]).status.success());
|
||||||
|
std::fs::write(
|
||||||
|
home.path().join("config.toml"),
|
||||||
|
"[iroh]\nrelay_mode = \"disabled\"\n",
|
||||||
|
)
|
||||||
|
.expect("write config");
|
||||||
let mut daemon = spawn_daemon(home.path());
|
let mut daemon = spawn_daemon(home.path());
|
||||||
wait_for_socket(&home.path().join("run/geth.sock"));
|
wait_for_socket(&home.path().join("run/geth.sock"));
|
||||||
|
|
||||||
|
|
@ -84,6 +94,7 @@ fn geth_status_against_running_daemon() {
|
||||||
assert!(stdout.contains("geth daemon: running"));
|
assert!(stdout.contains("geth daemon: running"));
|
||||||
assert!(stdout.contains("agent:"));
|
assert!(stdout.contains("agent:"));
|
||||||
assert!(stdout.contains("endpoint:"));
|
assert!(stdout.contains("endpoint:"));
|
||||||
|
assert!(stdout.contains("iroh relay: disabled"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
`geth` is a single-binary local-first mesh runtime. One executable provides both
|
`geth` is a single-binary local-first mesh runtime. One executable provides both
|
||||||
daemon mode and control mode. The daemon owns local identity, metadata storage,
|
daemon mode and control mode. The daemon owns local identity, metadata storage,
|
||||||
the future shared Iroh endpoint, resource registry, module routing, and local
|
the shared Iroh endpoint, resource registry, module routing, and local control
|
||||||
control socket. Control commands connect to the Unix socket and send typed JSONL
|
socket. Control commands connect to the Unix socket and send typed JSONL
|
||||||
requests.
|
requests.
|
||||||
|
|
||||||
Service management is also exposed through the single binary. `geth daemon
|
Service management is also exposed through the single binary. `geth daemon
|
||||||
|
|
@ -23,8 +23,9 @@ Rust-1.85-compatible candidates in the 0.93-0.95 range failed to compile through
|
||||||
a transitive `ed25519-dalek` prerelease dependency. `geth-iroh` wraps
|
a transitive `ed25519-dalek` prerelease dependency. `geth-iroh` wraps
|
||||||
`iroh::Endpoint::builder()`, configures geth ALPNs with `Builder::alpns`, uses
|
`iroh::Endpoint::builder()`, configures geth ALPNs with `Builder::alpns`, uses
|
||||||
`Builder::relay_mode`, persists an `iroh::SecretKey` as hex-encoded 32-byte key
|
`Builder::relay_mode`, persists an `iroh::SecretKey` as hex-encoded 32-byte key
|
||||||
material, and shuts down through `Endpoint::close().await`. Relay mode defaults
|
material, and shuts down through `Endpoint::close().await`. The default config
|
||||||
to disabled until daemon policy and discovery are implemented.
|
uses Iroh's default relay policy; local-only/offline development can set
|
||||||
|
`[iroh].relay_mode = "disabled"`.
|
||||||
|
|
||||||
The target product should use Iroh relay support for practical internet
|
The target product should use Iroh relay support for practical internet
|
||||||
connectivity and mDNS/LAN discovery for local networks. These are connectivity
|
connectivity and mDNS/LAN discovery for local networks. These are connectivity
|
||||||
|
|
|
||||||
|
|
@ -74,14 +74,20 @@ geth-to-geth connections without granting trust from discovery alone.
|
||||||
- Endpoint identity is bound to agent/node identity in local metadata.
|
- Endpoint identity is bound to agent/node identity in local metadata.
|
||||||
- Restarting the daemon preserves higher-level node identity.
|
- Restarting the daemon preserves higher-level node identity.
|
||||||
|
|
||||||
- `[ ]` Relay policy and configuration.
|
- `[x]` Built-in relay policy and configuration.
|
||||||
Acceptance criteria:
|
Acceptance criteria:
|
||||||
- Config can select disabled, default Iroh relays, staging relays, and future
|
- Config can select disabled, default Iroh relays, and staging relays.
|
||||||
custom relay maps.
|
|
||||||
- The intended product default uses relays unless explicitly disabled.
|
- The intended product default uses relays unless explicitly disabled.
|
||||||
- `geth status --json` reports the selected relay mode.
|
- `geth status --json` reports the selected relay mode.
|
||||||
- Tests cover config parsing and endpoint builder relay-mode selection.
|
- Tests cover config parsing and endpoint builder relay-mode selection.
|
||||||
|
|
||||||
|
- `[ ]` Custom relay maps.
|
||||||
|
Acceptance criteria:
|
||||||
|
- Config can define and select named custom relay maps.
|
||||||
|
- Invalid relay URLs fail config validation with clear errors.
|
||||||
|
- Status output identifies the selected custom relay map without exposing
|
||||||
|
unrelated config.
|
||||||
|
|
||||||
- `[ ]` LAN mDNS discovery.
|
- `[ ]` LAN mDNS discovery.
|
||||||
Acceptance criteria:
|
Acceptance criteria:
|
||||||
- The daemon can advertise and discover local geth peer cards over mDNS.
|
- The daemon can advertise and discover local geth peer cards over mDNS.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue