add conduit compat mode

This makes it possible to deploy Grapevine while using a database
originally created by Conduit, including leaving the admin bot user's
localpart the same as before.
This commit is contained in:
Charles Hall 2024-04-30 17:20:36 -07:00
parent 33e7a46b53
commit a25f2ec950
No known key found for this signature in database
GPG key ID: 7B8E0645816E07CF
6 changed files with 104 additions and 22 deletions

View file

@ -45,6 +45,13 @@ in
''; '';
default = "::1"; default = "::1";
}; };
global.conduit_compat = lib.mkOption {
type = types.bool;
description = ''
Whether to operate as a drop-in replacement for Conduit.
'';
default = false;
};
global.database_path = lib.mkOption { global.database_path = lib.mkOption {
type = types.nonEmptyStr; type = types.nonEmptyStr;
readOnly = true; readOnly = true;
@ -54,7 +61,9 @@ in
Note that this is read-only because this module makes use of Note that this is read-only because this module makes use of
systemd's `StateDirectory` option. systemd's `StateDirectory` option.
''; '';
default = "/var/lib/grapevine"; default = if cfg.settings.global.conduit_compat
then "/var/lib/matrix-conduit"
else "/var/lib/grapevine";
}; };
global.port = lib.mkOption { global.port = lib.mkOption {
type = types.port; type = types.port;
@ -104,12 +113,16 @@ in
RestrictNamespaces = true; RestrictNamespaces = true;
RestrictRealtime = true; RestrictRealtime = true;
StartLimitBurst = 5; StartLimitBurst = 5;
StateDirectory = "grapevine"; StateDirectory = if cfg.settings.global.conduit_compat
then "matrix-conduit"
else "grapevine";
StateDirectoryMode = "0700"; StateDirectoryMode = "0700";
SystemCallArchitectures = "native"; SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@privileged" ]; SystemCallFilter = [ "@system-service" "~@privileged" ];
UMask = "077"; UMask = "077";
User = "grapevine"; User = if cfg.settings.global.conduit_compat
then "conduit"
else "grapevine";
}; };
}; };
}; };

View file

