Compare commits

...

30 Commits

Author SHA1 Message Date
mae
aa3c3377d0 insert native lib into jar 2025-11-16 20:34:38 -06:00
mae
96bb989ad5 write to fd 2025-11-16 15:09:29 -06:00
mae
98950f21f3 write partial kotlin dsl 2025-11-15 21:53:36 -06:00
mae
7110fdb53e delete outdated kotlin source 2025-11-15 19:20:30 -06:00
mae
d52447dc23 write go manifest representation 2025-11-15 19:18:46 -06:00
b703b09f76 Merge pull request 'Convert to Go build system' (#2) from goification into main
Reviewed-on: #2
2025-11-16 07:26:08 +09:00
mae
404b2b66b7 adjust gitignore and add gradle wrapper 2025-11-15 13:37:11 -06:00
mae
df9d8b2482 add hakurei dependency 2025-11-15 13:27:01 -06:00
mae
7e2b93560f rename tests 2025-11-15 13:06:40 -06:00
mae
d0431ba1ed write plt-build test wrapper 2025-11-15 13:03:00 -06:00
mae
032f2ca7dc restructure to go structure 2025-11-15 12:29:04 -06:00
mae
8bd952a4c8 remove gradle stuff 2025-11-15 12:08:09 -06:00
mae
8bb386ebf7 bump hakurei version, add comments to api 2025-10-11 13:46:18 -05:00
mae
1dc17ac487 more build improvements 2025-10-09 00:16:50 -05:00
mae
ec73bdabfa elixir is kil 2025-10-08 23:16:14 -05:00
mae
e58cc0a1a6 add all modules, better gradle setup, and elixir for server 2025-10-07 19:34:23 -05:00
3efcca8651 Merge pull request 'kt-go-split' (#1) from kt-go-split into main
Reviewed-on: #1
2025-10-07 11:52:11 +09:00
mae
df5e191582 add minimal go setup 2025-10-06 21:49:44 -05:00
mae
21738818d5 write hakurei dsl 2025-10-04 22:11:40 -05:00
mae
9215adc740 Merge remote-tracking branch 'origin/kt-go-split' into kt-go-split 2025-09-28 21:02:27 -05:00
mae
a2e8249003 fix test 2025-09-28 21:02:18 -05:00
mae
0d8b0eaecd fix test 2025-09-28 21:01:15 -05:00
mae
d1ccb1c762 finish up Hakurei.kt, add HakureiTest.kt 2025-09-28 20:08:11 -05:00
mae
bc1c7172d4 write hakurei config representation 2025-09-27 03:33:23 -05:00
mae
b57edb8d3a delete basically everything lol 2025-09-26 23:54:15 -05:00
mae
bc08a46f28 Merge remote-tracking branch 'origin/main' 2025-08-26 23:41:41 -05:00
2e3a9a3aea update to java 24
Signed-off-by: mae <mae@maestoso.online>
2025-08-26 23:41:21 -05:00
mae
d7193b6a5f update to java 24 2025-08-26 23:18:42 -05:00
f369e5f6c5 fix gitignore 2025-08-12 18:47:47 -05:00
d3da1c57f8 additional tests 2025-08-12 18:42:01 -05:00
56 changed files with 709 additions and 956 deletions

19
.gitignore vendored
View File

@@ -1,14 +1,11 @@
.gradle ### Gradle/Java ###
build/ **/.gradle
!gradle/wrapper/gradle-wrapper.jar **/build/
!**/src/main/**/build/ !**/src/main/**/build/
!**/src/test/**/build/ !**/src/test/**/build/
### IntelliJ IDEA ### ### IntelliJ IDEA ###
.idea/modules.xml **/.idea
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws *.iws
*.iml *.iml
*.ipr *.ipr
@@ -43,5 +40,9 @@ bin/
### Mac OS ### ### Mac OS ###
.DS_Store .DS_Store
/app/src/test/resources/debian-images/
/app/src/test/resources/squashfs-root/ ### Go ###
go.sum
### Build ###
/hakureiUpdate.sh

5
.idea/.gitignore generated vendored
View File

@@ -1,5 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Environment-dependent path to Maven home directory
/mavenHomeManager.xml

View File

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

View File

@@ -1,8 +0,0 @@
<component name="ProjectDictionaryState">
<dictionary name="project">
<words>
<w>buildsrc</w>
<w>planterette</w>
</words>
</dictionary>
</component>

34
.idea/gradle.xml generated
View File

@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<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>
</option>
</component>
</project>

12
.idea/kotlinc.xml generated
View File

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

7
.idea/misc.xml generated
View File

@@ -1,7 +0,0 @@
<?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">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@@ -1,23 +1,2 @@
# planterette # 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`).

79
api/capability.go Normal file
View File

@@ -0,0 +1,79 @@
package api
import (
"encoding/json"
"fmt"
)
type Capability interface {
ID() string
}
type CapabilityJSON struct {
Capability
}
type BasicCapability struct {
Id string `json:"id"`
}
func (c BasicCapability) ID() string {
return c.Id
}
const CapabilityBasic string = "basic"
type DBusCapability struct {
Id string `json:"id"`
Own []string `json:"own,omitempty"`
}
func (c DBusCapability) ID() string {
return c.Id
}
const CapabilityDBus = "dbus"
type capabilityType struct {
Type string `json:"type"`
}
func (c *CapabilityJSON) MarshalJSON() ([]byte, error) {
if c == nil || c.Capability == nil {
return nil, fmt.Errorf("invalid capability")
}
var v any
switch cv := c.Capability.(type) {
case *BasicCapability:
v = &struct {
capabilityType
*BasicCapability
}{capabilityType{CapabilityBasic}, cv}
case *DBusCapability:
v = &struct {
capabilityType
*DBusCapability
}{capabilityType{CapabilityDBus}, cv}
default:
return nil, fmt.Errorf("invalid capability")
}
return json.Marshal(v)
}
func (c *CapabilityJSON) UnmarshalJSON(data []byte) error {
t := new(capabilityType)
if err := json.Unmarshal(data, &t); err != nil {
return err
}
switch t.Type {
case CapabilityBasic:
*c = CapabilityJSON{new(BasicCapability)}
case CapabilityDBus:
*c = CapabilityJSON{new(DBusCapability)}
default:
return fmt.Errorf("invalid capability")
}
return json.Unmarshal(data, c.Capability)
}

25
api/manifest.go Normal file
View File

@@ -0,0 +1,25 @@
package api
import "hakurei.app/container/check"
type Manifest struct {
Metadata Metadata `json:"metadata"`
Executable Executable `json:"executable"`
Capabilities []CapabilityJSON `json:"capabilities"`
Permissions []CapabilityJSON `json:"permissions"`
}
type Metadata struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Author string `json:"author"`
Icon *check.Absolute `json:"icon"`
Version int `json:"version"`
VersionName string `json:"version_name"`
}
type Executable struct {
BaseImage string `json:"base_image"`
Binary *check.Absolute `json:"binary"`
Args []string `json:"args"`
Env map[string]string `json:"env"`
}

View File

@@ -1,11 +0,0 @@
plugins {
`kotlin-dsl`
}
kotlin {
jvmToolchain(21)
}
dependencies {
implementation(libs.kotlinGradlePlugin)
}

View File

@@ -1,15 +0,0 @@
dependencyResolutionManagement {
@Suppress("UnstableApiUsage")
repositories {
mavenCentral()
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
rootProject.name = "buildSrc"

View File

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

View File

@@ -0,0 +1,5 @@
package main
func main() {
}

View File

@@ -0,0 +1,9 @@
package main
import (
"testing"
)
func TestPltBuildWrapper(t *testing.T) {
}

View File

@@ -0,0 +1,52 @@
plugins {
kotlin("jvm") version "2.2.10"
kotlin("plugin.serialization") version "2.2.20"
}
group = "moe.rosa"
version = "0.1.0"
repositories {
mavenCentral()
}
val kotlinVersion = "2.2.10"
val kotlinCoroutinesVersion = "1.7.0-RC"
dependencies {
testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testImplementation(kotlin("test"))
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
implementation(kotlin("reflect"))
implementation("org.jetbrains.kotlin:kotlin-scripting-common:${kotlinVersion}")
implementation("org.jetbrains.kotlin:kotlin-scripting-jvm:${kotlinVersion}")
implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies:${kotlinVersion}")
implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies-maven:${kotlinVersion}")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${kotlinCoroutinesVersion}")
}
sourceSets.main {
resources {
srcDirs("src/main/resources", "build/natives")
}
}
kotlin {
jvmToolchain(24)
}
tasks.test {
useJUnitPlatform()
}
tasks.register<Exec>("compileNativeLib") {
val java_home = System.getProperty("java.home")
environment("CGO_CFLAGS" to "-I$java_home/include -I$java_home/include/linux")
workingDir("src/main/go")
commandLine("go", "build", "-linkshared", "-buildmode=c-shared", "-o", layout.buildDirectory.get().dir("natives").file("pltbuild.so"))
}
tasks.processResources {
dependsOn += "compileNativeLib"
}

View File

@@ -0,0 +1 @@
kotlin.code.style=official

Binary file not shown.

View File

@@ -0,0 +1,6 @@
#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
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -15,8 +15,6 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@@ -57,7 +55,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (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. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -82,11 +80,13 @@ do
esac esac
done done
# This is normally unused APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# shellcheck disable=SC2034
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/} 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. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@@ -133,29 +133,22 @@ location of your Java installation."
fi fi
else else
JAVACMD=java JAVACMD=java
if ! command -v java >/dev/null 2>&1 which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
then
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 Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) 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 ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
case $MAX_FD in #( case $MAX_FD in #(
'' | soft) :;; #( '' | 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" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
@@ -200,15 +193,11 @@ if "$cygwin" || "$msys" ; then
done done
fi fi
# Collect all arguments for the java command;
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# Collect all arguments for the java command: # * put everything else in single quotes, so that it's not re-expanded.
# * 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.
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
@@ -216,12 +205,6 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \ 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. # Use "xargs" to parse quoted args.
# #
# With -n1 it outputs one arg per line, with the quotes and backslashes removed. # With -n1 it outputs one arg per line, with the quotes and backslashes removed.

