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