# Deployment

## REST API

Set the production values in `o20010.03` and run its normal schema initializer:

```text
MOBILE_VPN_ENABLED=true
MOBILE_VPN_ANDROID_ENABLED=true
MOBILE_VPN_IOS_ENABLED=false
MOBILE_VPN_HANDOFF_TTL_SECONDS=600
MOBILE_VPN_LAUNCH_URL=https://connect.portal.hivetechnology.net/vpn/
MOBILE_VPN_CUSTOM_SCHEME_URL=htportal-vpn://connect
MOBILE_VPN_WEBSOCKET_URL=wss://ws.portal.hivetechnology.net/mobile-vpn
```

The WebSocket reverse proxy must route `/mobile-vpn` to the REST API WebSocket
listener and forward `Authorization` and `X-HT-Mobile-VPN-Session` headers.
Do not cache any `/mobile-vpn/` response.

## Link host

Deploy `web/` through the dedicated host at
`https://connect.portal.hivetechnology.net/vpn/`, with HTTPS and no third-party
scripts. The portal repository keeps the deployed copy under `public/vpn/`;
the `connect` virtual host must expose that directory at `/vpn/`.
The production Nginx block is provided in
`docs/connect.portal.hivetechnology.net.nginx.conf` and is intended for
`/home/hivetechnology/docker/nginx/http.conf.d/` on the VPS.
Before deployment:

1. create the `connect.portal.hivetechnology.net` DNS record, include the host
   in the portal TLS certificate, and configure its dedicated HTTPS virtual host;
2. replace `__ANDROID_INSTALL_URL__` in `app.js` with the signed APK/store URL;
3. render `assetlinks.json.template` with every active Android signing
   certificate SHA-256 fingerprint and publish it as
   `https://connect.portal.hivetechnology.net/.well-known/assetlinks.json`;
4. when iOS distribution is authorized, render the AASA template with the Apple
   Team ID and publish it extensionless as
   `https://connect.portal.hivetechnology.net/.well-known/apple-app-site-association`;
5. serve both association files as `application/json`, without redirects;
6. set `Cache-Control: no-store` for `/vpn` and `app.js`.

The HTTPS virtual host must return these headers on the landing page and its
error responses:

```text
Content-Security-Policy: default-src 'none'; img-src 'self'; style-src 'self'; script-src 'self'; connect-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'
Referrer-Policy: no-referrer
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
```

`frame-ancestors` must be sent as an HTTP header because browsers ignore that
directive in a CSP `<meta>` element. The handoff is in the fragment and must
never appear in access logs.

## Android

Build with JDK 17+, Android SDK 36, and NDK 27.3.13750724. Use a stable Play App Signing identity,
insert its fingerprint in `assetlinks.json`, and test App Link verification on
an installed release artifact. The foreground service declaration and VPN
consent must remain visible to the user.

Publish the corresponding source archive generated by:

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

alongside the binary or from a durable source-offer URL.
The release AAB bundles GPL-3.0, LGPL-2.1, and third-party notices under
`assets/legal`; the in-app licenses dialog exposes those documents and the
exact corresponding-source URL for that Android version.

The tagged CI pipeline also creates a signed APK and exposes manual
`release:android:alpha`, `release:android:beta`, and
`release:android:stable` jobs. These publish immutable versioned files,
checksums, `latest.json`, `releases.json`, and the direct-download alias.

## iOS

Generate the Xcode project with `xcodegen generate`. Configure an App ID, Packet
Tunnel Network Extension entitlement, shared App Group, shared Keychain group,
and `applinks:connect.portal.hivetechnology.net` for both the provisioning profile and deployment.

For every uploaded iOS build, publish the archive produced by
`scripts/package-source-offer.sh` at the build-specific URL configured by
`HTP_GPL_SOURCE_URL`. Verify the public URL before upload and keep the archive
available for as long as that binary is distributed.

With a configured macOS GitLab runner, tagged pipelines export the IPA and
expose matching manual iOS release jobs. Store-exported IPAs are not public
sideload packages; use a managed export profile when direct installation is
required.
