kt-go-split #1
3
.idea/gradle.xml
generated
3
.idea/gradle.xml
generated
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
@ -8,8 +9,6 @@
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/definition" />
|
||||
<option value="$PROJECT_DIR$/host" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
|
||||
@ -13,6 +13,7 @@ repositories {
|
||||
dependencies {
|
||||
testImplementation(kotlin("test"))
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
|
||||
implementation(kotlin("reflect"))
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
|
||||
@ -2,5 +2,3 @@ plugins {
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
|
||||
}
|
||||
rootProject.name = "planterette"
|
||||
include("definition")
|
||||
include("host")
|
||||
@ -1,62 +1,85 @@
|
||||
package moe.rosa.planterette.hakurei
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.descriptors.*
|
||||
import kotlinx.serialization.encoding.*
|
||||
|
||||
@Serializable
|
||||
data class AbsolutePath(private val path: String) {
|
||||
override fun toString(): String {
|
||||
return path
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* 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.of(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.of(pathname).isAbsolute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface FSType {
|
||||
val type: String
|
||||
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)
|
||||
}
|
||||
}
|
||||
data class ApplyState(val autoEtcPrefix: String)
|
||||
|
||||
/**
|
||||
* AbsolutePathException is returned by @see AbsolutePath() and holds the invalid pathname.
|
||||
*/
|
||||
data class AbsolutePathException(val pathname: String) : IllegalArgumentException("Path $pathname is not absolute")
|
||||
|
||||
@Serializable sealed interface FilesystemConfig
|
||||
|
||||
@Serializable
|
||||
@SerialName("bind")
|
||||
data class FSBind(
|
||||
val target: AbsolutePath,
|
||||
val source: AbsolutePath,
|
||||
val write: Boolean,
|
||||
val device: Boolean,
|
||||
val ensure: Boolean,
|
||||
val optional: Boolean,
|
||||
val special: Boolean,
|
||||
|
||||
override val type: String = "bind",
|
||||
) : FSType
|
||||
@SerialName("dst") val target: AbsolutePath? = null,
|
||||
@SerialName("src") val source: AbsolutePath,
|
||||
val write: Boolean? = null,
|
||||
@SerialName("dev") val device: Boolean? = null,
|
||||
val ensure: Boolean? = null,
|
||||
val optional: Boolean? = null,
|
||||
val special: Boolean? = null,
|
||||
) : FilesystemConfig
|
||||
|
||||
@Serializable
|
||||
@SerialName("ephemeral")
|
||||
data class FSEphemeral(
|
||||
val target: AbsolutePath,
|
||||
@SerialName("dst") val target: AbsolutePath,
|
||||
val write: Boolean,
|
||||
val size: Int,
|
||||
val size: Int? = null,
|
||||
val perm: Int,
|
||||
|
||||
override val type: String = "ephemeral"
|
||||
) : FSType
|
||||
) : FilesystemConfig
|
||||
|
||||
@Serializable
|
||||
@SerialName("link")
|
||||
data class FSLink(
|
||||
val target: AbsolutePath,
|
||||
@SerialName("dst") val target: AbsolutePath,
|
||||
val linkname: String,
|
||||
val dereference: Boolean,
|
||||
|
||||
override val type: String = "link"
|
||||
) : FSType
|
||||
) : FilesystemConfig
|
||||
|
||||
@Serializable
|
||||
@SerialName("overlay")
|
||||
data class FSOverlay(
|
||||
val target: AbsolutePath,
|
||||
@SerialName("dst") val target: AbsolutePath,
|
||||
val lower: List<AbsolutePath>,
|
||||
val upper: AbsolutePath,
|
||||
val work: AbsolutePath,
|
||||
|
||||
override val type: String = "overlay"
|
||||
) : FSType
|
||||
|
||||
@Serializable
|
||||
data class FilesystemConfig(
|
||||
val config: FSType
|
||||
)
|
||||
) : FilesystemConfig
|
||||
@ -1,93 +1,91 @@
|
||||
package moe.rosa.planterette.hakurei
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.*
|
||||
|
||||
@Serializable
|
||||
data class HakureiConfig(
|
||||
val id: String,
|
||||
val path: AbsolutePath,
|
||||
val args: List<String>,
|
||||
val enablements: Enablements,
|
||||
val sessionBus: DBusConfig,
|
||||
val systemBus: DBusConfig,
|
||||
val directWayland: Boolean,
|
||||
val username: String,
|
||||
val shell: AbsolutePath,
|
||||
val home: AbsolutePath,
|
||||
val id: String? = null,
|
||||
val path: AbsolutePath? = null,
|
||||
val args: List<String>? = null,
|
||||
val enablements: Enablements? = null,
|
||||
@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? = null,
|
||||
val home: AbsolutePath? = null,
|
||||
|
||||
val extraPerms: ExtraPermsConfig,
|
||||
val identity: Int,
|
||||
val groups: List<String>,
|
||||
@SerialName("extra_perms") val extraPerms: List<ExtraPermsConfig>? = null,
|
||||
val identity: Int? = null,
|
||||
val groups: List<String>? = null,
|
||||
|
||||
val container: ContainerConfig,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class DBusConfig(
|
||||
val see: List<String>,
|
||||
val talk: List<String>,
|
||||
val own: List<String>,
|
||||
val call: Map<String, String>,
|
||||
val broadcast: Map<String, String>,
|
||||
val log: Boolean,
|
||||
val filter: Boolean,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Enablements(
|
||||
val wayland: Boolean,
|
||||
val x11: Boolean,
|
||||
val dbus: Boolean,
|
||||
val pulse: Boolean,
|
||||
val container: ContainerConfig? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ContainerConfig(
|
||||
val hostname: String,
|
||||
val waitDelay: Long,
|
||||
val seccompFlags: Int,
|
||||
val seccompPresets: Int,
|
||||
val seccompCompat: Boolean,
|
||||
val devel: Boolean,
|
||||
val userns: Boolean,
|
||||
val hostNet: Boolean,
|
||||
val hostAbstract: Boolean,
|
||||
val tty: Boolean,
|
||||
val multiarch: Boolean,
|
||||
val hostname: String? = null,
|
||||
@SerialName("wait_delay") val waitDelay: Long? = null,
|
||||
@SerialName("seccomp_compat") val seccompCompat: Boolean? = null,
|
||||
val devel: Boolean? = null,
|
||||
val userns: Boolean? = null,
|
||||
@SerialName("host_net") val hostNet: Boolean? = null,
|
||||
@SerialName("host_abstract") val hostAbstract: Boolean? = null,
|
||||
val tty: Boolean? = null,
|
||||
val multiarch: Boolean? = null,
|
||||
|
||||
val env: Map<String, String>,
|
||||
val env: Map<String, String>? = null,
|
||||
|
||||
val mapRealUid: Boolean,
|
||||
val device: Boolean,
|
||||
@SerialName("map_real_uid") val mapRealUid: Boolean? = null,
|
||||
val device: Boolean? = null,
|
||||
|
||||
val filesystem: List<FilesystemConfig>,
|
||||
val filesystem: List<FilesystemConfig>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ExtraPermsConfig(
|
||||
val ensure: Boolean,
|
||||
val ensure: Boolean? = null,
|
||||
val path: AbsolutePath,
|
||||
val read: Boolean,
|
||||
val write: Boolean,
|
||||
val execute: Boolean,
|
||||
@SerialName("r") val read: Boolean? = null,
|
||||
@SerialName("w") val write: Boolean? = null,
|
||||
@SerialName("x") val execute: Boolean? = null,
|
||||
) {
|
||||
override fun toString(): String {
|
||||
val buffer = StringBuffer(5 + path.toString().length)
|
||||
buffer.append("---")
|
||||
if(ensure) {
|
||||
if(ensure == true) {
|
||||
buffer.append("+")
|
||||
}
|
||||
buffer.append(":")
|
||||
buffer.append(path.toString())
|
||||
if(read) {
|
||||
if(read == true) {
|
||||
buffer.setCharAt(0, 'r')
|
||||
}
|
||||
if(write) {
|
||||
if(write == true) {
|
||||
buffer.setCharAt(1, 'w')
|
||||
}
|
||||
if(execute) {
|
||||
if(execute == true) {
|
||||
buffer.setCharAt(2, 'x')
|
||||
}
|
||||
return buffer.toString()
|
||||
}
|
||||
}
|
||||
|
||||
@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 Enablements(
|
||||
val wayland: Boolean? = null,
|
||||
val x11: Boolean? = null,
|
||||
val dbus: Boolean? = null,
|
||||
val pulse: Boolean? = null,
|
||||
)
|
||||
|
||||
193
src/test/kotlin/HakureiTest.kt
Normal file
193
src/test/kotlin/HakureiTest.kt
Normal file
@ -0,0 +1,193 @@
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import java.io.File
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
import moe.rosa.planterette.hakurei.*
|
||||
import org.junit.jupiter.api.assertDoesNotThrow
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import kotlin.test.assertFails
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertIs
|
||||
|
||||
class HakureiTest {
|
||||
companion object {
|
||||
val CHROMIUM_TEMPLATE = HakureiConfig(
|
||||
id = "org.chromium.Chromium",
|
||||
path = AbsolutePath("/run/current-system/sw/bin/chromium"),
|
||||
args = listOf(
|
||||
"chromium",
|
||||
"--ignore-gpu-blocklist",
|
||||
"--disable-smooth-scrolling",
|
||||
"--enable-features=UseOzonePlatform",
|
||||
"--ozone-platform=wayland"
|
||||
),
|
||||
enablements = Enablements(
|
||||
wayland = true,
|
||||
dbus = true,
|
||||
pulse = true
|
||||
),
|
||||
sessionBus = 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 = DBusConfig(
|
||||
see = null,
|
||||
talk = listOf(
|
||||
"org.bluez",
|
||||
"org.freedesktop.Avahi",
|
||||
"org.freedesktop.UPower"
|
||||
),
|
||||
own = null,
|
||||
call = null,
|
||||
broadcast = null,
|
||||
filter = true
|
||||
),
|
||||
username = "chronos",
|
||||
shell = AbsolutePath("/run/current-system/sw/bin/zsh"),
|
||||
home = AbsolutePath("/data/data/org.chromium.Chromium"),
|
||||
extraPerms = listOf(
|
||||
ExtraPermsConfig(
|
||||
ensure = true,
|
||||
path = AbsolutePath("/var/lib/hakurei/u0"),
|
||||
read = null,
|
||||
write = null,
|
||||
execute = true,
|
||||
),
|
||||
ExtraPermsConfig(
|
||||
ensure = null,
|
||||
path = AbsolutePath("/var/lib/hakurei/u0/org.chromium.Chromium"),
|
||||
read = true,
|
||||
write = true,
|
||||
execute = true,
|
||||
),
|
||||
),
|
||||
identity = 9,
|
||||
groups = listOf(
|
||||
"video",
|
||||
"dialout",
|
||||
"plugdev"
|
||||
),
|
||||
container = ContainerConfig(
|
||||
hostname = "localhost",
|
||||
waitDelay = -1,
|
||||
seccompCompat = true,
|
||||
devel = true,
|
||||
userns = true,
|
||||
hostNet = true,
|
||||
hostAbstract = true,
|
||||
tty = true,
|
||||
multiarch = true,
|
||||
env = mapOf(
|
||||
"GOOGLE_API_KEY" to "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
|
||||
"GOOGLE_DEFAULT_CLIENT_ID" to "77185425430.apps.googleusercontent.com",
|
||||
"GOOGLE_DEFAULT_CLIENT_SECRET" to "OTJgUOQcT7lO7GsGZq2G4IlT"
|
||||
),
|
||||
mapRealUid = true,
|
||||
device = true,
|
||||
filesystem = listOf(
|
||||
FSBind(
|
||||
target = AbsolutePath("/"),
|
||||
source = AbsolutePath("/var/lib/hakurei/base/org.debian"),
|
||||
write = true,
|
||||
special = true,
|
||||
),
|
||||
FSBind(
|
||||
target = AbsolutePath("/etc/"),
|
||||
source = AbsolutePath("/etc/"),
|
||||
special = true,
|
||||
),
|
||||
FSEphemeral(
|
||||
target = AbsolutePath("/tmp/"),
|
||||
write = true,
|
||||
perm = 493
|
||||
),
|
||||
FSOverlay(
|
||||
target = AbsolutePath("/nix/store"),
|
||||
lower = listOf(
|
||||
AbsolutePath("/mnt-root/nix/.ro-store")
|
||||
),
|
||||
upper = AbsolutePath("/mnt-root/nix/.rw-store/upper"),
|
||||
work = AbsolutePath("/mnt-root/nix/.rw-store/work")
|
||||
),
|
||||
FSBind(
|
||||
source = AbsolutePath("/nix/store")
|
||||
),
|
||||
FSLink(
|
||||
target = AbsolutePath("/run/current-system"),
|
||||
linkname = "/run/current-system",
|
||||
dereference = true
|
||||
),
|
||||
FSLink(
|
||||
target = AbsolutePath("/run/opengl-driver"),
|
||||
linkname = "/run/opengl-driver",
|
||||
dereference = true
|
||||
),
|
||||
FSBind(
|
||||
target = AbsolutePath("/data/data/org.chromium.Chromium"),
|
||||
source = AbsolutePath("/var/lib/hakurei/u0/org.chromium.Chromium"),
|
||||
write = true,
|
||||
ensure = true,
|
||||
),
|
||||
FSBind(
|
||||
source = AbsolutePath("/dev/dri"),
|
||||
device = true,
|
||||
optional = true
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val format = Json { prettyPrint = true }
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Test
|
||||
fun deserializeTest() {
|
||||
println(System.getProperty("user.dir"))
|
||||
val want = format.decodeFromStream<HakureiConfig>(File("/home/mae/Documents/Projects/Rosa/planterette/src/test/resources/hakurei-chromium.json").inputStream())
|
||||
assertEquals(CHROMIUM_TEMPLATE, want)
|
||||
}
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Test
|
||||
fun serializeTest() {
|
||||
val encoded = format.encodeToString(CHROMIUM_TEMPLATE)
|
||||
val decoded = format.decodeFromString<HakureiConfig>(encoded)
|
||||
assertEquals(CHROMIUM_TEMPLATE, decoded)
|
||||
}
|
||||
@Test
|
||||
fun absolutePathTest() {
|
||||
assertDoesNotThrow {
|
||||
AbsolutePath("/test/absolutepath")
|
||||
}
|
||||
assertFailsWith(AbsolutePathException::class) {
|
||||
AbsolutePath("./../../../../")
|
||||
}
|
||||
assertEquals(AbsolutePath("/test/absolutepath"), AbsolutePath("/test/") + "absolutepath")
|
||||
}
|
||||
@Test
|
||||
fun extraPermsTest() {
|
||||
assertIs<String>(CHROMIUM_TEMPLATE.extraPerms.toString())
|
||||
}
|
||||
}
|
||||
146
src/test/resources/hakurei-chromium.json
Normal file
146
src/test/resources/hakurei-chromium.json
Normal file
@ -0,0 +1,146 @@
|
||||
{
|
||||
"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": {
|
||||
"wayland": true,
|
||||
"dbus": true,
|
||||
"pulse": true
|
||||
},
|
||||
"session_bus": {
|
||||
"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": {
|
||||
"talk": [
|
||||
"org.bluez",
|
||||
"org.freedesktop.Avahi",
|
||||
"org.freedesktop.UPower"
|
||||
],
|
||||
"filter": true
|
||||
},
|
||||
"username": "chronos",
|
||||
"shell": "/run/current-system/sw/bin/zsh",
|
||||
"home": "/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_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
"host_net": true,
|
||||
"host_abstract": 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": [
|
||||
{
|
||||
"type": "bind",
|
||||
"dst": "/",
|
||||
"src": "/var/lib/hakurei/base/org.debian",
|
||||
"write": true,
|
||||
"special": true
|
||||
},
|
||||
{
|
||||
"type": "bind",
|
||||
"dst": "/etc/",
|
||||
"src": "/etc/",
|
||||
"special": true
|
||||
},
|
||||
{
|
||||
"type": "ephemeral",
|
||||
"dst": "/tmp/",
|
||||
"write": true,
|
||||
"perm": 493
|
||||
},
|
||||
{
|
||||
"type": "overlay",
|
||||
"dst": "/nix/store",
|
||||
"lower": [
|
||||
"/mnt-root/nix/.ro-store"
|
||||
],
|
||||
"upper": "/mnt-root/nix/.rw-store/upper",
|
||||
"work": "/mnt-root/nix/.rw-store/work"
|
||||
},
|
||||
{
|
||||
"type": "bind",
|
||||
"src": "/nix/store"
|
||||
},
|
||||
{
|
||||
"type": "link",
|
||||
"dst": "/run/current-system",
|
||||
"linkname": "/run/current-system",
|
||||
"dereference": true
|
||||
},
|
||||
{
|
||||
"type": "link",
|
||||
"dst": "/run/opengl-driver",
|
||||
"linkname": "/run/opengl-driver",
|
||||
"dereference": true
|
||||
},
|
||||
{
|
||||
"type": "bind",
|
||||
"dst": "/data/data/org.chromium.Chromium",
|
||||
"src": "/var/lib/hakurei/u0/org.chromium.Chromium",
|
||||
"write": true,
|
||||
"ensure": true
|
||||
},
|
||||
{
|
||||
"type": "bind",
|
||||
"src": "/dev/dri",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user