Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 0 additions & 46 deletions BUILDING.md

This file was deleted.

129 changes: 129 additions & 0 deletions FDROID_RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# F-Droid Release Architecture

This document explains how Session's F-Droid distribution works, why a custom script is
needed, and how to perform the F-Droid release manually if the script is unavailable or broken.

---

## Background: how F-Droid works

F-Droid is an open-source app store for Android. Unlike the Play Store, it operates on a
**repository model**: a repo is a directory of APKs and a signed index file that F-Droid
clients download to discover available apps and versions.

There are two ways an app can appear in F-Droid:

1. **Official F-Droid repo** — F-Droid builds the APK itself from source, using its own
signing key. The app developer has no control over the signing key or build timing.
2. **Self-hosted repo** — The developer hosts their own F-Droid-compatible repository,
signs the APKs and the repo index with their own keys, and F-Droid clients add it as a
custom repo source.

Session uses a **self-hosted repo** at
[session-foundation/session-fdroid](https://github.com/session-foundation/session-fdroid).
This gives us control over the signing key and release timing, at the cost of needing to
maintain the repo ourselves.

---

## Why a custom script is needed

The standard `fdroid` command-line tools are designed around a workflow where F-Droid
**builds** the APK from source. Because we sign APKs ourselves (to keep the signing key
private) and provide pre-built APKs, the standard workflow does not apply cleanly.

Our script bridges that gap by:

1. **Building and signing the APK** with Gradle, using our own keystore — `fdroid update`
only indexes and re-signs the repo index, it does not build or sign APKs itself.
2. **Managing APK inventory** — F-Droid clients expect old versions to remain available for
users who cannot upgrade immediately. The script maintains a rolling window of the last
four releases, removing anything older.
3. **Keeping secrets out of the repo** — `config.yml` (which contains keystore paths and
passwords) and the generated `metadata/<package-id>.yml` (which embeds the current
version code) are derived from committed template files (`.tpl`) at release time and
deleted afterwards. Neither sensitive file is ever committed.
4. **Automating the PR workflow** — the `session-fdroid` repo uses a PR-based flow so that
every release update is reviewed by a human before it reaches users. The script handles
branch creation and PR opening automatically.

---

## Repository structure of session-fdroid

```
session-fdroid/
├── fdroid/
│ ├── repo/ # APKs served to F-Droid clients
│ │ └── session-<version>-<abi>.apk
│ ├── metadata/
│ │ └── <package-id>.yml.tpl # Template; .yml is generated at release time
│ ├── config.yml.tpl # Template; config.yml is generated at release time
│ └── index-v1.jar / index-v2.json # Signed repo index, regenerated by `fdroid update`
```

The `.tpl` files are committed to the repo. The generated `config.yml` and
`metadata/<package-id>.yml` are produced locally, used by `fdroid update`, then deleted —
they are never committed.

---

## How to publish a release using fdroidserver

Our script automates the steps below, but understanding them means you can recover from a
broken script and reason about what the tooling is doing.

The core tool is `fdroid update` from the
[fdroidserver](https://gitlab.com/fdroid/fdroidserver) package. Its job is to:

- Scan the APKs in `repo/`
- Read the app metadata from `metadata/<package-id>.yml`
- Produce a signed repository index (`index-v1.jar` and `index-v2.json`) that F-Droid
clients download to learn what versions are available and where to get them

`fdroid update` does **not** build or sign APKs — it only indexes pre-built ones and signs
the repo index using the key configured in `config.yml`.

### config.yml

`config.yml` tells `fdroid update` how to sign the repo index and where to find the Android
SDK. The key fields are:

```yaml
repo_keyalias: <alias>
keystore: /path/to/repo-signing-keystore.p12 # PKCS12 format
keystorepass: <keystore-password>
keypass: <key-password>
android_sdk_path: /path/to/android/sdk
```

This file contains secrets and is **never committed**. In `session-fdroid` it is generated
from `config.yml.tpl` at release time and deleted after `fdroid update` runs.

### metadata/<package-id>.yml

F-Droid uses a metadata file per app to describe the app to clients — its name, description,
links, and crucially the `CurrentVersionCode`, which tells F-Droid clients which version is
the latest so they know when to prompt for an upgrade.

The full metadata format is documented at
https://f-droid.org/en/docs/Build_Metadata_Reference/

In `session-fdroid`, only the `CurrentVersionCode` field changes between releases. The rest
of the file is static and lives in `metadata/<package-id>.yml.tpl`. The `.tpl` is rendered
to `.yml` at release time with the new version code substituted in, used by `fdroid update`,
then deleted.

### Adding a new version

The fdroidserver workflow for adding a new version to a self-hosted repo is:

1. Place the new signed APK(s) in the `repo/` directory alongside existing ones.
2. Update `CurrentVersionCode` in `metadata/<package-id>.yml` to the new version code.
3. Run `fdroid update` from the repo root (where `config.yml` lives). It will scan all APKs,
merge in the metadata, and rewrite the signed index files.
4. Commit the updated index files and new APKs (but not `config.yml` or the rendered
`metadata/<package-id>.yml`).

When `master` of `session-fdroid` is updated, any F-Droid client that has added our repo
will pick up the new index on its next refresh and offer the update to users.
202 changes: 202 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
Building and Releasing Session
==============================

## Building

### Dependencies

Session uses standard Gradle/Android project structure.

Ensure the following are set up on your machine:

- **Android Studio** (recommended) or the Android SDK command-line tools
- **JDK 17** or later
- The following packages installed via the Android SDK Manager:
- Android SDK Build Tools (see `buildToolsVersion` in `build.gradle`)
- SDK Platform matching the `compileSdkVersion` in `build.gradle`
- Android Support Repository

### Setting up and building in Android Studio

[Android Studio](https://developer.android.com/studio) is the recommended development environment.

1. Open Android Studio. On a new installation the Quickstart panel will appear. If you have
open projects, close them via **File > Close Project** to return to the Quickstart panel.
2. Choose **Get from Version Control** and paste the repository URL:
`https://github.com/session-foundation/session-android.git`
3. The default Gradle sync and build configuration should work out of the box.

### Build flavors

The project has three product flavors:

| Flavor | Description |
|----------|----------------------------------------------|
| `play` | Google Play Store build |
| `fdroid` | F-Droid build (no proprietary dependencies) |
| `huawei` | Huawei AppGallery build (HMS push) |

To build a specific flavor manually, run the corresponding Gradle task, for example:

```sh
./gradlew assemblePlayDebug
./gradlew assembleFdroidDebug
./gradlew assembleHuaweiDebug -Phuawei
```

The `-Phuawei` flag is required for Huawei builds to include the HMS dependencies. If building
in Android Studio, add `-Phuawei` under
**Preferences > Build, Execution, Deployment > Gradle-Android Compiler > Command-line Options**.

### Building a signed release APK manually

The build is standard Gradle — `build-and-release.py` is a convenience wrapper and is not
required if you only need to produce a signed APK. Pass the signing credentials directly as
Gradle properties:

```sh
./gradlew \
-PSESSION_STORE_FILE='/path/to/keystore.jks' \
-PSESSION_STORE_PASSWORD='<keystore-password>' \
-PSESSION_KEY_ALIAS='<key-alias>' \
-PSESSION_KEY_PASSWORD='<key-password>' \
assembleFdroidRelease
```

The keystore file can be recreated from `release-creds.toml` — the `keystore` field in each
section is the JKS file base64-encoded, so it can be decoded back to a `.jks` file using any
standard base64 tool.

---

## Release process

> **Quick checklist**
>
> 1. [ ] Create a `release/MAJOR.MINOR.PATCH` branch from `master`
> 2. [ ] Merge `dev` into the release branch (full release), or cherry-pick the relevant patch commits
> 3. [ ] Bump the version code
> 4. [ ] Create a GitHub release draft for the version (e.g. `1.23.4`) in this repository
> 5. [ ] Obtain `release-creds.toml` from a project maintainer and place it in the project root
> 6. [ ] Run `./scripts/build-and-release.py` from the release branch
> 7. [ ] Upload the AAB bundle to the Play Store
> 8. [ ] Review and merge the automated F-Droid PR in `session-foundation/session-fdroid`
> 9. [ ] Review and publish the GitHub release draft

Steps 6–9 are explained in detail below. Steps 4 and 5 must be completed **before** running
the script — if no release draft exists the artifact upload is silently skipped, and without
the credentials file the script will exit immediately.

### Branching strategy

| Branch | Purpose |
|--------|---------|
| `master` | Represents production — always reflects what is live |
| `dev` | Integration branch for ongoing development |
| `release/MAJOR.MINOR.PATCH` | Short-lived branch used to prepare and build a release |

To start a release:

```sh
git checkout master
git checkout -b release/1.23.4
```

For a **full release**, merge `dev` into the release branch:

```sh
git merge dev
```

For a **patch release**, cherry-pick only the relevant commits:

```sh
git cherry-pick <commit>...
```

Once the branch is ready, bump the version code in `app/build.gradle` (the `versionCode` and
`versionName` fields), commit the change, then proceed with the steps below.

### Prerequisites

- [**uv**](https://docs.astral.sh/uv/) — the script uses `uv run` to manage its Python
dependencies (`fdroidserver`) automatically
- [**gh**](https://cli.github.com/) — GitHub CLI, authenticated and with access to
`session-foundation/session-android` and `session-foundation/session-fdroid`
- **Android SDK** — either `ANDROID_HOME` set in the environment, or `sdk.dir` defined in
`local.properties`. This would have been set up for you if you have opened the project in Android Studio.

### Credentials file

The script requires a `release-creds.toml` file in the project root. This file is not
committed to the repository — ask a project maintainer for it. Its structure is:

```toml
[build.play]
keystore = "<base64-encoded JKS keystore>"
keystore_password = "<password>"
key_alias = "<alias>"
key_password = "<password>"

[build.huawei]
keystore = "<base64-encoded JKS keystore>"
keystore_password = "<password>"
key_alias = "<alias>"
key_password = "<password>"

[fdroid]
keystore = "<base64-encoded PKCS12 keystore>"
keystore_password = "<password>"
key_alias = "<alias>"
key_password = "<password>"
```

### Running the script

From the project root, run:

```sh
./scripts/build-and-release.py
```

This performs a full build and release. The built artifacts are placed under where they suppose
to be according to standard Android project layout.

#### Options

| Flag | Description |
|-----------------|-----------------------------------------------------------------------------|
| `--build-only` | Build all flavors but skip the F-Droid PR and GitHub release upload steps |
| `--build-type` | Gradle build type to use (default: `release`) |

For example, to perform builds without publishing anything:

```sh
./scripts/build-and-release.py --build-only
```

### What the script does in detail

1. **Play build** — assembles split APKs and an AAB bundle for the `play` flavor, signed with
the Play keystore.
2. **F-Droid build** — assembles split APKs for the `fdroid` flavor.
3. **F-Droid repo update** (skipped with `--build-only`) — see [FDROID_RELEASE.md](FDROID_RELEASE.md)
for a full explanation of the architecture and manual steps:
- Clones `session-foundation/session-fdroid` into `build/fdroidrepo` if not already present.
- Creates a `release/<version>` branch.
- Copies the new APKs into the repo, pruning old versions (keeps the latest
four releases).
- Regenerates repository metadata using `fdroid update`.
- Commits and opens a pull request against `master` for human review and merge.
4. **Huawei build** — assembles a universal APK for the `huawei` flavor.
5. **GitHub release upload** (skipped with `--build-only`):
- Looks for a release draft in this repository matching the version name.
- If found, uploads the Play split APKs, the AAB bundle, and the Huawei APKs to it.
- If no draft exists, this step is skipped (no error).

---

## Contributing code

Code contributions should be submitted via GitHub as pull requests from feature branches,
[as explained here](https://help.github.com/articles/using-pull-requests).