Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{BlobStore, backend::fs::FsStore, registry::bootstrap::Bootstrap};
|
||||
use registry::schema::{prelude::ObjectType, structs};
|
||||
|
||||
#[allow(unreachable_patterns)]
|
||||
impl BlobStore {
|
||||
pub async fn build(bp: &mut Bootstrap) -> Option<Self> {
|
||||
let result = match bp.setting_infallible::<structs::BlobStore>().await {
|
||||
structs::BlobStore::Default => return Some(BlobStore::Store(bp.data_store.clone())),
|
||||
#[cfg(feature = "foundation")]
|
||||
structs::BlobStore::FoundationDb(foundation_db_store) => {
|
||||
crate::backend::foundationdb::FdbStore::open(foundation_db_store)
|
||||
.await
|
||||
.map(BlobStore::Store)
|
||||
}
|
||||
#[cfg(feature = "postgres")]
|
||||
structs::BlobStore::PostgreSql(postgre_sql_store) => {
|
||||
crate::backend::postgres::PostgresStore::open(postgre_sql_store)
|
||||
.await
|
||||
.map(BlobStore::Store)
|
||||
}
|
||||
#[cfg(feature = "mysql")]
|
||||
structs::BlobStore::MySql(my_sql_store) => {
|
||||
crate::backend::mysql::MysqlStore::open(my_sql_store)
|
||||
.await
|
||||
.map(BlobStore::Store)
|
||||
}
|
||||
#[cfg(feature = "s3")]
|
||||
structs::BlobStore::S3(s3_store) => crate::backend::s3::S3Store::open(s3_store).await,
|
||||
#[cfg(feature = "azure")]
|
||||
structs::BlobStore::Azure(azure_store) => {
|
||||
crate::backend::azure::AzureStore::open(azure_store).await
|
||||
}
|
||||
structs::BlobStore::FileSystem(file_system_store) => {
|
||||
FsStore::open(file_system_store).await
|
||||
}
|
||||
_ => Err("Binary was not compiled with the selected blob store backend".to_string()),
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(store) => Some(store),
|
||||
Err(err) => {
|
||||
bp.build_error(ObjectType::BlobStore.singleton(), err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
IterateParams, RegistryStore, RegistryStoreInner, Store, U16_LEN, U32_LEN, U64_LEN, ValueKey,
|
||||
write::{
|
||||
BatchBuilder, ValueClass,
|
||||
assert::AssertValue,
|
||||
key::{DeserializeBigEndian, KeySerializer},
|
||||
now,
|
||||
},
|
||||
};
|
||||
use registry::{
|
||||
schema::{enums::ClusterNodeStatus, structs::ClusterNode},
|
||||
types::datetime::UTCDateTime,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use trc::AddContext;
|
||||
use utils::snowflake::MAX_NODE_ID;
|
||||
|
||||
const STALE_NODE_TIMEOUT: u64 = 60 * 60; // 1 hour
|
||||
const DEAD_NODE_TIMEOUT: u64 = 60 * 60 * 24; // 24 hours
|
||||
const MAX_LEASE_RETRIES: u32 = 5;
|
||||
|
||||
struct NodeSlot {
|
||||
node_id: u16,
|
||||
hostname: String,
|
||||
last_renewal: u64,
|
||||
elapsed: u64,
|
||||
hash: u64,
|
||||
}
|
||||
|
||||
struct NodeClaim {
|
||||
node_id: u16,
|
||||
assert: AssertValue,
|
||||
}
|
||||
|
||||
impl RegistryStoreInner {
|
||||
pub(super) async fn acquire_node_id(&mut self) -> Result<(), String> {
|
||||
let mut retry_count = 0;
|
||||
let slots = loop {
|
||||
let now = now();
|
||||
let slots = NodeSlot::list(&self.store, now)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to iterate store: {err}"))?;
|
||||
let claim = NodeSlot::claim(&slots, &self.env_hostname)?;
|
||||
let mut batch = BatchBuilder::new();
|
||||
|
||||
batch
|
||||
.assert_value(ValueClass::NodeId(claim.node_id), claim.assert)
|
||||
.set(
|
||||
ValueClass::NodeId(claim.node_id),
|
||||
KeySerializer::new(self.env_hostname.len() + U64_LEN)
|
||||
.write(now)
|
||||
.write(&self.env_hostname)
|
||||
.finalize(),
|
||||
);
|
||||
|
||||
match self.store.write(batch.build_all()).await {
|
||||
Ok(_) => {
|
||||
self.node_id = claim.node_id;
|
||||
break slots;
|
||||
}
|
||||
Err(err) => {
|
||||
if err.is_assertion_failure() && retry_count < MAX_LEASE_RETRIES {
|
||||
retry_count += 1;
|
||||
continue;
|
||||
} else {
|
||||
return Err(format!("Failed to write node id to store: {err}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = NodeSlot::release(
|
||||
&self.store,
|
||||
slots
|
||||
.iter()
|
||||
.filter(|slot| slot.node_id != self.node_id && slot.is_dead()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
trc::error!(err.details("Failed to release expired node id leases"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl RegistryStore {
|
||||
pub fn node_id(&self) -> u16 {
|
||||
self.0.node_id
|
||||
}
|
||||
|
||||
pub fn refresh_node_id_interval(&self) -> Duration {
|
||||
Duration::from_secs(STALE_NODE_TIMEOUT / 2)
|
||||
}
|
||||
|
||||
pub async fn cluster_node_list(&self) -> trc::Result<Vec<ClusterNode>> {
|
||||
NodeSlot::list(&self.0.store, now())
|
||||
.await
|
||||
.map(|slots| slots.into_iter().map(ClusterNode::from).collect())
|
||||
}
|
||||
|
||||
pub async fn refresh_node_id_lease(&self) -> trc::Result<()> {
|
||||
let node_id = self.0.node_id;
|
||||
let assert = match NodeSlot::list(&self.0.store, now())
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.into_iter()
|
||||
.find(|slot| slot.node_id == node_id)
|
||||
{
|
||||
Some(slot) if slot.is_owned_by(&self.0.env_hostname) => AssertValue::Hash(slot.hash),
|
||||
Some(slot) => {
|
||||
return Err(trc::StoreEvent::AssertValueFailed
|
||||
.into_err()
|
||||
.details("Node id lease is held by another host")
|
||||
.ctx(trc::Key::Id, node_id)
|
||||
.ctx(trc::Key::Hostname, slot.hostname));
|
||||
}
|
||||
None => AssertValue::None,
|
||||
};
|
||||
|
||||
let mut batch = BatchBuilder::new();
|
||||
batch.assert_value(ValueClass::NodeId(node_id), assert).set(
|
||||
ValueClass::NodeId(node_id),
|
||||
KeySerializer::new(self.0.env_hostname.len() + U64_LEN)
|
||||
.write(now())
|
||||
.write(&self.0.env_hostname)
|
||||
.finalize(),
|
||||
);
|
||||
|
||||
self.0
|
||||
.store
|
||||
.write(batch.build_all())
|
||||
.await
|
||||
.caused_by(trc::location!())
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub async fn purge_dead_nodes(&self) -> trc::Result<()> {
|
||||
let node_id = self.0.node_id;
|
||||
let slots = NodeSlot::list(&self.0.store, now())
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
if !slots.iter().any(|slot| {
|
||||
slot.node_id == node_id && slot.is_owned_by(&self.0.env_hostname) && !slot.is_stale()
|
||||
}) {
|
||||
Ok(())
|
||||
} else {
|
||||
NodeSlot::release(
|
||||
&self.0.store,
|
||||
slots
|
||||
.iter()
|
||||
.filter(|slot| slot.node_id != node_id && slot.is_dead()),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeSlot {
|
||||
async fn list(store: &Store, now: u64) -> trc::Result<Vec<NodeSlot>> {
|
||||
let mut slots = Vec::new();
|
||||
|
||||
store
|
||||
.iterate(
|
||||
IterateParams::new(
|
||||
ValueKey::from(ValueClass::NodeId(0)),
|
||||
ValueKey::from(ValueClass::NodeId(u16::MAX)),
|
||||
)
|
||||
.ascending(),
|
||||
|key, value| {
|
||||
if key.len() == U16_LEN * 3 {
|
||||
let node_id = key.deserialize_be_u16(U32_LEN)?;
|
||||
|
||||
match (
|
||||
value.deserialize_be_u64(0),
|
||||
value
|
||||
.get(U64_LEN..)
|
||||
.and_then(|bytes| std::str::from_utf8(bytes).ok())
|
||||
.filter(|text| !text.is_empty()),
|
||||
) {
|
||||
(Ok(last_renewal), Some(hostname)) => {
|
||||
slots.push(NodeSlot {
|
||||
node_id,
|
||||
hostname: hostname.to_string(),
|
||||
last_renewal,
|
||||
elapsed: now.saturating_sub(last_renewal),
|
||||
hash: xxhash_rust::xxh3::xxh3_64(value),
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
trc::error!(
|
||||
trc::StoreEvent::DataCorruption
|
||||
.into_err()
|
||||
.details("Invalid node id lease")
|
||||
.ctx(trc::Key::Id, node_id)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map(|_| slots)
|
||||
}
|
||||
|
||||
fn claim(slots: &[NodeSlot], hostname: &str) -> Result<NodeClaim, String> {
|
||||
if let Some(slot) = slots
|
||||
.iter()
|
||||
.find(|slot| slot.is_owned_by(hostname) && slot.is_assignable())
|
||||
.or_else(|| {
|
||||
slots
|
||||
.iter()
|
||||
.find(|slot| slot.is_stale() && slot.is_assignable())
|
||||
})
|
||||
{
|
||||
return Ok(NodeClaim {
|
||||
node_id: slot.node_id,
|
||||
assert: AssertValue::Hash(slot.hash),
|
||||
});
|
||||
}
|
||||
|
||||
let mut leased = slots
|
||||
.iter()
|
||||
.filter(|slot| !slot.is_stale())
|
||||
.map(|slot| slot.node_id)
|
||||
.collect::<Vec<_>>();
|
||||
leased.sort_unstable();
|
||||
|
||||
let mut node_id = 0;
|
||||
for leased_id in leased {
|
||||
if leased_id > node_id {
|
||||
break;
|
||||
}
|
||||
node_id = leased_id.saturating_add(1);
|
||||
if node_id > MAX_NODE_ID {
|
||||
return Err(format!(
|
||||
"Failed to obtain a node id: all {} ids are leased by active nodes",
|
||||
MAX_NODE_ID as u32 + 1
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(NodeClaim {
|
||||
node_id,
|
||||
assert: AssertValue::None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn release<'x>(
|
||||
store: &Store,
|
||||
slots: impl Iterator<Item = &'x NodeSlot>,
|
||||
) -> trc::Result<()> {
|
||||
for slot in slots {
|
||||
let mut batch = BatchBuilder::new();
|
||||
batch
|
||||
.assert_value(
|
||||
ValueClass::NodeId(slot.node_id),
|
||||
AssertValue::Hash(slot.hash),
|
||||
)
|
||||
.clear(ValueClass::NodeId(slot.node_id));
|
||||
|
||||
if let Err(err) = store.write(batch.build_all()).await
|
||||
&& !err.is_assertion_failure()
|
||||
{
|
||||
return Err(err.caused_by(trc::location!()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_owned_by(&self, hostname: &str) -> bool {
|
||||
self.hostname == hostname
|
||||
}
|
||||
|
||||
fn is_stale(&self) -> bool {
|
||||
self.elapsed > STALE_NODE_TIMEOUT
|
||||
}
|
||||
|
||||
fn is_dead(&self) -> bool {
|
||||
self.elapsed > DEAD_NODE_TIMEOUT
|
||||
}
|
||||
|
||||
fn is_assignable(&self) -> bool {
|
||||
self.node_id <= MAX_NODE_ID
|
||||
}
|
||||
|
||||
fn status(&self) -> ClusterNodeStatus {
|
||||
if self.is_dead() {
|
||||
ClusterNodeStatus::Inactive
|
||||
} else if self.is_stale() {
|
||||
ClusterNodeStatus::Stale
|
||||
} else {
|
||||
ClusterNodeStatus::Active
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NodeSlot> for ClusterNode {
|
||||
fn from(slot: NodeSlot) -> Self {
|
||||
ClusterNode {
|
||||
status: slot.status(),
|
||||
last_renewal: UTCDateTime::from_timestamp(slot.last_renewal.cast_signed()),
|
||||
node_id: slot.node_id as u64,
|
||||
hostname: slot.hostname,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{Store, registry::bootstrap::Bootstrap};
|
||||
use registry::schema::{
|
||||
prelude::ObjectType,
|
||||
structs::{DataStore, MetricsStore, TracingStore},
|
||||
};
|
||||
|
||||
#[allow(unreachable_patterns)]
|
||||
impl Store {
|
||||
pub async fn build(config: DataStore) -> Result<Self, String> {
|
||||
#[allow(unreachable_patterns)]
|
||||
match config {
|
||||
#[cfg(feature = "rocks")]
|
||||
DataStore::RocksDb(store) => crate::backend::rocksdb::RocksDbStore::open(store).await,
|
||||
#[cfg(feature = "foundation")]
|
||||
DataStore::FoundationDb(store) => {
|
||||
crate::backend::foundationdb::FdbStore::open(store).await
|
||||
}
|
||||
#[cfg(feature = "postgres")]
|
||||
DataStore::PostgreSql(store) => {
|
||||
crate::backend::postgres::PostgresStore::open(store).await
|
||||
}
|
||||
#[cfg(feature = "mysql")]
|
||||
DataStore::MySql(store) => crate::backend::mysql::MysqlStore::open(store).await,
|
||||
#[cfg(feature = "sqlite")]
|
||||
DataStore::Sqlite(store) => crate::backend::sqlite::SqliteStore::open(store),
|
||||
_ => Err("Binary was not compiled with the selected data store backend".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn build_tracing(bp: &mut Bootstrap) -> Option<Self> {
|
||||
let result = match bp.setting_infallible::<TracingStore>().await {
|
||||
TracingStore::Disabled => Ok(None),
|
||||
TracingStore::Default => Ok(Some(bp.data_store.clone())),
|
||||
#[cfg(feature = "foundation")]
|
||||
TracingStore::FoundationDb(store) => {
|
||||
crate::backend::foundationdb::FdbStore::open(store)
|
||||
.await
|
||||
.map(Some)
|
||||
}
|
||||
#[cfg(feature = "postgres")]
|
||||
TracingStore::PostgreSql(store) => crate::backend::postgres::PostgresStore::open(store)
|
||||
.await
|
||||
.map(Some),
|
||||
#[cfg(feature = "mysql")]
|
||||
TracingStore::MySql(store) => crate::backend::mysql::MysqlStore::open(store)
|
||||
.await
|
||||
.map(Some),
|
||||
_ => Err("Binary was not compiled with the selected tracing store backend".to_string()),
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(store) => store,
|
||||
Err(err) => {
|
||||
bp.build_warning(ObjectType::TracingStore.singleton(), err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn build_metrics(bp: &mut Bootstrap) -> Option<Self> {
|
||||
let result = match bp.setting_infallible::<MetricsStore>().await {
|
||||
MetricsStore::Disabled => Ok(None),
|
||||
MetricsStore::Default => Ok(Some(bp.data_store.clone())),
|
||||
#[cfg(feature = "foundation")]
|
||||
MetricsStore::FoundationDb(store) => {
|
||||
crate::backend::foundationdb::FdbStore::open(store)
|
||||
.await
|
||||
.map(Some)
|
||||
}
|
||||
#[cfg(feature = "postgres")]
|
||||
MetricsStore::PostgreSql(store) => crate::backend::postgres::PostgresStore::open(store)
|
||||
.await
|
||||
.map(Some),
|
||||
#[cfg(feature = "mysql")]
|
||||
MetricsStore::MySql(store) => crate::backend::mysql::MysqlStore::open(store)
|
||||
.await
|
||||
.map(Some),
|
||||
_ => Err("Binary was not compiled with the selected metrics store backend".to_string()),
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(store) => store,
|
||||
Err(err) => {
|
||||
bp.build_warning(ObjectType::MetricsStore.singleton(), err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{LookupStores, registry::bootstrap::Bootstrap};
|
||||
use registry::schema::structs::{LookupStore, StoreLookup};
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
impl LookupStores {
|
||||
pub async fn build(bp: &mut Bootstrap) -> Self {
|
||||
let mut stores = LookupStores::default();
|
||||
stores.parse_stores(bp).await;
|
||||
stores.parse_static(bp).await;
|
||||
stores.parse_http(bp).await;
|
||||
stores
|
||||
}
|
||||
|
||||
#[allow(unreachable_patterns)]
|
||||
pub async fn parse_stores(&mut self, bp: &mut Bootstrap) {
|
||||
for store in bp.list_infallible::<StoreLookup>().await {
|
||||
let id = store.id;
|
||||
let store = store.object;
|
||||
|
||||
let result = match store.store {
|
||||
#[cfg(feature = "postgres")]
|
||||
LookupStore::PostgreSql(postgre_sql_store) => {
|
||||
crate::backend::postgres::PostgresStore::open(postgre_sql_store)
|
||||
.await
|
||||
.map(crate::InMemoryStore::Store)
|
||||
}
|
||||
#[cfg(feature = "mysql")]
|
||||
LookupStore::MySql(my_sql_store) => {
|
||||
crate::backend::mysql::MysqlStore::open(my_sql_store)
|
||||
.await
|
||||
.map(crate::InMemoryStore::Store)
|
||||
}
|
||||
#[cfg(feature = "sqlite")]
|
||||
LookupStore::Sqlite(sqlite_store) => {
|
||||
crate::backend::sqlite::SqliteStore::open(sqlite_store)
|
||||
.map(crate::InMemoryStore::Store)
|
||||
}
|
||||
#[cfg(feature = "redis")]
|
||||
LookupStore::Redis(redis_store) => {
|
||||
crate::backend::redis::RedisStore::open_single(redis_store).await
|
||||
}
|
||||
#[cfg(feature = "redis")]
|
||||
LookupStore::RedisCluster(redis_cluster_store) => {
|
||||
crate::backend::redis::RedisStore::open_cluster(redis_cluster_store).await
|
||||
}
|
||||
_ => Err(
|
||||
"Binary was not compiled with the selected lookup store backend".to_string(),
|
||||
),
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(lookup) => match self.stores.entry(store.namespace.as_str().into()) {
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(lookup);
|
||||
}
|
||||
Entry::Occupied(_) => {
|
||||
bp.build_error(
|
||||
id,
|
||||
format!(
|
||||
"A lookup store with the {} namespace already exists",
|
||||
store.namespace
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
bp.build_error(id, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{InMemoryStore, registry::bootstrap::Bootstrap};
|
||||
use registry::schema::{prelude::ObjectType, structs};
|
||||
|
||||
#[allow(unreachable_patterns)]
|
||||
impl InMemoryStore {
|
||||
pub async fn build(bp: &mut Bootstrap) -> Option<Self> {
|
||||
let result = match bp.setting_infallible::<structs::InMemoryStore>().await {
|
||||
structs::InMemoryStore::Default => {
|
||||
return Some(InMemoryStore::Store(bp.data_store.clone()));
|
||||
}
|
||||
#[cfg(feature = "redis")]
|
||||
structs::InMemoryStore::Redis(redis_store) => {
|
||||
crate::backend::redis::RedisStore::open_single(redis_store).await
|
||||
}
|
||||
#[cfg(feature = "redis")]
|
||||
structs::InMemoryStore::RedisCluster(redis_cluster_store) => {
|
||||
crate::backend::redis::RedisStore::open_cluster(redis_cluster_store).await
|
||||
}
|
||||
#[cfg(feature = "redis")]
|
||||
structs::InMemoryStore::RedisSentinel(redis_sentinel_store) => {
|
||||
crate::backend::redis::RedisStore::open_sentinel(redis_sentinel_store).await
|
||||
}
|
||||
_ => Err("Binary was not compiled with the selected in-memory backend".to_string()),
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(store) => Some(store),
|
||||
Err(err) => {
|
||||
bp.build_error(ObjectType::InMemoryStore.singleton(), err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
pub mod blob;
|
||||
pub mod cluster;
|
||||
pub mod data;
|
||||
pub mod lookup;
|
||||
pub mod memory;
|
||||
pub mod registry;
|
||||
pub mod search;
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
RegistryStore, RegistryStoreInner, Store, backend::ephemeral::EphemeralStore,
|
||||
registry::local::RegistryInit,
|
||||
};
|
||||
use rand::{RngExt, distr::Alphanumeric, rng};
|
||||
use std::path::PathBuf;
|
||||
|
||||
impl RegistryStore {
|
||||
pub async fn init(local: PathBuf, acquire_node_id: bool) -> Result<Self, String> {
|
||||
// Create inner store
|
||||
let mut inner = RegistryStoreInner::new(local);
|
||||
|
||||
// Build store
|
||||
inner.store = match inner.read_data_store().await {
|
||||
RegistryInit::Ok(data_store) => Store::build(data_store).await?,
|
||||
RegistryInit::Err(err) => return Err(err),
|
||||
RegistryInit::Bootstrap => {
|
||||
inner.env_recovery_mode = true;
|
||||
|
||||
if inner.env_recovery_admin.is_none() {
|
||||
let password = rng()
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(16)
|
||||
.map(char::from)
|
||||
.collect::<String>();
|
||||
eprintln!();
|
||||
eprintln!("════════════════════════════════════════════════════════════");
|
||||
eprintln!("🔑 Stalwart bootstrap mode - temporary administrator account");
|
||||
eprintln!();
|
||||
eprintln!(" username: admin");
|
||||
eprintln!(" password: {password}");
|
||||
eprintln!();
|
||||
eprintln!("Use these credentials to complete the initial setup at the");
|
||||
eprintln!("/admin web UI. Once setup is done, Stalwart will provision a");
|
||||
eprintln!("permanent administrator and this temporary account will no");
|
||||
eprintln!("longer apply.");
|
||||
eprintln!();
|
||||
eprintln!("This password is shown only once. To pin a credential");
|
||||
eprintln!("instead, set STALWART_RECOVERY_ADMIN=admin:<password> in the");
|
||||
eprintln!("env file.");
|
||||
eprintln!("════════════════════════════════════════════════════════════");
|
||||
eprintln!();
|
||||
inner.env_recovery_admin = Some(("admin".to_string(), password));
|
||||
}
|
||||
|
||||
EphemeralStore::open()
|
||||
}
|
||||
};
|
||||
|
||||
Self::from_inner(inner, acquire_node_id).await
|
||||
}
|
||||
|
||||
pub fn from_inner_bootstrapped(inner: RegistryStoreInner) -> Self {
|
||||
Self(inner.into())
|
||||
}
|
||||
|
||||
pub async fn from_inner(
|
||||
mut inner: RegistryStoreInner,
|
||||
acquire_node_id: bool,
|
||||
) -> Result<Self, String> {
|
||||
// Create tables (SQL only)
|
||||
inner
|
||||
.store
|
||||
.create_tables()
|
||||
.await
|
||||
.map_err(|err| format!("Failed to create tables: {err}"))?;
|
||||
|
||||
if acquire_node_id {
|
||||
inner.acquire_node_id().await?;
|
||||
}
|
||||
|
||||
Ok(Self(inner.into()))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn recovery_admin(&self) -> Option<&(String, String)> {
|
||||
self.0.env_recovery_admin.as_ref()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn cluster_role(&self) -> Option<&str> {
|
||||
self.0.env_cluster_role.as_deref()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn cluster_push_shard(&self) -> u32 {
|
||||
self.0.env_push_shard_id
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn local_hostname(&self) -> &str {
|
||||
&self.0.env_hostname
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn public_url(&self) -> Option<&str> {
|
||||
self.0.env_public_url.as_deref()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_recovery_mode(&self) -> bool {
|
||||
self.0.env_recovery_mode
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_bootstrap_mode(&self) -> bool {
|
||||
self.0.store.is_ephemeral()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn path(&self) -> &PathBuf {
|
||||
&self.0.local_path
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn store(&self) -> &Store {
|
||||
&self.0.store
|
||||
}
|
||||
|
||||
pub fn initialize_inner(&self, store: Store) -> RegistryStoreInner {
|
||||
let mut inner = self.0.as_ref().clone();
|
||||
inner.store = store;
|
||||
inner
|
||||
}
|
||||
|
||||
#[cfg(feature = "test_mode")]
|
||||
pub fn clone_with_public_url(&self, url: String) -> Self {
|
||||
let mut inner = self.0.as_ref().clone();
|
||||
inner.env_public_url = Some(url);
|
||||
Self(inner.into())
|
||||
}
|
||||
|
||||
#[cfg(feature = "test_mode")]
|
||||
pub async fn new(
|
||||
path: &str,
|
||||
store: Store,
|
||||
hostname: String,
|
||||
push_shard_id: u32,
|
||||
cluster_role: Option<String>,
|
||||
) -> Self {
|
||||
Self::from_inner(
|
||||
RegistryStoreInner {
|
||||
local_path: PathBuf::from(path),
|
||||
store,
|
||||
node_id: 0,
|
||||
env_recovery_mode: false,
|
||||
env_recovery_admin: Some(("admin".to_string(), "popolna_zapora".to_string())),
|
||||
env_cluster_role: cluster_role,
|
||||
env_push_shard_id: push_shard_id,
|
||||
env_hostname: hostname,
|
||||
env_public_url: None,
|
||||
id_generator: utils::snowflake::SnowflakeIdGenerator::new(),
|
||||
},
|
||||
true,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
SearchStore,
|
||||
backend::{elastic::ElasticSearchStore, meili::MeiliSearchStore},
|
||||
registry::bootstrap::Bootstrap,
|
||||
};
|
||||
use registry::schema::{prelude::ObjectType, structs};
|
||||
|
||||
#[allow(unreachable_patterns)]
|
||||
impl SearchStore {
|
||||
pub async fn build(bp: &mut Bootstrap) -> Option<Self> {
|
||||
let result = match bp.setting_infallible::<structs::SearchStore>().await {
|
||||
structs::SearchStore::Default => {
|
||||
return Some(SearchStore::Store(bp.data_store.clone()));
|
||||
}
|
||||
structs::SearchStore::ElasticSearch(elastic_search_store) => {
|
||||
ElasticSearchStore::open(elastic_search_store).await
|
||||
}
|
||||
structs::SearchStore::Meilisearch(meilisearch_store) => {
|
||||
MeiliSearchStore::open(meilisearch_store).await
|
||||
}
|
||||
#[cfg(feature = "foundation")]
|
||||
structs::SearchStore::FoundationDb(foundation_db_store) => {
|
||||
crate::backend::foundationdb::FdbStore::open(foundation_db_store)
|
||||
.await
|
||||
.map(SearchStore::Store)
|
||||
}
|
||||
#[cfg(feature = "postgres")]
|
||||
structs::SearchStore::PostgreSql(postgre_sql_store) => {
|
||||
crate::backend::postgres::PostgresStore::open(postgre_sql_store)
|
||||
.await
|
||||
.map(SearchStore::Store)
|
||||
}
|
||||
#[cfg(feature = "mysql")]
|
||||
structs::SearchStore::MySql(my_sql_store) => {
|
||||
crate::backend::mysql::MysqlStore::open(my_sql_store)
|
||||
.await
|
||||
.map(SearchStore::Store)
|
||||
}
|
||||
_ => Err("Binary was not compiled with the selected search store backend".to_string()),
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(store) => Some(store),
|
||||
Err(err) => {
|
||||
bp.build_error(ObjectType::SearchStore.singleton(), err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user