Featured image of post 使用 Docker 编译 OpenWrt 软件包,以及编译原版 OpenWrt 可使用的 Passwall

使用 Docker 编译 OpenWrt 软件包,以及编译原版 OpenWrt 可使用的 Passwall

配图: brown wooden ship’s wheel photo – Free Image on Unsplash

最近想在自用的软路由上装某个包,而那个包没有提供预编译的软件包,需要自行编译,而且由于某些原因,还不提供文档,只能自己摸索。由于个人喜欢原版固件多于 Lineol 和 Lean 两位的修改版,而又想尝试 Passwall,所以想尝试一下用原版 OpenWrt 的 SDK 编译 Passwall 看看是否可行。前几次因为种种原因,都失败了,最近这一次比较幸运,成功了。另外发现这次用的办法挺通用的,而且查了一圈,比较少有人这么做,故这里讲一下这次用的方法。

OpenWrt 项目的文档比较健全,好多东西都可以找到文档,但是呢,需要对 OpenWrt 的生态熟悉,且对 OpenWrt 怎么组织文档也要熟悉,不然,也不太好找到合适的资料,有几次就因为没找到,去 https://forum.openwrt.org/ 发贴询问后有人直接发文档链接出来,确实是需要的文档,但是就是没找到,emmm….

不管是编译 OpenWrt 固件还是软件包,第一步都是搭建环境,之前有几次尝试,都是因为这一步没有完成,才放弃的,编译环境依赖的东西很多很杂,不是很好搭,虽然好多东西都是查文档或者 Google 得到,但还是麻烦的,对没有相关经验甚至没有编程经验的人来说,门槛有点过高了。

昨天突然想到能不能用 Docker 呢,如果有做好的编译环境镜像,可以快速搭环境,同时 Docker 跨平台,各个平台上都可以以同样的方式编译软件包了,方便实用。

有没有镜像呢,查了一下,还真有,OpenWrt 官方提供了全架构的 ImageBuilder 和 SDK Docker 镜像,只需要找到对应的架构的镜像使用,就可以了,不需要关心编译环境本身的一些依赖缺失或者版本不对等等问题,只需要偶尔有些软件包需要安装一些系统工具的时候,稍加修补即可。

这里拿最开始想编译的那个包为例,讲一下整个过程

正文开始

安装 Docker

这里推荐 Docker 官方文档, https://docs.docker.com/get-docker/ ,已安装过的,可以略过此节。

找到对应的 SDK 镜像

Docker 镜像的名字和 OpenWrt 的架构直接相关,当前版本支持的架构可以在这里看到 https://downloads.openwrt.org/releases/21.02-SNAPSHOT/targets/ ,比如软路由一般用的 x86/64 和 R2S 的 rockchip/armv8,其对应的镜像就是 openwrtorg/sdk:x86_64-21.02-SNAPSHOTopenwrtorg/sdk:rockchip-armv8-21.02-SNAPSHOT,总之,在 https://hub.docker.com/r/openwrtorg/sdk 这里找就对了,总能找得到的。

编写编译脚本

通常,编译软件包的时候,经常会看到几条命令,类似这样的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 以 ar71xx 平台为例
tar xjf OpenWrt-SDK-ar71xx-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2.tar.bz2
cd OpenWrt-SDK-ar71xx-*
# 添加 feeds
git clone https://github.com/shadowsocks/openwrt-feeds.git package/feeds
# 获取 shadowsocks-libev Makefile
git clone https://github.com/shadowsocks/openwrt-shadowsocks.git package/shadowsocks-libev
# 选择要编译的包 Network -> shadowsocks-libev
make menuconfig
# 开始编译
make package/shadowsocks-libev/compile V=99

总结一下,这几行命令,做了以下几件事

  1. 下载 SDK 并解压
  2. 添加软件源
  3. 添加软件包(上面的例子中,这一步就是添加了要编译的软件包,主要是其 Makefile)
  4. 编译

这次要编译的是 Passwall,源码在这里 https://github.com/xiaorouji/openwrt-passwall

他和 shadowsocks-libev 不同的是,这个项目已经把所有的软件包组织成了 OpenWrt Feeds,编译时只需要添加这个 feed 就可以了,转换成的脚本如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/bin/bash

