mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-01 09:26:16 +00:00
Compare commits
2 Commits
92dee2d2d0
...
198fecea11
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
198fecea11 | ||
| ab67c981dd |
52
.github/workflows/build-android-apk.yml
vendored
Normal file
52
.github/workflows/build-android-apk.yml
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
# Build the SecuBox Android ToolBox client APK (#531).
|
||||
# No Gradle wrapper jar is committed (text-only scaffold) — setup-gradle
|
||||
# provides Gradle ; setup-android provides the SDK. Produces a debug APK
|
||||
# artifact (sideloadable). Release signing is a follow-up (needs a
|
||||
# keystore secret).
|
||||
name: build-android-apk
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths: [ "clients/android-toolbox/**", ".github/workflows/build-android-apk.yml" ]
|
||||
tags: [ "android-v*" ]
|
||||
pull_request:
|
||||
paths: [ "clients/android-toolbox/**" ]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
defaults:
|
||||
run:
|
||||
working-directory: clients/android-toolbox
|
||||
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@v3
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
gradle-version: "8.9"
|
||||
|
||||
- name: Build debug APK
|
||||
run: gradle :app:assembleDebug --no-daemon --stacktrace
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: secubox-toolbox-android-debug
|
||||
path: clients/android-toolbox/app/build/outputs/apk/debug/*.apk
|
||||
if-no-files-found: error
|
||||
11
clients/android-toolbox/.gitignore
vendored
Normal file
11
clients/android-toolbox/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Android / Gradle build artifacts
|
||||
.gradle/
|
||||
build/
|
||||
app/build/
|
||||
local.properties
|
||||
*.apk
|
||||
*.aab
|
||||
*.keystore
|
||||
*.jks
|
||||
.idea/
|
||||
captures/
|
||||
39
clients/android-toolbox/README.md
Normal file
39
clients/android-toolbox/README.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<!-- SPDX-License-Identifier: LicenseRef-CMSD-1.0 -->
|
||||
# SecuBox Android ToolBox client (#531)
|
||||
|
||||
One-tap **R3 onboarding** for the VILLAGE3B cabine : install the CA,
|
||||
import the WireGuard profile, verify the tunnel, then open the live
|
||||
*cartographie sociale*. Replaces the manual Android tutorial.
|
||||
|
||||
## Flow
|
||||
1. **Discover** — scan the kbin QR or type the booth host (`kbin.gk2.secubox.in`).
|
||||
2. **Install CA** — downloads `/wg/ca.crt`, launches the Android cert-install intent (`KeyChain.createInstallIntent`).
|
||||
3. **Import profile** — downloads `/wg/profile/new`, hands the `.conf` to the WireGuard app via `FileProvider` + `ACTION_VIEW`.
|
||||
4. **Verify** — polls `/wg/r3-check` → "Tunnel R3 actif ✓".
|
||||
5. **Live metrics** — opens `/social/me` (cartographie sociale).
|
||||
|
||||
## Build
|
||||
No Gradle wrapper jar is committed (text-only scaffold). CI builds it:
|
||||
- **GitHub Actions** `build-android-apk.yml` → debug APK artifact.
|
||||
Locally (with Android SDK + Gradle 8.9 + JDK 17):
|
||||
```bash
|
||||
cd clients/android-toolbox
|
||||
gradle :app:assembleDebug # app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
## Constraints (MVP)
|
||||
- Android 11+ restricts **user CA trust** ; the app launches the install
|
||||
intent + guides the manual confirm step. Browsers on the device need
|
||||
the CA trusted for the mitm R3 break — this is the known Android
|
||||
limitation (documented, not yet automated).
|
||||
- WireGuard profile import uses the **official WireGuard app** (no embedded
|
||||
tunnel in the MVP) — most reliable, no extra native deps.
|
||||
- Debug APK is self-signed (sideload). Release signing (published
|
||||
fingerprint, served from the toolbox) is a follow-up needing a keystore
|
||||
secret in CI.
|
||||
|
||||
## Tech
|
||||
Kotlin + Jetpack Compose, minSdk 26 / targetSdk 34. API client is plain
|
||||
`HttpURLConnection` (no Retrofit/OkHttp) to keep deps + CI minimal.
|
||||
|
||||
Package `in.secubox.toolbox`. License `LicenseRef-CMSD-1.0`.
|
||||
49
clients/android-toolbox/app/build.gradle.kts
Normal file
49
clients/android-toolbox/app/build.gradle.kts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "in.secubox.toolbox"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "in.secubox.toolbox"
|
||||
minSdk = 26
|
||||
targetSdk = 34
|
||||
versionCode = 1
|
||||
versionName = "0.1.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
// Signed in CI with a published-fingerprint key (sideload APK,
|
||||
// no Play Store). Debug builds are self-signed by the SDK.
|
||||
signingConfig = signingConfigs.findByName("release")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions { jvmTarget = "17" }
|
||||
buildFeatures { compose = true }
|
||||
composeOptions { kotlinCompilerExtensionVersion = "1.5.14" }
|
||||
packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
val composeBom = platform("androidx.compose:compose-bom:2024.06.00")
|
||||
implementation(composeBom)
|
||||
implementation("androidx.core:core-ktx:1.13.1")
|
||||
implementation("androidx.activity:activity-compose:1.9.0")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.3")
|
||||
implementation("androidx.compose.ui:ui")
|
||||
implementation("androidx.compose.ui:ui-graphics")
|
||||
implementation("androidx.compose.material3:material3")
|
||||
implementation("androidx.compose.material:material-icons-extended")
|
||||
// No Retrofit/OkHttp — the API client uses HttpURLConnection to keep
|
||||
// the dependency graph (and CI) minimal.
|
||||
}
|
||||
41
clients/android-toolbox/app/src/main/AndroidManifest.xml
Normal file
41
clients/android-toolbox/app/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- SPDX-License-Identifier: LicenseRef-CMSD-1.0 -->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<!-- Query the WireGuard app so we can hand it the generated profile. -->
|
||||
<queries>
|
||||
<package android:name="com.wireguard.android" />
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:theme="@style/Theme.SecuBoxToolBox"
|
||||
android:supportsRtl="true">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- FileProvider to share the downloaded CA + WG .conf with the
|
||||
system cert installer / the WireGuard app. -->
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
// SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
// Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
//
|
||||
// SecuBox-Deb :: Android ToolBox client (#531)
|
||||
// One-tap R3 onboarding : discover -> install CA -> import WG profile ->
|
||||
// verify tunnel -> live cartographie sociale. Replaces the manual
|
||||
// multi-step Android tutorial.
|
||||
package `in`.secubox.toolbox
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.security.KeyChain
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.content.FileProvider
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
private val Cosmos = Color(0xFF0A0A0F)
|
||||
private val Gold = Color(0xFFC9A84C)
|
||||
private val Cyan = Color(0xFF00D4FF)
|
||||
private val Matrix = Color(0xFF00FF41)
|
||||
private val Cinnabar = Color(0xFFE63946)
|
||||
private val TextPrimary = Color(0xFFE8E6D9)
|
||||
|
||||
enum class Step { Discover, InstallCa, ImportProfile, Verify, Done }
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent { OnboardApp() }
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun OnboardApp() {
|
||||
val ctx = androidx.compose.ui.platform.LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
var host by remember { mutableStateOf("kbin.gk2.secubox.in") }
|
||||
var step by remember { mutableStateOf(Step.Discover) }
|
||||
var status by remember { mutableStateOf("") }
|
||||
var busy by remember { mutableStateOf(false) }
|
||||
var onTunnel by remember { mutableStateOf(false) }
|
||||
var peerIp by remember { mutableStateOf<String?>(null) }
|
||||
val api = remember(host) { ToolboxApi(host) }
|
||||
|
||||
MaterialTheme(colorScheme = darkColorScheme(
|
||||
primary = Gold, secondary = Cyan, background = Cosmos, surface = Cosmos,
|
||||
onBackground = TextPrimary, onSurface = TextPrimary,
|
||||
)) {
|
||||
Surface(Modifier.fillMaxSize(), color = Cosmos) {
|
||||
Column(
|
||||
Modifier.fillMaxSize().padding(20.dp).verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text("📡 VILLAGE3B", color = Gold, fontSize = 26.sp, fontWeight = FontWeight.Bold)
|
||||
Text("ToolBoX — installation R3", color = TextPrimary, fontSize = 14.sp)
|
||||
Spacer(Modifier.height(20.dp))
|
||||
|
||||
Stepper(step)
|
||||
Spacer(Modifier.height(20.dp))
|
||||
|
||||
when (step) {
|
||||
Step.Discover -> {
|
||||
OutlinedTextField(
|
||||
value = host, onValueChange = { host = it },
|
||||
label = { Text("Borne (kbin…)") }, singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text("Scanne le QR de la cabine ou saisis l'adresse, puis Suivant.",
|
||||
color = TextPrimary, fontSize = 12.sp)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
BigButton("Suivant", busy) {
|
||||
busy = true; status = "Vérification de la borne…"
|
||||
scope.launch {
|
||||
val ok = withContext(Dispatchers.IO) { api.reachable() }
|
||||
busy = false
|
||||
if (ok) { step = Step.InstallCa; status = "" }
|
||||
else status = "Borne injoignable — vérifie l'adresse / le réseau."
|
||||
}
|
||||
}
|
||||
}
|
||||
Step.InstallCa -> {
|
||||
StepBody("1 · Installer le certificat (CA R3)",
|
||||
"Le certificat permet l'analyse TLS de la cabine. " +
|
||||
"Android te demandera de confirmer (Paramètres → Sécurité → " +
|
||||
"Certificat utilisateur).")
|
||||
BigButton("Installer le certificat", busy) {
|
||||
busy = true
|
||||
scope.launch {
|
||||
try {
|
||||
val ca = withContext(Dispatchers.IO) { api.downloadCa(ctx.cacheDir) }
|
||||
val der = ca.readBytes()
|
||||
val intent = KeyChain.createInstallIntent().apply {
|
||||
putExtra(KeyChain.EXTRA_CERTIFICATE, der)
|
||||
putExtra(KeyChain.EXTRA_NAME, "VILLAGE3B ToolBoX CA")
|
||||
}
|
||||
ctx.startActivity(intent)
|
||||
status = "Confirme l'installation dans Android, puis Suivant."
|
||||
} catch (e: Exception) {
|
||||
status = "Échec téléchargement CA : ${e.message}"
|
||||
} finally { busy = false }
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(8.dp))
|
||||
TextButton(onClick = {
|
||||
ctx.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
|
||||
}) { Text("Ouvrir Paramètres sécurité", color = Cyan) }
|
||||
Spacer(Modifier.height(8.dp))
|
||||
BigButton("Suivant", false) { step = Step.ImportProfile; status = "" }
|
||||
}
|
||||
Step.ImportProfile -> {
|
||||
StepBody("2 · Importer le profil WireGuard",
|
||||
"On génère un profil dédié et on l'ouvre dans l'app WireGuard. " +
|
||||
"Active le tunnel dans WireGuard, puis reviens ici.")
|
||||
BigButton("Importer dans WireGuard", busy) {
|
||||
busy = true
|
||||
scope.launch {
|
||||
try {
|
||||
val conf = withContext(Dispatchers.IO) { api.downloadProfile(ctx.cacheDir) }
|
||||
val uri = FileProvider.getUriForFile(
|
||||
ctx, "${ctx.packageName}.fileprovider", conf)
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(uri, "application/octet-stream")
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
setPackage("com.wireguard.android")
|
||||
}
|
||||
try { ctx.startActivity(intent) }
|
||||
catch (_: Exception) {
|
||||
// WireGuard not installed -> open Play / generic chooser.
|
||||
ctx.startActivity(Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse("https://play.google.com/store/apps/details?id=com.wireguard.android")))
|
||||
status = "Installe l'app WireGuard puis réessaie."
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
status = "Échec profil : ${e.message}"
|
||||
} finally { busy = false }
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(8.dp))
|
||||
BigButton("Suivant", false) { step = Step.Verify; status = "" }
|
||||
}
|
||||
Step.Verify -> {
|
||||
StepBody("3 · Vérifier le tunnel R3",
|
||||
"Active le tunnel dans WireGuard, puis vérifie.")
|
||||
BigButton("Vérifier", busy) {
|
||||
busy = true; status = "Vérification…"
|
||||
scope.launch {
|
||||
val (t, ip) = withContext(Dispatchers.IO) { api.r3Check() }
|
||||
busy = false; onTunnel = t; peerIp = ip
|
||||
if (t) { step = Step.Done; status = "" }
|
||||
else status = "Pas encore sur le tunnel — active WireGuard puis réessaie."
|
||||
}
|
||||
}
|
||||
}
|
||||
Step.Done -> {
|
||||
Icon(Icons.Filled.CheckCircle, null, tint = Matrix, modifier = Modifier.size(56.dp))
|
||||
Text("Tunnel R3 actif ✓", color = Matrix, fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
||||
peerIp?.let { Text("pair : $it", color = TextPrimary, fontSize = 12.sp) }
|
||||
Spacer(Modifier.height(20.dp))
|
||||
BigButton("🕸️ Voir ma cartographie sociale", false) {
|
||||
ctx.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(api.socialMeUrl)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (status.isNotBlank()) {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Text(status, color = if (status.contains("Échec") || status.contains("injoignable")) Cinnabar else Cyan,
|
||||
fontSize = 13.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Stepper(cur: Step) {
|
||||
val steps = listOf(Step.Discover, Step.InstallCa, Step.ImportProfile, Step.Verify, Step.Done)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
steps.forEach { s ->
|
||||
val done = s.ordinal < cur.ordinal
|
||||
val active = s == cur
|
||||
Box(Modifier.size(if (active) 14.dp else 10.dp)) {
|
||||
Surface(shape = MaterialTheme.shapes.small,
|
||||
color = when { done -> Matrix; active -> Gold; else -> Color(0xFF333333) },
|
||||
modifier = Modifier.fillMaxSize()) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StepBody(title: String, body: String) {
|
||||
Text(title, color = Gold, fontSize = 16.sp, fontWeight = FontWeight.Bold)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(body, color = TextPrimary, fontSize = 13.sp)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BigButton(label: String, busy: Boolean, onClick: () -> Unit) {
|
||||
Button(onClick = onClick, enabled = !busy, modifier = Modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Gold, contentColor = Cosmos)) {
|
||||
if (busy) CircularProgressIndicator(Modifier.size(18.dp), color = Cosmos, strokeWidth = 2.dp)
|
||||
else Text(label, fontWeight = FontWeight.Bold)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
// Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
package `in`.secubox.toolbox
|
||||
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
* Minimal HTTP client for the SecuBox ToolBox R3 endpoints. Uses
|
||||
* HttpURLConnection (no Retrofit/OkHttp) to keep the dependency graph
|
||||
* and CI minimal. All calls are blocking — invoke off the main thread.
|
||||
*
|
||||
* Endpoints (served on the kbin vhost, e.g. kbin.gk2.secubox.in) :
|
||||
* GET /wg/ca.crt -> CA root cert (Android DER/PEM)
|
||||
* GET /wg/profile/new -> wg-quick .conf (one fresh peer per call)
|
||||
* GET /wg/r3-check -> {"tunnel": bool, "peer_ip": "10.99.1.x"}
|
||||
* GET /social/me -> per-client cartographie sociale (web view)
|
||||
*/
|
||||
class ToolboxApi(rawHost: String) {
|
||||
|
||||
// Accept "kbin.gk2.secubox.in", "https://kbin…", trailing slashes…
|
||||
val base: String = rawHost.trim()
|
||||
.removePrefix("https://").removePrefix("http://")
|
||||
.trim('/')
|
||||
.let { "https://$it" }
|
||||
|
||||
val socialMeUrl: String get() = "$base/social/me"
|
||||
|
||||
private fun open(path: String): HttpURLConnection =
|
||||
(URL("$base$path").openConnection() as HttpURLConnection).apply {
|
||||
connectTimeout = 8000
|
||||
readTimeout = 12000
|
||||
setRequestProperty("User-Agent", "secubox-toolbox-android/0.1")
|
||||
instanceFollowRedirects = true
|
||||
}
|
||||
|
||||
/** Download a file (CA or WG profile) into the app cache, return it. */
|
||||
fun download(path: String, outName: String, cacheDir: File): File {
|
||||
val c = open(path)
|
||||
try {
|
||||
if (c.responseCode !in 200..299)
|
||||
throw RuntimeException("HTTP ${c.responseCode} for $path")
|
||||
val out = File(cacheDir, outName)
|
||||
c.inputStream.use { input -> out.outputStream().use { input.copyTo(it) } }
|
||||
return out
|
||||
} finally { c.disconnect() }
|
||||
}
|
||||
|
||||
fun downloadCa(cacheDir: File): File = download("/wg/ca.crt", "village3b-ca.crt", cacheDir)
|
||||
fun downloadProfile(cacheDir: File): File = download("/wg/profile/new", "village3b-toolbox.conf", cacheDir)
|
||||
|
||||
/** R3 tunnel status. Returns (onTunnel, peerIp?). */
|
||||
fun r3Check(): Pair<Boolean, String?> {
|
||||
val c = open("/wg/r3-check")
|
||||
try {
|
||||
if (c.responseCode !in 200..299) return false to null
|
||||
val body = c.inputStream.bufferedReader().readText()
|
||||
val j = JSONObject(body)
|
||||
return j.optBoolean("tunnel", false) to j.optString("peer_ip", null)
|
||||
} catch (_: Exception) {
|
||||
return false to null
|
||||
} finally { c.disconnect() }
|
||||
}
|
||||
|
||||
/** Cheap reachability probe for the discover step. */
|
||||
fun reachable(): Boolean = try {
|
||||
val c = open("/wg/r3-check"); val ok = c.responseCode in 200..499; c.disconnect(); ok
|
||||
} catch (_: Exception) { false }
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp" android:height="108dp"
|
||||
android:viewportWidth="108" android:viewportHeight="108">
|
||||
<path android:fillColor="#C9A84C"
|
||||
android:pathData="M54,40c-10,0 -19,6 -24,14c5,8 14,14 24,14s19,-6 24,-14c-5,-8 -14,-14 -24,-14zM54,64a10,10 0 1,1 0,-20a10,10 0 0,1 0,20z" />
|
||||
<path android:fillColor="#00D4FF"
|
||||
android:pathData="M54,49a5,5 0 1,0 0,10a5,5 0 0,0 0,-10z" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#0A0A0F</color>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- SPDX-License-Identifier: LicenseRef-CMSD-1.0 -->
|
||||
<resources>
|
||||
<string name="app_name">VILLAGE3B ToolBoX</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- SPDX-License-Identifier: LicenseRef-CMSD-1.0 -->
|
||||
<resources>
|
||||
<style name="Theme.SecuBoxToolBox" parent="android:Theme.Material.NoActionBar">
|
||||
<item name="android:statusBarColor">#0A0A0F</item>
|
||||
<item name="android:navigationBarColor">#0A0A0F</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- SPDX-License-Identifier: LicenseRef-CMSD-1.0 -->
|
||||
<paths>
|
||||
<cache-path name="cache" path="." />
|
||||
</paths>
|
||||
5
clients/android-toolbox/build.gradle.kts
Normal file
5
clients/android-toolbox/build.gradle.kts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
plugins {
|
||||
id("com.android.application") version "8.5.2" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.9.24" apply false
|
||||
}
|
||||
5
clients/android-toolbox/gradle.properties
Normal file
5
clients/android-toolbox/gradle.properties
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
android.useAndroidX=true
|
||||
kotlin.code.style=official
|
||||
android.nonTransitiveRClass=true
|
||||
18
clients/android-toolbox/settings.gradle.kts
Normal file
18
clients/android-toolbox/settings.gradle.kts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
// SecuBox-Deb :: Android ToolBox client (#531)
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
rootProject.name = "SecuBoxToolBox"
|
||||
include(":app")
|
||||
Loading…
Reference in New Issue
Block a user