build(train): Train is released as Learn is, with a key of its own, beside it in the F-Droid repository (ADR 0030, D58)
apps/train/release.dart configures the release tool for Train's APK on train.halkyon.eu; its release builds sign with its own key when the tool hands it over. Its F-Droid listing declares NonFreeDep for Polar's SDK and NonFreeNet; the repository's script copies each app's icon. The key's certificate is pinned once the key is made. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
0ec15d2fb2
commit
48951c1e1a
12 changed files with 180 additions and 12 deletions
File diff suppressed because one or more lines are too long
|
|
@ -15,7 +15,6 @@ android {
|
|||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "app.halkyon.train"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
|
|
@ -30,13 +29,46 @@ android {
|
|||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
// The app's own key signs release builds when tool/release.dart hands it over
|
||||
// through the environment, as Learn's does: the keystore's path and the key's
|
||||
// alias, and the password asked for at each release, never written down. Other
|
||||
// release builds are signed with the debug key, so `flutter run --release` works
|
||||
// anywhere; the release tool releases no APK signed otherwise.
|
||||
val releaseKeystore = System.getenv("HALKYON_ANDROID_KEYSTORE")
|
||||
signingConfigs {
|
||||
if (releaseKeystore != null) {
|
||||
create("release") {
|
||||
storeFile = file(releaseKeystore)
|
||||
keyAlias = System.getenv("HALKYON_ANDROID_KEY_ALIAS")
|
||||
// A PKCS #12 keystore has one password, for the store and its key.
|
||||
storePassword = System.getenv("HALKYON_ANDROID_KEYSTORE_PASSWORD")
|
||||
keyPassword = System.getenv("HALKYON_ANDROID_KEYSTORE_PASSWORD")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
signingConfig =
|
||||
signingConfigs.findByName("release") ?: signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
|
||||
// The native libraries go into the APK compressed, as Learn's: one APK serves
|
||||
// every phone, holding them for three architectures, and Android unpacks the
|
||||
// phone's own as it installs the app.
|
||||
packaging {
|
||||
jniLibs {
|
||||
useLegacyPackaging = true
|
||||
}
|
||||
}
|
||||
|
||||
// Leaves out the list of dependencies Gradle would otherwise add to the APK,
|
||||
// encrypted for Google Play alone; F-Droid's and others' scanners flag it.
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
includeInBundle = false
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
|
|
|
|||
35
apps/train/release.dart
Normal file
35
apps/train/release.dart
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// How Halkyon Train is released (`dart run tool/release.dart train`): the
|
||||
// release tool's configuration of it, registered in tool/src/release/apps.dart.
|
||||
import '../../tool/src/release/app_release.dart';
|
||||
|
||||
/// Halkyon Train's releases: the APK alone (Train is Android's: Health
|
||||
/// Connect, the recorder's foreground service, the Polar Loop), connecting
|
||||
/// to train.halkyon.eu, published on code.halkyon.app and in the project's
|
||||
/// F-Droid repository beside Learn.
|
||||
const trainRelease = AppRelease(
|
||||
id: 'train',
|
||||
name: 'Halkyon Train',
|
||||
applicationId: 'app.halkyon.train',
|
||||
directory: 'apps/train',
|
||||
flutterPackage: 'apps/train/halkyon_train_flutter',
|
||||
artifactName: 'halkyon-train',
|
||||
serverUrl: 'https://train.halkyon.eu/',
|
||||
forgejo: 'https://code.halkyon.app',
|
||||
repository: 'project-halkyon/halkyon',
|
||||
// A PKCS #12 keystore of its own outside the repository (also in the
|
||||
// maintainer's password manager), as Learn's: it is the app's identity, and
|
||||
// lost, installed apps can't be updated.
|
||||
android: AndroidSigning(
|
||||
certificateFile: 'tool/release/train-android-certificate.sha256',
|
||||
keystoreVariable: 'HALKYON_TRAIN_ANDROID_KEYSTORE',
|
||||
defaultKeystore: '.local/share/halkyon-train/android-release.p12',
|
||||
aliasVariable: 'HALKYON_TRAIN_ANDROID_KEY_ALIAS',
|
||||
defaultAlias: 'halkyon-train',
|
||||
),
|
||||
fdroid: FdroidListing(
|
||||
script: 'tool/fdroid_repo.sh',
|
||||
listing: 'tool/release/fdroid',
|
||||
indexCertificateFile: 'tool/release/fdroid-index-certificate.sha256',
|
||||
repositoryUrl: 'https://halkyon.app/fdroid/repo',
|
||||
),
|
||||
);
|
||||
|
|
@ -1447,6 +1447,16 @@ Strava. What was found, 27 Oct:
|
|||
when they are written; a reset of the account's keys (ADR 0017) renames every row,
|
||||
as it leaves what was sealed unreadable anyway. Train had no release, so no row
|
||||
needed moving.
|
||||
- **D58 Train's releases** (recommended, the developer's; 30 Oct): as Learn's, from
|
||||
`tool/release.dart train` on the developer's laptop (`apps/train/release.dart`), the
|
||||
APK alone (Train is Android's), connecting to train.halkyon.eu, published on
|
||||
code.halkyon.app and in the project's F-Droid repository beside Learn, signed with a
|
||||
key of Train's own (a PKCS #12 keystore at `~/.local/share/halkyon-train/`, its
|
||||
certificate pinned in `tool/release/train-android-certificate.sha256`): one key for
|
||||
both apps would let either's build pass for the other's update. Its listing
|
||||
(`tool/release/fdroid/metadata/app.halkyon.train.yml`) declares `NonFreeDep` (Polar's
|
||||
SDK, D36) and `NonFreeNet` (Polar's firmware service, the direct way of D40, and the
|
||||
server's SSPL, as Learn's says).
|
||||
|
||||
## Consequences
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'dart:io';
|
|||
import 'package:test/test.dart';
|
||||
|
||||
import '../../apps/learn/release.dart';
|
||||
import '../../apps/train/release.dart';
|
||||
import '../../tool/src/release/apps.dart';
|
||||
import '../../tool/src/release/version.dart';
|
||||
import 'fixtures.dart';
|
||||
|
|
@ -74,4 +75,37 @@ void main() {
|
|||
contains('name: ${learnRelease.snap!.name}\n'),
|
||||
);
|
||||
});
|
||||
|
||||
test("Train's release is registered, sound, names files that exist, and "
|
||||
'has a key of its own', () {
|
||||
expect(releasedApps['train'], same(trainRelease));
|
||||
expect(trainRelease.problems(), isEmpty);
|
||||
for (final path in [
|
||||
trainRelease.pubspec,
|
||||
trainRelease.android.certificateFile,
|
||||
...trainRelease.releaseFiles,
|
||||
]) {
|
||||
expect(
|
||||
FileSystemEntity.typeSync(path),
|
||||
isNot(FileSystemEntityType.notFound),
|
||||
reason: path,
|
||||
);
|
||||
}
|
||||
expect(trainRelease.snap, isNull);
|
||||
expect(
|
||||
trainRelease.android.certificateFile,
|
||||
isNot(learnRelease.android.certificateFile),
|
||||
);
|
||||
expect(
|
||||
trainRelease.android.defaultKeystore,
|
||||
isNot(learnRelease.android.defaultKeystore),
|
||||
);
|
||||
expect(
|
||||
File(
|
||||
'${trainRelease.fdroid!.listing}/metadata/'
|
||||
'${trainRelease.applicationId}.yml',
|
||||
).readAsStringSync(),
|
||||
contains('NonFreeDep'),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,12 +82,16 @@ mkdir -p "$working/repo"
|
|||
cp -r tool/release/fdroid/. "$working/"
|
||||
# fdroid refuses a config.yml others can read.
|
||||
chmod 600 "$working/config.yml"
|
||||
# Learn's own icon in the F-Droid app, the snap's (the repository's is the
|
||||
# kingfisher, halkyon.png in tool/release/fdroid/): fdroid can't find the launcher
|
||||
# icon in a release APK, whose build shortens the resources' paths (res/9w.png
|
||||
# rather than res/mipmap-hdpi/ic_launcher.png; fdroidserver issue 885).
|
||||
mkdir -p "$working/metadata/app.halkyon.learn/en-US"
|
||||
cp snap/gui/halkyon-learn.png "$working/metadata/app.halkyon.learn/en-US/icon.png"
|
||||
# Each app's own icon in the F-Droid app (the repository's is the kingfisher,
|
||||
# halkyon.png in tool/release/fdroid/): Learn's the snap's, Train's its launcher's
|
||||
# source. fdroid can't find the launcher icon in a release APK, whose build shortens
|
||||
# the resources' paths (res/9w.png rather than res/mipmap-hdpi/ic_launcher.png;
|
||||
# fdroidserver issue 885).
|
||||
for icon in app.halkyon.learn:snap/gui/halkyon-learn.png \
|
||||
app.halkyon.train:apps/train/halkyon_train_flutter/assets/icon/halkyon_train_icon.png; do
|
||||
mkdir -p "$working/metadata/${icon%%:*}/en-US"
|
||||
cp "${icon#*:}" "$working/metadata/${icon%%:*}/en-US/icon.png"
|
||||
done
|
||||
if [ -n "$apk" ]; then
|
||||
[ -f "$apk" ] || fail "No APK at $apk."
|
||||
cp "$apk" "$working/repo/"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
# The anti-features the repository's apps declare, as the main F-Droid
|
||||
# repository defines them: names, descriptions and icons from fdroiddata
|
||||
# (https://gitlab.com/fdroid/fdroiddata, config/antiFeatures.yml and the icons
|
||||
# beside it). The two icons are F-Droid's, under the GNU AGPL v3, fdroiddata's
|
||||
# beside it). The icons are F-Droid's, under the GNU AGPL v3, fdroiddata's
|
||||
# licence. fdroid copies them into the repository's icons.
|
||||
NonFreeDep:
|
||||
name: Non-Free Dependencies
|
||||
description: This app depends on other non-free apps
|
||||
icon: ic_antifeature_nonfreedep.png
|
||||
NonFreeNet:
|
||||
name: Non-Free Network Services
|
||||
description: This app promotes or depends entirely on a non-free network service
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
# The categories the repository's apps are listed in.
|
||||
Education:
|
||||
name: Education
|
||||
Sports & Health:
|
||||
name: Sports & Health
|
||||
|
|
|
|||
BIN
tool/release/fdroid/config/ic_antifeature_nonfreedep.png
Normal file
BIN
tool/release/fdroid/config/ic_antifeature_nonfreedep.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
40
tool/release/fdroid/metadata/app.halkyon.train.yml
Normal file
40
tool/release/fdroid/metadata/app.halkyon.train.yml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# Halkyon Train's listing in the F-Droid repository, and its anti-features with their
|
||||
# reasons (Halkyon Train's ADR 0030, D36).
|
||||
Categories:
|
||||
- Sports & Health
|
||||
License: AGPL-3.0-or-later
|
||||
AuthorName: Halkyon
|
||||
AuthorEmail: support@halkyon.app
|
||||
WebSite: https://halkyon.app/train
|
||||
SourceCode: https://code.halkyon.app/project-halkyon/halkyon
|
||||
|
||||
AntiFeatures:
|
||||
NonFreeDep: It reads the Polar Loop through Polar's BLE SDK, which isn't free
|
||||
software; the app's licence carries a permission to link it.
|
||||
NonFreeNet: The Polar Loop's firmware comes from Polar's firmware service, and the
|
||||
server's licence (SSPL) isn't a free licence by F-Droid's definition.
|
||||
|
||||
Name: Halkyon Train
|
||||
Summary: Record runs, rides, walks and rows, sealed and synced between your devices
|
||||
Description: |-
|
||||
Halkyon Train records your runs, walks, hikes, rides and rows: the track by GPS,
|
||||
heart rate from a chest strap or the Polar Loop, a bike's speed, cadence and
|
||||
power sensors, and a rowing machine's strokes. It pauses by itself when you
|
||||
stop, announces each kilometre, and follows interval plans and routes you plan
|
||||
on the map, turn by turn.
|
||||
|
||||
Music from your Jellyfin server plays in time with your steps. Workouts go to
|
||||
Health Connect and out as GPX, TCX or FIT.
|
||||
|
||||
The Polar Loop's days and nights (steps, sleep, Nightly Recharge, heart rate
|
||||
around the clock) are read from the band on the phone, never through Polar's
|
||||
cloud.
|
||||
|
||||
Everything is sealed on your phone with your account's keys before it reaches
|
||||
the instance, which can't read it. You sign in with SimpleLogin, by Proton, or
|
||||
with an email address, a password and an authenticator app. It connects to
|
||||
train.halkyon.eu, the project's instance, or to any other: everything an
|
||||
instance needs is in the repository, to run your own. The app is AGPL-3.0 and
|
||||
the server SSPL-1.0, so all of its code is public.
|
||||
|
||||
Questions and reports: support@halkyon.app
|
||||
2
tool/release/train-android-certificate.sha256
Normal file
2
tool/release/train-android-certificate.sha256
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# The SHA-256 of the certificate that signs Halkyon Train's Android releases (key alias
|
||||
# halkyon-train), as keytool prints it. tool/release.dart releases no APK signed otherwise.
|
||||
|
|
@ -2,9 +2,13 @@
|
|||
library;
|
||||
|
||||
import '../../../apps/learn/release.dart';
|
||||
import '../../../apps/train/release.dart';
|
||||
import 'app_release.dart';
|
||||
|
||||
/// Every app with a release configuration, by the id the tool takes. A new
|
||||
/// app's configuration lives beside it (`apps/<id>/release.dart`) and is
|
||||
/// added here.
|
||||
final releasedApps = <String, AppRelease>{learnRelease.id: learnRelease};
|
||||
final releasedApps = <String, AppRelease>{
|
||||
learnRelease.id: learnRelease,
|
||||
trainRelease.id: trainRelease,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue