feat: an F-Droid repository at learn.halkyon.app
All checks were successful
CI / check (push) Successful in 9m16s
CI / deploy (push) Has been skipped

Releases also publish the APK in the project's own F-Droid repository,
https://learn.halkyon.app/fdroid/repo. tool/fdroid_repo.sh adds the APK,
signs the indexes with the repository's own key and uploads them to
ramiel as the user fdroid, whose key can only write into the folder
Caddy serves (rrsync), the APKs first and the indexes last.
tool/release.sh checks that key and its password before building and
publishes after the Forgejo release.

- tool/release/fdroid/ holds the repository's settings (Halkyon, the
  snap's icon) and the app's listing (the snap's text), with the
  anti-features TetheredNet (learn.halkyon.eu only, for now) and
  NonFreeNet (YouTube, the server's SSPL) declared with their reasons,
  in F-Droid's own definitions and with its icons (fdroiddata, AGPL v3).
- The index key's certificate is pinned: a new key would make the F-Droid
  app refuse the repository.
- fdroidserver v2.4.5, since Ubuntu's v2.2.1 knows neither TetheredNet
  nor reasons.

README and AGENTS.md describe it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-05 20:25:02 +02:00
commit ae2496335d
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
11 changed files with 209 additions and 9 deletions

View file

@ -120,7 +120,7 @@ A spaced-repetition learning app: studysets of flashcards, reviewed on an FSRS s
- **CI** (`.forgejo/workflows/ci.yaml`) runs `tool/check.sh` on every push, on the Forgejo runner on sahaquiel (`deploy/runner/`). Jobs run directly on the host as the unprivileged user `runner`, never in containers: Docker access would be root on the production server. The runner's systemd unit keeps jobs away from production (no `/home`, no Docker socket, Docker's container networks unreachable, memory and CPU capped); keep it that way when changing it. `runner` has user ID 61000 and must never share one with a container's processes (PostgreSQL and the server run as 999, not remapped): the same user ID can read their environment and files. The job's first step (*Sandbox*) fails if any of this leaks. Flutter is pinned in the workflow (`FLUTTER_VERSION`): move it together with the local one. The server tests there get throwaway passwords from the environment (`SERVERPOD_…`, new for every job), since `passwords.yaml` is never committed; without them they exit with code 1 and no message (Serverpod's own template does the same). CI also runs `serverpod generate` and fails if the committed generated code differs from its output. CI runs on pushes only: a pull request from a fork would run its code on the production server. - **CI** (`.forgejo/workflows/ci.yaml`) runs `tool/check.sh` on every push, on the Forgejo runner on sahaquiel (`deploy/runner/`). Jobs run directly on the host as the unprivileged user `runner`, never in containers: Docker access would be root on the production server. The runner's systemd unit keeps jobs away from production (no `/home`, no Docker socket, Docker's container networks unreachable, memory and CPU capped); keep it that way when changing it. `runner` has user ID 61000 and must never share one with a container's processes (PostgreSQL and the server run as 999, not remapped): the same user ID can read their environment and files. The job's first step (*Sandbox*) fails if any of this leaks. Flutter is pinned in the workflow (`FLUTTER_VERSION`): move it together with the local one. The server tests there get throwaway passwords from the environment (`SERVERPOD_…`, new for every job), since `passwords.yaml` is never committed; without them they exit with code 1 and no message (Serverpod's own template does the same). CI also runs `serverpod generate` and fails if the committed generated code differs from its output. CI runs on pushes only: a pull request from a fork would run its code on the production server.
- **A green `main` deploys learn.halkyon.eu** through `deploy/redeploy.sh`, the only command the runner's key may run as `deploy` (pinned in `authorized_keys`, from sahaquiel itself only). It deploys the commit CI checked while it is still the tip, as a fast-forward, only if every new commit is signed by a key in `deploy/allowed-signers` (the deployed copy decides), after dumping the database (the last 3 dumps stay in `~deploy/backups`: the way back from a deploy, not backups), applies the migrations in a run of their own before switching (`--role=maintenance --apply-migrations`: a failing migration stops the deploy while the old server keeps serving, so migrations must keep the old server working until the switch), and fails unless the new server answers. A failed deploy leaves the checkout at the deployed commit; run the workflow again to retry. By hand: `ssh sahaquiel-deploy halkyon-learn/deploy/redeploy.sh <commit>`. Never `docker compose down -v` there. - **A green `main` deploys learn.halkyon.eu** through `deploy/redeploy.sh`, the only command the runner's key may run as `deploy` (pinned in `authorized_keys`, from sahaquiel itself only). It deploys the commit CI checked while it is still the tip, as a fast-forward, only if every new commit is signed by a key in `deploy/allowed-signers` (the deployed copy decides), after dumping the database (the last 3 dumps stay in `~deploy/backups`: the way back from a deploy, not backups), applies the migrations in a run of their own before switching (`--role=maintenance --apply-migrations`: a failing migration stops the deploy while the old server keeps serving, so migrations must keep the old server working until the switch), and fails unless the new server answers. A failed deploy leaves the checkout at the deployed commit; run the workflow again to retry. By hand: `ssh sahaquiel-deploy halkyon-learn/deploy/redeploy.sh <commit>`. Never `docker compose down -v` there.
- **Backups** (`deploy/backups.md`): every hour, `deploy/backup.sh` (a systemd timer, as `deploy`) dumps the database, which holds people's files too, and restic stores it in a Hetzner Storage Box (24 hourly, 7 daily, 4 weekly, 3 monthly); the box's own snapshots protect it from a taken-over server. A restore takes the instance back up to an hour, and people who started since lose what their device held: their app no longer finds its session, starts a new anonymous account and deletes the old folder. So a change that makes devices lose their session, or keeps deleted people's data longer, changes what a restore costs; say so in `deploy/backups.md`. - **Backups** (`deploy/backups.md`): every hour, `deploy/backup.sh` (a systemd timer, as `deploy`) dumps the database, which holds people's files too, and restic stores it in a Hetzner Storage Box (24 hourly, 7 daily, 4 weekly, 3 monthly); the box's own snapshots protect it from a taken-over server. A restore takes the instance back up to an hour, and people who started since lose what their device held: their app no longer finds its session, starts a new anonymous account and deletes the old folder. So a change that makes devices lose their session, or keeps deleted people's data longer, changes what a restore costs; say so in `deploy/backups.md`.
- **Releases** are the project's, not an instance's: the release, the Snap Store listing and the website point to learn.halkyon.app; the builds connect to learn.halkyon.eu, the project's instance, until the app lets people choose one. They come from a developer's laptop, never from CI: `tool/release.sh vX.Y.Z` on `main` as pushed, with the tag signed by a key in `deploy/allowed-signers` and pushed, and the build number raised. It builds one universal APK from a checkout of the tag, signed with the app's key, and the snap (`snap/snapcraft.yaml`, the committed HEAD, built in LXD), both with `SERVER_URL=https://learn.halkyon.eu/`; signs `SHA256SUMS` with GPG; creates the release on code.halkyon.app through Forgejo's API; and uploads the snap to the Store's edge channel. The key's password and the Forgejo token are asked for at each run and never stored. The app's key is a PKCS #12 keystore outside the repository (also in the maintainer's password manager); there is no Google Play, so it is the app's identity: lost, installed apps can't be updated. Its certificate's SHA-256 is in `tool/release/android-certificate.sha256`, and the script releases no APK signed otherwise; other release builds fall back to the debug key (`android/app/build.gradle.kts`), so `flutter run --release` works anywhere. - **Releases** are the project's, not an instance's: the release, the Snap Store listing and the website point to learn.halkyon.app; the builds connect to learn.halkyon.eu, the project's instance, until the app lets people choose one. They come from a developer's laptop, never from CI: `tool/release.sh vX.Y.Z` on `main` as pushed, with the tag signed by a key in `deploy/allowed-signers` and pushed, and the build number raised. It builds one universal APK from a checkout of the tag, signed with the app's key, and the snap (`snap/snapcraft.yaml`, the committed HEAD, built in LXD), both with `SERVER_URL=https://learn.halkyon.eu/`; signs `SHA256SUMS` with GPG; creates the release on code.halkyon.app through Forgejo's API; and uploads the snap to the Store's edge channel. The key's password and the Forgejo token are asked for at each run and never stored. The app's key is a PKCS #12 keystore outside the repository (also in the maintainer's password manager); there is no Google Play, so it is the app's identity: lost, installed apps can't be updated. Its certificate's SHA-256 is in `tool/release/android-certificate.sha256`, and the script releases no APK signed otherwise; other release builds fall back to the debug key (`android/app/build.gradle.kts`), so `flutter run --release` works anywhere. The APK also goes into the project's own F-Droid repository, https://learn.halkyon.app/fdroid/repo, through `tool/fdroid_repo.sh` (fdroidserver v2.4.5): its settings and the app's listing are in `tool/release/fdroid/` (the anti-features TetheredNet and NonFreeNet declared with their reasons and F-Droid's own icons); its index is signed with a key of its own (a PKCS #12 keystore beside the app's, password asked for, certificate pinned in `tool/release/fdroid-index-certificate.sha256`, since a new key makes the F-Droid app refuse the repository); and it is uploaded to ramiel as the user `fdroid`, whose key can only write into the folder Caddy serves (rrsync), the APKs first and the indexes last.
## Rules the code relies on ## Rules the code relies on

View file

@ -109,9 +109,11 @@ the CI runner described in `deploy/runner/`, deploys only commits signed by a ke
## Releases ## Releases
Releases are published on [code.halkyon.app](https://code.halkyon.app/project-halkyon/halkyon_learn/releases) Releases are published on [code.halkyon.app](https://code.halkyon.app/project-halkyon/halkyon_learn/releases),
and, for Linux, in the Snap Store (`halkyon-learn`). Their builds connect to for Android also in the project's F-Droid repository
learn.halkyon.eu, the project's instance. (`https://learn.halkyon.app/fdroid/repo?fingerprint=E8082788756F6B7E682AA22FCEAD24B53E2A8B0912AD66D65E8C2D4F07BB94B0`,
added in the F-Droid app as a repository), and for Linux in the Snap Store
(`halkyon-learn`). Their builds connect to learn.halkyon.eu, the project's instance.
- **Checking a download:** `SHA256SUMS` lists the checksum of every file, signed with the - **Checking a download:** `SHA256SUMS` lists the checksum of every file, signed with the
maintainer's GPG key (`SHA256SUMS.asc`). The APK is signed with the app's key, whose maintainer's GPG key (`SHA256SUMS.asc`). The APK is signed with the app's key, whose
@ -122,6 +124,9 @@ learn.halkyon.eu, the project's instance.
`<major>.<minor>.<patch>+<build>`. Every release raises the build number: Android only `<major>.<minor>.<patch>+<build>`. Every release raises the build number: Android only
installs a higher one over an older, and the server tells apps below the build it installs a higher one over an older, and the server tells apps below the build it
supports to update. supports to update.
- **The F-Droid repository** is kept by `tool/fdroid_repo.sh` (fdroidserver v2.4.5,
e.g. `pipx install fdroidserver==2.4.5`); its settings and the app's listing are in
`tool/release/fdroid/`.
- **Making one** is for maintainers, on their own machine and never in CI: - **Making one** is for maintainers, on their own machine and never in CI:
`tool/release.sh vX.Y.Z`. Its header says what it needs (the signed tag, the app's `tool/release.sh vX.Y.Z`. Its header says what it needs (the signed tag, the app's
key, a Forgejo token, `snapcraft login`). It builds the APK and the snap key, a Forgejo token, `snapcraft login`). It builds the APK and the snap

