diff --git a/Makefile b/Makefile index 7e25d258..2eb93b34 100644 --- a/Makefile +++ b/Makefile @@ -36,11 +36,17 @@ build-test: fi cross-build: - @echo "==> Cross-compiling for linux/darwin/windows × amd64/arm64..." + @echo "==> Cross-compiling \ + $(if $(GOOS),$(GOOS),linux/darwin/windows) × \ + $(if $(GOARCH),$(GOARCH),amd64/arm64) \ + (version=$(or $(VERSION),dev))..." @mkdir -p bin docker build \ --file docker/Dockerfile.cross \ --target export \ + --build-arg VERSION=$(or $(VERSION),dev) \ + $(if $(GOOS),--build-arg TARGET_OS=$(GOOS)) \ + $(if $(GOARCH),--build-arg TARGET_ARCH=$(GOARCH)) \ --output type=local,dest=./bin \ . @echo "==> Done. Binaries written to ./bin/" diff --git a/README.md b/README.md index b6199f23..26c5bdbf 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ pnpm format wavelet/ ├── main.go # Entry point (delegates to internal/cmd) ├── config.example.yaml # Configuration template -├── Makefile # Common commands (swagger, tidy, license) +├── Makefile # Common commands (swagger, tidy, license, cross-build) ├── docker/ # Docker image build files (integrated/frontend/backend) ├── docs/ # Swagger auto-generated docs ├── frontend/ # Next.js frontend application @@ -257,6 +257,47 @@ cd frontend && pnpm lint ## 🚀 Deployment +### Cross-platform Binary + +Build static binaries for all 6 targets (Linux / macOS / Windows × amd64 / arm64) with a single command. +The compiled frontend is embedded in every binary — no separate deployment needed. + +**Prerequisites:** Docker with BuildKit enabled (Docker 23+ defaults to on). + +```bash +# Build all 6 binaries → ./bin/ +make cross-build + +# Stamp a release version +make cross-build VERSION=v1.2.3 + +# Build only a specific OS (both architectures) +make cross-build GOOS=linux +make cross-build GOOS=darwin +make cross-build GOOS=windows + +# Build only a specific architecture (all OSes) +make cross-build GOARCH=amd64 +make cross-build GOARCH=arm64 + +# Combine filters — single binary +make cross-build GOOS=linux GOARCH=arm64 +make cross-build GOOS=darwin GOARCH=amd64 VERSION=v1.2.3 +``` + +Output files in `./bin/`: + +| File | Platform | +|------|----------| +| `wavelet_linux_amd64` | Linux x86-64 | +| `wavelet_linux_arm64` | Linux ARM64 | +| `wavelet_darwin_amd64` | macOS Intel | +| `wavelet_darwin_arm64` | macOS Apple Silicon | +| `wavelet_windows_amd64.exe` | Windows x86-64 | +| `wavelet_windows_arm64.exe` | Windows ARM64 | + +> The version string is accessible at runtime via `wavelet --version`. + ### Docker ```bash diff --git a/README_zh.md b/README_zh.md index 507a1201..686af6b6 100644 --- a/README_zh.md +++ b/README_zh.md @@ -212,7 +212,7 @@ pnpm format wavelet/ ├── main.go # 程序入口(委托给 internal/cmd) ├── config.example.yaml # 配置模板 -├── Makefile # 常用命令(swagger、tidy、license) +├── Makefile # 常用命令(swagger、tidy、license、cross-build) ├── docker/ # Docker 镜像构建文件(集成/前端/后端) ├── docs/ # Swagger 自动生成文档 ├── frontend/ # Next.js 前端应用 @@ -257,6 +257,47 @@ cd frontend && pnpm lint ## 🚀 部署 +### 跨平台二进制编译 + +一条命令构建全部 6 个平台的静态二进制文件(Linux / macOS / Windows × amd64 / arm64)。 +前端已内嵌到每个二进制文件中,无需单独部署。 + +**前提条件:** 已安装 Docker 且启用 BuildKit(Docker 23+ 默认开启)。 + +```bash +# 构建全部 6 个二进制文件 → ./bin/ +make cross-build + +# 指定版本号 +make cross-build VERSION=v1.2.3 + +# 只构建指定系统(两种架构均会构建) +make cross-build GOOS=linux +make cross-build GOOS=darwin +make cross-build GOOS=windows + +# 只构建指定架构(所有系统均会构建) +make cross-build GOARCH=amd64 +make cross-build GOARCH=arm64 + +# 同时指定系统和架构 — 只生成单个文件 +make cross-build GOOS=linux GOARCH=arm64 +make cross-build GOOS=darwin GOARCH=amd64 VERSION=v1.2.3 +``` + +输出到 `./bin/` 目录: + +| 文件名 | 平台 | +|--------|------| +| `wavelet_linux_amd64` | Linux x86-64 | +| `wavelet_linux_arm64` | Linux ARM64 | +| `wavelet_darwin_amd64` | macOS Intel | +| `wavelet_darwin_arm64` | macOS Apple Silicon | +| `wavelet_windows_amd64.exe` | Windows x86-64 | +| `wavelet_windows_arm64.exe` | Windows ARM64 | + +> 版本号可通过 `wavelet --version` 在运行时查看。 + ### Docker ```bash diff --git a/docker/Dockerfile.cross b/docker/Dockerfile.cross index 6bd8cba1..8439cf11 100644 --- a/docker/Dockerfile.cross +++ b/docker/Dockerfile.cross @@ -1,52 +1,114 @@ # syntax=docker/dockerfile:1.7 -# Cross-platform build workflow — produces 6 static binaries under ./bin/ +# Cross-platform build workflow — produces up to 6 static binaries under ./bin/ +# Each binary embeds the compiled frontend and is stamped with VERSION. +# +# Build args: +# VERSION — version string injected at link time (default: dev) +# TARGET_OS — restrict to one OS: linux | darwin | windows (default: all) +# TARGET_ARCH — restrict to one arch: amd64 | arm64 (default: all) # # Usage: # docker build -f docker/Dockerfile.cross --output ./bin . +# docker build -f docker/Dockerfile.cross \ +# --build-arg VERSION=v1.2.3 --output ./bin . +# docker build -f docker/Dockerfile.cross \ +# --build-arg TARGET_OS=linux --build-arg TARGET_ARCH=arm64 \ +# --output ./bin . # # Or via Makefile: # make cross-build +# make cross-build VERSION=v1.2.3 +# make cross-build GOOS=linux GOARCH=arm64 +# make cross-build GOOS=darwin ARG GO_VERSION=1.25 +ARG NODE_VERSION=22 ARG ALPINE_VERSION=3.23 +# ── Stage 1: Build frontend (static export for embedding) ──────────────────── +FROM node:${NODE_VERSION}-alpine AS frontend-builder + +ARG VERSION=dev + +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +ENV NODE_ENV=production +ENV NEXT_STANDALONE_EXPORT=true + +RUN corepack enable && corepack prepare pnpm@10.10.0 --activate + +WORKDIR /workspace/frontend + +# Cache dependency layer +COPY frontend/package.json frontend/pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile + +COPY frontend/ ./ + +# Stamp version into package.json when VERSION is not "dev" +RUN if [ "$VERSION" != "dev" ]; then \ + node -e " \ + const fs = require('fs'); \ + const pkg = require('./package.json'); \ + pkg.version = process.env.VERSION.replace(/^v/, ''); \ + fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n'); \ + "; \ + fi + +RUN pnpm build:embed + +# ── Stage 2: Cross-compile Go backend ────────────────────────────────────── FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS builder +ARG VERSION=dev +# Empty string means "build all"; set to a specific value to filter. +ARG TARGET_OS= +ARG TARGET_ARCH= + # git — needed by go mod download for VCS-stamped modules # ca-certificates — needed for HTTPS during module download RUN apk add --no-cache ca-certificates git WORKDIR /workspace -# Cache dependency downloads separately from source +# Cache dependency layer COPY go.mod go.sum ./ RUN go mod download COPY . . -# Build matrix: GOOS × GOARCH -# CGO_ENABLED=0 ensures fully static, portable binaries (no libc dependency). -# -trimpath removes local path info; -s -w strips debug info for smaller output. -ARG BUILD_FLAGS="-trimpath -ldflags=-s -w" +# Overlay the compiled frontend into the embed path +COPY --from=frontend-builder /workspace/frontend/out ./internal/router/dist +# Build matrix: GOOS × GOARCH +# CGO_ENABLED=0 — fully static, no libc dependency, required for cross-compilation. +# -trimpath — strip local filesystem paths from the binary. +# -s -w — omit symbol table and DWARF debug info (smaller output). +# -X — stamp version string at link time. +# TARGET_OS / TARGET_ARCH — when non-empty, skip non-matching combinations. RUN set -e; \ mkdir -p /out; \ + FILTER_OS="${TARGET_OS}"; \ + FILTER_ARCH="${TARGET_ARCH}"; \ for GOOS in linux darwin windows; do \ + [ -n "$FILTER_OS" ] && [ "$GOOS" != "$FILTER_OS" ] && continue; \ for GOARCH in amd64 arm64; do \ + [ -n "$FILTER_ARCH" ] && [ "$GOARCH" != "$FILTER_ARCH" ] && continue; \ EXT=""; \ [ "$GOOS" = "windows" ] && EXT=".exe"; \ OUTPUT="/out/wavelet_${GOOS}_${GOARCH}${EXT}"; \ - echo "==> Building ${OUTPUT}..."; \ + echo "==> Building ${OUTPUT} (version=${VERSION})..."; \ CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} \ go build \ + -tags embed_frontend \ -trimpath \ - -ldflags="-s -w" \ + -ldflags="-s -w -X github.com/Rain-kl/Wavelet/internal/cmd.Version=${VERSION}" \ -o "${OUTPUT}" \ ./main.go; \ done; \ done # ── Export stage ───────────────────────────────────────────────────────────── -# `docker build --output ./bin .` will copy everything from /out into the host. +# `docker build --output ./bin .` copies everything from /out to the host. FROM scratch AS export COPY --from=builder /out/ / diff --git a/frontend/components/common/home/home-main.tsx b/frontend/components/common/home/home-main.tsx index f577de96..b1755a0a 100644 --- a/frontend/components/common/home/home-main.tsx +++ b/frontend/components/common/home/home-main.tsx @@ -5,7 +5,7 @@ import {motion} from "motion/react" import {useUser} from "@/contexts/user-context" import {Card, CardHeader, CardTitle} from "@/components/ui/card" import {Button} from "@/components/ui/button" -import {ArrowRight, ExternalLink, FileText, HelpCircle, Layers, Shield, ShieldCheck, User} from "lucide-react" +import {ArrowRight, ExternalLink, FileText, HelpCircle, Layers, Shield, ShieldCheck, Terminal, User} from "lucide-react" import Link from "next/link" export function HomeMain() { @@ -80,6 +80,15 @@ export function HomeMain() { bgColor: "bg-teal-500/10", borderColor: "hover:border-teal-500/30", }, + { + title: "系统日志", + description: "查看异步任务执行日志与系统运行状态详情", + icon: Terminal, + url: "/admin/logs", + color: "text-cyan-500", + bgColor: "bg-cyan-500/10", + borderColor: "hover:border-cyan-500/30", + }, ] return ( @@ -163,7 +172,7 @@ export function HomeMain() {