pub mod docker {
use std::path::{Path, PathBuf};
pub const COMPOSE_FILE_NAME: &str = "docker-compose.yml";
pub const DOCKER_DIR_NAME: &str = "docker";
pub const ENV_FILE_NAME: &str = ".env";
pub const IMAGES_DIR_NAME: &str = "images";
pub const DATA_DIR_NAME: &str = "data";
pub const APP_DIR_NAME: &str = "app";
pub const CONFIG_DIR_NAME: &str = "config";
pub const UPLOAD_DIR_NAME: &str = "upload";
pub const BACKUPS_DIR_NAME: &str = "backups";
pub const LOGS_DIR_NAME: &str = "logs";
pub mod data_dirs {
pub const MYSQL_DATA_DIR: &str = "data/mysql";
pub const REDIS_DATA_DIR: &str = "data/redis";
pub const MILVUS_DATA_DIR: &str = "data/milvus";
pub const MILVUS_DATA_STORAGE_DIR: &str = "data/milvus/data";
pub const MILVUS_ETCD_DATA_DIR: &str = "data/milvus/etcd";
}
pub mod log_dirs {
pub const AGENT_LOG_DIR: &str = "logs/agent";
pub const MYSQL_LOG_DIR: &str = "logs/mysql";
pub const REDIS_LOG_DIR: &str = "logs/redis";
pub const MILVUS_LOG_DIR: &str = "logs/milvus";
}
pub mod ports {
pub const DEFAULT_FRONTEND_PORT: u16 = 80;
pub const DEFAULT_BACKEND_PORT: u16 = 8080;
pub const DEFAULT_BACKEND_DEBUG_PORT: u16 = 5005;
pub const DEFAULT_MYSQL_PORT: u16 = 3306;
pub const DEFAULT_REDIS_PORT: u16 = 6379;
pub const DEFAULT_MILVUS_PORT: u16 = 19530;
pub const DEFAULT_MILVUS_MANAGEMENT_PORT: u16 = 9091;
pub const DEFAULT_ETCD_PORT: u16 = 2379;
pub const DEFAULT_MINIO_API_PORT: u16 = 9000;
pub const DEFAULT_MINIO_CONSOLE_PORT: u16 = 9001;
pub const DEFAULT_LOG_PLATFORM_PORT: u16 = 8097;
pub const DEFAULT_QUICKWIT_PORT: u16 = 7280;
pub const DEFAULT_QUICKWIT_ADMIN_PORT: u16 = 7281;
pub const DEFAULT_VIDEO_ANALYSIS_MASTER_PORT: u16 = 8989;
pub const DEFAULT_MCP_PROXY_PORT: u16 = 8020;
}
pub mod env_vars {
pub const FRONTEND_HOST_PORT: &str = "FRONTEND_HOST_PORT";
pub const APP_PORT: &str = "APP_PORT";
pub const APP_DEBUG_PORT: &str = "APP_DEBUG_PORT";
pub const MYSQL_PORT: &str = "MYSQL_PORT";
pub const REDIS_PORT: &str = "REDIS_PORT";
pub const MILVUS_PORT: &str = "MILVUS_PORT";
pub const LOG_PLATFORM_HOST_PORT: &str = "LOG_PLATFORM_HOST_PORT";
pub const VIDEO_ANALYSIS_MASTER_HOST_PORT: &str = "VIDEO_ANALYSIS_MASTER_HOST_PORT";
pub const MASTER_APP_PORT: &str = "MASTER_APP_PORT";
}
#[cfg(unix)]
pub const DOCKER_SOCKET_PATH: &str = "/var/run/docker.sock";
#[cfg(windows)]
pub const DOCKER_SOCKET_PATH: &str = r"\\.\pipe\docker_engine";
pub fn get_compose_file_path() -> PathBuf {
Path::new(".").join(DOCKER_DIR_NAME).join(COMPOSE_FILE_NAME)
}
pub fn get_docker_work_dir() -> PathBuf {
Path::new(".").join(DOCKER_DIR_NAME)
}
pub fn get_compose_file_path_str() -> String {
get_compose_file_path().to_string_lossy().to_string()
}
pub fn get_env_file_path() -> PathBuf {
Path::new(".").join(DOCKER_DIR_NAME).join(ENV_FILE_NAME)
}
pub fn get_env_file_path_str() -> String {
get_env_file_path().to_string_lossy().to_string()
}
pub fn get_images_dir_path() -> PathBuf {
Path::new(".").join(DOCKER_DIR_NAME).join(IMAGES_DIR_NAME)
}
pub fn get_data_dir_path() -> PathBuf {
Path::new(".").join(DOCKER_DIR_NAME).join(DATA_DIR_NAME)
}
pub fn get_app_dir_path() -> PathBuf {
Path::new(".").join(DOCKER_DIR_NAME).join(APP_DIR_NAME)
}
pub fn get_config_dir_path() -> PathBuf {
Path::new(".").join(DOCKER_DIR_NAME).join(CONFIG_DIR_NAME)
}
pub fn get_upload_dir_path() -> PathBuf {
Path::new(".").join(DOCKER_DIR_NAME).join(UPLOAD_DIR_NAME)
}
pub fn get_backups_dir_path() -> PathBuf {
Path::new(".").join(DOCKER_DIR_NAME).join(BACKUPS_DIR_NAME)
}
pub fn get_logs_dir_path() -> PathBuf {
Path::new(".").join(DOCKER_DIR_NAME).join(LOGS_DIR_NAME)
}
pub fn get_all_required_directories() -> Vec<&'static str> {
vec![
DATA_DIR_NAME,
APP_DIR_NAME,
data_dirs::MYSQL_DATA_DIR,
data_dirs::REDIS_DATA_DIR,
data_dirs::MILVUS_DATA_DIR,
data_dirs::MILVUS_DATA_STORAGE_DIR,
data_dirs::MILVUS_ETCD_DATA_DIR,
LOGS_DIR_NAME,
log_dirs::AGENT_LOG_DIR,
log_dirs::MYSQL_LOG_DIR,
log_dirs::REDIS_LOG_DIR,
log_dirs::MILVUS_LOG_DIR,
UPLOAD_DIR_NAME,
CONFIG_DIR_NAME,
BACKUPS_DIR_NAME,
]
}
pub const EXCLUDE_DIRS: [&str; 8] = [
"upload",
"project_workspace",
"computer-project-workspace",
"project_zips",
"project_nginx",
"project_init",
"uv_cache",
"data",
];
}
pub mod api {
use crate::environment::Environment;
use url::Url;
pub const NUWAX_API_BASE_URL_ENV: &str = "NUWAX_API_BASE_URL";
pub const NUWAX_API_DOCKER_VERSION_URL_ENV: &str = "NUWAX_API_DOCKER_VERSION_URL";
const PRODUCTION_BASE_URL: &str = "https://api-version.nuwax.com";
const TESTING_BASE_URL: &str = "http://192.168.32.226:3000";
fn is_valid_url(url: &str) -> bool {
Url::parse(url).map_or(false, |parsed_url| {
matches!(parsed_url.scheme(), "http" | "https")
})
}
pub fn get_base_url() -> String {
if let Ok(custom_url) = std::env::var(NUWAX_API_BASE_URL_ENV) {
if is_valid_url(&custom_url) {
tracing::info!("Using custom API server: {}", custom_url);
return custom_url;
} else {
tracing::warn!(
"Invalid NUWAX_API_BASE_URL: '{}'. Expected to start with http:// or https://. Falling back to environment mode.",
custom_url
);
}
}
match Environment::from_env() {
Environment::Test => TESTING_BASE_URL.to_string(),
Environment::Production => PRODUCTION_BASE_URL.to_string(),
}
}
pub fn get_base_url_dynamic() -> String {
get_base_url()
}
pub const fn get_production_base_url() -> &'static str {
PRODUCTION_BASE_URL
}
pub const fn get_testing_base_url() -> &'static str {
TESTING_BASE_URL
}
pub const VERSION_PREFIX: &str = "/api/v1";
pub mod endpoints {
pub const CLIENT_REGISTER: &str = "/api/v1/clients/register";
pub const ANNOUNCEMENTS: &str = "/api/v1/clients/announcements";
pub const DOCKER_CHECK_VERSION: &str = "/api/v1/docker/checkVersion";
pub const DOCKER_UPDATE_VERSION_LIST: &str = "/api/v1/docker/updateVersionList";
pub const DOCKER_UPGRADE_VERSION_LATEST: &str = "/api/v1/docker/upgrade/versions/latest.json";
pub const DOCKER_VERSION_OSS_PROD: &str = "https://nuwa-packages.oss-rg-china-mainland.aliyuncs.com/docker-version/prod/latest.json";
pub const DOCKER_VERSION_OSS_BETA: &str = "https://nuwa-packages.oss-rg-china-mainland.aliyuncs.com/docker-version/beta/latest.json";
pub const DOCKER_DOWNLOAD_FULL: &str =
"/api/v1/clients/downloads/docker/services/full/latest";
pub const CLIENT_SELF_UPGRADE_HISTORY: &str = "/api/v1/clients/self-upgrade-history";
pub const SERVICE_UPGRADE_HISTORY: &str =
"/api/v1/clients/services/{service_name}/upgrade-history";
pub const TELEMETRY: &str = "/api/v1/clients/telemetry";
pub const OPENAPI_DOCS: &str = "/api-docs/openapi.json";
}
pub mod http {
pub const DEFAULT_TIMEOUT: u64 = 30;
pub const DEFAULT_RETRY_COUNT: u8 = 3;
pub const USER_AGENT: &str = "nuwax-cli/1.0";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_base_url_production_default() {
unsafe {
std::env::remove_var(NUWAX_API_BASE_URL_ENV);
std::env::remove_var("NUWAX_CLI_ENV");
}
assert_eq!(get_base_url(), PRODUCTION_BASE_URL);
}
#[test]
fn test_get_base_url_testing_env() {
unsafe {
std::env::remove_var(NUWAX_API_BASE_URL_ENV);
std::env::set_var("NUWAX_CLI_ENV", "testing");
}
assert_eq!(get_base_url(), TESTING_BASE_URL);
unsafe {
std::env::remove_var("NUWAX_CLI_ENV");
}
}
#[test]
fn test_custom_url_overrides_env() {
unsafe {
std::env::set_var(NUWAX_API_BASE_URL_ENV, "http://custom.example.com:8080");
std::env::set_var("NUWAX_CLI_ENV", "testing");
}
assert_eq!(get_base_url(), "http://custom.example.com:8080");
unsafe {
std::env::remove_var(NUWAX_API_BASE_URL_ENV);
std::env::remove_var("NUWAX_CLI_ENV");
}
}
#[test]
fn test_invalid_custom_url_falls_back() {
unsafe {
std::env::set_var(NUWAX_API_BASE_URL_ENV, "ftp://invalid.com");
std::env::set_var("NUWAX_CLI_ENV", "testing");
}
assert_eq!(get_base_url(), TESTING_BASE_URL);
unsafe {
std::env::remove_var(NUWAX_API_BASE_URL_ENV);
std::env::remove_var("NUWAX_CLI_ENV");
}
}
#[test]
fn test_is_valid_url() {
assert!(is_valid_url("http://example.com"));
assert!(is_valid_url("https://example.com"));
assert!(is_valid_url("http://localhost:8080"));
assert!(is_valid_url("https://192.168.1.1:3000"));
assert!(!is_valid_url("ftp://example.com"));
assert!(!is_valid_url("example.com"));
assert!(!is_valid_url(""));
}
#[test]
fn test_empty_custom_url_falls_back() {
unsafe {
std::env::set_var(NUWAX_API_BASE_URL_ENV, "");
std::env::set_var("NUWAX_CLI_ENV", "testing");
}
assert_eq!(get_base_url(), TESTING_BASE_URL);
unsafe {
std::env::remove_var(NUWAX_API_BASE_URL_ENV);
std::env::remove_var("NUWAX_CLI_ENV");
}
}
#[test]
fn test_custom_url_with_path() {
unsafe {
std::env::set_var(NUWAX_API_BASE_URL_ENV, "http://example.com/api/v1");
}
assert_eq!(get_base_url(), "http://example.com/api/v1");
unsafe {
std::env::remove_var(NUWAX_API_BASE_URL_ENV);
}
}
}
}
pub mod backup {
use std::path::{Path, PathBuf};
pub const DATA_DIR_NAME: &str = "data";
pub const BACKUP_DIR_NAME: &str = "backups";
pub const BACKUP_PREFIX: &str = "backup_";
pub const BACKUP_EXTENSION: &str = ".zip";
pub const MIN_ZIP_FILE_SIZE: u64 = 100;
pub fn get_backup_dir() -> PathBuf {
Path::new(".").join(DATA_DIR_NAME).join(BACKUP_DIR_NAME)
}
pub fn get_default_storage_dir() -> PathBuf {
Path::new(".").join(BACKUP_DIR_NAME)
}
}
pub mod upgrade {
use std::path::{Path, PathBuf};
pub const DATA_DIR_NAME: &str = "data";
pub const DOWNLOAD_DIR_NAME: &str = "downloads";
pub const TEMP_DIR_NAME: &str = "temp";
pub const DEFAULT_UPDATE_PACKAGE: &str = "update.zip";
pub fn get_download_dir() -> PathBuf {
Path::new(".").join(DATA_DIR_NAME).join(DOWNLOAD_DIR_NAME)
}
pub fn get_temp_extract_dir() -> PathBuf {
Path::new(".").join(DATA_DIR_NAME).join(TEMP_DIR_NAME)
}
}
pub mod file_format {
pub const ZIP_EXTENSION: &str = ".zip";
pub const TOML_EXTENSION: &str = ".toml";
pub const DB_EXTENSION: &str = ".db";
pub const ZIP_MAGIC_LOCAL_HEADER: [u8; 4] = [0x50, 0x4B, 0x03, 0x04];
pub const ZIP_MAGIC_CENTRAL_DIR_END: [u8; 4] = [0x50, 0x4B, 0x05, 0x06];
pub const ZIP_MAGIC_DATA_DESCRIPTOR: [u8; 4] = [0x50, 0x4B, 0x07, 0x08];
pub const ZIP_MAGIC_PK_PREFIX: [u8; 2] = [0x50, 0x4B];
}
pub mod timeout {
pub const SERVICE_STOP_TIMEOUT: u64 = 30;
pub const SERVICE_START_TIMEOUT: u64 = 60;
pub const DEPLOY_START_TIMEOUT: u64 = 90;
pub const SERVICE_CHECK_INTERVAL: u64 = 2;
pub const HEALTH_CHECK_TIMEOUT: u64 = 180;
pub const HEALTH_CHECK_INTERVAL: u64 = 5;
pub const RESTART_INTERVAL: u64 = 2;
pub const SERVICE_VERIFY_WAIT: u64 = 5;
}
pub mod sql {
pub const DEFAULT_RETRY_COUNT: u8 = 3;
pub const DEFAULT_MYSQL_CONTAINER_PORT: u16 = 13306;
pub const MYSQL_READY_TIMEOUT: u64 = 60;
pub const OTHER_SERVICES_TIMEOUT: u64 = 120;
pub const TEMP_SQL_DIR: &str = "temp_sql";
pub const OLD_SQL_FILE: &str = "init_mysql_old.sql";
pub const NEW_SQL_FILE: &str = "init_mysql_new.sql";
pub const DIFF_SQL_FILE: &str = "upgrade_diff.sql";
pub const CURRENT_SQL_PATH: &str = "docker/config/init_mysql.sql";
pub const CRITICAL_UPGRADE_FILES: &[&str] = &[
"config/init_mysql.sql",
];
pub const MAX_CLEANUP_ATTEMPTS: usize = 3;
}
pub mod network {
pub const LOCALHOST_IPV4: &str = "127.0.0.1";
pub const LOCALHOST_IPV6: &str = "::1";
pub const ALL_INTERFACES: &str = "0.0.0.0";
pub const PORT_MAPPING_EXAMPLES: [&str; 3] = ["8080:80", "127.0.0.1:8080:80", "8080:80/tcp"];
}
pub mod logging {
use std::path::{Path, PathBuf};
pub const DEFAULT_LOG_LEVEL: &str = "info";
pub const DATA_DIR_NAME: &str = "data";
pub const LOG_DIR_NAME: &str = "logs";
pub fn get_log_dir() -> PathBuf {
Path::new(".").join(DATA_DIR_NAME).join(LOG_DIR_NAME)
}
}
pub mod cron {
pub const DEFAULT_BACKUP_CRON: &str = "0 2 * * *";
pub const CRON_FIELDS_COUNT: usize = 5;
}
pub mod config {
use std::path::{Path, PathBuf};
pub const DATA_DIR_NAME: &str = "data";
pub const CONFIG_FILE_NAME: &str = "config.toml";
pub const DATABASE_FILE_NAME: &str = "duck_client.db";
pub const CACHE_DIR_NAME: &str = "cacheDuckData";
pub const DOWNLOAD_DIR_NAME: &str = "download";
pub fn get_config_file_path() -> PathBuf {
Path::new(".").join(DATA_DIR_NAME).join(CONFIG_FILE_NAME)
}
pub fn get_config_file_path_for_env() -> PathBuf {
let config_file_name = match crate::environment::Environment::from_env() {
crate::environment::Environment::Test => "config-test.toml",
crate::environment::Environment::Production => CONFIG_FILE_NAME,
};
Path::new(".").join(DATA_DIR_NAME).join(config_file_name)
}
pub fn get_config_file_name_for_env() -> &'static str {
match crate::environment::Environment::from_env() {
crate::environment::Environment::Test => "config-test.toml",
crate::environment::Environment::Production => CONFIG_FILE_NAME,
}
}
pub fn get_database_path() -> PathBuf {
Path::new(".").join(DATA_DIR_NAME).join(DATABASE_FILE_NAME)
}
pub fn get_database_path_for_env() -> PathBuf {
let db_file_name = match crate::environment::Environment::from_env() {
crate::environment::Environment::Test => "duck_client_test.db",
crate::environment::Environment::Production => DATABASE_FILE_NAME,
};
Path::new(".").join(DATA_DIR_NAME).join(db_file_name)
}
pub fn get_default_cache_dir() -> PathBuf {
Path::new(".").join(CACHE_DIR_NAME)
}
pub fn get_default_download_dir() -> PathBuf {
get_default_cache_dir().join(DOWNLOAD_DIR_NAME)
}
}
pub mod version {
pub mod version_info {
pub const CORE_VERSION: &str = env!("CARGO_PKG_VERSION");
pub const DEFAULT_DOCKER_SERVICE_VERSION: &str = "0.0.1";
pub const MIN_DOCKER_VERSION: &str = "20.10.0";
pub const MIN_COMPOSE_VERSION: &str = "2.0.0";
pub const API_VERSION: &str = "v1";
pub const CONFIG_FORMAT_VERSION: &str = "1.0";
pub const DATABASE_SCHEMA_VERSION: &str = "1.0";
}
}
pub mod updates {
pub const DEFAULT_CHECK_FREQUENCY: &str = "daily";
}