summaryrefslogtreecommitdiffstats
path: root/src/config/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/config/mod.rs')
-rw-r--r--src/config/mod.rs100
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");
}