2
cmd/plt-build/main.go Normal file
View File

@@ -0,0 +1,2 @@
//go:generate ./gradlew build
package plt_build

View File

@@ -0,0 +1,19 @@
package plt_build
import (
"os"
"os/exec"
"testing"
"time"
)
func TestPltBuild(t *testing.T) {
cmd := exec.CommandContext(t.Context(), "./gradlew", "test")
cmd.WaitDelay = 100 * time.Millisecond
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
err := cmd.Run()
if err != nil {
t.Error(err)
}
}

View File

@@ -0,0 +1 @@
rootProject.name = "plt-build"

View File

@@ -0,0 +1,37 @@
package main
//#include "pltbuild.h"
import "C"
import (
"errors"
"os"
"strconv"
"syscall"
)
//export planterette_write
func planterette_write(
fd C.int,
str_p *C.char, str_sz C.size_t,
errno_p *C.uintptr_t,
err_str_p **C.char,
) {
f := os.NewFile(uintptr(fd), strconv.Itoa(int(fd)))
defer f.Close()
_, err := f.WriteString(C.GoStringN(str_p, C.int(str_sz)))
if err == nil {
return
}
var pathError *os.PathError
if errors.As(err, &pathError) {
var errno syscall.Errno
if errors.As(pathError.Err, &errno) {
*errno_p = C.uintptr_t(errno)
return
}
}
*err_str_p = C.CString(err.Error())
return
}

View File

@@ -0,0 +1,26 @@
#include "pltbuild.h"
jint throwIOException( JNIEnv *env, char *message) {
jclass exClass;
char *className = "java/io/IOException";
exClass = (*env)->FindClass(env, className);
return (*env)->ThrowNew(env, exClass, message);
}
JNIEXPORT void JNICALL Java_moe_rosa_planterette_jni_GoFile_write__ILnet_java_String_2(JNIEnv *env, jobject obj, jint fd, jstring str) {
char *s = (*env)->GetStringUTFChars(env, str, 0);
size_t sz = (*env)->GetStringUTFLength(env, str);
uintptr_t *errno_p = NULL;
char **err_str_p = NULL;
planterette_write(fd, s, sz, errno_p, err_str_p);
if(errno_p || *err_str_p ) {
throwIOException(env, **err_str_p ? *err_str_p : strerror(*errno_p));
}
(*env)->ReleaseStringUTFChars(env, str, s);
free(err_str_p);
}

View File

@@ -0,0 +1,17 @@
#include <jni.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#ifndef _PLTBUILD_H
#define _PLTBUILD_H
/*
* moe/rosa/planterette/jni/GoFile#write(ILnet/java/String;)V
*/
JNIEXPORT void JNICALL Java_moe_rosa_planterette_jni_GoFile_write__ILnet_java_String_2(JNIEnv *, jobject, jint, jstring);
extern void planterette_write(int fd, char *str_p, size_t str_sz, uintptr_t *errno_p, char **err_str_p);
#endif

View File

@@ -0,0 +1,16 @@
package moe.rosa.planterette.api
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
sealed interface Capability {
val id: String
@SerialName("basic")
data class Basic(override val id: String) : Capability
@SerialName("dbus")
data class DBus(override val id: String, val own: List<String>) : Capability
}

View File

@@ -0,0 +1,36 @@
package moe.rosa.planterette.api
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class Manifest(
val metadata: Metadata,
val executable: Executable,
val capabilities: List<Capability>,
val permissions: List<Capability>
) {
@Serializable
data class Metadata(
var id: String,
var name: String,
var description: String,
var author: String,
var icon: String,
var version: Int,
@SerialName("version_name") var versionName: String
) {
constructor() : this("", "", "", "", "", -1, "")
}
@Serializable
data class Executable(
@SerialName("base_image") var baseImage: String,
var binary: String,
val args: MutableList<String>,
val env: MutableMap<String, String>
) {
constructor() : this("", "", mutableListOf(), mutableMapOf())
}
}

View File

@@ -0,0 +1,227 @@
@file:Suppress("unused")
package moe.rosa.planterette.dsl
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import moe.rosa.planterette.api.Capability
import moe.rosa.planterette.api.Manifest
import moe.rosa.planterette.jni.GoFile
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@DslMarker
annotation class PlanteretteBlockMarker
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@PlanteretteBlockMarker
annotation class MetadataBlockMarker
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@PlanteretteBlockMarker
annotation class ExecutableBlockMarker
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@PlanteretteBlockMarker
annotation class CapabilitiesBlockMarker
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@CapabilitiesBlockMarker
annotation class DBusBlockMarker
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@PlanteretteBlockMarker
annotation class PermissionsBlockMarker
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@PermissionsBlockMarker
annotation class FilesystemBlockMarker
@PlanteretteBlockMarker
data class PlanteretteBlock(
var metadata: MetadataBlock,
var executable: ExecutableBlock,
var capabilities: CapabilitiesBlock,
var permissions: PermissionsBlock,
var buildGuard: Boolean = false
) {
// NOTE the two underscores are because this function is only intended to be added at runtime
@OptIn(ExperimentalSerializationApi::class)
@Suppress("FunctionName")
fun __build(args: Array<out String>): PlanteretteBlock {
if(!buildGuard) {
val manifest = Manifest(metadata.metadata, executable.executable, capabilities.capabilities, permissions.permissions)
GoFile.write(args[0].toInt(), Json.encodeToString(manifest))
buildGuard = true
}
return this;
}
}
@MetadataBlockMarker
data class MetadataBlock(var metadata: Manifest.Metadata)
@ExecutableBlockMarker
data class ExecutableBlock(var executable: Manifest.Executable)
@CapabilitiesBlockMarker
data class CapabilitiesBlock(var capabilities: MutableList<Capability>)
// TODO add the rest of this
@DBusBlockMarker
data class DBusBlock(var own: MutableList<String>)
@PermissionsBlockMarker
data class PermissionsBlock(var permissions: MutableList<Capability>)
@FilesystemBlockMarker
class FilesystemBlock(var filesystemPermissions: MutableList<Capability>)
@PlanteretteBlockMarker
fun planterette(init: PlanteretteBlock.() -> Unit) {
PlanteretteBlock(MetadataBlock(Manifest.Metadata()), ExecutableBlock(Manifest.Executable()), CapabilitiesBlock(mutableListOf()), PermissionsBlock(mutableListOf())).apply(init)
}
@PlanteretteBlockMarker
fun PlanteretteBlock.metadata(init: @MetadataBlockMarker MetadataBlock.() -> Unit) {
this.metadata.apply(init)
}
@MetadataBlockMarker
fun MetadataBlock.id(id: String) {
this.metadata.id = id
}
@MetadataBlockMarker
fun MetadataBlock.name(name: String) {
this.metadata.name = name
}
@MetadataBlockMarker
fun MetadataBlock.description(description: String) {
this.metadata.description = description
}
@MetadataBlockMarker
fun MetadataBlock.author(author: String) {
this.metadata.author = author
}
@MetadataBlockMarker
fun MetadataBlock.icon(icon: String) {
this.metadata.icon = icon
}
@MetadataBlockMarker
fun MetadataBlock.version(number: Int, name: String) {
this.metadata.version = number
this.metadata.versionName = name
}
@PlanteretteBlockMarker
fun PlanteretteBlock.executable(init: @ExecutableBlockMarker ExecutableBlock.() -> Unit) {
this.executable.apply(init)
}
@ExecutableBlockMarker
fun ExecutableBlock.baseImage(baseImage: String) {
this.executable.baseImage = baseImage
}
@ExecutableBlockMarker
fun ExecutableBlock.binary(binary: String) {
this.executable.binary = binary
}
@ExecutableBlockMarker
fun ExecutableBlock.args(vararg args: String) {
this.executable.args.addAll(args)
}
@ExecutableBlockMarker
fun ExecutableBlock.env(vararg env: Pair<String, String>) {
this.executable.env.putAll(env)
}
@PlanteretteBlockMarker
fun PlanteretteBlock.capabilities(init: @CapabilitiesBlockMarker CapabilitiesBlock.() -> Unit) {
this.capabilities.apply(init)
}
internal fun CapabilitiesBlock.addBasic(id: String) {
this.capabilities.add(Capability.Basic(id))
}
@CapabilitiesBlockMarker
fun CapabilitiesBlock.portal() {
addBasic("moe.rosa.capabilities.Portal")
}
@CapabilitiesBlockMarker
fun CapabilitiesBlock.secrets() {
addBasic("moe.rosa.capabilities.Secrets")
}
@CapabilitiesBlockMarker
fun CapabilitiesBlock.developer() {
addBasic("moe.rosa.capabilities.Developer")
}
@CapabilitiesBlockMarker
fun CapabilitiesBlock.graphics() {
addBasic("moe.rosa.capabilities.Graphics")
}
@CapabilitiesBlockMarker
fun CapabilitiesBlock.audio() {
addBasic("moe.rosa.capabilities.Audio")
}
@CapabilitiesBlockMarker
fun CapabilitiesBlock.dbus(init: @DBusBlockMarker DBusBlock.() -> Unit) {
val dbus = DBusBlock(mutableListOf())
this.capabilities.add(Capability.DBus("moe.rosa.capabilities.DBus", dbus.own))
}
@PlanteretteBlockMarker
fun PlanteretteBlock.permissions(init: @PermissionsBlockMarker PermissionsBlock.() -> Unit) {
this.permissions.apply(init)
}
internal fun PermissionsBlock.addBasic(id: String) {
this.permissions.add(Capability.Basic(id))
}
@PermissionsBlockMarker
fun PermissionsBlock.notifications() {
addBasic("moe.rosa.permissions.Notifications")
}
@PermissionsBlockMarker
fun PermissionsBlock.filesystem(init: @FilesystemBlockMarker FilesystemBlock.() -> Unit) {
this.permissions.addAll(FilesystemBlock(mutableListOf()).apply(init).filesystemPermissions)
}
@FilesystemBlockMarker
fun FilesystemBlock.fileManager() {
this.filesystemPermissions.add(Capability.Basic("moe.rosa.permissions.filesystem.FileManager"))
}
@FilesystemBlockMarker
fun FilesystemBlock.etc() {
this.filesystemPermissions.add(Capability.Basic("moe.rosa.permissions.filesystem.Etc"))
}
@FilesystemBlockMarker
fun FilesystemBlock.tmp() {
this.filesystemPermissions.add(Capability.Basic("moe.rosa.permissions.filesystem.Tmp"))
}
@PermissionsBlockMarker
fun PermissionsBlock.network() {
addBasic("moe.rosa.permissions.Network")
}
@PermissionsBlockMarker
fun PermissionsBlock.system() {
addBasic("moe.rosa.permissions.System")
}

View File

@@ -0,0 +1,48 @@
package moe.rosa.planterette.dsl
import kotlinx.coroutines.runBlocking
import moe.rosa.planterette.api.Capability
import moe.rosa.planterette.api.Manifest
import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.api.*
import kotlin.script.experimental.dependencies.*
import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.dependenciesFromCurrentContext
import kotlin.script.experimental.jvm.jvm
@KotlinScript(
fileExtension = "plt.kts",
compilationConfiguration = ScriptWithMavenDepsConfiguration::class
)
open class PltRecipe
object ScriptWithMavenDepsConfiguration : ScriptCompilationConfiguration(
{
defaultImports(DependsOn::class, Repository::class)
defaultImports.append("moe.rosa.planterette.dsl.*")
jvm {
dependenciesFromCurrentContext(
"script",
"kotlin-scripting-dependencies"
)
}
refineConfiguration {
onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations)
}
}
)
private val resolver = CompoundDependenciesResolver(FileSystemDependenciesResolver(), MavenDependenciesResolver())
fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics<ScriptCompilationConfiguration> {
val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations)?.takeIf { it.isNotEmpty() }
?: return context.compilationConfiguration.asSuccess()
return runBlocking {
resolver.resolveFromScriptSourceAnnotations(annotations)
}.onSuccess {
context.compilationConfiguration.with {
dependencies.append(JvmDependency(it))
}.asSuccess()
}
}

View File

@@ -0,0 +1,19 @@
package moe.rosa.planterette.jni
import java.io.File
import java.nio.file.Files
object GoFile {
external fun write(fd: Int, str: String)
init {
val libname = "pltbuild.so"
val url = GoFile::class.java.getResource("/$libname")
val tmpDir = Files.createTempDirectory("pltbuild").toFile()
tmpDir.deleteOnExit()
val nativeLibTmpFile = File(tmpDir, libname)
url?.openStream().use {
Files.copy(it!!, nativeLibTmpFile.toPath())
}
System.load(nativeLibTmpFile.absolutePath)
}
}

5
cmd/plt-fetch/main.go Normal file
View File

@@ -0,0 +1,5 @@
package main
func main() {
}

View File

@@ -0,0 +1,9 @@
package main
import (
"testing"
)
func TestPltFetch(t *testing.T) {
}

5
cmd/plt-pkg/main.go Normal file
View File

@@ -0,0 +1,5 @@
package main
func main() {
}

9
cmd/plt-pkg/main_test.go Normal file
View File

@@ -0,0 +1,9 @@
package main
import (
"testing"
)
func TestPltPkg(t *testing.T) {
}

5
cmd/plt-server/main.go Normal file
View File

@@ -0,0 +1,5 @@
package main
func main() {
}

View File

@@ -0,0 +1,9 @@
package main
import (
"testing"
)
func TestPltServer(t *testing.T) {
}

5
cmd/plt-updated/main.go Normal file
View File

@@ -0,0 +1,5 @@
package main
func main() {
}

View File

@@ -0,0 +1,9 @@
package main
import (
"testing"
)
func TestPltUpdated(t *testing.T) {
}

5
go.mod Normal file
View File

@@ -0,0 +1,5 @@
module rosa.moe/planterette
go 1.24.9
require hakurei.app v0.3.1 // indirect

View File

@@ -1,3 +0,0 @@
org.gradle.caching=true
org.gradle.configuration-cache=true
kotlin.mpp.applyDefaultHierarchyTemplate=false

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,47 +0,0 @@
package app.hakurei.planterette.api
object Paths {
const val FHSRoot = "/"
const val FHSEtc = "/etc/"
const val FHSTmp = "/tmp/"
const val FHSRun = "/run/"
const val FHSRunUser = FHSRun + "user/"
const val FHSUsr = "/usr/"
const val FHSUsrBin = FHSUsr + "bin/"
const val FHSVar = "/var/"
const val FHSVarLib = FHSVar + "lib/"
const val FHSVarEmpty = FHSVar + "empty/"
const val FHSDev = "/dev/"
const val FHSProc = "/proc/"
const val FHSProcSys = FHSProc + "sys/"
const val FHSSys = "/sys/"
const val Nonexistent = FHSProc + "nonexistent"
const val sysrootDir = "sysroot"
const val hostDir = "host"
const val hostPath = FHSRoot + hostDir
const val sysrootPath = FHSRoot + sysrootDir
val AbsFHSRoot = AbsolutePath(FHSRoot)
val AbsFHSEtc = AbsolutePath(FHSEtc)
val AbsFHSTmp = AbsolutePath(FHSTmp)
val AbsFHSRun = AbsolutePath(FHSRun)
val AbsFHSRunUser = AbsolutePath(FHSRunUser)
val AbsFHSUsrBin = AbsolutePath(FHSUsrBin)
val AbsFHSVar = AbsolutePath(FHSVar)
val AbsFHSVarLib = AbsolutePath(FHSVarLib)
val AbsFHSDev = AbsolutePath(FHSDev)
val AbsFHSProc = AbsolutePath(FHSProc)
val AbsFHSSys = AbsolutePath(FHSSys)
val AbsNonexistent = AbsolutePath(Nonexistent)
}

View File

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

View File

@@ -1,230 +0,0 @@
package app.hakurei.planterette.api
import app.hakurei.planterette.api.HakureiConfig.Enablement.*
import app.hakurei.planterette.api.HakureiConfig.RootFlag
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.serializer
import kotlin.test.Test
import kotlin.test.assertEquals
class HakureiConfigTest {
@OptIn(ExperimentalSerializationApi::class)
val format = Json {
explicitNulls = false
prettyPrint = true
}
val want: String = """
{
"id": "org.chromium.Chromium",
"path": "/run/current-system/sw/bin/chromium",
"args": [
"chromium",
"--ignore-gpu-blocklist",
"--disable-smooth-scrolling",
"--enable-features=UseOzonePlatform",
"--ozone-platform=wayland"
],
"enablements": 13,
"session_bus": {
"see": null,
"talk": [
"org.freedesktop.Notifications",
"org.freedesktop.FileManager1",
"org.freedesktop.ScreenSaver",
"org.freedesktop.secrets",
"org.kde.kwalletd5",
"org.kde.kwalletd6",
"org.gnome.SessionManager"
],
"own": [
"org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.chromium.*"
],
"call": {
"org.freedesktop.portal.*": "*"
},
"broadcast": {
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"
},
"filter": true
},
"system_bus": {
"see": null,
"talk": [
"org.bluez",
"org.freedesktop.Avahi",
"org.freedesktop.UPower"
],
"own": null,
"call": null,
"broadcast": null,
"filter": true
},
"username": "chronos",
"shell": "/run/current-system/sw/bin/zsh",
"data": "/var/lib/hakurei/u0/org.chromium.Chromium",
"dir": "/data/data/org.chromium.Chromium",
"extra_perms": [
{
"ensure": true,
"path": "/var/lib/hakurei/u0",
"x": true
},
{
"path": "/var/lib/hakurei/u0/org.chromium.Chromium",
"r": true,
"w": true,
"x": true
}
],
"identity": 9,
"groups": [
"video",
"dialout",
"plugdev"
],
"container": {
"hostname": "localhost",
"wait_delay": -1,
"seccomp_flags": 1,
"seccomp_presets": 1,
"seccomp_compat": true,
"devel": true,
"userns": true,
"net": true,
"tty": true,
"multiarch": true,
"env": {
"GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
"GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
},
"map_real_uid": true,
"device": true,
"filesystem": [
{
"dst": "/tmp/",
"src": "/proc/nonexistent",
"write": true
},
{
"src": "/nix/store"
},
{
"src": "/run/current-system"
},
{
"src": "/run/opengl-driver"
},
{
"src": "/var/db/nix-channels"
},
{
"dst": "/data/data/org.chromium.Chromium",
"src": "/var/lib/hakurei/u0/org.chromium.Chromium",
"write": true,
"require": true
},
{
"src": "/dev/dri",
"dev": true
}
],
"symlink": [
{
"target": "/run/user/65534",
"linkname": "/run/user/150"
}
],
"auto_root": "/var/lib/hakurei/base/org.debian",
"root_flags": 2,
"etc": "/etc/",
"auto_etc": true
}
}
""".trimIndent()
@Test
fun testHakureiConfigSerialization() {
val testConfig = HakureiConfig(
id = "org.chromium.Chromium",
path = Paths.AbsFHSRun + "current-system/sw/bin/chromium",
args = listOf(
"chromium",
"--ignore-gpu-blocklist",
"--disable-smooth-scrolling",
"--enable-features=UseOzonePlatform",
"--ozone-platform=wayland",
),
enablements = HakureiConfig.Enablement.enablements(Wayland, DBus, Pulse),
sessionBus = HakureiConfig.DbusConfig(
see = null,
talk = listOf("org.freedesktop.Notifications", "org.freedesktop.FileManager1", "org.freedesktop.ScreenSaver"),
own = listOf("org.chromium.Chromium.*", "org.mpris.MediaPlayer2.org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.chromium.*"),
call = mapOf("org.freedesktop.portal.*" to "*"),
broadcast = mapOf("org.freedesktop.portal.*" to "@/org/freedesktop/portal/*"),
log = false,
filter = true,
),
systemBus = HakureiConfig.DbusConfig(
see = null,
talk = listOf("org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"),
own = null,
call = null,
broadcast = null,
log = false,
filter = true,
),
directWayland = false,
username = "chronos",
shell = Paths.AbsFHSRun + "current-system/sw/bin/zsh",
data = Paths.AbsFHSVarLib + "hakurei/u0/org.chromium.Chromium",
dir = AbsolutePath("/data/data/org.chromium.Chromium"),
extraPerms = listOf(
HakureiConfig.ExtraPermConfig(path = Paths.AbsFHSVarLib + "hakurei/u0", ensure = true, execute = true),
HakureiConfig.ExtraPermConfig(path = Paths.AbsFHSVarLib + "hakurei/u0/org.chromium.Chromium", read = true, write = true, execute = true)
),
identity = 9,
groups = listOf("video", "dialout", "plugdev"),
container = HakureiConfig.ContainerConfig(
hostname = "localhost",
devel = true,
userns = true,
net = true,
device = true,
waitDelay = -1,
seccompFlags = HakureiConfig.HakureiExportFlag.exportFlags(HakureiConfig.HakureiExportFlag.Multiarch),
seccompPresets = HakureiConfig.SeccompFilterPreset.filterPresets(HakureiConfig.SeccompFilterPreset.Ext),
seccompCompat = true,
tty = true,
multiarch = true,
mapRealUid = true,
env = mapOf(
"GOOGLE_API_KEY" to "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
"GOOGLE_DEFAULT_CLIENT_ID" to "77185425430.apps.googleusercontent.com",
"GOOGLE_DEFAULT_CLIENT_SECRET" to "OTJgUOQcT7lO7GsGZq2G4IlT"
),
filesystem = listOf(
HakureiConfig.FilesystemConfig(dst = Paths.AbsFHSTmp, src = Paths.AbsNonexistent, write = true),
HakureiConfig.FilesystemConfig(src = AbsolutePath("/nix/store")),
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSRun + "current-system"),
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSRun + "opengl-driver"),
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSVar + "db/nix-channels"),
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSVarLib + "hakurei/u0/org.chromium.Chromium", dst = AbsolutePath("/data/data/org.chromium.Chromium"), write = true, must = true),
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSDev + "dri", device = true)
),
link = listOf(HakureiConfig.LinkConfig(Paths.AbsFHSRunUser + "65534", Paths.FHSRunUser + "150")),
autoRoot = Paths.AbsFHSVarLib + "hakurei/base/org.debian",
rootFlags = RootFlag.rootFlags(RootFlag.Writable),
etc = Paths.AbsFHSEtc,
autoEtc = true
)
)
println("printing for now until a more proper test can be written")
println(format.encodeToString(testConfig))
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,18 +0,0 @@
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")