diff options
Diffstat (limited to 'src/config/mod.rs')
| -rw-r--r-- | src/config/mod.rs | 100 |
1 files changed, 97 insertions, 3 deletions
diff --git a/src/config/mod.rs b/src/config/mod.rs index ef28542..538689e 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -65,6 +65,9 @@ const fn option( } } +/// What `--force` may override at start (`force`). +pub(crate) const FORCE: &[&str] = &["swarmId", "badDocumentFile", "replaceDocumentFile"]; + /// Every option. Names are the long option names and the configuration file keys. pub(crate) const OPTIONS: &[OptionSpec] = &[ option( @@ -86,7 +89,7 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[ Kind::List, "USER,[USER...]", None, - "HTTP API users (from htpasswd) who may write protocol data under /store/_continuum (remake records, members, forget records), e.g. to repair a stuck remake. Such writes act on the whole swarm and are logged. Epoch metadata is never writable.", + "HTTP API users (from htpasswd) who may write protocol data under /store/_continuum (remake records, members, forget records), e.g. to repair a stuck remake. Such writes act on the whole swarm and are logged. Epoch metadata and the swarm ID are never writable.", ), option( "debug-api", @@ -100,14 +103,70 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[ Kind::List, "STRING,[STRING...]", None, - "How to reach a node of the swarm to join: a Mycelium public key, a Mycelium IPv6 address, or a host name with an AAAA record for one. Without seeds, the node starts a new swarm (genesis).", + "How to reach a node of the swarm to join: a Mycelium public key, a Mycelium IPv6 address, or a host name with an AAAA record for one. A node keeps trying until it has joined.", + ), + option( + "genesis", + Kind::Flag, + "BOOL", + Some("false"), + "Start a new swarm, as its genesis node, instead of joining one through seeds. Every start with it starts another new swarm: drop it once the swarm exists, and give the node seeds instead. Can't be combined with seeds. With a --state-file that remembers a swarm, it starts that swarm again instead (empty; nodes of it still running merge with this one).", + ), + option( + "force", + Kind::List, + "NAME,[NAME...]", + None, + "Override checks that otherwise refuse to start: true for all, or names: swarmId (a document to start on is of another swarm than the state file remembers), badDocumentFile (an unusable document file: moved aside, start without it), replaceDocumentFile (recover-from while the document file holds a document).", + ), + option( + "recover-from", + Kind::Text, + "PATH", + None, + "Start the swarm again from a backup (as GET /admin/document saves it): this node loads it, on the swarm and epoch it names, and is ready at once; other nodes join it. Members of the swarm still running merge with it, or it rejoins them if they moved on to a newer epoch (the backup is then dropped).", + ), + option( + "remake", + Kind::Text, + "KIND[:PATH]", + None, + "With --genesis: make the new swarm's document as a remake of this kind would, instead of starting it empty: import:PATH (an Automerge document, e.g. a backup, without its history and protocol data) or replace:PATH (a JSON document, the store root); compact changes nothing.", + ), + option( + "document-file", + Kind::Text, + "PATH", + None, + "This node's document on disk. Read at start: the node resumes with it, ready at once. Kept up to date while ready: changes are appended every --document-append-secs, and the file is rewritten after a switch, a rejoin or a restart, and when the appended changes outgrew it.", + ), + option( + "document-append-secs", + Kind::Integer, + "SECONDS", + Some("5"), + "How often changes are appended to the document file.", + ), + option( + "document-rewrite-secs", + Kind::Integer, + "SECONDS", + Some("3600"), + "Shortest time between rewrites of the document file because its appended changes outgrew its full save (a new epoch or a restart rewrites it at once).", + ), + option( + "state-file", + Kind::Text, + "PATH", + None, + "File where this node remembers its swarm across restarts: the swarm ID and some members to rejoin through (not the document). Written once ready, then rarely. Without it, a restarted node knows only its configured seeds.", ), option( "swarm-key-file", Kind::Text, "PATH", None, - "Pre-shared swarm key: only nodes that know it may join (raw bytes, at least 16; e.g. `head -c 32 /dev/urandom`). Without it, anyone who knows the swarm ID can.", + "Pre-shared swarm key: only nodes that know it may join (raw bytes, at least 16; e.g. `head -c 32 /dev/urandom`). Without it, any node that can reach a member may join.", ), option( "alias", @@ -213,6 +272,18 @@ pub(crate) struct Settings { pub(crate) debug_api: bool, #[serde(default)] pub(crate) seeds: Vec<String>, + #[serde(default)] + pub(crate) genesis: bool, + pub(crate) state_file: Option<PathBuf>, + pub(crate) document_file: Option<PathBuf>, + #[serde(default = "default_document_append_secs")] + pub(crate) document_append_secs: u64, + #[serde(default = "default_document_rewrite_secs")] + pub(crate) document_rewrite_secs: u64, + pub(crate) recover_from: Option<PathBuf>, + pub(crate) remake: Option<String>, + #[serde(default)] + pub(crate) force: Vec<String>, pub(crate) swarm_key_file: Option<PathBuf>, pub(crate) alias: Option<String>, #[serde(default = "default_keepalive_secs")] @@ -236,6 +307,12 @@ pub(crate) struct Settings { fn default_bind_addr() -> SocketAddr { SocketAddr::from(([127, 0, 0, 1], 3000)) } +fn default_document_append_secs() -> u64 { + 5 +} +fn default_document_rewrite_secs() -> u64 { + 3600 +} fn default_keepalive_secs() -> u64 { 60 } @@ -286,12 +363,29 @@ impl Settings { ); } } + crate::force::Force::parse(self.force.iter().map(String::as_str), FORCE) + .map_err(|reason| anyhow!("force: {reason}"))?; + // Contradictions, not overrides: an error wherever the options come from. + if self.genesis && !self.seeds.is_empty() { + bail!( + "genesis starts a new swarm, so there are no seeds to join through; unset seeds (--unset seeds) or genesis" + ); + } + if self.remake.is_some() && !self.genesis { + bail!("remake makes a new swarm's document; it needs genesis"); + } + if self.genesis && self.recover_from.is_some() { + bail!("recover-from starts the swarm from a backup; it can't be combined with genesis"); + } if self.debug_epoch.is_some() && !self.debug_api { bail!("debug-epoch needs debug-api"); } if self.neighbours < 2 { bail!("neighbours must be at least 2"); } + if self.document_append_secs == 0 || self.document_rewrite_secs == 0 { + bail!("document-append-secs and document-rewrite-secs must be at least 1"); + } if self.keepalive_secs == 0 { bail!("keepalive-secs must be at least 1"); } |
