kt-go-split #1
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,12 +1,14 @@ | ||||
| .gradle | ||||
| gradlew.bat | ||||
| build/ | ||||
| !gradle/wrapper/gradle-wrapper.jar | ||||
| !**/src/main/**/build/ | ||||
| !**/src/test/**/build/ | ||||
| 
 | ||||
| ### IntelliJ IDEA ### | ||||
| /.idea/ | ||||
| .idea/modules.xml | ||||
| .idea/jarRepositories.xml | ||||
| .idea/compiler.xml | ||||
| .idea/libraries/ | ||||
| *.iws | ||||
| *.iml | ||||
| *.ipr | ||||
|  | ||||
							
								
								
									
										2
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,5 +1,3 @@ | ||||
| # Default ignored files | ||||
| /shelf/ | ||||
| /workspace.xml | ||||
| # Environment-dependent path to Maven home directory | ||||
| /mavenHomeManager.xml | ||||
|  | ||||
							
								
								
									
										10
									
								
								.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| <component name="ProjectCodeStyleConfiguration"> | ||||
|   <code_scheme name="Project" version="173"> | ||||
|     <JetCodeStyleSettings> | ||||
|       <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> | ||||
|     </JetCodeStyleSettings> | ||||
|     <codeStyleSettings language="kotlin"> | ||||
|       <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> | ||||
|     </codeStyleSettings> | ||||
|   </code_scheme> | ||||
| </component> | ||||
							
								
								
									
										5
									
								
								.idea/codeStyles/codeStyleConfig.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.idea/codeStyles/codeStyleConfig.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| <component name="ProjectCodeStyleConfiguration"> | ||||
|   <state> | ||||
|     <option name="USE_PER_PROJECT_SETTINGS" value="true" /> | ||||
|   </state> | ||||
| </component> | ||||
							
								
								
									
										8
									
								
								.idea/dictionaries/project.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								.idea/dictionaries/project.xml
									
									
									
										generated
									
									
									
								
							| @ -1,8 +0,0 @@ | ||||
| <component name="ProjectDictionaryState"> | ||||
|   <dictionary name="project"> | ||||
|     <words> | ||||
|       <w>buildsrc</w> | ||||
|       <w>planterette</w> | ||||
|     </words> | ||||
|   </dictionary> | ||||
| </component> | ||||
							
								
								
									
										17
									
								
								.idea/gradle.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										17
									
								
								.idea/gradle.xml
									
									
									
										generated
									
									
									
								
							| @ -4,28 +4,11 @@ | ||||
|   <component name="GradleSettings"> | ||||
|     <option name="linkedExternalProjectsSettings"> | ||||
|       <GradleProjectSettings> | ||||
|         <compositeConfiguration> | ||||
|           <compositeBuild compositeDefinitionSource="SCRIPT"> | ||||
|             <builds> | ||||
|               <build path="$PROJECT_DIR$/buildSrc" name="buildSrc"> | ||||
|                 <projects> | ||||
|                   <project path="$PROJECT_DIR$/buildSrc" /> | ||||
|                 </projects> | ||||
|               </build> | ||||
|             </builds> | ||||
|           </compositeBuild> | ||||
|         </compositeConfiguration> | ||||
|         <option name="externalProjectPath" value="$PROJECT_DIR$" /> | ||||
|         <option name="gradleHome" value="" /> | ||||
|         <option name="modules"> | ||||
|           <set> | ||||
|             <option value="$PROJECT_DIR$" /> | ||||
|             <option value="$PROJECT_DIR$/buildSrc" /> | ||||
|             <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> | ||||
|  | ||||
							
								
								
									
										6
									
								
								.idea/kotlinc.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/kotlinc.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="KotlinJpsPluginSettings"> | ||||
|     <option name="version" value="2.2.20" /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										5
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							| @ -1,7 +1,10 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="ExternalStorageConfigurationManager" enabled="true" /> | ||||
|   <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK"> | ||||
|   <component name="FrameworkDetectionExcludesConfiguration"> | ||||
|     <file type="web" url="file://$PROJECT_DIR$" /> | ||||
|   </component> | ||||
|   <component name="ProjectRootManager" version="2" languageLevel="JDK_24" default="true" project-jdk-name="openjdk-24" project-jdk-type="JavaSDK"> | ||||
|     <output url="file://$PROJECT_DIR$/out" /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										21
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								README.md
									
									
									
									
									
								
							| @ -1,23 +1,2 @@ | ||||
| # planterette | ||||
| 
 | ||||
