From 73c5621fd3388ab5a32d25e51d33092b2eccf7c8 Mon Sep 17 00:00:00 2001 From: Hidden-Node Date: Mon, 20 Jul 2026 13:44:52 +0330 Subject: [PATCH 1/2] docs(readme): correct CI workflow list to match actual files Replace the stale GitHub Workflows subsection. Drop the non-existent release.yml reference, drop the false "Checks code quality" claim, and accurately describe android-ci.yml and release-manual.yml. --- README.md | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index c112ed1..8eb7f66 100644 --- a/README.md +++ b/README.md @@ -309,20 +309,30 @@ Output: `app/build/outputs/bundle/release/app-release.aab` ### GitHub Workflows **`.github/workflows/android-ci.yml`** -- Runs on every commit -- Builds debug APK -- Runs unit tests -- Checks code quality - -**`.github/workflows/release.yml`** -- Triggered on version tag (v*.*.*) -- Builds signed release APK/AAB -- Creates GitHub Release with artifacts -- Requires signing secrets +- Runs on every push to `main`/`master` and on every pull request +- Builds the Go mobile AAR (`bash ./android/build_go_mobile.sh`) +- Builds the debug APK (`./gradlew :app:assembleDebug`) +- Runs unit tests (`./gradlew :app:testDebugUnitTest`) — added in + the test-baseline work; if your branch is older, this step may not + be present yet +- Uploads the APK and AAR as workflow artifacts for download from the + Actions tab **`.github/workflows/release-manual.yml`** -- Manual trigger for releases -- Same as release.yml but on-demand +- Manually triggered (`workflow_dispatch`) with a required + `tag_name` input (e.g. `v1.2.3`) and optional `release_name` / + `make_latest` inputs +- Builds a signed, split-by-ABI release APK set plus a universal APK + and a Go AAR; requires the `ANDROID_KEYSTORE_BASE64`, + `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`, and + `ANDROID_KEY_PASSWORD` repo secrets +- Creates a GitHub Release with `softprops/action-gh-release@v2` + and uploads the AAR and all APKs as release assets + +> There is **no** `release.yml` workflow. Tag-triggered releases are +> performed by manually dispatching `release-manual.yml` with the +> desired tag. If you need a tag-triggered workflow, add one as a +> sibling to `release-manual.yml` and update this section. ### Secrets Required From 14e2c1bdec8c04e277654cb16e8fe8d039e581ff Mon Sep 17 00:00:00 2001 From: Hidden-Node Date: Mon, 20 Jul 2026 14:00:02 +0330 Subject: [PATCH 2/2] docs: add AGENTS.md for agent-assisted development Operating context for AI agents: project overview, architecture, build/verify commands, Kotlin/Go conventions, and common footguns. --- AGENTS.md | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..13b9808 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,189 @@ +# AGENTS.md + +Operating context for AI agents (opencode, Cursor, Claude Code, etc.) +working on this repository. Read this file first; it covers what the +project is, how to build/verify it, and the conventions to follow. + +## What this repo is + +`GooseRelayVPN-AndroidClient` is the Android client for the +GooseRelayVPN project. It wraps the upstream Go core (in `internal/`, +shared with the server) in an Android `VpnService` and exposes a +Jetpack Compose UI for VPN lifecycle, profile management, logs, and +settings. + +Upstream Go core: +This client: + +## Architecture (Android side) + +``` +┌─────────────────────────────────────────────┐ +│ Android UI Layer (Compose) │ +│ Home │ Profiles │ Settings │ Logs │ Info │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ ViewModels & Repository Layer │ +│ Room │ DataStore │ Hilt DI │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ VPN Service & Go Core │ +│ tun2socks │ GooseRelay Core (Go mobile) │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ Network Layer │ +│ SOCKS5 → Google Apps Script → VPS exit │ +└─────────────────────────────────────────────┘ +``` + +- `android/` — Gradle project (`settings.gradle.kts`, `build.gradle.kts`, `app/`). Kotlin 2.1.0, + AGP 8.13.0, `minSdk=21`, `targetSdk=36`, `compileSdk=36`, JVM 17. +- `android/app/src/main/java/com/gooserelay/gooserelayvpn/` — Kotlin source: + - `service/` — `GooseRelayVpnService` (the VpnService), + `VpnTileService` (Quick Settings tile), `BootReceiver` (boot, + currently a no-op). + - `ui/` — Compose screens and ViewModels, split by feature + (`home/`, `profiles/`, `settings/`, `logs/`, `info/`, + `navigation/`). + - `data/local/` — Room (`AppDatabase` v3, `ProfileEntity`, + `ProfileDao`). + - `data/repository/` — `ProfileRepository` (Hilt `@Singleton`). + - `dns/` — Kotlin FakeDNS interceptor (separate from the Go-side + FakeDNS in `mobile/tun/`). + - `di/` — Hilt `AppModule`. + - `util/` — `VpnManager` (singleton bridge between UI and Go + core), `ConfigGenerator` (profile → JSON config), + `GlobalSettingsStore` (DataStore-backed). +- `mobile/` — Go mobile bridge, gomobile bind target: + - `mobile/mobile.go` — `StartClient` / `StopClient` / + `StartTun` / `StopTun` / `StartTunBridge` / `StopTunBridge` + exported to Kotlin via `mobile.Mobile.*`. + - `mobile/tun/` — FakeDNS proxy (`fakedns_proxy.go`), + DNS mapper (`dns_mapper.go`), `tun_api.go` (exported + `StartFakeDNSProxy` etc.). +- `internal/` — upstream Go core (carrier, session, socks, config, + etc.). **Treat as read-only when working on the Android client.** +- `apps_script/` — Google Apps Script deployment that fronts the + carrier traffic over Google infrastructure. + +## How to build + +```bash +# 1. Build the Go mobile AAR (requires Go 1.25+, NDK installed). +# On Windows: use build_go_mobile.bat; on Unix: build_go_mobile.sh. +bash ./android/build_go_mobile.sh +# Output: android/app/libs/gooserelayvpn.aar + +# 2. Build the debug APK. +cd android +./gradlew :app:assembleDebug +# Output: android/app/build/outputs/apk/debug/GooseRelayVPN.apk + +# 3. Build the release AAB (requires signing config in local.properties). +./gradlew :app:bundleRelease +``` + +## How to verify + +```bash +cd android + +# Unit tests (JVM, no emulator needed). +./gradlew :app:testDebugUnitTest --stacktrace + +# Lint. +./gradlew :app:lintDebug + +# Compile-only fast check. +./gradlew :app:compileDebugKotlin --stacktrace + +# Go-side vet + format. +go vet ./mobile/... +gofmt -l mobile/ +``` + +Instrumented tests (`./gradlew :app:connectedDebugAndroidTest`) +require a connected emulator or device; CI does not run them. + +## Conventions + +### Kotlin + +- Style: `kotlin.code.style=official` (see + `android/gradle.properties`). +- ViewModels: `@HiltViewModel` + constructor injection. See + `ui/profiles/ProfilesViewModel.kt` as the canonical example. +- Singletons: `object` declarations for cross-cutting state + (`VpnManager`, `ConfigGenerator`, `GlobalSettingsStore`). +- Error handling: `runCatching { ... }.onFailure { ... }` for + non-fatal failures; `try { ... } catch (_: Exception) {}` only + for true background noise (e.g. closing sockets during shutdown). +- Coroutines: `CoroutineScope(SupervisorJob() + Dispatchers.X)`. + Use `withContext(Dispatchers.IO)` for thread hops; avoid nested + `launch` blocks inside a coroutine. Guard with `isActive` after + suspending operations. +- Logging: `android.util.Log` for system logcat; `VpnManager.appendLog` + for user-visible log lines (shown in the Logs screen, with a + 2000-line ring buffer). **Never log credentials** — + `ProfileEntity.socksPass`, `tunnelKey`, `scriptKeysText` (and their + JSON-serialized forms in `ConfigGenerator.exportProfileJson`) must + not appear in logs. + +### Go (in `mobile/`) + +- `gofmt`-clean; `go vet ./mobile/...` returns no findings. +- Exported functions PascalCase (gomobile convention). Logger uses + `log.Printf("[prefix] ...")` with brackets, e.g. `[TUN-API]`, + `[client]`, `[socks]`. +- Mutex discipline: `mu` guards app state (`running`, `tunActive`, + `tunBridgeRunning`, `cancelFn`, `socksLn`, `clientDone`). `engineMu` + guards the tun2socks engine Start/Stop. Take them in the same + order every time to avoid deadlocks. +- Errors wrapped with `fmt.Errorf("...: %w", err)`. + +### Build / CI + +- Commit style: `type(scope): subject` (conventional commits). + Examples from `git log`: `fix(android): ...`, + `feat(android): ...`, `perf(android): ...`, `style(ui): ...`, + `chore(mobile): ...`, `docs(readme): ...`, `ci(android): ...`. +- Don't push or open PRs unless the operator instructed it. +- Don't commit secrets. Signing keys live in + `$ANDROID_KEYSTORE_PATH` env var or in CI secrets. + +## Things that are easy to get wrong + +- **Credentials in logs.** Before adding any + `VpnManager.appendLog(...)` or `Log.d(...)` call that includes + profile data, redact `socksUser`, `socksPass`, `tunnelKey`, and + `scriptKeysText`. The exported JSON config object contains all + four. +- **`fallbackToDestructiveMigration`.** Don't add new Room schema + versions without a real `Migration` entry in + `ProfileMigrations.ALL` (see `data/local/ProfileMigrations.kt`). + A missing migration will crash the app at launch for any user + whose DB schema version is lower than the new one. +- **`engine.Stop()` panics.** The `recover()` blocks in + `mobile.go`'s `StopTun`/`StopTunBridge` are load-bearing — they + mask a tun2socks panic on stop that previously SIGSEGV'd the app + (commit `41c3eef`). Don't remove them. +- **`network_security_config.xml` allows cleartext globally.** + Tightening this is on the roadmap; don't add new `http://` + fetches to production paths without revisiting this config. +- **Go mobile rebuild.** After any `mobile/*.go` change, run + `bash ./android/build_go_mobile.sh` *before* `./gradlew :app:assembleDebug` + — the Gradle build loads `android/app/libs/gooserelayvpn.aar` as a + file dependency and will silently test against a stale AAR if the + AAR isn't refreshed. + +## Running the plans in `plans/` + +The `plans/` directory contains self-contained implementation plans. +Each plan's filename is `NNN-short-slug.md`; `plans/README.md` is the +index (priority order, dependencies, status). Read the full plan +before starting, honor the STOP conditions, update the status row in +`plans/README.md` when done. Don't improvise when reality doesn't +match the plan — report back to the operator.