最近开发了一款好几年前就想做的应用,一个管理 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 文件,添加签名配置:
1android {
2 ...
3 signingConfigs {
4 create("release") {
5 keyAlias = System.getenv("KEY_ALIAS")
6 keyPassword = System.getenv("KEY_PASSWORD")
7 storeFile = file(System.getenv("KEYSTORE_PATH"))
8 storePassword = System.getenv("KEYSTORE_PASSWORD")
9 }
10 }
11 buildTypes {
12 release {
13 signingConfig = signingConfigs.getByName("release")
14 ...
15 }
16 }
17}Android 签名指南 是 Flauth 中的签名流程,供大家参考。还有一点需要注意,AGP 默认会插入一个额外的签名块(extra signing block), 叫做 Dependency metadata,用于存储一些依赖信息。但 F-Droid 要求应用不能包含这个签名块,否则会报错,需要在 build.gradle.kts 中添加如下配置::
1android {
2 ...
3 dependenciesInfo {
4 // Disables dependency metadata when building APKs.
5 includeInApk = false
6 // Disables dependency metadata when building Android App Bundles.
7 includeInBundle = false
8 }
9}这样构建出来的 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:应用图标,512x512 像素images/phoneScreenshots/:应用截图,建议提供多张,分辨率无要求,但建议至少 1080x1920 像素
yaml 描述文件
F-Droid 使用 yaml 文件来描述应用的构建信息,文件名一般为包名加上 .yml 后缀,比如我的应用包名是 net.liujiacai.flauth ,那么文件名就是 net.liujiacai.flauth.yml。这个文件需要放在 F-Droid 的数据仓库中,一般不放在应用代码仓库中。templates 目录下有一些模板,可以参考。 #31694 是我提交的 MR,历时一周合入。
1Categories:
2 - Password & 2FA
3 - Security
4License: MIT
5AuthorName: Jiacai Liu
6AuthorEmail: [email protected]
7WebSite: https://jiacai2050.github.io/flauth/
8SourceCode: https://github.com/jiacai2050/flauth
9IssueTracker: https://github.com/jiacai2050/flauth/issues
10
11AutoName: Flauth
12
13RepoType: git
14Repo: https://github.com/jiacai2050/flauth
15Binaries: https://github.com/jiacai2050/flauth/releases/download/v%v/flauth-android-arm64-v8a-v%v.apk
16
17Builds:
18 - versionName: 1.0.0
19 versionCode: 2001
20 commit: 979f69e489430da468776e61ecc7f16e53dad1fc
21 sudo:
22 - mkdir -p /home/runner/work/flauth
23 - chown -R vagrant /home/runner/
24 output: build/app/outputs/flutter-apk/app-arm64-v8a-release.apk
25 srclibs:
26 - flutter@stable
27 rm:
28 - ios
29 - linux
30 - macos
31 - windows
32 - test
33 prebuild:
34 - flutterVersion=$(sed -n -E 's/.*flutter-version:[[:space:]]*"(.*)"/\1/p' .github/workflows/release.yml)
35 - '[[ $flutterVersion ]]'
36 - git -C $$flutter$$ checkout -f $flutterVersion
37 - export repo=/home/runner/work/flauth/flauth
38 - cd ..
39 - mv net.liujiacai.flauth $repo
40 - pushd $repo
41 - export PUB_CACHE=$(pwd)/.pub-cache
42 - $$flutter$$/bin/flutter config --no-analytics
43 - $$flutter$$/bin/flutter packages pub get
44 - popd
45 - mv $repo net.liujiacai.flauth
46 scandelete:
47 - .pub-cache
48 build:
49 - export repo=/home/runner/work/flauth/flauth
50 - cd ..
51 - mv net.liujiacai.flauth $repo
52 - pushd $repo
53 - export PUB_CACHE=$(pwd)/.pub-cache
54 - $$flutter$$/bin/flutter build apk --release --split-per-abi --target-platform="android-arm64"
55 - popd
56 - mv $repo net.liujiacai.flauth
57 ndk: r28c
58
59AllowedAPKSigningKeys: 6f089f1171a04315ff4905956c345e7fcb82a5232387e648c1c6e2af5344b22f
60
61AutoUpdateMode: Version
62UpdateCheckMode: Tags ^v[1-9].*
63VercodeOperation:
64 - '%c + 2000'
65UpdateCheckData: pubspec.yaml|version:\s.+\+(\d+)|.|version:\s(.+)\+
66CurrentVersion: 1.0.0
67CurrentVersionCode: 2001Categories:分类,可以参考 官方文档 进行选择,我选择了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 文件的签名指纹:1apksigner verify --print-certs flauth-android-arm64-v8a-v1.0.0.apkapksigner工具包含在 Android SDK 的 build-tools 目录下,可以参考 官方文档 进行使用。macOS 上的路径是:1~/Library/Android/sdk/build-tools/36.1.0/apksignerAutoUpdateMode:自动更新模式,我选择了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命令会根据仓库中的最新版本自动更新这两个字段。
构建与提交
准备好上述文件之后,就可以开始构建与提交了。F-Droid 提供了一个 CI 环境,可以在其中进行构建和测试。具体的构建命令可以参考上面的 yaml 文件中的 prebuild 和 build 部分。
1git clone --depth=1 https://gitlab.com/fdroid/fdroidserver ~/fdroidserver
2sudo sh -c 'apt-get update &&apt-get install -y docker.io'
3sudo docker run --rm -itu vagrant --entrypoint /bin/bash \
4 -v ~/fdroiddata:/build:z \
5 -v ~/fdroidserver:/home/vagrant/fdroidserver:Z \
6 registry.gitlab.com/fdroid/fdroidserver:buildserver在容器里面,可以运行以下命令进行构建:
1. /etc/profile
2export PATH="$fdroidserver:$PATH" PYTHONPATH="$fdroidserver"
3export JAVA_HOME=$(java -XshowSettings:properties -version 2>&1 > /dev/null | grep 'java.home' | awk -F'=' '{print $2}' | tr -d ' ')
4cd /build
5fdroid readmeta
6fdroid rewritemeta net.liujiacai.flauth
7fdroid checkupdates --allow-dirty net.liujiacai.flauth
8fdroid lint net.liujiacai.flauth
9fdroid build net.liujiacai.flauth如果中途报错,可以根据日志进行修复,然后重新运行上面的命令即可。用 -v 参数可以看到更详细的日志。如果一切顺利,就可以给 F-Droid 提交 MR 了,这需要在 F-Droid 数据仓库 中创建一个 MR(在 gitlab 上),等待审核通过即可。
审核通过后,一般过一两天应用就会上线了,这时就可以通过 F-Droid 客户端进行安装和更新。