fix(build): Docker 构建透传 git 信息,修复 x-yggdrasil-git 恒为 unknown
.dockerignore 排除 .git/,容器内 build.rs 跑不了 git 命令,导致 x-yggdrasil-git / git_hash / commit_date 响应头和启动日志全是 "unknown" (version 来自 CARGO_PKG_VERSION 不受影响)。 三级取值链(build.rs env_or_git): YGG_BUILD_* 环境变量 → 本地 git 命令 → "unknown" 第一级 std::env::var 优先读 docker --build-arg 注入的值,第二级保留 本地 cargo build 的既有行为,兜底不变。 - Dockerfile: 主应用 server build 前加 ARG+ENV 块(ARG 仅 RUN 可见, build.rs 由 cargo 调用非 RUN,故需 export 成 ENV 让 cargo build 继承) - Makefile: GIT_DESCRIBE/HASH/DATE 在宿主采集,GIT_BUILD_ARGS 复用块 注入 docker/docker-amd64/docker-multiarch 三个 target - deploy-to-linux skill: 两处 docker buildx build 命令(首装 + 更新) 加同名 --build-arg,附说明
This commit is contained in:
parent
eaabb87108
commit
856eeb758c
@ -110,12 +110,17 @@ grep UseVirtualizationFrameworkRosetta ~/Library/Group\ Containers/group.com.doc
|
||||
### 主应用镜像
|
||||
|
||||
```bash
|
||||
docker buildx build --platform linux/amd64 --load -t localhost/yggdrasil:latest .
|
||||
docker buildx build --platform linux/amd64 --load \
|
||||
--build-arg YGG_BUILD_GIT_DESCRIBE="$(git describe --tags --always --dirty)" \
|
||||
--build-arg YGG_BUILD_GIT_HASH="$(git rev-parse HEAD)" \
|
||||
--build-arg YGG_BUILD_GIT_COMMIT_DATE="$(git log -1 --format=%cd --date=iso-strict)" \
|
||||
-t localhost/yggdrasil:latest .
|
||||
```
|
||||
|
||||
- Dockerfile 用 `dpkg --print-architecture` 检测架构,amd64 腿原生构建 `x86_64-unknown-linux-musl` 静态二进制
|
||||
- 首次约 15-30 分钟(Rosetta 下 cargo 全量编译),有 buildkit 缓存后分钟级
|
||||
- 产物 `localhost/yggdrasil:latest`,scratch 运行时层约 16MB
|
||||
- **`--build-arg` 透传 git 信息**:`.dockerignore` 排除 `.git/`,容器内 `build.rs` 跑不了 git,必须在宿主采集后注入,否则镜像的 `x-yggdrasil-git` 响应头恒为 `unknown`。三个 `$(...)` 命令替换在本机 zsh/bash 下执行(非服务器)。`make docker-amd64` 等价(Makefile 已内置同样的采集+透传)
|
||||
|
||||
### 6 个 Code Runner 沙箱镜像
|
||||
|
||||
@ -418,7 +423,11 @@ rm -f /tmp/yggdrasil-*.tar* /tmp/.env
|
||||
|
||||
```bash
|
||||
# 1. 本地重新构建主应用(有缓存,快)
|
||||
docker buildx build --platform linux/amd64 --load -t localhost/yggdrasil:latest .
|
||||
docker buildx build --platform linux/amd64 --load \
|
||||
--build-arg YGG_BUILD_GIT_DESCRIBE="$(git describe --tags --always --dirty)" \
|
||||
--build-arg YGG_BUILD_GIT_HASH="$(git rev-parse HEAD)" \
|
||||
--build-arg YGG_BUILD_GIT_COMMIT_DATE="$(git log -1 --format=%cd --date=iso-strict)" \
|
||||
-t localhost/yggdrasil:latest .
|
||||
# 2. 导出传输
|
||||
docker save localhost/yggdrasil:latest | gzip > /tmp/yggdrasil-app.tar.gz
|
||||
scp /tmp/yggdrasil-app.tar.gz <host>:/root/docker/yggdrasil/
|
||||
|
||||
18
Dockerfile
18
Dockerfile
@ -148,6 +148,24 @@ COPY libs/lightbox/package.json libs/lightbox/
|
||||
COPY libs/yggdrasil-core/package.json libs/yggdrasil-core/
|
||||
RUN cd libs && pnpm install --frozen-lockfile
|
||||
|
||||
# Build-time git info, injected by the caller via --build-arg. `.dockerignore`
|
||||
# excludes `.git/`, so build.rs can't run `git` inside the container — these
|
||||
# ARGs are the only channel for git metadata to reach build.rs (it reads them
|
||||
# via std::env::var, its first-precedence source). Defaults are empty so a
|
||||
# bare `docker build` without args degrades to "unknown" gracefully (build.rs
|
||||
# falls back to the git command, then "unknown").
|
||||
#
|
||||
# Each ARG → ENV pair is needed: ARG is only visible in Dockerfile RUN
|
||||
# commands (and build.rs is invoked by cargo, not a RUN), so we export it as
|
||||
# ENV for the cargo build step to inherit. Both default to empty; Makefile's
|
||||
# docker/docker-amd64/docker-multiarch targets override them on the host.
|
||||
ARG YGG_BUILD_GIT_DESCRIBE=""
|
||||
ARG YGG_BUILD_GIT_HASH=""
|
||||
ARG YGG_BUILD_GIT_COMMIT_DATE=""
|
||||
ENV YGG_BUILD_GIT_DESCRIBE=${YGG_BUILD_GIT_DESCRIBE}
|
||||
ENV YGG_BUILD_GIT_HASH=${YGG_BUILD_GIT_HASH}
|
||||
ENV YGG_BUILD_GIT_COMMIT_DATE=${YGG_BUILD_GIT_COMMIT_DATE}
|
||||
|
||||
# Copy the rest of the source tree and build everything.
|
||||
COPY . .
|
||||
|
||||
|
||||
19
Makefile
19
Makefile
@ -186,19 +186,32 @@ doc-open:
|
||||
# Push examples:
|
||||
# make docker-multiarch IMAGE=ghcr.io/owner/yggdrasil:latest
|
||||
# make docker-multiarch IMAGE=user/yggdrasil:v1 PLATFORMS=linux/amd64
|
||||
#
|
||||
# git 信息透传:.dockerignore 排除 .git/,容器内 build.rs 跑不了 git 命令,
|
||||
# 所以在宿主(本 Makefile 里)采集后用 --build-arg 注入。Dockerfile 把 ARG
|
||||
# 再 export 成 ENV,build.rs 的 std::env::var 优先读它。三个值在 Make 变量
|
||||
# 里采集一次,所有 docker target 复用;git 不可用时退化为空串,Dockerfile
|
||||
# 默认值也是空,build.rs 最终降级为 "unknown",不阻断构建。
|
||||
IMAGE ?= yggdrasil
|
||||
PLATFORMS ?= linux/amd64,linux/arm64
|
||||
GIT_DESCRIBE := $(shell git describe --tags --always --dirty 2>/dev/null)
|
||||
GIT_HASH := $(shell git rev-parse HEAD 2>/dev/null)
|
||||
GIT_DATE := $(shell git log -1 --format=%cd --date=iso-strict 2>/dev/null)
|
||||
# build-arg 复用块:每个 docker target 展开一次。空值也传(让 Dockerfile 默认接管)。
|
||||
GIT_BUILD_ARGS = --build-arg YGG_BUILD_GIT_DESCRIBE="$(GIT_DESCRIBE)" \
|
||||
--build-arg YGG_BUILD_GIT_HASH="$(GIT_HASH)" \
|
||||
--build-arg YGG_BUILD_GIT_COMMIT_DATE="$(GIT_DATE)"
|
||||
docker:
|
||||
@docker buildx build --load -t yggdrasil .
|
||||
@docker buildx build --load $(GIT_BUILD_ARGS) -t yggdrasil .
|
||||
|
||||
# Cross-build x86_64 into the local daemon. buildx 仿真非原生架构,无需修改 Dockerfile
|
||||
# (它本就按 dpkg --print-architecture 自适应选 musl target)。Apple Silicon 上走 QEMU,
|
||||
# 比 native 慢;产物可直接 docker run / docker save 导出。
|
||||
docker-amd64:
|
||||
@docker buildx build --platform linux/amd64 --load -t yggdrasil:amd64 .
|
||||
@docker buildx build --platform linux/amd64 --load $(GIT_BUILD_ARGS) -t yggdrasil:amd64 .
|
||||
|
||||
docker-multiarch:
|
||||
@docker buildx build --platform $(PLATFORMS) -t $(IMAGE) --push .
|
||||
@docker buildx build --platform $(PLATFORMS) $(GIT_BUILD_ARGS) -t $(IMAGE) --push .
|
||||
|
||||
clean:
|
||||
@cargo clean
|
||||
|
||||
34
build.rs
34
build.rs
@ -7,6 +7,9 @@
|
||||
//! - git 不可用(非仓库 / tarball 构建)时降级为 "unknown",不 fail the build。
|
||||
//! - 声明 rerun-if-changed=.git/HEAD,否则 cargo 默认仅在 build.rs 自身变化时
|
||||
//! 重跑,会导致打印的还是旧 hash。
|
||||
//! - 三级取值:编译期环境变量(`YGG_BUILD_*`,CI / docker --build-arg 注入)
|
||||
//! → 本地 `git` 命令 → `"unknown"`。Docker 构建时 `.git/` 不进构建上下文
|
||||
//! (`.dockerignore` 排除),靠 Makefile 在宿主采集后用 `--build-arg` 透传。
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
@ -16,9 +19,9 @@ fn main() {
|
||||
// 工作区脏状态变化时 describe 的 --dirty 后缀也会变,重跑一次。
|
||||
println!("cargo:rerun-if-changed=.git/index");
|
||||
|
||||
set_env("YGG_BUILD_GIT_DESCRIBE", git_describe());
|
||||
set_env("YGG_BUILD_GIT_HASH", git_hash());
|
||||
set_env("YGG_BUILD_GIT_COMMIT_DATE", git_commit_date());
|
||||
set_env("YGG_BUILD_GIT_DESCRIBE", env_or_git("YGG_BUILD_GIT_DESCRIBE", &["describe", "--tags", "--always", "--dirty"]));
|
||||
set_env("YGG_BUILD_GIT_HASH", env_or_git("YGG_BUILD_GIT_HASH", &["rev-parse", "HEAD"]));
|
||||
set_env("YGG_BUILD_GIT_COMMIT_DATE", env_or_git("YGG_BUILD_GIT_COMMIT_DATE", &["log", "-1", "--format=%cd", "--date=iso-strict"]));
|
||||
set_env("YGG_BUILD_RUSTC_VERSION", rustc_version());
|
||||
// 编译时刻(Unix 秒)。std 无 ISO 8601 格式化,存秒数由运行时 chrono 解析。
|
||||
set_env("YGG_BUILD_TIME", build_time_unix());
|
||||
@ -29,20 +32,21 @@ fn set_env(key: &str, value: String) {
|
||||
println!("cargo:rustc-env={key}={value}");
|
||||
}
|
||||
|
||||
/// `git describe --tags --always --dirty`:最紧凑的"版本 + 提交数 + hash + 脏标记"。
|
||||
fn git_describe() -> String {
|
||||
git_output(&["describe", "--tags", "--always", "--dirty"]).unwrap_or_else(|| "unknown".into())
|
||||
/// 三级取值:`YGG_BUILD_<KEY>` 环境变量 → 本地 git 命令 → `"unknown"`。
|
||||
///
|
||||
/// - 第一级优先 `std::env::var`(Docker 构建时由 `--build-arg` → `ARG` → `ENV`
|
||||
/// 注入到 build.rs 进程环境),非空即采用——这是 Docker 构建唯一能拿到 git
|
||||
/// 信息的通道(`.git/` 被 `.dockerignore` 排除,容器内没有仓库可查)。
|
||||
/// - 第二级落回本地 `git` 命令(本地 `cargo build` / 非 Docker CI 走这条)。
|
||||
/// - 兜底 `"unknown"`(tarball 构建、git 未安装等)。
|
||||
fn env_or_git(env_key: &str, git_args: &[&str]) -> String {
|
||||
if let Ok(v) = std::env::var(env_key) {
|
||||
let v = v.trim();
|
||||
if !v.is_empty() {
|
||||
return v.to_string();
|
||||
}
|
||||
|
||||
/// 完整 40 位 commit hash。
|
||||
fn git_hash() -> String {
|
||||
git_output(&["rev-parse", "HEAD"]).unwrap_or_else(|| "unknown".into())
|
||||
}
|
||||
|
||||
/// 提交时间(ISO 8601 strict,带时区偏移,可直接被 chrono 解析)。
|
||||
fn git_commit_date() -> String {
|
||||
git_output(&["log", "-1", "--format=%cd", "--date=iso-strict"])
|
||||
.unwrap_or_else(|| "unknown".into())
|
||||
git_output(git_args).unwrap_or_else(|| "unknown".into())
|
||||
}
|
||||
|
||||
/// 执行一条 git 命令,返回 trim 后的 stdout。失败返回 None(降级路径)。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user