Move the canonical forge to Codeberg (#86)
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
This commit is contained in:
@@ -72,9 +72,14 @@ jobs:
|
|||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
|
|
||||||
|
# Fully qualified on purpose. Codeberg resolves bare `uses:` refs against
|
||||||
|
# data.forgejo.org, Forgejo's own action mirror — actions/checkout,
|
||||||
|
# setup-java and cache all exist there, but android-actions/setup-android
|
||||||
|
# does not, and the job dies with "repository not found". Gitea's instance
|
||||||
|
# defaults to GitHub, which is why this never surfaced before the split.
|
||||||
- name: Setup Android SDK
|
- name: Setup Android SDK
|
||||||
if: steps.scope.outputs.code == 'true'
|
if: steps.scope.outputs.code == 'true'
|
||||||
uses: android-actions/setup-android@v3
|
uses: https://github.com/android-actions/setup-android@v3
|
||||||
with:
|
with:
|
||||||
# Default ("tools platform-tools") drags in the Android Emulator
|
# Default ("tools platform-tools") drags in the Android Emulator
|
||||||
# (~300 MB) which the build never uses.
|
# (~300 MB) which the build never uses.
|
||||||
@@ -27,6 +27,14 @@ jobs:
|
|||||||
# whether this push actually cuts a new release (no tag for it yet). Keeps the
|
# whether this push actually cuts a new release (no tag for it yet). Keeps the
|
||||||
# heavy job from running on every merge to main.
|
# heavy job from running on every merge to main.
|
||||||
detect:
|
detect:
|
||||||
|
# Gitea only. The workflow directory split already keeps this file invisible
|
||||||
|
# to Codeberg — Forgejo's lookup is first-match-wins, and .forgejo/workflows
|
||||||
|
# exists — but that only holds while .forgejo/ is non-empty. Move the last
|
||||||
|
# file out of it and Codeberg would fall back to .gitea/workflows and start
|
||||||
|
# running the release pipeline on the contributor-facing runner, with no
|
||||||
|
# secrets. repository_owner differs between the two forges regardless of
|
||||||
|
# URL, proxy or instance rename, so this closes it permanently.
|
||||||
|
if: github.repository_owner == 'makiolaj'
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
outputs:
|
outputs:
|
||||||
is_release: ${{ steps.v.outputs.is_release }}
|
is_release: ${{ steps.v.outputs.is_release }}
|
||||||
@@ -41,8 +49,16 @@ jobs:
|
|||||||
- name: Resolve version and whether it is a new release
|
- name: Resolve version and whether it is a new release
|
||||||
id: v
|
id: v
|
||||||
env:
|
env:
|
||||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
# Tags are read from Codeberg, which is canonical — deliberately NOT
|
||||||
API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
|
# from the Gitea API this workflow runs on. The Codeberg -> Gitea sync
|
||||||
|
# is a push mirror, i.e. `git push --mirror`, which deletes refs the
|
||||||
|
# source does not have. A tag minted here on Gitea is therefore wiped
|
||||||
|
# by the next sync (Codeberg does not have it yet) and only reappears
|
||||||
|
# once the tag push at the end of this workflow propagates back.
|
||||||
|
# Asking Gitea inside that window would report "no tag" for a release
|
||||||
|
# that already shipped, and cut it a second time.
|
||||||
|
# Public repo, so this read needs no token.
|
||||||
|
TAG_API: https://codeberg.org/api/v1/repos/jlmakiola/calendula
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
VERSION=$(grep -oP 'versionName\s*=\s*"\K[^"]+' app/build.gradle.kts)
|
VERSION=$(grep -oP 'versionName\s*=\s*"\K[^"]+' app/build.gradle.kts)
|
||||||
@@ -60,15 +76,28 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
# A tag for this version already existing means the release shipped on
|
# A tag for this version already existing means the release shipped on
|
||||||
# an earlier push; do nothing. Absent => this merge cuts the release.
|
# an earlier push; do nothing. Absent => this merge cuts the release.
|
||||||
STATUS=$(curl -s -o /dev/null -w '%{http_code}' \
|
#
|
||||||
-H "Authorization: token $TOKEN" "$API/git/refs/tags/v$VERSION")
|
# Anything other than a clean 200/404 is treated as fatal rather than
|
||||||
if [ "$STATUS" = "200" ]; then
|
# as "no tag". A Codeberg outage or a network blip would otherwise
|
||||||
echo "Tag v$VERSION already exists — nothing to release."
|
# read as absent and re-cut a release that has already shipped —
|
||||||
echo "is_release=false" >> "$GITHUB_OUTPUT"
|
# republishing to F-Droid and Play. Failing here is recoverable; a
|
||||||
else
|
# duplicate release is not.
|
||||||
echo "No tag for v$VERSION yet — cutting the release."
|
STATUS=$(curl -s -o /dev/null -w '%{http_code}' "$TAG_API/git/refs/tags/v$VERSION" || echo 000)
|
||||||
echo "is_release=true" >> "$GITHUB_OUTPUT"
|
case "$STATUS" in
|
||||||
fi
|
200)
|
||||||
|
echo "Tag v$VERSION already exists on Codeberg — nothing to release."
|
||||||
|
echo "is_release=false" >> "$GITHUB_OUTPUT"
|
||||||
|
;;
|
||||||
|
404)
|
||||||
|
echo "No tag for v$VERSION on Codeberg yet — cutting the release."
|
||||||
|
echo "is_release=true" >> "$GITHUB_OUTPUT"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Codeberg tag lookup for v$VERSION returned HTTP $STATUS." >&2
|
||||||
|
echo "Refusing to guess: treating this as 'no tag' could re-cut a shipped release." >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
# Releases: build + sign + publish, then mint the tag and Gitea release.
|
# Releases: build + sign + publish, then mint the tag and Gitea release.
|
||||||
# Also runs on manual dispatch, where it skips the build and just re-signs and
|
# Also runs on manual dispatch, where it skips the build and just re-signs and
|
||||||
|
|||||||
@@ -29,18 +29,29 @@ jobs:
|
|||||||
- name: Run Renovate
|
- name: Run Renovate
|
||||||
run: renovate
|
run: renovate
|
||||||
env:
|
env:
|
||||||
# Self-hosted Gitea, not github.com.
|
# Renovate targets Codeberg (canonical) while still RUNNING on the
|
||||||
RENOVATE_PLATFORM: gitea
|
# Gitea runner. Moving the job to Codeberg would put a repo-write
|
||||||
RENOVATE_ENDPOINT: https://gitea.jeanlucmakiola.de/api/v1
|
# token on the contributor-facing runner, which is exactly what the
|
||||||
# Bot-account token (Gitea secret). Needs repo read/write + PR scope.
|
# .forgejo/ vs .gitea/ split exists to prevent — so the token stays
|
||||||
|
# where the other secrets live and only the API calls cross over.
|
||||||
|
#
|
||||||
|
# Platform is `forgejo`, not `gitea`: Codeberg runs Forgejo, and the
|
||||||
|
# pinned image ships a distinct forgejo platform module.
|
||||||
|
RENOVATE_PLATFORM: forgejo
|
||||||
|
RENOVATE_ENDPOINT: https://codeberg.org/api/v1
|
||||||
|
# Codeberg bot-account token (Gitea secret). Needs repo read/write +
|
||||||
|
# PR scope on jlmakiola/calendula.
|
||||||
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
||||||
# Scope to this repo only — no org-wide autodiscovery.
|
# Scope to this repo only — no org-wide autodiscovery.
|
||||||
RENOVATE_AUTODISCOVER: 'false'
|
RENOVATE_AUTODISCOVER: 'false'
|
||||||
RENOVATE_REPOSITORIES: '["makiolaj/calendula"]'
|
RENOVATE_REPOSITORIES: '["jlmakiola/calendula"]'
|
||||||
# Commits/PRs authored as the bot, not a real maintainer.
|
# Commits/PRs authored as the bot, not a real maintainer. This address
|
||||||
|
# must be a verified email on the Codeberg bot account, otherwise the
|
||||||
|
# commits show up unattributed there.
|
||||||
RENOVATE_GIT_AUTHOR: 'Renovate Bot <renovate@jeanlucmakiola.de>'
|
RENOVATE_GIT_AUTHOR: 'Renovate Bot <renovate@jeanlucmakiola.de>'
|
||||||
# Read-only github.com PAT (no scopes needed). We run on Gitea, but
|
# Read-only github.com PAT (no scopes needed). Unaffected by the forge
|
||||||
# nearly every dependency is *released* on GitHub — without this,
|
# move — nearly every dependency is *released* on GitHub, and without
|
||||||
|
# this,
|
||||||
# changelog/release-note lookups hit the 60/h anonymous rate limit
|
# changelog/release-note lookups hit the 60/h anonymous rate limit
|
||||||
# and PRs arrive with an empty "Release Notes" section.
|
# and PRs arrive with an empty "Release Notes" section.
|
||||||
RENOVATE_GITHUB_COM_TOKEN: ${{ secrets.GITHUB_COM_TOKEN }}
|
RENOVATE_GITHUB_COM_TOKEN: ${{ secrets.GITHUB_COM_TOKEN }}
|
||||||
|
|||||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
|||||||
[submodule "floret-kit"]
|
[submodule "floret-kit"]
|
||||||
path = floret-kit
|
path = floret-kit
|
||||||
url = https://gitea.jeanlucmakiola.de/makiolaj/floret-kit.git
|
url = https://codeberg.org/jlmakiola/floret-kit.git
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Calendula's source code now lives on **Codeberg**, where its issues already
|
||||||
|
were. The **Source code** and **License** links in Settings → About point
|
||||||
|
there, so reporting a bug and reading the code no longer land on two different
|
||||||
|
sites. Nothing about the app itself changes, and the F-Droid repository is
|
||||||
|
unaffected.
|
||||||
|
|
||||||
## [2.16.0] — 2026-07-24
|
## [2.16.0] — 2026-07-24
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
Reads, writes, and reminds — on top of the system calendar, with zero network access.</p>
|
Reads, writes, and reminds — on top of the system calendar, with zero network access.</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a href="https://gitea.jeanlucmakiola.de/makiolaj/calendula/actions"><img src="https://gitea.jeanlucmakiola.de/makiolaj/calendula/actions/workflows/ci.yaml/badge.svg?branch=main" alt="CI"></a>
|
<a href="https://codeberg.org/jlmakiola/calendula/actions"><img src="https://codeberg.org/jlmakiola/calendula/actions/workflows/ci.yaml/badge.svg?branch=main" alt="CI"></a>
|
||||||
<img src="https://img.shields.io/badge/Android-10%2B-3DDC84?logo=android&logoColor=white" alt="Android 10+">
|
<img src="https://img.shields.io/badge/Android-10%2B-3DDC84?logo=android&logoColor=white" alt="Android 10+">
|
||||||
<img src="https://img.shields.io/badge/Kotlin-Compose-7F52FF?logo=kotlin&logoColor=white" alt="Kotlin + Compose">
|
<img src="https://img.shields.io/badge/Kotlin-Compose-7F52FF?logo=kotlin&logoColor=white" alt="Kotlin + Compose">
|
||||||
<img src="https://img.shields.io/badge/Material%203-Expressive-4285F4" alt="Material 3 Expressive">
|
<img src="https://img.shields.io/badge/Material%203-Expressive-4285F4" alt="Material 3 Expressive">
|
||||||
|
|||||||
@@ -571,8 +571,8 @@
|
|||||||
<string name="settings_qs_tile">Add Quick Settings tile</string>
|
<string name="settings_qs_tile">Add Quick Settings tile</string>
|
||||||
<string name="settings_qs_tile_hint">Add a “New event” tile to the Quick Settings panel.</string>
|
<string name="settings_qs_tile_hint">Add a “New event” tile to the Quick Settings panel.</string>
|
||||||
|
|
||||||
<string name="about_source_url" translatable="false">https://gitea.jeanlucmakiola.de/makiolaj/calendula</string>
|
<string name="about_source_url" translatable="false">https://codeberg.org/jlmakiola/calendula</string>
|
||||||
<string name="about_license_url" translatable="false">https://gitea.jeanlucmakiola.de/makiolaj/calendula/src/branch/main/LICENSE</string>
|
<string name="about_license_url" translatable="false">https://codeberg.org/jlmakiola/calendula/src/branch/main/LICENSE</string>
|
||||||
<string name="about_privacy_url" translatable="false">https://jeanlucmakiola.de/calendula/privacy</string>
|
<string name="about_privacy_url" translatable="false">https://jeanlucmakiola.de/calendula/privacy</string>
|
||||||
<string name="about_support_url" translatable="false">https://ko-fi.com/jeanlucmakiola</string>
|
<string name="about_support_url" translatable="false">https://ko-fi.com/jeanlucmakiola</string>
|
||||||
<string name="about_translate_url" translatable="false">https://weblate.dev.jeanlucmakiola.de/engage/calendula/</string>
|
<string name="about_translate_url" translatable="false">https://weblate.dev.jeanlucmakiola.de/engage/calendula/</string>
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ Published version codes so far: `v0.1.0`→100 … `v1.0.0`→10000 … `v2.0.0`
|
|||||||
CI and release are split so a change is built once on its PR and only does
|
CI and release are split so a change is built once on its PR and only does
|
||||||
release work when a merge actually cuts a release:
|
release work when a merge actually cuts a release:
|
||||||
|
|
||||||
- **`ci.yaml`** (on `pull_request`) — lint + unit tests + a debug assemble (and
|
- **`ci.yaml`** (`.forgejo/workflows/`, on `pull_request`, **Codeberg**) — lint +
|
||||||
|
unit tests + a debug assemble (and
|
||||||
a Trivy scan), once per PR. Docs/metadata-only PRs skip the Android build but
|
a Trivy scan), once per PR. Docs/metadata-only PRs skip the Android build but
|
||||||
still report a green `CI` check.
|
still report a green `CI` check.
|
||||||
- **`release.yaml`** (on push to `main`, plus `workflow_dispatch`) — a cheap
|
- **`release.yaml`** (on push to `main`, plus `workflow_dispatch`) — a cheap
|
||||||
@@ -91,9 +92,9 @@ release work when a merge actually cuts a release:
|
|||||||
|
|
||||||
Alongside F-Droid, each release is mirrored to the Codeberg repo
|
Alongside F-Droid, each release is mirrored to the Codeberg repo
|
||||||
(`jlmakiola/calendula`) as a plain download for users who don't want F-Droid.
|
(`jlmakiola/calendula`) as a plain download for users who don't want F-Droid.
|
||||||
Gitea already **push-mirrors** branches and tags to Codeberg, but releases
|
Codeberg **push-mirrors** branches and tags to Gitea, but releases aren't git
|
||||||
aren't git objects and don't sync, so the pipeline creates the release over the
|
objects and don't sync in either direction, so the pipeline creates the release
|
||||||
Codeberg API and attaches `calendula_v<version>.apk` + its `.sha256`. It's the
|
over the Codeberg API and attaches `calendula_v<version>.apk` + its `.sha256`. It's the
|
||||||
same APK the F-Droid repo serves (same **app key**), so it adds no trust
|
same APK the F-Droid repo serves (same **app key**), so it adds no trust
|
||||||
surface. The step is best-effort: a Codeberg outage never fails an
|
surface. The step is best-effort: a Codeberg outage never fails an
|
||||||
already-published F-Droid release, and it skips cleanly if `CODEBERG_RELEASE_TOKEN` is
|
already-published F-Droid release, and it skips cleanly if `CODEBERG_RELEASE_TOKEN` is
|
||||||
@@ -109,6 +110,39 @@ build, the version bump, and tag/release creation, and just re-signs the
|
|||||||
existing F-Droid index with the configured repo key and re-uploads. Use this
|
existing F-Droid index with the configured repo key and re-uploads. Use this
|
||||||
for key rotation or repo recovery without publishing a new app version.
|
for key rotation or repo recovery without publishing a new app version.
|
||||||
|
|
||||||
|
## Two forges, one repo
|
||||||
|
|
||||||
|
**Codeberg (`jlmakiola/calendula`) is canonical** — git, issues, PRs, tags and
|
||||||
|
releases. The self-hosted Gitea instance is build infrastructure: it holds the
|
||||||
|
signing key, publishes the F-Droid repo, and runs the release pipeline. Codeberg
|
||||||
|
push-mirrors `main` and tags to Gitea, and a bumped `versionName` arriving there
|
||||||
|
triggers `release.yaml` exactly as before.
|
||||||
|
|
||||||
|
Workflows are separated by **directory**, not by conditionals. Forgejo looks in
|
||||||
|
`.forgejo/workflows` → `.gitea/workflows` → `.github/workflows` and stops at the
|
||||||
|
first that exists; Gitea doesn't know `.forgejo/` at all:
|
||||||
|
|
||||||
|
| Directory | Runs on | Contains | Secrets |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `.forgejo/workflows/` | Codeberg | `ci.yaml`, `translations.yaml` | **none** |
|
||||||
|
| `.gitea/workflows/` | Gitea | `release.yaml`, `renovate.yml` | signing key, F-Droid, Play, bot tokens |
|
||||||
|
|
||||||
|
The line is drawn at **secrets, not at CI-vs-release**. That's what makes fork
|
||||||
|
PRs safe: everything a contributor can trigger lives in `.forgejo/` and can
|
||||||
|
reference no secret. Renovate stays on the Gitea runner *even though it opens
|
||||||
|
PRs on Codeberg* — it talks to Codeberg's API rather than moving its token onto
|
||||||
|
the contributor-facing runner.
|
||||||
|
|
||||||
|
Two consequences worth remembering:
|
||||||
|
|
||||||
|
- **`detect` 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 would re-cut a shipped release.
|
||||||
|
- **Any ref that exists only on Gitea gets deleted** by the mirror. That's
|
||||||
|
correct under Codeberg-canonical, but don't debug a "vanished" branch without
|
||||||
|
remembering it.
|
||||||
|
|
||||||
## Secrets (Gitea → repo Settings → Actions → Secrets)
|
## Secrets (Gitea → repo Settings → Actions → Secrets)
|
||||||
|
|
||||||
| Secret | Purpose |
|
| Secret | Purpose |
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ Categories:
|
|||||||
- Calendar & Agenda
|
- Calendar & Agenda
|
||||||
License: MIT
|
License: MIT
|
||||||
AuthorName: Jean-Luc Makiola
|
AuthorName: Jean-Luc Makiola
|
||||||
SourceCode: https://gitea.jeanlucmakiola.de/makiolaj/calendula
|
SourceCode: https://codeberg.org/jlmakiola/calendula
|
||||||
IssueTracker: https://gitea.jeanlucmakiola.de/makiolaj/calendula/issues
|
IssueTracker: https://codeberg.org/jlmakiola/calendula/issues
|
||||||
Changelog: https://gitea.jeanlucmakiola.de/makiolaj/calendula/src/branch/main/CHANGELOG.md
|
Changelog: https://codeberg.org/jlmakiola/calendula/src/branch/main/CHANGELOG.md
|
||||||
Donate: https://ko-fi.com/jeanlucmakiola
|
Donate: https://ko-fi.com/jeanlucmakiola
|
||||||
|
|
||||||
AutoName: Calendula
|
AutoName: Calendula
|
||||||
|
|||||||
@@ -6,6 +6,6 @@ Summary: A modern Material 3 Expressive calendar for Android.
|
|||||||
Categories:
|
Categories:
|
||||||
- Time
|
- Time
|
||||||
|
|
||||||
SourceCode: https://gitea.jeanlucmakiola.de/makiolaj/calendula
|
SourceCode: https://codeberg.org/jlmakiola/calendula
|
||||||
IssueTracker: https://gitea.jeanlucmakiola.de/makiolaj/calendula/issues
|
IssueTracker: https://codeberg.org/jlmakiola/calendula/issues
|
||||||
Donate: https://ko-fi.com/jeanlucmakiola
|
Donate: https://ko-fi.com/jeanlucmakiola
|
||||||
|
|||||||
Reference in New Issue
Block a user