# Store release

This document covers the release channels used by HT Portal VPN:

- Google Play testing and production for Android;
- TestFlight and App Store distribution for iOS.

Use the testing channels to validate the same signed artifacts before promoting
them to production.

## Android

The Android package is `net.hivetechnology.o20010_06`. Android package
segments cannot start with a number, so the final segment uses an underscore.
The package name becomes permanent when the first bundle is uploaded.

Release builds read these Gradle properties or environment variables:

```text
HT_ANDROID_KEYSTORE_FILE
HT_ANDROID_KEYSTORE_PASSWORD
HT_ANDROID_KEY_ALIAS
HT_ANDROID_KEY_PASSWORD
```

Build the signed bundle with:

```sh
scripts/build-android-release.sh
```

The upload-ready outputs are `dist/HT-Portal-VPN-0.1.6.aab` for Google Play
and `dist/HT-Portal-VPN-0.1.6.apk` for direct managed installation. Gradle also
keeps the original outputs under `android/app/build/outputs/bundle/release/`
and `android/app/build/outputs/apk/release/`.
By default the local script reads the private, untracked file
`~/.config/hivetechnology/o20010.06/android-upload.env`. Override it with
`HT_ANDROID_SIGNING_PROPERTIES` when necessary.

For a tagged release, GitLab CI expects the upload keystore in the protected,
masked `HT_ANDROID_UPLOAD_KEYSTORE_BASE64` variable and the other three
credentials as protected, masked variables. Tags must use the format
`v0.1.6`.

Publish the corresponding source archive generated from the committed release
revision at the URL embedded in the app:

```sh
scripts/package-source-offer.sh 0.1.6 dist/0.1.6-android-source.tar.gz
```

For the first Play Console upload:

1. create **HT Portal VPN** as an app and choose Google-managed Play App
   Signing;
2. upload the signed AAB to **Internal testing**;
3. declare the app as a network-related remote-access tool that uses
   `VpnService`;
4. document that the tunnel is encrypted and upload the required short
   demonstration video;
5. use `https://portal.hivetechnology.net/privacy-policy` as the privacy URL;
6. add the internal tester email list and publish the internal release.

The VPN service uses Android's `systemExempted` foreground-service type, which
is the platform-defined type for active VPN apps. Do not submit a `specialUse`
foreground-service declaration.

The app sends a random installation identifier and connection diagnostics
needed to redeem, operate, and troubleshoot the requested session. It does not
contain advertising or analytics SDKs. Store declarations must reflect this
behavior and must be reviewed again if telemetry changes.

After the first upload, copy the SHA-256 fingerprint of the **Play app signing
certificate**, not the upload certificate, into the production
`assetlinks.json`. Keep the debug fingerprint only when local debug App Links
are intentionally supported.

## iOS

The application and extension bundle identifiers are:

```text
net.hivetechnology.o20010.06
net.hivetechnology.o20010.06.packet-tunnel
```

Create both identifiers in the Apple Developer account with the Network
Extension capability and create the app record in App Store Connect. The app
record SKU can be `O20010-06-IOS`.

Generate the project and create a Release archive on the signing Mac:

```sh
xcodegen generate
xcodebuild \
  -project HTPortalVPN.xcodeproj \
  -scheme HTPortalVPN \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  -archivePath build/HTPortalVPN.xcarchive \
  -allowProvisioningUpdates \
  archive
```

Before archiving, generate and publish the matching corresponding-source
archive with `scripts/package-source-offer.sh 0.1.0-build42`. Verify that the
URL shown in the app returns that exact archive.

In Xcode Organizer choose **Distribute App**, then **App Store Connect** and
upload the build. For an explicitly internal-only archive, pass
`HT_IOS_DISTRIBUTION_CHANNEL=internal` as an `xcodebuild` build setting and
choose **TestFlight Internal Only** in Organizer. Both paths must ship the
notices and durable GPL source offer described in `IOS_RELEASE_GATE.md`.

The app and its Packet Tunnel extension set
`ITSAppUsesNonExemptEncryption` to `NO`. This does not mean that the VPN is
unencrypted: it records the current App Store Connect determination that its
standard encryption is exempt from uploaded export-compliance documentation
while France is excluded from distribution. Before enabling distribution in
France, complete the required French cryptography declaration and review the
App Store Connect encryption declaration and Info.plist values again.

## GitLab release server

Semver tags create signed Android artifacts automatically. iOS artifacts are
created when a macOS runner tagged `macos` is available and the protected
variable `HT_ENABLE_IOS_RELEASE_CI=true` is set. The runner must use the macOS
account whose keychain contains the Apple Distribution certificate and the
required provisioning access.

Android CI requires the existing protected signing variables:

```text
HT_ANDROID_UPLOAD_KEYSTORE_BASE64
HT_ANDROID_KEYSTORE_PASSWORD
HT_ANDROID_KEY_ALIAS
HT_ANDROID_KEY_PASSWORD
```

iOS can use the Xcode account already configured on the runner. For unattended
provisioning, configure these protected variables:

```text
HT_IOS_ASC_PRIVATE_KEY_BASE64
HT_IOS_ASC_KEY_ID
HT_IOS_ASC_ISSUER_ID
HT_IOS_KEYCHAIN_PASSWORD
```

The release jobs use the same protected FTP credentials as the other HT Portal
repositories:

```text
O20010_RELEASE_FTP_HOST
O20010_RELEASE_FTP_PORT
O20010_RELEASE_FTP_USER
O20010_RELEASE_FTP_PASSWORD
```

After the build succeeds, start one of the manual
`release:<platform>:<channel>` jobs. Stable aliases are published at:

```text
https://releases.hivetechnology.net/o20010/06/android/stable/HT-Portal-VPN.apk
https://releases.hivetechnology.net/o20010/06/ios/stable/HT-Portal-VPN.ipa
```

An App Store Connect IPA is an archival/store artifact and cannot normally be
sideloaded by arbitrary users. A directly installable iOS package requires an
appropriate ad hoc, enterprise, or managed distribution profile; provide a
matching export options plist through `HT_IOS_EXPORT_OPTIONS_PLIST_BASE64`.
