From de8c89eb03e21faac23a606a63d2954f8b6f61f2 Mon Sep 17 00:00:00 2001 From: kevin Date: Tue, 1 Apr 2025 10:03:58 +0800 Subject: [PATCH] add binary file easytier-web-embed (#718) * embed web dashboard into easytier-web * add binary file easytier-web-embed --- .github/workflows/core.yml | 79 +++++++++++++++++++++++++++++++----- .github/workflows/test.yml | 28 +++++++++++++ Cargo.lock | 25 ++++++++++++ easytier-web/Cargo.toml | 5 +++ easytier-web/locales/app.yml | 6 +++ easytier-web/src/main.rs | 36 +++++++++++++++- easytier-web/src/web/mod.rs | 39 ++++++++++++++++++ 7 files changed, 206 insertions(+), 12 deletions(-) create mode 100644 easytier-web/src/web/mod.rs diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 2ecde44..c858de0 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -31,6 +31,47 @@ jobs: skip_after_successful_duplicate: 'true' cancel_others: 'true' paths: '["Cargo.toml", "Cargo.lock", "easytier/**", ".github/workflows/core.yml", ".github/workflows/install_rust.sh"]' + build_web: + runs-on: ubuntu-latest + needs: pre_job + if: needs.pre_job.outputs.should_skip != 'true' + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v4 + with: + node-version: 21 + + - name: Install pnpm + uses: pnpm/action-setup@v3 + with: + version: 9 + run_install: false + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - name: Setup pnpm cache + uses: actions/cache@v4 + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install frontend dependencies + run: | + pnpm -r install + pnpm -r --filter "./easytier-web/*" build + + - name: Archive artifact + uses: actions/upload-artifact@v4 + with: + name: easytier-web-dashboard + path: | + easytier-web/frontend/dist/* build: strategy: fail-fast: false @@ -87,7 +128,9 @@ jobs: TARGET: ${{ matrix.TARGET }} OS: ${{ matrix.OS }} OSS_BUCKET: ${{ secrets.ALIYUN_OSS_BUCKET }} - needs: pre_job + needs: + - pre_job + - build_web if: needs.pre_job.outputs.should_skip != 'true' steps: - uses: actions/checkout@v3 @@ -96,6 +139,12 @@ jobs: run: | echo "GIT_DESC=$(git log -1 --format=%cd.%h --date=format:%Y-%m-%d_%H:%M:%S)" >> $GITHUB_ENV + - name: Download web artifact + uses: actions/download-artifact@v4 + with: + name: easytier-web-dashboard + path: easytier-web/frontend/dist/ + - name: Cargo cache if: ${{ ! endsWith(matrix.TARGET, 'freebsd') }} uses: actions/cache@v4 @@ -121,23 +170,27 @@ jobs: if [[ $OS =~ ^ubuntu.*$ && $TARGET =~ ^mips.*$ ]]; then cargo +nightly build -r --verbose --target $TARGET -Z build-std=std,panic_abort --no-default-features --features mips --package=easytier else + if [[ $OS =~ ^windows.*$ ]]; then + SUFFIX=.exe + fi + cargo build --release --verbose --target $TARGET --package=easytier-web --features=embed + mv ./target/$TARGET/release/easytier-web"$SUFFIX" ./target/$TARGET/release/easytier-web-embed"$SUFFIX" cargo build --release --verbose --target $TARGET fi # Copied and slightly modified from @lmq8267 (https://github.com/lmq8267) - name: Build Core & Cli (X86_64 FreeBSD) - uses: cross-platform-actions/action@v0.23.0 + uses: vmactions/freebsd-vm@v1 if: ${{ endsWith(matrix.TARGET, 'freebsd') }} env: TARGET: ${{ matrix.TARGET }} with: - operating_system: freebsd - environment_variables: TARGET - architecture: x86-64 - version: ${{ matrix.BSD_VERSION }} - shell: bash - memory: 5G - cpu_count: 4 + envs: TARGET + release: ${{ matrix.BSD_VERSION }} + arch: x86_64 + usesh: true + mem: 6144 + cpu: 4 run: | uname -a echo $SHELL @@ -146,9 +199,9 @@ jobs: whoami env | sort - sudo pkg install -y git protobuf llvm-devel + pkg install -y git protobuf llvm-devel sudo curl curl --proto 'https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - source $HOME/.cargo/env + . $HOME/.cargo/env rustup set auto-self-update disable @@ -159,6 +212,8 @@ jobs: export CXX=clang++ export CARGO_TERM_COLOR=always + cargo build --release --verbose --target $TARGET --package=easytier-web --features=embed + mv ./target/$TARGET/release/easytier-web ./target/$TARGET/release/easytier-web-embed cargo build --release --verbose --target $TARGET - name: Install UPX @@ -196,6 +251,7 @@ jobs: mv ./target/$TARGET/release/easytier-cli"$SUFFIX" ./artifacts/objects/ if [[ ! $TARGET =~ ^mips.*$ ]]; then mv ./target/$TARGET/release/easytier-web"$SUFFIX" ./artifacts/objects/ + mv ./target/$TARGET/release/easytier-web-embed"$SUFFIX" ./artifacts/objects/ fi mv ./artifacts/objects/* ./artifacts/ @@ -213,6 +269,7 @@ jobs: runs-on: ubuntu-latest needs: - pre_job + - build_web - build steps: - name: Mark result as failed diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d989b3a..4575d24 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,6 +52,34 @@ jobs: sudo sysctl net.ipv6.conf.lo.disable_ipv6=0 sudo ip addr add 2001:db8::2/64 dev lo + - uses: actions/setup-node@v4 + with: + node-version: 21 + + - name: Install pnpm + uses: pnpm/action-setup@v3 + with: + version: 9 + run_install: false + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - name: Setup pnpm cache + uses: actions/cache@v4 + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install frontend dependencies + run: | + pnpm -r install + pnpm -r --filter "./easytier-web/*" build + - name: Cargo cache uses: actions/cache@v4 with: diff --git a/Cargo.lock b/Cargo.lock index d415c13..ca70395 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -571,6 +571,20 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-embed" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "077959a7f8cf438676af90b483304528eb7e16eadadb7f44e9ada4f9dceb9e62" +dependencies = [ + "axum-core", + "chrono", + "http", + "mime_guess", + "rust-embed", + "tower-service", +] + [[package]] name = "axum-login" version = "0.16.0" @@ -2028,6 +2042,7 @@ dependencies = [ "anyhow", "async-trait", "axum", + "axum-embed", "axum-login", "axum-messages", "base64 0.22.1", @@ -4116,6 +4131,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" diff --git a/easytier-web/Cargo.toml b/easytier-web/Cargo.toml index 2a33bb0..e2a7b3e 100644 --- a/easytier-web/Cargo.toml +++ b/easytier-web/Cargo.toml @@ -18,6 +18,7 @@ axum = { version = "0.7", features = ["macros"] } axum-login = { version = "0.16" } password-auth = { version = "1.0.0" } axum-messages = "0.7.0" +axum-embed = { version = "0.1.0", optional = true } tower-sessions-sqlx-store = { version = "0.14.1", features = ["sqlite"] } tower-sessions = { version = "0.13.0", default-features = false, features = [ "signed", @@ -59,3 +60,7 @@ uuid = { version = "1.5.0", features = [ ] } chrono = { version = "0.4.37", features = ["serde"] } + +[features] +default = [] +embed = ["dep:axum-embed"] \ No newline at end of file diff --git a/easytier-web/locales/app.yml b/easytier-web/locales/app.yml index 1ce6e5d..bca40a7 100644 --- a/easytier-web/locales/app.yml +++ b/easytier-web/locales/app.yml @@ -22,3 +22,9 @@ cli: api_server_port: en: "The port to listen for the restful server, acting as ApiHost and used by the web frontend" zh-CN: "restful 服务器的监听端口,作为 ApiHost 并被 web 前端使用" + web_server_port: + en: "The port to listen for the web dashboard server" + zh-CN: "web dashboard 服务器的监听端口" + no_web: + en: "Do not run the web dashboard server" + zh-CN: "不运行 web dashboard 服务器" \ No newline at end of file diff --git a/easytier-web/src/main.rs b/easytier-web/src/main.rs index 9b0f955..0a993d2 100644 --- a/easytier-web/src/main.rs +++ b/easytier-web/src/main.rs @@ -5,7 +5,7 @@ extern crate rust_i18n; use std::sync::Arc; -use clap::{command, Parser}; +use clap::Parser; use easytier::{ common::{ config::{ConfigLoader, ConsoleLoggerConfig, FileLoggerConfig, TomlConfigLoader}, @@ -21,6 +21,9 @@ mod db; mod migrator; mod restful; +#[cfg(feature = "embed")] +mod web; + rust_i18n::i18n!("locales", fallback = "en"); #[derive(Parser, Debug)] @@ -70,6 +73,23 @@ struct Cli { help = t!("cli.api_server_port").to_string(), )] api_server_port: u16, + + #[cfg(feature = "embed")] + #[arg( + long, + short='l', + default_value = "11210", + help = t!("cli.web_server_port").to_string(), + )] + web_server_port: u16, + + #[cfg(feature = "embed")] + #[arg( + long, + help = t!("cli.no_web").to_string(), + default_value = "false" + )] + no_web: bool, } pub fn get_listener_by_url( @@ -120,6 +140,20 @@ async fn main() { ) .await .unwrap(); + restful_server.start().await.unwrap(); + + #[cfg(feature = "embed")] + let mut web_server = web::WebServer::new( + format!("0.0.0.0:{}", cli.web_server_port).parse().unwrap() + ) + .await + .unwrap(); + + #[cfg(feature = "embed")] + if !cli.no_web { + web_server.start().await.unwrap(); + } + tokio::signal::ctrl_c().await.unwrap(); } diff --git a/easytier-web/src/web/mod.rs b/easytier-web/src/web/mod.rs new file mode 100644 index 0000000..98d0529 --- /dev/null +++ b/easytier-web/src/web/mod.rs @@ -0,0 +1,39 @@ +use axum::Router; +use easytier::common::scoped_task::ScopedTask; +use rust_embed::RustEmbed; +use std::net::SocketAddr; +use axum_embed::ServeEmbed; +use tokio::net::TcpListener; + +/// Embed assets for web dashboard, build frontend first +#[derive(RustEmbed, Clone)] +#[folder = "frontend/dist/"] +struct Assets; + +pub struct WebServer { + bind_addr: SocketAddr, + serve_task: Option>, +} + +impl WebServer { + pub async fn new(bind_addr: SocketAddr) -> anyhow::Result { + Ok(WebServer { + bind_addr, + serve_task: None, + }) + } + + pub async fn start(&mut self) -> Result<(), anyhow::Error> { + let listener = TcpListener::bind(self.bind_addr).await?; + let service = ServeEmbed::::new(); + let app = Router::new().fallback_service(service); + + let task = tokio::spawn(async move { + axum::serve(listener, app).await.unwrap(); + }); + + self.serve_task = Some(task.into()); + + Ok(()) + } +}