Skip to content

Commit

Permalink
chore: add proper cli
Browse files Browse the repository at this point in the history
  • Loading branch information
auguwu committed Apr 29, 2022
1 parent 6ca61f7 commit 14af502
Show file tree
Hide file tree
Showing 10 changed files with 325 additions and 5 deletions.
5 changes: 4 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ plugins {
apply(plugin = "kotlinx-atomicfu")

val JAVA_VERSION = JavaVersion.VERSION_17
val VERSION = Version(1, 0, 0, 0, ReleaseType.Beta)
val VERSION = Version(1, 0, 0, 0, ReleaseType.None)
val COMMIT_HASH by lazy {
val cmd = "git rev-parse --short HEAD".split("\\s".toRegex())
val proc = ProcessBuilder(cmd)
Expand Down Expand Up @@ -164,6 +164,9 @@ dependencies {
// Clikt (for CLI)
implementation("com.github.ajalt.clikt:clikt:3.4.2")

// Argon2
implementation("de.mkammerer:argon2-jvm:2.11")

// Testing utilities
testImplementation("io.kotest:kotest-runner-junit5")
testImplementation("io.kotest:kotest-assertions-core")
Expand Down
4 changes: 3 additions & 1 deletion src/main/kotlin/dev/floofy/hazel/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@

package dev.floofy.hazel

import dev.floofy.hazel.cli.HazelCli

object Main {
@JvmStatic
fun main(args: Array<String>) {
Bootstrap.bootstrap(null)
HazelCli.main(args)
}
}
47 changes: 47 additions & 0 deletions src/main/kotlin/dev/floofy/hazel/cli/HazelCli.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,50 @@
*/

package dev.floofy.hazel.cli

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.ProgramResult
import com.github.ajalt.clikt.core.subcommands
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.versionOption
import dev.floofy.hazel.Bootstrap
import dev.floofy.hazel.HazelInfo
import dev.floofy.hazel.cli.commands.GenerateCommand
import dev.floofy.hazel.cli.commands.PingCommand
import dev.floofy.hazel.cli.commands.keystore.KeystoreCommand

object HazelCli: CliktCommand(
name = "hazel",
invokeWithoutSubcommand = true,
allowMultipleSubcommands = true,
help = """
|Hazel is a simple, reliable Content Delivery Network (CDN) microservice to handle
|your needs without doing anything to your data.
|
|This command line is the command line utility to manage the keystore for user persistence,
|secret key storage, and managing the Hazel server.
""".trimMargin()
) {
private val configPath: String? by option(
"-c", "--config",
help = "The configuration path to run the server.",
envvar = "HAZEL_CONFIG_PATH"
)

init {
versionOption("v${HazelInfo.version} (${HazelInfo.commitHash} - ${HazelInfo.buildDate})", names = setOf("--version", "-v"))
subcommands(
PingCommand,
GenerateCommand,
KeystoreCommand
)
}

override fun run() {
// If we haven't invoked a subcommand with `hazel ...`, the server will run.
if (currentContext.invokedSubcommand == null) {
Bootstrap.bootstrap(configPath)
throw ProgramResult(0)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,45 @@
*/

package dev.floofy.hazel.cli.abstractions

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.required
import dev.floofy.hazel.core.KeystoreWrapper
import dev.floofy.hazel.data.KeystoreConfig

abstract class BaseKeystoreCommand(
name: String? = null,
help: String = ""
): CliktCommand(name = name, help = help) {
private val keystorePath: String by option(
"--path", "--ks-path", "--keystore-path", "-p",
help = "The keystore path to consume."
).required()

private val password: String? by option(
"--password", "--ks-pwd", "--pwd",
help = "The keystore password to use to unlock it."
)

fun wrapper(): KeystoreWrapper {
val cwd = System.getProperty("user.dir", "")
val home = System.getProperty("user.home", "/")

val path = when {
keystorePath.startsWith("~/") -> home + keystorePath.substring(1)
keystorePath.startsWith("./") -> cwd + keystorePath.substring(1)
else -> keystorePath
}

val wrapper = KeystoreWrapper(
KeystoreConfig(
path,
password
)
)

wrapper.initUnsafe()
return wrapper
}
}
43 changes: 43 additions & 0 deletions src/main/kotlin/dev/floofy/hazel/cli/commands/GenerateCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,46 @@
*/

package dev.floofy.hazel.cli.commands

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.file
import java.nio.file.Files
import java.nio.file.Paths

object GenerateCommand: CliktCommand(
name = "generate",
help = """
|The "generate" subcommand generates a configuration file that can be executed to run the server.
|
|The `dest` argument is the source to put the file in. The file cannot be a directory since Hazel
|loads it and runs it.
""".trimMargin()
) {
private val dest by argument(
"dest",
help = "The destination of the file to output in"
).file(mustExist = false, canBeFile = true, canBeDir = false)

override fun run() {
if (dest.exists()) {
println("[hazel:generate] File ${dest.path} already exists.")
return
}

println("[hazel:generate] Writing output to ${dest.path}...")
val contents = """
|[keystore]
|file = "./data/keystore.jks"
|
|[storage]
|class = "fs"
|
|[storage.fs]
|directory = "./data/hazel"
""".trimMargin()

Files.write(Paths.get(dest.toURI()), contents.toByteArray())
println("[hazel:generate] Generated a default configuration file in ${dest.path}!")
}
}
39 changes: 39 additions & 0 deletions src/main/kotlin/dev/floofy/hazel/cli/commands/PingCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,42 @@
*/

package dev.floofy.hazel.cli.commands

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.CliktError
import com.github.ajalt.clikt.parameters.arguments.argument
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.runBlocking

object PingCommand: CliktCommand(
name = "ping",
help = "Pings the server with the [URL] provided."
) {
private val url: String by argument(
"url",
help = "The URL of the server to hit"
)

override fun run() {
val client = HttpClient(OkHttp)
val res = runBlocking {
client.get("$url/heartbeat")
}

if (!res.status.isSuccess()) {
throw CliktError("Couldn't reach server at $url, is it open?")
}

val body = runBlocking {
res.bodyAsText()
}

if (body != "OK") {
throw CliktError("Server running at $url is not a Hazel server! (GET /heartbeat = OK)")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 🪶 hazel: Minimal, simple, and open source content delivery network made in Kotlin
* Copyright 2022 Noel <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dev.floofy.hazel.cli.commands.keystore

import com.github.ajalt.clikt.core.CliktCommand

object AddUserCommand: CliktCommand(
name = "add-user",
help = "Adds a user to the keystore."
) {
override fun run() {
println("soon:tm:")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 🪶 hazel: Minimal, simple, and open source content delivery network made in Kotlin
* Copyright 2022 Noel <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dev.floofy.hazel.cli.commands.keystore

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.PrintHelpMessage
import com.github.ajalt.clikt.core.subcommands

object KeystoreCommand: CliktCommand(
name = "keystore",
invokeWithoutSubcommand = true,
help = """
|The `keystore` command is the main management utility for managing Hazel's keystore.
|
|Hazel uses a Java keystore to handle secret key management, SSL certificates for the server,
|and user persistence without bringing in an external database.
|
|To generate a keystore, you can run `hazel keystore generate` to create the keystore in the path
|and you can load it using the `keystore.path` configuration key.
""".trimMargin()
) {
init {
subcommands(ListKeysCommand)
}

override fun run() {
if (currentContext.invokedSubcommand == null)
throw PrintHelpMessage(this, false)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 🪶 hazel: Minimal, simple, and open source content delivery network made in Kotlin
* Copyright 2022 Noel <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dev.floofy.hazel.cli.commands.keystore

import dev.floofy.hazel.cli.abstractions.BaseKeystoreCommand

object ListKeysCommand: BaseKeystoreCommand(
"list",
"List all the keys in the keystore."
) {
override fun run() {
val keystore = wrapper()
val keys = keystore.keys()

for (key in keys) {
val isUser = key.startsWith("users.")
if (isUser) {
val name = key.split(".").last()
println("===> User $name | Use `hazel keystore update-pwd $name` to update their password,")
println(" | or use `hazel keystore delete users.$name` to delete their account")
println(" | in the keystore.")
} else {
println("===> Key $key")
}
}
}
}
Loading

0 comments on commit 14af502

Please sign in to comment.