Some checks failed
RKNnoVPN Linux and Android CI / policy-and-go (push) Successful in 14s
RKNnoVPN Linux and Android CI / android-debug (push) Successful in 2m5s
Full build and Forgejo release / Privacy Lint (push) Successful in 3s
Full build and Forgejo release / Local-first Runtime Guardrail (push) Successful in 2s
Full build and Forgejo release / Go Tests (push) Successful in 13s
Full build and Forgejo release / Resolve sing-box release (push) Successful in 1s
Full build and Forgejo release / Resolve Xray-core release (push) Successful in 1s
Full build and Forgejo release / Android Guardrails & Tests (push) Failing after 16s
Full build and Forgejo release / Build APK (push) Has been skipped
Full build and Forgejo release / Build Runtime CLI (arm64) (push) Successful in 11s
Full build and Forgejo release / Build Runtime CLI (armv7) (push) Successful in 11s
Full build and Forgejo release / Build sing-box (arm64) (push) Failing after 2s
Full build and Forgejo release / Build sing-box (armv7) (push) Failing after 2s
Full build and Forgejo release / Build Xray-core (arm64) (push) Failing after 3s
Full build and Forgejo release / Build Xray-core (armv7) (push) Failing after 2s
Full build and Forgejo release / Build Magisk Module (push) Has been skipped
Full build and Forgejo release / Create Release (push) Has been skipped
6.2 KiB
6.2 KiB
RKNnoVPN Local-First Code Migration Plan
This plan is the guardrail for the daemon-to-local-first migration. The goal is to keep the existing UI and user-visible features while changing ownership and execution boundaries:
- APK owns profile, settings, subscriptions, and update cache.
- APK performs subscription and release network requests directly.
- Root/module runtime is only an executor for apply/start/stop/restart/reset, logs, and module staging.
- Old daemon IPC compatibility is not part of the active APK or runtime path.
Non-Breaking Feature Contract
The migration must preserve:
- direct link import;
- QR/import sheet;
- subscriptions;
- app list and package picker;
- routing modes;
- bypassRussia;
- DNS settings;
- always-direct apps;
- sharing/tethering settings;
- update check and download;
- start, stop, restart, reset;
- logs;
- node tests.
Backend ownership changes are expected:
- daemon profile get/apply/import calls become APK-local profile operations:
AppProfileStoreis dumb persistence, whileProfileRepositoryis the only owner/publisher of observable profile state; - daemon subscription preview/refresh becomes APK-side subscription fetching and
parsing through
ApkSubscriptionClient; - daemon update check/download becomes APK-side Forgejo release fetching through
ApkReleaseUpdater; - daemon backend controls become explicit root CLI commands through
RuntimeController; - daemon logs become
RuntimeController.logs.
Phase 1: APK Boot Without Runtime
Required code:
app/app/src/main/AndroidManifest.xmlapp/app/src/main/kotlin/com/rknnovpn/panel/profile/AppProfile.ktapp/app/src/main/kotlin/com/rknnovpn/panel/storage/AppProfileStore.ktapp/app/src/main/kotlin/com/rknnovpn/panel/storage/FileAppProfileStore.ktapp/app/src/main/kotlin/com/rknnovpn/panel/repository/ProfileRepository.kt
Acceptance:
- APK opens with no module installed.
- APK profile is created and stored under app-private storage.
- Profile editing and imports do not require root or runtime compatibility.
AppProfileStoreremains a persistence-only contract: noStateFlow, no long-livedprofile,warning, orerrorproperties, and no UI/runtime state ownership.ProfileRepositoryis the single owner of profile-related observable state: profile, freshness/loading, error, and notice flows are published from the repository, not from storage.
Phase 2: Remove Active IPC Compatibility
Forbidden in active code paths:
compat.checkipc.contractGeneratedDaemonContractrequireCompatiblecompatibilityCache- repair cooldowns
- stable invalid response recovery
- automatic module repair after parse errors
- daemon-owned profile as APK startup dependency
Acceptance:
- Forbidden strings are absent from
app,runtime,module,tools, and CI scripts except for the local-first checker that defines the forbidden patterns.
Phase 3: APK-Side Subscriptions
Required code:
app/app/src/main/kotlin/com/rknnovpn/panel/subscription/ApkSubscriptionClient.ktapp/app/src/main/kotlin/com/rknnovpn/panel/subscription/SubscriptionRepository.kt
Acceptance:
- APK downloads subscription URLs itself.
- APK parses payloads with
LinkParser. - APK saves imported nodes into
AppProfileStore. - Runtime/module absence does not block preview, commit, refresh, or refresh all.
Phase 4: APK-Side Updates
Required code:
app/app/src/main/kotlin/com/rknnovpn/panel/update/ApkReleaseUpdater.ktapp/app/src/main/kotlin/com/rknnovpn/panel/update/UpdateRepository.kt- settings update flow.
Acceptance:
- APK checks the latest release in the project Forgejo itself.
- APK downloads
module.zip,panel.apk, andSHA256SUMS. - APK verifies SHA256 before exposing downloaded update paths.
- Runtime is only needed to stage/install the root/module part.
Phase 5: Root CLI Runtime
Required code:
app/app/src/main/kotlin/com/rknnovpn/panel/runtime/RuntimeController.ktapp/app/src/main/kotlin/com/rknnovpn/panel/root/RootCommandExecutor.ktruntime/cmd/rknnovpn-runtime/main.go
Required commands:
rknnovpn-runtime versionrknnovpn-runtime statusrknnovpn-runtime apply <profile.json>rknnovpn-runtime startrknnovpn-runtime stoprknnovpn-runtime restartrknnovpn-runtime resetrknnovpn-runtime logsrknnovpn-runtime stage-module <module.zip>
Runtime actions are enabled only when:
- runtime release version equals APK
BuildConfig.VERSION_NAME; - runtime version code equals APK
BuildConfig.VERSION_CODE; - runtime
wireEpochequals1.
If runtime is missing, old, root-denied, or returns malformed JSON, APK local features remain usable and runtime controls fail as disabled/unavailable actions.
Phase 6: Runtime Profile Renderer
Required code:
app/app/src/main/kotlin/com/rknnovpn/panel/runtime/RuntimeProfileRenderer.kt
Acceptance:
- APK profile is converted into runtime config only during apply/start.
- APK boot does not depend on runtime config schema.
- Routing, DNS, sharing, always-direct apps, app groups, bypassRussia, and active node selection are carried into the runtime projection.
- Runtime projection does not carry APK-only subscription registry, share-link fallback data, or stale node metadata.
Phase 7: Tests And Validation
Expected coverage areas:
- APK profile file store behavior.
- Profile repository local-first behavior.
- Profile store/repository boundary: store returns operation results only, repository publishes profile/error/notice state.
- Subscription payload parsing and repository behavior.
- Release metadata/checksum parsing and update repository behavior.
- Runtime availability/protocol handling for missing, mismatched, and malformed runtime responses.
- Runtime profile rendering for routing, DNS, sharing, always-direct, app groups, and stale-node filtering.
Required validation commands:
./gradlew :app:assembleDebug
./gradlew :app:testDebugUnitTest
scripts/ci/check_local_first_runtime.sh
scripts/ci/check_apk_privacy.sh
rg "compat\\.check|ipc\\.contract|GeneratedDaemonContract|requireCompatible|compatibilityCache|Repair cooldown|stableInvalidResponse" app runtime module .forgejo
Release APK privacy validation requires a built APK:
scripts/ci/check_release_apk_privacy.sh path/to/app.apk