diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 849254b..8eb2b08 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -94,6 +94,12 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Get commit metadata + id: commit + run: | + echo "hash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + echo "time=$(git log -1 --format=%ct)" >> $GITHUB_OUTPUT + - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -126,6 +132,9 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + build-args: | + GIT_COMMIT_HASH=${{ steps.commit.outputs.hash }} + GIT_COMMIT_TIME=${{ steps.commit.outputs.time }} cache-from: type=gha cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile index 61ac35d..b4887c7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,27 +22,35 @@ RUN npm ci --prefix frontend # build.rs will run `npm run build` (node_modules already present) # and embed the resulting dist/ into the binary. COPY . . -RUN cargo build --release + +# Accept git metadata as build args so that build.rs can embed the correct +# version string even though .git is excluded from the Docker build context. +ARG GIT_COMMIT_HASH=unknown +ARG GIT_COMMIT_TIME= +RUN GIT_COMMIT_HASH=${GIT_COMMIT_HASH} GIT_COMMIT_TIME=${GIT_COMMIT_TIME} cargo build --release # ---- Stage 2: Runtime ---- FROM debian:bookworm-slim AS runtime RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates \ + ca-certificates gosu \ && rm -rf /var/lib/apt/lists/* WORKDIR /app -# Copy only the compiled binary +# Copy only the compiled binary and the entrypoint helper COPY --from=builder /app/target/release/motdtracker . +COPY entrypoint.sh /app/entrypoint.sh +RUN chmod +x /app/entrypoint.sh # Create the default data directory for the SQLite database RUN mkdir -p data -# Run as a non-root user for better security +# Create the unprivileged runtime user. +# chown the data dir so anonymous Docker volumes are initialised with the +# correct ownership; the entrypoint.sh re-applies chown for bind mounts. RUN useradd -r -u 10001 -s /bin/false motdtracker \ && chown motdtracker:motdtracker /app/data -USER motdtracker # Persist the SQLite database across container restarts VOLUME ["/app/data"] @@ -50,9 +58,13 @@ VOLUME ["/app/data"] # Default listen port (matches the default in AppConfig) EXPOSE 5011 +# entrypoint.sh runs as root, fixes /app/data ownership, then drops to the +# motdtracker user via gosu before executing the application. +# # Mount your config.toml at /app/config.toml before starting. # Example: # docker run -v ./config.toml:/app/config.toml \ # -v motdtracker_data:/app/data \ # -p 5011:5011 ghcr.io/poicraft/motdtracker +ENTRYPOINT ["/app/entrypoint.sh"] CMD ["./motdtracker"] diff --git a/README.md b/README.md index 15e05f7..62a68ab 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ Rust 高性能后端 + React 前端 · 单文件部署 · 前端内嵌 -[![CI](https://github.com/PoiCraft/motdtracker-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/PoiCraft/motdtracker-rs/actions/workflows/ci.yml) -[![Release](https://img.shields.io/github/v/release/PoiCraft/motdtracker-rs?label=Latest)](https://github.com/PoiCraft/motdtracker-rs/releases/latest) +[![CI](https://github.com/PoiCraft/MotdTracker/actions/workflows/ci.yml/badge.svg)](https://github.com/PoiCraft/MotdTracker/actions/workflows/ci.yml) +[![Release](https://img.shields.io/github/v/release/PoiCraft/MotdTracker?label=Latest)](https://github.com/PoiCraft/MotdTracker/releases/latest) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) [![Rust](https://img.shields.io/badge/Rust-1.75+-orange.svg)](https://www.rust-lang.org/) @@ -36,7 +36,7 @@ MotdTracker 是一个专为 Minecraft 服务器设计的多入口点实时监控 ## 下载预编译版本 -前往 [GitHub Releases](https://github.com/PoiCraft/motdtracker-rs/releases/latest) 下载对应平台的预编译二进制: +前往 [GitHub Releases](https://github.com/PoiCraft/MotdTracker/releases/latest) 下载对应平台的预编译二进制: | 平台 | 文件 | |------|------| @@ -45,7 +45,7 @@ MotdTracker 是一个专为 Minecraft 服务器设计的多入口点实时监控 | macOS x86_64 | `motdtracker-x86_64-apple-darwin.tar.gz` | | macOS ARM64 | `motdtracker-aarch64-apple-darwin.tar.gz` | -> 每次 push 到 main 分支会自动构建,可在 [Actions](https://github.com/PoiCraft/motdtracker-rs/actions/workflows/ci.yml) 页面下载最新开发版 artifact。 +> 每次 push 到 main 分支会自动构建,可在 [Actions](https://github.com/PoiCraft/MotdTracker/actions/workflows/ci.yml) 页面下载最新开发版 artifact。 下载解压后直接运行: @@ -307,7 +307,7 @@ git config core.hooksPath .githooks ## CI / CD -- **push / PR** → 自动 check + test + 多平台构建,产物上传到 [Actions](https://github.com/PoiCraft/motdtracker-rs/actions) +- **push / PR** → 自动 check + test + 多平台构建,产物上传到 [Actions](https://github.com/PoiCraft/MotdTracker/actions) - **打 tag(`v*`)** → 自动构建 + 生成 GitHub Release + 上传预编译二进制 + SHA256 校验和 ```bash @@ -326,15 +326,15 @@ git push origin v0.1.0 ### 直接运行(Docker) ```bash -# 拉取镜像(替换为实际 owner/repo) -docker pull ghcr.io/poicraft/motdtracker-rs:latest +# 拉取镜像 +docker pull ghcr.io/poicraft/motdtracker:latest # 以后台模式运行,映射端口并挂载数据目录 docker run -d --name motdtracker \ - -p 5011:5011 \ - -v $(pwd)/data:/app/data \ - -v $(pwd)/config.toml:/app/config.toml \ - ghcr.io/poicraft/motdtracker-rs:latest + -p 5011:5011 \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/config.toml:/app/config.toml \ + ghcr.io/poicraft/motdtracker:latest # 查看日志 docker logs -f motdtracker @@ -347,17 +347,17 @@ docker logs -f motdtracker ```yaml version: "3.8" services: - motdtracker: - image: ghcr.io/poicraft/motdtracker-rs:latest - container_name: motdtracker - restart: unless-stopped - ports: - - "5011:5011" - volumes: - - ./data:/app/data - - ./config.toml:/app/config.toml - environment: - - TZ=Asia/Shanghai + motdtracker: + image: ghcr.io/poicraft/motdtracker:latest + container_name: motdtracker + restart: unless-stopped + ports: + - "5011:5011" + volumes: + - ./data:/app/data + - ./config.toml:/app/config.toml + environment: + - TZ=Asia/Shanghai ``` 启动服务: diff --git a/build.rs b/build.rs index c52e20e..0c41b8f 100644 --- a/build.rs +++ b/build.rs @@ -14,22 +14,51 @@ fn main() { println!("cargo:rerun-if-changed=.git/{}", ref_path); } } + println!("cargo:rerun-if-env-changed=GIT_COMMIT_HASH"); + println!("cargo:rerun-if-env-changed=GIT_COMMIT_TIME"); // Generate pseudo version: vA.B.C-yyyyMMddhhmmss-{git-hash-short} + // Falls back to GIT_COMMIT_HASH / GIT_COMMIT_TIME env vars when the .git + // directory is absent (e.g. Docker builds where .git is in .dockerignore). let pkg_version = std::env::var("CARGO_PKG_VERSION").unwrap(); + let git_hash = Command::new("git") .args(["rev-parse", "--short", "HEAD"]) .output() .ok() .filter(|o| o.status.success()) .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) + .or_else(|| std::env::var("GIT_COMMIT_HASH").ok()) .unwrap_or_else(|| "unknown".to_string()); + // Use the commit's own timestamp, not the build time. + let commit_secs = Command::new("git") + .args(["log", "-1", "--format=%ct"]) + .output() + .ok() + .filter(|o| o.status.success()) + .and_then(|o| { + String::from_utf8_lossy(&o.stdout) + .trim() + .parse::() + .ok() + }) + .or_else(|| { + std::env::var("GIT_COMMIT_TIME") + .ok() + .and_then(|s| s.parse::().ok()) + .filter(|&t| t > 0) + }) + .unwrap_or_else(|| { + use std::time::{SystemTime, UNIX_EPOCH}; + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() + }); + let now = { - use std::time::{SystemTime, UNIX_EPOCH}; - let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - let secs = duration.as_secs(); - let tm = time_from_epoch(secs); + let tm = time_from_epoch(commit_secs); format!( "{:04}{:02}{:02}{:02}{:02}{:02}", tm.0, tm.1, tm.2, tm.3, tm.4, tm.5 diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..2ed7a4b --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,13 @@ +#!/bin/sh +set -e + +# When /app/data is bind-mounted from the host Docker may create the directory +# as root, preventing the unprivileged motdtracker user from writing the SQLite +# database. Fix ownership only when necessary (avoids startup latency on large +# bind mounts). +if [ "$(stat -c '%u:%g' /app/data 2>/dev/null)" != "10001:10001" ]; then + chown motdtracker:motdtracker /app/data || \ + echo "Warning: could not chown /app/data – database writes may fail if the directory is root-owned" +fi + +exec gosu motdtracker "$@" \ No newline at end of file