feat(ente-zero): wire de Arje con brahman-net (red P2P opcional + keypair persistente)
Cierra el ultimo pendiente del plan de red: Arje ahora puede arrancar
opcionalmente con BrahmanNet configurado, persistir su identidad
libp2p entre reboots, y participar en la malla brahman como nodo
publico. Sin breaking changes: usuarios actuales (sin env vars) siguen
viendo el comportamiento Unix-only de antes.
Activacion por env vars:
- BRAHMAN_LISTEN_MULTIADDR — si set, activa la red P2P. Ej:
/ip4/0.0.0.0/tcp/4101 (publico), /ip4/127.0.0.1/tcp/0 (loopback).
- BRAHMAN_KEYPAIR_PATH — override del path donde se persiste la
keypair Ed25519. Defaults: PID 1 -> /var/lib/brahman/init-keypair.bin;
dev mode -> $XDG_DATA_HOME/brahman/init-keypair.bin con fallbacks.
- BRAHMAN_BOOTSTRAP_PEERS — multiaddrs coma-separados a dial-ear al
arranque para entrar al DHT.
Comportamiento al activarse:
1. keypair_store::load_or_generate carga keypair de disco o genera y
persiste una nueva (32 bytes raw, 0o600, atomic rename). peer_id
estable across reboots.
2. BrahmanNet::with_keypair arma el swarm con esa identidad.
3. net.listen() resuelve la addr y se loggea.
4. BRAHMAN_BOOTSTRAP_PEERS si set -> dial cada multiaddr.
5. ServerConfig.net = Some(net), activando announce_outputs automatico
en el DHT por cada Card con outputs.
6. Ademas del Unix accept loop, se monta libp2p accept loop sobre el
mismo Server compartido (Arc<Server>). Sesiones locales y remotas
conviven en las mismas tablas.
Refactor del Unix accept loop: antes consumia server via run().await;
ahora usa Arc<Server>::accept_one().await en loop para coexistir con
el libp2p accept loop sin moverse el server.
Degradacion gracil: si la keypair no carga, multiaddr invalido,
listen falla, bootstrap dial revienta -> log + continuamos en
Unix-only. Doctrina PID 1 ("ningun subsistema opcional rompe el
bucle primordial") preservada.
Tests: 4 unit en keypair_store: generate_persist_and_reload_yields_
same_peer_id (la property fundamental), rejects_corrupted_file,
persisted_file_is_owner_only (0o600 verificados), default_path_
honors_env.
Activacion en una linea:
BRAHMAN_LISTEN_MULTIADDR=/ip4/0.0.0.0/tcp/4101 ente-zero
Pendientes: stop_providing en cleanup, allowlist/denylist (PKI),
rotacion de keypair sin perder peer_id.
This commit is contained in:
@@ -6,6 +6,80 @@ ratio/diff ver `git show <sha>`.
|
|||||||
|
|
||||||
## 2026-05-09
|
## 2026-05-09
|
||||||
|
|
||||||
|
### feat(ente-zero): wire de Arje con brahman-net (red P2P opcional + identidad persistente)
|
||||||
|
Cierra el último pendiente del plan de red: Arje ahora puede arrancar
|
||||||
|
opcionalmente con `BrahmanNet` configurado, persistir su identidad
|
||||||
|
libp2p entre reboots, y participar en la malla brahman como nodo
|
||||||
|
público. Sin breaking changes: usuarios actuales (sin env vars) siguen
|
||||||
|
viendo el comportamiento Unix-only de antes.
|
||||||
|
|
||||||
|
Activación por env vars:
|
||||||
|
- **`BRAHMAN_LISTEN_MULTIADDR`** — si set, activa la red P2P. Ej:
|
||||||
|
`/ip4/0.0.0.0/tcp/4101` (público), `/ip4/127.0.0.1/tcp/0` (loopback,
|
||||||
|
port aleatorio). Sin la var, `brahman_net = None` y todo sigue
|
||||||
|
como antes.
|
||||||
|
- **`BRAHMAN_KEYPAIR_PATH`** — override del path donde se persiste
|
||||||
|
la keypair Ed25519 de identidad libp2p del nodo. Defaults sensatos:
|
||||||
|
- PID 1 (root): `/var/lib/brahman/init-keypair.bin`.
|
||||||
|
- Dev mode: `$XDG_DATA_HOME/brahman/init-keypair.bin` →
|
||||||
|
`$HOME/.local/share/brahman/init-keypair.bin` →
|
||||||
|
`/tmp/brahman-init-keypair.bin` (último recurso).
|
||||||
|
- **`BRAHMAN_BOOTSTRAP_PEERS`** — lista coma-separada de multiaddrs
|
||||||
|
para dial-ear al arranque y entrar al DHT. Sin esto, el nodo
|
||||||
|
arranca aislado hasta que alguien se conecte a él.
|
||||||
|
|
||||||
|
Comportamiento al activarse:
|
||||||
|
1. `keypair_store::load_or_generate(path)` carga la keypair de disco
|
||||||
|
o genera+persiste una nueva (32 bytes raw, permisos 0o600,
|
||||||
|
atomic rename). Reboots conservan el `peer_id`.
|
||||||
|
2. `BrahmanNet::with_keypair(kp)` arma el swarm con esa identidad.
|
||||||
|
3. `net.listen(multiaddr)` espera dirección resuelta y la loggea.
|
||||||
|
4. `BRAHMAN_BOOTSTRAP_PEERS` (si set) → dial a cada multiaddr.
|
||||||
|
5. El handshake server se levanta con `ServerConfig.net = Some(net)`,
|
||||||
|
que activa `announce_outputs` automático en el DHT por cada Card
|
||||||
|
con outputs.
|
||||||
|
6. Además del Unix accept loop (existing), se monta un libp2p accept
|
||||||
|
loop sobre el mismo `Server` compartido. Sesiones locales y
|
||||||
|
remotas conviven en las mismas tablas (sessions, push_table,
|
||||||
|
broker, last_matches).
|
||||||
|
|
||||||
|
Refactor del Unix accept loop: antes consumía el server vía
|
||||||
|
`server.run().await`; ahora usa `Arc<Server>::accept_one().await` en
|
||||||
|
loop para coexistir con el libp2p accept loop sin moverse el server.
|
||||||
|
|
||||||
|
Degradación grácil en cada paso: si la keypair no carga, si el
|
||||||
|
multiaddr es inválido, si el listen falla, si el bootstrap dial
|
||||||
|
revienta — loggeamos y seguimos en modo Unix-only. La doctrina de
|
||||||
|
PID 1 ("ningún subsistema opcional rompe el bucle primordial") se
|
||||||
|
mantiene.
|
||||||
|
|
||||||
|
Tests: 4 unit en `keypair_store`:
|
||||||
|
- `generate_persist_and_reload_yields_same_peer_id` — peer_id estable
|
||||||
|
across reloads (la propiedad fundamental).
|
||||||
|
- `rejects_corrupted_file` — archivo de tamaño incorrecto rechazado.
|
||||||
|
- `persisted_file_is_owner_only` — permisos 0o600 verificados.
|
||||||
|
- `default_path_honors_env` — `BRAHMAN_KEYPAIR_PATH` override
|
||||||
|
respeta tanto dev como root mode.
|
||||||
|
|
||||||
|
Ente-zero compila clean. Ningún test del workspace regresa.
|
||||||
|
|
||||||
|
Lo que esto desbloquea hoy:
|
||||||
|
- Para activar Arje como nodo público, basta:
|
||||||
|
```sh
|
||||||
|
BRAHMAN_LISTEN_MULTIADDR=/ip4/0.0.0.0/tcp/4101 ente-zero
|
||||||
|
```
|
||||||
|
- Cualquier consumer (en otra máquina) puede luego dial-ar a ese
|
||||||
|
multiaddr + descubrir Cards anunciadas via DHT + abrir handshake
|
||||||
|
remoto firmado.
|
||||||
|
- La identidad del nodo (su `peer_id`) sobrevive reboots, así que
|
||||||
|
los nodos remotos pueden cachear "este peer_id es Arje en
|
||||||
|
máquina X" sin invalidarse cada vez.
|
||||||
|
|
||||||
|
Pendientes futuros:
|
||||||
|
- `stop_providing` al cleanup de sesión (records DHT con TTL ~24h).
|
||||||
|
- Allowlist/Denylist de peers (PKI explícito).
|
||||||
|
- Rotación de keypair sin perder peer_id (multi-key identity).
|
||||||
|
|
||||||
### feat(brahman-handshake): Fase 3 — trust remoto vía firma Ed25519 anclada al peer libp2p
|
### feat(brahman-handshake): Fase 3 — trust remoto vía firma Ed25519 anclada al peer libp2p
|
||||||
Cuarto y último paso del plan "el encuentro entre Entes no se
|
Cuarto y último paso del plan "el encuentro entre Entes no se
|
||||||
restringe a local". Cierra la falla de seguridad que dejaba la red
|
restringe a local". Cierra la falla de seguridad que dejaba la red
|
||||||
|
|||||||
Generated
+2
@@ -2969,6 +2969,7 @@ dependencies = [
|
|||||||
"brahman-admin",
|
"brahman-admin",
|
||||||
"brahman-broker",
|
"brahman-broker",
|
||||||
"brahman-handshake",
|
"brahman-handshake",
|
||||||
|
"brahman-net",
|
||||||
"ente-brain",
|
"ente-brain",
|
||||||
"ente-bus",
|
"ente-bus",
|
||||||
"ente-card",
|
"ente-card",
|
||||||
@@ -2982,6 +2983,7 @@ dependencies = [
|
|||||||
"nix 0.29.0",
|
"nix 0.29.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ ente-echo = { path = "../ente-echo" } # solo para constantes del demo
|
|||||||
brahman-handshake = { path = "../brahman-handshake" }
|
brahman-handshake = { path = "../brahman-handshake" }
|
||||||
brahman-broker = { path = "../brahman-broker" }
|
brahman-broker = { path = "../brahman-broker" }
|
||||||
brahman-admin = { path = "../brahman-admin" }
|
brahman-admin = { path = "../brahman-admin" }
|
||||||
|
brahman-net = { path = "../../shared/brahman-net" }
|
||||||
|
|
||||||
# Runtime / utilidades de PID 1
|
# Runtime / utilidades de PID 1
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
@@ -36,3 +37,6 @@ libc = { workspace = true }
|
|||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true }
|
tracing-subscriber = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile = { workspace = true }
|
||||||
|
|||||||
@@ -0,0 +1,184 @@
|
|||||||
|
//! Persistencia de la keypair Ed25519 de identidad libp2p de Arje.
|
||||||
|
//!
|
||||||
|
//! El `peer_id` que Arje presenta en la malla `brahman-net` deriva de
|
||||||
|
//! esta keypair. Si se regenera en cada arranque, el peer_id cambia
|
||||||
|
//! y los nodos remotos pierden la referencia. Persistir el secret a
|
||||||
|
//! disco (32 bytes raw, permisos 0o600) garantiza identidad estable.
|
||||||
|
//!
|
||||||
|
//! ## Path
|
||||||
|
//!
|
||||||
|
//! Por orden de precedencia:
|
||||||
|
//! 1. `BRAHMAN_KEYPAIR_PATH` env var (override explícito).
|
||||||
|
//! 2. Si PID 1 / root: `/var/lib/brahman/init-keypair.bin`.
|
||||||
|
//! 3. Si dev mode: `$XDG_DATA_HOME/brahman/init-keypair.bin`, fallback
|
||||||
|
//! a `$HOME/.local/share/brahman/init-keypair.bin`, último recurso
|
||||||
|
//! `/tmp/brahman-init-keypair.bin` (sin persistencia útil pero al
|
||||||
|
//! menos no rompe en CI minimalista).
|
||||||
|
//!
|
||||||
|
//! ## Formato
|
||||||
|
//!
|
||||||
|
//! 32 bytes raw del secret Ed25519. Sin header, sin metadata. La
|
||||||
|
//! public key se deriva determinísticamente al cargar. Esto evita
|
||||||
|
//! depender de un schema de serialización (postcard, json) que
|
||||||
|
//! pudiera bumpear y romper compat de identidad.
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use anyhow::{bail, Context, Result};
|
||||||
|
use brahman_net::Keypair;
|
||||||
|
|
||||||
|
/// Tamaño exacto del secret Ed25519.
|
||||||
|
const SECRET_LEN: usize = 32;
|
||||||
|
|
||||||
|
/// Carga la keypair desde `path` si existe, o genera una nueva,
|
||||||
|
/// la persiste y la devuelve. Devuelve también si fue cargada (true)
|
||||||
|
/// o generada (false), para logging.
|
||||||
|
pub fn load_or_generate(path: &Path) -> Result<(Keypair, bool)> {
|
||||||
|
if path.exists() {
|
||||||
|
let bytes = std::fs::read(path)
|
||||||
|
.with_context(|| format!("leer keypair de {}", path.display()))?;
|
||||||
|
if bytes.len() != SECRET_LEN {
|
||||||
|
bail!(
|
||||||
|
"keypair en {} tiene {} bytes, esperaba {}",
|
||||||
|
path.display(),
|
||||||
|
bytes.len(),
|
||||||
|
SECRET_LEN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let mut secret = [0u8; SECRET_LEN];
|
||||||
|
secret.copy_from_slice(&bytes);
|
||||||
|
let kp = Keypair::ed25519_from_bytes(secret)
|
||||||
|
.with_context(|| format!("decodificar keypair en {}", path.display()))?;
|
||||||
|
Ok((kp, true))
|
||||||
|
} else {
|
||||||
|
let kp = Keypair::generate_ed25519();
|
||||||
|
save(path, &kp).context("persistir keypair recién generada")?;
|
||||||
|
Ok((kp, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Persiste el secret de `keypair` a `path`. Crea directorios padres,
|
||||||
|
/// escribe atómico (vía rename), y aplica permisos 0o600 (sólo dueño).
|
||||||
|
fn save(path: &Path, keypair: &Keypair) -> Result<()> {
|
||||||
|
let secret = extract_secret_bytes(keypair)?;
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
std::fs::create_dir_all(parent)
|
||||||
|
.with_context(|| format!("crear dir {}", parent.display()))?;
|
||||||
|
}
|
||||||
|
let tmp = path.with_extension("tmp");
|
||||||
|
std::fs::write(&tmp, secret).with_context(|| format!("write tmp {}", tmp.display()))?;
|
||||||
|
apply_owner_only_perms(&tmp).context("permisos 0o600 en tmp")?;
|
||||||
|
std::fs::rename(&tmp, path)
|
||||||
|
.with_context(|| format!("rename {} → {}", tmp.display(), path.display()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_secret_bytes(keypair: &Keypair) -> Result<[u8; SECRET_LEN]> {
|
||||||
|
// libp2p::Keypair no expone secret() directo; pasamos
|
||||||
|
// por la variante ed25519. Solo Ed25519 soportado en brahman-net,
|
||||||
|
// así que el unwrap es seguro tras with_keypair.
|
||||||
|
let ed = keypair
|
||||||
|
.clone()
|
||||||
|
.try_into_ed25519()
|
||||||
|
.map_err(|_| anyhow::anyhow!("la keypair no es Ed25519 (no debería pasar)"))?;
|
||||||
|
let bytes = ed.secret();
|
||||||
|
let raw: &[u8] = bytes.as_ref();
|
||||||
|
if raw.len() != SECRET_LEN {
|
||||||
|
bail!("ed25519 secret no es {} bytes", SECRET_LEN);
|
||||||
|
}
|
||||||
|
let mut out = [0u8; SECRET_LEN];
|
||||||
|
out.copy_from_slice(raw);
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn apply_owner_only_perms(path: &Path) -> std::io::Result<()> {
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
let perms = std::fs::Permissions::from_mode(0o600);
|
||||||
|
std::fs::set_permissions(path, perms)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
fn apply_owner_only_perms(_path: &Path) -> std::io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resuelve el path del keystore según convención (env > root path >
|
||||||
|
/// XDG > HOME > tmp).
|
||||||
|
pub fn default_path(dev_mode: bool) -> PathBuf {
|
||||||
|
if let Ok(p) = std::env::var("BRAHMAN_KEYPAIR_PATH") {
|
||||||
|
return PathBuf::from(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dev_mode {
|
||||||
|
// PID 1: paths del sistema. /var/lib es el lugar canónico
|
||||||
|
// para state persistente de servicios root.
|
||||||
|
return PathBuf::from("/var/lib/brahman/init-keypair.bin");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dev mode: paths de usuario.
|
||||||
|
if let Ok(xdg) = std::env::var("XDG_DATA_HOME") {
|
||||||
|
return PathBuf::from(xdg).join("brahman").join("init-keypair.bin");
|
||||||
|
}
|
||||||
|
if let Ok(home) = std::env::var("HOME") {
|
||||||
|
return PathBuf::from(home)
|
||||||
|
.join(".local")
|
||||||
|
.join("share")
|
||||||
|
.join("brahman")
|
||||||
|
.join("init-keypair.bin");
|
||||||
|
}
|
||||||
|
PathBuf::from("/tmp/brahman-init-keypair.bin")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generate_persist_and_reload_yields_same_peer_id() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let path = tmp.path().join("identity.bin");
|
||||||
|
let (kp1, loaded) = load_or_generate(&path).unwrap();
|
||||||
|
assert!(!loaded, "primera vez debe generar");
|
||||||
|
let peer1 = kp1.public().to_peer_id();
|
||||||
|
|
||||||
|
let (kp2, loaded) = load_or_generate(&path).unwrap();
|
||||||
|
assert!(loaded, "segunda vez debe cargar");
|
||||||
|
let peer2 = kp2.public().to_peer_id();
|
||||||
|
|
||||||
|
assert_eq!(peer1, peer2, "peer_id estable across reloads");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_corrupted_file() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let path = tmp.path().join("bad.bin");
|
||||||
|
std::fs::write(&path, b"too short").unwrap();
|
||||||
|
assert!(load_or_generate(&path).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn persisted_file_is_owner_only() {
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let path = tmp.path().join("perm.bin");
|
||||||
|
let _ = load_or_generate(&path).unwrap();
|
||||||
|
let mode = std::fs::metadata(&path).unwrap().permissions().mode();
|
||||||
|
assert_eq!(
|
||||||
|
mode & 0o777,
|
||||||
|
0o600,
|
||||||
|
"permisos del keypair file deben ser 0o600 (solo dueño), got {:o}",
|
||||||
|
mode & 0o777
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_path_honors_env() {
|
||||||
|
std::env::set_var("BRAHMAN_KEYPAIR_PATH", "/custom/path.bin");
|
||||||
|
assert_eq!(default_path(false), PathBuf::from("/custom/path.bin"));
|
||||||
|
assert_eq!(default_path(true), PathBuf::from("/custom/path.bin"));
|
||||||
|
std::env::remove_var("BRAHMAN_KEYPAIR_PATH");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ mod brain_glue;
|
|||||||
mod bus;
|
mod bus;
|
||||||
mod events;
|
mod events;
|
||||||
mod graph;
|
mod graph;
|
||||||
|
mod keypair_store;
|
||||||
mod seed;
|
mod seed;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
@@ -158,23 +159,66 @@ async fn primordial_loop(
|
|||||||
current_context: broker_context.clone(),
|
current_context: broker_context.clone(),
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// Brahman-net opcional: si BRAHMAN_LISTEN_MULTIADDR está set,
|
||||||
|
// levantamos la malla P2P y la pasamos como ServerConfig.net (Fase
|
||||||
|
// 2 wire) para que cada Card con outputs se anuncie al DHT y
|
||||||
|
// pueda ser descubierta por nodos remotos. Identidad libp2p
|
||||||
|
// persistida en disco vía keypair_store (peer_id estable across
|
||||||
|
// reboots).
|
||||||
|
let brahman_net = setup_brahman_net(dev_mode).await;
|
||||||
|
|
||||||
let brahman_sock = brahman_handshake::transport::default_socket_path();
|
let brahman_sock = brahman_handshake::transport::default_socket_path();
|
||||||
match brahman_handshake::server::Server::bind(
|
match brahman_handshake::server::Server::bind(
|
||||||
&brahman_sock,
|
&brahman_sock,
|
||||||
brahman_handshake::server::ServerConfig {
|
brahman_handshake::server::ServerConfig {
|
||||||
init_attached: true,
|
init_attached: true,
|
||||||
broker: Some(brahman_broker.clone()),
|
broker: Some(brahman_broker.clone()),
|
||||||
// Fase 2: el Init aún no expone red P2P por default. Cuando
|
net: brahman_net.clone(),
|
||||||
// arje quiera publicar Cards al DHT remoto, pasar aquí un
|
|
||||||
// `Some(Arc<BrahmanNet>)` con la malla configurada.
|
|
||||||
net: None,
|
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Ok(server) => {
|
Ok(server) => {
|
||||||
info!(socket = %brahman_sock.display(), "brahman handshake escuchando");
|
info!(socket = %brahman_sock.display(), "brahman handshake escuchando (Unix)");
|
||||||
|
// Si hay malla P2P, además del Unix accept loop levantamos
|
||||||
|
// el accept loop libp2p sobre el mismo Server compartido.
|
||||||
|
// Las sesiones locales y remotas conviven en las mismas
|
||||||
|
// tablas (sessions, push_table, broker).
|
||||||
|
let server = std::sync::Arc::new(server);
|
||||||
|
if let Some(net) = brahman_net.clone() {
|
||||||
|
let s_libp2p = server.clone();
|
||||||
|
let n_libp2p = net.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = server.run().await {
|
if let Err(e) = brahman_handshake::network::run_libp2p_accept_loop(
|
||||||
warn!(?e, "brahman handshake server cayó");
|
s_libp2p, n_libp2p,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
warn!(?e, "brahman handshake libp2p accept loop cayó");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
info!(
|
||||||
|
"brahman handshake escuchando también vía libp2p (peer_id {})",
|
||||||
|
net.peer_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Unix accept loop: usa Arc<Server> en lugar del consume
|
||||||
|
// de run() para coexistir con el libp2p accept loop.
|
||||||
|
let s_unix = server.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
match s_unix.accept_one().await {
|
||||||
|
Ok(session) => {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(e) = session.handle().await {
|
||||||
|
warn!(?e, "session Unix terminó con error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!(?e, "brahman handshake accept_one Unix falló");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -573,3 +617,84 @@ async fn feed_brain(
|
|||||||
ente_brain::dispatch_actions(&rules, sink).await;
|
ente_brain::dispatch_actions(&rules, sink).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inicializa la malla `brahman-net` opcional. Activa sólo si
|
||||||
|
/// `BRAHMAN_LISTEN_MULTIADDR` está set. Identidad libp2p persistente
|
||||||
|
/// vía `keypair_store`. Bootstrap del DHT vía `BRAHMAN_BOOTSTRAP_PEERS`
|
||||||
|
/// (lista coma-separada de multiaddrs, opcional).
|
||||||
|
///
|
||||||
|
/// Toda fase de setup degrada grácilmente: si la keypair no carga,
|
||||||
|
/// si el listen falla, si bootstrap dial falla — loggea y devuelve
|
||||||
|
/// `None`. El Init sigue funcionando en modo Unix-only.
|
||||||
|
async fn setup_brahman_net(
|
||||||
|
dev_mode: bool,
|
||||||
|
) -> Option<std::sync::Arc<brahman_net::BrahmanNet>> {
|
||||||
|
let listen_addr = match std::env::var("BRAHMAN_LISTEN_MULTIADDR") {
|
||||||
|
Ok(s) if !s.is_empty() => s,
|
||||||
|
_ => {
|
||||||
|
tracing::debug!(
|
||||||
|
"brahman-net deshabilitado (sin BRAHMAN_LISTEN_MULTIADDR)"
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let multiaddr: brahman_net::Multiaddr = match listen_addr.parse() {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(e) => {
|
||||||
|
warn!(addr = %listen_addr, ?e, "BRAHMAN_LISTEN_MULTIADDR inválido — net deshabilitado");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let keypair_path = keypair_store::default_path(dev_mode);
|
||||||
|
let (keypair, loaded) = match keypair_store::load_or_generate(&keypair_path) {
|
||||||
|
Ok(kp) => kp,
|
||||||
|
Err(e) => {
|
||||||
|
warn!(path = %keypair_path.display(), ?e, "no pude cargar/generar keypair libp2p — net deshabilitado");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
info!(
|
||||||
|
path = %keypair_path.display(),
|
||||||
|
peer_id = %keypair.public().to_peer_id(),
|
||||||
|
loaded = loaded,
|
||||||
|
"identidad libp2p {}",
|
||||||
|
if loaded { "cargada" } else { "generada y persistida" }
|
||||||
|
);
|
||||||
|
|
||||||
|
let net = match brahman_net::BrahmanNet::with_keypair(keypair) {
|
||||||
|
Ok(n) => std::sync::Arc::new(n),
|
||||||
|
Err(e) => {
|
||||||
|
warn!(?e, "BrahmanNet::with_keypair falló — net deshabilitado");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let actual = net.listen(multiaddr).await;
|
||||||
|
info!(addr = %actual, peer_id = %net.peer_id, "brahman-net escuchando");
|
||||||
|
|
||||||
|
// Bootstrap opcional: dial-ar a peers conocidos para entrar al
|
||||||
|
// DHT. Sin bootstrap, el nodo arranca aislado hasta que alguien
|
||||||
|
// se conecte a él.
|
||||||
|
if let Ok(bootstrap) = std::env::var("BRAHMAN_BOOTSTRAP_PEERS") {
|
||||||
|
let mut dialed = 0usize;
|
||||||
|
for entry in bootstrap.split(',').filter(|s| !s.is_empty()) {
|
||||||
|
match entry.parse::<brahman_net::Multiaddr>() {
|
||||||
|
Ok(addr) => {
|
||||||
|
net.dial(addr.clone());
|
||||||
|
dialed += 1;
|
||||||
|
tracing::debug!(peer = %addr, "dial bootstrap");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!(entry = %entry, ?e, "bootstrap multiaddr inválido — saltado");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dialed > 0 {
|
||||||
|
info!(count = dialed, "bootstrap peers dial-eados");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(net)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user