feat: the F-Droid repository moves to halkyon.app
The repository is Halkyon's, for all of its apps, so its address is the brand's: https://halkyon.app/fdroid/repo. learn.halkyon.app stays Halkyon Learn's own address, for its website later. Moved before anyone but the maintainer had added it; the address is permanent once people have. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
ae2496335d
commit
d149d4a5e3
6 changed files with 11 additions and 10 deletions
|
|
@ -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. 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.
|
- **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://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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ the CI runner described in `deploy/runner/`, deploys only commits signed by a ke
|
||||||
|
|
||||||
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),
|
||||||
for Android also in the project's F-Droid repository
|
for Android also in the project's F-Droid repository
|
||||||
(`https://learn.halkyon.app/fdroid/repo?fingerprint=E8082788756F6B7E682AA22FCEAD24B53E2A8B0912AD66D65E8C2D4F07BB94B0`,
|
(`https://halkyon.app/fdroid/repo?fingerprint=E8082788756F6B7E682AA22FCEAD24B53E2A8B0912AD66D65E8C2D4F07BB94B0`,
|
||||||
added in the F-Droid app as a repository), and for Linux in the Snap Store
|
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.
|
(`halkyon-learn`). Their builds connect to learn.halkyon.eu, the project's instance.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Updates the F-Droid repository at https://learn.halkyon.app/fdroid/repo and
|
# Updates the F-Droid repository at https://halkyon.app/fdroid/repo and
|
||||||
# uploads it: adds an APK if one is given, signs the index with the repository's
|
# 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
|
# 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
|
# write into the folder Caddy serves (rrsync). tool/release.sh runs it with each
|
||||||
|
|
@ -26,7 +26,7 @@ keystore="${HALKYON_FDROID_KEYSTORE:-$HOME/.local/share/halkyon-learn/fdroid-ind
|
||||||
readonly key_alias='halkyon-fdroid'
|
readonly key_alias='halkyon-fdroid'
|
||||||
readonly pinned_file='tool/release/fdroid-index-certificate.sha256'
|
readonly pinned_file='tool/release/fdroid-index-certificate.sha256'
|
||||||
readonly upload_host='ramiel-fdroid'
|
readonly upload_host='ramiel-fdroid'
|
||||||
readonly repo_url='https://learn.halkyon.app/fdroid/repo'
|
readonly repo_url='https://halkyon.app/fdroid/repo'
|
||||||
|
|
||||||
fail() {
|
fail() {
|
||||||
echo "✗ $*" >&2
|
echo "✗ $*" >&2
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# 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, the APK in the F-Droid repository at learn.halkyon.app
|
# code.halkyon.app, the APK in the F-Droid repository at halkyon.app
|
||||||
# (tool/fdroid_repo.sh), and the snap in the Snap Store's edge channel. Run on a
|
# (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.
|
# developer's laptop, never in CI: the app's key never leaves it.
|
||||||
#
|
#
|
||||||
|
|
@ -198,7 +198,7 @@ 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 \
|
- **F-Droid:** the same APK in the repository \
|
||||||
https://learn.halkyon.app/fdroid/repo?fingerprint=$fdroid_fingerprint
|
https://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\` \
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# The SHA-256 of the certificate that signs the indexes of the F-Droid repository at
|
# 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.
|
# https://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
|
# The F-Droid app knows the repository by it; tool/fdroid_repo.sh uploads no index
|
||||||
# signed otherwise.
|
# 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
|
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
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
# The F-Droid repository at https://learn.halkyon.app/fdroid/repo, kept by
|
# The F-Droid repository at https://halkyon.app/fdroid/repo, for all of
|
||||||
|
# Halkyon's apps, kept by
|
||||||
# tool/fdroid_repo.sh: this folder is copied into the repository's working
|
# 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
|
# folder (outside Git) before every update. Nothing secret is in here: the index
|
||||||
# key's path and password come from the environment.
|
# key's path and password come from the environment.
|
||||||
repo_url: https://learn.halkyon.app/fdroid/repo
|
repo_url: https://halkyon.app/fdroid/repo
|
||||||
archive_url: https://learn.halkyon.app/fdroid/archive
|
archive_url: https://halkyon.app/fdroid/archive
|
||||||
repo_name: Halkyon
|
repo_name: Halkyon
|
||||||
repo_description: >-
|
repo_description: >-
|
||||||
Halkyon's apps, signed by their maintainer: the same APKs as on
|
Halkyon's apps, signed by their maintainer: the same APKs as on
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue