Add release/build tooling and README

- Real release build type: R8 shrinking + shrunk resources, with a keep
  rule for VeilHook specifically — LSPosed finds it by exact class name via
  the plaintext xposed_init asset, which R8 can't see, so obfuscating it
  would silently break module loading with no build error to point at why.
- Signing config that reads keystore.properties if present (gitignored,
  generated via scripts/generate-keystore.sh), falling back to debug
  signing otherwise so assembleRelease always produces something
  installable.
- scripts/release.sh builds and copies a version-stamped APK into dist/.
- GitHub Actions workflow building and publishing a release on any tag
  matching *.*.* (bare semver, e.g. 1.2.0 — no v prefix required), optionally
  using repo secrets for real release signing in CI.
- README covering setup, the feature table, and the DenyList gotcha.

No native code in this module, so a single APK already covers every
architecture — no per-ABI splitting needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Claude Sonnet 5
2026-08-06 21:46:16 +06:00
parent 615f000800
commit 328237d722
7 changed files with 218 additions and 1 deletions
+62
View File
@@ -0,0 +1,62 @@
name: Release
on:
push:
tags:
- "*.*.*"
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- name: Set up Android SDK
uses: android-actions/setup-android@v4
with:
packages: "platform-tools platforms;android-35 build-tools;35.0.0"
# Optional — only produces a properly release-signed APK if these repo secrets are set:
# KEYSTORE_BASE64 (base64 of a release .jks), KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD.
# Without them, the build falls back to debug signing (still installable, just not
# signed with a dedicated release key).
- name: Set up release signing
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: |
if [ -n "$KEYSTORE_BASE64" ]; then
echo "$KEYSTORE_BASE64" | base64 -d > release.jks
cat > keystore.properties <<EOF
storeFile=release.jks
storePassword=$KEYSTORE_PASSWORD
keyAlias=$KEY_ALIAS
keyPassword=$KEY_PASSWORD
EOF
else
echo "No KEYSTORE_BASE64 secret set — release APK will be debug-signed."
fi
- name: Build release APK
run: ./gradlew assembleRelease
- name: Package artifact
run: |
mkdir -p dist
cp app/build/outputs/apk/release/app-release.apk "dist/veil-${GITHUB_REF_NAME}.apk"
- name: Publish GitHub release
uses: softprops/action-gh-release@v2
with:
files: dist/*.apk
+4
View File
@@ -7,3 +7,7 @@ local.properties
.DS_Store
.cxx/
/captures/
keystore.properties
*.jks
*.keystore
/dist/
+59
View File
@@ -0,0 +1,59 @@
# Veil
An LSPosed module that hides accessibility, debugging, and root/hook state from specific
apps, per app, without changing the actual state of the device.
> [!IMPORTANT]
> Built entirely by AI in one evening to fix a personal problem. YMMV — it works for me
> and hasn't been audited beyond that.
## Requirements
- Rooted device with a working Zygisk implementation and LSPosed (or an LSPosed-compatible
framework such as Vector).
- Android 8.1+.
## Setup
1. Build (below) or grab a release APK from the repo's Releases page.
2. Enable the module in LSPosed Manager and scope it to the target app.
3. Open the target app once — it appears in Veil's own settings screen automatically.
4. Uncheck anything you don't want hidden for that app.
If the target app still won't launch, check whether it's on your root manager's DenyList
(e.g. KernelSU: app profile → "Umount modules"). An app on that list never gets Zygisk
modules injected at all, Veil included. The settings screen's help panel covers this and
a few other gotchas.
## Features
Per app, per feature — anything not listed gets everything hidden by default.
| Feature | Hides |
| ------------------------ | ------------------------------------------------------ |
| Accessibility services | Any accessibility service running |
| USB / wireless debugging | `adb_enabled` / `adb_wifi_enabled` |
| Developer options | Developer Options enabled state |
| Mock location | Legacy pre-Marshmallow flag only |
| Root / manager apps | Configurable package list (default: KernelSU, Magisk) |
| Build tags/type | Real build swapped for `release-keys` / `user` |
Doesn't cover native-level root/hook detection or remote attestation (Play Integrity,
SafetyNet) — that's your Zygisk implementation's job, not Veil's.
## Building
Requires JDK 17 and Android SDK (platform 35, build-tools 35.0.0).
```
./gradlew assembleDebug # debug-signed APK
./gradlew assembleRelease # minified release build, debug-signed unless keystore.properties exists
scripts/generate-keystore.sh # create a personal release-signing keystore
scripts/release.sh # build + copy signed release APK into dist/
```
## CI releases
Pushing a tag matching `*.*.*` (e.g. `1.2.0`) triggers `.github/workflows/release.yml`.
For a properly signed CI build, set repo secrets: `KEYSTORE_BASE64` (base64 of your
`release.jks`), `KEYSTORE_PASSWORD`, `KEY_ALIAS`, `KEY_PASSWORD`.
+34 -1
View File
@@ -1,23 +1,56 @@
import java.util.Properties
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
val keystorePropertiesFile = rootProject.file("keystore.properties")
val keystoreProperties = Properties()
val hasReleaseKeystore = keystorePropertiesFile.exists()
if (hasReleaseKeystore) {
keystoreProperties.load(keystorePropertiesFile.inputStream())
}
android {
namespace = "space.bdeshi.veil"
compileSdk = 35
defaultConfig {
applicationId = "space.bdeshi.veil"
// 27 (Android 8.1) is LSPosed/Zygisk's own practical floor — nothing here restricts
// it further. No upper bound: compileSdk is a compile-time API surface, not a ceiling
// on which OS versions can install and run the app.
minSdk = 27
targetSdk = 35
versionCode = 1
versionName = "1.0"
}
signingConfigs {
create("release") {
if (hasReleaseKeystore) {
storeFile = rootProject.file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
}
}
}
buildTypes {
release {
isMinifyEnabled = false
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
// Falls back to debug signing when no keystore.properties exists (e.g. a fresh
// clone, or CI without release-signing secrets configured), so assembleRelease
// always produces something installable. See scripts/generate-keystore.sh.
signingConfig = if (hasReleaseKeystore) {
signingConfigs.getByName("release")
} else {
signingConfigs.getByName("debug")
}
}
}
+5
View File
@@ -0,0 +1,5 @@
# LSPosed discovers the module entry point by fully-qualified class name via the plaintext
# assets/xposed_init file, not through anything R8 can see (no manifest reference, no
# reflection call it can trace) keep this class and its members intact or the module
# silently stops loading with no build error to point at why.
-keep class space.bdeshi.veil.VeilHook { *; }
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Creates a personal release-signing keystore and keystore.properties. Both are gitignored —
# back them up yourself if you want release builds to keep the same signing identity across
# reinstalls (Android treats a differently-signed update as a different app).
set -euo pipefail
cd "$(dirname "$0")/.."
if [ -f keystore.properties ]; then
echo "keystore.properties already exists — remove it first if you want to regenerate." >&2
exit 1
fi
KEYSTORE_PATH="release.jks"
ALIAS="veil"
read -rsp "New keystore password: " STORE_PASSWORD
echo
read -rsp "Key password (leave blank to reuse the keystore password): " KEY_PASSWORD
echo
KEY_PASSWORD="${KEY_PASSWORD:-$STORE_PASSWORD}"
keytool -genkeypair \
-keystore "$KEYSTORE_PATH" \
-alias "$ALIAS" \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-storepass "$STORE_PASSWORD" \
-keypass "$KEY_PASSWORD" \
-dname "CN=Veil, OU=Personal, O=Personal, L=Unknown, ST=Unknown, C=US"
cat > keystore.properties <<EOF
storeFile=$KEYSTORE_PATH
storePassword=$STORE_PASSWORD
keyAlias=$ALIAS
keyPassword=$KEY_PASSWORD
EOF
echo "Wrote $KEYSTORE_PATH and keystore.properties."
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Builds a release APK and copies it into dist/ with a version-stamped filename.
set -euo pipefail
cd "$(dirname "$0")/.."
./gradlew assembleRelease
VERSION=$(grep 'versionName' app/build.gradle.kts | head -1 | sed -E 's/.*"(.*)".*/\1/')
APK="app/build/outputs/apk/release/app-release.apk"
OUT="dist/veil-${VERSION}.apk"
mkdir -p dist
cp "$APK" "$OUT"
echo "Built $OUT"