1pub mod docker {
3 use std::path::{Path, PathBuf};
4
5 pub const COMPOSE_FILE_NAME: &str = "docker-compose.yml";
7
8 pub const DOCKER_DIR_NAME: &str = "docker";
10
11 pub const ENV_FILE_NAME: &str = ".env";
13
14 pub const IMAGES_DIR_NAME: &str = "images";
16
17 pub const DATA_DIR_NAME: &str = "data";
19
20 pub const APP_DIR_NAME: &str = "app";
22
23 pub const CONFIG_DIR_NAME: &str = "config";
25
26 pub const UPLOAD_DIR_NAME: &str = "upload";
28
29 pub const BACKUPS_DIR_NAME: &str = "backups";
31
32 pub const LOGS_DIR_NAME: &str = "logs";
34
35 pub mod data_dirs {
37 pub const MYSQL_DATA_DIR: &str = "data/mysql";
39
40 pub const REDIS_DATA_DIR: &str = "data/redis";
42
43 pub const MILVUS_DATA_DIR: &str = "data/milvus";
45
46 pub const MILVUS_DATA_STORAGE_DIR: &str = "data/milvus/data";
48
49 pub const MILVUS_ETCD_DATA_DIR: &str = "data/milvus/etcd";
51 }
52
53 pub mod log_dirs {
55 pub const AGENT_LOG_DIR: &str = "logs/agent";
57
58 pub const MYSQL_LOG_DIR: &str = "logs/mysql";
60
61 pub const REDIS_LOG_DIR: &str = "logs/redis";
63
64 pub const MILVUS_LOG_DIR: &str = "logs/milvus";
66 }
67
68 pub mod ports {
70 pub const DEFAULT_FRONTEND_PORT: u16 = 80;
72
73 pub const DEFAULT_BACKEND_PORT: u16 = 8080;
75
76 pub const DEFAULT_BACKEND_DEBUG_PORT: u16 = 5005;
78
79 pub const DEFAULT_MYSQL_PORT: u16 = 3306;
81
82 pub const DEFAULT_REDIS_PORT: u16 = 6379;
84
85 pub const DEFAULT_MILVUS_PORT: u16 = 19530;
87
88 pub const DEFAULT_MILVUS_MANAGEMENT_PORT: u16 = 9091;
90
91 pub const DEFAULT_ETCD_PORT: u16 = 2379;
93
94 pub const DEFAULT_MINIO_API_PORT: u16 = 9000;
96
97 pub const DEFAULT_MINIO_CONSOLE_PORT: u16 = 9001;
99
100 pub const DEFAULT_LOG_PLATFORM_PORT: u16 = 8097;
102
103 pub const DEFAULT_QUICKWIT_PORT: u16 = 7280;
105
106 pub const DEFAULT_QUICKWIT_ADMIN_PORT: u16 = 7281;
108
109 pub const DEFAULT_VIDEO_ANALYSIS_MASTER_PORT: u16 = 8989;
111
112 pub const DEFAULT_MCP_PROXY_PORT: u16 = 8020;
114 }
115
116 pub mod env_vars {
118 pub const FRONTEND_HOST_PORT: &str = "FRONTEND_HOST_PORT";
120
121 pub const APP_PORT: &str = "APP_PORT";
123
124 pub const APP_DEBUG_PORT: &str = "APP_DEBUG_PORT";
126
127 pub const MYSQL_PORT: &str = "MYSQL_PORT";
129
130 pub const REDIS_PORT: &str = "REDIS_PORT";
132
133 pub const MILVUS_PORT: &str = "MILVUS_PORT";
135
136 pub const LOG_PLATFORM_HOST_PORT: &str = "LOG_PLATFORM_HOST_PORT";
138
139 pub const VIDEO_ANALYSIS_MASTER_HOST_PORT: &str = "VIDEO_ANALYSIS_MASTER_HOST_PORT";
141
142 pub const MASTER_APP_PORT: &str = "MASTER_APP_PORT";
144 }
145
146 #[cfg(unix)]
150 pub const DOCKER_SOCKET_PATH: &str = "/var/run/docker.sock";
151
152 #[cfg(windows)]
153 pub const DOCKER_SOCKET_PATH: &str = r"\\.\pipe\docker_engine";
154
155 pub fn get_compose_file_path() -> PathBuf {
157 Path::new(".").join(DOCKER_DIR_NAME).join(COMPOSE_FILE_NAME)
158 }
159
160 pub fn get_docker_work_dir() -> PathBuf {
162 Path::new(".").join(DOCKER_DIR_NAME)
163 }
164
165 pub fn get_compose_file_path_str() -> String {
167 get_compose_file_path().to_string_lossy().to_string()
168 }
169
170 pub fn get_env_file_path() -> PathBuf {
172 Path::new(".").join(DOCKER_DIR_NAME).join(ENV_FILE_NAME)
173 }
174
175 pub fn get_env_file_path_str() -> String {
177 get_env_file_path().to_string_lossy().to_string()
178 }
179
180 pub fn get_images_dir_path() -> PathBuf {
182 Path::new(".").join(DOCKER_DIR_NAME).join(IMAGES_DIR_NAME)
183 }
184
185 pub fn get_data_dir_path() -> PathBuf {
187 Path::new(".").join(DOCKER_DIR_NAME).join(DATA_DIR_NAME)
188 }
189
190 pub fn get_app_dir_path() -> PathBuf {
192 Path::new(".").join(DOCKER_DIR_NAME).join(APP_DIR_NAME)
193 }
194
195 pub fn get_config_dir_path() -> PathBuf {
197 Path::new(".").join(DOCKER_DIR_NAME).join(CONFIG_DIR_NAME)
198 }
199
200 pub fn get_upload_dir_path() -> PathBuf {
202 Path::new(".").join(DOCKER_DIR_NAME).join(UPLOAD_DIR_NAME)
203 }
204
205 pub fn get_backups_dir_path() -> PathBuf {
207 Path::new(".").join(DOCKER_DIR_NAME).join(BACKUPS_DIR_NAME)
208 }
209
210 pub fn get_logs_dir_path() -> PathBuf {
212 Path::new(".").join(DOCKER_DIR_NAME).join(LOGS_DIR_NAME)
213 }
214
215 pub fn get_all_required_directories() -> Vec<&'static str> {
217 vec![
218 DATA_DIR_NAME,
219 APP_DIR_NAME,
220 data_dirs::MYSQL_DATA_DIR,
221 data_dirs::REDIS_DATA_DIR,
222 data_dirs::MILVUS_DATA_DIR,
223 data_dirs::MILVUS_DATA_STORAGE_DIR,
224 data_dirs::MILVUS_ETCD_DATA_DIR,
225 LOGS_DIR_NAME,
226 log_dirs::AGENT_LOG_DIR,
227 log_dirs::MYSQL_LOG_DIR,
228 log_dirs::REDIS_LOG_DIR,
229 log_dirs::MILVUS_LOG_DIR,
230 UPLOAD_DIR_NAME,
231 CONFIG_DIR_NAME,
232 BACKUPS_DIR_NAME,
233 ]
234 }
235
236 pub const EXCLUDE_DIRS: [&str; 8] = [
247 "upload",
248 "project_workspace",
249 "computer-project-workspace",
250 "project_zips",
251 "project_nginx",
252 "project_init",
253 "uv_cache",
254 "data",
255 ];
256}
257
258pub mod api {
260 use crate::environment::Environment;
261 use url::Url;
262
263 pub const NUWAX_API_BASE_URL_ENV: &str = "NUWAX_API_BASE_URL";
265
266 pub const NUWAX_API_DOCKER_VERSION_URL_ENV: &str = "NUWAX_API_DOCKER_VERSION_URL";
268
269 const PRODUCTION_BASE_URL: &str = "https://api-version.nuwax.com";
271
272 const TESTING_BASE_URL: &str = "http://192.168.32.226:3000";
274
275 fn is_valid_url(url: &str) -> bool {
277 Url::parse(url).map_or(false, |parsed_url| {
278 matches!(parsed_url.scheme(), "http" | "https")
280 })
281 }
282
283 pub fn get_base_url() -> String {
309 if let Ok(custom_url) = std::env::var(NUWAX_API_BASE_URL_ENV) {
311 if is_valid_url(&custom_url) {
312 tracing::info!("Using custom API server: {}", custom_url);
313 return custom_url;
314 } else {
315 tracing::warn!(
316 "Invalid NUWAX_API_BASE_URL: '{}'. Expected to start with http:// or https://. Falling back to environment mode.",
317 custom_url
318 );
319 }
320 }
321
322 match Environment::from_env() {
324 Environment::Test => TESTING_BASE_URL.to_string(),
325 Environment::Production => PRODUCTION_BASE_URL.to_string(),
326 }
327 }
328
329 pub fn get_base_url_dynamic() -> String {
333 get_base_url()
334 }
335
336 pub const fn get_production_base_url() -> &'static str {
338 PRODUCTION_BASE_URL
339 }
340
341 pub const fn get_testing_base_url() -> &'static str {
343 TESTING_BASE_URL
344 }
345
346 pub const VERSION_PREFIX: &str = "/api/v1";
348
349 pub mod endpoints {
351 pub const CLIENT_REGISTER: &str = "/api/v1/clients/register";
353
354 pub const ANNOUNCEMENTS: &str = "/api/v1/clients/announcements";
356
357 pub const DOCKER_CHECK_VERSION: &str = "/api/v1/docker/checkVersion";
359
360 pub const DOCKER_UPDATE_VERSION_LIST: &str = "/api/v1/docker/updateVersionList";
362
363 pub const DOCKER_UPGRADE_VERSION_LATEST: &str = "/api/v1/docker/upgrade/versions/latest.json";
365
366 pub const DOCKER_VERSION_OSS_PROD: &str = "https://nuwa-packages.oss-rg-china-mainland.aliyuncs.com/docker-version/prod/latest.json";
368
369 pub const DOCKER_VERSION_OSS_BETA: &str = "https://nuwa-packages.oss-rg-china-mainland.aliyuncs.com/docker-version/beta/latest.json";
371
372 pub const DOCKER_DOWNLOAD_FULL: &str =
374 "/api/v1/clients/downloads/docker/services/full/latest";
375
376 pub const CLIENT_SELF_UPGRADE_HISTORY: &str = "/api/v1/clients/self-upgrade-history";
378
379 pub const SERVICE_UPGRADE_HISTORY: &str =
381 "/api/v1/clients/services/{service_name}/upgrade-history";
382
383 pub const TELEMETRY: &str = "/api/v1/clients/telemetry";
385
386 pub const OPENAPI_DOCS: &str = "/api-docs/openapi.json";
388 }
389
390 pub mod http {
392 pub const DEFAULT_TIMEOUT: u64 = 30;
394
395 pub const DEFAULT_RETRY_COUNT: u8 = 3;
397
398 pub const USER_AGENT: &str = "nuwax-cli/1.0";
400 }
401
402 #[cfg(test)]
403 mod tests {
404 use super::*;
405
406 #[test]
407 fn test_get_base_url_production_default() {
408 unsafe {
409 std::env::remove_var(NUWAX_API_BASE_URL_ENV);
410 std::env::remove_var("NUWAX_CLI_ENV");
411 }
412 assert_eq!(get_base_url(), PRODUCTION_BASE_URL);
413 }
414
415 #[test]
416 fn test_get_base_url_testing_env() {
417 unsafe {
418 std::env::remove_var(NUWAX_API_BASE_URL_ENV);
419 std::env::set_var("NUWAX_CLI_ENV", "testing");
420 }
421 assert_eq!(get_base_url(), TESTING_BASE_URL);
422 unsafe {
423 std::env::remove_var("NUWAX_CLI_ENV");
424 }
425 }
426
427 #[test]
428 fn test_custom_url_overrides_env() {
429 unsafe {
430 std::env::set_var(NUWAX_API_BASE_URL_ENV, "http://custom.example.com:8080");
431 std::env::set_var("NUWAX_CLI_ENV", "testing");
432 }
433 assert_eq!(get_base_url(), "http://custom.example.com:8080");
434 unsafe {
435 std::env::remove_var(NUWAX_API_BASE_URL_ENV);
436 std::env::remove_var("NUWAX_CLI_ENV");
437 }
438 }
439
440 #[test]
441 fn test_invalid_custom_url_falls_back() {
442 unsafe {
443 std::env::set_var(NUWAX_API_BASE_URL_ENV, "ftp://invalid.com");
444 std::env::set_var("NUWAX_CLI_ENV", "testing");
445 }
446 assert_eq!(get_base_url(), TESTING_BASE_URL);
447 unsafe {
448 std::env::remove_var(NUWAX_API_BASE_URL_ENV);
449 std::env::remove_var("NUWAX_CLI_ENV");
450 }
451 }
452
453 #[test]
454 fn test_is_valid_url() {
455 assert!(is_valid_url("http://example.com"));
456 assert!(is_valid_url("https://example.com"));
457 assert!(is_valid_url("http://localhost:8080"));
458 assert!(is_valid_url("https://192.168.1.1:3000"));
459 assert!(!is_valid_url("ftp://example.com"));
460 assert!(!is_valid_url("example.com"));
461 assert!(!is_valid_url(""));
462 }
463
464 #[test]
465 fn test_empty_custom_url_falls_back() {
466 unsafe {
467 std::env::set_var(NUWAX_API_BASE_URL_ENV, "");
468 std::env::set_var("NUWAX_CLI_ENV", "testing");
469 }
470 assert_eq!(get_base_url(), TESTING_BASE_URL);
471 unsafe {
472 std::env::remove_var(NUWAX_API_BASE_URL_ENV);
473 std::env::remove_var("NUWAX_CLI_ENV");
474 }
475 }
476
477 #[test]
478 fn test_custom_url_with_path() {
479 unsafe {
480 std::env::set_var(NUWAX_API_BASE_URL_ENV, "http://example.com/api/v1");
481 }
482 assert_eq!(get_base_url(), "http://example.com/api/v1");
483 unsafe {
484 std::env::remove_var(NUWAX_API_BASE_URL_ENV);
485 }
486 }
487 }
488}
489
490pub mod backup {
492 use std::path::{Path, PathBuf};
493
494 pub const DATA_DIR_NAME: &str = "data";
496
497 pub const BACKUP_DIR_NAME: &str = "backups";
499
500 pub const BACKUP_PREFIX: &str = "backup_";
502
503 pub const BACKUP_EXTENSION: &str = ".zip";
505
506 pub const MIN_ZIP_FILE_SIZE: u64 = 100;
508
509 pub fn get_backup_dir() -> PathBuf {
511 Path::new(".").join(DATA_DIR_NAME).join(BACKUP_DIR_NAME)
512 }
513
514 pub fn get_default_storage_dir() -> PathBuf {
516 Path::new(".").join(BACKUP_DIR_NAME)
517 }
518}
519
520pub mod upgrade {
522 use std::path::{Path, PathBuf};
523
524 pub const DATA_DIR_NAME: &str = "data";
526
527 pub const DOWNLOAD_DIR_NAME: &str = "downloads";
529
530 pub const TEMP_DIR_NAME: &str = "temp";
532
533 pub const DEFAULT_UPDATE_PACKAGE: &str = "update.zip";
535
536 pub fn get_download_dir() -> PathBuf {
538 Path::new(".").join(DATA_DIR_NAME).join(DOWNLOAD_DIR_NAME)
539 }
540
541 pub fn get_temp_extract_dir() -> PathBuf {
543 Path::new(".").join(DATA_DIR_NAME).join(TEMP_DIR_NAME)
544 }
545}
546
547pub mod file_format {
549 pub const ZIP_EXTENSION: &str = ".zip";
551
552 pub const TOML_EXTENSION: &str = ".toml";
554
555 pub const DB_EXTENSION: &str = ".db";
557
558 pub const ZIP_MAGIC_LOCAL_HEADER: [u8; 4] = [0x50, 0x4B, 0x03, 0x04];
560
561 pub const ZIP_MAGIC_CENTRAL_DIR_END: [u8; 4] = [0x50, 0x4B, 0x05, 0x06];
563
564 pub const ZIP_MAGIC_DATA_DESCRIPTOR: [u8; 4] = [0x50, 0x4B, 0x07, 0x08];
566
567 pub const ZIP_MAGIC_PK_PREFIX: [u8; 2] = [0x50, 0x4B];
569}
570
571pub mod timeout {
573 pub const SERVICE_STOP_TIMEOUT: u64 = 30;
575
576 pub const SERVICE_START_TIMEOUT: u64 = 60;
578
579 pub const DEPLOY_START_TIMEOUT: u64 = 90;
581
582 pub const SERVICE_CHECK_INTERVAL: u64 = 2;
584
585 pub const HEALTH_CHECK_TIMEOUT: u64 = 180;
587
588 pub const HEALTH_CHECK_INTERVAL: u64 = 5;
590
591 pub const RESTART_INTERVAL: u64 = 2;
593
594 pub const SERVICE_VERIFY_WAIT: u64 = 5;
596}
597
598pub mod sql {
600 pub const DEFAULT_RETRY_COUNT: u8 = 3;
602
603 pub const DEFAULT_MYSQL_CONTAINER_PORT: u16 = 13306;
605
606 pub const MYSQL_READY_TIMEOUT: u64 = 60;
608
609 pub const OTHER_SERVICES_TIMEOUT: u64 = 120;
611
612 pub const TEMP_SQL_DIR: &str = "temp_sql";
614
615 pub const OLD_SQL_FILE: &str = "init_mysql_old.sql";
617
618 pub const NEW_SQL_FILE: &str = "init_mysql_new.sql";
620
621 pub const DIFF_SQL_FILE: &str = "upgrade_diff.sql";
623
624 pub const CURRENT_SQL_PATH: &str = "docker/config/init_mysql.sql";
626
627 pub const CRITICAL_UPGRADE_FILES: &[&str] = &[
630 "config/init_mysql.sql",
631 ];
635
636 pub const MAX_CLEANUP_ATTEMPTS: usize = 3;
638}
639
640pub mod network {
642 pub const LOCALHOST_IPV4: &str = "127.0.0.1";
644
645 pub const LOCALHOST_IPV6: &str = "::1";
647
648 pub const ALL_INTERFACES: &str = "0.0.0.0";
650
651 pub const PORT_MAPPING_EXAMPLES: [&str; 3] = ["8080:80", "127.0.0.1:8080:80", "8080:80/tcp"];
653}
654
655pub mod logging {
657 use std::path::{Path, PathBuf};
658
659 pub const DEFAULT_LOG_LEVEL: &str = "info";
661
662 pub const DATA_DIR_NAME: &str = "data";
664
665 pub const LOG_DIR_NAME: &str = "logs";
667
668 pub fn get_log_dir() -> PathBuf {
670 Path::new(".").join(DATA_DIR_NAME).join(LOG_DIR_NAME)
671 }
672}
673
674pub mod cron {
676 pub const DEFAULT_BACKUP_CRON: &str = "0 2 * * *";
678
679 pub const CRON_FIELDS_COUNT: usize = 5;
681}
682
683pub mod config {
685 use std::path::{Path, PathBuf};
686
687 pub const DATA_DIR_NAME: &str = "data";
689
690 pub const CONFIG_FILE_NAME: &str = "config.toml";
692
693 pub const DATABASE_FILE_NAME: &str = "duck_client.db";
695
696 pub const CACHE_DIR_NAME: &str = "cacheDuckData";
698
699 pub const DOWNLOAD_DIR_NAME: &str = "download";
701
702 pub fn get_config_file_path() -> PathBuf {
704 Path::new(".").join(DATA_DIR_NAME).join(CONFIG_FILE_NAME)
705 }
706
707 pub fn get_config_file_path_for_env() -> PathBuf {
713 let config_file_name = match crate::environment::Environment::from_env() {
714 crate::environment::Environment::Test => "config-test.toml",
715 crate::environment::Environment::Production => CONFIG_FILE_NAME,
716 };
717 Path::new(".").join(DATA_DIR_NAME).join(config_file_name)
718 }
719
720 pub fn get_config_file_name_for_env() -> &'static str {
722 match crate::environment::Environment::from_env() {
723 crate::environment::Environment::Test => "config-test.toml",
724 crate::environment::Environment::Production => CONFIG_FILE_NAME,
725 }
726 }
727
728 pub fn get_database_path() -> PathBuf {
730 Path::new(".").join(DATA_DIR_NAME).join(DATABASE_FILE_NAME)
731 }
732
733 pub fn get_database_path_for_env() -> PathBuf {
739 let db_file_name = match crate::environment::Environment::from_env() {
740 crate::environment::Environment::Test => "duck_client_test.db",
741 crate::environment::Environment::Production => DATABASE_FILE_NAME,
742 };
743 Path::new(".").join(DATA_DIR_NAME).join(db_file_name)
744 }
745
746 pub fn get_default_cache_dir() -> PathBuf {
748 Path::new(".").join(CACHE_DIR_NAME)
749 }
750
751 pub fn get_default_download_dir() -> PathBuf {
753 get_default_cache_dir().join(DOWNLOAD_DIR_NAME)
754 }
755}
756
757pub mod version {
759 pub mod version_info {
761 pub const CORE_VERSION: &str = env!("CARGO_PKG_VERSION");
763
764 pub const DEFAULT_DOCKER_SERVICE_VERSION: &str = "0.0.1";
766
767 pub const MIN_DOCKER_VERSION: &str = "20.10.0";
769
770 pub const MIN_COMPOSE_VERSION: &str = "2.0.0";
772
773 pub const API_VERSION: &str = "v1";
775
776 pub const CONFIG_FORMAT_VERSION: &str = "1.0";
778
779 pub const DATABASE_SCHEMA_VERSION: &str = "1.0";
781 }
782}
783
784pub mod updates {
786 pub const DEFAULT_CHECK_FREQUENCY: &str = "daily";
788}