mirror of
https://github.com/wgtunnel/desktop.git
synced 2026-06-02 00:29:09 +02:00
initial commit
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
|||||||
|
*.iml
|
||||||
|
.kotlin
|
||||||
|
.gradle
|
||||||
|
**/build/
|
||||||
|
xcuserdata
|
||||||
|
!src/**/build/
|
||||||
|
local.properties
|
||||||
|
output
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
captures
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx
|
||||||
|
*.xcodeproj/*
|
||||||
|
!*.xcodeproj/project.pbxproj
|
||||||
|
!*.xcodeproj/xcshareddata/
|
||||||
|
!*.xcodeproj/project.xcworkspace/
|
||||||
|
!*.xcworkspace/contents.xcworkspacedata
|
||||||
|
**/xcshareddata/WorkspaceSettings.xcsettings
|
||||||
|
node_modules/
|
||||||
|
composeApp/generated.conveyor.conf
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[submodule "daemon/winsw"]
|
||||||
|
path = daemon/winsw
|
||||||
|
url = https://github.com/wgtunnel/winsw
|
||||||
|
[submodule "tunnel/tools/amneziawg-tools"]
|
||||||
|
path = tunnel/tools/amneziawg-tools
|
||||||
|
url = https://github.com/amnezia-vpn/amneziawg-tools
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# WG Tunnel - Desktop
|
||||||
|
|
||||||
|
A WIP project for WG Tunnel desktop.
|
||||||
|
|
||||||
|
## Supported Platforms
|
||||||
|
- macOS (Future)
|
||||||
|
- Windows
|
||||||
|
- Linux
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// build.gradle.kts
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.composeHotReload) apply false
|
||||||
|
alias(libs.plugins.jetbrainsCompose) apply false
|
||||||
|
alias(libs.plugins.composeCompiler) apply false
|
||||||
|
alias(libs.plugins.kotlinMultiplatform) apply false
|
||||||
|
alias(libs.plugins.conveyor) apply false
|
||||||
|
alias(libs.plugins.moko) apply false
|
||||||
|
alias(libs.plugins.buildconfig) apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
val jvmVersion = libs.versions.jvm.get().toInt()
|
||||||
|
version = libs.versions.app.get()
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
group = "com.zaneschepke.wireguardautotunnel"
|
||||||
|
version = version
|
||||||
|
plugins.withId("org.jetbrains.kotlin.jvm") {
|
||||||
|
extensions.configure<org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension> {
|
||||||
|
jvmToolchain(jvmVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins.withId("org.jetbrains.kotlin.multiplatform") {
|
||||||
|
extensions.configure<org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension> {
|
||||||
|
jvmToolchain(jvmVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerConveyorTask(
|
||||||
|
taskName = "buildLinuxDeb",
|
||||||
|
packageType = "debian-package",
|
||||||
|
subDir = "deb",
|
||||||
|
)
|
||||||
|
|
||||||
|
registerConveyorTask(
|
||||||
|
taskName = "buildWindowsMsix",
|
||||||
|
packageType = "windows-msix",
|
||||||
|
subDir = "windows",
|
||||||
|
)
|
||||||
|
|
||||||
|
registerConveyorTask(
|
||||||
|
taskName = "buildConveyorSite",
|
||||||
|
packageType = "site",
|
||||||
|
subDir = "site"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
tasks.register<Delete>("clean") {
|
||||||
|
delete(layout.buildDirectory)
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
plugins {
|
||||||
|
`kotlin-dsl` // enable the Kotlin-DSL
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
gradlePluginPortal()
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("org.apache.commons:commons-lang3:3.20.0")
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
rootProject.name = "buildSrc"
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
object LocalProperties {
|
||||||
|
|
||||||
|
private val properties by lazy {
|
||||||
|
val props = Properties()
|
||||||
|
val file = File("local.properties")
|
||||||
|
if (file.exists()) {
|
||||||
|
FileInputStream(file).use { props.load(it) }
|
||||||
|
}
|
||||||
|
props
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get(key: String): String? = properties.getProperty(key)
|
||||||
|
|
||||||
|
fun getOrDefault(key: String, default: String): String = properties.getProperty(key, default)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
object SystemVar {
|
||||||
|
fun fromEnvironment(envVar : String) : String? {
|
||||||
|
return System.getenv(envVar)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.tasks.Exec
|
||||||
|
import org.gradle.kotlin.dsl.register
|
||||||
|
|
||||||
|
|
||||||
|
fun Project.registerConveyorTask(
|
||||||
|
taskName: String,
|
||||||
|
packageType: String,
|
||||||
|
subDir: String,
|
||||||
|
) {
|
||||||
|
tasks.register<Exec>(taskName) {
|
||||||
|
group = "distribution"
|
||||||
|
val outputDir = layout.buildDirectory.dir("conveyor/$subDir")
|
||||||
|
outputs.dir(outputDir)
|
||||||
|
|
||||||
|
environment(
|
||||||
|
"CONVEYOR_PASSPHRASE",
|
||||||
|
SystemVar.fromEnvironment("CONVEYOR_PASSPHRASE") ?: LocalProperties.get("conveyor.passphrase") ?: ""
|
||||||
|
)
|
||||||
|
|
||||||
|
val args = mutableListOf(
|
||||||
|
"conveyor",
|
||||||
|
"--passphrase=env:CONVEYOR_PASSPHRASE",
|
||||||
|
"make",
|
||||||
|
"--output-dir", outputDir.get().asFile.absolutePath,
|
||||||
|
packageType
|
||||||
|
)
|
||||||
|
|
||||||
|
commandLine(args)
|
||||||
|
|
||||||
|
dependsOn(
|
||||||
|
":composeApp:createDistributable",
|
||||||
|
":cli:installDist",
|
||||||
|
":daemon:installDist",
|
||||||
|
":composeApp:writeConveyorConfig"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
plugins {
|
||||||
|
application
|
||||||
|
alias(libs.plugins.serialization)
|
||||||
|
kotlin("jvm")
|
||||||
|
kotlin("kapt")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":client"))
|
||||||
|
// CLI
|
||||||
|
implementation(libs.picocli)
|
||||||
|
kapt(libs.picocli.codegen)
|
||||||
|
|
||||||
|
// DI
|
||||||
|
implementation(libs.koin.core)
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
implementation(libs.kermit)
|
||||||
|
implementation(libs.logback.classic)
|
||||||
|
|
||||||
|
implementation(libs.kotlinx.serialization)
|
||||||
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
|
}
|
||||||
|
|
||||||
|
kapt {
|
||||||
|
arguments {
|
||||||
|
arg("project", "${project.group}/${project.name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named<Sync>("installDist") {
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainClass.set("com.zaneschepke.wireguardautotunnel.cli.MainKt")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<Jar> {
|
||||||
|
manifest {
|
||||||
|
attributes(
|
||||||
|
"Implementation-Title" to project.name,
|
||||||
|
"Implementation-Version" to libs.versions.app.get(),
|
||||||
|
"Main-Class" to application.mainClass.get()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.cli
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.cli.CliRoot.Companion.BANNER
|
||||||
|
import com.zaneschepke.wireguardautotunnel.cli.commands.tunnel.TunnelCommand
|
||||||
|
import com.zaneschepke.wireguardautotunnel.cli.provider.ManifestVersionProvider
|
||||||
|
import picocli.CommandLine.Command
|
||||||
|
|
||||||
|
@Command(
|
||||||
|
name = "wgtunnel",
|
||||||
|
description = ["CLI client for WG Tunnel."],
|
||||||
|
mixinStandardHelpOptions = true,
|
||||||
|
versionProvider = ManifestVersionProvider::class,
|
||||||
|
header = [BANNER],
|
||||||
|
subcommands = [
|
||||||
|
TunnelCommand::class
|
||||||
|
]
|
||||||
|
)
|
||||||
|
class CliRoot : Runnable {
|
||||||
|
override fun run() {
|
||||||
|
|
||||||
|
}
|
||||||
|
companion object {
|
||||||
|
const val BANNER: String = (""
|
||||||
|
+ "██╗ ██╗ ██████╗ ████████╗██╗ ██╗███╗ ██╗███╗ ██╗███████╗██╗ \n"
|
||||||
|
+ "██║ ██║██╔════╝ ╚══██╔══╝██║ ██║████╗ ██║████╗ ██║██╔════╝██║ \n"
|
||||||
|
+ "██║ █╗ ██║██║ ███╗ ██║ ██║ ██║██╔██╗ ██║██╔██╗ ██║█████╗ ██║ \n"
|
||||||
|
+ "██║███╗██║██║ ██║ ██║ ██║ ██║██║╚██╗██║██║╚██╗██║██╔══╝ ██║ \n"
|
||||||
|
+ "╚███╔███╔╝╚██████╔╝ ██║ ╚██████╔╝██║ ╚████║██║ ╚████║███████╗███████╗\n"
|
||||||
|
+ " ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═══╝╚══════╝╚══════╝\n")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.cli
|
||||||
|
|
||||||
|
import co.touchlab.kermit.Logger
|
||||||
|
import co.touchlab.kermit.Severity
|
||||||
|
import co.touchlab.kermit.platformLogWriter
|
||||||
|
import com.zaneschepke.wireguardautotunnel.cli.commands.handler.CliExceptionHandler
|
||||||
|
import com.zaneschepke.wireguardautotunnel.cli.strategy.CliExecutionStrategy
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.di.databaseModule
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.di.serviceModule
|
||||||
|
import org.koin.core.context.startKoin
|
||||||
|
import picocli.CommandLine
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
Logger.setLogWriters(platformLogWriter())
|
||||||
|
Logger.setMinSeverity(Severity.Debug)
|
||||||
|
Logger.setTag("CLI")
|
||||||
|
startKoin {
|
||||||
|
modules(databaseModule, serviceModule)
|
||||||
|
}
|
||||||
|
val commandLine = CommandLine(CliRoot::class.java)
|
||||||
|
commandLine.executionStrategy = CliExecutionStrategy(commandLine.executionStrategy)
|
||||||
|
commandLine.executionExceptionHandler = CliExceptionHandler()
|
||||||
|
commandLine.execute(*args)
|
||||||
|
}
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.cli.commands.handler
|
||||||
|
|
||||||
|
import picocli.CommandLine
|
||||||
|
import picocli.CommandLine.IExecutionExceptionHandler
|
||||||
|
import picocli.CommandLine.ParseResult
|
||||||
|
|
||||||
|
class CliExceptionHandler : IExecutionExceptionHandler {
|
||||||
|
override fun handleExecutionException(
|
||||||
|
ex: Exception,
|
||||||
|
commandLine: CommandLine,
|
||||||
|
parseResult: ParseResult
|
||||||
|
): Int {
|
||||||
|
commandLine.err.println(
|
||||||
|
commandLine.colorScheme.errorText("Error completing command: ${ex.message}")
|
||||||
|
)
|
||||||
|
return CommandLine.ExitCode.SOFTWARE
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.cli.commands.tunnel
|
||||||
|
|
||||||
|
import picocli.CommandLine.Command
|
||||||
|
|
||||||
|
@Command(
|
||||||
|
name = "tunnel",
|
||||||
|
mixinStandardHelpOptions = true,
|
||||||
|
subcommands = [
|
||||||
|
TunnelUpCommand::class,
|
||||||
|
TunnelDownCommand::class,
|
||||||
|
TunnelImportCommand::class,
|
||||||
|
TunnelListCommand::class,
|
||||||
|
TunnelDeleteCommand::class,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
class TunnelCommand : Runnable {
|
||||||
|
override fun run() {
|
||||||
|
println("Please specify a subcommand: start, stop, list, etc..")
|
||||||
|
}
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.cli.commands.tunnel
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.repository.TunnelRepository
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
|
import picocli.CommandLine.*
|
||||||
|
import java.util.concurrent.Callable
|
||||||
|
|
||||||
|
@Command(
|
||||||
|
name = "delete",
|
||||||
|
description = ["Delete a tunnel."],
|
||||||
|
)
|
||||||
|
class TunnelDeleteCommand : Callable<Int> {
|
||||||
|
|
||||||
|
private val tunnelRepository: TunnelRepository by inject(TunnelRepository::class.java)
|
||||||
|
|
||||||
|
@Option(names = ["-y", "--yes"], description = ["Delete without additional prompts."])
|
||||||
|
var yes: Boolean? = null
|
||||||
|
|
||||||
|
@Parameters(index = "0", paramLabel = "<tunnel-name>", description = ["The name of the tunnel to bring up."])
|
||||||
|
lateinit var tunnelName: String
|
||||||
|
|
||||||
|
override fun call(): Int = runBlocking {
|
||||||
|
if(yes == null) {
|
||||||
|
print("Are you sure you want to delete $tunnelName? [y/N]: ")
|
||||||
|
val userInput = readlnOrNull()?.trim()?.lowercase()
|
||||||
|
if (userInput != "y" && userInput != "yes") return@runBlocking 0
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
tunnelRepository.deleteByName(tunnelName)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
System.err.println("Failed to delete $tunnelName! Check that the service is running.")
|
||||||
|
return@runBlocking 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return@runBlocking 0
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.cli.commands.tunnel
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.repository.TunnelRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.service.TunnelCommandService
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
|
import picocli.CommandLine.Command
|
||||||
|
import picocli.CommandLine.Parameters
|
||||||
|
|
||||||
|
@Command(name = "down", description = ["Bring a tunnel down."])
|
||||||
|
class TunnelDownCommand : Runnable {
|
||||||
|
private val tunnelService: TunnelCommandService by inject(TunnelCommandService::class.java)
|
||||||
|
|
||||||
|
private val tunnelRepository: TunnelRepository by inject(TunnelRepository::class.java)
|
||||||
|
|
||||||
|
@Parameters(index = "0", paramLabel = "<tunnel-name>", description = ["The name of the tunnel to bring down."])
|
||||||
|
lateinit var tunnelName: String
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
runBlocking {
|
||||||
|
val tunnel = tunnelRepository.getTunnelByName(tunnelName) ?: return@runBlocking println("Tunnel $tunnelName not found")
|
||||||
|
val result = tunnelService.stopTunnel(tunnel.id)
|
||||||
|
if (result.isSuccess) {
|
||||||
|
println("Tunnel stopped successfully.")
|
||||||
|
} else {
|
||||||
|
println("Failed to stop tunnel: ${result.exceptionOrNull()?.message ?: "Unknown error"}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.cli.commands.tunnel
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.service.TunnelImportService
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
|
import picocli.CommandLine.*
|
||||||
|
import java.io.File
|
||||||
|
import java.util.concurrent.Callable
|
||||||
|
|
||||||
|
@Command(
|
||||||
|
name = "import",
|
||||||
|
description = ["Import configuration from a file, string, or stdin."]
|
||||||
|
)
|
||||||
|
class TunnelImportCommand : Callable<Int> {
|
||||||
|
|
||||||
|
private val tunnelImportService: TunnelImportService by inject(TunnelImportService::class.java)
|
||||||
|
|
||||||
|
@ArgGroup(exclusive = true, multiplicity = "1")
|
||||||
|
lateinit var input: Input
|
||||||
|
|
||||||
|
class Input {
|
||||||
|
@Option(names = ["--file"], description = ["Import config from file"])
|
||||||
|
var file: File? = null
|
||||||
|
|
||||||
|
@Option(names = ["--string"], description = ["Import config from string literal"])
|
||||||
|
var string: String? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Option(names = ["--name"], description = ["Specify a tunnel name"])
|
||||||
|
var name: String? = null
|
||||||
|
|
||||||
|
override fun call(): Int = runBlocking {
|
||||||
|
val config : String = try {
|
||||||
|
when {
|
||||||
|
input.file != null -> {
|
||||||
|
val f = input.file!!
|
||||||
|
if (!f.exists()) {
|
||||||
|
System.err.println("Error: File does not exist: ${f.absolutePath}")
|
||||||
|
return@runBlocking 1
|
||||||
|
}
|
||||||
|
if (!f.isFile) {
|
||||||
|
System.err.println("Error: Not a file: ${f.absolutePath}")
|
||||||
|
return@runBlocking 1
|
||||||
|
}
|
||||||
|
f.readText()
|
||||||
|
}
|
||||||
|
|
||||||
|
input.string != null -> input.string!!
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
System.err.println("Error: No input source provided. Use --file, --string, or - for stdin.")
|
||||||
|
return@runBlocking 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
System.err.println("Error reading input: ${e.message}")
|
||||||
|
return@runBlocking 1
|
||||||
|
}
|
||||||
|
|
||||||
|
val name = name ?: input.file?.nameWithoutExtension
|
||||||
|
|
||||||
|
tunnelImportService.import(config , name)
|
||||||
|
|
||||||
|
return@runBlocking 0
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.cli.commands.tunnel
|
||||||
|
|
||||||
|
import co.touchlab.kermit.Logger
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.repository.TunnelRepository
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
|
import picocli.CommandLine.Command
|
||||||
|
import picocli.CommandLine.Option
|
||||||
|
import java.util.concurrent.Callable
|
||||||
|
|
||||||
|
@Command(
|
||||||
|
name = "list",
|
||||||
|
description = ["List configured WG Tunnel tunnels."]
|
||||||
|
)
|
||||||
|
class TunnelListCommand : Callable<Int> {
|
||||||
|
|
||||||
|
private val tunnelRepository: TunnelRepository by inject(TunnelRepository::class.java)
|
||||||
|
|
||||||
|
@Option(names = ["--json"], description = ["Output in JSON format for scripting."])
|
||||||
|
var json: Boolean = false
|
||||||
|
|
||||||
|
override fun call(): Int = runBlocking {
|
||||||
|
val tunnels = try {
|
||||||
|
tunnelRepository.getAll().sortedBy { it.position }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.e("failed to load tunnels", e)
|
||||||
|
System.err.println("Error: Failed to retrieve tunnels. ${e.message}")
|
||||||
|
return@runBlocking 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tunnels.isEmpty()) {
|
||||||
|
println("No tunnels found")
|
||||||
|
return@runBlocking 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json) {
|
||||||
|
val names = tunnels.map { it.name }
|
||||||
|
println(Json.encodeToString(names))
|
||||||
|
} else {
|
||||||
|
// TODO better strategy for large number of tunnels
|
||||||
|
println("Configured Tunnels:")
|
||||||
|
tunnels.forEach { println(it.name) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return@runBlocking 0
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.cli.commands.tunnel
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.repository.TunnelRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.service.TunnelCommandService
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
|
import picocli.CommandLine.Command
|
||||||
|
import picocli.CommandLine.Parameters
|
||||||
|
|
||||||
|
@Command(name = "up", description = ["Bring a tunnel up."])
|
||||||
|
class TunnelUpCommand : Runnable {
|
||||||
|
private val tunnelService: TunnelCommandService by inject(TunnelCommandService::class.java)
|
||||||
|
private val tunnelRepository: TunnelRepository by inject(TunnelRepository::class.java)
|
||||||
|
|
||||||
|
@Parameters(index = "0", paramLabel = "<tunnel-name>", description = ["The name of the tunnel to bring up."])
|
||||||
|
lateinit var tunnelName: String
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
runBlocking {
|
||||||
|
val tunnel = tunnelRepository.getTunnelByName(tunnelName) ?: return@runBlocking println("Failed to find the $tunnelName")
|
||||||
|
val result = tunnelService.startTunnel(tunnel.id)
|
||||||
|
if (result.isSuccess) {
|
||||||
|
println("Tunnel start triggered successfully.")
|
||||||
|
} else {
|
||||||
|
println("Failed to start tunnel: ${result.exceptionOrNull()?.message ?: "Unknown error"}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.cli.provider
|
||||||
|
|
||||||
|
import picocli.CommandLine
|
||||||
|
|
||||||
|
class ManifestVersionProvider : CommandLine.IVersionProvider {
|
||||||
|
override fun getVersion(): Array<String> {
|
||||||
|
val version = ManifestVersionProvider::class.java.getPackage().implementationVersion
|
||||||
|
return if (version != null) arrayOf(version) else arrayOf("Unknown version")
|
||||||
|
}
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.cli.strategy
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.service.DaemonHealthService
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
|
import picocli.CommandLine.*
|
||||||
|
|
||||||
|
class CliExecutionStrategy(private val defaultStrategy: IExecutionStrategy) : IExecutionStrategy {
|
||||||
|
|
||||||
|
val daemonHealthService : DaemonHealthService by inject(DaemonHealthService::class.java)
|
||||||
|
|
||||||
|
override fun execute(parseResult: ParseResult): Int = runBlocking {
|
||||||
|
// Drill down to the deepest subcommand
|
||||||
|
var current = parseResult
|
||||||
|
while (current.hasSubcommand()) {
|
||||||
|
current = current.subcommand()
|
||||||
|
}
|
||||||
|
val commandSpec = current.commandSpec()
|
||||||
|
|
||||||
|
val skipCheck = parseResult.isUsageHelpRequested || parseResult.isVersionHelpRequested
|
||||||
|
|
||||||
|
// if (!skipCheck && !daemonHealthService.alive()) {
|
||||||
|
// throw ExecutionException(
|
||||||
|
// commandSpec.commandLine(),
|
||||||
|
// "The WG Tunnel service must be installed and started to execute this command. " +
|
||||||
|
// "Install and start it with 'wgtunnel service install -y' or, if already installed, " +
|
||||||
|
// "start the service with 'wgtunnel service start'."
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
return@runBlocking defaultStrategy.execute(parseResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import dev.icerock.gradle.MRVisibility
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.kotlinMultiplatform)
|
||||||
|
alias(libs.plugins.ksp)
|
||||||
|
alias(libs.plugins.room)
|
||||||
|
alias(libs.plugins.serialization)
|
||||||
|
alias(libs.plugins.moko)
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvm()
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
val commonMain by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":parser"))
|
||||||
|
implementation(project(":keyring"))
|
||||||
|
implementation(project(":core"))
|
||||||
|
implementation(libs.androidx.room.runtime)
|
||||||
|
implementation(libs.androidx.sqlite.bundled)
|
||||||
|
|
||||||
|
implementation(libs.kermit)
|
||||||
|
implementation(libs.logback.classic)
|
||||||
|
|
||||||
|
implementation(libs.kotlinx.serialization)
|
||||||
|
|
||||||
|
api(libs.moko.core)
|
||||||
|
api(libs.moko.compose)
|
||||||
|
|
||||||
|
// DI
|
||||||
|
implementation(libs.koin.core)
|
||||||
|
|
||||||
|
implementation(libs.bundles.ktor.client.jvm)
|
||||||
|
|
||||||
|
// Util
|
||||||
|
implementation(libs.apache.commons.lang3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
"kspJvm"(libs.androidx.room.compiler)
|
||||||
|
}
|
||||||
|
|
||||||
|
room { schemaDirectory("$projectDir/schemas") }
|
||||||
|
|
||||||
|
multiplatformResources {
|
||||||
|
resourcesPackage.set("com.zaneschepke.wireguardautotunnel")
|
||||||
|
resourcesClassName.set("SharedRes")
|
||||||
|
resourcesVisibility.set(MRVisibility.Public)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,341 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 1,
|
||||||
|
"identityHash": "d66aaaa9eeab5a2e84406838017246b1",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "tunnel_config",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `quick_config` TEXT NOT NULL, `tunnel_networks` TEXT NOT NULL DEFAULT '', `is_primary_tunnel` INTEGER NOT NULL DEFAULT false, `active` INTEGER NOT NULL DEFAULT false, `ping_target` TEXT DEFAULT null, `is_ethernet_tunnel` INTEGER NOT NULL DEFAULT false, `is_ipv4_preferred` INTEGER NOT NULL DEFAULT true, `position` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "quickConfig",
|
||||||
|
"columnName": "quick_config",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tunnelNetworks",
|
||||||
|
"columnName": "tunnel_networks",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isPrimaryTunnel",
|
||||||
|
"columnName": "is_primary_tunnel",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "active",
|
||||||
|
"columnName": "active",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pingTarget",
|
||||||
|
"columnName": "ping_target",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isEthernetTunnel",
|
||||||
|
"columnName": "is_ethernet_tunnel",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isIpv4Preferred",
|
||||||
|
"columnName": "is_ipv4_preferred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_tunnel_config_name",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_tunnel_config_name` ON `${TABLE_NAME}` (`name`)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "proxy_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `socks5_proxy_enabled` INTEGER NOT NULL DEFAULT 0, `socks5_proxy_bind_address` TEXT, `http_proxy_enable` INTEGER NOT NULL DEFAULT 0, `http_proxy_bind_address` TEXT, `proxy_username` TEXT, `proxy_password` TEXT)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "socks5ProxyEnabled",
|
||||||
|
"columnName": "socks5_proxy_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "socks5ProxyBindAddress",
|
||||||
|
"columnName": "socks5_proxy_bind_address",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "httpProxyEnabled",
|
||||||
|
"columnName": "http_proxy_enable",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "httpProxyBindAddress",
|
||||||
|
"columnName": "http_proxy_bind_address",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "proxyUsername",
|
||||||
|
"columnName": "proxy_username",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "proxyPassword",
|
||||||
|
"columnName": "proxy_password",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "lockdown_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `bypass_lan` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bypassLan",
|
||||||
|
"columnName": "bypass_lan",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "general_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT 0, `app_mode` INTEGER NOT NULL DEFAULT 0, `theme` TEXT NOT NULL DEFAULT 'AUTOMATIC', `locale` TEXT, `already_donated` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isRestoreOnBootEnabled",
|
||||||
|
"columnName": "is_restore_on_boot_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "appMode",
|
||||||
|
"columnName": "app_mode",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "theme",
|
||||||
|
"columnName": "theme",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "'AUTOMATIC'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "locale",
|
||||||
|
"columnName": "locale",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alreadyDonated",
|
||||||
|
"columnName": "already_donated",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "dns_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `dns_protocol` INTEGER NOT NULL DEFAULT 0, `dns_endpoint` TEXT, `global_tunnel_dns_enabled` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dnsProtocol",
|
||||||
|
"columnName": "dns_protocol",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dnsEndpoint",
|
||||||
|
"columnName": "dns_endpoint",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isGlobalTunnelDnsEnabled",
|
||||||
|
"columnName": "global_tunnel_dns_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "auto_tunnel_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `trusted_network_ssids` TEXT NOT NULL DEFAULT '', `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT 0, `is_wildcards_enabled` INTEGER NOT NULL DEFAULT 0, `is_stop_on_no_internet_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_unsecure_enabled` INTEGER NOT NULL DEFAULT 0, `start_on_boot` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isAutoTunnelEnabled",
|
||||||
|
"columnName": "is_tunnel_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "trustedNetworkSSIDs",
|
||||||
|
"columnName": "trusted_network_ssids",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||||
|
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnWifiEnabled",
|
||||||
|
"columnName": "is_tunnel_on_wifi_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isWildcardsEnabled",
|
||||||
|
"columnName": "is_wildcards_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isStopOnNoInternetEnabled",
|
||||||
|
"columnName": "is_stop_on_no_internet_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnUnsecureEnabled",
|
||||||
|
"columnName": "is_tunnel_on_unsecure_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "startOnBoot",
|
||||||
|
"columnName": "start_on_boot",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd66aaaa9eeab5a2e84406838017246b1')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,341 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 1,
|
||||||
|
"identityHash": "d66aaaa9eeab5a2e84406838017246b1",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "tunnel_config",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `quick_config` TEXT NOT NULL, `tunnel_networks` TEXT NOT NULL DEFAULT '', `is_primary_tunnel` INTEGER NOT NULL DEFAULT false, `active` INTEGER NOT NULL DEFAULT false, `ping_target` TEXT DEFAULT null, `is_ethernet_tunnel` INTEGER NOT NULL DEFAULT false, `is_ipv4_preferred` INTEGER NOT NULL DEFAULT true, `position` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "quickConfig",
|
||||||
|
"columnName": "quick_config",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tunnelNetworks",
|
||||||
|
"columnName": "tunnel_networks",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isPrimaryTunnel",
|
||||||
|
"columnName": "is_primary_tunnel",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "active",
|
||||||
|
"columnName": "active",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pingTarget",
|
||||||
|
"columnName": "ping_target",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isEthernetTunnel",
|
||||||
|
"columnName": "is_ethernet_tunnel",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isIpv4Preferred",
|
||||||
|
"columnName": "is_ipv4_preferred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_tunnel_config_name",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_tunnel_config_name` ON `${TABLE_NAME}` (`name`)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "proxy_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `socks5_proxy_enabled` INTEGER NOT NULL DEFAULT 0, `socks5_proxy_bind_address` TEXT, `http_proxy_enable` INTEGER NOT NULL DEFAULT 0, `http_proxy_bind_address` TEXT, `proxy_username` TEXT, `proxy_password` TEXT)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "socks5ProxyEnabled",
|
||||||
|
"columnName": "socks5_proxy_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "socks5ProxyBindAddress",
|
||||||
|
"columnName": "socks5_proxy_bind_address",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "httpProxyEnabled",
|
||||||
|
"columnName": "http_proxy_enable",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "httpProxyBindAddress",
|
||||||
|
"columnName": "http_proxy_bind_address",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "proxyUsername",
|
||||||
|
"columnName": "proxy_username",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "proxyPassword",
|
||||||
|
"columnName": "proxy_password",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "lockdown_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `bypass_lan` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bypassLan",
|
||||||
|
"columnName": "bypass_lan",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "general_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT 0, `app_mode` INTEGER NOT NULL DEFAULT 0, `theme` TEXT NOT NULL DEFAULT 'AUTOMATIC', `locale` TEXT, `already_donated` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isRestoreOnBootEnabled",
|
||||||
|
"columnName": "is_restore_on_boot_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "appMode",
|
||||||
|
"columnName": "app_mode",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "theme",
|
||||||
|
"columnName": "theme",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "'AUTOMATIC'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "locale",
|
||||||
|
"columnName": "locale",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alreadyDonated",
|
||||||
|
"columnName": "already_donated",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "dns_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `dns_protocol` INTEGER NOT NULL DEFAULT 0, `dns_endpoint` TEXT, `global_tunnel_dns_enabled` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dnsProtocol",
|
||||||
|
"columnName": "dns_protocol",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dnsEndpoint",
|
||||||
|
"columnName": "dns_endpoint",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isGlobalTunnelDnsEnabled",
|
||||||
|
"columnName": "global_tunnel_dns_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "auto_tunnel_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `trusted_network_ssids` TEXT NOT NULL DEFAULT '', `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT 0, `is_wildcards_enabled` INTEGER NOT NULL DEFAULT 0, `is_stop_on_no_internet_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_unsecure_enabled` INTEGER NOT NULL DEFAULT 0, `start_on_boot` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isAutoTunnelEnabled",
|
||||||
|
"columnName": "is_tunnel_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "trustedNetworkSSIDs",
|
||||||
|
"columnName": "trusted_network_ssids",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||||
|
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnWifiEnabled",
|
||||||
|
"columnName": "is_tunnel_on_wifi_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isWildcardsEnabled",
|
||||||
|
"columnName": "is_wildcards_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isStopOnNoInternetEnabled",
|
||||||
|
"columnName": "is_stop_on_no_internet_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnUnsecureEnabled",
|
||||||
|
"columnName": "is_tunnel_on_unsecure_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "startOnBoot",
|
||||||
|
"columnName": "start_on_boot",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd66aaaa9eeab5a2e84406838017246b1')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data
|
||||||
|
|
||||||
|
import androidx.room.ProvidedTypeConverter
|
||||||
|
import androidx.room.TypeConverter
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.model.EncryptedField
|
||||||
|
import com.zaneschepke.wireguardautotunnel.core.crypto.Crypto
|
||||||
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
|
import javax.crypto.SecretKey
|
||||||
|
|
||||||
|
@ProvidedTypeConverter
|
||||||
|
class AppKeyringConverter {
|
||||||
|
|
||||||
|
private val secretKey: SecretKey by inject(SecretKey::class.java)
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun decryptQuick(encryptedQuick: String): EncryptedField {
|
||||||
|
return EncryptedField(Crypto.decryptWithMasterKey(encryptedQuick, secretKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun encryptQuick(quick: EncryptedField): String {
|
||||||
|
return Crypto.encryptWithMasterKey(quick.value, secretKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data
|
||||||
|
|
||||||
|
import androidx.room.ConstructedBy
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import androidx.room.RoomDatabaseConstructor
|
||||||
|
import androidx.room.TypeConverters
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.dao.AutoTunnelSettingsDao
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.dao.DnsSettingsDao
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.dao.GeneralSettingsDao
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.dao.LockdownSettingsDao
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.dao.ProxySettingsDao
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.dao.TunnelConfigDao
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.AutoTunnelSettings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.DnsSettings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.GeneralSettings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.LockdownSettings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.ProxySettings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.TunnelConfig
|
||||||
|
import com.zaneschepke.wireguardautotunnel.keyring.Keyring
|
||||||
|
import org.apache.commons.lang3.SystemUtils
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
@Database(entities = [TunnelConfig::class, ProxySettings::class, LockdownSettings::class,
|
||||||
|
GeneralSettings::class, DnsSettings::class, AutoTunnelSettings::class], version = 1, exportSchema = true)
|
||||||
|
@TypeConverters(DatabaseConverters::class, AppKeyringConverter::class)
|
||||||
|
@ConstructedBy(AppDatabaseConstructor::class)
|
||||||
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
abstract fun tunnelConfigDao(): TunnelConfigDao
|
||||||
|
|
||||||
|
abstract fun proxySettingsDao(): ProxySettingsDao
|
||||||
|
|
||||||
|
abstract fun generalSettingsDao(): GeneralSettingsDao
|
||||||
|
|
||||||
|
abstract fun autoTunnelSettingsDao(): AutoTunnelSettingsDao
|
||||||
|
|
||||||
|
abstract fun lockdownSettingsDao(): LockdownSettingsDao
|
||||||
|
|
||||||
|
abstract fun dnsSettingsDao(): DnsSettingsDao
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val DB_SECRET_KEY = "db_secret"
|
||||||
|
const val DB_KEYRING = "wg_tunnel"
|
||||||
|
const val DB_FILE_NAME = "wg_tunnel.db"
|
||||||
|
const val APP_NAME = "WGTunnel" // macos convention
|
||||||
|
|
||||||
|
fun getDatabaseDir() : File {
|
||||||
|
val home = System.getProperty("user.home")
|
||||||
|
return when {
|
||||||
|
SystemUtils.IS_OS_WINDOWS -> {
|
||||||
|
val appData = System.getenv("APPDATA") ?: "${System.getProperty("user.home")}\\AppData\\Roaming"
|
||||||
|
File("$appData\\$APP_NAME")
|
||||||
|
}
|
||||||
|
SystemUtils.IS_OS_MAC -> {
|
||||||
|
File("$home/Library/Application Support/$APP_NAME")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val xdgDataHome = System.getenv("XDG_DATA_HOME") ?: "$home/.local/share"
|
||||||
|
File("$xdgDataHome/${APP_NAME.lowercase()}") // linux lowercase convention
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NO_ACTUAL_FOR_EXPECT")
|
||||||
|
expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase> {
|
||||||
|
override fun initialize(): AppDatabase
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data
|
||||||
|
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import androidx.sqlite.SQLiteConnection
|
||||||
|
import androidx.sqlite.execSQL
|
||||||
|
|
||||||
|
class DatabaseCallback(private val databaseProvider: Lazy<AppDatabase>) : RoomDatabase.Callback() {
|
||||||
|
override fun onCreate(connection: SQLiteConnection) {
|
||||||
|
super.onCreate(connection)
|
||||||
|
connection.execSQL("INSERT INTO proxy_settings DEFAULT VALUES")
|
||||||
|
connection.execSQL("INSERT INTO general_settings DEFAULT VALUES")
|
||||||
|
connection.execSQL("INSERT INTO auto_tunnel_settings DEFAULT VALUES")
|
||||||
|
connection.execSQL("INSERT INTO dns_settings DEFAULT VALUES")
|
||||||
|
}
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data
|
||||||
|
|
||||||
|
import androidx.room.ProvidedTypeConverter
|
||||||
|
import androidx.room.TypeConverter
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.model.AppMode
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.model.DnsProtocol
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
@ProvidedTypeConverter
|
||||||
|
class DatabaseConverters {
|
||||||
|
@TypeConverter
|
||||||
|
fun listToString(value: List<String>): String {
|
||||||
|
return Json.encodeToString(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun stringToList(value: String): List<String> {
|
||||||
|
if (value.isBlank() || value.isEmpty()) return mutableListOf()
|
||||||
|
return try {
|
||||||
|
Json.decodeFromString<List<String>>(value)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
val list = value.split(",").toMutableList()
|
||||||
|
val json = listToString(list)
|
||||||
|
Json.decodeFromString<List<String>>(json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun setToString(value: Set<String>): String {
|
||||||
|
return listToString(value.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun stringToSet(value: String): Set<String> {
|
||||||
|
return stringToList(value).toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter fun toMode(value: Int): AppMode = AppMode.fromValue(value)
|
||||||
|
|
||||||
|
@TypeConverter fun fromMode(mode: AppMode): Int = mode.value
|
||||||
|
|
||||||
|
@TypeConverter fun toDnsProtocol(value: Int): DnsProtocol = DnsProtocol.fromValue(value)
|
||||||
|
|
||||||
|
@TypeConverter fun fromDnsProtocol(mode: DnsProtocol): Int = mode.value
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Upsert
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.AutoTunnelSettings
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface AutoTunnelSettingsDao {
|
||||||
|
@Query("SELECT * FROM auto_tunnel_settings LIMIT 1")
|
||||||
|
suspend fun getAutoTunnelSettings(): AutoTunnelSettings?
|
||||||
|
|
||||||
|
@Upsert suspend fun upsert(autoTunnelSettings: AutoTunnelSettings)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM auto_tunnel_settings LIMIT 1")
|
||||||
|
fun getAutoTunnelSettingsFlow(): Flow<AutoTunnelSettings?>
|
||||||
|
|
||||||
|
@Query("UPDATE auto_tunnel_settings SET is_tunnel_enabled = :enabled")
|
||||||
|
suspend fun updateAutoTunnelEnabled(enabled: Boolean)
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Upsert
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.DnsSettings
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface DnsSettingsDao {
|
||||||
|
@Query("SELECT * FROM dns_settings LIMIT 1") suspend fun getDnsSettings(): DnsSettings?
|
||||||
|
|
||||||
|
@Upsert suspend fun upsert(dnsSettings: DnsSettings)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM dns_settings LIMIT 1") fun getDnsSettingsFlow(): Flow<DnsSettings?>
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Upsert
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.GeneralSettings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.model.AppMode
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface GeneralSettingsDao {
|
||||||
|
@Query("SELECT * FROM general_settings LIMIT 1")
|
||||||
|
suspend fun getGeneralSettings(): GeneralSettings?
|
||||||
|
|
||||||
|
@Upsert suspend fun upsert(generalSettings: GeneralSettings)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM general_settings LIMIT 1")
|
||||||
|
fun getGeneralSettingsFlow(): Flow<GeneralSettings?>
|
||||||
|
|
||||||
|
@Query("UPDATE general_settings SET theme = :theme WHERE id = 1")
|
||||||
|
suspend fun updateTheme(theme: String)
|
||||||
|
|
||||||
|
@Query("UPDATE general_settings SET locale = :locale WHERE id = 1")
|
||||||
|
suspend fun updateLocale(locale: String)
|
||||||
|
|
||||||
|
@Query("UPDATE general_settings SET app_mode = :appMode WHERE id = 1")
|
||||||
|
suspend fun updateAppMode(appMode: AppMode)
|
||||||
|
}
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Upsert
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.LockdownSettings
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface LockdownSettingsDao {
|
||||||
|
@Query("SELECT * FROM lockdown_settings LIMIT 1")
|
||||||
|
suspend fun getLockdownSettings(): LockdownSettings?
|
||||||
|
|
||||||
|
@Upsert suspend fun upsert(lockdownSettings: LockdownSettings)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM lockdown_settings LIMIT 1")
|
||||||
|
fun getLockdownSettingsFlow(): Flow<LockdownSettings?>
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Upsert
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.ProxySettings
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface ProxySettingsDao {
|
||||||
|
@Upsert suspend fun upsert(proxySettings: ProxySettings)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM proxy_settings LIMIT 1") suspend fun getProxySettings(): ProxySettings?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM proxy_settings LIMIT 1") fun getProxySettingsFlow(): Flow<ProxySettings?>
|
||||||
|
}
|
||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.dao
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.TunnelConfig
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface TunnelConfigDao {
|
||||||
|
|
||||||
|
@Upsert suspend fun upsert(t: TunnelConfig)
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun saveAll(t: List<TunnelConfig>)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tunnel_config WHERE id=:id") suspend fun getById(id: Long): TunnelConfig?
|
||||||
|
|
||||||
|
@Query("UPDATE tunnel_config SET active = 0 WHERE active = 1")
|
||||||
|
suspend fun resetActiveTunnels()
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tunnel_config WHERE name=:name")
|
||||||
|
suspend fun getByName(name: String): TunnelConfig?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tunnel_config WHERE active=1")
|
||||||
|
suspend fun getActive(): List<TunnelConfig>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tunnel_config") suspend fun getAll(): List<TunnelConfig>
|
||||||
|
|
||||||
|
@Delete suspend fun delete(t: TunnelConfig)
|
||||||
|
|
||||||
|
@Delete suspend fun delete(t: List<TunnelConfig>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM tunnel_config WHERE name = :name")
|
||||||
|
suspend fun deleteByName(name: String)
|
||||||
|
|
||||||
|
@Query("SELECT COUNT('id') FROM tunnel_config") suspend fun count(): Long
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tunnel_config WHERE tunnel_networks LIKE '%' || :name || '%'")
|
||||||
|
suspend fun findByTunnelNetworkName(name: String): List<TunnelConfig>
|
||||||
|
|
||||||
|
@Query("UPDATE tunnel_config SET is_primary_tunnel = 0 WHERE is_primary_tunnel =1")
|
||||||
|
suspend fun resetPrimaryTunnel()
|
||||||
|
|
||||||
|
@Query("UPDATE tunnel_config SET is_ethernet_tunnel = 0 WHERE is_ethernet_tunnel =1")
|
||||||
|
suspend fun resetEthernetTunnel()
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tunnel_config WHERE is_primary_tunnel=1")
|
||||||
|
suspend fun findByPrimary(): List<TunnelConfig>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT * FROM tunnel_config
|
||||||
|
WHERE name != '${TunnelConfig.GLOBAL_CONFIG_NAME}'
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN is_primary_tunnel = 1 THEN 0 ELSE 1 END,
|
||||||
|
position ASC
|
||||||
|
LIMIT 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun getDefaultTunnel(): TunnelConfig?
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT * FROM tunnel_config
|
||||||
|
WHERE name != '${TunnelConfig.GLOBAL_CONFIG_NAME}'
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN active = 1 THEN 0
|
||||||
|
WHEN is_primary_tunnel = 1 THEN 1
|
||||||
|
ELSE 2 END,
|
||||||
|
position ASC
|
||||||
|
LIMIT 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun getStartTunnel(): TunnelConfig?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tunnel_config ORDER BY position")
|
||||||
|
fun getAllFlow(): Flow<List<TunnelConfig>>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tunnel_config WHERE name != :globalName ORDER BY position")
|
||||||
|
fun getAllTunnelsExceptGlobal(
|
||||||
|
globalName: String = TunnelConfig.GLOBAL_CONFIG_NAME
|
||||||
|
): Flow<List<TunnelConfig>>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tunnel_config WHERE name = :globalName LIMIT 1")
|
||||||
|
fun getGlobalTunnel(globalName: String = TunnelConfig.GLOBAL_CONFIG_NAME): Flow<TunnelConfig?>
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.entity
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "auto_tunnel_settings")
|
||||||
|
data class AutoTunnelSettings(
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Int = 0,
|
||||||
|
@ColumnInfo(name = "is_tunnel_enabled", defaultValue = "0")
|
||||||
|
val isAutoTunnelEnabled: Boolean = false,
|
||||||
|
@ColumnInfo(name = "trusted_network_ssids", defaultValue = "")
|
||||||
|
val trustedNetworkSSIDs: Set<String> = emptySet(),
|
||||||
|
@ColumnInfo(name = "is_tunnel_on_ethernet_enabled", defaultValue = "0")
|
||||||
|
val isTunnelOnEthernetEnabled: Boolean = false,
|
||||||
|
@ColumnInfo(name = "is_tunnel_on_wifi_enabled", defaultValue = "0")
|
||||||
|
val isTunnelOnWifiEnabled: Boolean = false,
|
||||||
|
@ColumnInfo(name = "is_wildcards_enabled", defaultValue = "0")
|
||||||
|
val isWildcardsEnabled: Boolean = false,
|
||||||
|
@ColumnInfo(name = "is_stop_on_no_internet_enabled", defaultValue = "0")
|
||||||
|
val isStopOnNoInternetEnabled: Boolean = false,
|
||||||
|
@ColumnInfo(name = "is_tunnel_on_unsecure_enabled", defaultValue = "0")
|
||||||
|
val isTunnelOnUnsecureEnabled: Boolean = false,
|
||||||
|
@ColumnInfo(name = "start_on_boot", defaultValue = "0") val startOnBoot: Boolean = false,
|
||||||
|
)
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.entity
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.model.DnsProtocol
|
||||||
|
|
||||||
|
@Entity(tableName = "dns_settings")
|
||||||
|
data class DnsSettings(
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Int = 0,
|
||||||
|
@ColumnInfo(name = "dns_protocol", defaultValue = "0")
|
||||||
|
val dnsProtocol: DnsProtocol = DnsProtocol.fromValue(0),
|
||||||
|
@ColumnInfo(name = "dns_endpoint") val dnsEndpoint: String? = null,
|
||||||
|
@ColumnInfo(name = "global_tunnel_dns_enabled", defaultValue = "0")
|
||||||
|
val isGlobalTunnelDnsEnabled: Boolean = false,
|
||||||
|
)
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.entity
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.model.AppMode
|
||||||
|
|
||||||
|
@Entity(tableName = "general_settings")
|
||||||
|
data class GeneralSettings(
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Int = 0,
|
||||||
|
@ColumnInfo(name = "is_restore_on_boot_enabled", defaultValue = "0")
|
||||||
|
val isRestoreOnBootEnabled: Boolean = false,
|
||||||
|
@ColumnInfo(name = "app_mode", defaultValue = "0") val appMode: AppMode = AppMode.fromValue(0),
|
||||||
|
@ColumnInfo(name = "theme", defaultValue = "AUTOMATIC") val theme: String = "AUTOMATIC",
|
||||||
|
@ColumnInfo(name = "locale") val locale: String? = null,
|
||||||
|
@ColumnInfo(name = "already_donated", defaultValue = "0") val alreadyDonated: Boolean = false,
|
||||||
|
)
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.entity
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "lockdown_settings")
|
||||||
|
data class LockdownSettings(
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||||
|
@ColumnInfo(name = "bypass_lan", defaultValue = "0") val bypassLan: Boolean = false
|
||||||
|
)
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.entity
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "proxy_settings")
|
||||||
|
data class ProxySettings(
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||||
|
@ColumnInfo(name = "socks5_proxy_enabled", defaultValue = "0")
|
||||||
|
val socks5ProxyEnabled: Boolean = false,
|
||||||
|
@ColumnInfo(name = "socks5_proxy_bind_address") val socks5ProxyBindAddress: String? = null,
|
||||||
|
@ColumnInfo(name = "http_proxy_enable", defaultValue = "0")
|
||||||
|
val httpProxyEnabled: Boolean = false,
|
||||||
|
@ColumnInfo(name = "http_proxy_bind_address") val httpProxyBindAddress: String? = null,
|
||||||
|
@ColumnInfo(name = "proxy_username") val proxyUsername: String? = null,
|
||||||
|
@ColumnInfo(name = "proxy_password") val proxyPassword: String? = null,
|
||||||
|
)
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.entity
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.Index
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import androidx.room.TypeConverters
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.AppKeyringConverter
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.model.EncryptedField
|
||||||
|
|
||||||
|
@Entity(tableName = "tunnel_config", indices = [Index(value = ["name"], unique = true)])
|
||||||
|
data class TunnelConfig(
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Int = 0,
|
||||||
|
@ColumnInfo(name = "name") val name: String,
|
||||||
|
@field:TypeConverters(AppKeyringConverter::class)
|
||||||
|
@ColumnInfo(name = "quick_config") val quickConfig: EncryptedField,
|
||||||
|
@ColumnInfo(name = "tunnel_networks", defaultValue = "")
|
||||||
|
val tunnelNetworks: Set<String> = setOf(),
|
||||||
|
@ColumnInfo(name = "is_primary_tunnel", defaultValue = "false")
|
||||||
|
val isPrimaryTunnel: Boolean = false,
|
||||||
|
@ColumnInfo(name = "active", defaultValue = "false") val active: Boolean = false,
|
||||||
|
@ColumnInfo(name = "ping_target", defaultValue = "null") var pingTarget: String? = null,
|
||||||
|
@ColumnInfo(name = "is_ethernet_tunnel", defaultValue = "false")
|
||||||
|
val isEthernetTunnel: Boolean = false,
|
||||||
|
@ColumnInfo(name = "is_ipv4_preferred", defaultValue = "true")
|
||||||
|
val isIpv4Preferred: Boolean = true,
|
||||||
|
@ColumnInfo(name = "position", defaultValue = "0") val position: Int = 0,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
const val GLOBAL_CONFIG_NAME = "4675ab06-903a-438b-8485-6ea4187a9512"
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.mapper
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.AutoTunnelSettings as Entity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.AutoTunnelSettings as Domain
|
||||||
|
|
||||||
|
fun Entity.toDomain(): Domain =
|
||||||
|
Domain(
|
||||||
|
id = id,
|
||||||
|
isAutoTunnelEnabled = isAutoTunnelEnabled,
|
||||||
|
trustedNetworkSSIDs = trustedNetworkSSIDs,
|
||||||
|
isTunnelOnEthernetEnabled = isTunnelOnEthernetEnabled,
|
||||||
|
isTunnelOnWifiEnabled = isTunnelOnWifiEnabled,
|
||||||
|
isWildcardsEnabled = isWildcardsEnabled,
|
||||||
|
isStopOnNoInternetEnabled = isStopOnNoInternetEnabled,
|
||||||
|
isTunnelOnUnsecureEnabled = isTunnelOnUnsecureEnabled,
|
||||||
|
startOnBoot = startOnBoot,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Domain.toEntity(): Entity =
|
||||||
|
Entity(
|
||||||
|
id = id,
|
||||||
|
isAutoTunnelEnabled = isAutoTunnelEnabled,
|
||||||
|
trustedNetworkSSIDs = trustedNetworkSSIDs,
|
||||||
|
isTunnelOnEthernetEnabled = isTunnelOnEthernetEnabled,
|
||||||
|
isTunnelOnWifiEnabled = isTunnelOnWifiEnabled,
|
||||||
|
isWildcardsEnabled = isWildcardsEnabled,
|
||||||
|
isStopOnNoInternetEnabled = isStopOnNoInternetEnabled,
|
||||||
|
isTunnelOnUnsecureEnabled = isTunnelOnUnsecureEnabled,
|
||||||
|
startOnBoot = startOnBoot,
|
||||||
|
)
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.mapper
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.model.DnsProtocol
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.DnsSettings as Entity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.DnsSettings as Domain
|
||||||
|
|
||||||
|
fun Entity.toDomain(): Domain =
|
||||||
|
Domain(
|
||||||
|
id = id,
|
||||||
|
dnsProtocol = dnsProtocol.value,
|
||||||
|
dnsEndpoint = dnsEndpoint,
|
||||||
|
isGlobalTunnelDnsEnabled = isGlobalTunnelDnsEnabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Domain.toEntity(): Entity =
|
||||||
|
Entity(
|
||||||
|
id = id,
|
||||||
|
dnsProtocol = DnsProtocol.fromValue(dnsProtocol),
|
||||||
|
dnsEndpoint = dnsEndpoint,
|
||||||
|
isGlobalTunnelDnsEnabled = isGlobalTunnelDnsEnabled,
|
||||||
|
)
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.mapper
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.LockdownSettings as Entity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.LockdownSettings as Domain
|
||||||
|
|
||||||
|
fun Entity.toDomain(): Domain =
|
||||||
|
Domain(id = id, bypassLan = bypassLan)
|
||||||
|
|
||||||
|
fun Domain.toEntity(): Entity =
|
||||||
|
Entity(
|
||||||
|
id = id,
|
||||||
|
bypassLan = bypassLan
|
||||||
|
)
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.mapper
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.ProxySettings as Entity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.ProxySettings as Domain
|
||||||
|
|
||||||
|
fun Entity.toDomain(): Domain =
|
||||||
|
Domain(
|
||||||
|
id = id,
|
||||||
|
socks5ProxyEnabled = socks5ProxyEnabled,
|
||||||
|
socks5ProxyBindAddress = socks5ProxyBindAddress,
|
||||||
|
httpProxyEnabled = httpProxyEnabled,
|
||||||
|
httpProxyBindAddress = httpProxyBindAddress,
|
||||||
|
proxyUsername = proxyUsername,
|
||||||
|
proxyPassword = proxyPassword,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Domain.toEntity(): Entity =
|
||||||
|
Entity(
|
||||||
|
id = id,
|
||||||
|
socks5ProxyEnabled = socks5ProxyEnabled,
|
||||||
|
socks5ProxyBindAddress = socks5ProxyBindAddress,
|
||||||
|
httpProxyEnabled = httpProxyEnabled,
|
||||||
|
httpProxyBindAddress = httpProxyBindAddress,
|
||||||
|
proxyUsername = proxyUsername,
|
||||||
|
proxyPassword = proxyPassword,
|
||||||
|
)
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.mapper
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.model.Theme
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.GeneralSettings as Entity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.GeneralSettings as Domain
|
||||||
|
|
||||||
|
fun Entity.toDomain(): Domain =
|
||||||
|
Domain(
|
||||||
|
id = id,
|
||||||
|
isRestoreOnBootEnabled = isRestoreOnBootEnabled,
|
||||||
|
appMode = appMode,
|
||||||
|
theme = Theme.valueOf(theme.uppercase()),
|
||||||
|
locale = locale,
|
||||||
|
alreadyDonated = alreadyDonated,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Domain.toEntity(): Entity =
|
||||||
|
Entity(
|
||||||
|
id = id,
|
||||||
|
isRestoreOnBootEnabled = isRestoreOnBootEnabled,
|
||||||
|
appMode = appMode,
|
||||||
|
theme = theme.name,
|
||||||
|
locale = locale,
|
||||||
|
alreadyDonated = alreadyDonated,
|
||||||
|
)
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.mapper
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.model.EncryptedField
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.TunnelConfig as Entity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.TunnelConfig as Domain
|
||||||
|
|
||||||
|
fun Entity.toDomain(): Domain =
|
||||||
|
Domain(
|
||||||
|
id = id,
|
||||||
|
name = name,
|
||||||
|
quickConfig = quickConfig.value,
|
||||||
|
tunnelNetworks = tunnelNetworks,
|
||||||
|
isPrimaryTunnel = isPrimaryTunnel,
|
||||||
|
active = active,
|
||||||
|
pingTarget = pingTarget,
|
||||||
|
isEthernetTunnel = isEthernetTunnel,
|
||||||
|
isIpv4Preferred = isIpv4Preferred,
|
||||||
|
position = position,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Domain.toEntity(): Entity =
|
||||||
|
Entity(
|
||||||
|
id = id,
|
||||||
|
name = name,
|
||||||
|
quickConfig = EncryptedField(quickConfig),
|
||||||
|
tunnelNetworks = tunnelNetworks,
|
||||||
|
isPrimaryTunnel = isPrimaryTunnel,
|
||||||
|
active = active,
|
||||||
|
pingTarget = pingTarget,
|
||||||
|
isEthernetTunnel = isEthernetTunnel,
|
||||||
|
isIpv4Preferred = isIpv4Preferred,
|
||||||
|
position = position,
|
||||||
|
)
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.model
|
||||||
|
|
||||||
|
enum class AppMode(val value: Int) {
|
||||||
|
VPN(0),
|
||||||
|
PROXY(1),
|
||||||
|
LOCK_DOWN(2),
|
||||||
|
KERNEL(3);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromValue(value: Int): com.zaneschepke.wireguardautotunnel.client.data.model.AppMode = entries.find { it.value == value } ?: VPN
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.model
|
||||||
|
|
||||||
|
enum class DnsProtocol(val value: Int) {
|
||||||
|
SYSTEM(0),
|
||||||
|
DOH(1);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromValue(value: Int): com.zaneschepke.wireguardautotunnel.client.data.model.DnsProtocol =
|
||||||
|
_root_ide_package_.com.zaneschepke.wireguardautotunnel.client.data.model.DnsProtocol.entries.find { it.value == value } ?: SYSTEM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class DnsProvider(private val systemAddress: String, private val dohAddress: String) {
|
||||||
|
CLOUDFLARE("1.1.1.1", "https://1.1.1.1/dns-query"),
|
||||||
|
ADGUARD("94.140.14.14", "https://94.140.14.14/dns-query");
|
||||||
|
|
||||||
|
fun asAddress(protocol: com.zaneschepke.wireguardautotunnel.client.data.model.DnsProtocol): String {
|
||||||
|
return when (protocol) {
|
||||||
|
_root_ide_package_.com.zaneschepke.wireguardautotunnel.client.data.model.DnsProtocol.SYSTEM -> systemAddress
|
||||||
|
_root_ide_package_.com.zaneschepke.wireguardautotunnel.client.data.model.DnsProtocol.DOH -> dohAddress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromAddress(address: String): com.zaneschepke.wireguardautotunnel.client.data.model.DnsProvider {
|
||||||
|
return entries.find { it.systemAddress == address || it.dohAddress == address }
|
||||||
|
?: CLOUDFLARE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.model
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class EncryptedField(val value: String)
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.model
|
||||||
|
|
||||||
|
enum class Theme {
|
||||||
|
AUTOMATIC,
|
||||||
|
LIGHT,
|
||||||
|
DARK,
|
||||||
|
DARKER,
|
||||||
|
AMOLED,
|
||||||
|
DYNAMIC,
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.repository
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.dao.AutoTunnelSettingsDao
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.mapper.toDomain
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.mapper.toEntity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.repository.AutoTunnelSettingsRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.AutoTunnelSettings as Entity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.AutoTunnelSettings as Domain
|
||||||
|
|
||||||
|
class RoomAutoTunnelSettingsRepository(private val autoTunnelSettingsDao: AutoTunnelSettingsDao) :
|
||||||
|
AutoTunnelSettingsRepository {
|
||||||
|
override suspend fun upsert(autoTunnelSettings: Domain) {
|
||||||
|
autoTunnelSettingsDao.upsert(autoTunnelSettings.toEntity())
|
||||||
|
}
|
||||||
|
|
||||||
|
override val flow: Flow<Domain>
|
||||||
|
get() =
|
||||||
|
autoTunnelSettingsDao.getAutoTunnelSettingsFlow().map { (it ?: Entity()).toDomain() }
|
||||||
|
|
||||||
|
override suspend fun getAutoTunnelSettings(): Domain {
|
||||||
|
return (autoTunnelSettingsDao.getAutoTunnelSettings() ?: Entity()).toDomain()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateAutoTunnelEnabled(enabled: Boolean) {
|
||||||
|
autoTunnelSettingsDao.updateAutoTunnelEnabled(enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.repository
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.dao.DnsSettingsDao
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.mapper.toDomain
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.mapper.toEntity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.repository.DnsSettingsRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.DnsSettings as Entity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.DnsSettings as Domain
|
||||||
|
|
||||||
|
class RoomDnsSettingsRepository(private val dnsSettingsDao: DnsSettingsDao) :
|
||||||
|
DnsSettingsRepository {
|
||||||
|
override suspend fun upsert(dnsSettings: Domain) {
|
||||||
|
dnsSettingsDao.upsert(dnsSettings.toEntity())
|
||||||
|
}
|
||||||
|
|
||||||
|
override val flow: Flow<Domain>
|
||||||
|
get() = dnsSettingsDao.getDnsSettingsFlow().map { (it ?: Entity()).toDomain() }
|
||||||
|
|
||||||
|
override suspend fun getDnsSettings(): Domain {
|
||||||
|
return (dnsSettingsDao.getDnsSettings() ?: Entity()).toDomain()
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.repository
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.dao.LockdownSettingsDao
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.mapper.toDomain
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.mapper.toEntity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.repository.LockdownSettingsRepository
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.LockdownSettings as Entity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.LockdownSettings as Domain
|
||||||
|
|
||||||
|
class RoomLockdownSettingsRepository(private val lockdownSettingsDao: LockdownSettingsDao) :
|
||||||
|
LockdownSettingsRepository {
|
||||||
|
override suspend fun upsert(lockdownSettings: Domain) {
|
||||||
|
lockdownSettingsDao.upsert(lockdownSettings.toEntity())
|
||||||
|
}
|
||||||
|
|
||||||
|
override val flow =
|
||||||
|
lockdownSettingsDao.getLockdownSettingsFlow().map { (it ?: Entity()).toDomain() }
|
||||||
|
|
||||||
|
override suspend fun getLockdownSettings(): Domain {
|
||||||
|
return (lockdownSettingsDao.getLockdownSettings() ?: Entity()).toDomain()
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.repository
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.dao.ProxySettingsDao
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.mapper.toDomain
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.mapper.toEntity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.repository.ProxySettingsRepository
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.ProxySettings as Entity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.ProxySettings as Domain
|
||||||
|
|
||||||
|
class RoomProxySettingsRepository(private val proxySettingsDao: ProxySettingsDao) :
|
||||||
|
ProxySettingsRepository {
|
||||||
|
|
||||||
|
override suspend fun upsert(proxySettings: Domain) {
|
||||||
|
proxySettingsDao.upsert(proxySettings.toEntity())
|
||||||
|
}
|
||||||
|
|
||||||
|
override val flow = proxySettingsDao.getProxySettingsFlow().map { (it ?: Entity()).toDomain() }
|
||||||
|
|
||||||
|
override suspend fun getProxySettings(): Domain {
|
||||||
|
return (proxySettingsDao.getProxySettings() ?: Entity()).toDomain()
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.repository
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.dao.GeneralSettingsDao
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.mapper.toDomain
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.mapper.toEntity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.model.AppMode
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.model.Theme
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.GeneralSettings as Domain
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.entity.GeneralSettings as Entity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.repository.GeneralSettingRepository
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
class RoomSettingsRepository(private val settingsDao: GeneralSettingsDao) :
|
||||||
|
GeneralSettingRepository {
|
||||||
|
|
||||||
|
override suspend fun upsert(generalSettings: Domain) {
|
||||||
|
settingsDao.upsert(generalSettings.toEntity())
|
||||||
|
}
|
||||||
|
|
||||||
|
override val flow = settingsDao.getGeneralSettingsFlow().map { (it ?: Entity()).toDomain() }
|
||||||
|
|
||||||
|
override suspend fun getGeneralSettings(): Domain {
|
||||||
|
return (settingsDao.getGeneralSettings() ?: Entity()).toDomain()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateTheme(theme: Theme) {
|
||||||
|
settingsDao.updateTheme(theme.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateLocale(locale: String) {
|
||||||
|
settingsDao.updateLocale(locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateAppMode(appMode: AppMode) {
|
||||||
|
settingsDao.updateAppMode(appMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.repository
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.dao.TunnelConfigDao
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.mapper.toDomain
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.mapper.toEntity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.repository.TunnelRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.TunnelConfig as Domain
|
||||||
|
|
||||||
|
class RoomTunnelRepository(private val tunnelConfigDao: TunnelConfigDao) : TunnelRepository {
|
||||||
|
|
||||||
|
override val flow =
|
||||||
|
tunnelConfigDao.getAllFlow().map { it.map { tunnelConfig -> tunnelConfig.toDomain() } }
|
||||||
|
|
||||||
|
override val userTunnelsFlow =
|
||||||
|
tunnelConfigDao.getAllTunnelsExceptGlobal().map {
|
||||||
|
it.map { tunnelConfig -> tunnelConfig.toDomain() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override val globalTunnelFlow: Flow<Domain?> =
|
||||||
|
tunnelConfigDao.getGlobalTunnel().map { it?.toDomain() }
|
||||||
|
|
||||||
|
override suspend fun getAll(): List<Domain> {
|
||||||
|
return tunnelConfigDao.getAll().map { it.toDomain() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun save(tunnelConfig: Domain) {
|
||||||
|
tunnelConfigDao.upsert(tunnelConfig.toEntity())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun saveAll(tunnelConfigList: List<Domain>) {
|
||||||
|
tunnelConfigDao.saveAll(tunnelConfigList.map { tunnelConfig -> tunnelConfig.toEntity() })
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updatePrimaryTunnel(tunnelConfig: Domain?) {
|
||||||
|
tunnelConfigDao.resetPrimaryTunnel()
|
||||||
|
tunnelConfig?.let { save(it.copy(isPrimaryTunnel = true)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun resetActiveTunnels() {
|
||||||
|
tunnelConfigDao.resetActiveTunnels()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateEthernetTunnel(tunnelConfig: Domain?) {
|
||||||
|
tunnelConfigDao.resetEthernetTunnel()
|
||||||
|
tunnelConfig?.let { save(it.copy(isEthernetTunnel = true)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(tunnelConfig: Domain) {
|
||||||
|
tunnelConfigDao.delete(tunnelConfig.toEntity())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteByName(name: String) {
|
||||||
|
tunnelConfigDao.deleteByName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getById(id: Int): Domain? {
|
||||||
|
return tunnelConfigDao.getById(id.toLong())?.toDomain()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getActive(): List<Domain> {
|
||||||
|
return tunnelConfigDao.getActive().map { it.toDomain() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getDefaultTunnel(): Domain? {
|
||||||
|
return tunnelConfigDao.getDefaultTunnel()?.toDomain()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getStartTunnel(): Domain? {
|
||||||
|
return tunnelConfigDao.getStartTunnel()?.toDomain()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getTunnelByName(name: String): Domain? {
|
||||||
|
return tunnelConfigDao.getByName(name)?.toDomain()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun count(): Int {
|
||||||
|
return tunnelConfigDao.count().toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findByTunnelName(name: String): Domain? {
|
||||||
|
return tunnelConfigDao.getByName(name)?.toDomain()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findByTunnelNetworksName(name: String): List<Domain> {
|
||||||
|
return tunnelConfigDao.findByTunnelNetworkName(name).map { it.toDomain() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findPrimary(): List<Domain> {
|
||||||
|
return tunnelConfigDao.findByPrimary().map { it.toDomain() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(tunnels: List<Domain>) {
|
||||||
|
tunnelConfigDao.delete(tunnels.map { it.toEntity() })
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.service
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.TunnelConfig
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.repository.TunnelRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.repository.extensions.saveTunnelsUniquely
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.service.QuickConfigMap
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.service.QuickString
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.service.TunnelImportService
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.service.TunnelName
|
||||||
|
|
||||||
|
class DefaultTunnelImportService(
|
||||||
|
private val tunnelRepository: TunnelRepository,
|
||||||
|
) : TunnelImportService {
|
||||||
|
|
||||||
|
override suspend fun import(config: QuickString, name: TunnelName?) {
|
||||||
|
import(mapOf(config to name))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun import(configs: QuickConfigMap) {
|
||||||
|
val tunnelConfigs =
|
||||||
|
configs.map { (config, name) -> TunnelConfig.fromQuickString(config, name) }
|
||||||
|
val existingNames = tunnelRepository.getAll().map { it.name }
|
||||||
|
tunnelRepository.saveTunnelsUniquely(tunnelConfigs, existingNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.service
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.service.DaemonHealthService
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
|
||||||
|
class UdsDaemonHealthService(
|
||||||
|
private val client : HttpClient
|
||||||
|
) : DaemonHealthService {
|
||||||
|
override suspend fun alive(): Boolean {
|
||||||
|
return try {
|
||||||
|
client.get("/status") {
|
||||||
|
}.status.isSuccess()
|
||||||
|
} catch (_ : Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.data.service
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.repository.TunnelRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.service.TunnelCommandService
|
||||||
|
import com.zaneschepke.wireguardautotunnel.core.ipc.dto.BackendMode
|
||||||
|
import com.zaneschepke.wireguardautotunnel.core.ipc.dto.BackendStatus
|
||||||
|
import com.zaneschepke.wireguardautotunnel.core.ipc.dto.StartTunnelRequest
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.call.*
|
||||||
|
import io.ktor.client.plugins.websocket.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.websocket.*
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okio.IOException
|
||||||
|
|
||||||
|
class UdsTunnelCommandService(
|
||||||
|
private val client: HttpClient,
|
||||||
|
private val tunnelRepository: TunnelRepository
|
||||||
|
) : TunnelCommandService {
|
||||||
|
|
||||||
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
override suspend fun startTunnel(id: Int): Result<Unit> = runCatching {
|
||||||
|
val tunnelConfig = tunnelRepository.getById(id)
|
||||||
|
?: throw IOException("Tunnel $id not found")
|
||||||
|
|
||||||
|
val request = StartTunnelRequest(
|
||||||
|
id = id,
|
||||||
|
name = tunnelConfig.name,
|
||||||
|
quickConfig = tunnelConfig.quickConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
val response = client.post("/tunnel/start") {
|
||||||
|
setBody(json.encodeToString(request))
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.status.isSuccess()) {
|
||||||
|
throw IOException("Failed to start tunnel $id: ${response.status.value} - ${response.bodyAsText()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun stopTunnel(id: Int): Result<Unit> = runCatching {
|
||||||
|
val response = client.post("/tunnel/stop/$id")
|
||||||
|
|
||||||
|
if (!response.status.isSuccess()) {
|
||||||
|
throw IOException("Failed to stop tunnel $id: ${response.status.value} - ${response.bodyAsText()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setMode(mode: BackendMode): Result<Unit> = runCatching {
|
||||||
|
val response = client.post("/tunnel/mode") {
|
||||||
|
setBody(json.encodeToString(mode))
|
||||||
|
contentType(ContentType.Text.Plain)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.status.isSuccess()) {
|
||||||
|
throw IOException("Failed to set mode: ${response.bodyAsText()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setKillSwitch(enabled: Boolean): Result<Unit> = runCatching {
|
||||||
|
val response = client.post("/tunnel/kill-switch") {
|
||||||
|
setBody(enabled.toString())
|
||||||
|
contentType(ContentType.Text.Plain)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.status.isSuccess()) {
|
||||||
|
throw IOException("Failed to set kill switch: ${response.bodyAsText()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getStatus(): Result<BackendStatus> = runCatching {
|
||||||
|
val response = client.get("/tunnel/status")
|
||||||
|
|
||||||
|
if (!response.status.isSuccess()) {
|
||||||
|
throw IOException("Failed to get status: ${response.status.value} - ${response.bodyAsText()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
response.body<BackendStatus>()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun statusFlow(): Flow<BackendStatus> = callbackFlow {
|
||||||
|
val session = client.webSocketSession("/tunnel/status/stream")
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (frame in session.incoming) {
|
||||||
|
if (frame is Frame.Text) {
|
||||||
|
val dto = json.decodeFromString<BackendStatus>(frame.readText())
|
||||||
|
trySend(dto)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
close(e)
|
||||||
|
} finally {
|
||||||
|
session.close()
|
||||||
|
awaitClose()
|
||||||
|
}
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.di
|
||||||
|
|
||||||
|
enum class Secret {
|
||||||
|
IPC
|
||||||
|
}
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.di
|
||||||
|
|
||||||
|
import androidx.room.Room
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.AppDatabase
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.AppKeyringConverter
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.DatabaseCallback
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.DatabaseConverters
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.dao.*
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.repository.*
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.repository.*
|
||||||
|
import com.zaneschepke.wireguardautotunnel.core.crypto.Crypto
|
||||||
|
import com.zaneschepke.wireguardautotunnel.keyring.Keyring
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import org.koin.dsl.module
|
||||||
|
import java.io.File
|
||||||
|
import javax.crypto.SecretKey
|
||||||
|
|
||||||
|
val databaseModule = module {
|
||||||
|
single<RoomDatabase.Callback> { DatabaseCallback(lazy { get<AppDatabase>() }) }
|
||||||
|
single<SecretKey> {
|
||||||
|
val dbKey = AppDatabase.DB_SECRET_KEY
|
||||||
|
val keyring = Keyring(AppDatabase.DB_KEYRING)
|
||||||
|
val encodedSecret = keyring.get(dbKey) ?: run {
|
||||||
|
val secret = Crypto.generateRandomBase64EncodedAesKey()
|
||||||
|
keyring.put(dbKey, secret)
|
||||||
|
secret
|
||||||
|
}
|
||||||
|
Crypto.decodeKey(encodedSecret)
|
||||||
|
}
|
||||||
|
single<AppDatabase> {
|
||||||
|
val dbFileName = AppDatabase.DB_FILE_NAME
|
||||||
|
val dbDir = AppDatabase.getDatabaseDir()
|
||||||
|
dbDir.mkdirs()
|
||||||
|
val dbFile = File(dbDir, dbFileName)
|
||||||
|
Room.databaseBuilder<AppDatabase>(dbFile.absolutePath)
|
||||||
|
.setDriver(BundledSQLiteDriver())
|
||||||
|
.fallbackToDestructiveMigration(true)
|
||||||
|
.addCallback(get())
|
||||||
|
.addTypeConverter(DatabaseConverters())
|
||||||
|
.addTypeConverter(AppKeyringConverter())
|
||||||
|
.setQueryCoroutineContext(Dispatchers.IO)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
single<TunnelConfigDao> { get<AppDatabase>().tunnelConfigDao() }
|
||||||
|
single<AutoTunnelSettingsDao> { get<AppDatabase>().autoTunnelSettingsDao() }
|
||||||
|
single<DnsSettingsDao> { get<AppDatabase>().dnsSettingsDao() }
|
||||||
|
single<LockdownSettingsDao> { get<AppDatabase>().lockdownSettingsDao() }
|
||||||
|
single<ProxySettingsDao> { get<AppDatabase>().proxySettingsDao() }
|
||||||
|
single<GeneralSettingsDao> { get<AppDatabase>().generalSettingsDao() }
|
||||||
|
|
||||||
|
single<TunnelRepository>() { RoomTunnelRepository(get()) }
|
||||||
|
single<AutoTunnelSettingsRepository>() { RoomAutoTunnelSettingsRepository(get()) }
|
||||||
|
single<DnsSettingsRepository>() { RoomDnsSettingsRepository(get()) }
|
||||||
|
single<LockdownSettingsRepository>() { RoomLockdownSettingsRepository(get()) }
|
||||||
|
single<ProxySettingsRepository>() { RoomProxySettingsRepository(get()) }
|
||||||
|
}
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.di
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.service.DefaultTunnelImportService
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.service.UdsDaemonHealthService
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.service.UdsTunnelCommandService
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.service.DaemonHealthService
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.service.TunnelCommandService
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.service.TunnelImportService
|
||||||
|
import com.zaneschepke.wireguardautotunnel.core.crypto.HmacProtector
|
||||||
|
import com.zaneschepke.wireguardautotunnel.core.ipc.IPC
|
||||||
|
import com.zaneschepke.wireguardautotunnel.core.ipc.dto.SecureCommand
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.engine.cio.*
|
||||||
|
import io.ktor.client.plugins.*
|
||||||
|
import io.ktor.client.plugins.contentnegotiation.*
|
||||||
|
import io.ktor.client.plugins.websocket.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.http.content.*
|
||||||
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val serviceModule = module {
|
||||||
|
single {
|
||||||
|
// so daemon knows where to look for secret
|
||||||
|
val user = System.getProperty("user.name")
|
||||||
|
HttpClient(CIO) {
|
||||||
|
defaultRequest {
|
||||||
|
unixSocket(IPC.getDaemonSocketPath())
|
||||||
|
}
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
encodeDefaults = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
install(WebSockets)
|
||||||
|
install("HmacSigner") {
|
||||||
|
requestPipeline.intercept(HttpRequestPipeline.Before) {
|
||||||
|
|
||||||
|
if (subject is SecureCommand) {
|
||||||
|
return@intercept
|
||||||
|
}
|
||||||
|
|
||||||
|
val payload = when (val body = subject) {
|
||||||
|
is String -> body
|
||||||
|
is TextContent -> body.text
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val timestamp = System.currentTimeMillis() / 1000
|
||||||
|
val signature = HmacProtector.generateSignature(
|
||||||
|
IPC.getIPCSecret(),
|
||||||
|
timestamp,
|
||||||
|
payload
|
||||||
|
)
|
||||||
|
|
||||||
|
val secureCommand = SecureCommand(timestamp, signature, user, payload)
|
||||||
|
context.contentType(ContentType.Application.Json)
|
||||||
|
context.setBody(secureCommand)
|
||||||
|
|
||||||
|
proceedWith(secureCommand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
single<DaemonHealthService> { UdsDaemonHealthService(get()) }
|
||||||
|
single<TunnelCommandService> { UdsTunnelCommandService(get(), tunnelRepository = get()) }
|
||||||
|
single<TunnelImportService> {
|
||||||
|
DefaultTunnelImportService(get())
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.domain.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AutoTunnelSettings(
|
||||||
|
val id: Int = 0,
|
||||||
|
val isAutoTunnelEnabled: Boolean = false,
|
||||||
|
val isTunnelOnMobileDataEnabled: Boolean = false,
|
||||||
|
val trustedNetworkSSIDs: Set<String> = emptySet(),
|
||||||
|
val isTunnelOnEthernetEnabled: Boolean = false,
|
||||||
|
val isTunnelOnWifiEnabled: Boolean = false,
|
||||||
|
val isWildcardsEnabled: Boolean = false,
|
||||||
|
val isStopOnNoInternetEnabled: Boolean = false,
|
||||||
|
val debounceDelaySeconds: Int = 3,
|
||||||
|
val isTunnelOnUnsecureEnabled: Boolean = false,
|
||||||
|
val wifiDetectionMethod: Int = 0,
|
||||||
|
val startOnBoot: Boolean = false,
|
||||||
|
)
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.domain.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class DnsSettings(
|
||||||
|
val id: Int = 0,
|
||||||
|
val dnsProtocol: Int = 0,
|
||||||
|
val dnsEndpoint: String? = null,
|
||||||
|
val isGlobalTunnelDnsEnabled: Boolean = false,
|
||||||
|
)
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.domain.model
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.model.AppMode
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.model.Theme
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GeneralSettings(
|
||||||
|
val id: Int = 0,
|
||||||
|
val isRestoreOnBootEnabled: Boolean = false,
|
||||||
|
val appMode: AppMode = AppMode.fromValue(0),
|
||||||
|
val theme: Theme = Theme.AUTOMATIC,
|
||||||
|
val locale: String? = null,
|
||||||
|
val alreadyDonated: Boolean = false,
|
||||||
|
)
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.domain.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class LockdownSettings(
|
||||||
|
val id: Long = 0L,
|
||||||
|
val bypassLan: Boolean = false,
|
||||||
|
val metered: Boolean = false,
|
||||||
|
val dualStack: Boolean = false,
|
||||||
|
)
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.domain.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ProxySettings(
|
||||||
|
val id: Long = 0,
|
||||||
|
val socks5ProxyEnabled: Boolean = false,
|
||||||
|
val socks5ProxyBindAddress: String? = null,
|
||||||
|
val httpProxyEnabled: Boolean = false,
|
||||||
|
val httpProxyBindAddress: String? = null,
|
||||||
|
val proxyUsername: String? = null,
|
||||||
|
val proxyPassword: String? = null,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
const val DEFAULT_SOCKS_BIND_ADDRESS = "127.0.0.1:25344"
|
||||||
|
const val DEFAULT_HTTP_BIND_ADDRESS = "127.0.0.1:25345"
|
||||||
|
}
|
||||||
|
}
|
||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.domain.model
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.parser.Config
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlin.collections.get
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TunnelConfig(
|
||||||
|
val id: Int = 0,
|
||||||
|
val name: String,
|
||||||
|
val quickConfig: String,
|
||||||
|
val tunnelNetworks: Set<String> = setOf(),
|
||||||
|
val isPrimaryTunnel: Boolean = false,
|
||||||
|
val active: Boolean = false,
|
||||||
|
val pingTarget: String? = null,
|
||||||
|
val isEthernetTunnel: Boolean = false,
|
||||||
|
val isIpv4Preferred: Boolean = true,
|
||||||
|
val position: Int = 0,
|
||||||
|
) {
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is TunnelConfig) return false
|
||||||
|
return id == other.id &&
|
||||||
|
name == other.name &&
|
||||||
|
quickConfig == other.quickConfig &&
|
||||||
|
isPrimaryTunnel == other.isPrimaryTunnel &&
|
||||||
|
isEthernetTunnel == other.isEthernetTunnel &&
|
||||||
|
pingTarget == other.pingTarget &&
|
||||||
|
tunnelNetworks == other.tunnelNetworks &&
|
||||||
|
isIpv4Preferred == other.isIpv4Preferred
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = id
|
||||||
|
result = 31 * result + name.hashCode()
|
||||||
|
result = 31 * result + quickConfig.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun asConfig(): Config {
|
||||||
|
return Config.parseQuickString(quickConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun generateRandom8Digits(): String {
|
||||||
|
val digits = ('0'..'9').toList()
|
||||||
|
return (1..8).map { digits.random() }.joinToString("")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateDefaultTunnelName(config: Config? = null): String {
|
||||||
|
return config?.peers[0]?.host ?: generateRandom8Digits()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun configFromQuick(quick: String): Config {
|
||||||
|
return Config.parseQuickString(quick)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fromQuickString(quick: String, name: String? = null): TunnelConfig {
|
||||||
|
val config = configFromQuick(quick)
|
||||||
|
return tunnelConfFromConfig(config, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tunnelConfFromConfig(config: Config, name: String? = null): TunnelConfig {
|
||||||
|
return TunnelConfig(
|
||||||
|
name = name ?: generateDefaultTunnelName(config),
|
||||||
|
quickConfig = config.asQuickString(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
private const val IPV6_ALL_NETWORKS = "::/0"
|
||||||
|
private const val IPV4_ALL_NETWORKS = "0.0.0.0/0"
|
||||||
|
val ALL_IPS = listOf(IPV4_ALL_NETWORKS, IPV6_ALL_NETWORKS)
|
||||||
|
val IPV4_PUBLIC_NETWORKS =
|
||||||
|
setOf(
|
||||||
|
"0.0.0.0/5",
|
||||||
|
"8.0.0.0/7",
|
||||||
|
"11.0.0.0/8",
|
||||||
|
"12.0.0.0/6",
|
||||||
|
"16.0.0.0/4",
|
||||||
|
"32.0.0.0/3",
|
||||||
|
"64.0.0.0/2",
|
||||||
|
"128.0.0.0/3",
|
||||||
|
"160.0.0.0/5",
|
||||||
|
"168.0.0.0/6",
|
||||||
|
"172.0.0.0/12",
|
||||||
|
"172.32.0.0/11",
|
||||||
|
"172.64.0.0/10",
|
||||||
|
"172.128.0.0/9",
|
||||||
|
"173.0.0.0/8",
|
||||||
|
"174.0.0.0/7",
|
||||||
|
"176.0.0.0/4",
|
||||||
|
"192.0.0.0/9",
|
||||||
|
"192.128.0.0/11",
|
||||||
|
"192.160.0.0/13",
|
||||||
|
"192.169.0.0/16",
|
||||||
|
"192.170.0.0/15",
|
||||||
|
"192.172.0.0/14",
|
||||||
|
"192.176.0.0/12",
|
||||||
|
"192.192.0.0/10",
|
||||||
|
"193.0.0.0/8",
|
||||||
|
"194.0.0.0/7",
|
||||||
|
"196.0.0.0/6",
|
||||||
|
"200.0.0.0/5",
|
||||||
|
"208.0.0.0/4",
|
||||||
|
)
|
||||||
|
val LAN_BYPASS_ALLOWED_IPS = setOf(IPV6_ALL_NETWORKS) + IPV4_PUBLIC_NETWORKS
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.domain.repository
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.AutoTunnelSettings
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface AutoTunnelSettingsRepository {
|
||||||
|
suspend fun upsert(autoTunnelSettings: AutoTunnelSettings)
|
||||||
|
|
||||||
|
val flow: Flow<AutoTunnelSettings>
|
||||||
|
|
||||||
|
suspend fun getAutoTunnelSettings(): AutoTunnelSettings
|
||||||
|
|
||||||
|
suspend fun updateAutoTunnelEnabled(enabled: Boolean)
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.domain.repository
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.DnsSettings
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface DnsSettingsRepository {
|
||||||
|
suspend fun upsert(dnsSettings: DnsSettings)
|
||||||
|
|
||||||
|
val flow: Flow<DnsSettings>
|
||||||
|
|
||||||
|
suspend fun getDnsSettings(): DnsSettings
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.domain.repository
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.model.AppMode
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.data.model.Theme
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.GeneralSettings
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface GeneralSettingRepository {
|
||||||
|
suspend fun upsert(generalSettings: GeneralSettings)
|
||||||
|
|
||||||
|
val flow: Flow<GeneralSettings>
|
||||||
|
|
||||||
|
suspend fun getGeneralSettings(): GeneralSettings
|
||||||
|
|
||||||
|
suspend fun updateTheme(theme: Theme)
|
||||||
|
|
||||||
|
suspend fun updateLocale(locale: String)
|
||||||
|
|
||||||
|
suspend fun updateAppMode(appMode: AppMode)
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.domain.repository
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.LockdownSettings
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface LockdownSettingsRepository {
|
||||||
|
suspend fun upsert(lockdownSettings: LockdownSettings)
|
||||||
|
|
||||||
|
val flow: Flow<LockdownSettings>
|
||||||
|
|
||||||
|
suspend fun getLockdownSettings(): LockdownSettings
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.domain.repository
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.ProxySettings
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface ProxySettingsRepository {
|
||||||
|
suspend fun upsert(proxySettings: ProxySettings)
|
||||||
|
|
||||||
|
val flow: Flow<ProxySettings>
|
||||||
|
|
||||||
|
suspend fun getProxySettings(): ProxySettings
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.domain.repository
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.TunnelConfig
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface TunnelRepository {
|
||||||
|
val flow: Flow<List<TunnelConfig>>
|
||||||
|
|
||||||
|
val userTunnelsFlow: Flow<List<TunnelConfig>>
|
||||||
|
|
||||||
|
val globalTunnelFlow: Flow<TunnelConfig?>
|
||||||
|
|
||||||
|
suspend fun getAll(): List<TunnelConfig>
|
||||||
|
|
||||||
|
suspend fun save(tunnelConfig: TunnelConfig)
|
||||||
|
|
||||||
|
suspend fun saveAll(tunnelConfigList: List<TunnelConfig>)
|
||||||
|
|
||||||
|
suspend fun updatePrimaryTunnel(tunnelConfig: TunnelConfig?)
|
||||||
|
|
||||||
|
suspend fun resetActiveTunnels()
|
||||||
|
|
||||||
|
suspend fun updateEthernetTunnel(tunnelConfig: TunnelConfig?)
|
||||||
|
|
||||||
|
suspend fun delete(tunnelConfig: TunnelConfig)
|
||||||
|
|
||||||
|
suspend fun deleteByName(name: String)
|
||||||
|
|
||||||
|
suspend fun getById(id: Int): TunnelConfig?
|
||||||
|
|
||||||
|
suspend fun getActive(): List<TunnelConfig>
|
||||||
|
|
||||||
|
suspend fun getDefaultTunnel(): TunnelConfig?
|
||||||
|
|
||||||
|
suspend fun getStartTunnel(): TunnelConfig?
|
||||||
|
|
||||||
|
suspend fun getTunnelByName(name: String): TunnelConfig?
|
||||||
|
|
||||||
|
suspend fun count(): Int
|
||||||
|
|
||||||
|
suspend fun findByTunnelName(name: String): TunnelConfig?
|
||||||
|
|
||||||
|
suspend fun findByTunnelNetworksName(name: String): List<TunnelConfig>
|
||||||
|
|
||||||
|
suspend fun findPrimary(): List<TunnelConfig>
|
||||||
|
|
||||||
|
suspend fun delete(tunnels: List<TunnelConfig>)
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.domain.repository.extensions
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.model.TunnelConfig
|
||||||
|
import com.zaneschepke.wireguardautotunnel.client.domain.repository.TunnelRepository
|
||||||
|
|
||||||
|
suspend fun TunnelRepository.saveTunnelsUniquely(
|
||||||
|
tunnels: List<TunnelConfig>,
|
||||||
|
existingNames: List<String>,
|
||||||
|
) {
|
||||||
|
val uniqueTunnels =
|
||||||
|
generateUniquelyNamedConfigs(
|
||||||
|
tunnels,
|
||||||
|
existingNames
|
||||||
|
)
|
||||||
|
saveAll(uniqueTunnels)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateUniquelyNamedConfigs(
|
||||||
|
incoming: List<TunnelConfig>,
|
||||||
|
existingNames: List<String>,
|
||||||
|
): List<TunnelConfig> {
|
||||||
|
val usedNames = existingNames.toMutableSet()
|
||||||
|
val result = mutableListOf<TunnelConfig>()
|
||||||
|
val regex = Regex("(.+)\\s*\\((\\d+)\\)$")
|
||||||
|
|
||||||
|
for (tun in incoming) {
|
||||||
|
var baseName = tun.name
|
||||||
|
var uniqueName = tun.name
|
||||||
|
var counter = 1
|
||||||
|
|
||||||
|
val matchResult = regex.find(baseName)
|
||||||
|
if (matchResult != null) {
|
||||||
|
baseName = matchResult.groupValues[1].trimEnd()
|
||||||
|
counter = matchResult.groupValues[2].toIntOrNull()?.plus(1) ?: 1
|
||||||
|
uniqueName = "$baseName ($counter)"
|
||||||
|
}
|
||||||
|
|
||||||
|
while (uniqueName in usedNames) {
|
||||||
|
uniqueName = "$baseName ($counter)"
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
|
||||||
|
usedNames.add(uniqueName)
|
||||||
|
result.add(tun.copy(name = uniqueName))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.service
|
||||||
|
|
||||||
|
interface DaemonHealthService {
|
||||||
|
suspend fun alive(): Boolean
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.service
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.core.ipc.dto.BackendMode
|
||||||
|
import com.zaneschepke.wireguardautotunnel.core.ipc.dto.BackendStatus
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface TunnelCommandService {
|
||||||
|
suspend fun startTunnel(id: Int): Result<Unit>
|
||||||
|
suspend fun stopTunnel(id: Int): Result<Unit>
|
||||||
|
suspend fun setMode(mode: BackendMode): Result<Unit>
|
||||||
|
suspend fun setKillSwitch(enabled: Boolean): Result<Unit>
|
||||||
|
suspend fun getStatus(): Result<BackendStatus>
|
||||||
|
fun statusFlow(): Flow<BackendStatus>
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.client.service
|
||||||
|
|
||||||
|
|
||||||
|
typealias QuickString = String
|
||||||
|
typealias TunnelName = String
|
||||||
|
typealias QuickConfigMap = Map<QuickString, TunnelName?>
|
||||||
|
|
||||||
|
interface TunnelImportService {
|
||||||
|
suspend fun import(config: QuickString, name: TunnelName? = null)
|
||||||
|
suspend fun import(configs: QuickConfigMap)
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">WG Tunnel</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.kotlinMultiplatform)
|
||||||
|
alias(libs.plugins.jetbrainsCompose)
|
||||||
|
alias(libs.plugins.composeCompiler)
|
||||||
|
alias(libs.plugins.composeHotReload)
|
||||||
|
alias(libs.plugins.conveyor)
|
||||||
|
}
|
||||||
|
|
||||||
|
group = "com.zaneschepke.wireguardautotunnel"
|
||||||
|
version = libs.versions.app.get()
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvm()
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
commonMain.dependencies {
|
||||||
|
implementation(project(":client"))
|
||||||
|
implementation(libs.compose.runtime)
|
||||||
|
implementation(libs.compose.foundation)
|
||||||
|
implementation(libs.compose.material3)
|
||||||
|
implementation(libs.compose.ui)
|
||||||
|
implementation(libs.compose.components.resources)
|
||||||
|
implementation(libs.compose.uiToolingPreview)
|
||||||
|
implementation(libs.androidx.lifecycle.viewmodelCompose)
|
||||||
|
implementation(libs.androidx.lifecycle.runtimeCompose)
|
||||||
|
}
|
||||||
|
commonTest.dependencies {
|
||||||
|
implementation(libs.kotlin.test)
|
||||||
|
}
|
||||||
|
jvmMain.dependencies {
|
||||||
|
implementation(compose.desktop.currentOs)
|
||||||
|
implementation(libs.kotlinx.coroutinesSwing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compose.desktop {
|
||||||
|
application {
|
||||||
|
mainClass = "com.zaneschepke.wireguardautotunnel.desktop.MainKt"
|
||||||
|
|
||||||
|
nativeDistributions {
|
||||||
|
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb, TargetFormat.Rpm, TargetFormat.AppImage)
|
||||||
|
packageName = "com.zaneschepke.wireguardautotunnel.desktop"
|
||||||
|
packageVersion = libs.versions.app.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conveyor
|
||||||
|
dependencies {
|
||||||
|
linuxAmd64(libs.desktop.jvm.linux.x64)
|
||||||
|
macAmd64(libs.desktop.jvm.macos.x64)
|
||||||
|
macAarch64(libs.desktop.jvm.macos.arm64)
|
||||||
|
windowsAmd64(libs.desktop.jvm.windows.x64)
|
||||||
|
windowsAarch64(libs.desktop.jvm.windows.arm64)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named<Delete>("clean") {
|
||||||
|
delete(file("generated.conveyor.conf"))
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="450dp"
|
||||||
|
android:height="450dp"
|
||||||
|
android:viewportWidth="64"
|
||||||
|
android:viewportHeight="64">
|
||||||
|
<path
|
||||||
|
android:pathData="M56.25,18V46L32,60 7.75,46V18L32,4Z"
|
||||||
|
android:fillColor="#6075f2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m41.5,26.5v11L32,43V60L56.25,46V18Z"
|
||||||
|
android:fillColor="#6b57ff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m32,43 l-9.5,-5.5v-11L7.75,18V46L32,60Z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:centerX="23.131"
|
||||||
|
android:centerY="18.441"
|
||||||
|
android:gradientRadius="42.132"
|
||||||
|
android:type="radial">
|
||||||
|
<item android:offset="0" android:color="#FF5383EC"/>
|
||||||
|
<item android:offset="0.867" android:color="#FF7F52FF"/>
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:pathData="M22.5,26.5 L32,21 41.5,26.5 56.25,18 32,4 7.75,18Z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:startX="44.172"
|
||||||
|
android:startY="4.377"
|
||||||
|
android:endX="17.973"
|
||||||
|
android:endY="34.035"
|
||||||
|
android:type="linear">
|
||||||
|
<item android:offset="0" android:color="#FF33C3FF"/>
|
||||||
|
<item android:offset="0.878" android:color="#FF5383EC"/>
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:pathData="m32,21 l9.526,5.5v11L32,43 22.474,37.5v-11z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.desktop
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.safeContentPadding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview
|
||||||
|
fun App() {
|
||||||
|
MaterialTheme {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(MaterialTheme.colorScheme.primaryContainer)
|
||||||
|
.safeContentPadding()
|
||||||
|
.fillMaxSize(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.desktop
|
||||||
|
|
||||||
|
import androidx.compose.ui.window.Window
|
||||||
|
import androidx.compose.ui.window.application
|
||||||
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
|
import com.zaneschepke.wireguardautotunnel.SharedRes
|
||||||
|
|
||||||
|
fun main() = application {
|
||||||
|
Window(
|
||||||
|
onCloseRequest = ::exitApplication,
|
||||||
|
title = stringResource(SharedRes.strings.app_name)
|
||||||
|
) {
|
||||||
|
App()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.desktop
|
||||||
|
|
||||||
|
class JVMPlatform {
|
||||||
|
val name: String = "Java ${System.getProperty("java.version")}"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPlatform() = JVMPlatform()
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.desktop
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class ComposeAppDesktopTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun example() {
|
||||||
|
assertEquals(3, 1 + 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
+204
@@ -0,0 +1,204 @@
|
|||||||
|
include required("https://raw.githubusercontent.com/hydraulic-software/conveyor/master/configs/jvm/extract-native-libraries.conf")
|
||||||
|
include required("composeApp/generated.conveyor.conf")
|
||||||
|
|
||||||
|
app {
|
||||||
|
fsname = wgtunnel
|
||||||
|
display-name = "WG Tunnel"
|
||||||
|
description = "WG Tunnel: WireGuard and AmneziaWG VPN client with auto-tunneling, lockdown and proxying."
|
||||||
|
license = MIT
|
||||||
|
homepage = "https://wgtunnel.com"
|
||||||
|
|
||||||
|
site.base-url = "http://localhost"
|
||||||
|
|
||||||
|
icons = ["icon.png"]
|
||||||
|
|
||||||
|
jvm {
|
||||||
|
# for performance
|
||||||
|
options += "-XX:+UseG1GC"
|
||||||
|
options += "-XX:+UseStringDeduplication"
|
||||||
|
|
||||||
|
# for high-res displays
|
||||||
|
system-properties {
|
||||||
|
"sun.java2d.uiScale" = "1.0"
|
||||||
|
"apple.laf.useScreenMenuBar" = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
modules = [ detect ]
|
||||||
|
|
||||||
|
gui {
|
||||||
|
main-class = com.zaneschepke.wireguardautotunnel.desktop.MainKt
|
||||||
|
}
|
||||||
|
|
||||||
|
cli {
|
||||||
|
wgtctl {
|
||||||
|
main-class = com.zaneschepke.wireguardautotunnel.cli.MainKt
|
||||||
|
exe-name = wgtctl
|
||||||
|
}
|
||||||
|
daemon {
|
||||||
|
main-class = com.zaneschepke.wireguardautotunnel.daemon.MainKt
|
||||||
|
console = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs += "composeApp/build/libs/*.jar"
|
||||||
|
inputs += "daemon/build/install/daemon/lib/*.jar"
|
||||||
|
inputs += "cli/build/install/cli/lib/*.jar"
|
||||||
|
|
||||||
|
// Target platforms
|
||||||
|
machines = [
|
||||||
|
linux.amd64.glibc,
|
||||||
|
windows.amd64,
|
||||||
|
// windows.aarch64,
|
||||||
|
mac.amd64,
|
||||||
|
mac.aarch64
|
||||||
|
]
|
||||||
|
|
||||||
|
linux {
|
||||||
|
deb.depends = ["systemd"]
|
||||||
|
rpm.requires = ["systemd"]
|
||||||
|
|
||||||
|
desktop-file {
|
||||||
|
"Desktop Entry" {
|
||||||
|
Categories = "Network;Security;Settings;Utility;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# for CLI
|
||||||
|
symlinks = [
|
||||||
|
/usr/bin/wgtunnel -> ${app.linux.install-path}/bin/wgtunnel,
|
||||||
|
/usr/bin/wgtctl -> ${app.linux.install-path}/bin/wgtctl,
|
||||||
|
/usr/bin/wgt -> ${app.linux.install-path}/bin/wgtctl
|
||||||
|
]
|
||||||
|
|
||||||
|
services {
|
||||||
|
daemon {
|
||||||
|
include "/stdlib/linux/service.conf"
|
||||||
|
|
||||||
|
file-name = "wgtunnel-daemon.service"
|
||||||
|
|
||||||
|
Unit {
|
||||||
|
Description = "WG Tunnel Daemon"
|
||||||
|
Documentation = "https://wgtunnel.com"
|
||||||
|
Before = "network-online.target"
|
||||||
|
After = "NetworkManager.service systemd-resolved.service"
|
||||||
|
StartLimitBurst = 5
|
||||||
|
StartLimitIntervalSec = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
Service {
|
||||||
|
Restart = always
|
||||||
|
RestartSec = 1s
|
||||||
|
ExecStart = ${app.linux.install-path}/bin/daemon
|
||||||
|
Type = exec
|
||||||
|
|
||||||
|
StandardOutput = journal
|
||||||
|
StandardError = journal
|
||||||
|
|
||||||
|
Environment = [
|
||||||
|
"WG_TUNNEL_SERVICE=1",
|
||||||
|
"HOME=%S/wgtunnel"
|
||||||
|
]
|
||||||
|
|
||||||
|
WorkingDirectory = ${app.linux.install-path}
|
||||||
|
|
||||||
|
# Allow socket access
|
||||||
|
UMask = 0000
|
||||||
|
|
||||||
|
ProtectSystem = full
|
||||||
|
|
||||||
|
StateDirectory = "wgtunnel"
|
||||||
|
LogsDirectory = "wgtunnel"
|
||||||
|
ConfigurationDirectory = "wgtunnel"
|
||||||
|
RuntimeDirectory = "wgtunnel"
|
||||||
|
RuntimeDirectoryMode = 0755
|
||||||
|
RuntimeDirectoryPreserve = "restart"
|
||||||
|
|
||||||
|
# Added CAP_DAC_OVERRIDE for per user IPC key read
|
||||||
|
CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_DAC_OVERRIDE"
|
||||||
|
AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_DAC_OVERRIDE"
|
||||||
|
|
||||||
|
RestrictAddressFamilies = "AF_INET AF_INET6 AF_NETLINK AF_UNIX"
|
||||||
|
|
||||||
|
KillSignal = SIGTERM
|
||||||
|
TimeoutStopSec = 30
|
||||||
|
|
||||||
|
ReadWritePaths = [
|
||||||
|
"/run/wgtunnel",
|
||||||
|
"/etc/resolv.conf",
|
||||||
|
"/var/lib/wgtunnel",
|
||||||
|
"/home", # Need home to be able to read user's IPC key
|
||||||
|
"/etc/resolv.conf",
|
||||||
|
"/run/systemd/resolve",
|
||||||
|
"/run/systemd/resolve/stub-resolv.conf",
|
||||||
|
"/run/systemd/resolve/resolv.conf"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Install {
|
||||||
|
WantedBy = "multi-user.target"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mac {
|
||||||
|
|
||||||
|
entitlements-plist = {
|
||||||
|
"com.apple.security.network.client" = true
|
||||||
|
"com.apple.security.network.server" = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
windows {
|
||||||
|
|
||||||
|
inputs += daemon/winsw/artifacts/publish/WinSW-x64.exe -> service-wrapper.exe
|
||||||
|
|
||||||
|
aarch64 {
|
||||||
|
inputs += tunnel/tools/wintun/arm64/wintun.dll -> wintun.dll
|
||||||
|
}
|
||||||
|
amd64 {
|
||||||
|
inputs += tunnel/tools/wintun/amd64/wintun.dll -> wintun.dll
|
||||||
|
}
|
||||||
|
|
||||||
|
manifests {
|
||||||
|
|
||||||
|
exe {
|
||||||
|
requested-execution-level = asInvoker
|
||||||
|
}
|
||||||
|
msix {
|
||||||
|
display-name = "WG Tunnel"
|
||||||
|
description = "WireGuard and AmneziaWG VPN client with auto-tunneling, lockdown and proxying."
|
||||||
|
|
||||||
|
min-version = "10.0.19041.0"
|
||||||
|
capabilities += "rescap:allowElevation"
|
||||||
|
capabilities += "rescap:localSystemServices"
|
||||||
|
capabilities += "rescap:packagedServices"
|
||||||
|
|
||||||
|
namespaces {
|
||||||
|
desktop6 = "http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
|
||||||
|
uap3 = "http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
|
||||||
|
}
|
||||||
|
|
||||||
|
ignorable-namespaces += "desktop6"
|
||||||
|
ignorable-namespaces += "uap3"
|
||||||
|
|
||||||
|
extensions-xml = """
|
||||||
|
<desktop6:Extension Category="windows.service" Executable="bin/service-wrapper.exe" EntryPoint="Windows.FullTrustApplication">
|
||||||
|
<desktop6:Service Name="wgtunnel-daemon" StartupType="auto" StartAccount="localSystem" />
|
||||||
|
</desktop6:Extension>
|
||||||
|
"""
|
||||||
|
|
||||||
|
virtualization {
|
||||||
|
excluded-directories += "LocalAppData/Temp"
|
||||||
|
excluded-directories += "CommonAppData/wgtunnel"
|
||||||
|
excluded-directories += "CommonAppData/wgtunnel/logs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start-on-login = false
|
||||||
|
updates = background
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conveyor.compatibility-level = 21
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
plugins {
|
||||||
|
kotlin("jvm")
|
||||||
|
alias(libs.plugins.serialization)
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.kotlinx.serialization)
|
||||||
|
implementation(libs.apache.commons.lang3)
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
implementation(libs.kermit)
|
||||||
|
implementation(libs.logback.classic)
|
||||||
|
|
||||||
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
|
|
||||||
|
// Backoff
|
||||||
|
implementation(libs.kotlin.retry)
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.core.crypto
|
||||||
|
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.SecretKey
|
||||||
|
import javax.crypto.spec.GCMParameterSpec
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
import kotlin.io.encoding.Base64
|
||||||
|
|
||||||
|
object Crypto {
|
||||||
|
|
||||||
|
const val KEY_ALGORITHM = "AES"
|
||||||
|
const val CYPHER = "AES/GCM/NoPadding"
|
||||||
|
|
||||||
|
private val random = SecureRandom()
|
||||||
|
|
||||||
|
fun generateRandomBase64(byteLength: Int = 32): String {
|
||||||
|
val bytes = ByteArray(byteLength)
|
||||||
|
random.nextBytes(bytes)
|
||||||
|
return Base64.encode(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateRandomAESKey() : SecretKey {
|
||||||
|
val keyBytes = ByteArray(32)
|
||||||
|
random.nextBytes(keyBytes)
|
||||||
|
return SecretKeySpec(keyBytes, KEY_ALGORITHM)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateRandomBase64EncodedAesKey() : String {
|
||||||
|
return Base64.encode(generateRandomAESKey().encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decodeKey(key: String): SecretKey {
|
||||||
|
return SecretKeySpec(Base64.decode(key), KEY_ALGORITHM)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encryptWithMasterKey(plainText: String, key: SecretKey): String {
|
||||||
|
val cipher = Cipher.getInstance(CYPHER)
|
||||||
|
val iv = ByteArray(12) // 96-bit IV for GCM
|
||||||
|
random.nextBytes(iv)
|
||||||
|
val spec = GCMParameterSpec(128, iv)
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, key, spec)
|
||||||
|
val cipherText = cipher.doFinal(plainText.toByteArray(Charsets.UTF_8))
|
||||||
|
|
||||||
|
// store IV + ciphertext together, base64-encoded
|
||||||
|
val combined = iv + cipherText
|
||||||
|
return Base64.encode(combined)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decryptWithMasterKey(encrypted: String, key: SecretKey): String {
|
||||||
|
val combined = Base64.decode(encrypted)
|
||||||
|
val iv = combined.copyOfRange(0, 12)
|
||||||
|
val cipherText = combined.copyOfRange(12, combined.size)
|
||||||
|
val cipher = Cipher.getInstance(CYPHER)
|
||||||
|
val spec = GCMParameterSpec(128, iv)
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, key, spec)
|
||||||
|
val decrypted = cipher.doFinal(cipherText)
|
||||||
|
return String(decrypted, Charsets.UTF_8)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.core.crypto
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.core.ipc.dto.SecureCommand
|
||||||
|
import javax.crypto.Mac
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
import kotlin.io.encoding.Base64
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
object HmacProtector {
|
||||||
|
private const val ALGORITHM = "HmacSHA256"
|
||||||
|
|
||||||
|
fun generateSignature(key: String, timestamp: Long, payload: String?): String {
|
||||||
|
val mac = Mac.getInstance(ALGORITHM)
|
||||||
|
mac.init(SecretKeySpec(key.toByteArray(), ALGORITHM))
|
||||||
|
val dataToSign = "$timestamp${payload ?: ""}"
|
||||||
|
return Base64.encode(mac.doFinal(dataToSign.toByteArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verify(key: String, command: SecureCommand): Boolean {
|
||||||
|
val now = System.currentTimeMillis() / 1000
|
||||||
|
// 30 seconds window to prevent replay attacks
|
||||||
|
if (abs(now - command.timestamp) > 30) return false
|
||||||
|
|
||||||
|
val expected = generateSignature(key, command.timestamp, command.payload)
|
||||||
|
return expected == command.signature
|
||||||
|
}
|
||||||
|
}
|
||||||
+198
@@ -0,0 +1,198 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.core.helper
|
||||||
|
|
||||||
|
import co.touchlab.kermit.Logger
|
||||||
|
import com.github.michaelbull.retry.policy.binaryExponentialBackoff
|
||||||
|
import com.github.michaelbull.retry.policy.plus
|
||||||
|
import com.github.michaelbull.retry.policy.stopAtAttempts
|
||||||
|
import com.github.michaelbull.retry.retry
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.apache.commons.lang3.SystemUtils
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.nio.file.attribute.PosixFilePermissions
|
||||||
|
|
||||||
|
object PermissionsHelper {
|
||||||
|
|
||||||
|
val socketRetryPolicy = binaryExponentialBackoff<Throwable>(min = 10L, max = 250L) + stopAtAttempts(25)
|
||||||
|
|
||||||
|
// unix
|
||||||
|
const val WORLD_WRITABLE_OCTAL = "666"
|
||||||
|
const val WORLD_READWRITE_SYMBOLIC = "rw-rw-rw-"
|
||||||
|
const val OWNER_FULL_CONTROL_OCTAL = "755"
|
||||||
|
const val OWNER_FULL_CONTROL_SYMBOLIC = "rwxr-xr-x"
|
||||||
|
const val OWNER_ONLY_PRIVATE_FILE = "rw-------"
|
||||||
|
const val OWNER_ONLY_PRIVATE_DIR = "rwx------"
|
||||||
|
|
||||||
|
// windows universal SIDs
|
||||||
|
private const val SID_SYSTEM = "*S-1-5-18"
|
||||||
|
private const val SID_ADMINISTRATORS = "*S-1-5-32-544"
|
||||||
|
private const val SID_USERS = "*S-1-5-32-545"
|
||||||
|
private const val SID_CREATOR_OWNER = "*S-1-3-0"
|
||||||
|
|
||||||
|
// windows permission flags
|
||||||
|
private const val WIN_DIR_MODIFY_INHERIT = ":(OI)(CI)(M)"
|
||||||
|
private const val WIN_FULL_CONTROL_INHERIT = ":(OI)(CI)(F)"
|
||||||
|
|
||||||
|
fun setupDirectoryPermissionsUnix(runtimeDirPath: String) {
|
||||||
|
val path = Paths.get(runtimeDirPath)
|
||||||
|
|
||||||
|
if (Files.exists(path)) {
|
||||||
|
try {
|
||||||
|
Files.setPosixFilePermissions(path, PosixFilePermissions.fromString(OWNER_FULL_CONTROL_SYMBOLIC))
|
||||||
|
Logger.i { "Successfully set directory permissions to " }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.e { "POSIX native permissions failed: ${e.message} → falling back to chmod" }
|
||||||
|
try {
|
||||||
|
val exitCode = ProcessBuilder("chmod", OWNER_FULL_CONTROL_OCTAL, runtimeDirPath)
|
||||||
|
.start()
|
||||||
|
.waitFor()
|
||||||
|
|
||||||
|
if (exitCode == 0) {
|
||||||
|
Logger.i { "Successfully set directory permissions using chmod" }
|
||||||
|
} else {
|
||||||
|
Logger.e { "chmod failed with exit code $exitCode" }
|
||||||
|
}
|
||||||
|
} catch (chmodEx: Exception) {
|
||||||
|
Logger.e { "Failed to execute chmod: ${chmodEx.message}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger.w { "Runtime directory $runtimeDirPath not found" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setupDirectoryPermissionsWindows(runtimeDirPath: String) {
|
||||||
|
try {
|
||||||
|
val process = ProcessBuilder(
|
||||||
|
"icacls", runtimeDirPath,
|
||||||
|
"/grant", "$SID_USERS$WIN_DIR_MODIFY_INHERIT",
|
||||||
|
"/grant", "$SID_SYSTEM$WIN_FULL_CONTROL_INHERIT",
|
||||||
|
"/grant", "$SID_ADMINISTRATORS$WIN_FULL_CONTROL_INHERIT"
|
||||||
|
).start()
|
||||||
|
|
||||||
|
if (process.waitFor() != 0) {
|
||||||
|
val error = process.errorStream.bufferedReader().use { it.readText() }
|
||||||
|
Logger.e { "icacls directory setup failed: $error" }
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.e(e) { "Failed to set Windows directory ACLs" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setupSocketPermissionsWithPollUnix(socketPath: String) = withContext(Dispatchers.IO) {
|
||||||
|
val socketFile = File(socketPath)
|
||||||
|
|
||||||
|
runCatching {
|
||||||
|
retry(socketRetryPolicy) {
|
||||||
|
if (!socketFile.exists()) {
|
||||||
|
throw FileNotFoundException("Socket $socketPath not found yet")
|
||||||
|
}
|
||||||
|
setupSocketPermissionsUnix(socketPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
val socketPerms = Files.getPosixFilePermissions(Paths.get(socketPath))
|
||||||
|
Logger.i { "Final socket permissions: $socketPerms" }
|
||||||
|
|
||||||
|
}.onFailure {
|
||||||
|
Logger.e { "Socket $socketPath failed to appear. Daemon likely failed to start: ${it.message}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setupSocketPermissionsWithPollWindows(socketPath: String) = withContext(Dispatchers.IO) {
|
||||||
|
val socketFile = File(socketPath)
|
||||||
|
runCatching {
|
||||||
|
retry(socketRetryPolicy) {
|
||||||
|
if (!socketFile.exists()) throw FileNotFoundException("Socket not found yet")
|
||||||
|
setupDirectoryPermissionsWindows(socketPath)
|
||||||
|
}
|
||||||
|
logWindowsACLs(socketPath)
|
||||||
|
}.onFailure {
|
||||||
|
Logger.e { "Socket $socketPath failed to appear on Windows: ${it.message}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fun setupSocketPermissionsUnix(socketPath: String) {
|
||||||
|
val path = Paths.get(socketPath)
|
||||||
|
try {
|
||||||
|
Files.setPosixFilePermissions(path, PosixFilePermissions.fromString(WORLD_READWRITE_SYMBOLIC))
|
||||||
|
Logger.i { "Successfully set socket permissions to 0666" }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.e { "POSIX native permissions failed: ${e.message} → falling back to chmod" }
|
||||||
|
|
||||||
|
try {
|
||||||
|
val exitCode = ProcessBuilder("chmod", WORLD_WRITABLE_OCTAL, socketPath)
|
||||||
|
.start()
|
||||||
|
.waitFor()
|
||||||
|
|
||||||
|
if (exitCode == 0) {
|
||||||
|
Logger.i { "Successfully set socket permissions using chmod" }
|
||||||
|
} else {
|
||||||
|
Logger.e { "chmod failed with exit code $exitCode" }
|
||||||
|
throw IllegalStateException("chmod exited with non-zero status")
|
||||||
|
}
|
||||||
|
} catch (chmodEx: Exception) {
|
||||||
|
Logger.e { "All POSIX methods failed: ${chmodEx.message} → using JVM fallback" }
|
||||||
|
|
||||||
|
// try file API
|
||||||
|
val socketFile = path.toFile()
|
||||||
|
val readOk = socketFile.setReadable(true, false)
|
||||||
|
val writeOk = socketFile.setWritable(true, false)
|
||||||
|
|
||||||
|
if (readOk && writeOk) {
|
||||||
|
Logger.w { "Applied weak Java fallback permissions (readable/writable for all)" }
|
||||||
|
} else {
|
||||||
|
Logger.e { "Failed to set any permissions on socket $socketPath" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOwnerOnly(path: Path) {
|
||||||
|
try {
|
||||||
|
if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
|
applyWindowsOwnerOnlyPermissions(path)
|
||||||
|
} else {
|
||||||
|
applyPosixOwnerOnlyPermissions(path)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.e(e) { "Failed to set permissions for: $path" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyPosixOwnerOnlyPermissions(path: Path) {
|
||||||
|
val isDir = Files.isDirectory(path)
|
||||||
|
val permsString = if (isDir) OWNER_ONLY_PRIVATE_DIR else OWNER_ONLY_PRIVATE_FILE
|
||||||
|
Files.setPosixFilePermissions(path, PosixFilePermissions.fromString(permsString))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyWindowsOwnerOnlyPermissions(path: Path) {
|
||||||
|
try {
|
||||||
|
val process = ProcessBuilder(
|
||||||
|
"icacls", path.toString(),
|
||||||
|
"/inheritance:r", // remove inherited perms
|
||||||
|
"/grant:r", "$SID_SYSTEM$WIN_FULL_CONTROL_INHERIT",
|
||||||
|
"/grant:r", "$SID_CREATOR_OWNER$WIN_FULL_CONTROL_INHERIT",
|
||||||
|
"/grant:r", "$SID_ADMINISTRATORS$WIN_FULL_CONTROL_INHERIT"
|
||||||
|
).start()
|
||||||
|
|
||||||
|
if (process.waitFor() != 0) {
|
||||||
|
Logger.e { "icacls owner-only failed for $path" }
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.e(e) { "Error applying owner-only Windows perms" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun logWindowsACLs(path: String) {
|
||||||
|
runCatching {
|
||||||
|
val output = ProcessBuilder("icacls", path).start().inputStream.bufferedReader().readText()
|
||||||
|
Logger.i { "Final ACLs for $path: $output" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.core.ipc
|
||||||
|
|
||||||
|
import co.touchlab.kermit.Logger
|
||||||
|
import com.zaneschepke.wireguardautotunnel.core.crypto.Crypto
|
||||||
|
import com.zaneschepke.wireguardautotunnel.core.helper.PermissionsHelper
|
||||||
|
import org.apache.commons.lang3.SystemUtils
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
object IPC {
|
||||||
|
const val KEY_FILE = "ipc.key"
|
||||||
|
const val USER_FOLDER = ".wgtunnel"
|
||||||
|
const val SOCKET_FILE_NAME = "daemon.sock"
|
||||||
|
|
||||||
|
fun resolveKeyForUser(user: String): String? {
|
||||||
|
if (!user.matches(Regex("^[a-zA-Z0-9._-]+$"))) {
|
||||||
|
Logger.w { "Invalid username format: $user" }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val userHome = getUserHome(user)
|
||||||
|
val keyPath = Paths.get(userHome, USER_FOLDER, KEY_FILE)
|
||||||
|
|
||||||
|
if (Files.exists(keyPath)) {
|
||||||
|
keyPath.toFile().readText().trim().takeIf { it.isNotBlank() }
|
||||||
|
} else {
|
||||||
|
Logger.w { "IPC key not found for user: $user → $keyPath" }
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.Companion.e(e) { "Failed to resolve IPC key for user: $user" }
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// should be called by client ONLY
|
||||||
|
fun getIPCSecret() : String {
|
||||||
|
val ipcFile = File(System.getProperty("user.home"), "${IPC.USER_FOLDER}/${IPC.KEY_FILE}")
|
||||||
|
if (!ipcFile.parentFile.exists()) ipcFile.parentFile.mkdirs()
|
||||||
|
|
||||||
|
return if (!ipcFile.exists()) {
|
||||||
|
val secret = Crypto.generateRandomBase64(32)
|
||||||
|
ipcFile.writeText(secret)
|
||||||
|
// Set 600 permissions immediately
|
||||||
|
PermissionsHelper.setOwnerOnly(ipcFile.toPath())
|
||||||
|
secret
|
||||||
|
} else {
|
||||||
|
ipcFile.readText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getUserHome(user: String): String {
|
||||||
|
return when {
|
||||||
|
SystemUtils.IS_OS_WINDOWS -> "C:\\Users\\$user"
|
||||||
|
SystemUtils.IS_OS_MAC_OSX -> "/Users/$user"
|
||||||
|
else -> "/home/$user"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDaemonSocketPath(): String {
|
||||||
|
return when {
|
||||||
|
SystemUtils.IS_OS_WINDOWS -> {
|
||||||
|
val baseDir = System.getenv("PROGRAMDATA") + "\\wgtunnel"
|
||||||
|
"$baseDir\\$SOCKET_FILE_NAME"
|
||||||
|
}
|
||||||
|
SystemUtils.IS_OS_MAC_OSX -> {
|
||||||
|
"/tmp/wgtunnel/$SOCKET_FILE_NAME"
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
"/run/wgtunnel/$SOCKET_FILE_NAME"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.core.ipc.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class BackendMode {
|
||||||
|
KERNEL, USERSPACE, PROXY
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.core.ipc.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class BackendStatus(
|
||||||
|
val killSwitchEnabled: Boolean,
|
||||||
|
val mode: BackendMode,
|
||||||
|
val activeTunnels: List<TunnelStatus>
|
||||||
|
)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.core.ipc.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SecureCommand(
|
||||||
|
val timestamp: Long,
|
||||||
|
val signature: String,
|
||||||
|
val userHint: String,
|
||||||
|
val payload: String? = null
|
||||||
|
)
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.core.ipc.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StartTunnelRequest(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
|
val quickConfig: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.core.ipc.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class TunnelState {
|
||||||
|
DOWN, STARTING, HEALTHY, HANDSHAKE_FAILURE, RESOLVING_DNS, UNKNOWN
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.core.ipc.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TunnelStatus(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
|
val state: TunnelState
|
||||||
|
)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user