restructure, begin implementing libplt

This commit is contained in:
lilly 2025-08-12 06:24:29 -05:00
parent ae166bc45c
commit ca586aaa36
Signed by: maemachinebroke
GPG Key ID: B54591A4805E9CC8
23 changed files with 682 additions and 556 deletions

9
.idea/gradle.xml generated
View File

@ -20,11 +20,12 @@
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/api" />
<option value="$PROJECT_DIR$/buildSrc" />
<option value="$PROJECT_DIR$/cli" />
<option value="$PROJECT_DIR$/daemon" />
<option value="$PROJECT_DIR$/gui" />
<option value="$PROJECT_DIR$/libplt" />
<option value="$PROJECT_DIR$/plt" />
<option value="$PROJECT_DIR$/plt-build" />
<option value="$PROJECT_DIR$/plt-fetch" />
<option value="$PROJECT_DIR$/plt-pkg" />
</set>
</option>
</GradleProjectSettings>

View File

@ -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<String>,
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<ExtraPermConfig>?,
val identity: Int,
val groups: List<String>,
val container: ContainerConfig
) {
@Serializable
data class DbusConfig(
val see: List<String>? = null,
val talk: List<String>? = null,
val own: List<String>? = null,
val call: Map<String, String>? = null,
val broadcast: Map<String, String>? = 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<String, String>,
@SerialName("map_real_uid") val mapRealUid: Boolean,
val device: Boolean? = null,
val filesystem: List<FilesystemConfig>,
@SerialName("symlink") val link: List<String>,
val etc: String? = null,
@SerialName("auto_etc") val autoEtc: Boolean,
val cover: List<String>
)
@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)
}
}
}
}

View File

@ -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, "")
}
}

View File

@ -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"
]
}
}

View File

@ -1,2 +0,0 @@
package app.hakurei.planterette.api.dsl

View File

@ -1,19 +0,0 @@
plugins {
kotlin("jvm")
}
repositories {
mavenCentral()
}
dependencies {
implementation(project(":api"))
testImplementation(kotlin("test"))
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(21)
}

View File

@ -1,13 +0,0 @@
plugins {
id("buildsrc.convention.kotlin-jvm")
kotlin("jvm")
application
}
dependencies {
implementation(project(":api"))
}
application {
mainClass = "app.hakurei.planterette.PlanteretteKt"
}

View File

@ -1,5 +0,0 @@
package app.hakurei.planterette
fun main() {
}

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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 {
}
}
}

View File

@ -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<AbsolutePath> {
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")

View File

@ -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<String>,
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<ExtraPermConfig>? = null,
val identity: Int,
val groups: List<String>,
val container: ContainerConfig
) {
@Serializable
data class DbusConfig(
val see: List<String>? = null,
val talk: List<String>? = null,
val own: List<String>? = null,
val call: Map<String, String>? = null,
val broadcast: Map<String, String>? = 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<String, String>,
@SerialName("map_real_uid") val mapRealUid: Boolean,
val device: Boolean?,
val filesystem: List<FilesystemConfig>,
@SerialName("symlink") val link: List<LinkConfig>,
@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)
}
}
}
}

View File

@ -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<Architecture>,
val baseImage: BaseImage
var hakureiConfig: HakureiConfig,
var id: String,
var version: Version,
var name: String,
var description: String,
var architecture: MutableList<Architecture>,
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 {
data class BaseImage(val type: Type) {
@Serializable
enum class Type {
DEBIAN,
CHIMERA,
}
}
companion object {
val fileList = listOf("planterette.json", "icon.png", "image.tar")

View File

@ -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)
}

View File

@ -14,3 +14,11 @@ object Util {
return buffer.readString()
}
}
inline fun <T> Array<out T>.orOf(selector: (T) -> Int): Int {
var sum = 0
for (element in this) {
sum = sum or selector(element)
}
return sum
}

View File

@ -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))
}
}

View File

@ -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"))
}
}
}

View File

@ -0,0 +1,16 @@
package app.hakurei.planterette.api
interface Task {
val execute: String
}
class AptInstallTask(val packages: List<String>) : Task {
private fun getPackageList(): String {
var string = ""
packages.forEach { p ->
string += "$p "
}
return string
}
override val execute: String
get() = "sudo apt install ${getPackageList()}"
}

View File

@ -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"))
}
}
}

22
plt-pkg/build.gradle.kts Normal file
View File

@ -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"))
}
}
}

22
plt/build.gradle.kts Normal file
View File

@ -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"))
}
}
}

View File

@ -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"
include("plt")
include("libplt")
include("plt-build")
include("plt-fetch")
include("plt-pkg")