summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/config/mod.rs11
-rw-r--r--src/files.rs10
-rw-r--r--src/main.rs6
-rw-r--r--src/remake/kind.rs48
-rw-r--r--src/remake/mod.rs1
5 files changed, 75 insertions, 1 deletions
diff --git a/src/config/mod.rs b/src/config/mod.rs
index c56afa6..538689e 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -127,6 +127,13 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[
"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",
@@ -274,6 +281,7 @@ pub(crate) struct Settings {
#[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>,
@@ -363,6 +371,9 @@ impl Settings {
"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");
}
diff --git a/src/files.rs b/src/files.rs
index 7bf9117..58fd043 100644
--- a/src/files.rs
+++ b/src/files.rs
@@ -18,16 +18,24 @@ pub(crate) enum FileKind {
State,
Document,
Backup,
+ /// The input of a genesis remake (`--genesis --remake`).
+ Remake,
}
impl FileKind {
- pub(crate) const ALL: [FileKind; 3] = [FileKind::State, FileKind::Document, FileKind::Backup];
+ pub(crate) const ALL: [FileKind; 4] = [
+ FileKind::State,
+ FileKind::Document,
+ FileKind::Backup,
+ FileKind::Remake,
+ ];
pub(crate) fn as_str(self) -> &'static str {
match self {
FileKind::State => "state",
FileKind::Document => "document",
FileKind::Backup => "backup",
+ FileKind::Remake => "remake",
}
}
diff --git a/src/main.rs b/src/main.rs
index 0a00f1b..8e1a07c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -141,6 +141,12 @@ async fn main() -> Result<()> {
),
None => (AutoCommit::new(), None, None, Vec::new()),
};
+ let mut initial_doc = initial_doc;
+ if let Some(spec) = &args.remake {
+ remake::build_genesis(spec, &files, &mut initial_doc)
+ .with_context(|| format!("Making the genesis document (--remake {spec})"))?;
+ info!(remake = %spec, "Made the genesis document");
+ }
let initial_actor = initial_doc.get_actor().clone();
let initial_epoch = match (starting_epoch, args.debug_epoch) {
(Some(epoch), _) => epoch,
diff --git a/src/remake/kind.rs b/src/remake/kind.rs
index ae2025d..2b24422 100644
--- a/src/remake/kind.rs
+++ b/src/remake/kind.rs
@@ -17,6 +17,9 @@
//! Protocol data under `_continuum` is not a kind's business: the generic part carries
//! it forward and replays it unchanged.
+use std::path::Path;
+
+use anyhow::{Context, anyhow, bail};
use automerge::AutoCommit;
use base64::engine::{Engine, general_purpose::STANDARD as BASE64};
use serde::{Deserialize, Serialize};
@@ -24,6 +27,7 @@ use serde_json::Value as JsonValue;
use super::snapshot::{META_ROOT, canonical};
use crate::document::store::{self, StoreError};
+use crate::files::{FileKind, Files};
/// Name of the kind used when a request doesn't name one.
pub(crate) const DEFAULT_KIND: &str = "compact";
@@ -91,6 +95,50 @@ pub(crate) fn by_name(name: &str) -> Option<&'static dyn RemakeKind> {
}
}
+/// Builds a new swarm's genesis content into `target` (`--genesis --remake SPEC`), the
+/// way a remake of that kind builds a new epoch from an empty one:
+/// * `import:PATH`: an Automerge document, e.g. a backup (its content, without history
+/// and protocol data);
+/// * `replace:PATH`: a JSON document, the store root;
+/// * `compact`: nothing (an empty document compacts to itself).
+pub(crate) fn build_genesis(
+ spec: &str,
+ files: &Files,
+ target: &mut AutoCommit,
+) -> anyhow::Result<()> {
+ let (name, path) = match spec.split_once(':') {
+ Some((name, path)) => (name, Some(Path::new(path))),
+ None => (spec, None),
+ };
+ let kind = by_name(name).ok_or_else(|| anyhow!("unknown remake kind {name:?}"))?;
+ let request = match (kind.name(), path) {
+ ("compact", None) => return Ok(()),
+ ("compact", Some(_)) => bail!("compact takes no file"),
+ (_, None) => bail!("{name} needs a file: {name}:PATH"),
+ (name, Some(path)) => {
+ let bytes = files
+ .read(FileKind::Remake, path)
+ .with_context(|| format!("Reading {}", path.display()))?
+ .with_context(|| format!("{} doesn't exist", path.display()))?;
+ match name {
+ "import" => serde_json::json!({ "document": BASE64.encode(&bytes) }),
+ "replace" => {
+ let document: JsonValue = serde_json::from_slice(&bytes)
+ .with_context(|| format!("{} is not JSON", path.display()))?;
+ serde_json::json!({ "document": document })
+ }
+ other => bail!("{other} can't make a genesis document"),
+ }
+ }
+ };
+ let prepared = kind
+ .propose(&request)
+ .map_err(|reason| anyhow!("{name}: {reason}"))?;
+ kind.build(&AutoCommit::new(), prepared.content.as_deref(), target)?;
+ target.commit();
+ Ok(())
+}
+
/// Paths owned by the protocol rather than by any kind.
pub(crate) fn is_reserved(path: &[String]) -> bool {
path.first().is_some_and(|root| root == META_ROOT)
diff --git a/src/remake/mod.rs b/src/remake/mod.rs
index ca17956..ca11cd4 100644
--- a/src/remake/mod.rs
+++ b/src/remake/mod.rs
@@ -32,6 +32,7 @@ pub(crate) mod snapshot;
mod switch;
mod vote;
+pub(crate) use kind::build_genesis;
pub(crate) use local::RemakeState;
pub(crate) use switch::run_worker;