在 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:.

太长不看版本:

  1. 在 Macbook 上安装 golang 和跨平台编译用的 musl
brew install golang FiloSottile/musl-cross/musl-cross
  1. 克隆 gogs 的 git 仓库并切换到最新稳定版本分支
git clone git@github.com:gogs/gogs.git
cd gogs
git checkout v0.13.2
  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
  1. 执行上述命令之后得到二进制文件 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。

方案

此时有四种方案:

  1. 升级操作系统的 glibc,但此举风险太大,glibc 是非常底层的依赖,操作不慎会把整个系统都搞崩,放弃此方案;
  2. 使用 Gogs 官方提供的 docker 镜像以容器方式运行,可以避免环境问题,但个人比较喜欢通过 ssh 协议进行代码库操作,利用证书可免除账号密码,可惜 docker 里的 ssh 无法和系统的融合,需要另开一个端口。可行性高,但不够优雅,作为候选方案;
  3. 搞一个对应版本的 glibc 库,专门给 gogs 使用,需要自行编译 glibc 且万一下次还有别的软件要用这种方式,就会使系统上并行的 glibc 库越来越多,不便管理,还不如 docker 方案;
  4. 自己编译一个 gogs 二进制包,个人本来是极力避免自行编译的,因为一旦选择这个方式,基本上以后凡是要升级都得自己编译,不过根据个人有限的 go 语言知识也知道 go 程序的编译算是非常方便的,比起上面两种方案,倒也算是不错的选择了。

首次编译与版本选择问题

  1. 安装 golang
brew install golang
  1. 拉取代码
git clone https://github.com/gogs/gogs.git
  1. 切换到最新稳定版本的分支
cd gogs
git checkout v0.13.2
  1. 编译出第一个版本
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 的指令就可以编译出对应的二进制程序。

浙ICP备15043004号-1