init
This commit is contained in:
commit
e54390a9f8
85 files changed
+29923
No files matched your search
+135
@@ -0,0 +1,135 @@
|
||||
# Dockerfile - 构建完全静态的 ligent
|
||||
#
|
||||
# 用法(podman 和 docker 都行,命令一样):
|
||||
#
|
||||
# podman build -t ligent-static .
|
||||
# podman run --rm ligent-static --version
|
||||
# podman create --name tmp ligent-static && podman cp tmp:/ligent-static . && podman rm tmp
|
||||
#
|
||||
# 或者直接跑 make 目标:
|
||||
# make docker-build # 构建镜像
|
||||
# make docker-extract # 把产物拷到当前目录(./ligent-static)
|
||||
#
|
||||
# 只在构建期需要网络(下载 zlib/OpenSSL/curl 源码);运行期不需要任何东西。
|
||||
#
|
||||
#
|
||||
# 为什么用 Alpine 做构建环境
|
||||
# --------------------------
|
||||
# ligent 的静态目标要一份**用 musl 编出来的** libcurl(发行版那份是给 glibc 的,
|
||||
# 混着链会得到一堆 undefined reference to __libc_start_main)。
|
||||
# Alpine 本身就是 musl 的发行版,`apk add build-base` 得到的 gcc 直接就是
|
||||
# musl 目标,省掉了"装 musl 工具链 + 配 spec 文件"这一整段麻烦,
|
||||
# 而且 musl-gcc 这个包装脚本在 Alpine 上根本不存在也不需要。
|
||||
#
|
||||
# 具体地:Alpine 的 gcc 默认 `--target=x86_64-alpine-linux-musl`,
|
||||
# 所以 tools/build-static-deps.sh 里的 CC 检测会落到 x86_64-linux-musl-gcc
|
||||
# 分支(我们显式给它 CC=cc),产出的 .a 天然就是 musl 的。
|
||||
#
|
||||
#
|
||||
# 为什么分多阶段
|
||||
# --------------
|
||||
# 最终镜像里只有那个静态二进制。构建工具链(gcc/perl/make)加上 zlib/OpenSSL/curl
|
||||
# 的源码和中间产物有好几百 MB,全都不该进最终镜像 —— 而 ligent 是静态链接的,
|
||||
# 最终镜像连 libc 都不需要,scratch 就够。
|
||||
#
|
||||
#
|
||||
# 关于构建缓存
|
||||
# ------------
|
||||
# 依赖的编译(OpenSSL 那步最慢,几分钟)单独占一层,且只依赖脚本本身。
|
||||
# 源码 COPY 放在它后面:改一行 C 代码不会让"重编 OpenSSL"这件事再发生一遍。
|
||||
# 这是这个 Dockerfile 里唯一一处为了速度而做的结构调整,值得。
|
||||
|
||||
# ------------------------------------------------------------------ 构建阶段
|
||||
FROM docker.io/library/alpine:3.20 AS build
|
||||
|
||||
# build-base: gcc/make/binutils(Alpine 的 gcc 就是 musl 目标的)
|
||||
# perl: OpenSSL 的 Configure 是 perl 写的
|
||||
# curl: tools/build-static-deps.sh 用来下载源码包
|
||||
# linux-headers: 依赖要内核 UAPI 头(linux/mman.h 这些)。
|
||||
# 注意在本机上这一项是用 -idirafter /usr/include 解决的,
|
||||
# 容器里显式装出来更干净。
|
||||
# tar/xz: 解压源码包
|
||||
RUN apk add --no-cache \
|
||||
build-base \
|
||||
perl \
|
||||
curl \
|
||||
tar \
|
||||
xz \
|
||||
linux-headers
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
# 先只拷贝依赖脚本,让"编依赖"这一步能独立于源码变化被缓存。
|
||||
# 版本号通过 build-arg 传进来(CI 里从 git tag 取),这样镜像内构建不依赖 .git ——
|
||||
# .dockerignore 把 .git 排除了,容器里本来也看不到它。
|
||||
COPY tools/build-static-deps.sh /src/tools/build-static-deps.sh
|
||||
|
||||
# CC=cc:Alpine 的 cc 就是 musl 目标的 gcc,不需要 musl-gcc 包装脚本。
|
||||
# 这一步会联网下载并编译 zlib + OpenSSL + curl,约几分钟。
|
||||
RUN CC=cc sh /src/tools/build-static-deps.sh
|
||||
|
||||
# 再拷源码。放在依赖之后,改 C 代码不会触发依赖重编。
|
||||
COPY src/ /src/src/
|
||||
COPY Makefile /src/Makefile
|
||||
COPY tools/get-version.sh /src/tools/get-version.sh
|
||||
|
||||
# 版本号由外面注入。容器里没有 .git,get-version.sh 会回退到源码文件的 mtime
|
||||
# —— 那是"这份源码的日期",不是"tag 的日期",所以 CI 应当显式传进来:
|
||||
# podman build --build-arg VERSION=$(git describe --tags --abbrev=0) .
|
||||
# podman build --build-arg VERSION=$(date -u +%Y.%m.%d) .
|
||||
ARG VERSION=
|
||||
ARG GIT_HASH=unknown
|
||||
ARG GIT_BRANCH=unknown
|
||||
# 完整的 Unix 时间戳(秒)。不要传 "2026-09-22" 这种只有日期的串 ——
|
||||
# date(1) 会把它解析成那天 00:00:00 UTC,产物的构建时间就永远是午夜。
|
||||
ARG BUILD_EPOCH=
|
||||
|
||||
RUN set -eux; \
|
||||
# 传了就用传进来的,没传就让脚本自己算(会回退到源码日期)
|
||||
if [ -n "$VERSION" ]; then export LIGENT_VERSION_OVERRIDE="$VERSION"; fi; \
|
||||
if [ -n "$GIT_HASH" ]; then export LIGENT_HASH_OVERRIDE="$GIT_HASH"; fi; \
|
||||
if [ -n "$GIT_BRANCH" ]; then export LIGENT_BRANCH_OVERRIDE="$GIT_BRANCH"; fi; \
|
||||
if [ -n "$BUILD_EPOCH" ]; then export SOURCE_DATE_EPOCH="$BUILD_EPOCH"; fi; \
|
||||
\
|
||||
# 静态目标。STATIC_CC=cc:同上,Alpine 的 cc 已经是 musl 目标。 \
|
||||
make static STATIC_CC=cc; \
|
||||
\
|
||||
# 自检:产物必须真的存在、真的是静态的、真的能跑。 \
|
||||
# 不检查的话,"构建成功"可能只是"链接器没报错",而产物是个空壳 \
|
||||
# (比如依赖没链全,或者 -s 把符号表剥没了导致入口找不到)。 \
|
||||
test -x /src/ligent-static; \
|
||||
if ldd /src/ligent-static 2>&1 | grep -qv "not a dynamic executable\|statically linked\|Not a valid dynamic program"; then \
|
||||
echo "产物不是静态链接的:" >&2; ldd /src/ligent-static >&2; exit 1; \
|
||||
fi; \
|
||||
V=$(/src/ligent-static --version); \
|
||||
echo "容器内产物版本: $V"; \
|
||||
# 版本号还是回退默认值 => -D 没生效,构建是"看起来成功"。
|
||||
# 这里只在**没传 VERSION** 时警告:那种情况下回退是预期行为(比如有人
|
||||
# 直接 podman build 不带参数),传了却没用上才是真问题。
|
||||
if [ -n "$VERSION" ] && echo "$V" | grep -q '0.0.0-unknown'; then \
|
||||
echo "错误:传了 VERSION=$VERSION 但产物仍报回退版本号" >&2; exit 1; \
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------ 运行阶段
|
||||
# scratch 就够了:ligent-static 是完全静态的,连 libc 都不需要。
|
||||
# 没有任何攻击面可言 —— 镜像里只有一个二进制文件,没有 shell、没有包管理器。
|
||||
FROM scratch
|
||||
|
||||
# CA 证书是**唯一**需要的外部文件:TLS 握手要它。
|
||||
# 注意 ligent 用的是编进 libcurl 的默认路径 /etc/ssl/certs/ca-certificates.crt
|
||||
# (libcurl 库不读 CURL_CA_BUNDLE,那是 curl 命令行工具的行为),
|
||||
# 所以证书必须放在那个位置才行,放别处不会被读到。
|
||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
|
||||
COPY --from=build /src/ligent-static /ligent
|
||||
|
||||
# 说明一下这个镜像是干什么的、怎么用。
|
||||
# 用 LABEL 而不是放个 README 文件:scratch 里没有 cat,放文件也读不了。
|
||||
LABEL org.opencontainers.image.title="ligent" \
|
||||
org.opencontainers.image.description="纯 C 实现的 AI Agent(静态构建)" \
|
||||
org.opencontainers.image.licenses="MIT" \
|
||||
org.opencontainers.image.source="https://github.com/pluvium27/ligent"
|
||||
|
||||
# 默认进 REPL 需要 tty;不带参数跑会因为没有 API key 而给出提示,
|
||||
# 所以这里不做 ENTRYPOINT 的花活,直接就是那个二进制。
|
||||
ENTRYPOINT ["/ligent"]
|
||||
Reference in new issue
Block a user