diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index f704299..ae49d26 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -45,13 +45,35 @@ jobs:
- name: Restore frontend build cache
uses: actions/cache@v4
with:
- path: apps/nxmesh-frontend/build
+ path: apps/nxmesh-frontend/dist
key: frontend-build-${{ runner.os }}-run-${{ github.run_id }}
restore-keys: |
frontend-build-${{ runner.os }}-
+ # TODO: uncomment until artifact hanlding fixed in gitea
+ # - name: Download frontend build artifact
+ # uses: actions/download-artifact@v4
+ # with:
+ # name: frontend-dist
+ # path: apps/nxmesh-frontend/dist
+
+ # - name: Copy frontend build to expected location
+ # run: |
+ # # unlink frontend-dist
+ # rm -f apps/nxmesh-master/frontend-dist || true
+ # rm -rf apps/nxmesh-master/frontend-dist || true
+ # cp -r apps/nxmesh-frontend/dist apps/nxmesh-master/frontend-dist
+
+ # ls -la apps/nxmesh-master/frontend-dist
+
+ - name: Create dummy build artifacts
+ run: |
+ rm -f apps/nxmesh-master/frontend-dist || true
+ mkdir -p apps/nxmesh-master/frontend-dist
+ echo "
Dummy Build
" > apps/nxmesh-master/frontend-dist/index.html
+
- name: Run tests
- run: cargo test --all-features
+ run: cargo test --all-features -- --show-output
lint-crates:
runs-on: ubuntu-latest
@@ -74,11 +96,31 @@ jobs:
- name: Restore frontend build cache
uses: actions/cache@v4
with:
- path: apps/nxmesh-frontend/build
+ path: apps/nxmesh-frontend/dist
key: frontend-build-${{ runner.os }}-run-${{ github.run_id }}
restore-keys: |
frontend-build-${{ runner.os }}-
+ # TODO: uncomment until artifact hanlding fixed in gitea
+ # - name: Download frontend build artifact
+ # uses: actions/download-artifact@v4
+ # with:
+ # name: frontend-dist
+ # path: apps/nxmesh-frontend/dist
+
+ # - name: Copy frontend build to expected location
+ # run: |
+ # # unlink frontend-dist
+ # rm -f apps/nxmesh-master/frontend-dist || true
+ # rm -rf apps/nxmesh-master/frontend-dist || true
+ # cp -r apps/nxmesh-frontend/dist apps/nxmesh-master/frontend-dist
+
+ - name: Create dummy build artifacts
+ run: |
+ rm -f apps/nxmesh-master/frontend-dist || true
+ mkdir -p apps/nxmesh-master/frontend-dist
+ echo "Dummy Build
" > apps/nxmesh-master/frontend-dist/index.html
+
- name: Run clippy
run: cargo clippy --all-features
@@ -147,7 +189,12 @@ jobs:
- name: Cache frontend build
uses: actions/cache@v4
with:
- path: apps/nxmesh-frontend/build
+ path: apps/nxmesh-frontend/dist
key: frontend-build-${{ runner.os }}-run-${{ github.run_id }}
- restore-keys: |
- frontend-build-${{ runner.os }}-
+ # IGNORE restore-keys
+ # TODO: uncomment until artifact hanlding fixed in gitea
+ # - name: Upload frontend build artifact
+ # uses: actions/upload-artifact@v4
+ # with:
+ # name: frontend-dist
+ # path: apps/nxmesh-frontend/dist
diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml
index 49bd73f..b06c201 100644
--- a/.github/workflows/verify.yaml
+++ b/.github/workflows/verify.yaml
@@ -109,14 +109,14 @@ jobs:
- name: Apply migrations
if: steps.check_changes.outputs.changed == 'true'
run: |
- cargo run -p nxmesh-migration -- up
+ cd crates && sea-orm-cli migrate up
- name: Regenerate entities
if: steps.check_changes.outputs.changed == 'true'
run: |
- sea-orm-cli generate entity \
+ cd crates && sea-orm-cli generate entity \
--database-url "$DATABASE_URL" \
- --output-dir apps/nxmesh-master/src/db/entities \
+ --output-dir ../apps/nxmesh-master/src/db/entities \
--with-serde both \
--with-copy-enums \
--date-time-crate chrono
diff --git a/Cargo.lock b/Cargo.lock
index 8b33a46..56998a6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -538,6 +538,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
dependencies = [
"axum-core",
+ "axum-macros",
"base64",
"bytes",
"form_urlencoded",
@@ -586,6 +587,45 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "axum-macros"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "axum-test"
+version = "20.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a86bfe2ef15bee102ac34912f7f4542b0bb37dc464fa55461763999c4d625e7"
+dependencies = [
+ "anyhow",
+ "axum",
+ "bytes",
+ "bytesize",
+ "cookie",
+ "expect-json",
+ "http",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "mime",
+ "pretty_assertions",
+ "reserve-port",
+ "rust-multipart-rfc7578_2",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "tower",
+ "url",
+]
+
[[package]]
name = "base64"
version = "0.22.1"
@@ -727,6 +767,12 @@ version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+[[package]]
+name = "bytesize"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bd91ee7b2422bcb158d90ef4d14f75ef67f340943fc4149891dcce8f8b972a3"
+
[[package]]
name = "bzip2"
version = "0.6.1"
@@ -911,6 +957,16 @@ dependencies = [
"unicode-segmentation",
]
+[[package]]
+name = "cookie"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
+dependencies = [
+ "time",
+ "version_check",
+]
+
[[package]]
name = "core-foundation"
version = "0.10.1"
@@ -1135,6 +1191,12 @@ dependencies = [
"unicode-xid",
]
+[[package]]
+name = "diff"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
+
[[package]]
name = "digest"
version = "0.10.7"
@@ -1188,6 +1250,15 @@ dependencies = [
"serde",
]
+[[package]]
+name = "email_address"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "encoding_rs"
version = "0.8.35"
@@ -1262,6 +1333,35 @@ dependencies = [
"pin-project-lite",
]
+[[package]]
+name = "expect-json"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "869f97f4abe8e78fc812a94ad6b721d72c4fb5532877c79610f2c238d7ccf6c4"
+dependencies = [
+ "chrono",
+ "email_address",
+ "expect-json-macros",
+ "num",
+ "regex",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "typetag",
+ "uuid",
+]
+
+[[package]]
+name = "expect-json-macros"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e6fdf550180a6c29a28cb9aac262dc0064c25735641d2317f670075e9a469d9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "fastrand"
version = "2.3.0"
@@ -1975,6 +2075,15 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "inventory"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b"
+dependencies = [
+ "rustversion",
+]
+
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
@@ -2345,6 +2454,20 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "num"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
+dependencies = [
+ "num-bigint",
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
[[package]]
name = "num-bigint"
version = "0.4.6"
@@ -2421,6 +2544,17 @@ dependencies = [
"num-modular",
]
+[[package]]
+name = "num-rational"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
+dependencies = [
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -2483,6 +2617,7 @@ dependencies = [
"async-stream",
"async-trait",
"axum",
+ "axum-test",
"chrono",
"clap",
"config",
@@ -2490,12 +2625,14 @@ dependencies = [
"handlebars",
"hex",
"jsonwebtoken",
+ "mime_guess",
"mockall",
"nxmesh-core",
"nxmesh-migration",
"nxmesh-proto",
"rand 0.10.0",
"rcgen",
+ "rust-embed",
"sea-orm",
"sea-orm-migration",
"serde",
@@ -2945,6 +3082,16 @@ dependencies = [
"termtree",
]
+[[package]]
+name = "pretty_assertions"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
+dependencies = [
+ "diff",
+ "yansi",
+]
+
[[package]]
name = "prettyplease"
version = "0.2.37"
@@ -3274,6 +3421,15 @@ dependencies = [
"bytecheck",
]
+[[package]]
+name = "reserve-port"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94070964579245eb2f76e62a7668fe87bd9969ed6c41256f3bf614e3323dd3cc"
+dependencies = [
+ "thiserror",
+]
+
[[package]]
name = "ring"
version = "0.17.14"
@@ -3351,6 +3507,40 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "rust-embed"
+version = "8.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27"
+dependencies = [
+ "rust-embed-impl",
+ "rust-embed-utils",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-impl"
+version = "8.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rust-embed-utils",
+ "syn 2.0.117",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-utils"
+version = "8.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
+dependencies = [
+ "sha2",
+ "walkdir",
+]
+
[[package]]
name = "rust-ini"
version = "0.21.3"
@@ -3361,6 +3551,21 @@ dependencies = [
"ordered-multimap",
]
+[[package]]
+name = "rust-multipart-rfc7578_2"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00bdaa068902270ca7fa8619775e1838e23a63620abac0947ce0f715819b8cec"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-util",
+ "http",
+ "mime",
+ "rand 0.10.0",
+ "thiserror",
+]
+
[[package]]
name = "rust_decimal"
version = "1.40.0"
@@ -3467,6 +3672,15 @@ version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
[[package]]
name = "schannel"
version = "0.1.28"
@@ -4712,6 +4926,30 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
+[[package]]
+name = "typetag"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be2212c8a9b9bcfca32024de14998494cf9a5dfa59ea1b829de98bac374b86bf"
+dependencies = [
+ "erased-serde",
+ "inventory",
+ "once_cell",
+ "serde",
+ "typetag-impl",
+]
+
+[[package]]
+name = "typetag-impl"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27a7a9b72ba121f6f1f6c3632b85604cac41aedb5ddc70accbebb6cac83de846"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "ucd-trie"
version = "0.1.7"
@@ -4889,6 +5127,16 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
[[package]]
name = "want"
version = "0.3.1"
@@ -5059,6 +5307,15 @@ dependencies = [
"wasite",
]
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "windows-core"
version = "0.62.2"
diff --git a/apps/nxmesh-master/Cargo.toml b/apps/nxmesh-master/Cargo.toml
index a851768..4fa030e 100644
--- a/apps/nxmesh-master/Cargo.toml
+++ b/apps/nxmesh-master/Cargo.toml
@@ -84,7 +84,13 @@ time = "0.3"
# Cert handling
zip = { workspace = true }
+rust-embed = { version = "8.11.0", features = [] }
+mime_guess = "2.0.5"
+axum-test = "20.0.0"
[dev-dependencies]
tokio-test.workspace = true
mockall.workspace = true
+
+[features]
+dev-tools = ["axum/macros"]
diff --git a/apps/nxmesh-master/frontend-dist b/apps/nxmesh-master/frontend-dist
new file mode 120000
index 0000000..3d33621
--- /dev/null
+++ b/apps/nxmesh-master/frontend-dist
@@ -0,0 +1 @@
+../nxmesh-frontend/dist/
\ No newline at end of file
diff --git a/apps/nxmesh-master/src/connector/agent/ssh.rs b/apps/nxmesh-master/src/connector/agent/ssh.rs
index 5b02082..39d95e6 100644
--- a/apps/nxmesh-master/src/connector/agent/ssh.rs
+++ b/apps/nxmesh-master/src/connector/agent/ssh.rs
@@ -6,6 +6,7 @@ use nxmesh_proto::{
};
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter};
use tonic::transport::Server;
+use tracing::info;
use crate::{db::entities::public_key_revocations, service::agent::AgentServerService};
@@ -69,7 +70,14 @@ impl AgentConnectorTrait for SshAgentConnector {
.layer(ssh_interceptor)
.add_service(agent_server_service);
- router.serve(addr).await?;
+ info!("SSH Agent gRPC server is listening on {}", addr);
+ router
+ .serve(addr)
+ .await
+ .inspect(|_| info!("SSH Agent gRPC server stopped gracefully."))
+ .inspect_err(|e| {
+ tracing::error!("SSH Agent gRPC server failed: {}", e);
+ })?;
Ok(())
}
}
diff --git a/apps/nxmesh-master/src/main.rs b/apps/nxmesh-master/src/main.rs
index 5aad5d4..d4a7896 100644
--- a/apps/nxmesh-master/src/main.rs
+++ b/apps/nxmesh-master/src/main.rs
@@ -14,6 +14,7 @@ mod cli;
mod config;
mod connector;
mod db;
+mod routes;
mod service;
#[tokio::main]
diff --git a/apps/nxmesh-master/src/routes/frontend/mod.rs b/apps/nxmesh-master/src/routes/frontend/mod.rs
new file mode 100644
index 0000000..db345f6
--- /dev/null
+++ b/apps/nxmesh-master/src/routes/frontend/mod.rs
@@ -0,0 +1,160 @@
+use axum::{Router, response::IntoResponse};
+use tracing::error;
+
+// In development, build the frontend from the source directory, the soft link will handle the path resolution
+// In deployment, pre-build the frontend and replace the frontend-dist folder with the built assets, the rust-embed will handle the embedding and path resolution
+#[derive(rust_embed::Embed)]
+#[folder = "./frontend-dist/"]
+struct FrontendAssets;
+
+const INDEX_HTML: &str = "index.html";
+
+pub async fn get_router() -> Router {
+ Router::new()
+ .route(
+ "/",
+ axum::routing::get(get_fallback_handler)
+ .head(get_fallback_handler)
+ .options(get_fallback_handler),
+ )
+ .route(
+ "/{*path}",
+ axum::routing::get(get_file_handler)
+ .head(get_file_handler)
+ .options(get_file_handler),
+ )
+ //
+ .fallback(get_fallback_handler().await)
+}
+
+pub async fn get_fallback_handler() -> Result>, axum::http::StatusCode>
+{
+ let index_html = get_index_html();
+ match index_html {
+ Some(html) => Ok(axum::response::Html(html)),
+ None => Err(axum::http::StatusCode::NOT_FOUND),
+ }
+}
+
+fn get_index_html() -> Option> {
+ FrontendAssets::get(INDEX_HTML).map(|asset| asset.data.as_ref().to_owned())
+}
+
+async fn get_file_handler(
+ axum::extract::Path(path): axum::extract::Path,
+) -> Result {
+ let file_path = if path.is_empty() {
+ INDEX_HTML.to_string()
+ } else {
+ path
+ };
+
+ match FrontendAssets::get(&file_path) {
+ Some(asset) => {
+ let content_type = mime_guess::from_path(&file_path).first_or_octet_stream();
+ let response = axum::response::Response::builder()
+ .header(axum::http::header::CONTENT_TYPE, content_type.as_ref())
+ .body(asset.data.into_owned().into())
+ .map_err(|e| {
+ error!("Failed to build response for {}: {}", file_path, e);
+ axum::http::StatusCode::INTERNAL_SERVER_ERROR
+ })?;
+ Ok(response)
+ }
+ // return index.html for any file not found to support client-side routing in the frontend
+ None => get_fallback_handler()
+ .await
+ .map(|html| html.into_response()),
+ }
+}
+
+#[cfg(test)]
+#[allow(clippy::expect_used)]
+mod tests {
+ use super::*;
+
+ #[tokio::test]
+ async fn test_asset() {
+ // list all embedded assets for debugging
+ let assets = FrontendAssets::iter().collect::>();
+ println!("Embedded assets: {:?}", assets);
+ assert!(
+ !assets.is_empty(),
+ "Expected to find embedded assets, but found none"
+ );
+ }
+
+ #[tokio::test]
+ async fn test_get_index_html() {
+ let index_html = get_index_html();
+ assert!(
+ index_html.is_some(),
+ "Expected to find index.html in embedded assets"
+ );
+ }
+
+ #[tokio::test]
+ async fn test_get_file_handler_existing_file() {
+ let response = get_file_handler(axum::extract::Path("index.html".to_string())).await;
+ assert!(
+ response.is_ok(),
+ "Expected to successfully retrieve index.html"
+ );
+ let response = response.expect("Expected response to be Ok");
+ assert_eq!(response.status(), axum::http::StatusCode::OK);
+ assert!(
+ response
+ .headers()
+ .get(axum::http::header::CONTENT_TYPE)
+ .map(|ct| ct.to_str().unwrap_or(""))
+ .expect("Content-Type header should be present")
+ .starts_with("text/html")
+ );
+ }
+
+ #[tokio::test]
+ async fn test_get_file_handler_nonexistent_file() {
+ let response = get_file_handler(axum::extract::Path("nonexistent.txt".to_string())).await;
+ assert!(
+ response.is_ok(),
+ "Expected to fallback to index.html for nonexistent file"
+ );
+ let response = response.expect("Expected response to be Ok");
+ assert_eq!(response.status(), axum::http::StatusCode::OK);
+ assert!(
+ response
+ .headers()
+ .get(axum::http::header::CONTENT_TYPE)
+ .map(|ct| ct.to_str().unwrap_or(""))
+ .expect("Content-Type header should be present")
+ .starts_with("text/html")
+ )
+ }
+}
+
+#[cfg(test)]
+mod axum_tests {
+ use super::*;
+ use axum_test::TestServer;
+
+ #[tokio::test]
+ async fn test_should_return_index_html_for_root_path() {
+ let router = get_router().await;
+ let server = TestServer::new(router);
+ let response = server.get("/").await;
+ assert_eq!(response.status_code(), 200);
+ }
+
+ #[tokio::test]
+ async fn test_should_return_index_html_for_nonexistent_path() {
+ let router = get_router().await;
+ let server = TestServer::new(router);
+ let fallback_response = server.get("/nonexistent").await;
+ assert_eq!(fallback_response.status_code(), 200);
+
+ let index_response = server.get("/").await;
+ assert_eq!(index_response.status_code(), 200);
+
+ assert_eq!(fallback_response.text(), index_response.text());
+ }
+}
diff --git a/apps/nxmesh-master/src/routes/mod.rs b/apps/nxmesh-master/src/routes/mod.rs
new file mode 100644
index 0000000..0583ef2
--- /dev/null
+++ b/apps/nxmesh-master/src/routes/mod.rs
@@ -0,0 +1,36 @@
+use axum::Router;
+
+mod frontend;
+
+pub async fn get_root_router() -> Router {
+ Router::new()
+ .merge(frontend::get_router().await)
+ .fallback(frontend::get_fallback_handler().await)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use axum_test::TestServer;
+
+ #[tokio::test]
+ async fn test_should_return_index_html_for_root_path() {
+ let router = get_root_router().await;
+ let server = TestServer::new(router);
+ let response = server.get("/").await;
+ assert_eq!(response.status_code(), 200);
+ }
+
+ #[tokio::test]
+ async fn test_should_return_index_html_for_nonexistent_path() {
+ let router = get_root_router().await;
+ let server = TestServer::new(router);
+ let fallback_response = server.get("/nonexistent").await;
+ assert_eq!(fallback_response.status_code(), 200);
+
+ let index_response = server.get("/").await;
+ assert_eq!(index_response.status_code(), 200);
+
+ assert_eq!(fallback_response.text(), index_response.text());
+ }
+}
diff --git a/apps/nxmesh-master/src/service/mod.rs b/apps/nxmesh-master/src/service/mod.rs
index a3ade80..1599a17 100644
--- a/apps/nxmesh-master/src/service/mod.rs
+++ b/apps/nxmesh-master/src/service/mod.rs
@@ -1,4 +1,6 @@
-use std::sync::Arc;
+use std::{net::ToSocketAddrs, sync::Arc};
+
+use tracing::info;
use crate::{connector::agent::AgentConnectorTrait, service::certificate::CertificateService};
@@ -26,15 +28,38 @@ pub async fn start_master_server(
println!("Certificate generated and stored successfully.");
}
- // Initialize agent connector
- let mut agent_connector = crate::connector::agent::AgentConnector::new(Box::new(
- crate::connector::agent::ssh::SshAgentConnector::new(settings.clone())?,
- ));
+ let ssh_connector = crate::connector::agent::ssh::SshAgentConnector::new(settings.clone())?;
+ let cert_service_for_agent = cert_service.clone();
+ let settings_for_agent = settings.clone();
+ let connection_for_agent = db_connection.clone();
- // Start the agent server
- agent_connector
- .start_server(&settings, cert_service, db_connection)
- .await?;
+ tokio::spawn(async move {
+ let mut connector = ssh_connector;
+ tracing::info!("Starting agent server...");
+ if let Err(e) = connector
+ .start_server(
+ &settings_for_agent,
+ cert_service_for_agent,
+ connection_for_agent,
+ )
+ .await
+ {
+ tracing::error!("Agent server failed: {}", e);
+ } else {
+ tracing::info!("Agent server stopped.");
+ }
+ });
+
+ let axum_router = crate::routes::get_root_router().await;
+
+ // Start the HTTP server
+ let addr = format!("{}:{}", settings.server.bind_address, settings.server.port)
+ .to_socket_addrs()?
+ .next()
+ .ok_or("Invalid bind address")?;
+ let listener = tokio::net::TcpListener::bind(addr).await?;
+ info!("Web/API server is listening on {}", addr);
+ axum::serve(listener, axum_router).await?;
Ok(())
}