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(()) }