mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
Add GitHub Actions workflow for building Dashboard Android APK
This commit is contained in:
195
.github/workflows/dashboard-apk.yaml
vendored
Normal file
195
.github/workflows/dashboard-apk.yaml
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
name: Build Dashboard Android APK
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
manifest_url:
|
||||
description: "Public URL to the Dashboard manifest"
|
||||
required: false
|
||||
default: "https://app.oneuptime.com/dashboard/manifest.json"
|
||||
pwa_origin:
|
||||
description: "Origin that serves the Dashboard (used to resolve relative asset paths)"
|
||||
required: false
|
||||
default: "https://app.oneuptime.com"
|
||||
package_id:
|
||||
description: "Android applicationId (reverse DNS)"
|
||||
required: false
|
||||
default: "com.oneuptime.dashboard"
|
||||
host_name:
|
||||
description: "Host name for the Trusted Web Activity (no scheme)"
|
||||
required: false
|
||||
default: "app.oneuptime.com"
|
||||
signing_keystore_base64:
|
||||
description: "Optional base64 keystore (JKS). Leave empty to build with an auto-generated debug key."
|
||||
required: false
|
||||
signing_key_alias:
|
||||
description: "Alias inside the provided keystore"
|
||||
required: false
|
||||
signing_key_password:
|
||||
description: "Key password inside the provided keystore"
|
||||
required: false
|
||||
signing_store_password:
|
||||
description: "Store password for the provided keystore"
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
build-apk:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
MANIFEST_SOURCE_PATH: Dashboard/public/manifest.json
|
||||
MANIFEST_URL: ${{ inputs.manifest_url }}
|
||||
PWA_ORIGIN: ${{ inputs.pwa_origin }}
|
||||
PACKAGE_ID: ${{ inputs.package_id }}
|
||||
HOST_NAME: ${{ inputs.host_name }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Compute version numbers
|
||||
id: version
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VERSION_PREFIX=$(cat VERSION_PREFIX | tr -d ' \n')
|
||||
VERSION_NAME="${VERSION_PREFIX}.${GITHUB_RUN_NUMBER}"
|
||||
echo "name=${VERSION_NAME}" >> $GITHUB_OUTPUT
|
||||
echo "code=${GITHUB_RUN_NUMBER}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
|
||||
- name: Install Android SDK components
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ANDROID_HOME=${ANDROID_HOME:-/usr/local/lib/android/sdk}
|
||||
ANDROID_SDK_ROOT=$ANDROID_HOME
|
||||
echo "ANDROID_HOME=${ANDROID_HOME}" >> $GITHUB_ENV
|
||||
echo "ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT}" >> $GITHUB_ENV
|
||||
yes | "${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" --sdk_root="${ANDROID_HOME}" --licenses >/dev/null
|
||||
yes | "${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" --sdk_root="${ANDROID_HOME}" \
|
||||
"platform-tools" \
|
||||
"platforms;android-34" \
|
||||
"build-tools;34.0.0"
|
||||
|
||||
- name: Install Bubblewrap CLI
|
||||
run: npm install -g @bubblewrap/cli
|
||||
|
||||
- name: Prepare signing key
|
||||
id: signing_key
|
||||
env:
|
||||
INPUT_KEYSTORE: ${{ inputs.signing_keystore_base64 }}
|
||||
INPUT_KEY_ALIAS: ${{ inputs.signing_key_alias }}
|
||||
INPUT_KEY_PASS: ${{ inputs.signing_key_password }}
|
||||
INPUT_STORE_PASS: ${{ inputs.signing_store_password }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -n "${INPUT_KEYSTORE}" ]; then
|
||||
echo "${INPUT_KEYSTORE}" | base64 --decode > android.keystore
|
||||
STORE_PASS="${INPUT_STORE_PASS:-android}"
|
||||
KEY_PASS="${INPUT_KEY_PASS:-${STORE_PASS}}"
|
||||
KEY_ALIAS="${INPUT_KEY_ALIAS:-release}"
|
||||
echo "Restored signing key from workflow input"
|
||||
else
|
||||
STORE_PASS=android
|
||||
KEY_PASS=android
|
||||
KEY_ALIAS=oneuptimeDebug
|
||||
keytool -genkeypair \
|
||||
-keystore android.keystore \
|
||||
-storepass "${STORE_PASS}" \
|
||||
-alias "${KEY_ALIAS}" \
|
||||
-keypass "${KEY_PASS}" \
|
||||
-validity 3650 \
|
||||
-keyalg RSA \
|
||||
-dname "CN=OneUptime, OU=Engineering, O=OneUptime, L=San Francisco, S=CA, C=US"
|
||||
echo "Generated temporary debug keystore"
|
||||
fi
|
||||
echo "signing_alias=${KEY_ALIAS}" >> $GITHUB_OUTPUT
|
||||
echo "signing_key_pass=${KEY_PASS}" >> $GITHUB_OUTPUT
|
||||
echo "signing_store_pass=${STORE_PASS}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate TWA manifest config
|
||||
env:
|
||||
VERSION_NAME: ${{ steps.version.outputs.name }}
|
||||
VERSION_CODE: ${{ steps.version.outputs.code }}
|
||||
SIGNING_ALIAS: ${{ steps.signing_key.outputs.signing_alias }}
|
||||
SIGNING_KEY_PASS: ${{ steps.signing_key.outputs.signing_key_pass }}
|
||||
SIGNING_STORE_PASS: ${{ steps.signing_key.outputs.signing_store_pass }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p android
|
||||
node <<'NODE'
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const manifestPath = path.join(process.env.GITHUB_WORKSPACE, process.env.MANIFEST_SOURCE_PATH);
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
||||
const icons = manifest.icons || [];
|
||||
const pickIcon = (predicate) => icons.find(predicate) || icons[0];
|
||||
const bestIcon = pickIcon(icon => /512x512/.test(icon?.sizes || ''));
|
||||
const maskableIcon = pickIcon(icon => (icon?.purpose || '').includes('maskable'));
|
||||
const abs = (src) => src ? new URL(src, process.env.PWA_ORIGIN).href : undefined;
|
||||
const twaManifest = {
|
||||
packageId: process.env.PACKAGE_ID,
|
||||
host: process.env.HOST_NAME,
|
||||
name: manifest.name || manifest.short_name || 'OneUptime',
|
||||
launcherName: manifest.short_name || manifest.name || 'OneUptime',
|
||||
display: manifest.display || 'standalone',
|
||||
themeColor: manifest.theme_color || '#000000',
|
||||
navigationColor: manifest.theme_color || '#000000',
|
||||
backgroundColor: manifest.background_color || '#ffffff',
|
||||
enableNotifications: true,
|
||||
startUrl: manifest.start_url || '/dashboard/',
|
||||
webManifestUrl: process.env.MANIFEST_URL,
|
||||
iconUrl: abs(bestIcon?.src),
|
||||
maskableIconUrl: abs(maskableIcon?.src || bestIcon?.src),
|
||||
shortcuts: manifest.shortcuts || [],
|
||||
appVersion: process.env.VERSION_NAME,
|
||||
appVersionCode: Number(process.env.VERSION_CODE),
|
||||
signingKey: {
|
||||
path: path.join(process.env.GITHUB_WORKSPACE, 'android.keystore'),
|
||||
alias: process.env.SIGNING_ALIAS,
|
||||
password: process.env.SIGNING_KEY_PASS,
|
||||
keystorePassword: process.env.SIGNING_STORE_PASS
|
||||
},
|
||||
features: {
|
||||
notifications: {
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
splashScreenFadeOutDuration: 300
|
||||
};
|
||||
fs.writeFileSync(path.join(process.env.GITHUB_WORKSPACE, 'android', 'twa-manifest.json'), JSON.stringify(twaManifest, null, 2));
|
||||
NODE
|
||||
|
||||
- name: Scaffold Android project
|
||||
working-directory: android
|
||||
run: bubblewrap init --suppressPrompts
|
||||
|
||||
- name: Build signed APK
|
||||
working-directory: android
|
||||
run: bubblewrap build
|
||||
|
||||
- name: Locate APK
|
||||
id: locate_apk
|
||||
run: |
|
||||
set -euo pipefail
|
||||
APK_PATH=$(find android -type f -name "*release*.apk" | head -n 1)
|
||||
if [ -z "$APK_PATH" ]; then
|
||||
echo "Failed to locate release APK" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "apk_path=$APK_PATH" >> $GITHUB_OUTPUT
|
||||
echo "Found APK at $APK_PATH"
|
||||
|
||||
- name: Upload APK artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dashboard-apk-${{ steps.version.outputs.name }}
|
||||
path: ${{ steps.locate_apk.outputs.apk_path }}
|
||||
Reference in New Issue
Block a user