cmd/app: high-level app configuration syntax
Test / Create distribution (push) Successful in 55s
Test / Sandbox (push) Successful in 2m54s
Test / ShareFS (push) Successful in 4m8s
Test / Hakurei (push) Successful in 4m15s
Test / Sandbox (race detector) (push) Successful in 5m45s
Test / Hakurei (race detector) (push) Successful in 7m0s
Test / Flake checks (push) Successful in 1m23s
Test / Create distribution (push) Successful in 55s
Test / Sandbox (push) Successful in 2m54s
Test / ShareFS (push) Successful in 4m8s
Test / Hakurei (push) Successful in 4m15s
Test / Sandbox (race detector) (push) Successful in 5m45s
Test / Hakurei (race detector) (push) Successful in 7m0s
Test / Flake checks (push) Successful in 1m23s
This replaces the nixos module. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
+262
@@ -0,0 +1,262 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
// parsePair parses a NUL-delimited quoted paths pair.
|
||||
func parsePair(s string) (source, target *check.Absolute, err error) {
|
||||
var p string
|
||||
if p, err = strconv.Unquote(s); err != nil {
|
||||
return
|
||||
}
|
||||
_source, _target, ok := strings.Cut(p, "\x00")
|
||||
if source, err = check.NewAbs(_source); err != nil {
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
target, err = check.NewAbs(_target)
|
||||
return
|
||||
}
|
||||
|
||||
// parse decodes a high-level configuration stream and returns its
|
||||
// corresponding [hst.Config].
|
||||
func parse(id string, base *check.Absolute, r io.Reader) (*hst.Config, error) {
|
||||
shell := fhs.AbsRoot.Append("bin", "zsh")
|
||||
home := hst.AbsPrivateTmp.Append("home")
|
||||
|
||||
c := hst.Config{
|
||||
ID: id,
|
||||
Enablements: new(hst.Enablements),
|
||||
|
||||
SessionBus: &hst.BusConfig{
|
||||
Own: []string{
|
||||
id + ".*",
|
||||
"org.mpris.MediaPlayer2." + id + ".*",
|
||||
},
|
||||
Filter: true,
|
||||
},
|
||||
SystemBus: &hst.BusConfig{Filter: true},
|
||||
|
||||
Container: &hst.ContainerConfig{
|
||||
Env: make(map[string]string),
|
||||
Filesystem: []hst.FilesystemConfigJSON{
|
||||
{FilesystemConfig: &hst.FSOverlay{
|
||||
Target: fhs.AbsRoot,
|
||||
Lower: []*check.Absolute{
|
||||
base.Append("template", "initial"),
|
||||
base.Append("template", "upper"),
|
||||
},
|
||||
}},
|
||||
{FilesystemConfig: &hst.FSBind{
|
||||
Target: home,
|
||||
Source: base.Append("state", id),
|
||||
Write: true,
|
||||
Ensure: true,
|
||||
}},
|
||||
|
||||
{FilesystemConfig: &hst.FSEphemeral{
|
||||
Target: fhs.AbsVar.Append("tmp"),
|
||||
Write: true,
|
||||
Perm: 01777,
|
||||
}},
|
||||
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("block")}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("bus")}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("class")}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("dev")}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("devices")}},
|
||||
},
|
||||
|
||||
Username: "chronos",
|
||||
Shell: shell,
|
||||
Home: home,
|
||||
Path: shell,
|
||||
Args: []string{"zsh", "-c"},
|
||||
},
|
||||
}
|
||||
|
||||
s := bufio.NewScanner(r)
|
||||
scanOnce := func() error {
|
||||
if s.Scan() {
|
||||
return nil
|
||||
}
|
||||
if err := s.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
if err := scanOnce(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v, err := strconv.Atoi(s.Text()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.Identity = v
|
||||
}
|
||||
|
||||
if err := scanOnce(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Container.Args = append(c.Container.Args, s.Text())
|
||||
|
||||
var flagGPU, flagSystemBus bool
|
||||
flags := map[string]*bool{
|
||||
"gpu": &flagGPU,
|
||||
"system_bus": &flagSystemBus,
|
||||
}
|
||||
|
||||
for s.Scan() {
|
||||
key, value, ok := strings.Cut(s.Text(), " ")
|
||||
if key != "" && key[0] == ';' {
|
||||
continue
|
||||
}
|
||||
|
||||
if !ok {
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var p *bool
|
||||
if p, ok = flags[key]; ok {
|
||||
*p = true
|
||||
continue
|
||||
}
|
||||
|
||||
switch key {
|
||||
case "wayland":
|
||||
*c.Enablements |= hst.EWayland
|
||||
case "x11":
|
||||
*c.Enablements |= hst.EX11
|
||||
case "dbus":
|
||||
*c.Enablements |= hst.EDBus
|
||||
case "pipewire":
|
||||
*c.Enablements |= hst.EPipeWire
|
||||
|
||||
case "multiarch":
|
||||
c.Container.Flags |= hst.FMultiarch
|
||||
case "devel":
|
||||
c.Container.Flags |= hst.FDevel
|
||||
case "userns":
|
||||
c.Container.Flags |= hst.FUserns
|
||||
case "net":
|
||||
c.Container.Flags |= hst.FHostNet
|
||||
case "abstract":
|
||||
c.Container.Flags |= hst.FHostAbstract
|
||||
case "tty":
|
||||
c.Container.Flags |= hst.FTty
|
||||
case "mapuid":
|
||||
c.Container.Flags |= hst.FMapRealUID
|
||||
case "device":
|
||||
c.Container.Flags |= hst.FDevice
|
||||
|
||||
case "share_runtime":
|
||||
c.Container.Flags |= hst.FShareRuntime
|
||||
case "share_tmpdir":
|
||||
c.Container.Flags |= hst.FShareTmpdir
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid flag %q", key)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
switch key {
|
||||
case "group":
|
||||
c.Groups = append(c.Groups, value)
|
||||
continue
|
||||
|
||||
case "env":
|
||||
if key, value, ok = strings.Cut(value, "="); !ok {
|
||||
return nil, fmt.Errorf("invalid environment %q", key)
|
||||
}
|
||||
c.Container.Env[key] = value
|
||||
continue
|
||||
|
||||
case "ro":
|
||||
source, target, err := parsePair(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Container.Filesystem = append(c.Container.Filesystem,
|
||||
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
|
||||
Target: target,
|
||||
Source: source,
|
||||
}},
|
||||
)
|
||||
continue
|
||||
|
||||
case "rw":
|
||||
source, target, err := parsePair(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Container.Filesystem = append(c.Container.Filesystem,
|
||||
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
|
||||
Target: target,
|
||||
Source: source,
|
||||
Write: true,
|
||||
}},
|
||||
)
|
||||
continue
|
||||
|
||||
case "own":
|
||||
c.SessionBus.Own = append(c.SessionBus.Own, value)
|
||||
continue
|
||||
case "own_system":
|
||||
c.SystemBus.Own = append(c.SystemBus.Own, value)
|
||||
continue
|
||||
|
||||
case "talk":
|
||||
c.SessionBus.Talk = append(c.SessionBus.Talk, value)
|
||||
continue
|
||||
case "talk_system":
|
||||
c.SystemBus.Talk = append(c.SystemBus.Talk, value)
|
||||
continue
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid key %q", key)
|
||||
}
|
||||
}
|
||||
if err := s.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if flagGPU {
|
||||
c.Container.Filesystem = append(c.Container.Filesystem, []hst.FilesystemConfigJSON{
|
||||
{FilesystemConfig: &hst.FSBind{
|
||||
Source: fhs.AbsDev.Append("dri"),
|
||||
Device: true,
|
||||
Optional: true,
|
||||
}},
|
||||
}...)
|
||||
}
|
||||
|
||||
if !flagSystemBus {
|
||||
c.SystemBus = nil
|
||||
}
|
||||
|
||||
if c.Container.Flags&hst.FShareTmpdir == 0 {
|
||||
c.Container.Filesystem = append(c.Container.Filesystem,
|
||||
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSEphemeral{
|
||||
Target: fhs.AbsTmp,
|
||||
Write: true,
|
||||
Perm: 01777,
|
||||
}},
|
||||
)
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
base := fhs.AbsProc.Append("nonexistent")
|
||||
testCases := []struct {
|
||||
name string
|
||||
data string
|
||||
want *hst.Config
|
||||
err error
|
||||
}{
|
||||
{"com.discordapp.Discord", `8
|
||||
exec Discord --ozone-platform-hint=wayland
|
||||
|
||||
gpu
|
||||
wayland
|
||||
dbus
|
||||
system_bus
|
||||
pipewire
|
||||
userns
|
||||
net
|
||||
mapuid
|
||||
|
||||
share_runtime
|
||||
share_tmpdir
|
||||
|
||||
group media_rw
|
||||
env ELECTRON_TRASH=gio
|
||||
rw "/sdcard"
|
||||
; remove before reusing
|
||||
ro "/bin\x00/.hakurei/bin"
|
||||
|
||||
talk org.kde.StatusNotifierWatcher
|
||||
talk com.canonical.AppMenu.Registrar
|
||||
talk com.canonical.indicator.application
|
||||
talk com.canonical.Unity
|
||||
`, &hst.Config{
|
||||
Identity: 8,
|
||||
ID: "com.discordapp.Discord",
|
||||
Enablements: new(hst.EWayland | hst.EDBus | hst.EPipeWire),
|
||||
Groups: []string{"media_rw"},
|
||||
|
||||
SessionBus: &hst.BusConfig{
|
||||
Talk: []string{
|
||||
"org.kde.StatusNotifierWatcher",
|
||||
"com.canonical.AppMenu.Registrar",
|
||||
"com.canonical.indicator.application",
|
||||
"com.canonical.Unity",
|
||||
},
|
||||
Own: []string{
|
||||
"com.discordapp.Discord.*",
|
||||
"org.mpris.MediaPlayer2.com.discordapp.Discord.*",
|
||||
},
|
||||
Filter: true,
|
||||
},
|
||||
SystemBus: &hst.BusConfig{Filter: true},
|
||||
|
||||
Container: &hst.ContainerConfig{
|
||||
Env: map[string]string{
|
||||
"ELECTRON_TRASH": "gio",
|
||||
},
|
||||
Filesystem: []hst.FilesystemConfigJSON{
|
||||
{FilesystemConfig: &hst.FSOverlay{
|
||||
Target: fhs.AbsRoot,
|
||||
Lower: []*check.Absolute{
|
||||
base.Append("template", "initial"),
|
||||
base.Append("template", "upper"),
|
||||
},
|
||||
}},
|
||||
{FilesystemConfig: &hst.FSBind{
|
||||
Target: hst.AbsPrivateTmp.Append("home"),
|
||||
Source: base.Append("state", "com.discordapp.Discord"),
|
||||
Write: true,
|
||||
Ensure: true,
|
||||
}},
|
||||
|
||||
{FilesystemConfig: &hst.FSEphemeral{
|
||||
Target: fhs.AbsVar.Append("tmp"),
|
||||
Write: true,
|
||||
Perm: 01777,
|
||||
}},
|
||||
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("block")}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("bus")}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("class")}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("dev")}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("devices")}},
|
||||
|
||||
{FilesystemConfig: &hst.FSBind{
|
||||
Source: check.MustAbs("/sdcard"),
|
||||
Write: true,
|
||||
}},
|
||||
{FilesystemConfig: &hst.FSBind{
|
||||
Target: check.MustAbs("/.hakurei/bin"),
|
||||
Source: check.MustAbs("/bin"),
|
||||
}},
|
||||
|
||||
{FilesystemConfig: &hst.FSBind{
|
||||
Source: fhs.AbsDev.Append("dri"),
|
||||
Device: true,
|
||||
Optional: true,
|
||||
}},
|
||||
},
|
||||
|
||||
Username: "chronos",
|
||||
Shell: fhs.AbsRoot.Append("bin", "zsh"),
|
||||
Home: hst.AbsPrivateTmp.Append("home"),
|
||||
Path: fhs.AbsRoot.Append("bin", "zsh"),
|
||||
Args: []string{
|
||||
"zsh", "-c",
|
||||
"exec Discord --ozone-platform-hint=wayland",
|
||||
},
|
||||
|
||||
Flags: hst.FUserns | hst.FHostNet | hst.FMapRealUID |
|
||||
hst.FShareRuntime | hst.FShareTmpdir,
|
||||
},
|
||||
}, nil},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, err := parse(
|
||||
tc.name,
|
||||
base,
|
||||
strings.NewReader(tc.data),
|
||||
)
|
||||
|
||||
if !reflect.DeepEqual(err, tc.err) {
|
||||
t.Errorf("parse: error = %v, want %v", err, tc.err)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, tc.want) {
|
||||
t.Errorf("parse: %#v, want %#v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package hst
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -161,6 +162,10 @@ type ContainerConfig struct {
|
||||
Flags Flags `json:"-"`
|
||||
}
|
||||
|
||||
func (c *ContainerConfig) GoString() string {
|
||||
return fmt.Sprintf("&%#v", *c)
|
||||
}
|
||||
|
||||
// ContainerConfigF is [ContainerConfig] stripped of its methods.
|
||||
//
|
||||
// The [ContainerConfig.Flags] field does not survive a [json] round trip.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package hst
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -61,6 +62,10 @@ type BusConfig struct {
|
||||
Filter bool `json:"filter"`
|
||||
}
|
||||
|
||||
func (c *BusConfig) GoString() string {
|
||||
return fmt.Sprintf("&%#v", *c)
|
||||
}
|
||||
|
||||
// Interfaces iterates over all interface strings specified in [BusConfig].
|
||||
func (c *BusConfig) Interfaces(yield func(string) bool) {
|
||||
if c == nil {
|
||||
|
||||
Reference in New Issue
Block a user