Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion common/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ impl Default for LFSSshConfig {
}
}

#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct OauthConfig {
pub github_client_id: String,
pub github_client_secret: String,
Expand All @@ -550,6 +550,26 @@ pub struct OauthConfig {
pub allowed_cors_origins: Vec<String>,
}

impl Default for OauthConfig {
fn default() -> Self {
Self {
github_client_id: String::new(),
github_client_secret: String::new(),
ui_domain: "http://localhost".to_string(),
cookie_domain: "localhost".to_string(),
campsite_api_domain: "http://api.gitmono.test:3001".to_string(),
allowed_cors_origins: vec![
"http://localhost",
"http://app.gitmega.com",
"http://app.gitmono.test",
]
.into_iter()
.map(|s| s.to_string())
.collect(),
}
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
23 changes: 6 additions & 17 deletions context/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::sync::Arc;
use jupiter::tests::test_storage;
use std::sync::Arc;

/// This is the main application context for the Mono application.
/// It holds shared state and configuration for the application.
Expand All @@ -23,19 +23,12 @@ pub struct AppContext {
impl AppContext {
/// Creates a new application context with the given configuration.
pub async fn new(config: common::config::Config) -> Self {

let config = Arc::new(config);


let storage = jupiter::storage::Storage::new(config.clone()).await;

let storage_for_vault = storage.clone();
let vault = tokio::task::spawn_blocking(move || {
vault::integration::vault_core::VaultCore::new(storage_for_vault)
})
.await
.expect("VaultCore::new panicked");

let vault = vault::integration::vault_core::VaultCore::new(storage_for_vault);

#[cfg(feature = "p2p")]
let client = gemini::p2p::client::P2PClient::new(storage.clone(), vault.clone());
Expand All @@ -49,31 +42,28 @@ impl AppContext {
#[cfg(feature = "p2p")]
client,
}


}

pub fn wrapped_context(&self) -> Arc<Self> {
Arc::new(self.clone())
}

pub async fn mock(config: common::config::Config) -> Self {

let config = Arc::new(config);

// use Existing test method
let storage = test_storage(config.base_dir.clone()).await;

let storage_for_vault = storage.clone();
let temp_dir = config.base_dir.clone().join("vault");
let key_path = temp_dir.join("core_key.json");
std::fs::create_dir_all(&temp_dir).expect("Mock: Failed to create vault dir");

let vault = tokio::task::spawn_blocking(move || {
vault::integration::vault_core::VaultCore::config(storage_for_vault, key_path)
})
.await
.expect("VaultCore::config panicked");
.await
.expect("VaultCore::config panicked");

#[cfg(feature = "p2p")]
let client = gemini::p2p::client::P2PClient::new(storage.clone(), vault.clone());
Expand All @@ -87,4 +77,3 @@ impl AppContext {
}
}
}

1 change: 1 addition & 0 deletions jupiter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ aws-sdk-s3 = { workspace = true, features = ["rt-tokio"] }
anyhow = { workspace = true }
tempfile = { workspace = true }
indexmap = { workspace = true }
url = { workspace = true }
132 changes: 122 additions & 10 deletions jupiter/src/storage/init.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,68 @@
use common::config::DbConfig;
use common::errors::MegaError;
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
use std::{path::Path, time::Duration};
use std::{
net::{SocketAddr, TcpStream},
path::Path,
time::Duration,
};
use tracing::log;
use url::Url;

use crate::migration::apply_migrations;

use crate::utils::id_generator;

/// Create a database connection.
/// When postgres is set but not available, it will fall back to sqlite automatically.
/// Create a database connection with failover logic.
///
/// This function attempts to connect to a database based on the provided configuration:
/// - If PostgreSQL is specified but unavailable, it automatically falls back to SQLite
/// - For local PostgreSQL connections, it first checks port reachability to avoid long timeouts
///
/// The failover logic works as follows:
/// 1. For PostgreSQL connections:
/// - If the host is local (localhost, 127.0.0.1, etc.), performs a quick port check (100ms timeout)
/// - If port is unreachable, immediately falls back to SQLite without waiting for a full connection timeout
/// - If port is reachable but connection fails, logs the error and falls back to SQLite
/// 2. For non-local PostgreSQL, attempts connection with normal timeouts (3 seconds)
/// - On failure, logs the error and falls back to SQLite
/// 3. For SQLite connections, connects directly without fallback
///
/// After successful connection, applies any pending database migrations.
///
/// This optimization helps avoid long waits when local PostgreSQL isn't running.
pub async fn database_connection(db_config: &DbConfig) -> DatabaseConnection {
id_generator::set_up_options().unwrap();

let conn = if db_config.db_type == "postgres" {
match postgres_connection(db_config).await {
Ok(conn) => conn,
Err(e) => {
log::error!("Failed to connect to postgres: {e}");
log::info!("Falling back to sqlite");
if should_check_port_first(&db_config.db_url) {
if !is_port_reachable(&db_config.db_url) {
log::info!("Local postgres port not reachable, falling back to sqlite");
sqlite_connection(db_config)
.await
.expect("Cannot connect to any database")
} else {
match postgres_connection(db_config).await {
Ok(conn) => conn,
Err(e) => {
log::error!("Failed to connect to postgres: {e}");
log::info!("Falling back to sqlite");
sqlite_connection(db_config)
.await
.expect("Cannot connect to any database")
}
}
}
} else {
match postgres_connection(db_config).await {
Ok(conn) => conn,
Err(e) => {
log::error!("Failed to connect to postgres: {e}");
log::info!("Falling back to sqlite");
sqlite_connection(db_config)
.await
.expect("Cannot connect to any database")
}
}
}
} else {
Expand All @@ -34,6 +75,29 @@ pub async fn database_connection(db_config: &DbConfig) -> DatabaseConnection {
conn
}

fn should_check_port_first(db_url: &str) -> bool {
if let Ok(url) = Url::parse(db_url) {
if let Some(host) = url.host_str() {
return host == "localhost"
|| host == "127.0.0.1"
|| host == "::1"
|| host == "0.0.0.0";
}
}
false
}

fn is_port_reachable(db_url: &str) -> bool {
if let Ok(url) = Url::parse(db_url) {
if let (Some(host), Some(port)) = (url.host_str(), url.port()) {
if let Ok(addr) = format!("{host}:{port}").parse::<SocketAddr>() {
return TcpStream::connect_timeout(&addr, Duration::from_millis(100)).is_ok();
}
}
}
false
}

async fn postgres_connection(db_config: &DbConfig) -> Result<DatabaseConnection, MegaError> {
let db_url = db_config.db_url.to_owned();
log::info!("Connecting to database: {db_url}");
Expand Down Expand Up @@ -61,11 +125,59 @@ fn setup_option(db_url: impl Into<String>) -> ConnectOptions {
let mut opt = ConnectOptions::new(db_url);
opt.max_connections(5)
.min_connections(1)
.acquire_timeout(Duration::from_secs(3))
.connect_timeout(Duration::from_secs(3))
.acquire_timeout(Duration::from_secs(1))

Copilot AI Jul 19, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reducing acquire_timeout from 3 seconds to 1 second may cause connection failures under high load or slow network conditions. Consider keeping the original 3-second timeout or making this configurable.

Copilot uses AI. Check for mistakes.

Copilot AI Jul 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The acquire_timeout has been reduced from 3 seconds to 1 second. This aggressive timeout might cause connection failures under normal load conditions. Consider using a timeout between 2-3 seconds for acquire operations to maintain stability.

Suggested change
.acquire_timeout(Duration::from_secs(1))
.acquire_timeout(Duration::from_secs(2))

Copilot uses AI. Check for mistakes.
.connect_timeout(Duration::from_secs(1))

Copilot AI Jul 19, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reducing connect_timeout from 3 seconds to 1 second may cause legitimate connections to fail, especially in containerized or network-constrained environments. This aggressive timeout could lead to unnecessary fallbacks to SQLite.

Suggested change
.connect_timeout(Duration::from_secs(1))
.connect_timeout(Duration::from_secs(3))

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HELL NO

Copilot AI Jul 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The connect_timeout has been reduced from 3 seconds to 1 second. While this helps with fast failure detection, it may be too aggressive for legitimate connections that take longer to establish, especially in containerized environments or under load.

Suggested change
.connect_timeout(Duration::from_secs(1))
.connect_timeout(Duration::from_secs(3))

Copilot uses AI. Check for mistakes.
.idle_timeout(Duration::from_secs(8))
.max_lifetime(Duration::from_secs(8))
.sqlx_logging(true)
.sqlx_logging_level(log::LevelFilter::Debug);
opt
}

#[cfg(test)]
pub mod test {
use super::*;

/// Creates a test database connection for unit tests.
pub fn test_local_db_address() {
assert!("postgres://mono:mono@localhost:5432/mono_test"
.parse::<Url>()
.is_ok());

// Test localhost variants - should return true
assert_eq!(
should_check_port_first("postgres://mono:mono@localhost:5432/mono_test"),
true
);
assert_eq!(
should_check_port_first("postgres://mono:mono@127.0.0.1:5432/mono_test"),
true
);
assert_eq!(
should_check_port_first("postgres://mono:mono@::1:5432/mono_test"),
true
);
assert_eq!(
should_check_port_first("postgres://mono:mono@0.0.0.0:5432/mono_test"),
true
);

// Test remote addresses - should return false
assert_eq!(
should_check_port_first("postgres://mono:mono@192.168.1.100:5432/mono_test"),
false
);
assert_eq!(
should_check_port_first("postgres://mono:mono@example.com:5432/mono_test"),
false
);
assert_eq!(
should_check_port_first("postgres://mono:mono@10.0.0.1:5432/mono_test"),
false
);

// Test invalid URLs - should return false
assert_eq!(should_check_port_first("invalid_url"), false);
assert_eq!(should_check_port_first(""), false);
}
}
9 changes: 6 additions & 3 deletions mega/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,20 @@ pub fn parse(args: Option<Vec<&str>>) -> MegaResult {
None => cli().try_get_matches().unwrap_or_else(|e| e.exit()),
};

// Get the current directory
// Load configuration from the config file or default location
let current_dir = env::current_dir()?;
// Get the path to the config file in the current directory
let base_dir = common::config::mega_base();
let config_path = current_dir.join("config.toml");
let config_path_alt = base_dir.join("etc/config.toml");

let config = if let Some(path) = matches.get_one::<PathBuf>("config").cloned() {
Config::new(path.to_str().unwrap()).unwrap()
} else if config_path.exists() {
Config::new(config_path.to_str().unwrap()).unwrap()
} else if config_path_alt.exists() {
Config::new(config_path_alt.to_str().unwrap()).unwrap()
} else {
eprintln!("can't find config.toml under {:?}, you can manually set config.toml path with --config parameter", env::current_dir().unwrap());
eprintln!("can't find config.toml under {:?} or {:?}, you can manually set config.toml path with --config parameter", env::current_dir().unwrap(), base_dir);
Config::default()
};

Expand Down
11 changes: 7 additions & 4 deletions mono/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,20 @@ pub fn parse(args: Option<Vec<&str>>) -> MegaResult {
None => cli().try_get_matches().unwrap_or_else(|e| e.exit()),
};

// Get the current directory
// Load configuration from the config file or default location
let current_dir = env::current_dir()?;
// Get the path to the config file in the current directory
let config_path = current_dir.join("../../config/config.toml");
let base_dir = common::config::mega_base();
let config_path = current_dir.join("config.toml");
let config_path_alt = base_dir.join("etc/config.toml");

let config = if let Some(path) = matches.get_one::<PathBuf>("config").cloned() {
Config::new(path.to_str().unwrap()).unwrap()
} else if config_path.exists() {
Config::new(config_path.to_str().unwrap()).unwrap()
} else if config_path_alt.exists() {
Config::new(config_path_alt.to_str().unwrap()).unwrap()
} else {
eprintln!("can't find config.toml under {:?}, you can manually set config.toml path with --config parameter", env::current_dir().unwrap());
eprintln!("can't find config.toml under {:?} or {:?}, you can manually set config.toml path with --config parameter", env::current_dir().unwrap(), base_dir);
Config::default()
};

Expand Down
2 changes: 1 addition & 1 deletion mono/src/server/https_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ pub async fn app(storage: Storage, host: String, port: u16) -> Router {
};

let config = storage.config();
let oauth_config = config.oauth.clone().unwrap();
let oauth_config = config.oauth.clone().unwrap_or_default();
let api_state = MonoApiServiceState {
storage: storage.clone(),
oauth_client: Some(oauth_client(oauth_config.clone()).unwrap()),
Expand Down
11 changes: 1 addition & 10 deletions vault/src/integration/vault_core.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

use crate::integration::jupiter_backend::JupiterBackend;
use common::errors::MegaError;
use jupiter::storage::Storage;
Expand All @@ -7,7 +6,6 @@ use std::{
sync::{Arc, RwLock},
};


use rusty_vault::{
core::Core,
logical::{Operation, Request, Response},
Expand All @@ -19,8 +17,6 @@ use tracing::log;

const CORE_KEY_FILE: &str = "core_key.json"; // where the core key is stored, like `root_token`



#[derive(Debug, Clone, Serialize, Deserialize)]
struct CoreKey {
secret_shares: Vec<Vec<u8>>,
Expand Down Expand Up @@ -57,7 +53,6 @@ impl VaultCore {
tracing::info!("{key_path:?}");
std::fs::create_dir_all(&dir).expect("Failed to create vault directory");
Self::config(ctx.clone(), key_path)

}

pub fn config(ctx: Storage, key_path: PathBuf) -> Self {
Expand All @@ -75,16 +70,13 @@ impl VaultCore {
};
let core = Arc::new(RwLock::new(core));


let key = {
let mut managed_core = core.write().unwrap();
managed_core
.config(core.clone(), None)
.expect("Failed to configure vault core");

let core_key = if !key_path.exists() {


let result = managed_core
.init(&seal_config)
.expect("Failed to initialize vault");
Expand Down Expand Up @@ -211,8 +203,7 @@ mod tests {
"Vault core should be initialized"
);
}



#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_vault_api() {
let temp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
Expand Down
Loading