Skip to content

fix: sanitize characters in log output to prevent garbled log screen #262

fix: sanitize characters in log output to prevent garbled log screen

fix: sanitize characters in log output to prevent garbled log screen #262

Workflow file for this run

name: Build OpenClaw Apps
on:
push:
branches: ['*']
paths:
- 'flutter_app/**'
- 'scripts/fetch-proot-binaries.sh'
- 'scripts/build-apk.sh'
- '.github/workflows/flutter-build.yml'
pull_request:
branches: [main]
paths:
- 'flutter_app/**'
- 'scripts/fetch-proot-binaries.sh'
- 'scripts/build-apk.sh'
- '.github/workflows/flutter-build.yml'
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
build:
name: Build APK & AAB
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Java 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.0'
channel: stable
cache: true
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('flutter_app/android/**/*.gradle*', 'flutter_app/android/gradle-wrapper.properties') }}
restore-keys: gradle-${{ runner.os }}-
- name: Cache pub dependencies
uses: actions/cache@v4
with:
path: |
~/.pub-cache
flutter_app/.dart_tool
key: pub-${{ runner.os }}-${{ hashFiles('flutter_app/pubspec.lock') }}
restore-keys: pub-${{ runner.os }}-
- name: Setup signing
if: env.KEYSTORE_BASE64 != ''
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: |
echo "$KEYSTORE_BASE64" | base64 -d > "${{ github.workspace }}/release.jks"
cat > flutter_app/android/key.properties <<EOF
storePassword=$KEYSTORE_PASSWORD
keyPassword=$KEY_PASSWORD
keyAlias=$KEY_ALIAS
storeFile=${{ github.workspace }}/release.jks
EOF
sed -i 's/^[[:space:]]*//' flutter_app/android/key.properties
- name: Fetch PRoot binaries
run: bash scripts/fetch-proot-binaries.sh
- name: Verify PRoot binaries
run: |
echo "Checking jniLibs contents..."
for abi in arm64-v8a armeabi-v7a x86_64; do
for lib in libproot.so libprootloader.so; do
FILE="flutter_app/android/app/src/main/jniLibs/${abi}/${lib}"
if [ ! -f "$FILE" ]; then
echo "ERROR: Missing $FILE"
exit 1
fi
SIZE=$(stat -c%s "$FILE")
if [ "$SIZE" -lt 1000 ]; then
echo "WARN: $FILE looks like a placeholder (${SIZE} bytes)"
else
echo "OK: $FILE (${SIZE} bytes)"
fi
done
done
- name: Get Flutter dependencies
working-directory: flutter_app
run: flutter pub get
- name: Analyze Dart code
working-directory: flutter_app
run: flutter analyze --no-fatal-infos
continue-on-error: true
- name: Build fat APK (all ABIs)
working-directory: flutter_app
run: flutter build apk --release
- name: Build per-ABI APKs
working-directory: flutter_app
run: flutter build apk --release --split-per-abi
- name: Build AAB (App Bundle)
working-directory: flutter_app
run: flutter build appbundle --release
- name: Rename and collect artifacts
id: apks
run: |
VERSION=$(grep '^version:' flutter_app/pubspec.yaml | awk '{print $2}' | cut -d'+' -f1)
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
mkdir -p artifacts
# Fat APK
cp flutter_app/build/app/outputs/flutter-apk/app-release.apk \
"artifacts/OpenClaw-v${VERSION}-universal.apk"
# Per-ABI APKs
for abi in arm64-v8a armeabi-v7a x86_64; do
SRC="flutter_app/build/app/outputs/flutter-apk/app-${abi}-release.apk"
if [ -f "$SRC" ]; then
cp "$SRC" "artifacts/OpenClaw-v${VERSION}-${abi}.apk"
fi
done
# AAB
AAB="flutter_app/build/app/outputs/bundle/release/app-release.aab"
if [ -f "$AAB" ]; then
cp "$AAB" "artifacts/OpenClaw-v${VERSION}.aab"
fi
# Summary
echo "### Build Artifacts" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "| File | Size |" >> "$GITHUB_STEP_SUMMARY"
echo "|------|------|" >> "$GITHUB_STEP_SUMMARY"
for f in artifacts/*; do
SIZE=$(du -h "$f" | cut -f1)
echo "| \`$(basename $f)\` | $SIZE |" >> "$GITHUB_STEP_SUMMARY"
done
echo "Built artifacts:"
ls -lh artifacts/
- name: Upload APK artifacts
uses: actions/upload-artifact@v4
with:
name: openclaw-apks
path: artifacts/*.apk
retention-days: 30
- name: Upload AAB artifact
uses: actions/upload-artifact@v4
with:
name: openclaw-aab
path: artifacts/*.aab
retention-days: 30
- name: Comment on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const version = '${{ steps.apks.outputs.version }}';
const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
const body = [
`## Build Successful`,
``,
`**Version:** \`v${version}\``,
``,
`### Download APKs`,
``,
`| Architecture | Download |`,
`|---|---|`,
`| arm64-v8a (recommended) | [OpenClaw-v${version}-arm64-v8a.apk](${runUrl}#artifacts) |`,
`| armeabi-v7a (32-bit) | [OpenClaw-v${version}-armeabi-v7a.apk](${runUrl}#artifacts) |`,
`| x86_64 (emulator) | [OpenClaw-v${version}-x86_64.apk](${runUrl}#artifacts) |`,
`| Universal (all ABIs) | [OpenClaw-v${version}-universal.apk](${runUrl}#artifacts) |`,
`| App Bundle (Play Store) | [OpenClaw-v${version}.aab](${runUrl}#artifacts) |`,
``,
`> Download from [**Actions Artifacts**](${runUrl}#artifacts)`,
``,
`---`,
`*Built from ${process.env.GITHUB_SHA.substring(0, 7)} by [GitHub Actions](${runUrl})*`,
].join('\n');
// Find existing bot comment to update instead of spamming
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(c =>
c.user.type === 'Bot' && c.body.includes('## Build Successful')
);
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}
- name: Comment build failure on PR
if: github.event_name == 'pull_request' && failure()
uses: actions/github-script@v7
with:
script: |
const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: [
`## Build Failed`,
``,
`The build failed for commit ${process.env.GITHUB_SHA.substring(0, 7)}.`,
``,
`[View build logs](${runUrl})`,
].join('\n'),
});
release:
name: Create GitHub Release
needs: [build]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Download APK artifacts
uses: actions/download-artifact@v4
with:
name: openclaw-apks
path: artifacts
- name: Download AAB artifact
uses: actions/download-artifact@v4
with:
name: openclaw-aab
path: artifacts
- name: Get version
id: version
run: |
VERSION=$(grep '^version:' flutter_app/pubspec.yaml | awk '{print $2}' | cut -d'+' -f1)
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
# Check if this tag already exists
if git ls-remote --tags origin "refs/tags/v${VERSION}" | grep -q .; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
- name: Create Release
if: steps.version.outputs.exists == 'false'
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.version.outputs.version }}
name: OpenClaw v${{ steps.version.outputs.version }}
body: |
## OpenClaw Android v${{ steps.version.outputs.version }}
Standalone Android app — no Termux required.
### Downloads
| File | Description |
|------|-------------|
| `OpenClaw-v${{ steps.version.outputs.version }}-arm64-v8a.apk` | Most modern Android phones (recommended) |
| `OpenClaw-v${{ steps.version.outputs.version }}-armeabi-v7a.apk` | Older 32-bit ARM devices |
| `OpenClaw-v${{ steps.version.outputs.version }}-x86_64.apk` | Emulators / x86 devices |
| `OpenClaw-v${{ steps.version.outputs.version }}-universal.apk` | All architectures (larger) |
| `OpenClaw-v${{ steps.version.outputs.version }}.aab` | Android App Bundle (Play Store) |
### First Run
1. Install the APK
2. Follow the setup wizard (downloads ~500MB Ubuntu rootfs)
3. Start the gateway from the dashboard
4. Access the web dashboard at `http://127.0.0.1:18789`
### Requirements
- Android 10+ (API 29)
- ~500MB free storage for initial setup
- Internet connection for first-time setup
files: artifacts/*
draft: false
prerelease: false