commit 3800ffbeebb24991b3031db2d201bf04d6a62794
parent e0ecaced5986cdc07ff47df7614d67f57abf10a7
Author: Joris Hartog <jorishartog@hotmail.com>
Date: Mon, 10 Aug 2026 20:05:22 +0200
Add UI-configurable P2P bind port
Diffstat:
5 files changed, 218 insertions(+), 26 deletions(-)
diff --git a/src/adapters/config_store.rs b/src/adapters/config_store.rs
@@ -20,6 +20,7 @@ pub const DEFAULT_BURN_FEE: Amount = DEFAULT_BURN_AMOUNT;
pub const DEFAULT_RECOVERY_VDF_TOP_RANK_PERCENT: u8 = 50;
pub const DEFAULT_POW_MINING_WORKERS: u8 = 1;
pub const MAX_POW_MINING_WORKERS: u8 = 32;
+pub const DEFAULT_P2P_BIND_PORT: u16 = 9444;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct UiConfig {
@@ -34,6 +35,7 @@ pub struct UiConfig {
pub recovery_vdf_top_rank_percent: u8,
pub keep_track_of_metrics: bool,
pub p2p_accept_inbound: bool,
+ pub p2p_bind_port: u16,
pub p2p_announce_addr: Option<String>,
pub peers: Vec<String>,
pub address_book: BTreeMap<String, String>,
@@ -52,6 +54,7 @@ impl Default for UiConfig {
recovery_vdf_top_rank_percent: DEFAULT_RECOVERY_VDF_TOP_RANK_PERCENT,
keep_track_of_metrics: false,
p2p_accept_inbound: false,
+ p2p_bind_port: DEFAULT_P2P_BIND_PORT,
p2p_announce_addr: None,
peers: Vec::new(),
address_book: BTreeMap::new(),
@@ -83,6 +86,8 @@ struct ConfigFile {
keep_track_of_metrics: bool,
#[serde(default)]
p2p_accept_inbound: Option<bool>,
+ #[serde(default = "default_p2p_bind_port")]
+ p2p_bind_port: u16,
#[serde(default)]
p2p_announce_addr: Option<String>,
#[serde(default)]
@@ -120,6 +125,7 @@ pub fn save(path: &Path, config: &UiConfig) -> Result<()> {
recovery_vdf_top_rank_percent: Some(config.recovery_vdf_top_rank_percent),
keep_track_of_metrics: config.keep_track_of_metrics,
p2p_accept_inbound: Some(config.p2p_accept_inbound),
+ p2p_bind_port: config.p2p_bind_port,
p2p_announce_addr: config.p2p_announce_addr.clone(),
peers: config.peers.clone(),
address_book: config.address_book.clone(),
@@ -177,6 +183,7 @@ fn load(path: &Path) -> Result<UiConfig> {
.min(100),
keep_track_of_metrics: stored.keep_track_of_metrics,
p2p_accept_inbound,
+ p2p_bind_port: stored.p2p_bind_port,
p2p_announce_addr: stored.p2p_announce_addr,
peers: stored.peers,
address_book: stored.address_book,
@@ -191,6 +198,10 @@ fn default_pow_mining_workers() -> u8 {
DEFAULT_POW_MINING_WORKERS
}
+fn default_p2p_bind_port() -> u16 {
+ DEFAULT_P2P_BIND_PORT
+}
+
fn create_config_file(path: &Path) -> Result<File> {
let mut options = OpenOptions::new();
options.write(true).create(true).truncate(true);
@@ -233,6 +244,7 @@ mod tests {
assert!(!stored.contains("required_burn"));
assert!(stored.contains("\"keep_track_of_metrics\": false"));
assert!(stored.contains("\"p2p_accept_inbound\": false"));
+ assert!(stored.contains("\"p2p_bind_port\": 9444"));
assert!(stored.contains("\"p2p_announce_addr\": null"));
assert!(stored.contains("\"peers\": []"));
assert!(stored.contains("\"address_book\": {}"));
@@ -255,6 +267,7 @@ mod tests {
burn_fee: 3 * MICRO_IUNA,
keep_track_of_metrics: true,
p2p_accept_inbound: true,
+ p2p_bind_port: 9555,
p2p_announce_addr: Some("203.0.113.10:9444".to_string()),
peers: vec!["127.0.0.1:9444".to_string()],
address_book: [("iuna-address".to_string(), "Alice".to_string())].into(),
@@ -273,6 +286,7 @@ mod tests {
assert_eq!(config.burn_fee, 3 * MICRO_IUNA);
assert!(config.keep_track_of_metrics);
assert!(config.p2p_accept_inbound);
+ assert_eq!(config.p2p_bind_port, 9555);
assert_eq!(
config.p2p_announce_addr.as_deref(),
Some("203.0.113.10:9444")
@@ -321,6 +335,7 @@ mod tests {
assert_eq!(config.burn_fee, DEFAULT_BURN_FEE);
assert!(!config.keep_track_of_metrics);
assert!(!config.p2p_accept_inbound);
+ assert_eq!(config.p2p_bind_port, 9444);
assert_eq!(config.peers, vec!["127.0.0.1:9444"]);
assert!(config.address_book.is_empty());
}
diff --git a/src/adapters/http.rs b/src/adapters/http.rs
@@ -169,6 +169,15 @@ struct P2pAnnounceForm {
#[derive(Debug, Deserialize)]
struct P2pInboundForm {
enabled: bool,
+ bind_port: Option<u16>,
+}
+
+#[derive(Debug, Serialize)]
+struct ConfigResponse {
+ #[serde(flatten)]
+ config: UiConfig,
+ p2p_inbound_runtime_active: bool,
+ p2p_runtime_bind_addr: String,
}
#[derive(Debug, Deserialize)]
@@ -851,8 +860,12 @@ async fn api_blocks(
Json(ui_blocks(blocks, &snapshot, &pending, &burn_leader_ranks))
}
-async fn api_config(State(state): State<HttpState>) -> Json<UiConfig> {
- Json(state.ui_config.lock().await.clone())
+async fn api_config(State(state): State<HttpState>) -> Json<ConfigResponse> {
+ Json(ConfigResponse {
+ config: state.ui_config.lock().await.clone(),
+ p2p_inbound_runtime_active: state.gossip.accepts_inbound().await,
+ p2p_runtime_bind_addr: state.gossip.listen_addr().to_string(),
+ })
}
async fn api_wallet_setup(
@@ -1152,7 +1165,7 @@ async fn api_p2p_inbound_form(
State(state): State<HttpState>,
Form(form): Form<P2pInboundForm>,
) -> Json<ActionResponse> {
- action_json(set_p2p_accept_inbound(&state, form.enabled).await)
+ action_json(set_p2p_accept_inbound(&state, form.enabled, form.bind_port).await)
}
async fn burn_per_block_form(
@@ -1346,15 +1359,24 @@ async fn set_p2p_announce_addr(state: &HttpState, addr: String) -> Result<()> {
Ok(())
}
-async fn set_p2p_accept_inbound(state: &HttpState, enabled: bool) -> Result<()> {
+async fn set_p2p_accept_inbound(
+ state: &HttpState,
+ enabled: bool,
+ bind_port: Option<u16>,
+) -> Result<()> {
+ let bind_port = bind_port.unwrap_or(config_store::DEFAULT_P2P_BIND_PORT);
+ if bind_port == 0 {
+ bail!("P2P bind port must be between 1 and 65535");
+ }
let previous = state.gossip.accepts_inbound().await;
- if enabled {
+ if enabled && previous {
state.gossip.set_accept_inbound(true).await?;
}
let mut config = state.ui_config.lock().await;
let mut next_config = config.clone();
next_config.p2p_accept_inbound = enabled;
+ next_config.p2p_bind_port = bind_port;
if let Err(error) = config_store::save(&state.config_path, &next_config) {
let _ = state.gossip.set_accept_inbound(previous).await;
return Err(error);
@@ -3242,6 +3264,7 @@ const INDEX_HTML: &str = r#"<!doctype html>
.flash { position: fixed; top: 18px; right: 18px; z-index: 80; width: min(420px, calc(100vw - 36px)); border-radius: 6px; padding: 10px 12px; border: 1px solid; font-weight: 700; box-shadow: 0 18px 48px rgba(0, 0, 0, .38); }
.flash.success { color: #d5f55f; background: #1c2516; border-color: #566d25; }
.flash.error { color: #ffb1a8; background: #2a1717; border-color: #713434; }
+ .persistent-banner { border: 1px solid #566d25; border-radius: 8px; padding: 10px 12px; margin: -4px 0 16px; color: #d5f55f; background: #1c2516; font-weight: 800; }
.ok { color: #d5f55f; }
.page-title { margin-bottom: 16px; }
.setup-overlay { position: fixed; inset: 0; z-index: 30; display: grid; place-items: center; padding: 22px; background: rgba(8, 9, 10, .72); backdrop-filter: blur(8px); }
@@ -3633,6 +3656,7 @@ const INDEX_HTML: &str = r#"<!doctype html>
</header>
<div class="flash" :class="flash?.kind" x-show="flash" x-transition x-text="flash?.message"></div>
+ <div class="persistent-banner" x-show="p2pRestartRequired()" x-transition x-text="p2pRestartMessage()"></div>
<section x-show="tab === 'wallet'">
<div class="page-title">
@@ -4257,8 +4281,9 @@ const INDEX_HTML: &str = r#"<!doctype html>
</label>
</div>
<form class="settings-form public-p2p-form" x-show="p2pAcceptInbound" x-transition @submit.prevent="saveP2pAnnounce">
+ <label>Bind port<input x-model.number="p2pBindPort" @input="p2pBindPortDirty = true" type="number" min="1" max="65535" step="1" required></label>
<label>Public P2P address<input x-model="p2pAnnounceAddr" @input="p2pAnnounceDirty = true" placeholder="203.0.113.10:9444"></label>
- <div class="muted">Use this only when TCP port 9444 is reachable from the internet.</div>
+ <div class="muted">Use this only when TCP port <span x-text="p2pBindPort"></span> is reachable from the internet.</div>
<div class="setup-actions"><button class="primary" type="submit">Save</button></div>
</form>
</div>
@@ -4525,6 +4550,7 @@ const INDEX_HTML: &str = r#"<!doctype html>
</div>
<div class="setup-network-row">
<label><span x-text="setupRequiresPeer() ? 'Bootstrap peer (required)' : 'Bootstrap peer'"></span><input x-model="setupPeerAddress" placeholder="iuna.jhx.app:9444"></label>
+ <label x-show="setupNodeMode === 'listening'" x-transition>Bind port<input x-model.number="p2pBindPort" @input="p2pBindPortDirty = true" type="number" min="1" max="65535" step="1" required></label>
</div>
<div class="setup-network-copy" x-text="setupRequiresPeer() ? 'A bootstrap peer is required before this node can join the network. Known nodes help discovery; they do not control your wallet or decide valid blocks.' : 'You can add a bootstrap peer now or later from the P2P screen. Known nodes help discovery; they do not control your wallet or decide valid blocks.'"></div>
</div>
@@ -5371,18 +5397,23 @@ mod tests {
other => panic!("expected peer list, got {other:?}"),
}
- super::set_p2p_accept_inbound(&state, true).await.unwrap();
+ super::set_p2p_accept_inbound(&state, true, Some(9555))
+ .await
+ .unwrap();
let config = config_store::load_or_create(&config_path).unwrap();
assert!(config.p2p_accept_inbound);
- assert!(state.gossip.accepts_inbound().await);
+ assert_eq!(config.p2p_bind_port, 9555);
+ assert!(!state.gossip.accepts_inbound().await);
match state.gossip.peer_exchange().await {
GossipEnvelope::PeerList { peers } => {
- assert!(peers.contains(&"203.0.113.10:9444".to_string()));
+ assert!(!peers.contains(&"203.0.113.10:9444".to_string()));
}
other => panic!("expected peer list, got {other:?}"),
}
- super::set_p2p_accept_inbound(&state, false).await.unwrap();
+ super::set_p2p_accept_inbound(&state, false, None)
+ .await
+ .unwrap();
let config = config_store::load_or_create(&config_path).unwrap();
assert!(!config.p2p_accept_inbound);
assert!(!state.gossip.accepts_inbound().await);
@@ -6154,6 +6185,8 @@ mod tests {
assert!(super::INDEX_HTML.contains("selectSetupNodeMode('wallet')"));
assert!(super::INDEX_HTML.contains("selectSetupNodeMode('non-listening')"));
assert!(super::INDEX_HTML.contains("selectSetupNodeMode('listening')"));
+ assert!(super::INDEX_HTML.contains("setupNodeMode === 'listening'"));
+ assert!(super::INDEX_HTML.contains("x-model.number=\"p2pBindPort\""));
assert!(super::INDEX_HTML.contains("Change later in Settings"));
}
@@ -6177,10 +6210,24 @@ mod tests {
assert!(app_js.contains("async applySetupNodeMode()"));
assert!(app_js.contains("await this.applySetupNodeMode();"));
assert!(app_js.contains("\"/api/settings/p2p-inbound\""));
+ assert!(app_js.contains("bind_port: this.p2pBindPortValue()"));
assert!(app_js.contains("this.setUiMode(mode === \"wallet\" ? \"basic\" : \"advanced\")"));
}
#[test]
+ fn p2p_bind_port_changes_show_global_restart_notice() {
+ let app_js = include_str!("../../www/assets/iuna-ui.js");
+ assert!(super::INDEX_HTML.contains("Bind port"));
+ assert!(super::INDEX_HTML.contains("persistent-banner"));
+ assert!(super::INDEX_HTML.contains("p2pRestartRequired()"));
+ assert!(app_js.contains("p2pBindPort: 9444"));
+ assert!(app_js.contains("p2pConfiguredBindAddr()"));
+ assert!(app_js.contains("p2pRestartMessage()"));
+ assert!(app_js.contains("Restart iuna to close the public P2P listener."));
+ assert!(app_js.contains("0.0.0.0:${port}"));
+ }
+
+ #[test]
fn polling_refreshes_paged_datasets_without_visible_loaders() {
let app_js = include_str!("../../www/assets/iuna-ui.js");
assert!(app_js.contains("setInterval(() => this.refresh({ silent: true }), 5000)"));
diff --git a/src/adapters/p2p.rs b/src/adapters/p2p.rs
@@ -431,6 +431,10 @@ impl GossipNetwork {
self.inner.accept_task.lock().await.is_some()
}
+ pub fn listen_addr(&self) -> SocketAddr {
+ self.inner.listen_addr
+ }
+
pub async fn set_p2p_announce_addr(&self, addr: Option<SocketAddr>) {
*self.inner.p2p_announce_addr.lock().await = addr;
}
diff --git a/src/main.rs b/src/main.rs
@@ -1,6 +1,6 @@
use std::{
collections::BTreeMap,
- net::SocketAddr,
+ net::{Ipv4Addr, SocketAddr},
path::{Path, PathBuf},
str::FromStr,
sync::Arc,
@@ -48,13 +48,11 @@ async fn main() -> Result<()> {
);
}
let mut ui_config = config_store::load_or_create(&config_path)?;
+ let ui_config_dirty = apply_cli_p2p_config_overrides(&opts, &mut ui_config);
let p2p_announce_addr = configured_p2p_announce_addr(&opts, &ui_config)?;
- let p2p_accept_inbound = opts.p2p_announce_addr.is_some() || ui_config.p2p_accept_inbound;
- if let Some(addr) = opts.p2p_announce_addr {
- ui_config.p2p_accept_inbound = true;
- ui_config.p2p_announce_addr = Some(addr.to_string());
- }
- let advertised_p2p_addr = p2p_announce_addr.unwrap_or(opts.p2p_addr);
+ let configured_p2p_addr = configured_p2p_bind_addr(&opts, &ui_config);
+ let p2p_accept_inbound = ui_config.p2p_accept_inbound;
+ let advertised_p2p_addr = p2p_announce_addr.unwrap_or(configured_p2p_addr);
let wallet_load = load_startup_wallet(&wallet_path)?;
let wallet_address = wallet_load.address().to_string();
if opts.chain_mode == ChainMode::Genesis {
@@ -64,6 +62,8 @@ async fn main() -> Result<()> {
ui_config.burn_per_block = GENESIS_INITIAL_BURN_PER_BLOCK;
ui_config.burn_fee = GENESIS_INITIAL_BURN_FEE;
config_store::save(&config_path, &ui_config)?;
+ } else if ui_config_dirty {
+ config_store::save(&config_path, &ui_config)?;
}
let ledger =
initialize_ledger(&opts, &wallet_address, &chain_store, advertised_p2p_addr).await?;
@@ -121,7 +121,7 @@ async fn main() -> Result<()> {
println!("chain database: {}", chain_store.path().display());
println!("management UI: http://{}", opts.http_addr);
if p2p_accept_inbound {
- println!("p2p listener: {}", opts.p2p_addr);
+ println!("p2p listener: {}", configured_p2p_addr);
} else {
println!("p2p listener: disabled (outbound-only)");
}
@@ -139,7 +139,7 @@ async fn main() -> Result<()> {
let gossip = p2p::GossipNetwork::start(
Arc::clone(&node),
Arc::clone(&peers),
- opts.p2p_addr,
+ configured_p2p_addr,
p2p_announce_addr,
p2p_accept_inbound,
)
@@ -312,6 +312,38 @@ fn configured_p2p_announce_addr(
.transpose()
}
+fn configured_p2p_bind_addr(opts: &CliOptions, ui_config: &config_store::UiConfig) -> SocketAddr {
+ if opts.p2p_addr_configured || ui_config.p2p_accept_inbound {
+ return SocketAddr::from((Ipv4Addr::UNSPECIFIED, ui_config.p2p_bind_port));
+ }
+ opts.p2p_addr
+}
+
+fn apply_cli_p2p_config_overrides(
+ opts: &CliOptions,
+ ui_config: &mut config_store::UiConfig,
+) -> bool {
+ let mut dirty = false;
+ if opts.p2p_addr_configured {
+ let bind_port = opts.p2p_addr.port();
+ if ui_config.p2p_bind_port != bind_port {
+ ui_config.p2p_bind_port = bind_port;
+ dirty = true;
+ }
+ }
+ if let Some(addr) = opts.p2p_announce_addr {
+ let announce_addr = addr.to_string();
+ if !ui_config.p2p_accept_inbound
+ || ui_config.p2p_announce_addr.as_deref() != Some(&announce_addr)
+ {
+ ui_config.p2p_accept_inbound = true;
+ ui_config.p2p_announce_addr = Some(announce_addr);
+ dirty = true;
+ }
+ }
+ dirty
+}
+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ChainMode {
Setup,
@@ -325,6 +357,7 @@ struct CliOptions {
chain_db_path: Option<PathBuf>,
http_addr: SocketAddr,
p2p_addr: SocketAddr,
+ p2p_addr_configured: bool,
p2p_announce_addr: Option<SocketAddr>,
stratum_addr: Option<SocketAddr>,
peers: Vec<String>,
@@ -345,6 +378,7 @@ impl CliOptions {
chain_db_path: None,
http_addr: SocketAddr::from_str("127.0.0.1:18661")?,
p2p_addr: SocketAddr::from_str("127.0.0.1:9444")?,
+ p2p_addr_configured: false,
p2p_announce_addr: None,
stratum_addr: None,
peers: Vec::new(),
@@ -384,6 +418,7 @@ impl CliOptions {
opts.p2p_addr = next_value(&mut args, "--p2p")?
.parse()
.context("invalid --p2p address")?;
+ opts.p2p_addr_configured = true;
}
"--p2p-announce" => {
opts.p2p_announce_addr = Some(
@@ -844,10 +879,10 @@ mod tests {
use super::{
ChainMode, CliOptions, GENESIS_INITIAL_BURN_FEE, GENESIS_INITIAL_BURN_PER_BLOCK,
- StartupWallet, configured_p2p_announce_addr, extrapolate_vdf_rounds, help_text,
- initial_burn_fee, initial_burn_per_block, initialize_ledger, load_startup_wallet,
- measure_vdf_rounds, persist_chain_snapshot, run_chain_persistence_with_interval,
- validate_wallet_for_mode,
+ StartupWallet, apply_cli_p2p_config_overrides, configured_p2p_announce_addr,
+ configured_p2p_bind_addr, extrapolate_vdf_rounds, help_text, initial_burn_fee,
+ initial_burn_per_block, initialize_ledger, load_startup_wallet, measure_vdf_rounds,
+ persist_chain_snapshot, run_chain_persistence_with_interval, validate_wallet_for_mode,
};
fn parse(args: &[&str]) -> anyhow::Result<Option<CliOptions>> {
@@ -1119,6 +1154,49 @@ mod tests {
}
#[test]
+ fn configured_p2p_bind_addr_uses_configured_public_port() {
+ let opts = parse(&[]).unwrap().unwrap();
+ let config = UiConfig {
+ p2p_accept_inbound: true,
+ p2p_bind_port: 9555,
+ ..UiConfig::default()
+ };
+
+ assert_eq!(
+ configured_p2p_bind_addr(&opts, &config).to_string(),
+ "0.0.0.0:9555"
+ );
+ }
+
+ #[test]
+ fn configured_p2p_bind_addr_uses_cli_port_after_config_override() {
+ let opts = parse(&["--p2p", "127.0.0.1:9555"]).unwrap().unwrap();
+ let config = UiConfig {
+ p2p_accept_inbound: true,
+ p2p_bind_port: 9555,
+ ..UiConfig::default()
+ };
+
+ assert_eq!(
+ configured_p2p_bind_addr(&opts, &config).to_string(),
+ "0.0.0.0:9555"
+ );
+ }
+
+ #[test]
+ fn cli_p2p_port_overrides_config_bind_port() {
+ let opts = parse(&["--p2p", "127.0.0.1:9555"]).unwrap().unwrap();
+ let mut config = UiConfig {
+ p2p_accept_inbound: true,
+ p2p_bind_port: 9444,
+ ..UiConfig::default()
+ };
+
+ assert!(apply_cli_p2p_config_overrides(&opts, &mut config));
+ assert_eq!(config.p2p_bind_port, 9555);
+ }
+
+ #[test]
fn http_management_port_defaults_to_iuna_port() {
let opts = parse(&[]).unwrap().unwrap();
assert_eq!(opts.http_addr.to_string(), "127.0.0.1:18661");
diff --git a/www/assets/iuna-ui.js b/www/assets/iuna-ui.js
@@ -57,6 +57,8 @@ window.iunaApp = function iunaApp() {
addressBookDraftAddress: "",
addressBookDraftName: "",
p2pAcceptInbound: false,
+ p2pBindPort: 9444,
+ p2pBindPortDirty: false,
p2pAnnounceAddr: "",
p2pAnnounceDirty: false,
setupWallet: { address: null, seed_phrase: null, dev_verify_bypass: false, requires_peer: false },
@@ -396,6 +398,9 @@ window.iunaApp = function iunaApp() {
this.recoveryVdfTopRankPercent
);
this.p2pAcceptInbound = this.config.p2p_accept_inbound === true;
+ if (!this.p2pBindPortDirty) {
+ this.p2pBindPort = Number(this.config.p2p_bind_port || 9444);
+ }
if (
options.addressBookVersion === undefined ||
options.addressBookVersion >= this.addressBookVersion
@@ -584,7 +589,10 @@ window.iunaApp = function iunaApp() {
: "wallet";
const acceptInbound = mode === "listening";
if (this.p2pAcceptInbound !== acceptInbound) {
- await this.submitForm("/api/settings/p2p-inbound", { enabled: acceptInbound });
+ await this.submitForm("/api/settings/p2p-inbound", {
+ enabled: acceptInbound,
+ bind_port: this.p2pBindPortValue(),
+ });
this.p2pAcceptInbound = acceptInbound;
}
this.setUiMode(mode === "wallet" ? "basic" : "advanced");
@@ -1387,9 +1395,10 @@ window.iunaApp = function iunaApp() {
this.p2pAcceptInbound = enabled;
await this.postForm(
"/api/settings/p2p-inbound",
- { enabled },
- enabled ? "Public node enabled" : "Switched to outbound-only P2P"
+ { enabled, bind_port: this.p2pBindPortValue() },
+ enabled ? "Public node setting saved" : "Switched to outbound-only P2P"
);
+ this.p2pBindPortDirty = false;
await this.refreshConfig();
} catch (error) {
this.p2pAcceptInbound = previous;
@@ -1397,6 +1406,37 @@ window.iunaApp = function iunaApp() {
}
},
+ p2pBindPortValue() {
+ const port = Number(this.p2pBindPort);
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
+ throw new Error("P2P bind port must be between 1 and 65535");
+ }
+ return port;
+ },
+
+ p2pConfiguredBindAddr() {
+ const port = Number(this.config.p2p_bind_port || 9444);
+ if (!Number.isInteger(port) || port < 1 || port > 65535) return null;
+ return `0.0.0.0:${port}`;
+ },
+
+ p2pRestartRequired() {
+ const runtimeActive = this.config.p2p_inbound_runtime_active === true;
+ if (this.p2pAcceptInbound !== runtimeActive) return true;
+ if (!this.p2pAcceptInbound) return false;
+ const configured = this.p2pConfiguredBindAddr();
+ return configured ? this.config.p2p_runtime_bind_addr !== configured : false;
+ },
+
+ p2pRestartMessage() {
+ if (!this.p2pRestartRequired()) return "";
+ if (!this.p2pAcceptInbound && this.config.p2p_inbound_runtime_active === true) {
+ return "Restart iuna to close the public P2P listener.";
+ }
+ const configured = this.p2pConfiguredBindAddr();
+ return `Restart iuna to open public P2P on ${configured || "the configured bind port"}.`;
+ },
+
async saveP2pAnnounce() {
if (!this.p2pAcceptInbound) {
this.showFlash("Enable public node before setting a public P2P address", "error");
@@ -1404,6 +1444,13 @@ window.iunaApp = function iunaApp() {
}
const addr = this.p2pAnnounceAddr.trim();
try {
+ if (this.p2pBindPortDirty) {
+ await this.submitForm("/api/settings/p2p-inbound", {
+ enabled: true,
+ bind_port: this.p2pBindPortValue(),
+ });
+ this.p2pBindPortDirty = false;
+ }
await this.postForm(
"/api/settings/p2p-announce",
{ addr },
@@ -1411,6 +1458,7 @@ window.iunaApp = function iunaApp() {
);
this.p2pAnnounceAddr = addr;
this.p2pAnnounceDirty = false;
+ await this.refreshConfig();
} catch (error) {
this.showFlash(error.message, "error");
}