diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 6793009..49774bd 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -20,11 +20,12 @@
diff --git a/api/src/commonMain/kotlin/app/hakurei/planterette/api/HakureiConfig.kt b/api/src/commonMain/kotlin/app/hakurei/planterette/api/HakureiConfig.kt
deleted file mode 100644
index 500efa0..0000000
--- a/api/src/commonMain/kotlin/app/hakurei/planterette/api/HakureiConfig.kt
+++ /dev/null
@@ -1,126 +0,0 @@
-package app.hakurei.planterette.api
-
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-
-@Serializable
-data class HakureiConfig(
- val id: String,
-
- val path: String? = null,
- val args: List,
-
- val enablements: Byte,
-
- @SerialName("session_bus") val sessionBus: DbusConfig? = null,
- @SerialName("system_bus") val systemBus: DbusConfig? = null,
- @SerialName("direct_wayland") val directWayland: Boolean? = null,
-
- val username: String? = null,
- val shell: String? = null,
- val data: String,
- val dir: String,
- @SerialName("extra_perms") val extraPerms: List?,
-
- val identity: Int,
- val groups: List,
-
- val container: ContainerConfig
-) {
- @Serializable
- data class DbusConfig(
- val see: List? = null,
- val talk: List? = null,
- val own: List? = null,
- val call: Map? = null,
- val broadcast: Map? = null,
- val log: Boolean? = null,
- val filter: Boolean? = null
- ) {
- }
-
- @Serializable
- data class ExtraPermConfig(
- val ensure: Boolean? = null,
- val path: String,
- @SerialName("r") val read: Boolean? = null,
- @SerialName("w") val write: Boolean? = null,
- @SerialName("x") val execute: Boolean? = null
- )
- @Serializable
- data class ContainerConfig(
- val hostname: String? = null,
- @SerialName("wait_delay") val delay: Int = -1,
- @SerialName("seccomp_flags") val seccompFlags: Int,
- @SerialName("seccomp_presets") val seccompPresets: Int,
- @SerialName("seccomp_compat") val seccompCompat: Boolean? = null,
-
- val devel: Boolean? = null,
- val userns: Boolean? = null,
- val net: Boolean? = null,
- val tty: Boolean? = null,
- val multiarch: Boolean? = null,
-
- val env: Map,
- @SerialName("map_real_uid") val mapRealUid: Boolean,
-
- val device: Boolean? = null,
- val filesystem: List,
- @SerialName("symlink") val link: List,
-
- val etc: String? = null,
- @SerialName("auto_etc") val autoEtc: Boolean,
- val cover: List
- )
-
- @Serializable
- data class FilesystemConfig(
- val dst: String? = null,
- val src: String,
- val write: Boolean? = null,
- @SerialName("dev") val device: Boolean? = null,
- @SerialName("require") val must: Boolean? = null
- )
-
- @Serializable
- enum class Enablement(val value: Int) {
- Wayland(1 shl 0),
- X11(1 shl 1),
- DBus(1 shl 2),
- Pulse(1 shl 3);
-
- companion object {
- fun enablements(vararg enablements: Enablement): Byte {
- return enablements.sumOf(Enablement::value).toByte()
- }
- }
- }
-
- @Serializable
- enum class SeccompFilterPreset(val value: Int) {
- Ext(1 shl 0),
- DenyNS(1 shl 1),
- DenyTTY(1 shl 2),
- DenyDevel(1 shl 3),
- Linux32(1 shl 4);
-
- companion object {
- fun filterPresets(vararg filterPresets: SeccompFilterPreset): Int {
- return filterPresets.sumOf(SeccompFilterPreset::value)
- }
- }
- }
-
- @Serializable
- enum class HakureiExportFlag(val value: Int) {
- Multiarch(1 shl 0),
- CAN(1 shl 1),
- Bluetooth(1 shl 2);
-
- companion object {
- fun exportFlags(vararg exportFlags: HakureiExportFlag): Int {
- return exportFlags.sumOf(HakureiExportFlag::value)
- }
- }
- }
-}
diff --git a/api/src/commonTest/kotlin/app/hakurei/planterette/api/HakureiConfigTest.kt b/api/src/commonTest/kotlin/app/hakurei/planterette/api/HakureiConfigTest.kt
deleted file mode 100644
index f1158ca..0000000
--- a/api/src/commonTest/kotlin/app/hakurei/planterette/api/HakureiConfigTest.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-package app.hakurei.planterette.api
-
-import kotlinx.serialization.ExperimentalSerializationApi
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.serializer
-import kotlin.test.Test
-import kotlin.test.assertEquals
-
-class HakureiConfigTest {
- @OptIn(ExperimentalSerializationApi::class)
- val format = Json {
- explicitNulls = false
- prettyPrint = true
- }
-
- @Test
- fun testHakureiConfigSerialization() {
- val testConfig = HakureiConfig(
- id = "org.chromium.Chromium",
- path = "/run/current-system/sw/bin/chromium",
- args = listOf(
- "chromium",
- "--ignore-gpu-blocklist",
- "--disable-smooth-scrolling",
- "--enable-features=UseOzonePlatform",
- "--ozone-platform=wayland"
- ),
- enablements = HakureiConfig.Enablement.enablements(HakureiConfig.Enablement.Wayland, HakureiConfig.Enablement.DBus,
- HakureiConfig.Enablement.Pulse),
- sessionBus = HakureiConfig.DbusConfig(
- see = null,
- 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/*"),
- filter = true
- ),
- systemBus = HakureiConfig.DbusConfig(
- talk = listOf("org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"),
- filter = true
- ),
-
- username = "chronos",
- shell = "/run/current-system/sw/bin/zsh",
- data = "/var/lib/hakurei/u0/org.chromium.Chromium",
- dir = "/data/data/org.chromium.Chromium",
- extraPerms = listOf(
- HakureiConfig.ExtraPermConfig(path = "/var/lib/hakurei/u0", ensure = true, execute = true),
- HakureiConfig.ExtraPermConfig(path = "/var/lib/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,
- seccompFlags = HakureiConfig.HakureiExportFlag.Multiarch.value,
- seccompPresets = HakureiConfig.SeccompFilterPreset.Ext.value,
- 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(src = "/nix/store"),
- HakureiConfig.FilesystemConfig(src = "/run/current-system"),
- HakureiConfig.FilesystemConfig(src = "/run/opengl-driver"),
- HakureiConfig.FilesystemConfig(src = "/var/db/nix-channels"),
- HakureiConfig.FilesystemConfig(
- src = "/var/lib/hakurei/u0/org.chromium.Chromium",
- dst = "/data/data/org.chromium.Chromium",
- write = true,
- must = true
- ),
- HakureiConfig.FilesystemConfig(src = "/dev/dri", device = true)
- ),
- link = listOf("/run/user/65534", "/run/user/150"),
- etc = "/etc",
- autoEtc = true,
- cover = listOf("/var/run/nscd"),
- delay = -1,
- seccompCompat = true
- )
- )
- val json = format.encodeToString(serializer(), testConfig)
- val deserializeJson: HakureiConfig = format.decodeFromString(json)
- val templateJson: HakureiConfig = format.decodeFromString(Util.readFileToString("/home/lilly/Documents/Projects/Rosa/planterette/api/src/commonTest/resources/template.json"))
- assertEquals(templateJson, deserializeJson, "")
- }
-}
\ No newline at end of file
diff --git a/api/src/commonTest/resources/template.json b/api/src/commonTest/resources/template.json
deleted file mode 100644
index 543b572..0000000
--- a/api/src/commonTest/resources/template.json
+++ /dev/null
@@ -1,123 +0,0 @@
-{
- "id": "org.chromium.Chromium",
- "path": "/run/current-system/sw/bin/chromium",
- "args": [
- "chromium",
- "--ignore-gpu-blocklist",
- "--disable-smooth-scrolling",
- "--enable-features=UseOzonePlatform",
- "--ozone-platform=wayland"
- ],
- "enablements": 13,
- "session_bus": {
- "see": null,
- "talk": [
- "org.freedesktop.Notifications",
- "org.freedesktop.FileManager1",
- "org.freedesktop.ScreenSaver",
- "org.freedesktop.secrets",
- "org.kde.kwalletd5",
- "org.kde.kwalletd6",
- "org.gnome.SessionManager"
- ],
- "own": [
- "org.chromium.Chromium.*",
- "org.mpris.MediaPlayer2.org.chromium.Chromium.*",
- "org.mpris.MediaPlayer2.chromium.*"
- ],
- "call": {
- "org.freedesktop.portal.*": "*"
- },
- "broadcast": {
- "org.freedesktop.portal.*": "@/org/freedesktop/portal/*"
- },
- "filter": true
- },
- "system_bus": {
- "see": null,
- "talk": [
- "org.bluez",
- "org.freedesktop.Avahi",
- "org.freedesktop.UPower"
- ],
- "own": null,
- "call": null,
- "broadcast": null,
- "filter": true
- },
- "username": "chronos",
- "shell": "/run/current-system/sw/bin/zsh",
- "data": "/var/lib/hakurei/u0/org.chromium.Chromium",
- "dir": "/data/data/org.chromium.Chromium",
- "extra_perms": [
- {
- "ensure": true,
- "path": "/var/lib/hakurei/u0",
- "x": true
- },
- {
- "path": "/var/lib/hakurei/u0/org.chromium.Chromium",
- "r": true,
- "w": true,
- "x": true
- }
- ],
- "identity": 9,
- "groups": [
- "video",
- "dialout",
- "plugdev"
- ],
- "container": {
- "hostname": "localhost",
- "wait_delay": -1,
- "seccomp_flags": 1,
- "seccomp_presets": 1,
- "seccomp_compat": true,
- "devel": true,
- "userns": true,
- "net": true,
- "tty": true,
- "multiarch": true,
- "env": {
- "GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
- "GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
- "GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
- },
- "map_real_uid": true,
- "device": true,
- "filesystem": [
- {
- "src": "/nix/store"
- },
- {
- "src": "/run/current-system"
- },
- {
- "src": "/run/opengl-driver"
- },
- {
- "src": "/var/db/nix-channels"
- },
- {
- "dst": "/data/data/org.chromium.Chromium",
- "src": "/var/lib/hakurei/u0/org.chromium.Chromium",
- "write": true,
- "require": true
- },
- {
- "src": "/dev/dri",
- "dev": true
- }
- ],
- "symlink": [
- "/run/user/65534",
- "/run/user/150"
- ],
- "etc": "/etc",
- "auto_etc": true,
- "cover": [
- "/var/run/nscd"
- ]
- }
-}
diff --git a/api/src/jvmMain/kotlin/app/hakurei/planterette/api/dsl/HakureiDSL.kt b/api/src/jvmMain/kotlin/app/hakurei/planterette/api/dsl/HakureiDSL.kt
deleted file mode 100644
index 8ecaee7..0000000
--- a/api/src/jvmMain/kotlin/app/hakurei/planterette/api/dsl/HakureiDSL.kt
+++ /dev/null
@@ -1,2 +0,0 @@
-package app.hakurei.planterette.api.dsl
-
diff --git a/cli/build.gradle.kts b/cli/build.gradle.kts
deleted file mode 100644
index 840fe91..0000000
--- a/cli/build.gradle.kts
+++ /dev/null
@@ -1,19 +0,0 @@
-plugins {
- kotlin("jvm")
-}
-
-repositories {
- mavenCentral()
-}
-
-dependencies {
- implementation(project(":api"))
- testImplementation(kotlin("test"))
-}
-
-tasks.test {
- useJUnitPlatform()
-}
-kotlin {
- jvmToolchain(21)
-}
\ No newline at end of file
diff --git a/daemon/build.gradle.kts b/daemon/build.gradle.kts
deleted file mode 100644
index 8eca01e..0000000
--- a/daemon/build.gradle.kts
+++ /dev/null
@@ -1,13 +0,0 @@
-plugins {
- id("buildsrc.convention.kotlin-jvm")
- kotlin("jvm")
- application
-}
-
-dependencies {
- implementation(project(":api"))
-}
-
-application {
- mainClass = "app.hakurei.planterette.PlanteretteKt"
-}
diff --git a/daemon/src/main/kotlin/app/hakurei/planterette/Planterette.kt b/daemon/src/main/kotlin/app/hakurei/planterette/Planterette.kt
deleted file mode 100644
index 735a8cd..0000000
--- a/daemon/src/main/kotlin/app/hakurei/planterette/Planterette.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package app.hakurei.planterette
-
-fun main() {
-
-}
\ No newline at end of file
diff --git a/daemon/src/test/resources/debian.json b/daemon/src/test/resources/debian.json
deleted file mode 100644
index 2f7684e..0000000
--- a/daemon/src/test/resources/debian.json
+++ /dev/null
@@ -1,124 +0,0 @@
-{
- "id": "org.debian",
- "path": "/bin/bash",
- "args": [
- "bash"
- ],
- "enablements": 13,
- "session_bus": {
- "see": null,
- "talk": [
- "org.freedesktop.Notifications",
- "org.freedesktop.FileManager1",
- "org.freedesktop.ScreenSaver"
- ],
- "own": null,
- "call": null,
- "broadcast": null,
- "filter": true
- },
- "system_bus": {
- "see": null,
- "talk": null,
- "own": null,
- "call": null,
- "broadcast": null,
- "filter": true
- },
- "username": "chronos",
- "shell": "/bin/bash",
- "data": "/tmp/chronos",
- "dir": "/.hakurei/home",
- "identity": 9,
- "groups": null,
- "container": {
- "hostname": "debian",
- "seccomp_flags": 0,
- "seccomp_presets": 0,
- "devel": true,
- "userns": true,
- "net": true,
- "tty": true,
- "multiarch": true,
- "env": null,
- "map_real_uid": true,
- "filesystem": [
- {
- "dst": "/boot",
- "src": "/mnt/debian/boot"
- },
- {
- "dst": "/home",
- "src": "/mnt/debian/home"
- },
- {
- "dst": "/media",
- "src": "/mnt/debian/media"
- },
- {
- "dst": "/mnt",
- "src": "/mnt/debian/mnt"
- },
- {
- "dst": "/opt",
- "src": "/mnt/debian/opt"
- },
- {
- "dst": "/root",
- "src": "/mnt/debian/root"
- },
- {
- "dst": "/srv",
- "src": "/mnt/debian/srv"
- },
- {
- "dst": "/usr",
- "src": "/mnt/debian/usr"
- },
- {
- "dst": "/var",
- "src": "/mnt/debian/var"
- },
- {
- "src": "/sys/block"
- },
- {
- "src": "/sys/bus"
- },
- {
- "src": "/sys/class"
- },
- {
- "src": "/sys/dev"
- },
- {
- "src": "/sys/devices"
- },
- {
- "src": "/dev/dri",
- "dev": true
- }
- ],
- "symlink": [
- [
- "usr/bin",
- "/bin"
- ],
- [
- "usr/lib",
- "/lib"
- ],
- [
- "usr/lib64",
- "/lib64"
- ],
- [
- "usr/sbin",
- "/sbin"
- ]
- ],
- "etc": "/mnt/debian/etc",
- "auto_etc": false,
- "cover": null
- }
-}
diff --git a/gui/build.gradle.kts b/gui/build.gradle.kts
deleted file mode 100644
index ae280b1..0000000
--- a/gui/build.gradle.kts
+++ /dev/null
@@ -1,20 +0,0 @@
-plugins {
- kotlin("jvm")
-}
-
-repositories {
- mavenCentral()
-}
-
-dependencies {
- implementation(project(":api"))
- implementation(project(":cli"))
- testImplementation(kotlin("test"))
-}
-
-tasks.test {
- useJUnitPlatform()
-}
-kotlin {
- jvmToolchain(21)
-}
\ No newline at end of file
diff --git a/api/build.gradle.kts b/libplt/build.gradle.kts
similarity index 83%
rename from api/build.gradle.kts
rename to libplt/build.gradle.kts
index 096d052..bb4ddb7 100644
--- a/api/build.gradle.kts
+++ b/libplt/build.gradle.kts
@@ -11,7 +11,7 @@ kotlin {
}
nativeTarget.binaries {
sharedLib {
- baseName = "planterette"
+ baseName = "libplt"
}
}
sourceSets {
@@ -22,11 +22,5 @@ kotlin {
implementation(libs.bundles.kotlinxEcosystem)
implementation(kotlin("test"))
}
- jvmMain.dependencies {
-
- }
- jvmTest.dependencies {
-
- }
}
}
diff --git a/libplt/src/commonMain/kotlin/app/hakurei/planterette/api/AbsolutePath.kt b/libplt/src/commonMain/kotlin/app/hakurei/planterette/api/AbsolutePath.kt
new file mode 100644
index 0000000..f787b76
--- /dev/null
+++ b/libplt/src/commonMain/kotlin/app/hakurei/planterette/api/AbsolutePath.kt
@@ -0,0 +1,51 @@
+package app.hakurei.planterette.api
+
+import app.hakurei.planterette.api.AbsolutePath.Companion.isAbsolute
+import kotlinx.io.files.Path
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.Serializer
+import kotlinx.serialization.Transient
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+
+/**
+ * AbsolutePath holds a pathname checked to be absolute.
+ * @constructor checks pathname and returns a new AbsolutePath if pathname is absolute.
+ */
+@Serializable(with = AbsolutePathSerializer::class)
+data class AbsolutePath(val pathname: String, @Transient val path: Path = Path(pathname)) {
+ init {
+ if(!isAbsolute(pathname)) {
+ throw AbsolutePathException(pathname)
+ }
+ }
+ operator fun plus(other: String): AbsolutePath {
+ return AbsolutePath(pathname + other)
+ }
+ companion object {
+ fun isAbsolute(pathname: String): Boolean {
+ return Path(pathname).isAbsolute
+ }
+ }
+}
+
+object AbsolutePathSerializer : KSerializer {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(this::class.qualifiedName!!, PrimitiveKind.STRING)
+ override fun serialize(encoder: Encoder, value: AbsolutePath) {
+ encoder.encodeString(value.pathname)
+ }
+
+ override fun deserialize(decoder: Decoder): AbsolutePath {
+ val path = decoder.decodeString()
+ return AbsolutePath(path)
+ }
+}
+
+/**
+ * AbsolutePathException is returned by @see AbsolutePath() and holds the invalid pathname.
+ */
+data class AbsolutePathException(val pathname: String) : IllegalArgumentException("Path $pathname is not absolute")
\ No newline at end of file
diff --git a/libplt/src/commonMain/kotlin/app/hakurei/planterette/api/HakureiConfig.kt b/libplt/src/commonMain/kotlin/app/hakurei/planterette/api/HakureiConfig.kt
new file mode 100644
index 0000000..81c2493
--- /dev/null
+++ b/libplt/src/commonMain/kotlin/app/hakurei/planterette/api/HakureiConfig.kt
@@ -0,0 +1,209 @@
+package app.hakurei.planterette.api
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+/**
+ * HakureiConfig is used to seal an app implementation.
+ * @param id reverse-DNS style arbitrary identifier string from config; Passed to wayland security-context-v1 as application ID and used as part of defaults in dbus session proxy
+ *
+ * @param path absolute path to executable file
+ * @param args final args passed to container init
+ *
+ * @param enablements system services to make available in the container
+ *
+ * @param sessionBus session D-Bus proxy configuration; null makes session bus proxy assume built-in defaults
+ * @param systemBus system D-Bus proxy configuration; null disables system bus proxy
+ * @param directWayland direct access to the wayland socket; when this gets set no attempt is made to attach security-context-v1 and the bare socket is mounted to the sandbox
+ *
+ * @param username passwd username in container, defaults to passwd name of target uid or chronos
+ * @param shell absolute path to shell
+ * @param data absolute path to home directory in the init mount namespace
+ * @param dir directory to ender and use as home in the container mount namespace, null for Data
+ * @param extraPerms extra ACL ops, dispatches before container init
+ *
+ * @param identity numerical application id, used for init user namespace credentials
+ * @param groups list of supplementary groups inherited by container processes
+ *
+ * @param container abstract container configuration baseline
+ */
+@Serializable
+data class HakureiConfig(
+ val id: String,
+
+ val path: AbsolutePath? = null,
+ val args: List,
+
+ val enablements: Byte,
+
+ @SerialName("session_bus") val sessionBus: DbusConfig? = null,
+ @SerialName("system_bus") val systemBus: DbusConfig? = null,
+ @SerialName("direct_wayland") val directWayland: Boolean? = null,
+
+ val username: String? = null,
+ val shell: AbsolutePath,
+ val data: AbsolutePath,
+ val dir: AbsolutePath,
+ @SerialName("extra_perms") val extraPerms: List? = null,
+
+ val identity: Int,
+ val groups: List,
+
+ val container: ContainerConfig
+) {
+ @Serializable
+ data class DbusConfig(
+ val see: List? = null,
+ val talk: List? = null,
+ val own: List? = null,
+ val call: Map? = null,
+ val broadcast: Map? = null,
+
+ val log: Boolean? = null,
+ val filter: Boolean
+ )
+
+ /**
+ * ExtraPermConfig describes an ACL update op.
+ */
+ @Serializable
+ data class ExtraPermConfig(
+ val ensure: Boolean? = null,
+ val path: AbsolutePath,
+ @SerialName("r") val read: Boolean? = null,
+ @SerialName("w") val write: Boolean? = null,
+ @SerialName("x") val execute: Boolean? = null
+ )
+
+ /**
+ * ContainerConfig describes the container configuration baseline to which the app implementation adds upon.
+ * @param hostname container hostname
+ * @param waitDelay duration to wait for after interrupting a container's initial process in nanoseconds; a negative value causes the container to be terminated immediately on cancellation
+ * @param seccompFlags extra seccomp flags
+ * @param seccompPresets extra seccomp presets
+ * @param seccompCompat disable project-specific filter extensions
+ * @param devel allow ptrace and friends
+ * @param userns allow userns creation in container
+ * @param net share host net namespace
+ * @param tty allow dangerous terminal I/O
+ * @param multiarch allow multiarch
+ *
+ * @param env initial process environment variables
+ * @param mapRealUid map target user uid to privileged user uid in the user namespace
+ *
+ * @param device pass through all devices
+ * @param filesystem container host filesystem bind mounts
+ * @param link create symlinks inside container filesystem
+ *
+ * @param autoRoot automatically bind mount top-level directories to container root; the zero value disables this behaviour
+ * @param rootFlags extra flags for AutoRoot
+ *
+ * @param etc read-only /etc directory
+ * @param autoEtc automatically set up /etc symlinks
+ */
+ @Serializable
+ data class ContainerConfig(
+ val hostname: String?,
+ @SerialName("wait_delay") val waitDelay: Int? = null,
+ @SerialName("seccomp_flags") val seccompFlags: Int,
+ @SerialName("seccomp_presets") val seccompPresets: Int,
+ @SerialName("seccomp_compat") val seccompCompat: Boolean? = null,
+
+ val devel: Boolean? = null,
+ val userns: Boolean? = null,
+ val net: Boolean? = null,
+ val tty: Boolean? = null,
+ val multiarch: Boolean? = null,
+
+ val env: Map,
+ @SerialName("map_real_uid") val mapRealUid: Boolean,
+
+ val device: Boolean?,
+ val filesystem: List,
+ @SerialName("symlink") val link: List,
+
+ @SerialName("auto_root") val autoRoot: AbsolutePath,
+ @SerialName("root_flags") val rootFlags: Int,
+
+ val etc: AbsolutePath?,
+
+ @SerialName("auto_etc") val autoEtc: Boolean,
+ )
+
+ /**
+ * FilesystemConfig is an abstract representation of a bind mount.
+ * @param dst mount point in container, same as src if empty
+ * @param src host filesystem path to make available to the container
+ * @param write do not mount filesystem read-only
+ * @param device do not disable device files
+ * @param must fail if the bind mount cannot be established for any reason
+ */
+ @Serializable
+ data class FilesystemConfig(
+ val dst: AbsolutePath? = null,
+ val src: AbsolutePath,
+ val write: Boolean? = null,
+ @SerialName("dev") val device: Boolean? = null,
+ @SerialName("require") val must: Boolean? = null
+ )
+
+ /**
+ * @param target symlink target in container
+ * @param linkname linkname the symlink points to; prepend '*' to dereference an absolute pathname on host
+ */
+ @Serializable
+ data class LinkConfig(
+ val target: AbsolutePath,
+ val linkname: String
+ )
+ enum class Enablement(val value: Int) {
+ Wayland(1 shl 0),
+ X11(1 shl 1),
+ DBus(1 shl 2),
+ Pulse(1 shl 3);
+
+ companion object {
+ fun enablements(vararg enablements: Enablement): Byte {
+ return enablements.orOf(Enablement::value).toByte()
+ }
+ }
+ }
+
+ enum class SeccompFilterPreset(val value: Int) {
+ Ext(1 shl 0),
+ DenyNS(1 shl 1),
+ DenyTTY(1 shl 2),
+ DenyDevel(1 shl 3),
+ Linux32(1 shl 4);
+
+ companion object {
+ fun filterPresets(vararg filterPresets: SeccompFilterPreset): Int {
+ return filterPresets.orOf(SeccompFilterPreset::value)
+ }
+ }
+ }
+
+ enum class HakureiExportFlag(val value: Int) {
+ Multiarch(1 shl 0),
+ CAN(1 shl 1),
+ Bluetooth(1 shl 2);
+
+ companion object {
+ fun exportFlags(vararg exportFlags: HakureiExportFlag): Int {
+ return exportFlags.orOf(HakureiExportFlag::value)
+ }
+ }
+ }
+
+ enum class RootFlag(val value: Int) {
+ Optional(1 shl 0),
+ Writable(1 shl 1),
+ Device(1 shl 2);
+
+ companion object {
+ fun rootFlags(vararg rootFlags: RootFlag): Int {
+ return rootFlags.orOf(RootFlag::value)
+ }
+ }
+ }
+}
diff --git a/api/src/commonMain/kotlin/app/hakurei/planterette/api/PackageManifest.kt b/libplt/src/commonMain/kotlin/app/hakurei/planterette/api/PackageManifest.kt
similarity index 57%
rename from api/src/commonMain/kotlin/app/hakurei/planterette/api/PackageManifest.kt
rename to libplt/src/commonMain/kotlin/app/hakurei/planterette/api/PackageManifest.kt
index ddf4b1b..bdb2d42 100644
--- a/api/src/commonMain/kotlin/app/hakurei/planterette/api/PackageManifest.kt
+++ b/libplt/src/commonMain/kotlin/app/hakurei/planterette/api/PackageManifest.kt
@@ -4,13 +4,13 @@ import kotlinx.serialization.Serializable
@Serializable
data class PackageManifest(
- val hakureiConfig: HakureiConfig,
- val id: String,
- val version: Version,
- val name: String,
- val description: String,
- val architecture: List,
- val baseImage: BaseImage
+ var hakureiConfig: HakureiConfig,
+ var id: String,
+ var version: Version,
+ var name: String,
+ var description: String,
+ var architecture: MutableList,
+ var baseImage: BaseImage
) {
@Serializable
data class Version(val version: String, val canonicalVersion: UInt)
@@ -21,10 +21,12 @@ data class PackageManifest(
ARM64,
}
@Serializable
- enum class BaseImage {
- DEBIAN,
- CHIMERA,
-
+ data class BaseImage(val type: Type) {
+ @Serializable
+ enum class Type {
+ DEBIAN,
+ CHIMERA,
+ }
}
companion object {
val fileList = listOf("planterette.json", "icon.png", "image.tar")
diff --git a/libplt/src/commonMain/kotlin/app/hakurei/planterette/api/Paths.kt b/libplt/src/commonMain/kotlin/app/hakurei/planterette/api/Paths.kt
new file mode 100644
index 0000000..ee6dad8
--- /dev/null
+++ b/libplt/src/commonMain/kotlin/app/hakurei/planterette/api/Paths.kt
@@ -0,0 +1,47 @@
+package app.hakurei.planterette.api
+
+object Paths {
+ const val FHSRoot = "/"
+ const val FHSEtc = "/etc/"
+ const val FHSTmp = "/tmp/"
+
+ const val FHSRun = "/run/"
+ const val FHSRunUser = FHSRun + "user/"
+
+ const val FHSUsr = "/usr/"
+ const val FHSUsrBin = FHSUsr + "bin/"
+
+ const val FHSVar = "/var/"
+ const val FHSVarLib = FHSVar + "lib/"
+ const val FHSVarEmpty = FHSVar + "empty/"
+
+ const val FHSDev = "/dev/"
+ const val FHSProc = "/proc/"
+ const val FHSProcSys = FHSProc + "sys/"
+ const val FHSSys = "/sys/"
+
+ const val Nonexistent = FHSProc + "nonexistent"
+ const val sysrootDir = "sysroot"
+ const val hostDir = "host"
+ const val hostPath = FHSRoot + hostDir
+ const val sysrootPath = FHSRoot + sysrootDir
+
+ val AbsFHSRoot = AbsolutePath(FHSRoot)
+ val AbsFHSEtc = AbsolutePath(FHSEtc)
+ val AbsFHSTmp = AbsolutePath(FHSTmp)
+
+ val AbsFHSRun = AbsolutePath(FHSRun)
+ val AbsFHSRunUser = AbsolutePath(FHSRunUser)
+
+ val AbsFHSUsrBin = AbsolutePath(FHSUsrBin)
+
+ val AbsFHSVar = AbsolutePath(FHSVar)
+ val AbsFHSVarLib = AbsolutePath(FHSVarLib)
+
+ val AbsFHSDev = AbsolutePath(FHSDev)
+ val AbsFHSProc = AbsolutePath(FHSProc)
+ val AbsFHSSys = AbsolutePath(FHSSys)
+
+ val AbsNonexistent = AbsolutePath(Nonexistent)
+}
+
diff --git a/api/src/commonMain/kotlin/app/hakurei/planterette/api/Util.kt b/libplt/src/commonMain/kotlin/app/hakurei/planterette/api/Util.kt
similarity index 71%
rename from api/src/commonMain/kotlin/app/hakurei/planterette/api/Util.kt
rename to libplt/src/commonMain/kotlin/app/hakurei/planterette/api/Util.kt
index cfd3ad3..b5c7bd5 100644
--- a/api/src/commonMain/kotlin/app/hakurei/planterette/api/Util.kt
+++ b/libplt/src/commonMain/kotlin/app/hakurei/planterette/api/Util.kt
@@ -13,4 +13,12 @@ object Util {
source.buffered().transferTo(buffer)
return buffer.readString()
}
+}
+
+inline fun Array.orOf(selector: (T) -> Int): Int {
+ var sum = 0
+ for (element in this) {
+ sum = sum or selector(element)
+ }
+ return sum
}
\ No newline at end of file
diff --git a/libplt/src/commonTest/kotlin/app/hakurei/planterette/api/HakureiConfigTest.kt b/libplt/src/commonTest/kotlin/app/hakurei/planterette/api/HakureiConfigTest.kt
new file mode 100644
index 0000000..d5264ed
--- /dev/null
+++ b/libplt/src/commonTest/kotlin/app/hakurei/planterette/api/HakureiConfigTest.kt
@@ -0,0 +1,230 @@
+package app.hakurei.planterette.api
+
+import app.hakurei.planterette.api.HakureiConfig.Enablement.*
+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
+
+class HakureiConfigTest {
+ @OptIn(ExperimentalSerializationApi::class)
+ val format = Json {
+ explicitNulls = false
+ prettyPrint = true
+ }
+ val want: String = """
+ {
+ "id": "org.chromium.Chromium",
+ "path": "/run/current-system/sw/bin/chromium",
+ "args": [
+ "chromium",
+ "--ignore-gpu-blocklist",
+ "--disable-smooth-scrolling",
+ "--enable-features=UseOzonePlatform",
+ "--ozone-platform=wayland"
+ ],
+ "enablements": 13,
+ "session_bus": {
+ "see": null,
+ "talk": [
+ "org.freedesktop.Notifications",
+ "org.freedesktop.FileManager1",
+ "org.freedesktop.ScreenSaver",
+ "org.freedesktop.secrets",
+ "org.kde.kwalletd5",
+ "org.kde.kwalletd6",
+ "org.gnome.SessionManager"
+ ],
+ "own": [
+ "org.chromium.Chromium.*",
+ "org.mpris.MediaPlayer2.org.chromium.Chromium.*",
+ "org.mpris.MediaPlayer2.chromium.*"
+ ],
+ "call": {
+ "org.freedesktop.portal.*": "*"
+ },
+ "broadcast": {
+ "org.freedesktop.portal.*": "@/org/freedesktop/portal/*"
+ },
+ "filter": true
+ },
+ "system_bus": {
+ "see": null,
+ "talk": [
+ "org.bluez",
+ "org.freedesktop.Avahi",
+ "org.freedesktop.UPower"
+ ],
+ "own": null,
+ "call": null,
+ "broadcast": null,
+ "filter": true
+ },
+ "username": "chronos",
+ "shell": "/run/current-system/sw/bin/zsh",
+ "data": "/var/lib/hakurei/u0/org.chromium.Chromium",
+ "dir": "/data/data/org.chromium.Chromium",
+ "extra_perms": [
+ {
+ "ensure": true,
+ "path": "/var/lib/hakurei/u0",
+ "x": true
+ },
+ {
+ "path": "/var/lib/hakurei/u0/org.chromium.Chromium",
+ "r": true,
+ "w": true,
+ "x": true
+ }
+ ],
+ "identity": 9,
+ "groups": [
+ "video",
+ "dialout",
+ "plugdev"
+ ],
+ "container": {
+ "hostname": "localhost",
+ "wait_delay": -1,
+ "seccomp_flags": 1,
+ "seccomp_presets": 1,
+ "seccomp_compat": true,
+ "devel": true,
+ "userns": true,
+ "net": true,
+ "tty": true,
+ "multiarch": true,
+ "env": {
+ "GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
+ "GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
+ "GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
+ },
+ "map_real_uid": true,
+ "device": true,
+ "filesystem": [
+ {
+ "dst": "/tmp/",
+ "src": "/proc/nonexistent",
+ "write": true
+ },
+ {
+ "src": "/nix/store"
+ },
+ {
+ "src": "/run/current-system"
+ },
+ {
+ "src": "/run/opengl-driver"
+ },
+ {
+ "src": "/var/db/nix-channels"
+ },
+ {
+ "dst": "/data/data/org.chromium.Chromium",
+ "src": "/var/lib/hakurei/u0/org.chromium.Chromium",
+ "write": true,
+ "require": true
+ },
+ {
+ "src": "/dev/dri",
+ "dev": true
+ }
+ ],
+ "symlink": [
+ {
+ "target": "/run/user/65534",
+ "linkname": "/run/user/150"
+ }
+ ],
+ "auto_root": "/var/lib/hakurei/base/org.debian",
+ "root_flags": 2,
+ "etc": "/etc/",
+ "auto_etc": true
+ }
+ }
+ """.trimIndent()
+ @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,
+
+ 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))
+ }
+}
\ No newline at end of file
diff --git a/plt-build/build.gradle.kts b/plt-build/build.gradle.kts
new file mode 100644
index 0000000..1442968
--- /dev/null
+++ b/plt-build/build.gradle.kts
@@ -0,0 +1,30 @@
+plugins {
+ kotlin("multiplatform")
+}
+kotlin {
+ jvm()
+ val nativeTarget = if(System.getProperty("os.arch") == "aarch64") {
+ linuxArm64("native")
+ } else {
+ linuxX64("native")
+ }
+ nativeTarget.binaries {
+ executable()
+ }
+ sourceSets {
+ nativeMain.dependencies {
+ implementation(project(":libplt"))
+ }
+ nativeTest.dependencies {
+ implementation(project(":libplt"))
+ implementation(kotlin("test"))
+ }
+ jvmMain.dependencies {
+ implementation(project(":libplt"))
+ }
+ jvmTest.dependencies {
+ implementation(project(":libplt"))
+ implementation(kotlin("test"))
+ }
+ }
+}
\ No newline at end of file
diff --git a/plt-build/src/jvmMain/kotlin/app/hakurei/planterette/api/dsl/Task.kt b/plt-build/src/jvmMain/kotlin/app/hakurei/planterette/api/dsl/Task.kt
new file mode 100644
index 0000000..ea8a76f
--- /dev/null
+++ b/plt-build/src/jvmMain/kotlin/app/hakurei/planterette/api/dsl/Task.kt
@@ -0,0 +1,16 @@
+package app.hakurei.planterette.api
+
+interface Task {
+ val execute: String
+}
+class AptInstallTask(val packages: List) : Task {
+ private fun getPackageList(): String {
+ var string = ""
+ packages.forEach { p ->
+ string += "$p "
+ }
+ return string
+ }
+ override val execute: String
+ get() = "sudo apt install ${getPackageList()}"
+}
\ No newline at end of file
diff --git a/plt-fetch/build.gradle.kts b/plt-fetch/build.gradle.kts
new file mode 100644
index 0000000..b641d4f
--- /dev/null
+++ b/plt-fetch/build.gradle.kts
@@ -0,0 +1,22 @@
+plugins {
+ kotlin("multiplatform")
+}
+kotlin {
+ val nativeTarget = if(System.getProperty("os.arch") == "aarch64") {
+ linuxArm64("native")
+ } else {
+ linuxX64("native")
+ }
+ nativeTarget.binaries {
+ executable()
+ }
+ sourceSets {
+ nativeMain.dependencies {
+ implementation(project(":libplt"))
+ }
+ nativeTest.dependencies {
+ implementation(project(":libplt"))
+ implementation(kotlin("test"))
+ }
+ }
+}
\ No newline at end of file
diff --git a/plt-pkg/build.gradle.kts b/plt-pkg/build.gradle.kts
new file mode 100644
index 0000000..b641d4f
--- /dev/null
+++ b/plt-pkg/build.gradle.kts
@@ -0,0 +1,22 @@
+plugins {
+ kotlin("multiplatform")
+}
+kotlin {
+ val nativeTarget = if(System.getProperty("os.arch") == "aarch64") {
+ linuxArm64("native")
+ } else {
+ linuxX64("native")
+ }
+ nativeTarget.binaries {
+ executable()
+ }
+ sourceSets {
+ nativeMain.dependencies {
+ implementation(project(":libplt"))
+ }
+ nativeTest.dependencies {
+ implementation(project(":libplt"))
+ implementation(kotlin("test"))
+ }
+ }
+}
\ No newline at end of file
diff --git a/plt/build.gradle.kts b/plt/build.gradle.kts
new file mode 100644
index 0000000..8cd057f
--- /dev/null
+++ b/plt/build.gradle.kts
@@ -0,0 +1,22 @@
+plugins {
+ kotlin("multiplatform")
+}
+kotlin {
+ val nativeTarget = if(System.getProperty("os.arch") == "aarch64") {
+ linuxArm64("native")
+ } else {
+ linuxX64("native")
+ }
+ nativeTarget.binaries {
+ executable()
+ }
+ sourceSets {
+ nativeMain.dependencies {
+ implementation(project(":libplt"))
+ }
+ nativeTest.dependencies {
+ implementation(project(":libplt"))
+ implementation(kotlin("test"))
+ }
+ }
+}
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 2ba66ff..edcb46b 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -9,9 +9,10 @@ plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
}
-include(":daemon")
-include(":api")
-include(":cli")
-include(":gui")
+rootProject.name = "planterette"
-rootProject.name = "planterette"
\ No newline at end of file
+include("plt")
+include("libplt")
+include("plt-build")
+include("plt-fetch")
+include("plt-pkg")
\ No newline at end of file