1. 为什么要在鲲鹏服务器上折腾 Gogs
先把场景说清楚。手上有一台华为云的鲲鹏实例,CPU 是 ARM 架构的,跑的是 openEuler 或者麒麟这类国产系统。现在想在这台机器上搭一个轻量的 Git 服务,用来托管团队内部的代码仓库。选 Gogs 而不是 GitLab,理由很直接:GitLab 那套东西对内存和 CPU 的胃口太大,一台 2 核 4G 的鲲鹏实例跑起来基本就喘不上气了,而 Gogs 是 Go 写的,编译出来就一个二进制文件,内存占用小,启动快,几百兆内存就能稳稳跑起来,非常适合这种资源不算宽裕的云主机场景。
但问题也随之而来。鲲鹏是 ARM64 架构,跟常见的 x86 服务器不是一回事。网上大部分 Gogs 的安装教程都是 x86 的,直接照搬会遇到两个坑:一是预编译的二进制包大多是 amd64 的,拿到鲲鹏上根本跑不起来;二是系统自带的包管理源里,某些依赖的版本和 x86 不一样,装数据库、配服务的时候容易出岔子。所以这篇东西的核心,就是把这台 ARM 架构的鲲鹏服务器从零到一跑起 Gogs 的完整过程讲透,包括架构适配怎么处理、数据库怎么选、服务怎么托管、反向代理怎么配,以及我实际踩过的那些坑。
适合谁看?如果你手上有鲲鹏或者其他 ARM 架构的云主机,想搭一个私有代码托管服务,又不想被 GitLab 那种重型方案拖垮,那这篇就是给你写的。就算你用的是 x86 机器,里面的数据库配置、服务托管、反向代理这些思路也是通用的,只是编译那一步可以省掉。下面我按实际操作的顺序,把每个环节拆开讲。
2. 动手前的整体规划与选型思路
2.1 架构适配:为什么不能直接下二进制包
这是整个项目里最关键的一个决策点。Gogs 官方在发布页提供了预编译的二进制包,但你去下载的时候会发现,文件名里带的是amd64、386、armv7这些标识。amd64是 x86 的 64 位,armv7是 32 位 ARM,而鲲鹏是arm64(也叫aarch64),是 64 位 ARM。这三个东西指令集不兼容,硬把 amd64 的包丢到鲲鹏上,执行的时候会直接报cannot execute binary file或者Exec format error。
那有没有 arm64 的预编译包?早期 Gogs 的发布里 arm64 的支持时有时无,版本也不一定跟得上。与其去赌某个版本刚好有 arm64 包,不如直接从源码编译。Gogs 是 Go 写的,Go 的交叉编译和原生编译都很成熟,在鲲鹏上装好 Go 环境,go build一下就能产出原生的 arm64 二进制,这是最稳妥的路子。而且自己编译还有个好处:能控制版本,能确认编译出来的东西确实是为当前架构优化的。
提示:编译前先确认系统架构,执行
uname -m,输出aarch64就说明是 64 位 ARM,跟鲲鹏对得上。如果输出x86_64,那说明你拿到的不是鲲鹏实例,后面的编译步骤可以跳过,直接下 amd64 的包。
2.2 数据库选型:SQLite、MySQL 还是 PostgreSQL
Gogs 支持三种数据库后端:SQLite、MySQL、PostgreSQL。这个选择直接影响后面的安装配置和维护成本,得根据实际使用规模来定。
SQLite 是最省事的,它就是一个文件,不需要单独装数据库服务,Gogs 直接读写这个文件就行。对于个人用或者三五人的小团队,仓库数量不多、并发访问很少的场景,SQLite 完全够用,而且备份就是复制一个文件,简单到不能再简单。但它的短板也明显:并发写入能力弱,多人同时 push 的时候容易锁表,仓库一多、访问一频繁就会成为瓶颈。
MySQL 和 PostgreSQL 是正经的数据库服务,能扛并发,适合团队规模稍大、仓库数量多的场景。两者在 Gogs 场景下差别不大,MySQL 的生态和运维资料更多一些,PostgreSQL 在数据一致性和复杂查询上更强。考虑到鲲鹏服务器上装 MySQL 的包源比较成熟,我这次选 MySQL。如果你追求更轻的运维负担,PostgreSQL 也是好选择,配置逻辑基本一致。
| 数据库 | 适用规模 | 优点 | 缺点 |
|---|---|---|---|
| SQLite | 个人/3人以下 | 零配置、单文件、备份简单 | 并发弱、易锁表 |
| MySQL | 中小团队 | 生态成熟、资料多、并发好 | 需单独维护服务 |
| PostgreSQL | 中小团队 | 一致性强、功能丰富 | 运维门槛略高 |
2.3 服务托管方式:systemd 还是裸跑
Gogs 编译出来是个可执行文件,理论上./gogs web就能跑起来。但这样跑有个致命问题:终端一关,进程就没了;服务器一重启,服务也不会自动起来。生产环境肯定不能这么干。
正确的做法是用 systemd 把它托管成一个系统服务。systemd 能负责进程的启动、停止、开机自启、崩溃自动重启,还能把日志统一收集起来用journalctl查看。配置一个.service文件,把 Gogs 的工作目录、启动用户、启动命令写进去,之后就用systemctl start gogs来管理,这才是正经的部署方式。下面实操部分会给出完整的 service 文件内容。
2.4 反向代理:为什么建议套一层 Nginx
Gogs 自己带了一个 Web 服务,默认监听 3000 端口,直接访问http://服务器IP:3000也能用。但直接暴露 3000 端口有几个问题:一是没法用 80/443 标准端口,访问要带端口号,不优雅;二是 HTTPS 配置麻烦,Gogs 自带的 HTTPS 支持需要手动指定证书,不如 Nginx 灵活;三是没法做统一的请求转发、限流、静态资源缓存这些。
所以标准做法是在 Gogs 前面套一层 Nginx,Nginx 监听 80 或 443,把请求转发给本地的 3000 端口。这样对外就是标准的 HTTP/HTTPS 服务,证书在 Nginx 层配置,Gogs 只管处理业务逻辑。这个架构也是绝大多数生产部署的标配。
3. 鲲鹏环境准备与依赖安装
3.1 系统信息确认与基础工具补齐
第一步永远是确认自己在什么环境里。登录鲲鹏服务器后,依次执行下面几条命令,把关键信息摸清楚:
uname -m # 确认架构,应为 aarch64 cat /etc/os-release # 查看系统版本 free -h # 查看内存 df -h # 查看磁盘我这次的环境是 openEuler 系列的国产系统,内存 4G,磁盘 40G。确认架构是aarch64之后,就可以放心走 ARM 原生编译的路线了。
接着补齐基础工具。编译 Go 项目需要 git 来拉源码,需要 gcc 来编译部分 C 依赖(虽然 Gogs 主体是纯 Go,但某些依赖可能用到 cgo),需要 wget 或 curl 来下载文件。用系统自带的包管理器安装:
# openEuler / CentOS 系 sudo dnf install -y git gcc make wget tar # 如果是 Debian / Ubuntu 系 sudo apt update sudo apt install -y git gcc make wget tar这里有个细节要注意:鲲鹏上的包源有时候跟 x86 不完全同步,个别包的版本会旧一些。如果安装过程中提示找不到某个包,先dnf search一下确认包名,国产系统里有些包的名字跟通用发行版不一样。
3.2 安装适配 ARM64 的 Go 环境
Gogs 的编译依赖 Go,而且对 Go 版本有要求,太老的版本编译会报错。去 Go 官方下载页找 linux-arm64 的包,注意一定要选arm64后缀的,别下成amd64。
# 下载 Go(版本号以实际最新稳定版为准) wget https://go.dev/dl/go1.21.5.linux-arm64.tar.gz # 解压到 /usr/local sudo tar -C /usr/local -xzf go1.21.5.linux-arm64.tar.gz # 配置环境变量 echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc echo 'export GOPATH=$HOME/go' >> ~/.bashrc source ~/.bashrc # 验证 go version执行go version应该输出类似go version go1.21.5 linux/arm64的信息,看到linux/arm64就说明装对了。如果输出的是linux/amd64,那说明你下错包了,回去重新下 arm64 版本。
注意:Go 的版本不要选太新的尝鲜版,选一个稳定的正式版。Gogs 的某些依赖对 Go 版本有上限要求,太新的 Go 可能因为语法或标准库变动导致编译失败。我实测 1.20 到 1.21 这个区间比较稳。
3.3 安装并初始化 MySQL
如果决定用 MySQL,先把它装上。鲲鹏上装 MySQL 有两条路:一是用系统源里的 MariaDB(MySQL 的开源分支,兼容性很好),二是去 MySQL 官方下 ARM64 的包。为了省事,我直接用系统源里的 MariaDB。
sudo dnf install -y mariadb-server mariadb sudo systemctl enable --now mariadb sudo systemctl status mariadb服务起来之后,做安全初始化,设置 root 密码、删除匿名用户、禁止 root 远程登录:
sudo mysql_secure_installation然后登录数据库,为 Gogs 创建专用的数据库和用户。这里不要图省事直接用 root,给 Gogs 单独建一个账号,权限只给到它自己的库,这是基本的安全习惯。
CREATE DATABASE gogs CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER 'gogs'@'localhost' IDENTIFIED BY '你的强密码'; GRANT ALL PRIVILEGES ON gogs.* TO 'gogs'@'localhost'; FLUSH PRIVILEGES;字符集一定要用utf8mb4,不要用utf8。utf8在 MySQL 里其实是个残缺的实现,存不了 emoji 和部分生僻字,仓库描述、提交信息里一旦出现这些字符就会报错。utf8mb4才是真正的完整 UTF-8,这是踩过坑才知道的。
3.4 创建专用的运行用户
Gogs 不应该用 root 跑,这是安全底线。创建一个专用的系统用户,把 Gogs 的所有文件都归它管:
sudo useradd -r -m -d /home/git -s /bin/bash git-r表示创建系统用户,-m表示同时创建家目录,-d指定家目录路径,-s指定 shell。之后 Gogs 的仓库数据、配置、日志都放在这个用户的家目录下,权限清晰,出问题也好排查。
4. 从源码编译 Gogs 的完整过程
4.1 拉取源码与切换版本
用 git 把 Gogs 的源码拉下来。为了编译稳定,建议切到一个明确的发布 tag,而不是直接用主分支,主分支的代码随时在变,可能引入未测试的问题。
sudo -u git -H bash cd /home/git git clone --depth 1 --branch v0.13.0 https://github.com/gogs/gogs.git gogs-src cd gogs-src--depth 1表示只拉最近一次提交,能大幅减少下载量,编译场景不需要完整历史。--branch v0.13.0指定版本,你可以换成当时最新的稳定 tag。切到git用户下操作,是为了保证编译产物和后续运行时的文件属主一致,避免权限混乱。
4.2 执行编译并处理依赖
进入源码目录后,直接编译:
go build -o gogs第一次编译会下载一堆依赖,耗时取决于网络。编译过程中如果报错,常见的有两类:一类是依赖下载失败,多半是网络问题,重试或者配置 Go 的模块代理;另一类是 cgo 相关的编译错误,说明缺 C 库,按提示装上对应的-devel包即可。
编译成功后,当前目录下会出现一个gogs可执行文件。用file gogs确认一下它的架构:
file gogs # 应输出:ELF 64-bit LSB executable, ARM aarch64, ...看到ARM aarch64就说明编译出来的确实是鲲鹏能跑的原生二进制,这一步是整个项目里最能体现架构适配价值的地方。
4.3 目录结构与文件部署
编译产物不能随便放,得按 Gogs 的约定组织目录。Gogs 默认会在工作目录下找custom、data、log这些子目录。标准的部署结构是这样的:
cd /home/git mkdir -p gogs/{custom,data,log} cp gogs-src/gogs gogs/ cp -r gogs-src/{templates,public} gogs/gogs:主程序二进制custom:自定义配置,app.ini就放这里data:附件、头像等数据log:日志文件templates:页面模板public:静态资源(CSS、JS、图片)
templates和public必须跟二进制放在一起,否则页面会样式错乱或者直接报模板找不到。这一步很多人会漏,编译完只拷了二进制,结果访问页面一片空白。
4.4 首次启动与安装向导
目录准备好之后,先手动启动一次,走一遍安装向导:
cd /home/git/gogs ./gogs web程序会监听 3000 端口。这时候用浏览器访问http://服务器IP:3000,会看到 Gogs 的安装页面。安装页面要填的东西不少,逐项说明:
- 数据库类型:选 MySQL
- 数据库主机:
127.0.0.1:3306 - 数据库用户:
gogs - 数据库密码:刚才设的密码
- 数据库名称:
gogs - 应用名称:随便起,比如
内部代码托管 - 仓库根目录:
/home/git/gogs-repositories - 运行系统用户:
git - 域名:填你的服务器 IP 或域名
- SSH 端口:默认 22,如果改了 SSH 端口要对应填
- HTTP 端口:
3000 - 应用 URL:
http://你的域名或IP/
填完点安装,Gogs 会自动建表、写配置文件。安装完成后,custom/conf/app.ini就生成了,后续所有配置都改这个文件。
提示:安装向导里有个坑,如果数据库连接信息填错,页面会报错但不会告诉你具体哪里错。这时候去看
log/gogs.log,里面会有详细的数据库连接错误信息,比页面提示有用得多。
5. 用 systemd 托管服务与 Nginx 反向代理
5.1 编写 systemd 服务文件
手动./gogs web只是验证,正式运行必须交给 systemd。创建服务文件:
sudo vim /etc/systemd/system/gogs.service内容如下:
[Unit] Description=Gogs Git Service After=network.target mariadb.service Wants=mariadb.service [Service] Type=simple User=git Group=git WorkingDirectory=/home/git/gogs ExecStart=/home/git/gogs/gogs web Restart=always RestartSec=3 Environment=USER=git HOME=/home/git [Install] WantedBy=multi-user.target几个关键点解释一下。After和Wants保证 MySQL 先起来再启动 Gogs,避免 Gogs 启动时连不上数据库。Restart=always让进程崩溃后自动拉起,RestartSec=3是重启间隔。Environment里指定HOME很重要,Gogs 会用它来定位一些默认路径,不设的话可能找不到家目录。
保存后重载并启动:
sudo systemctl daemon-reload sudo systemctl enable --now gogs sudo systemctl status gogs看到active (running)就说明服务正常跑起来了。
5.2 配置 Nginx 反向代理
先装 Nginx:
sudo dnf install -y nginx sudo systemctl enable --now nginx然后为 Gogs 写一个站点配置:
server { listen 80; server_name git.example.com; location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } client_max_body_size 100m; }proxy_set_header这几行是必须的,否则 Gogs 拿到的客户端 IP 全是127.0.0.1,日志和审计就没意义了。client_max_body_size要调大,默认 1M 太小,push 大仓库或者上传大附件会被 Nginx 直接拦掉,报 413 错误。
配置好后测试并重载:
sudo nginx -t sudo systemctl reload nginx5.3 配置 HTTPS 与 SSH 访问
HTTPS 用 Let's Encrypt 的证书最省事,用 certbot 自动申请和续期。证书配好后,把 Nginx 的 443 端口配置补上,同时把 80 端口的请求重定向到 443。
SSH 访问这块要特别注意。Gogs 的 SSH 克隆走的是系统的 SSH 服务,默认 22 端口。如果服务器上已经有 SSH 服务在跑,Gogs 会复用这个服务,通过git用户的authorized_keys来识别用户。要确保git用户能正常通过 SSH 登录,并且authorized_keys的权限是 600,.ssh目录是 700,权限不对 SSH 会拒绝使用这个密钥。
注意:如果服务器 SSH 端口不是 22,要在
app.ini里把SSH_PORT改成实际端口,否则 Gogs 生成的克隆地址会带错端口,用户复制过去克隆会失败。
6. 常见问题排查与避坑经验
6.1 编译与运行阶段的典型报错
实际操作中遇到的问题,我整理成一张速查表,方便对照排查:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
Exec format error | 用了 amd64 的二进制 | 重新编译 arm64 版本 |
| 编译时依赖下载失败 | 网络问题 | 重试或配置模块代理 |
| 页面样式错乱 | 缺 templates/public | 把这两个目录拷到程序同级 |
| 数据库连接失败 | 账号密码或权限问题 | 查 log/gogs.log 看详细错误 |
| push 报 413 | Nginx 体积限制 | 调大 client_max_body_size |
| SSH 克隆失败 | 端口或密钥权限问题 | 检查 SSH_PORT 和 .ssh 权限 |
| 服务启动即退出 | 工作目录或用户不对 | 检查 service 文件的 WorkingDirectory 和 User |
6.2 权限与属主问题的排查思路
Gogs 的权限问题是最容易让人抓狂的。核心原则就一条:所有 Gogs 相关的文件和目录,属主都必须是运行 Gogs 的那个用户。如果编译时用的是 root,运行时用的是 git,那二进制文件 git 用户可能没权限执行;如果仓库目录属主不对,push 的时候会报权限拒绝。
排查权限问题的通用命令:
ls -la /home/git/gogs ls -la /home/git/gogs-repositories确认属主是git:git。如果不对,用chown -R git:git修正。另外要注意 SELinux,国产系统上 SELinux 默认可能是 enforcing 状态,会拦截 Nginx 转发到 3000 端口的请求。如果 Nginx 报 502 但 Gogs 本身正常,多半是 SELinux 在作祟,可以临时用setenforce 0验证,确认后要么关掉,要么配置对应的策略。
6.3 数据备份与迁移的实操建议
Gogs 的数据分三块:数据库、仓库目录、配置文件。备份的时候三块都要覆盖,缺一不可。
- 数据库:
mysqldump -u gogs -p gogs > gogs_db.sql - 仓库目录:
/home/git/gogs-repositories - 配置和附件:
/home/git/gogs/custom和/home/git/gogs/data
迁移到新机器的时候,先把这三块恢复到位,再改app.ini里的数据库连接信息和域名,最后启动服务。这里有个容易忽略的点:app.ini里有个SECRET_KEY,迁移时不要改它,改了之后所有用户的登录会话都会失效,得重新登录。
提示:仓库目录里存的是裸仓库(bare repository),直接复制就行,不需要额外导出。但复制的时候要用
rsync -a保留权限和时间戳,用cp有时候会丢权限。
7. 我在这套方案里的一些实际体会
鲲鹏上跑 Gogs 这套组合,我用了挺长一段时间,整体感受是稳。ARM 原生编译出来的二进制,跑起来跟 x86 上没什么区别,性能也够用,2 核 4G 的实例带十几个人的小团队完全没压力。真正花时间的不是编译本身,而是那些边边角角的配置:数据库字符集、Nginx 的请求头、SSH 的权限、SELinux 的拦截,这些才是让新手卡住的地方。
如果让我给后来者一句建议,那就是:先把架构确认清楚,再动手。很多人一上来就照着 x86 的教程走,下二进制、配服务,结果第一步就卡在Exec format error,然后开始怀疑人生。其实只要uname -m看一眼,确认是aarch64,后面走源码编译的路子,整个流程就顺了。
另外,数据库这块别偷懒用 SQLite 然后指望它扛团队协作。我见过有人图省事用 SQLite,结果三五个人同时 push 就开始锁表报错,最后还得迁移到 MySQL,反而更折腾。一开始就上 MySQL,省心。
最后再分享一个小技巧:Gogs 的app.ini改完之后,不用重启整个服务,执行systemctl reload gogs或者直接kill -HUP主进程,大部分配置就能热加载生效,省去重启的等待时间。不过数据库连接相关的配置改了还是得重启,这个要注意区分。