# 添加包含 passwall 的 feed
echo "src-git passwall https://github.com/xiaorouji/openwrt-passwall" >> feeds.conf.default

# 因为部分软件包需要用到 upx,而官方 SDK 镜像中没有包含,所以自行安装
sudo apt-get update
sudo apt-get install upx -y
cp /usr/bin/upx staging_dir/host/bin
cp /usr/bin/upx-ucl staging_dir/host/bin

# 更新所有 feeds
./scripts/feeds update -a

# 这里因为 Xray 使用的 golang 版本较新而 SDK 中依赖的 golang 版本较旧,所以替换了旧的,用了 21.02 的 golang 配置
pushd feeds/packages/lang
rm -rf golang && svn co https://github.com/openwrt/packages/branches/openwrt-21.02/lang/golang
popd

./scripts/feeds install luci-app-passwall

make defconfig

# 编译
make package/luci-app-passwall/{clean,compile} -j4
# 生成 index 和签名
make package/index

启动容器,执行编译脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
version: '3.5'
services:
  openwrt-packages-build:
    image: "openwrtorg/sdk:x86_64-19.07.7"
    container_name: openwrt-packages-build
    volumes:
     - ./bin:/home/build/openwrt/bin
     - ./key-build:/home/build/openwrt/key-build
     - ./build.sh:/home/build/openwrt/build.sh
    command: "./build.sh"

这是一个 docker-compose 配置,使用 docker-compose 启动即可,命令如下

1
docker-compose up 

完成后即可在 bin 目录中看到对应的软件包

编译过程总结

以上步骤为了讲解,具体使用可以这样

1
2
3
git clone https://github.com/ekkog/DockerOpenWrtPackagesBuilder
cd DockerOpenWrtPackagesBuilder
docker-compose up

包签名

在使用 OpenWrt 过程中,经常会遇到一个东西,叫签名,比如添加软件源后,要手动添加一个 key

1
2
wget https://example.com/xxx.pub
opkg-key add xxx.pub

再比如编译固件或者包时,添加了一个 feed,编译时会遇到签名验证错误 signature check failed 等,这时候就需要添加对应的 key 或者删除签名验证

1
2
sed '/.*check_signature/d' repositories.conf > tmp_repositories.conf
mv tmp_repositories.conf repositories.conf

不验证是不建议的,有这个机制来保证安全的情况下,还是建议使用。

再有就是我们看到,opkg 源都会看到类似这样的结构:

这些东西都是签名这个机制中的东西,

Packages.asc 的 GPG 生成的签名,Packages.sig 是 usign 生成的签名,由私钥生成,而 .pub 文件就是公钥,用来验证私钥生成的签名,确保相应的包文件确实是由信任的人或者机构签发的,说了这么多,我们已经生成了包文件,那么想生成签名怎么办呢?

签名生成

根据 OpenWrt 文档,https://openwrt.org/docs/guide-user/security/release_signatures 和 https://openwrt.org/docs/guide-user/security/keygen,得到的信息,OpenWrt 支持两种签名方式,GNUPG 和 OpenBSD signify,我们通常看到的像下面这样的公钥,是 OpenBSD signify 工具生成的

1
2
untrusted comment: simonsmh dist
RWQSvYTRniY/ZJW7YGI5idGoXjyXrnFWCrLURjEcpDLkVAxoOyyRyutb

OpenWrt 官方提供了 usign 来实现和 OpenBSD signify 一样的功能。

usign 的使用见文档 https://openwrt.org/docs/guide-user/security/keygen#generate_usign_key_pair

生成的密钥对的私钥,要好好保存,这是证明你是合法的发布者的唯一方式,密钥的生成需要私钥,复制一份私钥,命名为 key-build 并放到 SDK 的根目录下,在编译完成后执行 make package/index 命令的时候,签名会自动生成

一个之前的疑惑