107
tool/fdroid_repo.sh Executable file
View file

@ -0,0 +1,107 @@
#!/usr/bin/env bash
# Updates the F-Droid repository at https://learn.halkyon.app/fdroid/repo and
# uploads it: adds an APK if one is given, signs the index with the repository's
# key, and uploads everything to ramiel as the user fdroid, whose key can only
# write into the folder Caddy serves (rrsync). tool/release.sh runs it with each
# release's APK; run alone, it publishes the repository as it is, e.g. after its
# settings changed.
#
# tool/fdroid_repo.sh [APK]
# tool/fdroid_repo.sh --check (the tools, the key and its password only)
#
# The repository's working folder, outside Git, keeps every APK released so far:
# $HALKYON_FDROID_DIR (default below). Its settings and the apps' listings come
# from tool/release/fdroid/, copied in at every run. The index key is a PKCS #12
# keystore at $HALKYON_FDROID_KEYSTORE, alias halkyon-fdroid, whose certificate's
# SHA-256 is pinned in tool/release/fdroid-index-certificate.sha256: an index
# signed otherwise would make the F-Droid app refuse the repository. Its password
# comes from $HALKYON_FDROID_KEYSTORE_PASSWORD, or is asked for; it's never
# written down. Needs fdroidserver v2.4.5 (pipx), rsync, a JDK, the Android SDK,
# and the ssh host ramiel-fdroid.
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
readonly working="${HALKYON_FDROID_DIR:-$HOME/.local/share/halkyon-learn/fdroid}"
keystore="${HALKYON_FDROID_KEYSTORE:-$HOME/.local/share/halkyon-learn/fdroid-index.p12}"
readonly key_alias='halkyon-fdroid'
readonly pinned_file='tool/release/fdroid-index-certificate.sha256'
readonly upload_host='ramiel-fdroid'
readonly repo_url='https://learn.halkyon.app/fdroid/repo'
fail() {
echo "✗ $*" >&2
exit 1
}
step() { echo "▸ $*"; }
[ $# -le 1 ] || fail 'Usage: tool/fdroid_repo.sh [APK | --check]'
check_only=false
apk=''
case "${1:-}" in
--check) check_only=true ;;
'') ;;
*) apk="$1" ;;
esac
for tool in fdroid rsync; do
command -v "$tool" >/dev/null || fail "$tool is missing."
done
# fdroid takes jarsigner and keytool from the PATH, apksigner from the SDK.
jdk_bin=$(dirname "$(command -v java || echo /snap/android-studio/current/jbr/bin/java)")
[ -x "$jdk_bin/jarsigner" ] && [ -x "$jdk_bin/keytool" ] ||
fail 'No JDK found (jarsigner, keytool).'
export PATH="$jdk_bin:$PATH"
export ANDROID_HOME="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$HOME/Android/Sdk}}"
[ -f "$keystore" ] || fail "No keystore at $keystore (HALKYON_FDROID_KEYSTORE)."
keystore=$(realpath "$keystore")
export HALKYON_FDROID_KEYSTORE="$keystore"
[ -f "$pinned_file" ] || fail "$pinned_file is missing."
pinned=$(grep -v '^#' "$pinned_file" | tr -d ' :\n' | tr 'a-f' 'A-F' || true)
[[ "$pinned" =~ ^[0-9A-F]{64}$ ]] || fail "$pinned_file holds no SHA-256."
if [ -z "${HALKYON_FDROID_KEYSTORE_PASSWORD:-}" ]; then
read -rsp "Password of $keystore: " HALKYON_FDROID_KEYSTORE_PASSWORD
echo
fi
export HALKYON_FDROID_KEYSTORE_PASSWORD
# The F-Droid app knows the repository by this fingerprint: the certificate's
# SHA-256 in capitals, without colons.
fingerprint=$(keytool -list -v -keystore "$keystore" -alias "$key_alias" \
-storepass:env HALKYON_FDROID_KEYSTORE_PASSWORD 2>/dev/null |
sed -n 's/^.*SHA256: *//p' | head -n 1 | tr -d ':' | tr 'a-f' 'A-F' || true)
[ -n "$fingerprint" ] || fail "Wrong password, or no key $key_alias in $keystore."
[ "$fingerprint" = "$pinned" ] ||
fail "The key $key_alias in $keystore isn't the one in $pinned_file."
if $check_only; then
exit 0
fi
step 'Updating the F-Droid repository'
mkdir -p "$working/repo"
cp -r tool/release/fdroid/. "$working/"
# fdroid refuses a config.yml others can read.
chmod 600 "$working/config.yml"
cp snap/gui/halkyon-learn.png "$working/halkyon.png"
if [ -n "$apk" ]; then
[ -f "$apk" ] || fail "No APK at $apk."
cp "$apk" "$working/repo/"
fi
# Names every APK as F-Droid does (<app id>_<version code>.apk) and signs the
# indexes with the repository's key.
(cd "$working" && fdroid update --rename-apks)
step "Uploading it to $upload_host"
# The APKs and icons first, the indexes last, so the F-Droid app never reads an
# index naming a file that isn't there yet.
for dir in repo archive; do
[ -d "$working/$dir" ] || continue
rsync -a --chmod=D755,F644 --exclude='index*' --exclude='entry.*' \
"$working/$dir/" "$upload_host:$dir/"
done
for dir in repo archive; do
[ -d "$working/$dir" ] || continue
rsync -a --chmod=D755,F644 --delete-after "$working/$dir/" "$upload_host:$dir/"
done
echo " $repo_url?fingerprint=$fingerprint"

