Add GitHub Actions workflow for building Dashboard Android APK

This commit is contained in:
Simon Larsen
2025-11-20 21:08:18 +00:00
parent 17f6507d0c
commit bfc761aac5

195
.github/workflows/dashboard-apk.yaml vendored Normal file
View 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 }}