Compare commits

..

3 Commits

Author SHA1 Message Date
mae
d7193b6a5f update to java 24 2025-08-26 23:18:42 -05:00
f369e5f6c5
fix gitignore 2025-08-12 18:47:47 -05:00
d3da1c57f8
additional tests 2025-08-12 18:42:01 -05:00
9 changed files with 250 additions and 153 deletions

8
.gitignore vendored
View File

@ -1,14 +1,12 @@
.gradle
gradlew.bat
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
/.idea/
*.iws
*.iml
*.ipr
@ -43,5 +41,3 @@ bin/
### Mac OS ###
.DS_Store
/app/src/test/resources/debian-images/
/app/src/test/resources/squashfs-root/

View File

@ -1,8 +0,0 @@
<component name="ArtifactManager">
<artifact type="jar" name="api-jvm">
<output-path>$PROJECT_DIR$/api/build/libs</output-path>
<root id="archive" name="api-jvm.jar">
<element id="module-output" name="planterette.api.jvmMain" />
</root>
</artifact>
</component>

12
.idea/kotlinc.xml generated
View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Kotlin2JsCompilerArguments">
<option name="moduleKind" value="plain" />
</component>
<component name="Kotlin2JvmCompilerArguments">
<option name="jvmTarget" value="1.8" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.2.0" />
</component>
</project>

View File