这里只讲一个我个人之前迷惑的问题,之前编译 R2S 的软件包的时候遇到签名验证失败问题,没有找到解决方法,就移除了签名验证,另外那次发现 R2S 的 SDK 解压后有一个 keys 文件夹,里面都是看起来是随机生成文件名的文件,内容是 key

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
b5043e70f9a75cde
0b26f36ae0f4106d
2f8b0b98e08306bf
9ef4694208102c43
72a57f2191b211e0
792d9d9b39f180dc
1035ac73cc4e59e3
5151f69420c3f508
b2d571e0880ff617
c10b9afab19ee428
dace9d4df16896bf
dd6de0d06bbd3d85
f94b9dd6febac963

看到这些只知道是根据这些 key 来验证的,但是这名字从哪来的?后来看到 https://openwrt.org/docs/guide-user/security/keygen#add_public_key_to_the_repository 这个文档的时候,知道这个文件名是这个公钥的 fingerprint,所以要这样得到 fingerprint

1
2
$ ./usign -F -p public.key 
72a57f2191b211e0

将生成的公钥重命名成其 fingerprint,再放到 keys 文件夹就可以正常验证签名了。

搭建 OpenWrt 软件源

软件包编译好了,可以拷到系统里进行安装,但是又不太方便,可不可以做软件源,这样只需要 opkg install xxx 就可以安装了, GitHub 就可以提供这样的服务,当然这并不是 GitHub 的本意,只不过是因为 GitHub 提供了对仓库文件的直接访问机制而已

比如 simonsmh/openwrt-dist 仓库下的 readme.md 文件就可以通过下面的链接访问到

https://github.com/simonsmh/openwrt-dist/raw/master/readme.md

opkg 软件源也只不过是需要对一个目录下的所有文档进行访问就可以了,那么把 bin 下 带着这些文件的文件夹初始化为一个 git 仓库并推送到 GitHub 仓库,就可以了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Packages
Packages.gz
Packages.manifest
Packages.sig
dns2socks_2.1-20200218_aarch64_generic.ipk
ipt2socks_1.1.3-1_aarch64_generic.ipk
luci-app-passwall_4-17_all.ipk
microsocks_1.0.2-2_aarch64_generic.ipk
pdnsd-alt_1.2.9b-par-a8e46ccba7b0fa2230d6c42ab6dcd92926f6c21d_aarch64_generic.ipk
shadowsocksr-libev-alt_2.5.6-5_aarch64_generic.ipk
shadowsocksr-libev-server_2.5.6-5_aarch64_generic.ipk
shadowsocksr-libev-ssr-local_2.5.6-5_aarch64_generic.ipk
shadowsocksr-libev_2.5.6-5_aarch64_generic.ipk
simple-obfs-server_0.0.5-4_aarch64_generic.ipk
simple-obfs_0.0.5-4_aarch64_generic.ipk
tcping_0.3-1_aarch64_generic.ipk
trojan-plus_10.0.3-2_aarch64_generic.ipk
v2ray-plugin_4.36.0-1_aarch64_generic.ipk

比如推送到了 xxx/passwall 的 x86/64 分支,那么下面的链接就可以作为一个软件源来使用了

1
https://github.com/xxx/passwall/raw/x86/64

因为可以在链接后面加一个 Packages 就可以访问到软件源的索引,这样它就是一个合法的软件源

这样就可以在 OpenWrt 系统的 /etc/opkg/customfeeds.conf 中添加源

1
echo "src/gz passwall https://github.com/xxx/passwall/raw/x86/64 > /etc/opkg/customfeeds.conf"

或者在编译固件或软件包时,添加到 repositories.conf

1
echo "src/gz passwall https://github.com/xxx/passwall/raw/x86/64 > ./repositories.conf"

当然如果需要验证签名,还需要处理一下,添加对应的 key 文件到系统中。

这里有一个真实的例子:

https://github.com/simonsmh/openwrt-dist/raw/packages/x86/64 是可以作为软件源的,因为 https://github.com/simonsmh/openwrt-dist/raw/packages/x86/64/Packages 是可以访问到的

写在最后

这里通过一个例子将了使用 Docker 编译 OpenWrt 软件包,其他的软件包也可以这样编译,方法是通用的,只不过中间要进行一些小的调整,希望对大家有所帮助。

参考资料

Built with Hugo
主题 StackJimmy 设计