mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
Merge self-hosted XPREM OTA updates wiring into staging
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
commit
2664791233
9 files changed
+2485
-39
No files matched your search
@@ -0,0 +1,101 @@
|
||||
// Dynamic Expo config.
|
||||
//
|
||||
// `app.json` stays the static source of truth for everything that is stable
|
||||
// across environments, including the `version`, `ios.buildNumber`, and
|
||||
// `android.versionCode` fields that EAS rewrites during a release build. This
|
||||
// file only layers the over-the-air update configuration on top, because the
|
||||
// XPREM update server selects a release channel from a build-time request
|
||||
// header and that value differs per EAS build profile.
|
||||
//
|
||||
// See `docs/mobile-ota.md`.
|
||||
|
||||
/* global __dirname */
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const appJson = require('./app.json');
|
||||
|
||||
/** Base URL of the self-hosted XPREM update server. */
|
||||
const OTA_SERVER_URL = 'https://ota.apps.jbrunton.co.uk';
|
||||
|
||||
/** XPREM manifest endpoint. XPREM serves the Expo Updates protocol here. */
|
||||
const OTA_MANIFEST_URL = `${OTA_SERVER_URL}/manifest`;
|
||||
|
||||
/**
|
||||
* XPREM application UUID for Sauci, shown in the XPREM dashboard under App
|
||||
* Info. Not a secret; overridable for pointing a build at another app.
|
||||
*/
|
||||
const OTA_APP_ID = process.env.XPREM_APP_ID || '9a66f3c2-fda7-416f-87e2-6db833b32e9d';
|
||||
|
||||
/** Public code-signing certificate downloaded from the XPREM dashboard. */
|
||||
const CODE_SIGNING_CERTIFICATE_PATH = './certs/certificate.pem';
|
||||
|
||||
/** Marker string present only in the committed placeholder certificate. */
|
||||
const CERTIFICATE_PLACEHOLDER_MARKER = 'XPREM-PLACEHOLDER-CERTIFICATE';
|
||||
|
||||
/**
|
||||
* Release channel bound to the build. XPREM maps a channel to a branch server
|
||||
* side, so a channel can be repointed at a different branch without rebuilding.
|
||||
* Set per EAS build profile in `eas.json` and exported before `eoas publish`.
|
||||
*/
|
||||
const releaseChannel = process.env.RELEASE_CHANNEL || 'development';
|
||||
|
||||
/**
|
||||
* Code signing is skipped for local development, where the packager serves the
|
||||
* bundle directly and no signed manifest exists:
|
||||
* DISABLE_CODE_SIGNING=1 npx expo start --dev-client
|
||||
*/
|
||||
const codeSigningDisabled = Boolean(process.env.DISABLE_CODE_SIGNING);
|
||||
|
||||
function assertUpdateConfigIsReal() {
|
||||
const certificatePath = path.join(__dirname, CODE_SIGNING_CERTIFICATE_PATH);
|
||||
const certificate = fs.existsSync(certificatePath)
|
||||
? fs.readFileSync(certificatePath, 'utf8')
|
||||
: '';
|
||||
|
||||
if (!certificate || certificate.includes(CERTIFICATE_PLACEHOLDER_MARKER)) {
|
||||
throw new Error(
|
||||
`RELEASE_CHANNEL=${releaseChannel} requires the real XPREM code-signing ` +
|
||||
`certificate at apps/mobile/${CODE_SIGNING_CERTIFICATE_PATH}. ` +
|
||||
'Download it from the XPREM dashboard App Info page. See docs/mobile-ota.md.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// A build or publish always names its channel explicitly. Fail loudly there
|
||||
// rather than shipping a build that can never validate a signed manifest.
|
||||
if (process.env.RELEASE_CHANNEL) {
|
||||
assertUpdateConfigIsReal();
|
||||
}
|
||||
|
||||
module.exports = () => ({
|
||||
...appJson.expo,
|
||||
// Updates are gated on the app version. A JavaScript-only change ships over
|
||||
// the air; anything that changes native code must ship a new store build with
|
||||
// a bumped `version`, which starts a fresh runtime version on the server.
|
||||
runtimeVersion: {
|
||||
policy: 'appVersion',
|
||||
},
|
||||
updates: {
|
||||
enabled: true,
|
||||
url: OTA_MANIFEST_URL,
|
||||
checkAutomatically: 'ON_LOAD',
|
||||
fallbackToCacheTimeout: 5000,
|
||||
codeSigningCertificate: codeSigningDisabled
|
||||
? undefined
|
||||
: CODE_SIGNING_CERTIFICATE_PATH,
|
||||
codeSigningMetadata: codeSigningDisabled
|
||||
? undefined
|
||||
: { keyid: 'main', alg: 'rsa-v1_5-sha256' },
|
||||
// expo-updates only sends headers that were declared at build time, so
|
||||
// every header XPREM may need has to be present here even when empty.
|
||||
requestHeaders: {
|
||||
'expo-channel-name': releaseChannel,
|
||||
'expo-app-id': OTA_APP_ID,
|
||||
// Branch surfing: lets a non-production build be pointed at another
|
||||
// branch at runtime. Empty means the channel mapping decides.
|
||||
'xprem-branch': '',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
# The update-signing private key belongs to the XPREM server, never to the app.
|
||||
private-key.pem
|
||||
*.key
|
||||
# The public certificate must be committed despite the root *.pem rule: the
|
||||
# expo-updates plugin embeds it into every build.
|
||||
!certificate.pem
|
||||
@@ -0,0 +1,18 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICzTCCAbWgAwIBAgIJAPrPjxnUOGisMA0GCSqGSIb3DQEBCwUAMBAxDjAMBgNV
|
||||
BAMTBVNhdWNpMB4XDTI2MDgzMDE4MTAzMFoXDTM2MDgzMDE4MTAzMFowEDEOMAwG
|
||||
A1UEAxMFU2F1Y2kwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWiUac
|
||||
8i9bIbNZjv8iunVr6kqlgb9vea98JHz/A83biyszg0+GpmLYsr4zV2F7A6RitiIx
|
||||
SOML+wVedyD0F2quWccBGXinvMAds0TxgIOjQfP+8+VJ/duDjveHvn+OFW4s7XxW
|
||||
SY7TK1ufDcPWo/f5XzfqtBveug49zv3/F3MwfmMUbEY/zvH7nayPGJNuSyf3wISn
|
||||
JIt0BsHNhWykRXCzjaF2fy3nAebOyezbtbapCjUlxPENVtBCzBQ7C9kaQXCBw5mV
|
||||
hxhJlOxHWXpHJgDLHpev8A/CWQRh67tsevjKi6R6SDlf0R0xSbDJTElx8goWOOQW
|
||||
VO9aDqI+oH0KpWfnAgMBAAGjKjAoMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8E
|
||||
DDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEAyUc20bIRCxoBTJg84mPl
|
||||
2nFDWSaAfzgo0K0b6x2T1suuq4LAOUgzBqJISrQAALd0besK1qUZSAMhjQu1wPZT
|
||||
MHuWRMxQHfOkyHMYUfOoPMS4bE+4K0WjzfoVt3y1A+oxhcIAQUUGyNgNmebfEN07
|
||||
JlX0B8UI8aIMjyfpsIDgZx1piHKwittGy/V4MPOLbmT2BBwS9BELcmWE4CwWbfeo
|
||||
JM4lXY0hk3M/GTstRYaUKiwBWEU+z1PdAmWAgrtxpVnXN+oaToWh0WbVkbF1K6r5
|
||||
3oBGFTL7kT+Wogg9GK04XoAREw0jZJ/k77oJx68lGVBNiB+Ue2KqX0H/0cf2CslY
|
||||
Ug==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -7,19 +7,24 @@
|
||||
"development": {
|
||||
"node": "20.19.4",
|
||||
"developmentClient": true,
|
||||
"distribution": "internal"
|
||||
"distribution": "internal",
|
||||
"env": {
|
||||
"RELEASE_CHANNEL": "development"
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"node": "20.19.4",
|
||||
"distribution": "internal",
|
||||
"env": {
|
||||
"EXPO_PUBLIC_API_URL": "https://api.preprod.sauci.app"
|
||||
"EXPO_PUBLIC_API_URL": "https://api.preprod.sauci.app",
|
||||
"RELEASE_CHANNEL": "staging"
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
"node": "20.19.4",
|
||||
"autoIncrement": true,
|
||||
"env": {
|
||||
"RELEASE_CHANNEL": "production",
|
||||
"EXPO_PUBLIC_API_URL": "https://api.sauci.app",
|
||||
"EXPO_PUBLIC_SUPABASE_URL": "https://ckjcrkjpmhqhiucifukx.supabase.co",
|
||||
"EXPO_PUBLIC_SUPABASE_ANON_KEY": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNramNya2pwbWhxaGl1Y2lmdWt4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjcxMDc1ODEsImV4cCI6MjA4MjY4MzU4MX0.7FA_RmFXUjlmZF96O9xlAUfxHrQrSIZcJclNReLL4lI",
|
||||
|
||||
@@ -18,7 +18,11 @@
|
||||
"release:ios:preview": "dotenv -e .env -- eas build --platform ios --profile preview --local",
|
||||
"release:android": "dotenv -e .env -- eas build --platform android --profile production --local",
|
||||
"release:android:preview": "dotenv -e .env -- eas build --platform android --profile preview --local",
|
||||
"release:android:upload": "bundle exec fastlane android upload"
|
||||
"release:android:upload": "bundle exec fastlane android upload",
|
||||
"ota:publish:staging": "RELEASE_CHANNEL=staging eoas publish --branch staging",
|
||||
"ota:publish:production": "RELEASE_CHANNEL=production eoas publish --branch production",
|
||||
"ota:rollback": "eoas rollback",
|
||||
"ota:doctor": "eoas doctor"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bacons/apple-targets": "^0.1.6",
|
||||
@@ -62,6 +66,7 @@
|
||||
"expo-secure-store": "~15.0.8",
|
||||
"expo-sharing": "~14.0.8",
|
||||
"expo-status-bar": "~3.0.9",
|
||||
"expo-updates": "~29.0.20",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-native": "0.81.5",
|
||||
@@ -88,6 +93,7 @@
|
||||
"@types/react": "~19.1.10",
|
||||
"@types/react-test-renderer": "^19.1.0",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"eoas": "^3.1.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-expo": "~10.0.0",
|
||||
"jest": "^29.7.0",
|
||||
|
||||
Reference in new issue
Block a user