From 6d09b81329d33deb7e2ee614aa0c3025555c0f14 Mon Sep 17 00:00:00 2001 From: tv Date: Sun, 25 Oct 2026 20:01:00 +0100 Subject: allow to remake genesis --- src/config/mod.rs | 11 +++++++++++ src/files.rs | 10 +++++++++- src/main.rs | 6 ++++++ src/remake/kind.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ src/remake/mod.rs | 1 + 5 files changed, 75 insertions(+), 1 deletion(-) (limited to 'src') 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 @@ -126,6 +126,13 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[ 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, @@ -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, + pub(crate) remake: Option, #[serde(default)] pub(crate) force: Vec, pub(crate) swarm_key_file: Option, @@ -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; -- cgit v1.3.1