| This project uses [Gradle](https://gradle.org/). | ||||
| To build and run the application, use the *Gradle* tool window by clicking the Gradle icon in the right-hand toolbar, | ||||
| or run it directly from the terminal: | ||||
| 
 | ||||
| * Run `./gradlew run` to build and run the application. | ||||
| * Run `./gradlew build` to only build the application. | ||||
| * Run `./gradlew check` to run all checks, including tests. | ||||
| * Run `./gradlew clean` to clean all build outputs. | ||||
| 
 | ||||
| Note the usage of the Gradle Wrapper (`./gradlew`). | ||||
| This is the suggested way to use Gradle in production projects. | ||||
| 
 | ||||
| [Learn more about the Gradle Wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html). | ||||
| 
 | ||||
| [Learn more about Gradle tasks](https://docs.gradle.org/current/userguide/command_line_interface.html#common_tasks). | ||||
| 
 | ||||
| This project follows the suggested multi-module setup and consists of the `app` and `api` subprojects. | ||||
| The shared build logic was extracted to a convention plugin located in `buildSrc`. | ||||
| 
 | ||||
| This project uses a version catalog (see `gradle/libs.versions.toml`) to declare and version dependencies | ||||
| and both a build cache and a configuration cache (see `gradle.properties`). | ||||
							
								
								
									
										46
									
								
								build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| plugins { | ||||
|     kotlin("jvm") version "2.2.10" | ||||
|     kotlin("plugin.serialization") version "2.2.20" | ||||
| } | ||||
| 
 | ||||
| group = "moe.rosa" | ||||
| version = "0.1.0" | ||||
| 
 | ||||
| repositories { | ||||
|     mavenCentral() | ||||
| } | ||||
| 
 | ||||
| dependencies { | ||||
|     testImplementation(kotlin("test")) | ||||
|     implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") | ||||
|     implementation(kotlin("reflect")) | ||||
| } | ||||
| 
 | ||||
| tasks.test { | ||||
|     useJUnitPlatform() | ||||
|     dependsOn(testGo) | ||||
| } | ||||
| tasks.classes { | ||||
|     dependsOn(compileGo) | ||||
| } | ||||
| kotlin { | ||||
|     jvmToolchain(24) | ||||
| } | ||||
| 
 | ||||
| val compileGo = tasks.register("compileGo", Exec::class) { | ||||
|     group = "go" | ||||
|     workingDir(layout.projectDirectory) | ||||
|     commandLine("/usr/bin/go", "build", "-o", "build/go/planterette", "./src/main/go") | ||||
| } | ||||
| 
 | ||||
| val testGo = tasks.register("testGo", Exec::class) { | ||||
|     group = "go" | ||||
|     workingDir(layout.projectDirectory) | ||||
|     commandLine("/usr/bin/go", "test", "./src/main/go") | ||||
| } | ||||
| 
 | ||||
| val runGo = tasks.register("runGo", Exec::class) { | ||||
|     group = "go" | ||||
|     workingDir(layout.projectDirectory) | ||||
|     commandLine("/usr/bin/go", "run", "./src/main/go") | ||||
| } | ||||
| @ -1,11 +0,0 @@ | ||||
| plugins { | ||||
|     `kotlin-dsl` | ||||
| } | ||||
| 
 | ||||
| kotlin { | ||||
|     jvmToolchain(24) | ||||
| } | ||||
| 
 | ||||
| dependencies { | ||||
|     implementation(libs.kotlinGradlePlugin) | ||||
| } | ||||
| @ -1,15 +0,0 @@ | ||||
| dependencyResolutionManagement { | ||||
| 
 | ||||
|     @Suppress("UnstableApiUsage") | ||||
|     repositories { | ||||
|         mavenCentral() | ||||
|     } | ||||
| 
 | ||||
|     versionCatalogs { | ||||
|         create("libs") { | ||||
|             from(files("../gradle/libs.versions.toml")) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| rootProject.name = "buildSrc" | ||||
| @ -1,15 +0,0 @@ | ||||
| package buildsrc.convention | ||||
| 
 | ||||
| import org.gradle.api.tasks.testing.logging.TestLogEvent | ||||
| 
 | ||||
| tasks.withType<Test>().configureEach { | ||||
|     useJUnitPlatform() | ||||
| 
 | ||||
|     testLogging { | ||||
|         events( | ||||
|             TestLogEvent.FAILED, | ||||
|             TestLogEvent.PASSED, | ||||
|             TestLogEvent.SKIPPED | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @ -1,3 +1 @@ | ||||
| org.gradle.caching=true | ||||
| org.gradle.configuration-cache=true | ||||
| kotlin.mpp.applyDefaultHierarchyTemplate=false | ||||
| kotlin.code.style=official | ||||
|  | ||||
| @ -1,20 +0,0 @@ | ||||
| [versions] | ||||
| kotlin = "2.2.0" | ||||
| kotlinxDatetime = "0.6.1" | ||||
| kotlinxSerializationJSON = "1.7.3" | ||||
| kotlinxCoroutines = "1.9.0" | ||||
| kotlinxIo = "0.8.0" | ||||
| 
 | ||||
| [libraries] | ||||
| kotlinGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } | ||||
| kotlinxDatetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } | ||||
| kotlinxSerialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJSON" } | ||||
| kotlinxCoroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } | ||||
| kotlinxIo = { module = "org.jetbrains.kotlinx:kotlinx-io-core", version.ref = "kotlinxIo" } | ||||
| 
 | ||||
| [bundles] | ||||
| kotlinxEcosystem = ["kotlinxDatetime", "kotlinxSerialization", "kotlinxCoroutines", "kotlinxIo"] | ||||
| 
 | ||||
| [plugins] | ||||
| kotlinPluginSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } | ||||
| kotlinPluginMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } | ||||
							
								
								
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,4 @@ | ||||
| #Tue Aug 26 23:15:20 CDT 2025 | ||||
| #Fri Sep 26 23:47:58 CDT 2025 | ||||
| distributionBase=GRADLE_USER_HOME | ||||
| distributionPath=wrapper/dists | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip | ||||
|  | ||||
							
								
								
									
										43
									
								
								gradlew
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										43
									
								
								gradlew
									
									
									
									
										vendored
									
									
								
							| @ -15,8 +15,6 @@ | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| # SPDX-License-Identifier: Apache-2.0 | ||||
| # | ||||
| 
 | ||||
| ############################################################################## | ||||
| # | ||||
| @ -57,7 +55,7 @@ | ||||
| #       Darwin, MinGW, and NonStop. | ||||
| # | ||||
| #   (3) This script is generated from the Groovy template | ||||
| #       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt | ||||
| #       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt | ||||
| #       within the Gradle project. | ||||
| # | ||||
| #       You can find Gradle at https://github.com/gradle/gradle/. | ||||
| @ -82,11 +80,13 @@ do | ||||
|     esac | ||||
| done | ||||
| 
 | ||||
| # This is normally unused | ||||
| # shellcheck disable=SC2034 | ||||
| APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit | ||||
| 
 | ||||
| APP_NAME="Gradle" | ||||
| APP_BASE_NAME=${0##*/} | ||||
| # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) | ||||
| APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit | ||||
| 
 | ||||
| # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||||
| DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' | ||||
| 
 | ||||
| # Use the maximum available, or set MAX_FD != -1 to use that value. | ||||
| MAX_FD=maximum | ||||
| @ -133,29 +133,22 @@ location of your Java installation." | ||||
|     fi | ||||
| else | ||||
|     JAVACMD=java | ||||
|     if ! command -v java >/dev/null 2>&1 | ||||
|     then | ||||
|         die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||||
|     which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||||
| 
 | ||||
| Please set the JAVA_HOME variable in your environment to match the | ||||
| location of your Java installation." | ||||
| fi | ||||
| fi | ||||
| 
 | ||||
| # Increase the maximum file descriptors if we can. | ||||
| if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then | ||||
|     case $MAX_FD in #( | ||||
|       max*) | ||||
|         # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. | ||||
|         # shellcheck disable=SC2039,SC3045 | ||||
|         MAX_FD=$( ulimit -H -n ) || | ||||
|             warn "Could not query maximum file descriptor limit" | ||||
|     esac | ||||
|     case $MAX_FD in  #( | ||||
|       '' | soft) :;; #( | ||||
|       *) | ||||
|         # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. | ||||
|         # shellcheck disable=SC2039,SC3045 | ||||
|         ulimit -n "$MAX_FD" || | ||||
|             warn "Could not set maximum file descriptor limit to $MAX_FD" | ||||
|     esac | ||||
| @ -200,15 +193,11 @@ if "$cygwin" || "$msys" ; then | ||||
|     done | ||||
| fi | ||||
| 
 | ||||
| 
 | ||||
| # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||||
| DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' | ||||
| 
 | ||||
| # Collect all arguments for the java command: | ||||
| #   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, | ||||
| #     and any embedded shellness will be escaped. | ||||
| #   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be | ||||
| #     treated as '${Hostname}' itself on the command line. | ||||
| # Collect all arguments for the java command; | ||||
| #   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of | ||||
| #     shell script including quotes and variable substitutions, so put them in | ||||
| #     double quotes to make sure that they get re-expanded; and | ||||
| #   * put everything else in single quotes, so that it's not re-expanded. | ||||
| 
 | ||||
| set -- \ | ||||
|         "-Dorg.gradle.appname=$APP_BASE_NAME" \ | ||||
| @ -216,12 +205,6 @@ set -- \ | ||||
|         org.gradle.wrapper.GradleWrapperMain \ | ||||
|         "$@" | ||||
| 
 | ||||
| # Stop when "xargs" is not available. | ||||
| if ! command -v xargs >/dev/null 2>&1 | ||||
| then | ||||
|     die "xargs is not available" | ||||
| fi | ||||
| 
 | ||||
| # Use "xargs" to parse quoted args. | ||||
| # | ||||
| # With -n1 it outputs one arg per line, with the quotes and backslashes removed. | ||||
|  | ||||
							
								
								
									
										89
									
								
								gradlew.bat
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								gradlew.bat
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | ||||
| @rem | ||||
| @rem Copyright 2015 the original author or authors. | ||||
| @rem | ||||
| @rem Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| @rem you may not use this file except in compliance with the License. | ||||
| @rem You may obtain a copy of the License at | ||||
| @rem | ||||
| @rem      https://www.apache.org/licenses/LICENSE-2.0 | ||||
| @rem | ||||
| @rem Unless required by applicable law or agreed to in writing, software | ||||
| @rem distributed under the License is distributed on an "AS IS" BASIS, | ||||
| @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| @rem See the License for the specific language governing permissions and | ||||
| @rem limitations under the License. | ||||
| @rem | ||||
| 
 | ||||
| @if "%DEBUG%" == "" @echo off | ||||
| @rem ########################################################################## | ||||
| @rem | ||||
| @rem  Gradle startup script for Windows | ||||
| @rem | ||||
| @rem ########################################################################## | ||||
| 
 | ||||
| @rem Set local scope for the variables with windows NT shell | ||||
| if "%OS%"=="Windows_NT" setlocal | ||||
| 
 | ||||
| set DIRNAME=%~dp0 | ||||
| if "%DIRNAME%" == "" set DIRNAME=. | ||||
| set APP_BASE_NAME=%~n0 | ||||
| set APP_HOME=%DIRNAME% | ||||
| 
 | ||||
| @rem Resolve any "." and ".." in APP_HOME to make it shorter. | ||||
| for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi | ||||
| 
 | ||||
| @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||||
| set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" | ||||
| 
 | ||||
| @rem Find java.exe | ||||
| if defined JAVA_HOME goto findJavaFromJavaHome | ||||
| 
 | ||||
| set JAVA_EXE=java.exe | ||||
| %JAVA_EXE% -version >NUL 2>&1 | ||||
| if "%ERRORLEVEL%" == "0" goto execute | ||||
| 
 | ||||
| echo. | ||||
| echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||||
| echo. | ||||
| echo Please set the JAVA_HOME variable in your environment to match the | ||||
| echo location of your Java installation. | ||||
| 
 | ||||
| goto fail | ||||
| 
 | ||||
| :findJavaFromJavaHome | ||||
| set JAVA_HOME=%JAVA_HOME:"=% | ||||
| set JAVA_EXE=%JAVA_HOME%/bin/java.exe | ||||
| 
 | ||||
| if exist "%JAVA_EXE%" goto execute | ||||
| 
 | ||||
| echo. | ||||
| echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% | ||||
| echo. | ||||
| echo Please set the JAVA_HOME variable in your environment to match the | ||||
| echo location of your Java installation. | ||||
| 
 | ||||
| goto fail | ||||
| 
 | ||||
| :execute | ||||
| @rem Setup the command line | ||||
| 
 | ||||
| set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | ||||
| 
 | ||||
| 
 | ||||
| @rem Execute Gradle | ||||
| "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* | ||||
| 
 | ||||
| :end | ||||
| @rem End local scope for the variables with windows NT shell | ||||
| if "%ERRORLEVEL%"=="0" goto mainEnd | ||||
| 
 | ||||
| :fail | ||||
| rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | ||||
| rem the _cmd.exe /c_ return code! | ||||
| if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 | ||||
| exit /b 1 | ||||
| 
 | ||||
| :mainEnd | ||||
| if "%OS%"=="Windows_NT" endlocal | ||||
| 
 | ||||
| :omega | ||||
| @ -1,26 +0,0 @@ | ||||
| plugins { | ||||
|     kotlin("multiplatform") | ||||
|     alias(libs.plugins.kotlinPluginSerialization) | ||||
| } | ||||
| kotlin { | ||||
|     jvm() | ||||
|     val nativeTarget = if(System.getProperty("os.arch") == "aarch64") { | ||||
|         linuxArm64("native") | ||||
|     } else { | ||||
|         linuxX64("native") | ||||
|     } | ||||
|     nativeTarget.binaries { | ||||
|         sharedLib { | ||||
|             baseName = "libplt" | ||||
|         } | ||||
|     } | ||||
|     sourceSets { | ||||
|         commonMain.dependencies { | ||||
|             implementation(libs.bundles.kotlinxEcosystem) | ||||
|         } | ||||
|         commonTest.dependencies { | ||||
|             implementation(libs.bundles.kotlinxEcosystem) | ||||
|             implementation(kotlin("test")) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,51 +0,0 @@ | ||||
| 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") | ||||
| @ -1,209 +0,0 @@ | ||||
| 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) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,35 +0,0 @@ | ||||
| package app.hakurei.planterette.api | ||||
| 
 | ||||
| import kotlinx.serialization.Serializable | ||||
| 
 | ||||
| @Serializable | ||||
| data class PackageManifest( | ||||
|     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) | ||||
| 
 | ||||
|     @Serializable | ||||
|     enum class Architecture { | ||||
|         X86_64, | ||||
|         ARM64, | ||||
|     } | ||||
|     @Serializable | ||||
|     data class BaseImage(val type: Type) { | ||||
|         @Serializable | ||||
|         enum class Type { | ||||
|             DEBIAN, | ||||
|             CHIMERA, | ||||
|         } | ||||
|     } | ||||
|     companion object { | ||||
|         val fileList = listOf("planterette.json", "icon.png", "image.tar") | ||||
|         val signatureFile = "package.sig" | ||||
|     } | ||||
| } | ||||
| @ -1,48 +0,0 @@ | ||||
| package app.hakurei.planterette.api | ||||
| 
 | ||||
| @Suppress("unused") | ||||
| object Paths { | ||||
|     const val FHS_ROOT = "/" | ||||
|     const val FHS_ETC = "/etc/" | ||||
|     const val FHS_TMP = "/tmp/" | ||||
| 
 | ||||
|     const val FHS_RUN = "/run/" | ||||
|     const val FHS_RUN_USER = FHS_RUN + "user/" | ||||
| 
 | ||||
|     const val FHS_USR = "/usr/" | ||||
|     const val FHS_USR_BIN = FHS_USR + "bin/" | ||||
| 
 | ||||
|     const val FHS_VAR = "/var/" | ||||
|     const val FHS_VAR_LIB = FHS_VAR + "lib/" | ||||
|     const val FHS_VAR_EMPTY = FHS_VAR + "empty/" | ||||
| 
 | ||||
|     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 = 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(FHS_ROOT) | ||||
|     val AbsFHSEtc = AbsolutePath(FHS_ETC) | ||||
|     val AbsFHSTmp = AbsolutePath(FHS_TMP) | ||||
| 
 | ||||
|     val AbsFHSRun = AbsolutePath(FHS_RUN) | ||||
|     val AbsFHSRunUser = AbsolutePath(FHS_RUN_USER) | ||||
| 
 | ||||
|     val AbsFHSUsrBin = AbsolutePath(FHS_USR_BIN) | ||||
| 
 | ||||
|     val AbsFHSVar = AbsolutePath(FHS_VAR) | ||||
|     val AbsFHSVarLib = AbsolutePath(FHS_VAR_LIB) | ||||
| 
 | ||||
|     val AbsFHSDev = AbsolutePath(FHS_DEV) | ||||
|     val AbsFHSProc = AbsolutePath(FHS_PROC) | ||||
|     val AbsFHSSys = AbsolutePath(FHS_SYS) | ||||
| 
 | ||||
|     val AbsNonexistent = AbsolutePath(NONEXISTENT) | ||||
| } | ||||
| 
 | ||||
| @ -1,9 +0,0 @@ | ||||
| package app.hakurei.planterette.api | ||||
| 
 | ||||
| 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 | ||||
| } | ||||
| @ -1,26 +0,0 @@ | ||||
| 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") | ||||
|     } | ||||
| } | ||||
| @ -1,333 +0,0 @@ | ||||
| package app.hakurei.planterette.api | ||||
| 
 | ||||
| import app.hakurei.planterette.api.HakureiConfig.Enablement.* | ||||
| 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 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() | ||||
|     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 json = format.encodeToString(testConfig) | ||||
|         val test = format.decodeFromString<HakureiConfig>(json) | ||||
|         assertEquals(testConfig, test) | ||||
|         val want = format.decodeFromString<HakureiConfig>(want) | ||||
| 
 | ||||
|         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) | ||||
|     } | ||||
| } | ||||
| @ -1,30 +0,0 @@ | ||||
| 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")) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,16 +0,0 @@ | ||||
| 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()}" | ||||
| } | ||||
| @ -1,22 +0,0 @@ | ||||
| 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")) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,22 +0,0 @@ | ||||
| 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")) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,22 +0,0 @@ | ||||
| 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")) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,18 +1,4 @@ | ||||
| dependencyResolutionManagement { | ||||
|     @Suppress("UnstableApiUsage") | ||||
|     repositories { | ||||
|         mavenCentral() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| plugins { | ||||
|     id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" | ||||
| } | ||||
| 
 | ||||
| rootProject.name = "planterette" | ||||
| 
 | ||||
| include("plt") | ||||
| include("libplt") | ||||
| include("plt-build") | ||||
| include("plt-fetch") | ||||
| include("plt-pkg") | ||||
							
								
								
									
										5
									
								
								src/main/go/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/main/go/main.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| package main | ||||
| 
 | ||||
| func main() { | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										10
									
								
								src/main/go/main_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/main/go/main_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 	_ "testing" | ||||
| ) | ||||
| 
 | ||||
| func TestHelloWorld(t *testing.T) { | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										2
									
								
								src/main/kotlin/moe/rosa/planterette/Main.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/main/kotlin/moe/rosa/planterette/Main.kt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| package moe.rosa.planterette | ||||
| 
 | ||||
| @ -0,0 +1,5 @@ | ||||
| package moe.rosa.planterette | ||||
| 
 | ||||
| import moe.rosa.planterette.hakurei.HakureiConfig | ||||
| 
 | ||||
| data class PlanteretteConfig(var hakurei: HakureiConfig?) | ||||
							
								
								
									
										12
									
								
								src/main/kotlin/moe/rosa/planterette/dsl/DSL.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/main/kotlin/moe/rosa/planterette/dsl/DSL.kt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| package moe.rosa.planterette.dsl | ||||
| 
 | ||||
| import moe.rosa.planterette.PlanteretteConfig | ||||
| 
 | ||||
| @Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) | ||||
| @DslMarker | ||||
| annotation class PlanteretteDSL | ||||
| 
 | ||||
| @PlanteretteDSL | ||||
| fun planterette(init: PlanteretteConfig.() -> Unit): PlanteretteConfig { | ||||
|     return PlanteretteConfig(hakurei = null).apply(init) | ||||
| } | ||||
							
								
								
									
										392
									
								
								src/main/kotlin/moe/rosa/planterette/dsl/HakureiDSL.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										392
									
								
								src/main/kotlin/moe/rosa/planterette/dsl/HakureiDSL.kt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,392 @@ | ||||
| package moe.rosa.planterette.dsl | ||||
| 
 | ||||
| import moe.rosa.planterette.PlanteretteConfig | ||||
| import moe.rosa.planterette.dsl.DSLEnablements.* | ||||
| import moe.rosa.planterette.hakurei.* | ||||
| 
 | ||||
| @Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) | ||||
| @PlanteretteDSL | ||||
| annotation class HakureiDSL | ||||
| 
 | ||||
| @Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) | ||||
| @HakureiDSL | ||||
| annotation class DBusDSL | ||||
| 
 | ||||
