在 Macbook 上跨平台编译 Gogs 以解决官方二进制包需要更高版本 glibc 而无法在 Anolis OS 8 上运行的问题
背景
几年前在阿里云上通过官方二进制包自托管了一套 gogs ,但是一直没有升级过。这次想一步到位升级到最新版本,却发现官方二进制包依赖更高版本的 glibc 而无法在 Anolis OS 8 上运行,最终计划通过源码编译一个二进制包来适配当前的服务器环境。
服务器环境
##### root@AnolisOS8
####### --------------
##O#O## OS: Anolis OS release 8.9 x86_64
####### Host: Alibaba Cloud ECS pc-i440fx-2.1
########### Kernel: 4.18.0-553.40.1.0.1.an8.x86_64
############# Uptime: 1 day, 23 hours, 40 mins
############### Packages: 790 (rpm)
################ Shell: bash 4.4.20
################# Resolution: 1024x768
##################### Terminal: /dev/pts/1
##################### CPU: Intel Xeon Platinum (2) @ 2.500GHz
################# GPU: 00:02.0 Cirrus Logic GD 5446
Memory: 631MiB / 1889MiB
个人电脑环境
'c. xiongliding@MacBook-Pro.local
,xNMM. -----------------------------
.OMMMMo OS: macOS 15.3.1 24D70 arm64
OMMM0, Host: Mac16,8
.;loddo:' loolloddol;. Kernel: 24.3.0
cKMMMMMMMMMMNWMMMMMMMMMM0: Uptime: 9 days, 3 hours, 32 mins
.KMMMMMMMMMMMMMMMMMMMMMMMWd. Packages: 198 (brew)
XMMMMMMMMMMMMMMMMMMMMMMMX. Shell: zsh 5.9
;MMMMMMMMMMMMMMMMMMMMMMMM: Resolution: 1512x982@2x
:MMMMMMMMMMMMMMMMMMMMMMMM: DE: Aqua
.MMMMMMMMMMMMMMMMMMMMMMMMX. WM: Rectangle
kMMMMMMMMMMMMMMMMMMMMMMMMWd. Terminal: iTerm2
.XMMMMMMMMMMMMMMMMMMMMMMMMMMk Terminal Font: Menlo-Regular 14
.XMMMMMMMMMMMMMMMMMMMMMMMMK. CPU: Apple M4 Pro
kMMMMMMMMMMMMMMMMMMMMMMd GPU: Apple M4 Pro
;KMMMMMMMWXXWMMMMMMMk. Memory: 4572MiB / 24576MiB
.cooc,. .,coo:.
太长不看版本:
- 在 Macbook 上安装 golang 和跨平台编译用的 musl
brew install golang FiloSottile/musl-cross/musl-cross
- 克隆 gogs 的 git 仓库并切换到最新稳定版本分支
git clone git@github.com:gogs/gogs.git
cd gogs
git checkout v0.13.2
- 按照下列参数编译生成二进制文件
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-linux-musl-gcc CXX=x86_64-linux-musl-g++ go build -ldflags "-linkmode external -extldflags -static" -o gogs
- 执行上述命令之后得到二进制文件 gogs,替换服务器上的同名文件,然后按照常规的方式运行
./gogs web
说来话长版本:
问题
尝试在 Anolis OS 8 上运行最新的 Gogs 0.13.2 版本,系统提示
./gogs: /lib64/libc.so.6: version `GLIBC_2.33' not found (required by ./gogs)
确认后发现 Anois OS 8.9 上的 glibc 版本为 2.28。
方案
此时有四种方案:
- 升级操作系统的 glibc,但此举风险太大,glibc 是非常底层的依赖,操作不慎会把整个系统都搞崩,放弃此方案;
- 使用 Gogs 官方提供的 docker 镜像以容器方式运行,可以避免环境问题,但个人比较喜欢通过 ssh 协议进行代码库操作,利用证书可免除账号密码,可惜 docker 里的 ssh 无法和系统的融合,需要另开一个端口。可行性高,但不够优雅,作为候选方案;
- 搞一个对应版本的 glibc 库,专门给 gogs 使用,需要自行编译 glibc 且万一下次还有别的软件要用这种方式,就会使系统上并行的 glibc 库越来越多,不便管理,还不如 docker 方案;
- 自己编译一个 gogs 二进制包,个人本来是极力避免自行编译的,因为一旦选择这个方式,基本上以后凡是要升级都得自己编译,不过根据个人有限的 go 语言知识也知道 go 程序的编译算是非常方便的,比起上面两种方案,倒也算是不错的选择了。
首次编译与版本选择问题
- 安装 golang
brew install golang
- 拉取代码
git clone https://github.com/gogs/gogs.git
- 切换到最新稳定版本的分支
cd gogs
git checkout v0.13.2
- 编译出第一个版本
GOOS=linux GOARCH=amd64 go build -o gogs
执行完上面的命令后,已经可以得到一个在 Anolis OS 8.9 上运行的 gogs 程序。
上面的代码在官方文档的基础上有几处调整
# 克隆仓库到 "gogs" 子目录
git clone --depth 1 https://github.com/gogs/gogs.git gogs
# 修改工作目录
cd gogs
# 编译主程序,这个步骤会下载所有依赖
go build -o gogs
首先官方文档中的 git clone 命令加了depth 1
,使其可以最快速度克隆下来一个最新的开发版本,而不是最新的稳定版,且无法通过切换分支的方式调整版本
其次直接运行 go build -o gogs
编译出来的是当前设备上运行的版本,苹果电脑是 arm 架构,编译结果无法在 x64 的 Linux 系统上运行。
跨版本升级的问题
将上面的 gogs 二进制包放到服务器上运行,发现提示一行文字后就自动退出了,查看日志来了解具体原因。
$ tail logs/gogs.log
[FATAL] [...b/gogs/internal/route/install.go:75 GlobalInit()] Failed to initialize ORM engine: open database: failed to connect to `host=127.0.0.1 user=gogs database=gogs`: dial error (dial tcp 127.0.0.1:5432: connect: connection refused)
根据提示,直接的错误原因是无法连接到数据库,但是我使用的是 sqlite 数据库,不应该访问本地的 MySQL ,为了验证是否自己编译的程序会读取不同路径的配置文件,导致找不到配置而使用默认值,尝试修改 custom/conf/app.ini 中的数据库地址参数为 127.0.0.2,再次运行发现错误中的地址也变化了,说明配置文件已经被读取到了。
一度怀疑是否自己编译的版本没有启用 sqlite 支持,在编译命令中加上了 -tags "sqlite” 进行了尝试,后来证明是走了弯路,但也学会了两个技巧。通过 -tags 调整编译的内容,以及通过 go version -m ./gogs 查看编译结果中包含了哪些内容。
最终发现是 v0.13 版本对配置文件中的参数名称进行了调整。控制数据库类型的参数从 DB_TYPE 改成了 TYPE。
[database]
TYPE = sqlite3
PATH = data/gogs.db
调整完毕后,发现系统会尝试使用 sqlite 数据库了,同时又引发一个新问题
[FATAL] [...b/gogs/internal/route/install.go:75 GlobalInit()] Failed to initialize ORM engine: open database: Binary was compiled with 'CGO_ENABLED=0', go-sqlite3 requires cgo to work. This is a stub
启用CGO
通过错误提示,可以知道 sqlite3 需要启用 CGO 才能正确运行,于是在命令中加入 CGO_ENABLED=1
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -o gogs
提示新错误
linux_syscall.c:67:13: error: call to undeclared function 'setresgid'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
具体原因是启用了跨平台编译后,go 编译器尝试在系统中寻找 x64 Linux 所需 lib 库无果,使用 brew 安装跨平台的 musl 可解决此问题
brew install FiloSottile/musl-cross/musl-cross
再次执行编译程序即可成功
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -o gogs
但是将二进制包放到服务器上运行后,仍然报错
./gogs: no such file or directory
究其原因是编译的时候基于 musl 库,但最终运行环境中没有,需要将其作为静态依赖打包到二进制包里,从而避免对外部库的依赖,于是得到最终的编译指令
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-linux-musl-gcc CXX=x86_64-linux-musl-g++ go build -ldflags "-linkmode external -extldflags -static" -o gogs
整理
虽然目前位置已经可以正常运行,但是还有几点可以优化,使后续运行和升级更加顺畅。
通过 systemd 来运行 gogs
在源代码中可以找到一个 scripts/systemd/gogs.service
文件,将其复制到服务器的 /etc/systemd/system/gogs.service
,
然后执行 systemctl enable gogs
,即可设置 gogs 随开机自动运行
为更新做准备
在源代码根目录下创建一个可以执行的 build.sh
git checkout main
git pull
git checkout $1
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-linux-musl-gcc CXX=x86_64-linux-musl-g++ go build -ldflags "-linkmode external -extldflags -static" -o gogs
这样以后有新版本的时候,只要执行类似 ./build.sh v0.13.2
的指令就可以编译出对应的二进制程序。