Makes Codeberg canonical for git, issues, PRs, tags and releases. The self-hosted Gitea instance stays build infrastructure: signing key, F-Droid publishing, release pipeline. **This PR is its own test.** It is the first PR opened on Codeberg, so a green `CI` check proves the new runner works *and* that the submodule resolves from its new home. ### 1 · floret-kit moved Mirrored to `jlmakiola/floret-kit` (6 branches, 3 tags, every SHA verified identical) and `.gitmodules` repointed. This is what actually unblocks contributors — a clone previously could not resolve its submodule without reaching the personal Gitea instance. The Gitea copy is **kept**: every existing tag records the old submodule URL, so rebuilds of past releases (including F-Droid reproducible rebuilds) still resolve. ### 2 · Workflows split by directory Forgejo's lookup is first-match-wins across `.forgejo/` → `.gitea/` → `.github/`, and Gitea cannot see `.forgejo/` at all. So each forge sees exactly one set, with no duplicated files and no expression to keep in sync: | Directory | Runs on | Contains | Secrets | | --- | --- | --- | --- | | `.forgejo/workflows/` | Codeberg | `ci.yaml`, `translations.yaml` | **none** | | `.gitea/workflows/` | Gitea | `release.yaml`, `renovate.yml` | all of them | The line is drawn at **secrets, not CI-vs-release** — that is what makes fork PRs safe. Renovate deliberately does *not* move despite opening PRs here; it keeps running where its token already lives and merely talks to Codeberg's API. ### 3 · Two release-pipeline safety changes - `release.yaml`'s `detect` gets an explicit `repository_owner` guard. The directory split only holds while `.forgejo/` is non-empty; empty it and Codeberg would fall back to `.gitea/` and start running the release pipeline on the contributor-facing runner, without secrets. - `detect` now reads tags from **Codeberg**, not from the Gitea instance it runs on. Push mirroring is `git push --mirror`, so a tag minted on Gitea is deleted by the next sync until the Codeberg tag push propagates back — asking Gitea inside that window reports "no tag" for an already-shipped release and would cut it twice. It also now fails on any status other than 200/404 rather than reading a transient error as "no tag": a failed job is recoverable, a duplicate release is not. ### 4 · Links repointed In-app Source/License links, README badge, both F-Droid metadata files. **`Repo:` in `docs/fdroid-official/` deliberately stays on Gitea** — it keeps receiving `main` and every tag, so it remains a complete build source, and leaving it alone means no fdroiddata MR and no reproducible-build risk. ### Not in this PR Renovate + Weblate repointing, and the Codeberg → Gitea push mirror (browser-side). Supersedes Gitea PR #104. Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de> Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/86
Official F-Droid submission (draft)
Goal: publish Calendula to the official F-Droid repo alongside the self-hosted repo, as one app — same signed binary in both channels, so existing self-hosted users migrate with no reinstall and no data loss.
This folder holds the draft fdroiddata recipe. Nothing here is submitted
yet. The self-hosted pipeline (../../fdroid-metadata/, .gitea/workflows/release.yaml)
is unaffected by these files.
Publishing model: reproducible build + developer-signed binary
F-Droid builds Calendula from source on its buildserver, then verifies the
output is byte-for-byte identical to our signed APK (fetched via the
Binaries URL). On a match it publishes our binary, signed with our
key — identified by AllowedAPKSigningKeys. Result: official and self-hosted
both carry the same signature. If a build ever fails to match, F-Droid simply
skips that version (fails safe — no bad publish).
Verification status (2026-06-21)
- ✅ Run-to-run reproducible —
v2.7.0built twice from a clean worktree (R8 minify + resource-shrink, build cache off) → byte-for-byte identical (sha256 568c944a…). - ✅ Cross-JDK reproducible — rebuilt with JDK 17 and JDK 21 → identical APK. JDK version is not a sensitivity, so it need not be pinned.
- ✅ Tag self-consistent — committed
versionCode/versionNamealready equal the tag-derived values, so F-Droid building the tag as-is matches the CI-published binary (CI'ssedsubstitution is a no-op). - ✅ App signing cert SHA-256 extracted from published APKs (identical
across v1.0.0 / v2.0.0 / v2.4.0):
5cdaee8e…. No keystore needed. - ✅ Eligibility — MIT, 100% FOSS deps (no Play Services / Firebase /
analytics / billing), no
INTERNETpermission, no native code. - ✅ End-to-end reproducible vs the DISTRIBUTED binary — built tag
v2.7.1from source and compared against the publishedcalendula_v2.7.1.apk: every app entry (dex / resources / assets / manifest) byte-identical. The only difference wasMETA-INF/version-control-info.textproto(AGP's git metadata, env-dependent). Fixed withvcsInfo { include = false }on the release build; re-validated → 0 non-signature differences. Takes effect from the first release built after that change — submit the recipe starting at that version (v2.7.1 and earlier still embed the textproto and won't verify). - ⚠️ Not yet proven: cross-host / fixed-build-path reproducibility on F-Droid's buildserver (low risk for a no-NDK pure-JVM app; with vcsInfo disabled, the env-dependent field is gone; F-Droid confirms at review).
- ✅ Buildserver toolchain supported (checked 2026-06-21):
- Gradle 9.5.1 is in F-Droid's gradle-transparency-log (
checksums.json) with a verified sha256, sogradlew.pywill download + verify + run it. - AGP 9.2 is in
gradlew.py'sMIN_GRADLE_VERSIONmap (9.2 -> 9.4.1). - JDK 17 is standard on the buildserver (AGP 9.2 requires exactly 17).
- build-tools 36.0.0 / android-37: not statically preinstalled (baseline
stops at 33), but
provision-android-sdkmakes$ANDROID_HOME/build-toolsand/platformsgroup-writable so AGP/Gradle install newer ones on demand.
- Gradle 9.5.1 is in F-Droid's gradle-transparency-log (
Before submitting — checklist
- Confirm the
BinariesURL is publicly reachable and stable long-term, e.g.https://apps.dev.jeanlucmakiola.de/dev/fdroid/repo/calendula_v2.7.0.apkresolves to the dev-signed APK. F-Droid re-fetches it on every build. - Confirm F-Droid's buildserver supports the toolchain (see timing risk).
- (Recommended) Reproduce one published release end-to-end: build the tag from
source, strip signatures, and diff against the downloaded
calendula_v<ver>.apkto confirm the from-source build matches the distributed binary — not just another local build.
Submission steps (fdroiddata, GitLab)
- Fork
https://gitlab.com/fdroid/fdroiddata. - Copy
de.jeanlucmakiola.calendula.ymltometadata/in the fork. - Test locally with
fdroid build -v de.jeanlucmakiola.calendulaandfdroid lint de.jeanlucmakiola.calendula(andfdroid readmeta). - Open a Merge Request. Initial review can take weeks to months; the self-hosted repo keeps serving in the meantime, so there's no rush.
Listing metadata (descriptions, screenshots, icon)
These are not in the recipe .yml. F-Droid's fdroid update harvests them
automatically from the fastlane tree in the app's source repo:
fastlane/metadata/android/<locale>/
short_description.txt full_description.txt title.txt
images/icon.png images/phoneScreenshots/*.png
changelogs/<versionCode>.txt (optional, per release)
This is the single source of truth: en-US + de-DE already exist there, and the
self-hosted repo is generated from the same tree at release time via
scripts/fastlane_to_fdroid_localized.sh. Nothing extra to add to fdroiddata —
F-Droid picks the listing up from source. (Screenshots may not be committed to
the fdroiddata repo anyway, so the source-tree fastlane layout is required.)
Per-version changelogs: the self-hosted release workflow generates
changelogs/<versionCode>.txt at release time. For the official repo to show a
changelog, that file must be committed into fastlane/metadata/android/<locale>/changelogs/
at the tagged commit — wire this into the release/version-bump step, or accept
no in-client changelog for the official listing initially.