mirror of
https://gitlab.computer.surgery/matrix/grapevine.git
synced 2025-12-16 23:31:24 +01:00
add subcmd to repair some persistent state
This commit is contained in:
parent
5be1e20eb4
commit
07d05fa1f9
3 changed files with 131 additions and 0 deletions
30
src/cli.rs
30
src/cli.rs
|
|
@ -13,6 +13,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
mod check_config;
|
mod check_config;
|
||||||
|
mod repair;
|
||||||
mod serve;
|
mod serve;
|
||||||
|
|
||||||
/// Command line arguments
|
/// Command line arguments
|
||||||
|
|
@ -33,6 +34,21 @@ pub(crate) enum Command {
|
||||||
|
|
||||||
/// Check the configuration file for syntax and semantic errors.
|
/// Check the configuration file for syntax and semantic errors.
|
||||||
CheckConfig(CheckConfigArgs),
|
CheckConfig(CheckConfigArgs),
|
||||||
|
|
||||||
|
/// Repair (some) persistent state (MAKE A BACKUP FIRST!)
|
||||||
|
///
|
||||||
|
/// This subcommand is idempotent; if it exits zero, it can safely be run
|
||||||
|
/// again.
|
||||||
|
///
|
||||||
|
/// If this subcommand exits nonzero, restore from a backup. Some causes
|
||||||
|
/// of failure can be fixed by the user and then this subcommand can be
|
||||||
|
/// attempted again, others may be a bug in this subcommand.
|
||||||
|
///
|
||||||
|
/// Currently fixes the following issues:
|
||||||
|
///
|
||||||
|
/// * <https://gitlab.computer.surgery/matrix/grapevine/-/issues/27>
|
||||||
|
/// * <https://gitlab.computer.surgery/matrix/grapevine/-/issues/54>
|
||||||
|
Repair(RepairArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Args)]
|
#[derive(clap::Args)]
|
||||||
|
|
@ -88,6 +104,15 @@ pub(crate) struct ServeArgs {
|
||||||
pub(crate) config: ConfigArg,
|
pub(crate) config: ConfigArg,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(clap::Args)]
|
||||||
|
pub(crate) struct RepairArgs {
|
||||||
|
#[clap(flatten)]
|
||||||
|
observability: ObservabilityArgs,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub(crate) config: ConfigArg,
|
||||||
|
}
|
||||||
|
|
||||||
impl Args {
|
impl Args {
|
||||||
pub(crate) async fn run(self) -> Result<(), error::Main> {
|
pub(crate) async fn run(self) -> Result<(), error::Main> {
|
||||||
if let Some((format, filter)) = self.command.cli_observability_args() {
|
if let Some((format, filter)) = self.command.cli_observability_args() {
|
||||||
|
|
@ -99,6 +124,7 @@ impl Args {
|
||||||
Command::CheckConfig(args) => {
|
Command::CheckConfig(args) => {
|
||||||
check_config::run(args.config).await?;
|
check_config::run(args.config).await?;
|
||||||
}
|
}
|
||||||
|
Command::Repair(args) => repair::run(args).await?,
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -113,6 +139,10 @@ impl Command {
|
||||||
args.observability.log_format,
|
args.observability.log_format,
|
||||||
args.observability.log_filter.clone(),
|
args.observability.log_filter.clone(),
|
||||||
)),
|
)),
|
||||||
|
Command::Repair(args) => Some((
|
||||||
|
args.observability.log_format,
|
||||||
|
args.observability.log_filter.clone(),
|
||||||
|
)),
|
||||||
Command::Serve(_) => None,
|
Command::Serve(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
76
src/cli/repair.rs
Normal file
76
src/cli/repair.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
#![warn(clippy::missing_docs_in_private_items)]
|
||||||
|
|
||||||
|
//! Implementation of the `repair` subcommand
|
||||||
|
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use tracing as t;
|
||||||
|
|
||||||
|
use super::RepairArgs;
|
||||||
|
use crate::{config, database::KeyValueDatabase, error, services, Services};
|
||||||
|
|
||||||
|
/// Subcommand entrypoint
|
||||||
|
pub(crate) async fn run(args: RepairArgs) -> Result<(), error::RepairCommand> {
|
||||||
|
use error::RepairCommand as Error;
|
||||||
|
|
||||||
|
t::info!("Repairing persistent state");
|
||||||
|
|
||||||
|
let config =
|
||||||
|
config::load(args.config.config).await.map_err(Error::Config)?;
|
||||||
|
|
||||||
|
let db = Box::leak(Box::new(
|
||||||
|
KeyValueDatabase::load_or_create(&config).map_err(Error::Database)?,
|
||||||
|
));
|
||||||
|
|
||||||
|
Services::build(db, config, None)
|
||||||
|
.map_err(Error::InitializeServices)?
|
||||||
|
.install();
|
||||||
|
|
||||||
|
services().globals.err_if_server_name_changed()?;
|
||||||
|
|
||||||
|
db.apply_migrations().await.map_err(Error::Database)?;
|
||||||
|
|
||||||
|
repair_roomuserid_joined(db)
|
||||||
|
.map_err(|e| Error::Repair("roomuserid_joined", e))?;
|
||||||
|
|
||||||
|
t::info!("Done");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Repair the `roomuserid_joined` map
|
||||||
|
#[t::instrument(skip(db))]
|
||||||
|
fn repair_roomuserid_joined(
|
||||||
|
db: &KeyValueDatabase,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
t::info!(length = db.roomuserid_joined.iter().count(), "Old map length");
|
||||||
|
|
||||||
|
db.roomuserid_joined.clear()?;
|
||||||
|
|
||||||
|
for (k, _) in db.userroomid_joined.iter() {
|
||||||
|
let mut segments = k.split(|x| *x == 0xFF);
|
||||||
|
|
||||||
|
let Some(user_id_bytes) = segments.next() else {
|
||||||
|
t::warn!("No user ID bytes in key, skipping this pair");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(room_id_bytes) = segments.next() else {
|
||||||
|
t::warn!("No room ID bytes in key, skipping this pair");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let count = segments.count();
|
||||||
|
if count != 0 {
|
||||||
|
t::warn!(count, "Extra segments in key, ignoring them");
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = [room_id_bytes, user_id_bytes].join(&0xFF);
|
||||||
|
|
||||||
|
db.roomuserid_joined.insert(&key, &[])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
t::info!(length = db.roomuserid_joined.iter().count(), "New map length");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
25
src/error.rs
25
src/error.rs
|
|
@ -48,6 +48,9 @@ pub(crate) enum Main {
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
CheckConfigCommand(#[from] CheckConfigCommand),
|
CheckConfigCommand(#[from] CheckConfigCommand),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
RepairCommand(#[from] RepairCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Errors returned from the `serve` CLI subcommand.
|
/// Errors returned from the `serve` CLI subcommand.
|
||||||
|
|
@ -85,6 +88,28 @@ pub(crate) enum CheckConfigCommand {
|
||||||
Config(#[from] Config),
|
Config(#[from] Config),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Errors returned from the `repair` CLI subcommand.
|
||||||
|
// Missing docs are allowed here since that kind of information should be
|
||||||
|
// encoded in the error messages themselves anyway.
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub(crate) enum RepairCommand {
|
||||||
|
#[error("failed to load configuration")]
|
||||||
|
Config(#[from] Config),
|
||||||
|
|
||||||
|
#[error("failed to load or create the database")]
|
||||||
|
Database(#[source] crate::utils::error::Error),
|
||||||
|
|
||||||
|
#[error("failed to initialize services")]
|
||||||
|
InitializeServices(#[source] crate::utils::error::Error),
|
||||||
|
|
||||||
|
#[error("`server_name` change check failed")]
|
||||||
|
ServerNameChanged(#[from] ServerNameChanged),
|
||||||
|
|
||||||
|
#[error("repair \"{0}\" failed, restoring from a backup is recommended")]
|
||||||
|
Repair(&'static str, #[source] Box<dyn std::error::Error>),
|
||||||
|
}
|
||||||
|
|
||||||
/// Error generated if `server_name` has changed or if checking this failed
|
/// Error generated if `server_name` has changed or if checking this failed
|
||||||
// Missing docs are allowed here since that kind of information should be
|
// Missing docs are allowed here since that kind of information should be
|
||||||
// encoded in the error messages themselves anyway.
|
// encoded in the error messages themselves anyway.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue