最近开发了一款好几年前就想做的应用,一个管理 2FA(两步验证)令牌的工具 – Flauth ,基于 Flutter 开发,支持全平台。目前已经发布到 F-Droid 上,欢迎大家安装使用。
在如今 Agent 盛行的年代,开发这个软件本身没什么难度,大概 80% 的代码都是 Vibe 的,Dart 也是现学现卖,剩下的 20% 着重花在了设计与测试上,之所以想自己开发,主要是市面上没有同时满足以下三点需求的 App:
- 支持多平台,我目前主要用到 macOS、Linux、Android
- 开源,最起码要支持数据的导入、导出,并且是开放的格式,没有 Vendor Lock-in 的风险
- 支持自定义备份,毕竟数据在自己手上才是最安全的
Flauth 就是上述需求的产物,技术细节这里不多赘述,感兴趣的读者可以 Flauth 相关文档:
在小范围内测之后,功能已经完备,我也把 Authy 中的十几个令牌 一个个 (鄙视脸!)迁移过来,就准备发布到 F-Droid 上,之所以选择这个平台,主要是因为它是开源软件的集中地,用户群体也比较符合我的预期。但由于是第一次使用 F-Droid 发布应用,过程还是遇到了一些问题,花了一些时间才弄明白。这篇博客文章就来分享一下我的发布经验,希望能帮助到有类似需求的读者。
签名
在发布之前,需要对 apk 文件签名,这是 Android 应用发布的基本要求。出于安全考虑,应用开发者也需要签名,以防止应用被篡改。Sign the app 是 Flutter 的文档介绍。需要修改 build.gradle.kts 文件,添加签名配置:
android {
...
signingConfigs {
create("release") {
keyAlias = System.getenv("KEY_ALIAS")
keyPassword = System.getenv("KEY_PASSWORD")
storeFile = file(System.getenv("KEYSTORE_PATH"))
storePassword = System.getenv("KEYSTORE_PASSWORD")
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
...
}
}
}
Android 签名指南 是 Flauth 中的签名流程,供大家参考。还有一点需要注意,AGP 默认会插入一个额外的签名块(extra signing block),
叫做 Dependency metadata,用于存储一些依赖信息。但 F-Droid 要求应用不能包含这个签名块,否则会报错,需要在 build.gradle.kts 中添加如下配置::
android {
...
dependenciesInfo {
// Disables dependency metadata when building APKs.
includeInApk = false
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}
}
这样构建出来的 apk 文件就不会包含这个签名块了,这里有具体的原因解释。
文档
- Submitting to F-Droid Quick Start Guide
- Build Metadata Reference
- https://gitlab.com/fdroid/fdroiddata/blob/master/CONTRIBUTING.md
- https://gitlab.com/fdroid/wiki/-/wikis/Tips-for-fdroiddata-contributors/FOSS-libs
上面这些文档是官方提供的,涵盖了从准备元信息、编写 yaml 描述文件,到构建与提交的全过程,建议先粗略浏览一遍,有个整体流程的概念,后续遇到问题时再回头细读。
元信息准备
为了在 F-Droid 上正确显示应用的信息,需要准备一些元信息,包括:图标、截图、描述文件、ChangeLog 等,这些需要放在项目中,目录结构如下:
$ tree metadata/
metadata/
└──en-US
├──full_description.txt
├──short_description.txt
├──title.txt
├──changelogs
│ └──2001.txt
└──images
├──icon.png
└──phoneScreenshots
├──1.png
├──2.png
└──3.png
full_description.txt:应用的完整描述short_description.txt:应用的简短描述title.txt:应用的标题changelogs/2001.txt:版本代码(versionCode)2001 的更新日志images/icon.png:应用图标,512×512 像素images/phoneScreenshots/:应用截图,建议提供多张,分辨率无要求,但建议至少 1080×1920 像素
yaml 描述文件
F-Droid 使用 yaml 文件来描述应用的构建信息,文件名一般为包名加上 .yml 后缀,比如我的应用包名是 net.liujiacai.flauth ,那么文件名就是 net.liujiacai.flauth.yml。这个文件需要放在 F-Droid 的数据仓库中,一般不放在应用代码仓库中。templates 目录下有一些模板,可以参考。 #31694 是我提交的 MR,历时一周合入。
Categories:
- Password & 2FA
- Security
License: MIT
AuthorName: Jiacai Liu
AuthorEmail: [email protected]
WebSite: https://jiacai2050.github.io/flauth/
SourceCode: https://github.com/jiacai2050/flauth
IssueTracker: https://github.com/jiacai2050/flauth/issues
AutoName: Flauth
RepoType: git
Repo: https://github.com/jiacai2050/flauth
Binaries: https://github.com/jiacai2050/flauth/releases/download/v%v/flauth-android-arm64-v8a-v%v.apk
Builds:
- versionName: 1.0.0
versionCode: 2001
commit: 979f69e489430da468776e61ecc7f16e53dad1fc
sudo:
- mkdir -p /home/runner/work/flauth
- chown -R vagrant /home/runner/
output: build/app/outputs/flutter-apk/app-arm64-v8a-release.apk
srclibs:
- flutter@stable
rm:
- ios
- linux
- macos
- windows
- test
prebuild:
- flutterVersion=$(sed -n -E 's/.*flutter-version:[[:space:]]*"(.*)"/\1/p' .github/workflows/release.yml)
- '[[ $flutterVersion ]]'
- git -C $$flutter$$ checkout -f $flutterVersion
- export repo=/home/runner/work/flauth/flauth
- cd ..
- mv net.liujiacai.flauth $repo
- pushd $repo
- export PUB_CACHE=$(pwd)/.pub-cache
- $$flutter$$/bin/flutter config --no-analytics
- $$flutter$$/bin/flutter packages pub get
- popd
- mv $repo net.liujiacai.flauth
scandelete:
- .pub-cache
build:
- export repo=/home/runner/work/flauth/flauth
- cd ..
- mv net.liujiacai.flauth $repo
- pushd $repo
- export PUB_CACHE=$(pwd)/.pub-cache
- $$flutter$$/bin/flutter build apk --release --split-per-abi --target-platform="android-arm64"
- popd
- mv $repo net.liujiacai.flauth
ndk: r28c
AllowedAPKSigningKeys: 6f089f1171a04315ff4905956c345e7fcb82a5232387e648c1c6e2af5344b22f
AutoUpdateMode: Version
UpdateCheckMode: Tags ^v[1-9].*
VercodeOperation:
- '%c + 2000'
UpdateCheckData: pubspec.yaml|version:\s.+\+(\d+)|.|version:\s(.+)\+
CurrentVersion: 1.0.0
CurrentVersionCode: 2001
Categories:分类,可以参考 官方文档 进行选择,我选择了Password & 2FA和Security。License:许可证类型,我选择了MIT。AuthorName、AuthorEmail、WebSite、SourceCode、IssueTracker:这些都是应用的基本信息,按实际填写即可。AutoName:应用的名称。RepoType和Repo:代码仓库类型和地址,我的应用托管在 GitHub 上,所以填写git和仓库地址。Binaries:预编译的二进制文件下载地址,我这里填写了 Android 版本的 apk 下载地址,%v会被替换为版本号,%c会被替换为版本号代码。这个需要配合后面的AllowedAPKSigningKeys一起使用,确保 apk 文件的签名正确。-
Builds:构建信息列表,每个版本对应一个条目,包含以下字段:versionName:版本名称versionCode:版本代码commit:对应的 git 提交哈希sudo:构建前需要执行的命令列表,这里我创建了工作目录并修改了权限。output:构建产物路径srclibs:需要使用的源代码库,这里我指定了 Flutter 的稳定版分支。rm:构建前需要删除的目录列表,我删除了不需要的平台目录。prebuild:构建前需要执行的命令列表,这里我设置了 Flutter 版本,获取依赖等。scandelete:扫描后需要删除的目录列表,我删除了.pub-cache目录。build:构建命令列表,这里我执行了 Flutter 的构建命令,需要注意的是,为了满足 reproducible build 的构建要求,这里对齐了 GitHub Actions 中的路径,
因为 Binaries 处指定的二进制就是从那里构建出来的。ndk:指定使用的 NDK 版本,这里我选择了r28c。
-
AllowedAPKSigningKeys:允许的 APK 签名密钥列表,我填写了我的签名公钥的 SHA-256 指纹。可以通过以下命令获取 apk 文件的签名指纹:apksigner verify --print-certs flauth-android-arm64-v8a-v1.0.0.apkapksigner工具包含在 Android SDK 的 build-tools 目录下,可以参考 官方文档 进行使用。macOS 上的路径是:~/Library/Android/sdk/build-tools/36.1.0/apksigner AutoUpdateMode:自动更新模式,我选择了Version。UpdateCheckMode:更新检查模式,我选择了基于标签的检查,正则表达式为^v[1-9].*,表示匹配以v开头,后面跟数字的标签,比如v1.0.0、v2.1.3等。VercodeOperation:版本代码操作,这里之所以需要这么做,是因为构建时使用了split-per-abi,会生成多个 apk 文件,好处是最终的 apk 体积变小。每个 apk 文件的版本代码需要不同,Flutter 会在 pubspec.yaml 中 versionCode 的基础上加 2000,比如是 1, 那么 arm64 架构就是 2001,其他架构可能会是 2002,根据实际调整就好,但 Android 手机一般现在都只需要 arm64 架构就够了。UpdateCheckData:更新检查数据,我指定了从pubspec.yaml文件中提取版本号和版本代码的正则表达式。CurrentVersion和CurrentVersionCode:当前版本号和版本代码,fdroid checkupdates --allow-dirty com.example命令会根据仓库中的最新版本自动更新这两个字段。
可重复构建
可重复构建(Reproducible Builds)是 Fdroid 目前唯一接受的方式,一般来说新的应用都需要采用这种方式。
其目标是实现任何人都能反复运行的软件构建流程,并能够重现与原始版本完全相同的 APK 文件,因此上面的 yml 文件中的构建方式和 Flauth 在 GitHub Action 中构建的方式完全一致。
在进行 1.1.0 更新时,遇到这么一个新的错误:
Binary files /tmp/tmpyxatyivj/tmp_binaries_net.liujiacai.flauth_2002.binary/content/lib/arm64-v8a/libdartjni.so and /tmp/tmpyxatyivj/_tmp_tmpyxatyivj_sigcp_net.liujiacai.flauth_2002/content/lib/arm64-v8a/libdartjni.so differ
这是由于在不同的构建机器上,NDK 的路径以及项目(及其 jni 目录)的路径各不相同。这会导致调试符号中的源文件路径不一致,进而使链接器生成不同的 build-id(即使在去除调试信息后,该 ID 仍会被保留)。
一种可行的解决方案是向链接器传递 --build-id=none 参数,这将完全禁用 build-id 的生成。相关提交:
构建与提交
准备好上述文件之后,就可以开始构建与提交了。F-Droid 提供了一个 CI 环境,可以在其中进行构建和测试。具体的构建命令可以参考上面的 yaml 文件中的 prebuild 和 build 部分。
git clone --depth=1 https://gitlab.com/fdroid/fdroidserver ~/fdroidserver
sudo sh -c 'apt-get update &&apt-get install -y docker.io'
sudo docker run --rm -itu vagrant --entrypoint /bin/bash \
-v ~/fdroiddata:/build:z \
-v ~/fdroidserver:/home/vagrant/fdroidserver:Z \
registry.gitlab.com/fdroid/fdroidserver:buildserver
在容器里面,可以运行以下命令进行构建:
. /etc/profile
export PATH="$fdroidserver:$PATH" PYTHONPATH="$fdroidserver"
export JAVA_HOME=$(java -XshowSettings:properties -version 2>&1 > /dev/null | grep 'java.home' | awk -F'=' '{print $2}' | tr -d ' ')
cd /build
fdroid readmeta
fdroid rewritemeta net.liujiacai.flauth
fdroid checkupdates --allow-dirty net.liujiacai.flauth
fdroid lint net.liujiacai.flauth
fdroid build net.liujiacai.flauth
如果中途报错,可以根据日志进行修复,然后重新运行上面的命令即可。用 -v 参数可以看到更详细的日志。如果一切顺利,就可以给 F-Droid 提交 MR 了,这需要在 F-Droid 数据仓库 中创建一个 MR(在 gitlab 上),等待审核通过即可。
审核通过后,一般过一两天应用就会上线了,这时就可以通过 F-Droid 客户端进行安装和更新。











暂无评论内容