@ -16,6 +16,8 @@ use self::proxy::ProxyConfig;
#[allow(clippy::struct_excessive_bools)] #[allow(clippy::struct_excessive_bools)]
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub(crate) struct Config { pub(crate) struct Config {
#[serde(default = "false_fn")]
pub(crate) conduit_compat: bool,
#[serde(default = "default_address")] #[serde(default = "default_address")]
pub(crate) address: IpAddr, pub(crate) address: IpAddr,
#[serde(default = "default_port")] #[serde(default = "default_port")]

View file

@ -176,7 +176,16 @@ impl KeyValueDatabase {
fn check_db_setup(config: &Config) -> Result<()> { fn check_db_setup(config: &Config) -> Result<()> {
let path = Path::new(&config.database_path); let path = Path::new(&config.database_path);
let sqlite_exists = path.join("grapevine.db").exists(); let sqlite_exists = path
.join(format!(
"{}.db",
if config.conduit_compat {
"conduit"
} else {
"grapevine"
}
))
.exists();
let rocksdb_exists = path.join("IDENTITY").exists(); let rocksdb_exists = path.join("IDENTITY").exists();
let mut count = 0; let mut count = 0;
@ -401,9 +410,15 @@ impl KeyValueDatabase {
// Matrix resource ownership is based on the server name; changing it // Matrix resource ownership is based on the server name; changing it
// requires recreating the database from scratch. // requires recreating the database from scratch.
if services().users.count()? > 0 { if services().users.count()? > 0 {
let grapevine_user = let grapevine_user = UserId::parse_with_server_name(
UserId::parse_with_server_name("grapevine", services().globals.server_name()) if services().globals.config.conduit_compat {
.expect("@grapevine:server_name is valid"); "conduit"
} else {
"grapevine"
},
services().globals.server_name(),
)
.expect("admin bot username should be valid");
if !services().users.exists(&grapevine_user)? { if !services().users.exists(&grapevine_user)? {
error!( error!(

View file

@ -97,7 +97,14 @@ impl Engine {
impl KeyValueDatabaseEngine for Arc<Engine> { impl KeyValueDatabaseEngine for Arc<Engine> {
fn open(config: &Config) -> Result<Self> { fn open(config: &Config) -> Result<Self> {
let path = Path::new(&config.database_path).join("grapevine.db"); let path = Path::new(&config.database_path).join(format!(
"{}.db",
if config.conduit_compat {
"conduit"
} else {
"grapevine"
}
));
// calculates cache-size per permanent connection // calculates cache-size per permanent connection
// 1. convert MB to KiB // 1. convert MB to KiB

View file

@ -212,9 +212,16 @@ impl Service {
// TODO: Use futures when we have long admin commands // TODO: Use futures when we have long admin commands
//let mut futures = FuturesUnordered::new(); //let mut futures = FuturesUnordered::new();
let grapevine_user = let grapevine_user = UserId::parse(format!(
UserId::parse(format!("@grapevine:{}", services().globals.server_name())) "@{}:{}",
.expect("@grapevine:server_name is valid"); if services().globals.config.conduit_compat {
"conduit"
} else {
"grapevine"
},
services().globals.server_name()
))
.expect("admin bot username should be valid");
if let Ok(Some(grapevine_room)) = services().admin.get_admin_room() { if let Ok(Some(grapevine_room)) = services().admin.get_admin_room() {
loop { loop {
@ -568,7 +575,11 @@ impl Service {
if !services().users.exists(&user_id)? if !services().users.exists(&user_id)?
|| user_id || user_id
== UserId::parse_with_server_name( == UserId::parse_with_server_name(
"grapevine", if services().globals.config.conduit_compat {
"conduit"
} else {
"grapevine"
},
services().globals.server_name(), services().globals.server_name(),
) )
.expect("grapevine user exists") .expect("grapevine user exists")
@ -866,9 +877,15 @@ impl Service {
// Utility to turn clap's `--help` text to HTML. // Utility to turn clap's `--help` text to HTML.
fn usage_to_html(text: &str, server_name: &ServerName) -> String { fn usage_to_html(text: &str, server_name: &ServerName) -> String {
// Replace `@grapevine:servername:-subcmdname` with `@grapevine:servername: subcmdname` // Replace `@grapevine:servername:-subcmdname` with `@grapevine:servername: subcmdname`
let localpart = if services().globals.config.conduit_compat {
"conduit"
} else {
"grapevine"
};
let text = text.replace( let text = text.replace(
&format!("@grapevine:{server_name}:-"), &format!("@{localpart}:{server_name}:-"),
&format!("@grapevine:{server_name}: "), &format!("@{localpart}:{server_name}: "),
); );
// For the grapevine admin room, subcommands become main commands // For the grapevine admin room, subcommands become main commands
@ -952,9 +969,16 @@ impl Service {
let state_lock = mutex_state.lock().await; let state_lock = mutex_state.lock().await;
// Create a user for the server // Create a user for the server
let grapevine_user = let grapevine_user = UserId::parse(format!(
UserId::parse_with_server_name("grapevine", services().globals.server_name()) "@{}:{}",
.expect("@grapevine:server_name is valid"); if services().globals.config.conduit_compat {
"conduit"
} else {
"grapevine"
},
services().globals.server_name()
))
.expect("admin bot username should be valid");
services().users.create(&grapevine_user, None)?; services().users.create(&grapevine_user, None)?;
@ -1218,9 +1242,15 @@ impl Service {
let state_lock = mutex_state.lock().await; let state_lock = mutex_state.lock().await;
// Use the server user to grant the new admin's power level // Use the server user to grant the new admin's power level
let grapevine_user = let grapevine_user = UserId::parse_with_server_name(
UserId::parse_with_server_name("grapevine", services().globals.server_name()) if services().globals.config.conduit_compat {
.expect("@grapevine:server_name is valid"); "conduit"
} else {
"grapevine"
},
services().globals.server_name(),
)
.expect("admin bot username should be valid");
// Invite and join the real user // Invite and join the real user
services() services()

View file

@ -483,7 +483,15 @@ impl Service {
.search .search
.index_pdu(shortroomid, &pdu_id, &body)?; .index_pdu(shortroomid, &pdu_id, &body)?;
let server_user = format!("@grapevine:{}", services().globals.server_name()); let server_user = format!(
"@{}:{}",
if services().globals.config.conduit_compat {
"conduit"
} else {
"grapevine"
},
services().globals.server_name()
);
let to_grapevine = body.starts_with(&format!("{server_user}: ")) let to_grapevine = body.starts_with(&format!("{server_user}: "))
|| body.starts_with(&format!("{server_user} ")) || body.starts_with(&format!("{server_user} "))
@ -822,7 +830,14 @@ impl Service {
.filter(|v| v.starts_with('@')) .filter(|v| v.starts_with('@'))
.unwrap_or(sender.as_str()); .unwrap_or(sender.as_str());
let server_name = services().globals.server_name(); let server_name = services().globals.server_name();
let server_user = format!("@grapevine:{server_name}"); let server_user = format!(
"@{}:{server_name}",
if services().globals.config.conduit_compat {
"conduit"
} else {
"grapevine"
},
);
let content = serde_json::from_str::<ExtractMembership>(pdu.content.get()) let content = serde_json::from_str::<ExtractMembership>(pdu.content.get())
.map_err(|_| Error::bad_database("Invalid content in pdu."))?; .map_err(|_| Error::bad_database("Invalid content in pdu."))?;