mirror of
https://gitlab.computer.surgery/matrix/grapevine.git
synced 2025-12-19 00:31:24 +01:00
I originally didn't plan to include anything like this, but a few people in #grapevine:computer.surgery pointed out use-cases where it might be desirable: - a server that doesn't have enough room to store two full copies of the db. Transferring a copy over the network to back up is viable. - making a reflinked copy on a CoW filesystem (we could support this in the tool, but don't currently) - a server with some other backup mechanism available like snapshotting the entire filesystem
213 lines
6.7 KiB
Rust
213 lines
6.7 KiB
Rust
//! Integration with `clap`
|
|
//!
|
|
//! CLI argument structs are defined in this module. Execution logic for each
|
|
//! command goes in a submodule.
|
|
|
|
use std::{path::PathBuf, str::FromStr};
|
|
|
|
use clap::{Parser, Subcommand};
|
|
|
|
use crate::error;
|
|
|
|
mod migrate_db;
|
|
mod serve;
|
|
|
|
/// Command line arguments
|
|
#[derive(Parser)]
|
|
#[clap(
|
|
about,
|
|
version = crate::version(),
|
|
)]
|
|
pub(crate) struct Args {
|
|
#[clap(subcommand)]
|
|
pub(crate) command: Command,
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
pub(crate) enum Command {
|
|
/// Run the server.
|
|
Serve(ServeArgs),
|
|
|
|
/// Commands for interacting with the database.
|
|
#[clap(subcommand)]
|
|
Db(DbCommand),
|
|
}
|
|
|
|
/// Wrapper for the `--config` arg.
|
|
///
|
|
/// This exists to centralize the `mut_arg` code that sets the help value based
|
|
/// on runtime information.
|
|
#[derive(clap::Args)]
|
|
#[clap(mut_arg("config", |x| {
|
|
let help = "Set the path to the configuration file";
|
|
x.help(help).long_help(format!(
|
|
"{}\n\nIf this option is specified, the provided value is used \
|
|
as-is.\n\nIf this option is not specified, then the XDG Base \
|
|
Directory Specification is followed, searching for the path `{}` \
|
|
in the configuration directories.
|
|
",
|
|
help,
|
|
crate::config::DEFAULT_PATH.display(),
|
|
))
|
|
}))]
|
|
pub(crate) struct ConfigArg {
|
|
/// Path to the configuration file
|
|
#[clap(long, short)]
|
|
pub(crate) config: Option<PathBuf>,
|
|
}
|
|
|
|
#[derive(clap::Args)]
|
|
pub(crate) struct ServeArgs {
|
|
#[clap(flatten)]
|
|
pub(crate) config: ConfigArg,
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
pub(crate) enum DbCommand {
|
|
/// Migrate database from one server implementation to another.
|
|
///
|
|
/// This command is not protected against symlink-swapping attacks. Do not
|
|
/// use it when any subdirectories or parents of the `--in`, `--out`, or
|
|
/// `--inplace` directories may be written by an untrusted user during
|
|
/// execution.
|
|
Migrate(MigrateDbArgs),
|
|
}
|
|
|
|
#[derive(clap::Args)]
|
|
pub(crate) struct MigrateDbArgs {
|
|
#[clap(flatten)]
|
|
config: ConfigArg,
|
|
|
|
/// Target server implementation to migrate database to.
|
|
///
|
|
/// If migrating to the current version of grapevine, specify the version
|
|
/// as 'grapevine'.
|
|
///
|
|
/// If migrating to a released version of conduit, specified the version
|
|
/// of conduit as `conduit-{version}` (example: `conduit-0.8.0`). If
|
|
/// migrating to an unreleased conduit build, instead specify the raw
|
|
/// database version as `conduit-db-{version}` (example: `conduit-db-13`).
|
|
/// The raw database version can be found by looking at the
|
|
/// `latest_database_version` variable in `src/database/mod.rs`.
|
|
///
|
|
/// The server implementation used for the current database will be
|
|
/// detected automatically, and does not need to be specified.
|
|
#[clap(long)]
|
|
pub(crate) to: DbMigrationTarget,
|
|
|
|
/// Path to read database from.
|
|
#[clap(long = "in", short, required_unless_present("inplace_path"))]
|
|
pub(crate) in_path: Option<PathBuf>,
|
|
|
|
/// Path to write migrated database to.
|
|
#[clap(long = "out", short, required_unless_present("inplace_path"))]
|
|
pub(crate) out_path: Option<PathBuf>,
|
|
|
|
/// Path to modify an existing database in-place, instead of copying before
|
|
/// migrating.
|
|
///
|
|
/// Note that even a successful migration may lose data, because some parts
|
|
/// of the schema present in the initial database may not exist in the
|
|
/// target version. Because of this, it's very important to have a
|
|
/// backup of the initial database when migrating. The preferred way to
|
|
/// do this is with the --in and --out flags, which ensure that the
|
|
/// original database path is left unmodified. In some situations, it
|
|
/// may be possible to take a backup some other way (transferring it
|
|
/// over the network, for example), but copying the files locally is
|
|
/// undesirable. In this case, setting the --i-have-tested-my-backups
|
|
/// flag enables the use of --inplace to modify the database without
|
|
/// copying to a new location first.
|
|
#[clap(long = "inplace", conflicts_with_all(["in_path", "out_path"]))]
|
|
pub(crate) inplace_path: Option<PathBuf>,
|
|
|
|
/// Set if you have tested your backups, to enable use of the --inplace
|
|
/// flag.
|
|
///
|
|
/// See the documentation of --inplace for more details.
|
|
#[clap(long)]
|
|
pub(crate) i_have_tested_my_backups: bool,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub(crate) enum DbMigrationTarget {
|
|
/// The latest grapevine db version
|
|
///
|
|
/// Example:
|
|
///
|
|
/// ```
|
|
/// assert_eq!("grapevine".parse(), Ok(DbMigrationTarget::Grapevine))
|
|
/// ```
|
|
Grapevine,
|
|
/// A conduit-compatible db version.
|
|
///
|
|
/// This may either be specified as a released version number or directly
|
|
/// as a database version. The raw database version must be used when
|
|
/// migrating to a conduit deployment built from an unreleased commit
|
|
/// on the `next` branch.
|
|
Conduit(ConduitDbVersion),
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub(crate) enum ConduitDbVersion {
|
|
/// A conduit release version number
|
|
///
|
|
/// Example:
|
|
///
|
|
/// ```
|
|
/// assert_eq!(
|
|
/// "conduit-0.8.0".parse(),
|
|
/// Ok(DbMigrationTarget::Conduit(ConduitDbVersion::Release("0.8.0")))
|
|
/// );
|
|
/// ```
|
|
Release(String),
|
|
/// A raw database version
|
|
///
|
|
/// This corresponds directly to a
|
|
/// [`crate::service::globals::DbVersion::Conduit`] version.
|
|
///
|
|
/// Example:
|
|
///
|
|
/// ```
|
|
/// assert_eq!(
|
|
/// "conduit-db-13".parse(),
|
|
/// Ok(DbMigrationTarget::Conduit(ConduitDbVersion::Db(13)))
|
|
/// );
|
|
/// ```
|
|
Db(u64),
|
|
}
|
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
#[error("invalid db migration target version")]
|
|
pub(crate) struct DbMigrationTargetParseError;
|
|
|
|
impl FromStr for DbMigrationTarget {
|
|
type Err = DbMigrationTargetParseError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
if s == "grapevine" {
|
|
Ok(DbMigrationTarget::Grapevine)
|
|
} else if let Some(version) = s.strip_prefix("conduit-db-") {
|
|
let version =
|
|
version.parse().map_err(|_| DbMigrationTargetParseError)?;
|
|
Ok(DbMigrationTarget::Conduit(ConduitDbVersion::Db(version)))
|
|
} else if let Some(version) = s.strip_prefix("conduit-") {
|
|
Ok(DbMigrationTarget::Conduit(ConduitDbVersion::Release(
|
|
version.to_owned(),
|
|
)))
|
|
} else {
|
|
Err(DbMigrationTargetParseError)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Args {
|
|
pub(crate) async fn run(self) -> Result<(), error::Main> {
|
|
match self.command {
|
|
Command::Serve(args) => serve::run(args).await?,
|
|
Command::Db(DbCommand::Migrate(args)) => {
|
|
migrate_db::run(args).await?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|