| @Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) | ||||
| @HakureiDSL | ||||
| annotation class ExtraPermsDSL | ||||
| 
 | ||||
| @Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) | ||||
| @HakureiDSL | ||||
| annotation class ContainerDSL | ||||
| 
 | ||||
| @Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) | ||||
| @ContainerDSL | ||||
| annotation class FilesystemDSL | ||||
| 
 | ||||
| @Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) | ||||
| @FilesystemDSL | ||||
| annotation class FSBindDSL | ||||
| 
 | ||||
| @Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) | ||||
| @FilesystemDSL | ||||
| annotation class FSEphemeralDSL | ||||
| 
 | ||||
| @Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) | ||||
| @FilesystemDSL | ||||
| annotation class FSLinkDSL | ||||
| 
 | ||||
| @Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) | ||||
| @FilesystemDSL | ||||
| annotation class FSOverlayDSL | ||||
| 
 | ||||
| @PlanteretteDSL | ||||
| fun PlanteretteConfig.hakurei(id: String, init: @HakureiDSL HakureiConfig.() -> Unit) { | ||||
|     this.hakurei = HakureiConfig(id).apply(init) | ||||
| } | ||||
| @HakureiDSL | ||||
| fun HakureiConfig.executable(path: String, vararg args: String) { | ||||
|     this.path = AbsolutePath(path) | ||||
|     this.args = args.toList() | ||||
| } | ||||
| @HakureiDSL | ||||
| enum class DSLEnablements { | ||||
|     Wayland, | ||||
|     X11, | ||||
|     DBus, | ||||
|     Pulse | ||||
| } | ||||
| @HakureiDSL | ||||
| fun HakureiConfig.enable(vararg enablements: DSLEnablements) { | ||||
|     val enable = Enablements(wayland = null, x11 = null, dbus = null, pulse = null) | ||||
|     enablements.map { | ||||
|         when(it) { | ||||
|             Wayland -> enable.wayland = true | ||||
|             X11 -> enable.x11 = true | ||||
|             DBus -> enable.dbus = true | ||||
|             Pulse -> enable.pulse = true | ||||
|         } | ||||
|     } | ||||
|     this.enablements = enable | ||||
| } | ||||
| @HakureiDSL | ||||
| fun HakureiConfig.directWayland(directWayland: Boolean = true) { | ||||
|     this.directWayland = directWayland | ||||
| } | ||||
| @HakureiDSL | ||||
| fun HakureiConfig.username(username: String) { | ||||
|     this.username = username | ||||
| } | ||||
| @HakureiDSL | ||||
| fun HakureiConfig.shell(shell: String) { | ||||
|     this.shell = AbsolutePath(shell) | ||||
| } | ||||
| @HakureiDSL | ||||
| fun HakureiConfig.home(home: String) { | ||||
|     this.home = AbsolutePath(home) | ||||
| } | ||||
| //TODO(mae) automatic identity? | ||||
| @HakureiDSL | ||||
| fun HakureiConfig.identity(identity: Int? = null) { | ||||
|     this.identity = identity | ||||
| } | ||||
| @HakureiDSL | ||||
| fun HakureiConfig.groups(vararg groups: String) { | ||||
|     this.groups = groups.toList() | ||||
| } | ||||
| data class DBusConfigs(var session: DBusConfig? = null, var system: DBusConfig? = null) | ||||
| 
 | ||||
