workflows #5

Merged
GW_MC merged 5 commits from workflows into master 2026-04-16 13:02:30 +08:00
15 changed files with 586 additions and 7 deletions

View File

@@ -30,7 +30,8 @@
"ghcr.io/guiyomh/features/just:0": {},
"ghcr.io/devcontainers-extra/features/bun": {
"version": "latest"
}
},
"ghcr.io/devcontainers-extra/features/act": {}
},
"customizations": {

1
.github/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.env

4
.github/.secrets.env.template vendored Normal file
View File

@@ -0,0 +1,4 @@
# This is an example environment variable file for GitHub Actions. You can copy this file to .github/.secrets.env and fill in the values to override the default registry and GitHub token used in the CI workflow. This is useful for testing with a private registry or using a different GitHub account for authentication.
OVERRIDE_REGISTRY=<your-registry-url>
OVERRIDE_GITHUB_TOKEN=<your-github-token>
GITHUB_USERNAME=<your-github-username>

View File

@@ -0,0 +1,70 @@
name: 'Setup CI metadata'
description: 'Composite action to derive the registry and CI image tag for the current repository.'
inputs:
registry:
description: 'Container registry derived from the current GitHub server URL'
required: false
default: ''
repository:
description: 'GitHub repository in the format owner/repo'
required: false
default: ${{ github.repository }}
image_tag:
description: 'Tag for the CI image'
required: false
default: 'latest'
outputs:
registry:
description: 'Container registry derived from the current GitHub server URL'
value: ${{ steps.setup.outputs.registry }}
image_tag:
description: 'Fully qualified CI image tag'
value: ${{ steps.setup.outputs.image_tag }}
latest_tag:
description: 'Fully qualified latest CI image tag'
value: ${{ steps.setup.outputs.latest_tag }}
runs:
using: 'composite'
steps:
- name: Setup Dynamic Metadata
id: setup
shell: bash
run: |
# Extract the domain from server_url, handling both https:// and ssh:// schemes
SERVER_URL="${{ github.server_url }}"
if [[ "$SERVER_URL" =~ ^ssh:// ]]; then
# For SSH URLs like ssh://git@host:port/path, extract just the hostname
SERVER_DOMAIN=$(echo "$SERVER_URL" | sed -e 's|^ssh://||' -e 's|^[^@]*@||' -e 's|:[0-9]*.*||')
else
# For HTTPS URLs, extract domain without scheme
SERVER_DOMAIN=$(echo "$SERVER_URL" | sed -e 's|^[^/]*//||' -e 's|/.*$||')
fi
echo "Extracted server domain: $SERVER_DOMAIN"
if [[ -n "${{ inputs.registry }}" ]]; then
REGISTRY="${{ inputs.registry }}"
elif [[ "$SERVER_DOMAIN" == "github.com" ]]; then
REGISTRY="ghcr.io"
else
REGISTRY="$SERVER_DOMAIN"
fi
# Extract owner/repo from github.repository, handling SSH URLs
REPO="${{ inputs.repository }}"
if [[ "$REPO" =~ ^ssh:// ]] || [[ "$REPO" =~ ^https:// ]]; then
# Extract owner/repo from URLs like ssh://git@host/owner/repo.git or https://host/owner/repo.git
REPO=$(echo "$REPO" | sed -e 's|^[^/]*/||' -e 's|\.git$||' | rev | cut -d'/' -f1,2 | rev)
fi
# Docker image names must be lowercase
REGISTRY="${REGISTRY,,}"
REPO="${REPO,,}"
IMAGE_TAG="${REGISTRY}/${REPO}/ci:${{ inputs.image_tag }}"
LATEST_TAG="${REGISTRY}/${REPO}/ci:latest"
echo "registry=$REGISTRY" >> "$GITHUB_OUTPUT"
echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
echo "latest_tag=$LATEST_TAG" >> "$GITHUB_OUTPUT"

74
.github/actions/setup-rust/action.yaml vendored Normal file
View File

@@ -0,0 +1,74 @@
name: 'Setup Rust environment'
description: 'Composite action to checkout the repo, restore cargo caches and set up the Rust toolchain. Use this from job steps to keep setup DRY across jobs.'
inputs:
toolchain:
description: 'Rust toolchain to install'
required: false
default: 'stable'
override:
description: 'Whether to override the default toolchain'
required: false
default: 'true'
components:
description: 'Comma-separated list of additional rust components to install'
required: false
default: 'clippy, rustfmt'
skip_cache:
description: 'Whether to skip restoring and uploading caches (useful for testing the workflow without cache interference)'
required: false
default: 'false'
runs:
using: 'composite'
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Cache cargo registry
uses: actions/cache@v4
if: inputs.skip_cache != 'true'
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v4
if: inputs.skip_cache != 'true'
with:
path: ~/.cargo/index
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
- name: Sanitize components input
shell: bash
run: echo "SANITIZED_COMPONENTS=${{ inputs.components }}" | sed -E 's/, ?| /-/g' >> $GITHUB_ENV
- name: Cache Rust toolchain
uses: actions/cache@v3
if: inputs.skip_cache != 'true'
with:
path: ~/.rustup
# Key includes the OS and the toolchain version (e.g., 'stable')
key: ${{ runner.os }}-rustup-${{ hashFiles('rust-toolchain.toml') }}-v1-${{ inputs.toolchain }}-${{ env.SANITIZED_COMPONENTS }}
restore-keys: |
${{ runner.os }}-rustup-
- name: Cache cargo build (target)
uses: actions/cache@v3
if: inputs.skip_cache != 'true'
with:
path: target
key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }}
- name: Set up rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ inputs.toolchain }}
override: ${{ inputs.override }}
components: ${{ inputs.components }}

31
.github/docker/ci.Dockerfile vendored Normal file
View File

@@ -0,0 +1,31 @@
FROM node:24-bookworm-slim
# Install necessary dependencies for building Rust projects and running tests
RUN apt-get update && apt-get install -y \
curl \
git \
zstd \
build-essential \
pkg-config \
libssl-dev \
gnupg \
unzip \
tar \
&& rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y \
postgresql-client \
protobuf-compiler \
&& rm -rf /var/lib/apt/lists/*
# install bun
RUN curl -fsSL https://bun.sh/install | bash
ENV PATH="/root/.bun/bin:${PATH}"
# install rust and cargo
RUN apt-get update && apt-get install -y curl build-essential
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
# Set the working directory
WORKDIR /app

54
.github/workflows/build-ci.yaml vendored Normal file
View File

@@ -0,0 +1,54 @@
name: Build CI Environment
on:
workflow_dispatch:
inputs:
image_tag:
description: 'Tag for the CI image (e.g., latest)'
required: true
default: 'latest'
env:
# OVERRIDE_REGISTRY can be set as a secret to override the default registry (e.g., for testing with a private registry). Else '' will be used, which defaults to ghcr.io for github.com and the GitHub server domain for self-hosted GitHub instances.
OVERRIDE_REGISTRY: ${{ secrets.OVERRIDE_REGISTRY }}
permissions:
contents: read
packages: write
concurrency:
group: build-ci
cancel-in-progress: true
jobs:
build-ci-image:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup CI metadata
id: setup
uses: ./.github/actions/setup-ci-metadata
with:
registry: ${{ env.OVERRIDE_REGISTRY }}
image_tag: ${{ github.event.inputs.image_tag }}
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
registry: ${{ steps.setup.outputs.registry }}
username: ${{ secrets.GITHUB_USERNAME || github.actor }}
password: ${{ secrets.OVERRIDE_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
- name: Build and push Docker image for CI
uses: docker/build-push-action@v3
with:
context: .
file: .github/docker/ci.Dockerfile
push: true
tags: |
${{ steps.setup.outputs.image_tag }}
${{ steps.setup.outputs.latest_tag }}

153
.github/workflows/test.yaml vendored Normal file
View File

@@ -0,0 +1,153 @@
# this workflow runs tests on pull request and push events targeting master branch
# it also verify the generated code is up to date and valid
name: Test
on:
pull_request:
branches:
- master
push:
branches:
- master
jobs:
get-ci-image:
runs-on: ubuntu-latest
outputs:
image_tag: ${{ steps.setup.outputs.image_tag }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup CI metadata
id: setup
uses: ./.github/actions/setup-ci-metadata
with:
registry: ${{ secrets.OVERRIDE_REGISTRY }}
image_tag: latest
test-crates:
runs-on: ubuntu-latest
needs:
- frontend-build
- get-ci-image
container:
image: ${{ needs.get-ci-image.outputs.image_tag }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Rust, checkout and restore caches
uses: ./.github/actions/setup-rust
- name: Restore frontend build cache
uses: actions/cache@v4
with:
path: apps/nxmesh-frontend/build
key: frontend-build-${{ runner.os }}-run-${{ github.run_id }}
restore-keys: |
frontend-build-${{ runner.os }}-
- name: Run tests
run: cargo test --all-features
lint-crates:
runs-on: ubuntu-latest
needs:
- frontend-build
- get-ci-image
container:
image: ${{ needs.get-ci-image.outputs.image_tag }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Rust, checkout and restore caches
uses: ./.github/actions/setup-rust
with:
components: clippy, rustfmt
- name: Restore frontend build cache
uses: actions/cache@v4
with:
path: apps/nxmesh-frontend/build
key: frontend-build-${{ runner.os }}-run-${{ github.run_id }}
restore-keys: |
frontend-build-${{ runner.os }}-
- name: Run clippy
run: cargo clippy --all-features
- name: Check code formatting
run: cargo fmt --all -- --check
lint-frontend:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- uses: oven-sh/setup-bun@v2
name: Install Bun
- name: Install frontend dependencies
run: |
cd apps/nxmesh-frontend
bun install
- name: Run frontend linter
run: |
cd apps/nxmesh-frontend
bun run lint
test-frontend:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: oven-sh/setup-bun@v2
name: Install Bun
- name: Install frontend dependencies
run: |
cd apps/nxmesh-frontend
bun install
- name: Run frontend tests
run: |
cd apps/nxmesh-frontend
bun run test
frontend-build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- uses: oven-sh/setup-bun@v2
name: Install Bun
- name: Install frontend dependencies
run: |
cd apps/nxmesh-frontend
bun install
- name: Build frontend
run: |
cd apps/nxmesh-frontend
bun run build
- name: Cache frontend build
uses: actions/cache@v4
with:
path: apps/nxmesh-frontend/build
key: frontend-build-${{ runner.os }}-run-${{ github.run_id }}
restore-keys: |
frontend-build-${{ runner.os }}-

139
.github/workflows/verify.yaml vendored Normal file
View File

@@ -0,0 +1,139 @@
# this workflow verifies the generated code is up to date and valid
name: Verify
on:
pull_request:
branches:
- master
push:
branches:
- master
env:
# OVERRIDE_REGISTRY can be set as a secret to override the default registry (e.g., for testing with a private registry). Else '' will be used, which defaults to ghcr.io for github.com and the GitHub server domain for self-hosted GitHub instances.
OVERRIDE_REGISTRY: ${{ secrets.OVERRIDE_REGISTRY }}
ACTIONS_STEP_DEBUG: true
jobs:
get-ci-image:
runs-on: ubuntu-latest
outputs:
image_tag: ${{ steps.setup.outputs.image_tag }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup CI metadata
id: setup
uses: ./.github/actions/setup-ci-metadata
with:
registry: ${{ secrets.OVERRIDE_REGISTRY }}
image_tag: latest
verify-generated-db-entities:
runs-on: ubuntu-latest
needs:
- get-ci-image
container:
image: ${{ needs.get-ci-image.outputs.image_tag }}
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: nxmesh
# ! do not set a fixed port to avoid conflicts when running multiple jobs in parallel, use Docker's internal networking instead
# ports:
# - 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d nxmesh"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
DATABASE_URL: postgres://postgres:postgres@postgres:5432/nxmesh
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check whether migrations/entities changed
id: check_changes
shell: bash
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE_SHA=${{ github.event.pull_request.base.sha }}
HEAD_SHA=${{ github.event.pull_request.head.sha }}
else
BASE_SHA=${{ github.event.before }}
HEAD_SHA=${{ github.sha }}
fi
if [ -z "$HEAD_SHA" ]; then
HEAD_SHA=$(git rev-parse --verify HEAD 2>/dev/null || echo "")
fi
if [ -z "$BASE_SHA" ]; then
PREV=$(git rev-parse --verify "${HEAD_SHA}^" 2>/dev/null || true)
if [ -n "$PREV" ]; then
BASE_SHA=$PREV
else
BASE_SHA=$HEAD_SHA
fi
fi
echo "Comparing $BASE_SHA..$HEAD_SHA"
CHANGED_FILES=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" || true)
echo "$CHANGED_FILES"
echo "$CHANGED_FILES" | grep -E '^(crates/migration/src/|apps/nxmesh-master/src/db/entities/)' >/dev/null 2>&1 \
&& echo "changed=true" >> $GITHUB_OUTPUT \
|| echo "changed=true" >> $GITHUB_OUTPUT
# || echo "changed=false" >> $GITHUB_OUTPUT
- name: Setup Rust, checkout and restore caches
if: steps.check_changes.outputs.changed == 'true'
uses: ./.github/actions/setup-rust
with:
skip_cache: ${{ vars.SKIP_CACHE }}
- name: Install SeaORM CLI
if: steps.check_changes.outputs.changed == 'true'
run: |
cargo install sea-orm-cli@^2.0.0-rc --features "sqlx-postgres runtime-tokio-rustls"
- name: Apply migrations
if: steps.check_changes.outputs.changed == 'true'
run: |
cargo run -p nxmesh-migration -- up
- name: Regenerate entities
if: steps.check_changes.outputs.changed == 'true'
run: |
sea-orm-cli generate entity \
--database-url "$DATABASE_URL" \
--output-dir apps/nxmesh-master/src/db/entities \
--with-serde both \
--with-copy-enums \
--date-time-crate chrono
- name: Check for uncommitted changes in entities
if: steps.check_changes.outputs.changed == 'true'
shell: bash
run: |
if [[ -n $(git status --porcelain --untracked-files=all | grep 'apps/nxmesh-master/src/db/entities/') ]]; then
echo "Generated SeaORM entities are not up to date."
echo "Run 'just db-generate' after applying migrations and commit the result."
git status --porcelain --untracked-files=all | grep 'apps/nxmesh-master/src/db/entities/'
exit 1
else
echo "Generated SeaORM entities are up to date."
fi
- name: Skip entity generation (no relevant changes)
if: steps.check_changes.outputs.changed == 'false'
run: echo "No changes in migrations/entities, skipping SeaORM entity verification."

1
.gitignore vendored
View File

@@ -68,6 +68,7 @@ web_modules/
# dotenv environment variable files
.env
.env.*
*.env
!.env.example
# parcel-bundler cache (https://parceljs.org/)

View File

@@ -0,0 +1,38 @@
use std::sync::Arc;
use nxmesh_proto::ConfigUpdate;
use tracing::info;
use crate::connector::master::MasterConnector;
#[async_trait::async_trait]
pub trait MasterHandler {
async fn on_config_update(
&self,
config_info: ConfigUpdate,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
}
pub struct MasterHandlerImpl {
settings: Arc<crate::config::settings::Settings>,
}
impl MasterHandlerImpl {
pub fn new(settings: impl Into<Arc<crate::config::settings::Settings>>) -> Self {
Self {
settings: settings.into(),
}
}
}
#[async_trait::async_trait]
impl MasterHandler for MasterHandlerImpl {
async fn on_config_update(
&self,
config_info: ConfigUpdate,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
info!("Received config update from master: {:?}", config_info);
Ok(())
}
}

View File

@@ -7,7 +7,8 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
"preview": "vite preview",
"test": "echo \"No test specified\" && exit 0"
},
"dependencies": {
"react": "^19.2.0",

View File

@@ -0,0 +1 @@

View File

@@ -10,6 +10,7 @@ pub mod agent {
pub use agent::*;
pub mod auth;
#[allow(ambiguous_glob_reexports)]
pub use tonic_async_interceptor::*;
#[cfg(test)]

View File

@@ -25,7 +25,6 @@ setup-rust-tools:
cargo install sea-orm-cli@^2.0.0-rc --features "sqlx-postgres runtime-tokio-rustls"
cargo install cargo-watch
# Setup frontend dependencies
setup-frontend:
@echo "📦 Installing frontend dependencies..."
@@ -35,6 +34,12 @@ setup-frontend:
# Development Commands
# =============================================================================
# act
act *ARGS:
# run act with custom secret-file
@echo "🎬 Running act with custom secrets file..."
act --env-file .github/.env --secret-file .github/.secrets.env --var-file .github/.var.env --network host {{ ARGS }}
# Start all services for development
dev:
@echo "🚀 Starting all development services..."
@@ -45,11 +50,11 @@ dev:
# Start Rust backend with hot reload
dev-master *ARGS:
@echo "🔧 Starting Rust backend..."
cargo watch -w apps/nxmesh-master -x 'run --bin nxmesh-master -- {{ARGS}}'
cargo watch -w apps/nxmesh-master -x 'run --bin nxmesh-master -- {{ ARGS }}'
dev-agent *ARGS:
@echo "🔧 Starting Rust agent..."
cargo watch -w apps/nxmesh-agent -x 'run --bin nxmesh-agent -- {{ARGS}}'
cargo watch -w apps/nxmesh-agent -x 'run --bin nxmesh-agent -- {{ ARGS }}'
# Start Vite frontend development server
dev-frontend:
@@ -89,7 +94,7 @@ build-frontend:
# =============================================================================
db *ARGS:
cd crates && sea-orm-cli {{ARGS}}
cd crates && sea-orm-cli {{ ARGS }}
# Setup database
db-setup:
@@ -205,6 +210,11 @@ docker-run:
@echo "🐳 Running Docker container..."
docker run -p 8080:8080 --env-file .env nxmesh:latest
# Build Docker image for CI
docker-build-ci REGISTRY="ghcr.io/nxmesh":
@echo "🐳 Building Docker image for CI..."
docker build -t {{ REGISTRY }}/ci:latest -f ./.github/docker/ci.Dockerfile .
# =============================================================================
# Nginx Commands (Shared PID Namespace + Docker Fallback)
# =============================================================================