Repositories / gitweb2.git

gitweb2.git

Clone (read-only): git clone http://git.guha-anderson.com/git/gitweb2.git

Branch

Fork server process; default kotlin-git path to external disk

Run the HTTP server in a forked child so the parent can wait and
forward signals. Document and default kotlinGitDir to the path
under /media/external0.

Made-with: Cursor
Author
Arjun Guha <a.guha@northeastern.edu>
Date
2026-04-19 17:20:46 -0400
Commit
eec261fcf2c8d14e9afd7983471542fa4b227f84
README.md
index adc2924..2d8ea96 100644
--- a/README.md
+++ b/README.md
@@ -3,14 +3,14 @@
 A Kotlin/Native, read-only web viewer for local Git repositories.
 
 The app uses Ktor Native with the CIO engine for HTTP and the local
-`/home/arjun/repos/homebox/kotlin-git` library for repository reads.
+`/media/external0/arjun/repos/homebox/kotlin-git` library for repository reads.
 
 ## Requirements
 
 - Kotlin/Native through the Gradle wrapper
 - system `libgit2` visible through `pkg-config`
 - the local `kotlin-git` klibs built at
-  `/home/arjun/repos/homebox/kotlin-git/.build/klib`
+  `/media/external0/arjun/repos/homebox/kotlin-git/.build/klib`
 - the included Gradle wrapper
 
 ## Build
build.gradle.kts
index 53e36fb..ef42c45 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -9,7 +9,7 @@ val ktorVersion = "3.4.2"
 val okioVersion = "3.17.0"
 val processVersion = "0.5.0"
 val kotlinGitDir = providers.gradleProperty("kotlinGitDir")
-    .orElse("/home/arjun/repos/homebox/kotlin-git")
+    .orElse("/media/external0/arjun/repos/homebox/kotlin-git")
 val kotlinGitKlib = kotlinGitDir.map { file("$it/.build/klib/kotlin-git.klib") }
 val libgit2Klib = kotlinGitDir.map { file("$it/.build/klib/libgit2.klib") }
 
src/main/kotlin/Main.kt
index b9a4893..40801bd 100644
--- a/src/main/kotlin/Main.kt
+++ b/src/main/kotlin/Main.kt
@@ -1,3 +1,5 @@
+@file:OptIn(ExperimentalForeignApi::class)
+
 package gitweb
 
 import io.ktor.http.ContentType
@@ -13,6 +15,13 @@ import io.ktor.server.routing.get
 import io.ktor.server.routing.routing
 import io.ktor.utils.io.charsets.Charsets
 import io.matthewnelson.kmp.process.Process as KmpProcess
+import kotlinx.cinterop.ExperimentalForeignApi
+import kotlinx.cinterop.IntVar
+import kotlinx.cinterop.alloc
+import kotlinx.cinterop.memScoped
+import kotlinx.cinterop.ptr
+import kotlinx.cinterop.staticCFunction
+import kotlinx.cinterop.value
 import kotlinx.git.Blob
 import kotlinx.git.Branch
 import kotlinx.git.BranchType
@@ -22,6 +31,14 @@ import kotlinx.git.Repository
 import kotlinx.git.TreeEntryKind
 import okio.FileSystem
 import okio.Path.Companion.toPath
+import platform.posix.SIGINT
+import platform.posix.SIGKILL
+import platform.posix.SIGTERM
+import platform.posix._exit
+import platform.posix.fork
+import platform.posix.kill
+import platform.posix.signal
+import platform.posix.waitpid
 import kotlin.system.exitProcess
 
 private const val DefaultHost = "127.0.0.1"
@@ -31,7 +48,7 @@ private const val DefaultPygmentsCommand = "pygmentize"
 
 fun runGitWeb2(args: Array<String>) {
     val config = parseArgs(args) ?: return
-    GitWebServer(config.root, config.host, config.port, config.pygmentsCommand).start()
+    runServerProcess(config)
 }
 
 private data class Config(val root: String, val host: String, val port: Int, val pygmentsCommand: String)
@@ -355,6 +372,34 @@ private fun readCommandOutput(command: String, input: String? = null): String? {
     return output.stdout.replace("\r\n", "\n").replace('\r', '\n')
 }
 
+private var childPidForSignal: Int = 0
+
+private val parentSignalHandler = staticCFunction<Int, Unit> { signal ->
+    val childPid = childPidForSignal
+    if (childPid > 0) {
+        kill(childPid, SIGKILL)
+    }
+    _exit(128 + signal)
+}
+
+private fun runServerProcess(config: Config) {
+    val childPid = fork()
+    when {
+        childPid < 0 -> GitWebServer(config.root, config.host, config.port, config.pygmentsCommand).start()
+        childPid == 0 -> GitWebServer(config.root, config.host, config.port, config.pygmentsCommand).start()
+        else -> waitForServerProcess(childPid)
+    }
+}
+
+private fun waitForServerProcess(childPid: Int): Nothing = memScoped {
+    childPidForSignal = childPid
+    signal(SIGINT, parentSignalHandler)
+    signal(SIGTERM, parentSignalHandler)
+    val status = alloc<IntVar>()
+    waitpid(childPid, status.ptr, 0)
+    exitProcess(0)
+}
+
 private fun discoverRepos(root: String): List<RepoInfo> {
     val fileSystem = FileSystem.SYSTEM
     val found = mutableListOf<RepoInfo>()