@ -3,7 +3,7 @@ plugins {
}
kotlin {
jvmToolchain(21)
jvmToolchain(24)
}
dependencies {

View File

@ -0,0 +1,6 @@
#Tue Aug 26 23:15:20 CDT 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -1,47 +1,48 @@
package app.hakurei.planterette.api
@Suppress("unused")
object Paths {
const val FHSRoot = "/"
const val FHSEtc = "/etc/"
const val FHSTmp = "/tmp/"
const val FHS_ROOT = "/"
const val FHS_ETC = "/etc/"
const val FHS_TMP = "/tmp/"
const val FHSRun = "/run/"
const val FHSRunUser = FHSRun + "user/"
const val FHS_RUN = "/run/"
const val FHS_RUN_USER = FHS_RUN + "user/"
const val FHSUsr = "/usr/"
const val FHSUsrBin = FHSUsr + "bin/"
const val FHS_USR = "/usr/"
const val FHS_USR_BIN = FHS_USR + "bin/"
const val FHSVar = "/var/"
const val FHSVarLib = FHSVar + "lib/"
const val FHSVarEmpty = FHSVar + "empty/"
const val FHS_VAR = "/var/"
const val FHS_VAR_LIB = FHS_VAR + "lib/"
const val FHS_VAR_EMPTY = FHS_VAR + "empty/"
const val FHSDev = "/dev/"
const val FHSProc = "/proc/"
const val FHSProcSys = FHSProc + "sys/"
const val FHSSys = "/sys/"
const val FHS_DEV = "/dev/"
const val FHS_PROC = "/proc/"
const val FHS_PROC_SYS = FHS_PROC + "sys/"
const val FHS_SYS = "/sys/"
const val Nonexistent = FHSProc + "nonexistent"
const val sysrootDir = "sysroot"
const val hostDir = "host"
const val hostPath = FHSRoot + hostDir
const val sysrootPath = FHSRoot + sysrootDir
const val NONEXISTENT = FHS_PROC + "nonexistent"
const val SYSROOT_DIR = "sysroot"
const val HOST_DIR = "host"
const val HOST_PATH = FHS_ROOT + HOST_DIR
const val SYSROOT_PATH = FHS_ROOT + SYSROOT_DIR
val AbsFHSRoot = AbsolutePath(FHSRoot)
val AbsFHSEtc = AbsolutePath(FHSEtc)
val AbsFHSTmp = AbsolutePath(FHSTmp)
val AbsFHSRoot = AbsolutePath(FHS_ROOT)
val AbsFHSEtc = AbsolutePath(FHS_ETC)
val AbsFHSTmp = AbsolutePath(FHS_TMP)
val AbsFHSRun = AbsolutePath(FHSRun)
val AbsFHSRunUser = AbsolutePath(FHSRunUser)
val AbsFHSRun = AbsolutePath(FHS_RUN)
val AbsFHSRunUser = AbsolutePath(FHS_RUN_USER)
val AbsFHSUsrBin = AbsolutePath(FHSUsrBin)
val AbsFHSUsrBin = AbsolutePath(FHS_USR_BIN)
val AbsFHSVar = AbsolutePath(FHSVar)
val AbsFHSVarLib = AbsolutePath(FHSVarLib)
val AbsFHSVar = AbsolutePath(FHS_VAR)
val AbsFHSVarLib = AbsolutePath(FHS_VAR_LIB)
val AbsFHSDev = AbsolutePath(FHSDev)
val AbsFHSProc = AbsolutePath(FHSProc)
val AbsFHSSys = AbsolutePath(FHSSys)
val AbsFHSDev = AbsolutePath(FHS_DEV)
val AbsFHSProc = AbsolutePath(FHS_PROC)
val AbsFHSSys = AbsolutePath(FHS_SYS)
val AbsNonexistent = AbsolutePath(Nonexistent)
val AbsNonexistent = AbsolutePath(NONEXISTENT)
}

View File

@ -1,20 +1,5 @@
package app.hakurei.planterette.api
import kotlinx.io.Buffer
import kotlinx.io.buffered
import kotlinx.io.files.Path
import kotlinx.io.files.SystemFileSystem
import kotlinx.io.readString
object Util {
fun readFileToString(path: String): String {
val buffer = Buffer()
val source = SystemFileSystem.source(Path(path))
source.buffered().transferTo(buffer)
return buffer.readString()
}
}
inline fun <T> Array<out T>.orOf(selector: (T) -> Int): Int {
var sum = 0
for (element in this) {

View File

@ -0,0 +1,26 @@
package app.hakurei.planterette.api
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertIs
class AbsolutePathTest {
@Test
fun testAbsolutePath() {
val path = AbsolutePath("/dev/null")
assertIs<AbsolutePath>(path)
}
@Test
fun testRelativePath() {
assertFailsWith(AbsolutePathException::class) {
AbsolutePath("./test")
}
}
@Test
fun testPathJoin() {
val want = AbsolutePath("/dev/null")
val base = AbsolutePath("/dev/")
assertEquals(want, base + "null")
}
}

View File

@ -1,11 +1,13 @@
package app.hakurei.planterette.api
import app.hakurei.planterette.api.HakureiConfig.Enablement.*
import app.hakurei.planterette.api.HakureiConfig.RootFlag
import app.hakurei.planterette.api.HakureiConfig.SeccompFilterPreset.*
import app.hakurei.planterette.api.HakureiConfig.HakureiExportFlag.*
import app.hakurei.planterette.api.HakureiConfig.RootFlag.*
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.serializer
import kotlin.test.Test
import kotlin.test.assertEquals
@ -146,85 +148,186 @@ class HakureiConfigTest {
}
}
""".trimIndent()
val testConfig = HakureiConfig(
id = "org.chromium.Chromium",
path = Paths.AbsFHSRun + "current-system/sw/bin/chromium",
args = listOf(
"chromium",
"--ignore-gpu-blocklist",
"--disable-smooth-scrolling",
"--enable-features=UseOzonePlatform",
"--ozone-platform=wayland",
),
enablements = HakureiConfig.Enablement.enablements(Wayland, DBus, Pulse),
sessionBus = HakureiConfig.DbusConfig(
talk = listOf(
"org.freedesktop.Notifications",
"org.freedesktop.FileManager1",
"org.freedesktop.ScreenSaver",
"org.freedesktop.secrets",
"org.kde.kwalletd5",
"org.kde.kwalletd6",
"org.gnome.SessionManager"
),
own = listOf(
"org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.chromium.*"
),
call = mapOf("org.freedesktop.portal.*" to "*"),
broadcast = mapOf("org.freedesktop.portal.*" to "@/org/freedesktop/portal/*"),
log = false,
filter = true,
),
systemBus = HakureiConfig.DbusConfig(
talk = listOf("org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"),
log = false,
filter = true,
),
directWayland = false,
username = "chronos",
shell = Paths.AbsFHSRun + "current-system/sw/bin/zsh",
data = Paths.AbsFHSVarLib + "hakurei/u0/org.chromium.Chromium",
dir = AbsolutePath("/data/data/org.chromium.Chromium"),
extraPerms = listOf(
HakureiConfig.ExtraPermConfig(path = Paths.AbsFHSVarLib + "hakurei/u0", ensure = true, execute = true),
HakureiConfig.ExtraPermConfig(path = Paths.AbsFHSVarLib + "hakurei/u0/org.chromium.Chromium", read = true, write = true, execute = true)
),
identity = 9,
groups = listOf("video", "dialout", "plugdev"),
container = HakureiConfig.ContainerConfig(
hostname = "localhost",
devel = true,
userns = true,
net = true,
device = true,
waitDelay = -1,
seccompFlags = HakureiConfig.HakureiExportFlag.exportFlags(Multiarch),
seccompPresets = HakureiConfig.SeccompFilterPreset.filterPresets(Ext),
seccompCompat = true,
tty = true,
multiarch = true,
mapRealUid = true,
env = mapOf(
"GOOGLE_API_KEY" to "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
"GOOGLE_DEFAULT_CLIENT_ID" to "77185425430.apps.googleusercontent.com",
"GOOGLE_DEFAULT_CLIENT_SECRET" to "OTJgUOQcT7lO7GsGZq2G4IlT"
),
filesystem = listOf(
HakureiConfig.FilesystemConfig(dst = Paths.AbsFHSTmp, src = Paths.AbsNonexistent, write = true),
HakureiConfig.FilesystemConfig(src = AbsolutePath("/nix/store")),
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSRun + "current-system"),
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSRun + "opengl-driver"),
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSVar + "db/nix-channels"),
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSVarLib + "hakurei/u0/org.chromium.Chromium", dst = AbsolutePath("/data/data/org.chromium.Chromium"), write = true, must = true),
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSDev + "dri", device = true)
),
link = listOf(HakureiConfig.LinkConfig(Paths.AbsFHSRunUser + "65534", Paths.FHS_RUN_USER + "150")),
autoRoot = Paths.AbsFHSVarLib + "hakurei/base/org.debian",
rootFlags = HakureiConfig.RootFlag.rootFlags(Writable),
etc = Paths.AbsFHSEtc,
autoEtc = true
)
)
internal fun compare(want: HakureiConfig, test: HakureiConfig) {
assertEquals(want.id, test.id, "id")
assertEquals(want.path, test.path, "path")
assertEquals(want.args, test.args, "args")
assertEquals(want.enablements, test.enablements, "enablements")
assertEquals(want.sessionBus?.see, test.sessionBus?.see, "sessionBus.see")
assertEquals(want.sessionBus?.talk, test.sessionBus?.talk, "sessionBus.talk")
assertEquals(want.sessionBus?.own, test.sessionBus?.own, "sessionBus.own")
assertEquals(want.sessionBus?.broadcast, test.sessionBus?.broadcast, "sessionBus.broadcast")
assertEquals(want.sessionBus?.log ?: false, test.sessionBus?.log, "sessionBus.log")
assertEquals(want.sessionBus?.filter, test.sessionBus?.filter, "sessionBus.filter")
assertEquals(want.systemBus?.see, test.systemBus?.see, "systemBus.see")
assertEquals(want.systemBus?.talk, test.systemBus?.talk, "systemBus.talk")
assertEquals(want.systemBus?.own, test.systemBus?.own, "systemBus.own")
assertEquals(want.systemBus?.broadcast, test.systemBus?.broadcast, "systemBus.broadcast")
assertEquals(want.systemBus?.log ?: false, test.systemBus?.log, "systemBus.log")
assertEquals(want.systemBus?.filter, test.systemBus?.filter, "systemBus.filter")
assertEquals(want.directWayland ?: false, test.directWayland, "directWayland")
assertEquals(want.username, test.username, "username")
assertEquals(want.shell, test.shell, "shell")
assertEquals(want.data, test.data, "data")
assertEquals(want.dir, test.dir, "dir")
assertEquals(want.extraPerms, test.extraPerms, "extraPerms")
assertEquals(want.identity, test.identity, "identity")
assertEquals(want.groups, test.groups, "groups")
assertEquals(want.container.waitDelay, test.container.waitDelay, "waitDelay")
assertEquals(want.container.seccompFlags, test.container.seccompFlags, "seccompFlags")
assertEquals(want.container.seccompPresets, test.container.seccompPresets, "seccompPresets")
assertEquals(want.container.seccompCompat, test.container.seccompCompat, "seccompCompat")
assertEquals(want.container.devel, test.container.devel, "devel")
assertEquals(want.container.userns, test.container.userns, "userns")
assertEquals(want.container.net, test.container.net, "net")
assertEquals(want.container.tty, test.container.tty, "tty")
assertEquals(want.container.multiarch, test.container.multiarch, "multiarch")
assertEquals(want.container.env, test.container.env, "env")
assertEquals(want.container.mapRealUid, test.container.mapRealUid, "mapRealUid")
assertEquals(want.container.device, test.container.device, "device")
assertEquals(want.container.filesystem.size, test.container.filesystem.size, "filesystem.size")
for(i in 0 until want.container.filesystem.size) {
val w = want.container.filesystem[i]
val t = test.container.filesystem[i]
assertEquals(w.src, t.src, "filesystem[$i].src")
assertEquals(w.dst, t.dst, "filesystem[$i].dst")
assertEquals(w.write, t.write, "filesystem[$i].write")
assertEquals(w.device, t.device, "filesystem[$i].device")
assertEquals(w.must, t.must, "filesystem[$i].must")
}
assertEquals(want.container.link, test.container.link, "link")
assertEquals(want.container.autoRoot, test.container.autoRoot, "autoroot")
assertEquals(want.container.etc, test.container.etc, "etc")
assertEquals(want.container.autoEtc, test.container.autoEtc, "autoEtc")
}
@Test
fun testHakureiConfigSerialization() {
val testConfig = HakureiConfig(
id = "org.chromium.Chromium",
path = Paths.AbsFHSRun + "current-system/sw/bin/chromium",
args = listOf(
"chromium",
"--ignore-gpu-blocklist",
"--disable-smooth-scrolling",
"--enable-features=UseOzonePlatform",
"--ozone-platform=wayland",
),
enablements = HakureiConfig.Enablement.enablements(Wayland, DBus, Pulse),
sessionBus = HakureiConfig.DbusConfig(
see = null,
talk = listOf("org.freedesktop.Notifications", "org.freedesktop.FileManager1", "org.freedesktop.ScreenSaver"),
own = listOf("org.chromium.Chromium.*", "org.mpris.MediaPlayer2.org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.chromium.*"),
call = mapOf("org.freedesktop.portal.*" to "*"),
broadcast = mapOf("org.freedesktop.portal.*" to "@/org/freedesktop/portal/*"),
log = false,
filter = true,
),
systemBus = HakureiConfig.DbusConfig(
see = null,
talk = listOf("org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"),
own = null,
call = null,
broadcast = null,
log = false,
filter = true,
),
directWayland = false,
val json = format.encodeToString(testConfig)
val test = format.decodeFromString<HakureiConfig>(json)
assertEquals(testConfig, test)
val want = format.decodeFromString<HakureiConfig>(want)
username = "chronos",
shell = Paths.AbsFHSRun + "current-system/sw/bin/zsh",
data = Paths.AbsFHSVarLib + "hakurei/u0/org.chromium.Chromium",
dir = AbsolutePath("/data/data/org.chromium.Chromium"),
extraPerms = listOf(
HakureiConfig.ExtraPermConfig(path = Paths.AbsFHSVarLib + "hakurei/u0", ensure = true, execute = true),
HakureiConfig.ExtraPermConfig(path = Paths.AbsFHSVarLib + "hakurei/u0/org.chromium.Chromium", read = true, write = true, execute = true)
),
identity = 9,
groups = listOf("video", "dialout", "plugdev"),
container = HakureiConfig.ContainerConfig(
hostname = "localhost",
devel = true,
userns = true,
net = true,
device = true,
waitDelay = -1,
seccompFlags = HakureiConfig.HakureiExportFlag.exportFlags(HakureiConfig.HakureiExportFlag.Multiarch),
seccompPresets = HakureiConfig.SeccompFilterPreset.filterPresets(HakureiConfig.SeccompFilterPreset.Ext),
seccompCompat = true,
tty = true,
multiarch = true,
mapRealUid = true,
env = mapOf(
"GOOGLE_API_KEY" to "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
"GOOGLE_DEFAULT_CLIENT_ID" to "77185425430.apps.googleusercontent.com",
"GOOGLE_DEFAULT_CLIENT_SECRET" to "OTJgUOQcT7lO7GsGZq2G4IlT"
),
filesystem = listOf(
HakureiConfig.FilesystemConfig(dst = Paths.AbsFHSTmp, src = Paths.AbsNonexistent, write = true),
HakureiConfig.FilesystemConfig(src = AbsolutePath("/nix/store")),
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSRun + "current-system"),
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSRun + "opengl-driver"),
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSVar + "db/nix-channels"),
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSVarLib + "hakurei/u0/org.chromium.Chromium", dst = AbsolutePath("/data/data/org.chromium.Chromium"), write = true, must = true),
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSDev + "dri", device = true)
),
link = listOf(HakureiConfig.LinkConfig(Paths.AbsFHSRunUser + "65534", Paths.FHSRunUser + "150")),
autoRoot = Paths.AbsFHSVarLib + "hakurei/base/org.debian",
rootFlags = RootFlag.rootFlags(RootFlag.Writable),
etc = Paths.AbsFHSEtc,
autoEtc = true
)
)
println("printing for now until a more proper test can be written")
println(format.encodeToString(testConfig))
compare(want, test)
}
@Test
fun testEnablements() {
val enablement = HakureiConfig.Enablement.enablements(Wayland, X11, DBus, Pulse)
assertEquals(enablement, 0b1111)
}
@Test
fun testSeccompFilterPresets() {
val presets = HakureiConfig.SeccompFilterPreset.filterPresets(Ext, DenyNS, DenyTTY, DenyDevel, Linux32)
assertEquals(presets, 0b11111)
}
@Test
fun testExportFlags() {
val flags = HakureiConfig.HakureiExportFlag.exportFlags(Multiarch, CAN, Bluetooth)
assertEquals(flags, 0b111)
}
@Test
fun testRootFlags() {
val flags = HakureiConfig.RootFlag.rootFlags(Optional, Writable, Device)
assertEquals(flags, 0b111)
}
}