View file

@ -2,17 +2,19 @@
# Builds a release and publishes it: the Android APK (one for every phone, signed with # Builds a release and publishes it: the Android APK (one for every phone, signed with
# the app's key) and the Linux snap, both connecting to learn.halkyon.eu, the project's # the app's key) and the Linux snap, both connecting to learn.halkyon.eu, the project's
# instance; a list of their checksums signed with GPG; all of it as a release on # instance; a list of their checksums signed with GPG; all of it as a release on
# code.halkyon.app, and the snap in the Snap Store's edge channel. Run on a developer's # code.halkyon.app, the APK in the F-Droid repository at learn.halkyon.app
# laptop, never in CI: the app's key never leaves it. # (tool/fdroid_repo.sh), and the snap in the Snap Store's edge channel. Run on a
# developer's laptop, never in CI: the app's key never leaves it.
# #
# tool/release.sh v0.1.0 # tool/release.sh v0.1.0
# #
# Before: the version, with a build number above the last release's, in # Before: the version, with a build number above the last release's, in
# halkyon_learn_flutter/pubspec.yaml on main, pushed; the tag signed on that commit and # halkyon_learn_flutter/pubspec.yaml on main, pushed; the tag signed on that commit and
# pushed (git tag -s v0.1.0 -m 'Halkyon Learn 0.1.0' && git push origin v0.1.0); # pushed (git tag -s v0.1.0 -m 'Halkyon Learn 0.1.0' && git push origin v0.1.0);
# `snapcraft login` with the project's account. The script asks for the key's password # `snapcraft login` with the project's account. The script asks for the key's password,
# and a Forgejo token (scope write:repository) first, checks both, and then runs # the F-Droid repository key's password and a Forgejo token (scope write:repository)
# unattended until GPG asks for its passphrase; neither is ever written down. # first, checks them, and then runs unattended until GPG asks for its passphrase (and
# ssh for the upload key's, unless an agent holds it); none is ever written down.
# #
# The app's key is a PKCS #12 keystore at $HALKYON_ANDROID_KEYSTORE (default below), its # The app's key is a PKCS #12 keystore at $HALKYON_ANDROID_KEYSTORE (default below), its
# alias $HALKYON_ANDROID_KEY_ALIAS. Its certificate's SHA-256 is committed in # alias $HALKYON_ANDROID_KEY_ALIAS. Its certificate's SHA-256 is committed in
@ -25,6 +27,7 @@ readonly forgejo='https://code.halkyon.app'
readonly repository='project-halkyon/halkyon_learn' readonly repository='project-halkyon/halkyon_learn'
readonly pubspec='halkyon_learn_flutter/pubspec.yaml' readonly pubspec='halkyon_learn_flutter/pubspec.yaml'
readonly certificate_file='tool/release/android-certificate.sha256' readonly certificate_file='tool/release/android-certificate.sha256'
readonly fdroid_certificate_file='tool/release/fdroid-index-certificate.sha256'
keystore="${HALKYON_ANDROID_KEYSTORE:-$HOME/.local/share/halkyon-learn/android-release.p12}" keystore="${HALKYON_ANDROID_KEYSTORE:-$HOME/.local/share/halkyon-learn/android-release.p12}"
readonly key_alias="${HALKYON_ANDROID_KEY_ALIAS:-halkyon-learn}" readonly key_alias="${HALKYON_ANDROID_KEY_ALIAS:-halkyon-learn}"
@ -111,6 +114,13 @@ key_certificate=$(HALKYON_ANDROID_KEYSTORE_PASSWORD="$password" "$keytool" -list
[ "$key_certificate" = "$certificate" ] || [ "$key_certificate" = "$certificate" ] ||
fail "The key $key_alias in $keystore isn't the one in $certificate_file." fail "The key $key_alias in $keystore isn't the one in $certificate_file."
read -rsp "Password of the F-Droid repository's key: " HALKYON_FDROID_KEYSTORE_PASSWORD
echo
export HALKYON_FDROID_KEYSTORE_PASSWORD
tool/fdroid_repo.sh --check
fdroid_fingerprint=$(grep -v '^#' "$fdroid_certificate_file" | tr -d ' :\n' | tr 'a-f' 'A-F')
readonly fdroid_fingerprint
read -rsp "Forgejo token for $forgejo (write:repository): " token read -rsp "Forgejo token for $forgejo (write:repository): " token
echo echo
# The token goes to curl in a configuration read from a file descriptor, so it never # The token goes to curl in a configuration read from a file descriptor, so it never
@ -187,6 +197,8 @@ the project's instance.
- **Android:** \`$apk\`, one APK for every phone, signed with the app's key; the \ - **Android:** \`$apk\`, one APK for every phone, signed with the app's key; the \
SHA-256 of its certificate is \`$certificate\`. SHA-256 of its certificate is \`$certificate\`.
- **F-Droid:** the same APK in the repository \
https://learn.halkyon.app/fdroid/repo?fingerprint=$fdroid_fingerprint
- **Linux:** \`$snap\`, also in the Snap Store: \`snap install halkyon-learn --edge\`. - **Linux:** \`$snap\`, also in the Snap Store: \`snap install halkyon-learn --edge\`.
\`SHA256SUMS\` lists the files' checksums, signed with the GPG key \`$signer\` \ \`SHA256SUMS\` lists the files' checksums, signed with the GPG key \`$signer\` \
@ -201,6 +213,10 @@ for file in "$apk" "$snap" SHA256SUMS SHA256SUMS.asc; do
done done
echo " $forgejo/$repository/releases/tag/$tag" echo " $forgejo/$repository/releases/tag/$tag"
step 'Publishing the APK in the F-Droid repository'
tool/fdroid_repo.sh "$out/$apk"
unset HALKYON_FDROID_KEYSTORE_PASSWORD
# Last, as the Store may hold a revision for review; the release above stands either way. # Last, as the Store may hold a revision for review; the release above stands either way.
step "Uploading the snap to the Snap Store's edge channel (as $account)" step "Uploading the snap to the Snap Store's edge channel (as $account)"
snapcraft upload --release=edge "$out/$snap" || snapcraft upload --release=edge "$out/$snap" ||

View file

@ -0,0 +1,5 @@
# The SHA-256 of the certificate that signs the indexes of the F-Droid repository at
# https://learn.halkyon.app/fdroid/repo (key alias halkyon-fdroid), as keytool prints it.
# The F-Droid app knows the repository by it; tool/fdroid_repo.sh uploads no index
# signed otherwise.
E8:08:27:88:75:6F:6B:7E:68:2A:A2:2F:CE:AD:24:B5:3E:2A:8B:09:12:AD:66:D6:5E:8C:2D:4F:07:BB:94:B0

View file

@ -0,0 +1,26 @@
# The F-Droid repository at https://learn.halkyon.app/fdroid/repo, kept by
# tool/fdroid_repo.sh: this folder is copied into the repository's working
# folder (outside Git) before every update. Nothing secret is in here: the index
# key's path and password come from the environment.
repo_url: https://learn.halkyon.app/fdroid/repo
archive_url: https://learn.halkyon.app/fdroid/archive
repo_name: Halkyon
repo_description: >-
Halkyon's apps, signed by their maintainer: the same APKs as on
code.halkyon.app.
archive_name: Halkyon (archive)
archive_description: >-
Older versions of Halkyon's apps.
# The snap's icon, which tool/fdroid_repo.sh copies beside this file.
repo_icon: halkyon.png
archive_icon: halkyon.png
# The newest three versions of each app stay in the repository, older ones move
# to the archive.
archive_older: 3
keystore: $HALKYON_FDROID_KEYSTORE
repo_keyalias: halkyon-fdroid
# A PKCS #12 keystore has one password, for the store and its key.
keystorepass: {env: HALKYON_FDROID_KEYSTORE_PASSWORD}
keypass: {env: HALKYON_FDROID_KEYSTORE_PASSWORD}
keydname: CN=Halkyon, O=Halkyon

View file

@ -0,0 +1,13 @@
# 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
# licence. fdroid copies them into the repository's icons.
NonFreeNet:
name: Non-Free Network Services
description: This app promotes or depends entirely on a non-free network service
icon: ic_antifeature_nonfreenet.png
TetheredNet:
name: Tethered Network Services
description: This app depends entirely on a certain instance of a network service
icon: ic_antifeature_tetherednet.png

View file

@ -0,0 +1,3 @@
# The categories the repository's apps are listed in.
Education:
name: Education

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -0,0 +1,25 @@
# Halkyon Learn's listing in the F-Droid repository: the snap's summary and
# description (snap/snapcraft.yaml), and its anti-features with their reasons.
Categories:
- Education
License: AGPL-3.0-or-later
AuthorName: Halkyon
AuthorEmail: support@halkyon.app
WebSite: https://learn.halkyon.app
SourceCode: https://code.halkyon.app/project-halkyon/halkyon_learn
AntiFeatures:
TetheredNet: It connects to learn.halkyon.eu, the project's instance, and can't be
pointed at another one yet.
NonFreeNet: YouTube videos in study material play through YouTube, and the
server's licence (SSPL) isn't a free licence by F-Droid's definition.
Name: Halkyon Learn
Summary: Learn with flashcards, studied offline and synced between your devices
Description: |-
Halkyon Learn is a spaced-repetition learning app: studysets of flashcards with
rich text, notes, learning paths and PDFs, reviewed on an FSRS schedule. It works
offline and syncs between your devices.
It starts with an anonymous account; signing in with GitHub later keeps
everything. It connects to learn.halkyon.eu, the project's instance.