| @HakureiDSL | ||||
| fun HakureiConfig.dbus(init: @DBusDSL DBusConfigs.() -> Unit) { | ||||
|     val dbus = DBusConfigs().apply(init) | ||||
|     this.sessionBus = dbus.session | ||||
|     this.systemBus = dbus.system | ||||
| } | ||||
| @DBusDSL | ||||
| fun DBusConfigs.session(init: @DBusDSL DBusConfig.() -> Unit) { | ||||
|     this.session = DBusConfig().apply(init) | ||||
| } | ||||
| @DBusDSL | ||||
| fun DBusConfigs.system(init: @DBusDSL DBusConfig.() -> Unit) { | ||||
|     this.system = DBusConfig().apply(init) | ||||
| } | ||||
| @DBusDSL | ||||
| fun DBusConfig.see(vararg see: String) { | ||||
|     this.see = see.toList() | ||||
| } | ||||
| @DBusDSL | ||||
| fun DBusConfig.talk(vararg talk: String) { | ||||
|     this.talk = talk.toList() | ||||
| } | ||||
| @DBusDSL | ||||
| fun DBusConfig.own(vararg own: String) { | ||||
|     this.own = own.toList() | ||||
| } | ||||
| @DBusDSL | ||||
| fun DBusConfig.call(vararg call: Pair<String, String>) { | ||||
|     this.call = call.toMap() | ||||
| } | ||||
| @DBusDSL | ||||
| fun DBusConfig.broadcast(vararg broadcast: Pair<String, String>) { | ||||
|     this.broadcast = broadcast.toMap() | ||||
| } | ||||
| @DBusDSL | ||||
| fun DBusConfig.log(log: Boolean = true) { | ||||
|     this.log = log | ||||
| } | ||||
| @DBusDSL | ||||
| fun DBusConfig.filter(filter: Boolean = true) { | ||||
|     this.filter = filter | ||||
| } | ||||
| @HakureiDSL | ||||
| fun HakureiConfig.extraPerms(vararg extraPerms: ExtraPermsConfig) { | ||||
|     this.extraPerms = extraPerms.toList() | ||||
| } | ||||
| @ExtraPermsDSL | ||||
| fun perm(path: String, init: ExtraPermsConfig.() -> Unit): ExtraPermsConfig { | ||||
|     return ExtraPermsConfig(path = AbsolutePath(path)).apply(init) | ||||
| } | ||||
| @ExtraPermsDSL | ||||
| fun perm(path: String, ensure: Boolean? = null, rwx: String): ExtraPermsConfig { | ||||
|     if(rwx.length != 3) throw IllegalArgumentException() | ||||
|     // TODO(mae): is there a difference between null and false in this case? | ||||
|     val read: Boolean? = when(rwx[0]) { | ||||
|         'r', 'R' -> true | ||||
|         else -> null | ||||
|     } | ||||
|     val write: Boolean? = when(rwx[1]) { | ||||
|         'w', 'W' -> true | ||||
|         else -> null | ||||
|     } | ||||
|     val execute: Boolean? = when(rwx[2]) { | ||||
|         'x', 'X' -> true | ||||
|         else -> null | ||||
|     } | ||||
|     return ExtraPermsConfig(ensure, path = AbsolutePath(path), read, write, execute) | ||||
| } | ||||
| @ExtraPermsDSL | ||||
| fun ExtraPermsConfig.ensure(ensure: Boolean = true) { | ||||
|     this.ensure = ensure | ||||
| } | ||||
| @ExtraPermsDSL | ||||
| fun ExtraPermsConfig.read(read: Boolean = true) { | ||||
|     this.read = read | ||||
| } | ||||
| @ExtraPermsDSL | ||||
| fun ExtraPermsConfig.write(write: Boolean = true) { | ||||
|     this.write = write | ||||
| } | ||||
| @ExtraPermsDSL | ||||
| fun ExtraPermsConfig.execute(execute: Boolean = true) { | ||||
|     this.execute = execute | ||||
| } | ||||
| 
 | ||||
| @HakureiDSL | ||||
| fun HakureiConfig.container(init: @ContainerDSL ContainerConfig.() -> Unit) { | ||||
|     this.container = ContainerConfig().apply(init) | ||||
| } | ||||
| @ContainerDSL | ||||
| fun ContainerConfig.hostname(hostname: String) { | ||||
|     this.hostname = hostname | ||||
| } | ||||
| @ContainerDSL | ||||
| fun ContainerConfig.waitDelay(waitDelay: Long) { | ||||
|     this.waitDelay = waitDelay | ||||
| } | ||||
| @ContainerDSL | ||||
| fun ContainerConfig.noTimeout() { | ||||
|     this.waitDelay = -1 | ||||
| } | ||||
| @ContainerDSL | ||||
| fun ContainerConfig.seccompCompat(seccompCompat: Boolean = true) { | ||||
|     this.seccompCompat = seccompCompat | ||||
| } | ||||
| @ContainerDSL | ||||
| fun ContainerConfig.devel(devel: Boolean = true) { | ||||
|     this.devel = devel | ||||
| } | ||||
| @ContainerDSL | ||||
| fun ContainerConfig.userns(userns: Boolean = true) { | ||||
|     this.userns = userns | ||||
| } | ||||
| @ContainerDSL | ||||
| fun ContainerConfig.hostNet(hostNet: Boolean = true) { | ||||
|     this.hostNet = hostNet | ||||
| } | ||||
| @ContainerDSL | ||||
| fun ContainerConfig.hostAbstract(hostAbstract: Boolean = true) { | ||||
|     this.hostAbstract = hostAbstract | ||||
| } | ||||
| @ContainerDSL | ||||
| fun ContainerConfig.tty(tty: Boolean = true) { | ||||
|     this.tty = tty | ||||
| } | ||||
| @ContainerDSL | ||||
| fun ContainerConfig.multiarch(multiarch: Boolean = true) { | ||||
|     this.multiarch = multiarch | ||||
| } | ||||
| @ContainerDSL | ||||
| fun ContainerConfig.env(vararg env: Pair<String, String>) { | ||||
|     this.env = env.toMap() | ||||
| } | ||||
| @ContainerDSL | ||||
| fun ContainerConfig.mapRealUid(mapRealUid: Boolean = true) { | ||||
|     this.mapRealUid = mapRealUid | ||||
| } | ||||
| @ContainerDSL | ||||
| fun ContainerConfig.device(device: Boolean = true) { | ||||
|     this.device = device | ||||
| } | ||||
| 
 | ||||
| @FilesystemDSL | ||||
| data class FilesystemConfigs(val configs: MutableList<FilesystemConfig> = mutableListOf()) | ||||
| 
 | ||||
| @ContainerDSL | ||||
| fun ContainerConfig.filesystem(init: @FilesystemDSL FilesystemConfigs.() -> Unit) { | ||||
|     val config = FilesystemConfigs().apply(init) | ||||
|     this.filesystem = config.configs | ||||
| } | ||||
| @FilesystemDSL | ||||
| data class DummyFSBind(var target: String? = null, | ||||
|                        var source: String? = null, | ||||
|                        var write: Boolean? = null, | ||||
|                        var device: Boolean? = null, | ||||
|                        var ensure: Boolean? = null, | ||||
|                        var optional: Boolean? = null, | ||||
|                        var special: Boolean? = null) { | ||||
|     fun build(): FSBind { | ||||
|         return FSBind( | ||||
|             target = if(target != null) { AbsolutePath(target!!) } else null, | ||||
|             source = AbsolutePath(source!!), | ||||
|             write = write, | ||||
|             device = device, | ||||
|             ensure = ensure, | ||||
|             optional = optional, | ||||
|             special = special | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @FilesystemDSL | ||||
| fun FilesystemConfigs.bind(src2dst: Pair<String, String>, init: @FSBindDSL DummyFSBind.() -> Unit = {}) { | ||||
|     val fs = DummyFSBind(target = src2dst.second, source = src2dst.first) | ||||
|     fs.apply(init) | ||||
|     this.configs.add(fs.build()) | ||||
| } | ||||
| @FilesystemDSL | ||||
| fun FilesystemConfigs.bind(source: String, init: @FSBindDSL DummyFSBind.() -> Unit = {}) { | ||||
|     val fs = DummyFSBind(source = source) | ||||
|     fs.apply(init) | ||||
|     this.configs.add(fs.build()) | ||||
| } | ||||
| @FSBindDSL | ||||
| fun DummyFSBind.write(write: Boolean? = true) { | ||||
|     this.write = write | ||||
| } | ||||
| @FSBindDSL | ||||
| fun DummyFSBind.device(device: Boolean? = true) { | ||||
|     this.device = device | ||||
| } | ||||
| @FSBindDSL | ||||
| fun DummyFSBind.ensure(ensure: Boolean? = true) { | ||||
|     this.ensure = ensure | ||||
| } | ||||
| @FSBindDSL | ||||
| fun DummyFSBind.optional(optional: Boolean? = true) { | ||||
|     this.optional = optional | ||||
| } | ||||
| @FSBindDSL | ||||
| fun DummyFSBind.special(special: Boolean? = true) { | ||||
|     this.special = special | ||||
| } | ||||
| @FilesystemDSL | ||||
| data class DummyFSEphemeral(val target: String? = null, | ||||
|                             var write: Boolean? = null, | ||||
|                             var size: Int? = null, | ||||
|                             var perm: Int? = null) { | ||||
|     fun build(): FSEphemeral { | ||||
|         return FSEphemeral( | ||||
|             target = AbsolutePath(target!!), | ||||
|             write = write!!, | ||||
|             size = size, | ||||
|             perm = perm!! | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @FSEphemeralDSL | ||||
| fun DummyFSEphemeral.write(write: Boolean = true) { | ||||
|     this.write = write | ||||
| } | ||||
| @FSEphemeralDSL | ||||
| fun DummyFSEphemeral.size(size: Int) { | ||||
|     this.size = size | ||||
| } | ||||
| @FSEphemeralDSL | ||||
| fun DummyFSEphemeral.perm(perm: Int) { | ||||
|     this.perm = perm | ||||
| } | ||||
| @FilesystemDSL | ||||
| fun FilesystemConfigs.ephemeral(target: String, init: @FSEphemeralDSL DummyFSEphemeral.() -> Unit = {}) { | ||||
|     val fs = DummyFSEphemeral(target = target) | ||||
|     fs.apply(init) | ||||
|     this.configs.add(fs.build()) | ||||
| } | ||||
| @FilesystemDSL | ||||
| data class DummyFSLink(val target: String? = null, | ||||
|                        val linkname: String? = null, | ||||
|                        var dereference: Boolean? = null) { | ||||
|     fun build(): FSLink { | ||||
|         return FSLink( | ||||
|             target = AbsolutePath(target!!), | ||||
|             linkname = linkname!!, | ||||
|             dereference = dereference!! | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @FSLinkDSL | ||||
| fun DummyFSLink.dereference(dereference: Boolean = true) { | ||||
|     this.dereference = dereference | ||||
| } | ||||
| @FilesystemDSL | ||||
| fun FilesystemConfigs.link(lnk2dst: Pair<String, String>, init: @FSLinkDSL DummyFSLink.() -> Unit = {}) { | ||||
|     val fs = DummyFSLink(target = lnk2dst.second, linkname = lnk2dst.first) | ||||
|     fs.apply(init) | ||||
|     this.configs.add(fs.build()) | ||||
| } | ||||
| @FilesystemDSL | ||||
| fun FilesystemConfigs.link(target: String, init: @FSLinkDSL DummyFSLink.() -> Unit = {}) { | ||||
|     val fs = DummyFSLink(target = target, linkname = target) | ||||
|     fs.apply(init) | ||||
|     this.configs.add(fs.build()) | ||||
| } | ||||
| @FilesystemDSL | ||||
| data class DummyFSOverlay(val target: String? = null, | ||||
|                           var lower: MutableList<String>? = mutableListOf(), | ||||
|                           var upper: String? = null, | ||||
|                           var work: String? = null) { | ||||
|     fun build(): FSOverlay { | ||||
|         return FSOverlay( | ||||
|             target = AbsolutePath(target!!), | ||||
|             lower = lower!!.map { AbsolutePath(it)}, | ||||
|             upper = AbsolutePath(upper!!), | ||||
|             work = AbsolutePath(work!!) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @FilesystemDSL | ||||
| fun FilesystemConfigs.overlay(target: String, init: @FSOverlayDSL DummyFSOverlay.() -> Unit = {}) { | ||||
|     val fs = DummyFSOverlay(target = target) | ||||
|     fs.apply(init) | ||||
|     this.configs.add(fs.build()) | ||||
| } | ||||
| @FSOverlayDSL | ||||
| fun DummyFSOverlay.lower(vararg lower: String) { | ||||
|     this.lower!!.addAll(lower.toList()) | ||||
| } | ||||
| @FSOverlayDSL | ||||
| fun DummyFSOverlay.upper(upper: String) { | ||||
|     this.upper = upper | ||||
| } | ||||
| @FSOverlayDSL | ||||
| fun DummyFSOverlay.work(work: String) { | ||||
|     this.work = work | ||||
| } | ||||
							
								
								
									
										85
									
								
								src/main/kotlin/moe/rosa/planterette/hakurei/Filesystem.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/main/kotlin/moe/rosa/planterette/hakurei/Filesystem.kt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | ||||
| package moe.rosa.planterette.hakurei | ||||
| 
 | ||||
| import kotlinx.serialization.* | ||||
| import kotlinx.serialization.descriptors.* | ||||
| import kotlinx.serialization.encoding.* | ||||
| 
 | ||||
| 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 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 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") | ||||
| 
 | ||||
| @Serializable sealed interface FilesystemConfig | ||||
| 
 | ||||
| @Serializable | ||||
| @SerialName("bind") | ||||
| data class FSBind( | ||||
|     @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( | ||||
|     @SerialName("dst") val target: AbsolutePath, | ||||
|     val write: Boolean, | ||||
|     val size: Int? = null, | ||||
|     val perm: Int, | ||||
| ) : FilesystemConfig | ||||
| 
 | ||||
| @Serializable | ||||
| @SerialName("link") | ||||
| data class FSLink( | ||||
|     @SerialName("dst") val target: AbsolutePath, | ||||
|     val linkname: String, | ||||
|     val dereference: Boolean, | ||||
| ) : FilesystemConfig | ||||
| 
 | ||||
| @Serializable | ||||
| @SerialName("overlay") | ||||
| data class FSOverlay( | ||||
|     @SerialName("dst") val target: AbsolutePath, | ||||
|     val lower: List<AbsolutePath>, | ||||
|     val upper: AbsolutePath, | ||||
|     val work: AbsolutePath, | ||||
| ) : FilesystemConfig | ||||
							
								
								
									
										91
									
								
								src/main/kotlin/moe/rosa/planterette/hakurei/Hakurei.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/main/kotlin/moe/rosa/planterette/hakurei/Hakurei.kt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,91 @@ | ||||
| package moe.rosa.planterette.hakurei | ||||
| 
 | ||||
| import kotlinx.serialization.* | ||||
| 
 | ||||
| @Serializable | ||||
| data class HakureiConfig( | ||||
|     var id: String? = null, | ||||
|     var path: AbsolutePath? = null, | ||||
|     var args: List<String>? = null, | ||||
|     var enablements: Enablements? = null, | ||||
|     @SerialName("session_bus") var sessionBus: DBusConfig? = null, | ||||
|     @SerialName("system_bus") var systemBus: DBusConfig? = null, | ||||
|     @SerialName("direct_wayland") var directWayland: Boolean? = null, | ||||
|     var username: String? = null, | ||||
|     var shell: AbsolutePath? = null, | ||||
|     var home: AbsolutePath? = null, | ||||
| 
 | ||||
|     @SerialName("extra_perms") var extraPerms: List<ExtraPermsConfig>? = null, | ||||
|     var identity: Int? = null, | ||||
|     var groups: List<String>? = null, | ||||
| 
 | ||||
|     var container: ContainerConfig? = null, | ||||
| ) | ||||
| 
 | ||||
| @Serializable | ||||
| data class ContainerConfig( | ||||
|     var hostname: String? = null, | ||||
|     @SerialName("wait_delay") var waitDelay: Long? = null, | ||||
|     @SerialName("seccomp_compat") var seccompCompat: Boolean? = null, | ||||
|     var devel: Boolean? = null, | ||||
|     var userns: Boolean? = null, | ||||
|     @SerialName("host_net") var hostNet: Boolean? = null, | ||||
|     @SerialName("host_abstract") var hostAbstract: Boolean? = null, | ||||
|     var tty: Boolean? = null, | ||||
|     var multiarch: Boolean? = null, | ||||
| 
 | ||||
|     var env: Map<String, String>? = null, | ||||
| 
 | ||||
|     @SerialName("map_real_uid") var mapRealUid: Boolean? = null, | ||||
|     var device: Boolean? = null, | ||||
| 
 | ||||
|     var filesystem: List<FilesystemConfig>? = null, | ||||
| ) | ||||
| 
 | ||||
| @Serializable | ||||
| data class ExtraPermsConfig( | ||||
|     var ensure: Boolean? = null, | ||||
|     var path: AbsolutePath, | ||||
|     @SerialName("r") var read: Boolean? = null, | ||||
|     @SerialName("w") var write: Boolean? = null, | ||||
|     @SerialName("x") var execute: Boolean? = null, | ||||
| ) { | ||||
|     override fun toString(): String { | ||||
|         val buffer = StringBuffer(5 + path.toString().length) | ||||
|         buffer.append("---") | ||||
|         if(ensure == true) { | ||||
|             buffer.append("+") | ||||
|         } | ||||
|         buffer.append(":") | ||||
|         buffer.append(path.toString()) | ||||
|         if(read == true) { | ||||
|             buffer.setCharAt(0, 'r') | ||||
|         } | ||||
|         if(write == true) { | ||||
|             buffer.setCharAt(1, 'w') | ||||
|         } | ||||
|         if(execute == true) { | ||||
|             buffer.setCharAt(2, 'x') | ||||
|         } | ||||
|         return buffer.toString() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @Serializable | ||||
| data class DBusConfig( | ||||
|     var see: List<String>? = null, | ||||
|     var talk: List<String>? = null, | ||||
|     var own: List<String>? = null, | ||||
|     var call: Map<String, String>? = null, | ||||
|     var broadcast: Map<String, String>? = null, | ||||
|     var log: Boolean? = null, | ||||
|     var filter: Boolean? = null, | ||||
| ) | ||||
| 
 | ||||
| @Serializable | ||||
| data class Enablements( | ||||
|     var wayland: Boolean? = null, | ||||
|     var x11: Boolean? = null, | ||||
|     var dbus: Boolean? = null, | ||||
|     var pulse: Boolean? = null, | ||||
| ) | ||||
							
								
								
									
										111
									
								
								src/test/kotlin/DSLTest.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/test/kotlin/DSLTest.kt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,111 @@ | ||||
| import moe.rosa.planterette.dsl.* | ||||
| import moe.rosa.planterette.dsl.DSLEnablements.* | ||||
| import kotlin.test.Test | ||||
| import kotlin.test.assertEquals | ||||
| 
 | ||||
| class DSLTest { | ||||
|     companion object { | ||||
|         val HAKUREI_DSL_TEST = planterette { | ||||
|             hakurei("org.chromium.Chromium") { | ||||
|                 executable("/run/current-system/sw/bin/chromium", | ||||
|                     "chromium", | ||||
|                     "--ignore-gpu-blocklist", | ||||
|                     "--disable-smooth-scrolling", | ||||
|                     "--enable-features=UseOzonePlatform", | ||||
|                     "--ozone-platform=wayland" | ||||
|                 ) | ||||
|                 enable(Wayland, DBus, Pulse) | ||||
|                 dbus { | ||||
|                     session { | ||||
|                         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.*" to "*") | ||||
|                         broadcast("org.freedesktop.portal.*" to "@/org/freedesktop/portal/*") | ||||
|                         filter() | ||||
|                     } | ||||
|                     system { | ||||
|                         talk("org.bluez", | ||||
|                             "org.freedesktop.Avahi", | ||||
|                             "org.freedesktop.UPower") | ||||
|                         filter() | ||||
|                     } | ||||
|                 } | ||||
|                 username("chronos") | ||||
|                 shell("/run/current-system/sw/bin/zsh") | ||||
|                 home("/data/data/org.chromium.Chromium") | ||||
|                 extraPerms( | ||||
|                     perm("/var/lib/hakurei/u0") { | ||||
|                         ensure() | ||||
|                         execute() | ||||
|                     }, | ||||
|                     perm("/var/lib/hakurei/u0/org.chromium.Chromium", rwx = "rwx") | ||||
|                 ) | ||||
|                 identity(9) | ||||
|                 groups("video", | ||||
|                     "dialout", | ||||
|                     "plugdev") | ||||
|                 container { | ||||
|                     hostname("localhost") | ||||
|                     noTimeout() | ||||
|                     seccompCompat() | ||||
|                     devel() | ||||
|                     userns() | ||||
|                     hostNet() | ||||
|                     hostAbstract() | ||||
|                     tty() | ||||
|                     multiarch() | ||||
|                     env("GOOGLE_API_KEY" to "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY", | ||||
|                         "GOOGLE_DEFAULT_CLIENT_ID" to "77185425430.apps.googleusercontent.com", | ||||
|                         "GOOGLE_DEFAULT_CLIENT_SECRET" to "OTJgUOQcT7lO7GsGZq2G4IlT") | ||||
|                     mapRealUid() | ||||
|                     device() | ||||
|                     filesystem { | ||||
|                         bind("/var/lib/hakurei/base/org.debian" to "/") { | ||||
|                             write() | ||||
|                             special() | ||||
|                         } | ||||
|                         bind("/etc/" to "/etc/") { | ||||
|                             special() | ||||
|                         } | ||||
|                         ephemeral("/tmp/") { | ||||
|                             write() | ||||
|                             perm(493) | ||||
|                         } | ||||
|                         overlay("/nix/store") { | ||||
|                             lower("/mnt-root/nix/.ro-store") | ||||
|                             upper("/mnt-root/nix/.rw-store/upper") | ||||
|                             work("/mnt-root/nix/.rw-store/work") | ||||
|                         } | ||||
|                         bind("/nix/store") | ||||
|                         link("/run/current-system") { | ||||
|                             dereference() | ||||
|                         } | ||||
|                         link("/run/opengl-driver") { | ||||
|                             dereference() | ||||
|                         } | ||||
|                         bind("/var/lib/hakurei/u0/org.chromium.Chromium" to "/data/data/org.chromium.Chromium") { | ||||
|                             write() | ||||
|                             ensure() | ||||
|                         } | ||||
|                         bind("/dev/dri") { | ||||
|                             device() | ||||
|                             optional() | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     @Test | ||||
|     fun hakureiDSLTest() { | ||||
|         assertEquals(HakureiTest.TEMPLATE_DATA, HAKUREI_DSL_TEST.hakurei) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										194
									
								
								src/test/kotlin/HakureiTest.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								src/test/kotlin/HakureiTest.kt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,194 @@ | ||||
| import kotlinx.serialization.ExperimentalSerializationApi | ||||
| import kotlinx.serialization.json.Json | ||||
| import moe.rosa.planterette.hakurei.* | ||||
| import org.junit.jupiter.api.assertDoesNotThrow | ||||
| import kotlin.test.* | ||||
| 
 | ||||
| class HakureiTest { | ||||
|     companion object { | ||||
|         val TEMPLATE_DATA = 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 TEMPLATE_JSON = ProcessBuilder("hakurei", "template") | ||||
|             .start() | ||||
|             .inputStream | ||||
|             .readAllBytes() | ||||
|             .toString(Charsets.UTF_8) | ||||
|         val format = Json { | ||||
|             prettyPrint = true | ||||
|             ignoreUnknownKeys = true | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @OptIn(ExperimentalSerializationApi::class) | ||||
|     @Test | ||||
|     fun deserializeTest() { | ||||
| 
 | ||||
|         println(TEMPLATE_JSON) | ||||
|         val want = format.decodeFromString<HakureiConfig>(TEMPLATE_JSON) | ||||
|         assertEquals(TEMPLATE_DATA, want) | ||||
|     } | ||||
|     @OptIn(ExperimentalSerializationApi::class) | ||||
|     @Test | ||||
|     fun serializeTest() { | ||||
|         val encoded = format.encodeToString(TEMPLATE_DATA) | ||||
|         val decoded = format.decodeFromString<HakureiConfig>(encoded) | ||||
|         assertEquals(TEMPLATE_DATA, decoded) | ||||
|     } | ||||
|     @Test | ||||
|     fun absolutePathTest() { | ||||
|         assertDoesNotThrow { | ||||
|             AbsolutePath("/test/absolutepath") | ||||
|         } | ||||
|         assertFailsWith(AbsolutePathException::class) { | ||||
|             AbsolutePath("./../../../../") | ||||
|         } | ||||
|         assertEquals(AbsolutePath("/test/absolutepath"), AbsolutePath("/test/") + "absolutepath") | ||||
|     } | ||||
|     @Test | ||||
|     fun extraPermsTest() { | ||||
|         assertIs<String>(TEMPLATE_DATA.extraPerms.toString()) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										0
									
								
								src/test/resources/ChromiumExample.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/test/resources/ChromiumExample.kts
									
									
									
									
									
										Normal file
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user