Compare commits

..

38 Commits

Author SHA1 Message Date
cat 3a5f4af114 release: 0.4.5
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-24 06:18:02 +09:00
cat 6195260480 internal/rosa/package/firmware: 20260519 to 20260622
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-24 06:11:20 +09:00
cat 21044d5a60 internal/rosa/package/vim: 9.2.0461 to 9.2.0707
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-24 06:10:58 +09:00
cat 025810bf0f internal/rosa/package/python: setuptools-scm 10.0.5 to 10.1.2
All in-between releases are broken. Not sure how that happened.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-24 06:10:32 +09:00
cat 20354c0411 internal/rosa/package/python: vcs-versioning 1.1.1 to 2.1.1
The environment variable changed for this package only, did not appear to affect other packages.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-24 06:09:42 +09:00
cat 9fbcd0daf2 internal/rosa/package/libarchive: 3.8.7 to 3.8.8
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-24 06:08:47 +09:00
cat 248f44a5a7 cmd/app: zero state buffer before reuse
Package internal/store expects a zero-initialised buffer.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-23 09:19:11 +09:00
cat 401dd57cbc cmd/app: display user-facing error message
This is required for useful error messages for errors originating from internal/store.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-23 09:09:55 +09:00
cat 854bcc998b cmd/app: expose scheduling configuration
Useful for the music player.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-23 06:33:27 +09:00
cat 358247be5b internal/rosa/package/x: xkeyboard-config 2.47 to 2.48
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-23 00:44:18 +09:00
cat f517a8ef07 internal/rosa/package/libffi: 3.5.2 to 3.6.0
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-23 00:43:24 +09:00
cat 973218f91f internal/rosa/package/qemu: disable netdev-socket test
This fails with ipv6 disabled.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-22 01:21:09 +09:00
cat 0ea195837b internal/rosa/package/glib: disable gio suite
This fails with ipv6 disabled.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-22 00:53:40 +09:00
cat 1348991634 internal/rosa/meson: skip specific test suites
For disabling specific broken or flaky tests.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-22 00:53:17 +09:00
cat 14b445fde5 internal/rosa/package/openssl: disable test_bio_dgram
This fails when ipv6 is disabled.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-21 21:43:46 +09:00
cat 218f7fa345 cmd/app: enforce mutable instance exclusion
This avoids invoking undefined behaviour in the underlying overlay filesystem implementation.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-21 02:09:01 +09:00
cat cd493fd95f cmd/app: centralise workdir
This makes the directory structure significantly more manageable.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-21 00:48:18 +09:00
cat 9db70c83e3 cmd/app: configure username and hostname
These no longer need to be hardcoded since this is not subject to the limitations of home-manager.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-20 22:15:49 +09:00
cat 4e09241e5f cmd/app: optional interactive shell
Enabling this unconditionally causes the new configuration prompt to be shown when started from a terminal, and is generally less robust than not reading zshrc unless explicitly required.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-20 19:08:47 +09:00
cat bd4b300ea6 internal/rosa/package/cmake: 4.3.3 to 4.3.4
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-20 17:56:09 +09:00
cat 6dc8214a1a internal/rosa/package/kernel: 6.12.93 to 6.12.94
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-20 17:55:49 +09:00
cat cada5a46ad internal/rosa/package/mesa: 26.1.2 to 26.1.3
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-20 17:55:08 +09:00
cat e747942829 internal/rosa/package/python: pytest 9.1.0 to 9.1.1
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-20 17:54:38 +09:00
cat 33b855123e internal/rosa/package/spirv: spirv-headers 1.4.350.0 to 1.4.350.1
This unfortunately does not unblock SPIRV-LLVM-Translator.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-20 04:43:04 +09:00
cat 58ce134718 internal/outcome: attempt nscd path-hiding if present
This avoids creating the mount point on musl setups which accomplishes nothing and can run into permission problems.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-20 02:42:35 +09:00
cat 2066093343 cmd/app: remove sysfs bind mounts
This should be in common instead.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-20 01:57:55 +09:00
cat 07509b3ba2 cmd/app: additional bind types
This adds optional and device mount points.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-20 01:51:57 +09:00
cat a7485d587a cmd/app: pass user-specified arguments
An extra argument is added to pad out argv0.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-20 01:44:47 +09:00
cat 4892beefc1 cmd/app: optionally override configured command
Useful for multiple applications sharing state.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-20 01:31:07 +09:00
cat 7ab54b8c94 internal/rosa: read overridden version string from source
This is more correct than the hardcoded string and is generally more robust against relative paths.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-20 01:18:12 +09:00
cat a4fab67811 internal/pkg: optionally exempt implementations from cures counter
This avoids holding up many slots with a long pipeline.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-20 01:03:28 +09:00
cat ed5cdd38a4 cmd/dist: build hsu separately
This program must be built with cgo disabled, and was missed when migrating build script.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-20 00:59:47 +09:00
cat f6318304ee hst: fix ephemeral overlay order
This is quite counterintuitive otherwise.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-20 00:20:31 +09:00
cat cb618093d5 hst: optionally disable file placement
This works around stubborn package managers.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-20 00:16:35 +09:00
cat b0b2471c0c cmd/app: include template name in container metadata
This helps disambiguate active mutable containers.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-19 23:36:31 +09:00
cat 344d2b8207 cmd/app: use ROSA_ prefix
This avoids awkward case-insensitive zsh completion.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-19 23:29:51 +09:00
cat 3938e8bce5 cmd/app: multiple template uppers
Having multiple environments is useful, and this was trivial to implement.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-18 03:51:49 +09:00
cat aee15b4f2a cmd/app: common configuration file
Generally useful for shared storage and environment.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-18 02:35:56 +09:00
44 changed files with 529 additions and 149 deletions
+76 -17
View File
@@ -8,6 +8,7 @@ import (
"strings" "strings"
"hakurei.app/check" "hakurei.app/check"
"hakurei.app/ext"
"hakurei.app/fhs" "hakurei.app/fhs"
"hakurei.app/hst" "hakurei.app/hst"
) )
@@ -31,10 +32,19 @@ func parsePair(s string) (source, target *check.Absolute, err error) {
// parse decodes a high-level configuration stream and returns its // parse decodes a high-level configuration stream and returns its
// corresponding [hst.Config]. // corresponding [hst.Config].
func parse(id string, base *check.Absolute, r io.Reader) (*hst.Config, error) { func parse(
id string,
base *check.Absolute,
r io.Reader,
templateP *string,
) (*hst.Config, error) {
shell := fhs.AbsRoot.Append("bin", "zsh") shell := fhs.AbsRoot.Append("bin", "zsh")
home := hst.AbsPrivateTmp.Append("home") home := hst.AbsPrivateTmp.Append("home")
root := hst.FSOverlay{
Target: fhs.AbsRoot,
Lower: []*check.Absolute{base.Append("initial")},
}
c := hst.Config{ c := hst.Config{
ID: id, ID: id,
Enablements: new(hst.Enablements), Enablements: new(hst.Enablements),
@@ -51,13 +61,7 @@ func parse(id string, base *check.Absolute, r io.Reader) (*hst.Config, error) {
Container: &hst.ContainerConfig{ Container: &hst.ContainerConfig{
Env: make(map[string]string), Env: make(map[string]string),
Filesystem: []hst.FilesystemConfigJSON{ Filesystem: []hst.FilesystemConfigJSON{
{FilesystemConfig: &hst.FSOverlay{ {FilesystemConfig: &root},
Target: fhs.AbsRoot,
Lower: []*check.Absolute{
base.Append("template", "initial"),
},
Upper: base.Append("template", "upper"),
}},
{FilesystemConfig: &hst.FSBind{ {FilesystemConfig: &hst.FSBind{
Target: home, Target: home,
Source: base.Append("state", id), Source: base.Append("state", id),
@@ -70,12 +74,6 @@ func parse(id string, base *check.Absolute, r io.Reader) (*hst.Config, error) {
Write: true, Write: true,
Perm: 01777, 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", Username: "chronos",
@@ -102,19 +100,26 @@ func parse(id string, base *check.Absolute, r io.Reader) (*hst.Config, error) {
if err := scanOnce(); err != nil { if err := scanOnce(); err != nil {
return nil, err return nil, err
} }
if v, err := strconv.Atoi(s.Text()); err != nil { if template, identity, ok := strings.Cut(s.Text(), ":"); !ok {
return nil, io.ErrUnexpectedEOF
} else if v, err := strconv.Atoi(identity); err != nil {
return nil, err return nil, err
} else { } else {
if templateP != nil {
*templateP = template
}
c.Identity = v c.Identity = v
root.Upper = base.Append("template", template)
} }
if err := scanOnce(); err != nil { if err := scanOnce(); err != nil {
return nil, err return nil, err
} }
c.Container.Args = append(c.Container.Args, s.Text()) c.Container.Args = append(c.Container.Args, s.Text(), "")
var flagGPU, flagSystemBus bool var flagInteractive, flagGPU, flagSystemBus bool
flags := map[string]*bool{ flags := map[string]*bool{
"interactive": &flagInteractive,
"gpu": &flagGPU, "gpu": &flagGPU,
"system_bus": &flagSystemBus, "system_bus": &flagSystemBus,
} }
@@ -176,10 +181,32 @@ func parse(id string, base *check.Absolute, r io.Reader) (*hst.Config, error) {
} }
switch key { switch key {
case "username":
c.Container.Username = value
continue
case "hostname":
c.Container.Hostname = value
continue
case "group": case "group":
c.Groups = append(c.Groups, value) c.Groups = append(c.Groups, value)
continue continue
case "sched_policy":
if err := c.SchedPolicy.UnmarshalText([]byte(value)); err != nil {
return nil, err
}
continue
case "sched_priority":
v, err := strconv.Atoi(value)
if err != nil {
return nil, err
}
c.SchedPriority = ext.Int(v)
continue
case "env": case "env":
if key, value, ok = strings.Cut(value, "="); !ok { if key, value, ok = strings.Cut(value, "="); !ok {
return nil, fmt.Errorf("invalid environment %q", key) return nil, fmt.Errorf("invalid environment %q", key)
@@ -200,6 +227,20 @@ func parse(id string, base *check.Absolute, r io.Reader) (*hst.Config, error) {
) )
continue 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,
Optional: true,
}},
)
continue
case "rw": case "rw":
source, target, err := parsePair(value) source, target, err := parsePair(value)
if err != nil { if err != nil {
@@ -214,6 +255,20 @@ func parse(id string, base *check.Absolute, r io.Reader) (*hst.Config, error) {
) )
continue continue
case "dev":
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,
Device: true,
}},
)
continue
case "own": case "own":
c.SessionBus.Own = append(c.SessionBus.Own, value) c.SessionBus.Own = append(c.SessionBus.Own, value)
continue continue
@@ -236,6 +291,10 @@ func parse(id string, base *check.Absolute, r io.Reader) (*hst.Config, error) {
return nil, err return nil, err
} }
if flagInteractive {
c.Container.Args[1] += "i"
}
if flagGPU { if flagGPU {
c.Container.Filesystem = append(c.Container.Filesystem, []hst.FilesystemConfigJSON{ c.Container.Filesystem = append(c.Container.Filesystem, []hst.FilesystemConfigJSON{
{FilesystemConfig: &hst.FSBind{ {FilesystemConfig: &hst.FSBind{
+5 -9
View File
@@ -20,7 +20,7 @@ func TestParse(t *testing.T) {
want *hst.Config want *hst.Config
err error err error
}{ }{
{"com.discordapp.Discord", `8 {"com.discordapp.Discord", `nonfree:8
exec Discord --ozone-platform-hint=wayland exec Discord --ozone-platform-hint=wayland
gpu gpu
@@ -74,9 +74,9 @@ talk com.canonical.Unity
{FilesystemConfig: &hst.FSOverlay{ {FilesystemConfig: &hst.FSOverlay{
Target: fhs.AbsRoot, Target: fhs.AbsRoot,
Lower: []*check.Absolute{ Lower: []*check.Absolute{
base.Append("template", "initial"), base.Append("initial"),
}, },
Upper: base.Append("template", "upper"), Upper: base.Append("template", "nonfree"),
}}, }},
{FilesystemConfig: &hst.FSBind{ {FilesystemConfig: &hst.FSBind{
Target: hst.AbsPrivateTmp.Append("home"), Target: hst.AbsPrivateTmp.Append("home"),
@@ -91,12 +91,6 @@ talk com.canonical.Unity
Perm: 01777, 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{ {FilesystemConfig: &hst.FSBind{
Source: check.MustAbs("/sdcard"), Source: check.MustAbs("/sdcard"),
Write: true, Write: true,
@@ -120,6 +114,7 @@ talk com.canonical.Unity
Args: []string{ Args: []string{
"zsh", "-c", "zsh", "-c",
"exec Discord --ozone-platform-hint=wayland", "exec Discord --ozone-platform-hint=wayland",
"",
}, },
Flags: hst.FCoverRun | hst.FUserns | hst.FHostNet | hst.FMapRealUID | Flags: hst.FCoverRun | hst.FUserns | hst.FHostNet | hst.FMapRealUID |
@@ -135,6 +130,7 @@ talk com.canonical.Unity
tc.name, tc.name,
base, base,
strings.NewReader(tc.data), strings.NewReader(tc.data),
nil,
) )
if !reflect.DeepEqual(err, tc.err) { if !reflect.DeepEqual(err, tc.err) {
+112
View File
@@ -0,0 +1,112 @@
package main
import (
"errors"
"log"
"os"
"strconv"
"strings"
"hakurei.app/check"
"hakurei.app/fhs"
"hakurei.app/hst"
"hakurei.app/internal/env"
"hakurei.app/internal/lockedfile"
"hakurei.app/internal/outcome"
)
// MutationConflictError describes an active mutable instance.
type MutationConflictError string
func (e MutationConflictError) Error() string {
return "mutable instance active at " + string(e)
}
// informTemplate guards intention of a template or its derivatives.
func informTemplate(base *check.Absolute, name string, mutable bool) (func() error, error) {
mu := lockedfile.MutexAt(base.Append("lock", name).String())
if unlock, err := mu.Lock(); err != nil {
return nil, err
} else {
defer unlock()
}
marker := base.Append("lock", "."+name)
if p, err := os.ReadFile(marker.String()); err == nil {
if _, err = os.Stat(fhs.AbsProc.Append(string(p)).String()); err == nil {
return nil, MutationConflictError(p)
} else if !errors.Is(err, os.ErrNotExist) {
return nil, err
}
log.Printf("removing stale marker by %s", string(p))
if err = os.Remove(marker.String()); err != nil {
return nil, err
}
} else if !errors.Is(err, os.ErrNotExist) {
return nil, err
}
if !mutable {
return nil, nil
}
var active []hst.ID
var sc hst.Paths
env.CopyPaths().Copy(&sc, new(outcome.Hsu).MustID(nil))
entries, copyError := outcome.NewStore(&sc).All()
var s hst.State
for eh := range entries {
s = hst.State{}
if _, err := eh.Load(&s); err != nil {
return nil, err
}
if s.Validate(0) != nil || len(s.Container.Filesystem) < 1 {
continue
}
root, ok := s.Container.Filesystem[0].FilesystemConfig.(*hst.FSOverlay)
if !ok || root == nil {
continue
}
if !root.Target.Is(fhs.AbsRoot) ||
len(root.Lower) != 1 ||
!root.Lower[0].Is(base.Append("initial")) ||
!root.Upper.Is(base.Append("template", name)) ||
root.Work != nil {
continue
}
active = append(active, s.ID)
}
if err := copyError(); err != nil {
return nil, err
}
if len(active) != 0 {
var buf strings.Builder
buf.WriteString("derivative instances still active:")
for _, id := range active {
buf.WriteString("\n\t")
buf.WriteString(id.String())
}
return nil, errors.New(buf.String())
}
return func() error { return os.RemoveAll(marker.String()) }, os.WriteFile(
marker.String(),
[]byte(strconv.Itoa(os.Getpid())),
0400,
)
}
// acquireTemplate obtains exclusivity of a template.
func acquireTemplate(base *check.Absolute, name string) (remove func() error, err error) {
return informTemplate(base, name, true)
}
// enterTemplate checks against exclusivity of a template.
func enterTemplate(base *check.Absolute, name string) error {
_, err := informTemplate(base, name, false)
return err
}
+88 -16
View File
@@ -7,6 +7,8 @@ package main
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"io"
"log" "log"
"os" "os"
"os/exec" "os/exec"
@@ -34,7 +36,7 @@ func main() {
flagVerbose bool flagVerbose bool
flagBase string flagBase string
base, template, initial, upper, work *check.Absolute base, template, initial *check.Absolute
) )
c := command.New(os.Stderr, log.Printf, "app", func([]string) (err error) { c := command.New(os.Stderr, log.Printf, "app", func([]string) (err error) {
msg.SwapVerbose(flagVerbose) msg.SwapVerbose(flagVerbose)
@@ -49,9 +51,7 @@ func main() {
} }
template = base.Append("template") template = base.Append("template")
initial = template.Append("initial") initial = base.Append("initial")
upper = template.Append("upper")
work = template.Append("work")
return return
}).Flag( }).Flag(
&flagVerbose, &flagVerbose,
@@ -59,7 +59,7 @@ func main() {
"Increase log verbosity", "Increase log verbosity",
).Flag( ).Flag(
&flagBase, &flagBase,
"d", command.StringFlag("$HAKUREI_APP_PATH"), "d", command.StringFlag("$ROSA_APP_PATH"),
"Configuration and state directory", "Configuration and state directory",
) )
@@ -70,17 +70,31 @@ func main() {
) )
c.NewCommand( c.NewCommand(
"enter", "Enter mutable state template", "enter", "Enter mutable state template",
func([]string) error { func(args []string) error {
if len(args) != 1 {
dents, err := os.ReadDir(template.String())
if err != nil {
return err
}
for _, dent := range dents {
if !dent.IsDir() {
continue
}
fmt.Println(dent.Name())
}
return nil
}
config := hst.Config{ config := hst.Config{
ID: "app.hakurei.mutable", ID: "app.hakurei.mutable." + args[0],
Container: &hst.ContainerConfig{ Container: &hst.ContainerConfig{
Hostname: "mutable", Hostname: args[0] + "-mutable",
Filesystem: []hst.FilesystemConfigJSON{ Filesystem: []hst.FilesystemConfigJSON{
{FilesystemConfig: &hst.FSOverlay{ {FilesystemConfig: &hst.FSOverlay{
Target: fhs.AbsRoot, Target: fhs.AbsRoot,
Lower: []*check.Absolute{initial}, Lower: []*check.Absolute{initial},
Upper: upper, Upper: template.Append(args[0]),
Work: work, Work: base.Append("work", args[0]),
}}, }},
{FilesystemConfig: &hst.FSEphemeral{ {FilesystemConfig: &hst.FSEphemeral{
Target: fhs.AbsTmp, Target: fhs.AbsTmp,
@@ -89,7 +103,8 @@ func main() {
}}, }},
}, },
Username: "chronos", Username: "chronos",
Flags: hst.FMultiarch | Flags: hst.FNoPlace |
hst.FMultiarch |
hst.FDevel | hst.FDevel |
hst.FUserns | hst.FUserns |
hst.FHostNet | hst.FHostNet |
@@ -113,7 +128,12 @@ func main() {
config.Container.Home = a config.Container.Home = a
} }
return run(ctx, msg, &config) remove, err := acquireTemplate(base, args[0])
if err != nil {
return err
}
err = run(ctx, msg, &config)
return errors.Join(err, remove())
}, },
).Flag( ).Flag(
&flagShell, &flagShell,
@@ -126,29 +146,75 @@ func main() {
) )
} }
{
var (
flagCommand string
)
c.NewCommand( c.NewCommand(
"run", "Start the named application", "run", "Start the named application",
func(args []string) error { func(args []string) error {
if len(args) != 1 { if len(args) < 1 {
return errors.New("run requires 1 argument") dents, err := os.ReadDir(base.Append("app").String())
if err != nil {
return err
}
for _, dent := range dents {
if dent.IsDir() {
continue
}
fmt.Println(dent.Name())
}
return nil
} }
var config *hst.Config var config *hst.Config
var r io.Reader
f, err := os.Open(base.Append("app", args[0]).String()) f, err := os.Open(base.Append("app", args[0]).String())
if err != nil { if err != nil {
return err return err
} }
config, err = parse(args[0], base, f) r = f
var common *os.File
if common, err = os.Open(base.Append("common").String()); err != nil {
if !errors.Is(err, os.ErrNotExist) {
_ = f.Close()
return err
}
} else {
r = io.MultiReader(f, common)
}
var name string
config, err = parse(args[0], base, r, &name)
if closeErr := f.Close(); err == nil { if closeErr := f.Close(); err == nil {
err = closeErr err = closeErr
} }
if common != nil {
if closeErr := common.Close(); err == nil {
err = closeErr
}
}
if err != nil { if err != nil {
return err return err
} }
return run(ctx, msg, config) if flagCommand != "" {
config.Container.Args[2] = flagCommand
}
if err = enterTemplate(base, name); err != nil {
return err
}
return run(ctx, msg, config, args[1:]...)
}, },
).
Flag(
&flagCommand,
"command", command.StringFlag(""),
"Override configured command",
) )
}
c.MustParse(os.Args[1:], func(err error) { c.MustParse(os.Args[1:], func(err error) {
if e, ok := errors.AsType[*exec.ExitError](err); ok && e != nil { if e, ok := errors.AsType[*exec.ExitError](err); ok && e != nil {
@@ -156,7 +222,13 @@ func main() {
} }
if w, ok := err.(interface{ Unwrap() []error }); !ok { if w, ok := err.(interface{ Unwrap() []error }); !ok {
var m string
m, ok = message.GetMessage(err)
if !ok {
log.Fatal(err) log.Fatal(err)
return
}
log.Fatal(m)
} else { } else {
errs := w.Unwrap() errs := w.Unwrap()
for i, e := range errs { for i, e := range errs {
+7 -1
View File
@@ -12,7 +12,12 @@ import (
) )
// run starts a container via cmd/hakurei and returns after it terminates. // run starts a container via cmd/hakurei and returns after it terminates.
func run(ctx context.Context, msg message.Msg, config *hst.Config) error { func run(
ctx context.Context,
msg message.Msg,
config *hst.Config,
args ...string,
) error {
c, cancel := context.WithCancel(ctx) c, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
@@ -25,6 +30,7 @@ func run(ctx context.Context, msg message.Msg, config *hst.Config) error {
cmd.Args = append(cmd.Args, "-v") cmd.Args = append(cmd.Args, "-v")
} }
cmd.Args = append(cmd.Args, "run", "3") cmd.Args = append(cmd.Args, "run", "3")
cmd.Args = append(cmd.Args, args...)
r, w, err := os.Pipe() r, w, err := os.Pipe()
if err != nil { if err != nil {
+1 -1
View File
@@ -1 +1 @@
v0.4.4 v0.4.5
+24 -9
View File
@@ -35,8 +35,11 @@ func getenv(key, fallback string) string {
// mustRun runs a command with the current process's environment and panics // mustRun runs a command with the current process's environment and panics
// on error or non-zero exit code. // on error or non-zero exit code.
func mustRun(ctx context.Context, name string, arg ...string) { func mustRun(ctx context.Context, env []string, name string, arg ...string) {
cmd := exec.CommandContext(ctx, name, arg...) cmd := exec.CommandContext(ctx, name, arg...)
if env != nil {
cmd.Env = append(cmd.Environ(), env...)
}
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
panic(err) panic(err)
@@ -49,6 +52,7 @@ var comp []byte
func main() { func main() {
log.SetFlags(0) log.SetFlags(0)
log.SetPrefix("") log.SetPrefix("")
log.SetOutput(os.Stdout)
verbose := os.Getenv("VERBOSE") != "" verbose := os.Getenv("VERBOSE") != ""
runTests := os.Getenv("HAKUREI_DIST_MAKE") == "" runTests := os.Getenv("HAKUREI_DIST_MAKE") == ""
@@ -91,26 +95,37 @@ func main() {
verboseFlag = "-buildvcs=false" verboseFlag = "-buildvcs=false"
} }
log.Printf("Building hakurei for %s/%s.", runtime.GOOS, runtime.GOARCH) log.Printf("Building hakurei %s for %s/%s.", version, runtime.GOOS, runtime.GOARCH)
mustRun(ctx, "go", "generate", "./...") mustRun(ctx, nil, "go", "generate", "./...")
mustRun( mustRun(
ctx, "go", "build", ctx, nil, "go", "build",
"-trimpath", "-trimpath",
verboseFlag, "-o", s, verboseFlag, "-o", s,
"-ldflags=-s -w "+ "-ldflags=-s -w "+
"-buildid= -linkmode external -extldflags=-static "+ "-buildid= -linkmode external -extldflags=-static "+
"-X hakurei.app/internal/info.buildVersion="+version+" "+ "-X hakurei.app/internal/info.buildVersion="+version+" "+
"-X hakurei.app/internal/info.hakureiPath="+prefix+"/bin/hakurei "+ "-X hakurei.app/internal/info.hakureiPath="+prefix+"/bin/hakurei "+
"-X hakurei.app/internal/info.hsuPath="+prefix+"/bin/hsu "+ "-X hakurei.app/internal/info.hsuPath="+prefix+"/bin/hsu",
"-X main.hakureiPath="+prefix+"/bin/hakurei", "./cmd/hakurei",
"./...", "./cmd/sharefs",
) )
log.Println()
log.Printf("Building cmd/hsu for %s/%s.", runtime.GOOS, runtime.GOARCH)
mustRun(
ctx, []string{"CGO_ENABLED=0"}, "go", "build",
"-trimpath",
verboseFlag, "-o", s,
"-ldflags=-s -w "+
"-buildid= "+
"-X main.hakureiPath="+prefix+"/bin/hakurei",
"./cmd/hsu",
)
log.Println()
if runTests { if runTests {
log.Println("##### Testing Hakurei.") log.Println("##### Testing Hakurei.")
mustRun( mustRun(
ctx, "go", "test", ctx, nil, "go", "test",
"-ldflags=-buildid= -linkmode external -extldflags=-static", "-ldflags=-buildid= -linkmode external -extldflags=-static",
"./...", "./...",
) )
+5 -2
View File
@@ -64,7 +64,7 @@ func TestPrintShowInstance(t *testing.T) {
Identity: 9 (org.chromium.Chromium) Identity: 9 (org.chromium.Chromium)
Enablements: wayland, dbus, pipewire Enablements: wayland, dbus, pipewire
Groups: video, dialout, plugdev Groups: video, dialout, plugdev
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, cover_run, runtime, tmpdir Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, noplace, device, cover_run, runtime, tmpdir
Home: /data/data/org.chromium.Chromium Home: /data/data/org.chromium.Chromium
Hostname: localhost Hostname: localhost
Path: /run/current-system/sw/bin/chromium Path: /run/current-system/sw/bin/chromium
@@ -161,7 +161,7 @@ App
Identity: 9 (org.chromium.Chromium) Identity: 9 (org.chromium.Chromium)
Enablements: wayland, dbus, pipewire Enablements: wayland, dbus, pipewire
Groups: video, dialout, plugdev Groups: video, dialout, plugdev
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, cover_run, runtime, tmpdir Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, noplace, device, cover_run, runtime, tmpdir
Home: /data/data/org.chromium.Chromium Home: /data/data/org.chromium.Chromium
Hostname: localhost Hostname: localhost
Path: /run/current-system/sw/bin/chromium Path: /run/current-system/sw/bin/chromium
@@ -354,6 +354,7 @@ App
"tty": true, "tty": true,
"multiarch": true, "multiarch": true,
"map_real_uid": true, "map_real_uid": true,
"noplace": true,
"device": true, "device": true,
"cover_run": true, "cover_run": true,
"share_runtime": true, "share_runtime": true,
@@ -506,6 +507,7 @@ App
"tty": true, "tty": true,
"multiarch": true, "multiarch": true,
"map_real_uid": true, "map_real_uid": true,
"noplace": true,
"device": true, "device": true,
"cover_run": true, "cover_run": true,
"share_runtime": true, "share_runtime": true,
@@ -705,6 +707,7 @@ func TestPrintPs(t *testing.T) {
"tty": true, "tty": true,
"multiarch": true, "multiarch": true,
"map_real_uid": true, "map_real_uid": true,
"noplace": true,
"device": true, "device": true,
"cover_run": true, "cover_run": true,
"share_runtime": true, "share_runtime": true,
+10
View File
@@ -65,6 +65,8 @@ const (
// Some programs fail to connect to dbus session running as a different uid, // Some programs fail to connect to dbus session running as a different uid,
// this option works around it by mapping priv-side caller uid in container. // this option works around it by mapping priv-side caller uid in container.
FMapRealUID FMapRealUID
// FNoPlace disables placement of /etc/passwd and /etc/group.
FNoPlace
// FDevice mount /dev/ from the init mount namespace as is in the container // FDevice mount /dev/ from the init mount namespace as is in the container
// mount namespace. // mount namespace.
@@ -101,6 +103,8 @@ func (flags Flags) String() string {
return "tty" return "tty"
case FMapRealUID: case FMapRealUID:
return "mapuid" return "mapuid"
case FNoPlace:
return "noplace"
case FDevice: case FDevice:
return "device" return "device"
case FCoverRun: case FCoverRun:
@@ -197,6 +201,8 @@ type containerConfigJSON = struct {
// Corresponds to [FMapRealUID]. // Corresponds to [FMapRealUID].
MapRealUID bool `json:"map_real_uid"` MapRealUID bool `json:"map_real_uid"`
// Corresponds to [FNoPlace].
NoPlace bool `json:"noplace,omitempty"`
// Corresponds to [FDevice]. // Corresponds to [FDevice].
Device bool `json:"device,omitempty"` Device bool `json:"device,omitempty"`
@@ -224,6 +230,7 @@ func (c *ContainerConfig) MarshalJSON() ([]byte, error) {
Tty: c.Flags&FTty != 0, Tty: c.Flags&FTty != 0,
Multiarch: c.Flags&FMultiarch != 0, Multiarch: c.Flags&FMultiarch != 0,
MapRealUID: c.Flags&FMapRealUID != 0, MapRealUID: c.Flags&FMapRealUID != 0,
NoPlace: c.Flags&FNoPlace != 0,
Device: c.Flags&FDevice != 0, Device: c.Flags&FDevice != 0,
CoverRun: c.Flags&FCoverRun != 0, CoverRun: c.Flags&FCoverRun != 0,
ShareRuntime: c.Flags&FShareRuntime != 0, ShareRuntime: c.Flags&FShareRuntime != 0,
@@ -266,6 +273,9 @@ func (c *ContainerConfig) UnmarshalJSON(data []byte) error {
if v.MapRealUID { if v.MapRealUID {
c.Flags |= FMapRealUID c.Flags |= FMapRealUID
} }
if v.NoPlace {
c.Flags |= FNoPlace
}
if v.Device { if v.Device {
c.Flags |= FDevice c.Flags |= FDevice
} }
+3 -3
View File
@@ -21,8 +21,8 @@ func TestFlagsString(t *testing.T) {
}{ }{
{"none", 0, "none"}, {"none", 0, "none"},
{"none high", hst.FAll + 1, "none"}, {"none high", hst.FAll + 1, "none"},
{"all", hst.FAll, "multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, cover_run, runtime, tmpdir"}, {"all", hst.FAll, "multiarch, compat, devel, userns, net, abstract, tty, mapuid, noplace, device, cover_run, runtime, tmpdir"},
{"all high", math.MaxUint, "multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, cover_run, runtime, tmpdir"}, {"all high", math.MaxUint, "multiarch, compat, devel, userns, net, abstract, tty, mapuid, noplace, device, cover_run, runtime, tmpdir"},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
@@ -53,7 +53,7 @@ func TestContainerConfig(t *testing.T) {
{"hostnet hostabstract mapuid", &hst.ContainerConfig{Flags: hst.FHostNet | hst.FHostAbstract | hst.FMapRealUID}, {"hostnet hostabstract mapuid", &hst.ContainerConfig{Flags: hst.FHostNet | hst.FHostAbstract | hst.FMapRealUID},
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"host_net":true,"host_abstract":true,"map_real_uid":true}`}, `{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"host_net":true,"host_abstract":true,"map_real_uid":true}`},
{"all", &hst.ContainerConfig{Flags: hst.FAll}, {"all", &hst.ContainerConfig{Flags: hst.FAll},
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"seccomp_compat":true,"devel":true,"userns":true,"host_net":true,"host_abstract":true,"tty":true,"multiarch":true,"map_real_uid":true,"device":true,"cover_run":true,"share_runtime":true,"share_tmpdir":true}`}, `{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"seccomp_compat":true,"devel":true,"userns":true,"host_net":true,"host_abstract":true,"tty":true,"multiarch":true,"map_real_uid":true,"noplace":true,"device":true,"cover_run":true,"share_runtime":true,"share_tmpdir":true}`},
} }
for _, tc := range testCases { for _, tc := range testCases {
+2 -2
View File
@@ -82,9 +82,9 @@ func (o *FSOverlay) Apply(z *ApplyState) {
z.Overlay(o.Target, o.Upper, o.Work, o.Lower...) z.Overlay(o.Target, o.Upper, o.Work, o.Lower...)
} else { } else {
z.OverlayEphemeral(o.Target, slices.Concat( z.OverlayEphemeral(o.Target, slices.Concat(
[]*check.Absolute{o.Upper},
o.Lower, o.Lower,
[]*check.Absolute{o.Upper})..., )...)
)
} }
} else { } else {
z.OverlayReadonly(o.Target, o.Lower...) z.OverlayReadonly(o.Target, o.Lower...)
+1 -1
View File
@@ -70,7 +70,7 @@ func TestFSOverlay(t *testing.T) {
Upper: m("/tmp/upper"), Upper: m("/tmp/upper"),
}, true, container.Ops{&container.MountOverlayOp{ }, true, container.Ops{&container.MountOverlayOp{
Target: m("/"), Target: m("/"),
Lower: ms("/tmp/.src0", "/tmp/.src1", "/tmp/upper"), Lower: ms("/tmp/upper", "/tmp/.src0", "/tmp/.src1"),
Upper: fhs.AbsRoot, Upper: fhs.AbsRoot,
}}, m("/"), ms("/tmp/upper", "/tmp/.src0", "/tmp/.src1"), }}, m("/"), ms("/tmp/upper", "/tmp/.src0", "/tmp/.src1"),
"e*/:/tmp/upper:/tmp/.src0:/tmp/.src1"}, "e*/:/tmp/upper:/tmp/.src0:/tmp/.src1"},
+1
View File
@@ -244,6 +244,7 @@ func TestTemplate(t *testing.T) {
"tty": true, "tty": true,
"multiarch": true, "multiarch": true,
"map_real_uid": true, "map_real_uid": true,
"noplace": true,
"device": true, "device": true,
"cover_run": true, "cover_run": true,
"share_runtime": true, "share_runtime": true,
+24 -4
View File
@@ -1,21 +1,29 @@
// Package env provides the [Paths] struct for efficiently building paths from the environment. // Package env provides the [Paths] struct for efficiently building paths from
// the environment.
package env package env
import ( import (
"errors"
"io/fs"
"log" "log"
"os" "os"
"strconv" "strconv"
"hakurei.app/check" "hakurei.app/check"
"hakurei.app/fhs"
"hakurei.app/hst" "hakurei.app/hst"
) )
const VarRunNscd = fhs.Var + "run/nscd"
// Paths holds paths copied from the environment and is used to create [hst.Paths]. // Paths holds paths copied from the environment and is used to create [hst.Paths].
type Paths struct { type Paths struct {
// TempDir is returned by [os.TempDir]. // TempDir is returned by [os.TempDir].
TempDir *check.Absolute TempDir *check.Absolute
// RuntimePath is copied from $XDG_RUNTIME_DIR. // RuntimePath is copied from $XDG_RUNTIME_DIR.
RuntimePath *check.Absolute RuntimePath *check.Absolute
// Whether [VarRunNscd] is a directory.
HasNscd bool
} }
// Copy expands [Paths] into [hst.Paths]. // Copy expands [Paths] into [hst.Paths].
@@ -37,14 +45,17 @@ func (env *Paths) Copy(v *hst.Paths, userid int) {
} }
// CopyPaths returns a populated [Paths]. // CopyPaths returns a populated [Paths].
func CopyPaths() *Paths { return CopyPathsFunc(log.Fatalf, os.TempDir, os.Getenv) } func CopyPaths() *Paths {
return CopyPathsFunc(log.Fatalf, os.TempDir, os.Getenv, os.Stat)
}
// CopyPathsFunc returns a populated [Paths], // CopyPathsFunc returns a populated [Paths], using the provided [log.Fatalf],
// using the provided [log.Fatalf], [os.TempDir], [os.Getenv] functions. // [os.TempDir], [os.Getenv] functions.
func CopyPathsFunc( func CopyPathsFunc(
fatalf func(format string, v ...any), fatalf func(format string, v ...any),
tempdir func() string, tempdir func() string,
getenv func(key string) string, getenv func(key string) string,
stat func(name string) (fs.FileInfo, error),
) *Paths { ) *Paths {
const xdgRuntimeDir = "XDG_RUNTIME_DIR" const xdgRuntimeDir = "XDG_RUNTIME_DIR"
@@ -61,5 +72,14 @@ func CopyPathsFunc(
env.RuntimePath = a env.RuntimePath = a
} }
if fi, err := stat(VarRunNscd); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
fatalf("%v", err)
panic("unreachable")
}
} else {
env.HasNscd = fi.IsDir()
}
return &env return &env
} }
+4 -1
View File
@@ -2,6 +2,7 @@ package env_test
import ( import (
"fmt" "fmt"
"io/fs"
"reflect" "reflect"
"testing" "testing"
@@ -104,7 +105,9 @@ func TestCopyPaths(t *testing.T) {
t.Fatalf("fatalf: %q, want %q", got, tc.fatal) t.Fatalf("fatalf: %q, want %q", got, tc.fatal)
} }
panic(stub.PanicExit) panic(stub.PanicExit)
}, func() string { return tc.tmp }, func(key string) string { return tc.env[key] }) }, func() string { return tc.tmp }, func(key string) string { return tc.env[key] }, func(name string) (fs.FileInfo, error) {
return nil, fs.ErrNotExist
})
if tc.fatal != "" { if tc.fatal != "" {
t.Fatalf("copyPaths: expected fatal %q", tc.fatal) t.Fatalf("copyPaths: expected fatal %q", tc.fatal)
+2
View File
@@ -23,6 +23,7 @@ import (
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/env"
"hakurei.app/internal/stub" "hakurei.app/internal/stub"
"hakurei.app/internal/system" "hakurei.app/internal/system"
"hakurei.app/message" "hakurei.app/message"
@@ -174,6 +175,7 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
call("cmdOutput", stub.ExpectArgs{container.Nonexistent, os.Stderr, []string{}, "/"}, []byte("0"), nil), call("cmdOutput", stub.ExpectArgs{container.Nonexistent, os.Stderr, []string{}, "/"}, []byte("0"), nil),
call("tempdir", stub.ExpectArgs{}, container.Nonexistent+"/tmp", nil), call("tempdir", stub.ExpectArgs{}, container.Nonexistent+"/tmp", nil),
call("lookupEnv", stub.ExpectArgs{"XDG_RUNTIME_DIR"}, wantRuntimePath, nil), call("lookupEnv", stub.ExpectArgs{"XDG_RUNTIME_DIR"}, wantRuntimePath, nil),
call("stat", stub.ExpectArgs{env.VarRunNscd}, stubFileInfoIsDir(true), nil),
call("getuid", stub.ExpectArgs{}, 1000, nil), call("getuid", stub.ExpectArgs{}, 1000, nil),
call("getgid", stub.ExpectArgs{}, 100, nil), call("getgid", stub.ExpectArgs{}, 100, nil),
+1 -1
View File
@@ -110,7 +110,7 @@ func newOutcomeState(k syscallDispatcher, msg message.Msg, id *hst.ID, config *h
Paths: env.CopyPathsFunc(k.fatalf, k.tempdir, func(key string) string { Paths: env.CopyPathsFunc(k.fatalf, k.tempdir, func(key string) string {
v, _ := k.lookupEnv(key) v, _ := k.lookupEnv(key)
return v return v
}), }, k.stat),
Container: config.Container, Container: config.Container,
} }
+2 -8
View File
@@ -143,10 +143,6 @@ func TestOutcomeRun(t *testing.T) {
// spTmpdirOp // spTmpdirOp
Bind(m("/tmp/hakurei.0/tmpdir/9"), fhs.AbsTmp, std.BindWritable). Bind(m("/tmp/hakurei.0/tmpdir/9"), fhs.AbsTmp, std.BindWritable).
// spAccountOp
Place(m("/etc/passwd"), []byte("chronos:x:1971:100:Hakurei:/data/data/org.chromium.Chromium:/run/current-system/sw/bin/zsh\n")).
Place(m("/etc/group"), []byte("hakurei:x:100:\n")).
// spWaylandOp // spWaylandOp
Bind(m("/tmp/hakurei.0/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/wayland"), m("/run/user/1971/wayland-0"), 0). Bind(m("/tmp/hakurei.0/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/wayland"), m("/run/user/1971/wayland-0"), 0).
@@ -453,7 +449,7 @@ func TestOutcomeRun(t *testing.T) {
Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"), Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"),
Flags: hst.FUserns | hst.FHostNet | hst.FMapRealUID | hst.FShareRuntime | hst.FShareTmpdir, Flags: hst.FUserns | hst.FHostNet | hst.FMapRealUID | hst.FNoPlace | hst.FShareRuntime | hst.FShareTmpdir,
}, },
SystemBus: &hst.BusConfig{ SystemBus: &hst.BusConfig{
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"}, Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
@@ -548,8 +544,6 @@ func TestOutcomeRun(t *testing.T) {
Tmpfs(m("/run/user/"), xdgRuntimeDirSize, 0755). Tmpfs(m("/run/user/"), xdgRuntimeDirSize, 0755).
Bind(m("/tmp/hakurei.0/runtime/1"), m("/run/user/1971"), std.BindWritable). Bind(m("/tmp/hakurei.0/runtime/1"), m("/run/user/1971"), std.BindWritable).
Bind(m("/tmp/hakurei.0/tmpdir/1"), m("/tmp/"), std.BindWritable). Bind(m("/tmp/hakurei.0/tmpdir/1"), m("/tmp/"), std.BindWritable).
Place(m("/etc/passwd"), []byte("u0_a1:x:1971:100:Hakurei:/var/lib/persist/module/hakurei/0/1:/run/current-system/sw/bin/zsh\n")).
Place(m("/etc/group"), []byte("hakurei:x:100:\n")).
Bind(m("/run/user/1971/wayland-0"), m("/run/user/1971/wayland-0"), 0). Bind(m("/run/user/1971/wayland-0"), m("/run/user/1971/wayland-0"), 0).
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/pipewire"), m("/run/user/1971/pipewire-0"), 0). Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/pipewire"), m("/run/user/1971/pipewire-0"), 0).
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus"), m("/run/user/1971/bus"), 0). Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus"), m("/run/user/1971/bus"), 0).
@@ -716,7 +710,7 @@ func (k *stubNixOS) lookupEnv(key string) (string, bool) {
func (k *stubNixOS) stat(name string) (fs.FileInfo, error) { func (k *stubNixOS) stat(name string) (fs.FileInfo, error) {
switch name { switch name {
case "/var/run/nscd": case "/var/run/nscd":
return nil, nil return stubFileInfoIsDir(true), nil
case "/run/user/1971/pulse": case "/run/user/1971/pulse":
return nil, nil return nil, nil
case "/run/user/1971/pulse/native": case "/run/user/1971/pulse/native":
-4
View File
@@ -78,10 +78,6 @@ func TestShimEntrypoint(t *testing.T) {
// spTmpdirOp // spTmpdirOp
Bind(m("/tmp/hakurei.10/tmpdir/9999"), fhs.AbsTmp, std.BindWritable). Bind(m("/tmp/hakurei.10/tmpdir/9999"), fhs.AbsTmp, std.BindWritable).
// spAccountOp
Place(m("/etc/passwd"), []byte("chronos:x:1000:100:Hakurei:/data/data/org.chromium.Chromium:/run/current-system/sw/bin/zsh\n")).
Place(m("/etc/group"), []byte("hakurei:x:100:\n")).
// spWaylandOp // spWaylandOp
Bind(m("/tmp/hakurei.10/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/wayland"), m("/run/user/1000/wayland-0"), 0). Bind(m("/tmp/hakurei.10/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/wayland"), m("/run/user/1000/wayland-0"), 0).
+3
View File
@@ -6,6 +6,7 @@ import (
"syscall" "syscall"
"hakurei.app/fhs" "hakurei.app/fhs"
"hakurei.app/hst"
"hakurei.app/internal/validate" "hakurei.app/internal/validate"
) )
@@ -41,6 +42,7 @@ func (s spAccountOp) toContainer(state *outcomeStateParams) error {
state.env["USER"] = username state.env["USER"] = username
state.env["SHELL"] = state.Container.Shell.String() state.env["SHELL"] = state.Container.Shell.String()
if state.Container.Flags&hst.FNoPlace == 0 {
state.params. state.params.
Place(fhs.AbsEtc.Append("passwd"), Place(fhs.AbsEtc.Append("passwd"),
[]byte(username+":x:"+ []byte(username+":x:"+
@@ -51,6 +53,7 @@ func (s spAccountOp) toContainer(state *outcomeStateParams) error {
state.Container.Shell.String()+"\n")). state.Container.Shell.String()+"\n")).
Place(fhs.AbsEtc.Append("group"), Place(fhs.AbsEtc.Append("group"),
[]byte("hakurei:x:"+state.mapgid.String()+":\n")) []byte("hakurei:x:"+state.mapgid.String()+":\n"))
}
return nil return nil
} }
+2 -3
View File
@@ -38,6 +38,7 @@ func TestSpAccountOp(t *testing.T) {
{"success fallback username", func(bool, bool) outcomeOp { return spAccountOp{} }, func() *hst.Config { {"success fallback username", func(bool, bool) outcomeOp { return spAccountOp{} }, func() *hst.Config {
c := hst.Template() c := hst.Template()
c.Container.Username = "" c.Container.Username = ""
c.Container.Flags = hst.FMapRealUID
return c return c
}, nil, []stub.Call{ }, nil, []stub.Call{
// this op performs basic validation and does not make calls during toSystem // this op performs basic validation and does not make calls during toSystem
@@ -60,9 +61,7 @@ func TestSpAccountOp(t *testing.T) {
// this op configures the container state and does not make calls during toContainer // this op configures the container state and does not make calls during toContainer
}, &container.Params{ }, &container.Params{
Dir: config.Container.Home, Dir: config.Container.Home,
Ops: new(container.Ops). Ops: new(container.Ops),
Place(m("/etc/passwd"), []byte("chronos:x:1000:100:Hakurei:/data/data/org.chromium.Chromium:/run/current-system/sw/bin/zsh\n")).
Place(m("/etc/group"), []byte("hakurei:x:100:\n")),
}, paramsWantEnv(config, map[string]string{ }, paramsWantEnv(config, map[string]string{
"HOME": config.Container.Home.String(), "HOME": config.Container.Home.String(),
"USER": config.Container.Username, "USER": config.Container.Username,
+14 -9
View File
@@ -18,13 +18,12 @@ import (
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/acl" "hakurei.app/internal/acl"
"hakurei.app/internal/dbus" "hakurei.app/internal/dbus"
"hakurei.app/internal/env"
"hakurei.app/internal/system" "hakurei.app/internal/system"
"hakurei.app/internal/validate" "hakurei.app/internal/validate"
"hakurei.app/message" "hakurei.app/message"
) )
const varRunNscd = fhs.Var + "run/nscd"
func init() { gob.Register(new(spParamsOp)) } func init() { gob.Register(new(spParamsOp)) }
// spParamsOp initialises unordered fields of [container.Params] and the // spParamsOp initialises unordered fields of [container.Params] and the
@@ -136,17 +135,23 @@ type spFilesystemOp struct {
} }
func (s *spFilesystemOp) toSystem(state *outcomeStateSys) error { func (s *spFilesystemOp) toSystem(state *outcomeStateSys) error {
/* retrieve paths and hide them if they're made available in the sandbox; // retrieve paths and hide them if they're made available in the sandbox
//
this feature tries to improve user experience of permissive defaults, and // this feature tries to improve user experience of permissive defaults, and
to warn about issues in custom configuration; it is NOT a security feature // to warn about issues in custom configuration; it is NOT a security feature
and should not be treated as such, ALWAYS be careful with what you bind */ // and should not be treated as such, ALWAYS be careful with what you bind
hidePaths := []string{ hidePaths := []string{
state.sc.RuntimePath.String(), state.sc.RuntimePath.String(),
state.sc.SharePath.String(), state.sc.SharePath.String(),
}
// this causes emulated passwd database to be bypassed on some /etc/ setups if state.Paths == nil || state.HasNscd {
varRunNscd, hidePaths = append(hidePaths,
// this causes emulated passwd database to be bypassed on some /etc/
// setups, made optional to avoid needlessly creating it on
// non-glibc systems when invoking permissive defaults
env.VarRunNscd,
)
} }
// dbus.Address does not go through syscallDispatcher // dbus.Address does not go through syscallDispatcher
+6
View File
@@ -278,6 +278,8 @@ type archiveArtifact struct {
f Artifact f Artifact
} }
var _ CuresExempt = archiveArtifact{}
// NewArchive returns a new [Artifact] backed by the supplied [Artifact]. The // NewArchive returns a new [Artifact] backed by the supplied [Artifact]. The
// source [Artifact] must be a [FileArtifact] and produce a stream compatible // source [Artifact] must be a [FileArtifact] and produce a stream compatible
// with [Reader]. // with [Reader].
@@ -403,3 +405,7 @@ func (a archiveArtifact) Cure(t *TContext) (err error) {
} }
return return
} }
// CuresExempt exempts the cheap [KindArchive] implementation often found at
// the end of a [FileArtifact] pipeline.
func (archiveArtifact) CuresExempt() {}
+5
View File
@@ -25,6 +25,7 @@ type decompressArtifact struct {
} }
var _ FileArtifact = new(decompressArtifact) var _ FileArtifact = new(decompressArtifact)
var _ CuresExempt = new(decompressArtifact)
// decompressArtifactNamed embeds decompressArtifact for a [fmt.Stringer] stream. // decompressArtifactNamed embeds decompressArtifact for a [fmt.Stringer] stream.
type decompressArtifactNamed struct { type decompressArtifactNamed struct {
@@ -117,3 +118,7 @@ func (a *decompressArtifact) Cure(r *RContext) (io.ReadCloser, error) {
return nil, os.ErrInvalid return nil, os.ErrInvalid
} }
} }
// CuresExempt exempts the cheap [KindDecompress] implementation often part of
// a [FileArtifact] pipeline.
func (*decompressArtifact) CuresExempt() {}
+4
View File
@@ -11,6 +11,7 @@ import (
type fileArtifact []byte type fileArtifact []byte
var _ KnownChecksum = new(fileArtifact) var _ KnownChecksum = new(fileArtifact)
var _ CuresExempt = new(fileArtifact)
// fileArtifactNamed embeds fileArtifact alongside a caller-supplied name. // fileArtifactNamed embeds fileArtifact alongside a caller-supplied name.
type fileArtifactNamed struct { type fileArtifactNamed struct {
@@ -79,3 +80,6 @@ func (a *fileArtifact) Checksum() Checksum {
func (a *fileArtifact) Cure(*RContext) (io.ReadCloser, error) { func (a *fileArtifact) Cure(*RContext) (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader(*a)), nil return io.NopCloser(bytes.NewReader(*a)), nil
} }
// CuresExempt exempts the cheap [KindFile] implementation.
func (*fileArtifact) CuresExempt() {}
+14
View File
@@ -485,6 +485,16 @@ type KnownChecksum interface {
Checksum() Checksum Checksum() Checksum
} }
// CuresExempt is optionally implemented for an artifact exempt to the
// cache-wide cures counter and limit.
type CuresExempt interface {
Artifact
// CuresExempt is a no-op function but serves to distinguish implementations
// that are cures-exempt.
CuresExempt()
}
// FileArtifact refers to an [Artifact] backed by a single file. // FileArtifact refers to an [Artifact] backed by a single file.
// //
// FileArtifact does not support fine-grained cancellation. Its context is // FileArtifact does not support fine-grained cancellation. Its context is
@@ -1892,6 +1902,10 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
} }
}() }()
if _, ok := a.(CuresExempt); ok {
curesExempt = true
}
var ( var (
ctx context.Context ctx context.Context
done chan<- struct{} done chan<- struct{}
+6
View File
@@ -16,6 +16,8 @@ type tarArtifact struct {
f Artifact f Artifact
} }
var _ CuresExempt = new(tarArtifact)
// tarArtifactNamed embeds tarArtifact for a [fmt.Stringer] tarball. // tarArtifactNamed embeds tarArtifact for a [fmt.Stringer] tarball.
type tarArtifactNamed struct { type tarArtifactNamed struct {
tarArtifact tarArtifact
@@ -211,3 +213,7 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
} }
return return
} }
// CuresExempt exempts the cheap [KindTar] implementation often at the end of a
// [FileArtifact] pipeline.
func (*tarArtifact) CuresExempt() {}
+5
View File
@@ -18,6 +18,8 @@ type MesonHelper struct {
// Flags passed to the setup command. // Flags passed to the setup command.
Setup []KV Setup []KV
// Test suites to skip.
SkipTests []string
// Whether to skip meson test. // Whether to skip meson test.
SkipTest bool SkipTest bool
// Run tests with interactive input/output. // Run tests with interactive input/output.
@@ -61,6 +63,9 @@ meson test \
scriptTest += ` \ scriptTest += ` \
--interactive` --interactive`
} }
for _, suite := range attr.SkipTests {
scriptTest += " \\\n\t--no-suite='" + suite + "'"
}
} }
return ` return `
+2 -2
View File
@@ -3,12 +3,12 @@ package cmake {
website = "https://cmake.org"; website = "https://cmake.org";
anitya = 306; anitya = 306;
version# = "4.3.3"; version# = "4.3.4";
source = remoteGitHubRelease { source = remoteGitHubRelease {
suffix = "Kitware/CMake"; suffix = "Kitware/CMake";
tag = "v"+version; tag = "v"+version;
name = "cmake-"+version+".tar.gz"; name = "cmake-"+version+".tar.gz";
checksum = "VS-b6cN4S9hfNv3JOUAbAfI9nh3EeuVwY_IVgUdgq6VKwvfchhXwvvFAUcpZG6Ez"; checksum = "6A50pqarKDXKOCv7ffKMvWJWmNTVp_ep8KoaWwNdg-RYQRddgKbxayHveTo6jZ_7";
compress = gzip; compress = gzip;
}; };
patches = [ patches = [
+2 -2
View File
@@ -3,12 +3,12 @@ package firmware {
website = "https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git"; website = "https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git";
anitya = 141464; anitya = 141464;
version# = "20260519"; version# = "20260622";
source = remoteGitLab { source = remoteGitLab {
domain = "gitlab.com"; domain = "gitlab.com";
suffix = "kernel-firmware/linux-firmware"; suffix = "kernel-firmware/linux-firmware";
ref = version; ref = version;
checksum = "l-wBRTWclYnJsgV4qtUV1-UL5Y4nknAPre8CMe0dH7PxtAqbaeudEIM_Fnuj0TCV"; checksum = "gpytkDM58EVjaUuryOekcekh_rWsfuv3S7LQxopHlNlVGmMsuqNwfug4BjgTgizE";
}; };
// dedup creates temporary file // dedup creates temporary file
+2
View File
@@ -19,6 +19,8 @@ package glib {
setup = { setup = {
"Ddefault_library": "both"; "Ddefault_library": "both";
}; };
// fails with ipv6 disabled
skipTests = [ "gio" ];
}; };
inputs = [ inputs = [
+6 -1
View File
@@ -48,8 +48,13 @@ go build -trimpath -tags=rosa -o /work/system/libexec/hakurei -ldflags="-s -w
-X hakurei.app/internal/info.buildVersion=$(cat cmd/dist/VERSION) -X hakurei.app/internal/info.buildVersion=$(cat cmd/dist/VERSION)
-X hakurei.app/internal/info.hakureiPath=/system/bin/hakurei -X hakurei.app/internal/info.hakureiPath=/system/bin/hakurei
-X hakurei.app/internal/info.hsuPath=/system/bin/hsu -X hakurei.app/internal/info.hsuPath=/system/bin/hsu
" ./cmd/hakurei ./cmd/sharefs
echo "Building hsu for $(go env GOOS)/$(go env GOARCH)."
CGO_ENABLED=0 go build -trimpath -tags=rosa -o /work/system/libexec/hakurei -ldflags="-s -w
-buildid=
-X main.hakureiPath=/system/bin/hakurei -X main.hakureiPath=/system/bin/hakurei
" ./... " ./cmd/hsu
echo`; echo`;
check = ` check = `
echo '##### Testing hakurei.' echo '##### Testing hakurei.'
+2 -2
View File
@@ -3,11 +3,11 @@ package kernel-source {
website = "https://kernel.org"; website = "https://kernel.org";
exclude = true; exclude = true;
version# = "6.12.93"; version# = "6.12.94";
output = remoteTar { output = remoteTar {
url = "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/"+ url = "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/"+
"snapshot/linux-"+version+".tar.gz"; "snapshot/linux-"+version+".tar.gz";
checksum = "cGFcgR-h4Vwv2BU78jV4HmU-3yU_ER8l8LyKF0MibEsB-kUbbrIgqxMedXZ1j8Xw"; checksum = "4bTz1hWJwsat4OzRAWqKUoyW0giZP6fDKG_-nzhghi7SgBqNFX3ZVr0DwwWs2_iJ";
compress = gzip; compress = gzip;
}; };
} }
+2 -2
View File
@@ -3,11 +3,11 @@ package libarchive {
website = "https://www.libarchive.org"; website = "https://www.libarchive.org";
anitya = 1558; anitya = 1558;
version# = "3.8.7"; version# = "3.8.8";
source = remoteGitHub { source = remoteGitHub {
suffix = "libarchive/libarchive"; suffix = "libarchive/libarchive";
tag = "v"+version; tag = "v"+version;
checksum = "CUJK4MDQmZmATClgQBH2Wt-7Ts4iiSUlg1J_TVb6-5IK3rVUgVLIMc5k-bnWB9w3"; checksum = "MYlQdlxHX14rFL_zEYvDArKgGVIueTPzSEDgCzaZkmi0_N7WsaEpogGO3qnHpfTc";
}; };
files = { "CTestCustom.cmake"; }; files = { "CTestCustom.cmake"; };
+2 -2
View File
@@ -3,12 +3,12 @@ package libffi {
website = "https://sourceware.org/libffi"; website = "https://sourceware.org/libffi";
anitya = 1611; anitya = 1611;
version# = "3.5.2"; version# = "3.6.0";
source = remoteGitHubRelease { source = remoteGitHubRelease {
suffix = "libffi/libffi"; suffix = "libffi/libffi";
tag = "v"+version; tag = "v"+version;
name = "libffi-"+version+".tar.gz"; name = "libffi-"+version+".tar.gz";
checksum = "2_Q-ZNBBbVhltfL5zEr0wljxPegUimTK4VeMSiwJEGksls3n4gj3lV0Ly3vviSFH"; checksum = "y3H_jP_eoByznlztjvni3wDfGA4CIJoOh3eRnzjnE3G3Ms3AWk53oOZxkbruvLxM";
compress = gzip; compress = gzip;
}; };
+2 -2
View File
@@ -4,12 +4,12 @@ package mesa {
anitya = 1970; anitya = 1970;
latest = anityaFallback; latest = anityaFallback;
version# = "26.1.2"; version# = "26.1.3";
source = remoteGitLab { source = remoteGitLab {
domain = "gitlab.freedesktop.org"; domain = "gitlab.freedesktop.org";
suffix = "mesa/mesa"; suffix = "mesa/mesa";
ref = "mesa-"+version; ref = "mesa-"+version;
checksum = "EcY_vsm4rjUzVj7jQraWb9i3y0I2F0oH3Tav01QszQMxNzjLbSWHrQYR1mPRU-J4"; checksum = "3Uk4-DVrqPhTb4NrLVSOvqpzzSI0kyAwDFgrP5RMzRZdnnGpnJ111llBTUYPlQGj";
}; };
exec = meson { exec = meson {
+1
View File
@@ -33,6 +33,7 @@ package openssl {
check = [ check = [
"HARNESS_JOBS=" + jobsE, "HARNESS_JOBS=" + jobsE,
"TESTS='-test_bio_dgram'",
"test", "test",
]; ];
}; };
+7 -7
View File
@@ -124,15 +124,15 @@ package python-vcs-versioning {
website = "https://setuptools-scm.readthedocs.io/en/latest"; website = "https://setuptools-scm.readthedocs.io/en/latest";
anitya = 389421; anitya = 389421;
version# = "1.1.1"; version# = "2.1.1";
source = remoteGitHub { source = remoteGitHub {
suffix = "pypa/setuptools-scm"; suffix = "pypa/setuptools-scm";
tag = "vcs-versioning-v"+version; tag = "vcs-versioning-v"+version;
checksum = "rXZixTsZcRcIoUC1LvWrjySsiXSv5uhW6ng2P-yXZrbdj7FrSrDeJLCfC2b-ladV"; checksum = "9QRY65iBhyohRC0xPJeq4KUalL-a7p3qTPeD7Y7l6O4qMfvq0psg0X-bb4WPqdGW";
}; };
env = [ env = [
"SETUPTOOLS_SCM_PRETEND_VERSION=" + version, "VCS_VERSIONING_PRETEND_VERSION=" + version,
]; ];
exec = pip { exec = pip {
@@ -158,11 +158,11 @@ package python-setuptools-scm {
website = "https://setuptools-scm.readthedocs.io/en/latest"; website = "https://setuptools-scm.readthedocs.io/en/latest";
anitya = 7874; anitya = 7874;
version# = "10.0.5"; version# = "10.1.2";
source = remoteGitHub { source = remoteGitHub {
suffix = "pypa/setuptools-scm"; suffix = "pypa/setuptools-scm";
tag = "setuptools-scm-v"+version; tag = "setuptools-scm-v"+version;
checksum = "vTN_TPd-b4Wbsw5WmAcsWjrs-FNXXznOeVTDnb54NtXve9Oy-eb2HPy-RG3FzNqp"; checksum = "pu9_XHYONnvziRwJ-Q44yjmCI0inPSCs0SvyAudTrpcdUluo65Fy-tmJkLgNOIzs";
}; };
env = [ env = [
@@ -382,11 +382,11 @@ package python-pytest {
website = "https://pytest.org"; website = "https://pytest.org";
anitya = 3765; anitya = 3765;
version# = "9.1.0"; version# = "9.1.1";
source = remoteGitHub { source = remoteGitHub {
suffix = "pytest-dev/pytest"; suffix = "pytest-dev/pytest";
tag = version; tag = version;
checksum = "UNd_5ArXTfdGROVW5a0Z22FE4uOLfCMW4NeAnAp9SKHLGja9Db2Xc3BF48x7Hr_l"; checksum = "PnsF2mxJDF1d1MeYfMmlrSa_OlI92F8OPlSVA-lqsh4ggRZiAHjbTdO_jfNox065";
}; };
env = [ env = [
@@ -0,0 +1,20 @@
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index be4fa627b5..d6af6a036b 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -29,7 +29,6 @@ qtests_generic = [
'test-hmp',
'qos-test',
'readconfig-test',
- 'netdev-socket',
]
if enable_modules
qtests_generic += [ 'modules-test' ]
@@ -402,7 +401,6 @@ qtests = {
'tpm-tis-device-test': [io, tpmemu_files, 'tpm-tis-util.c'],
'virtio-net-failover': test_migration_files,
'vmgenid-test': files('boot-sector.c', 'acpi-utils.c'),
- 'netdev-socket': files('netdev-socket.c', '../unit/socket-helpers.c'),
}
if vnc.found()
+5 -1
View File
@@ -9,7 +9,11 @@ package qemu {
checksum = "J3j3uNpiqxEoIEngBX2objV_1tzGfEgEphp5Ph86AJQvA_XMwYUakyvRH7YKEkwV"; checksum = "J3j3uNpiqxEoIEngBX2objV_1tzGfEgEphp5Ph86AJQvA_XMwYUakyvRH7YKEkwV";
compress = bzip2; compress = bzip2;
}; };
patches = [ "disable-mcast-test.patch" ]; patches = [
"disable-mcast-test.patch",
// fails with ipv6 disabled
"disable-netdev-socket.patch",
];
// configure script uses source as scratch space // configure script uses source as scratch space
writable = true; writable = true;
+2 -2
View File
@@ -5,11 +5,11 @@ package spirv-headers {
// upstream changed version scheme, anitya incapable of filtering them // upstream changed version scheme, anitya incapable of filtering them
latest = anityaFilterSPIRV; latest = anityaFilterSPIRV;
version# = "1.4.350.0"; version# = "1.4.350.1";
source = remoteGitHub { source = remoteGitHub {
suffix = "KhronosGroup/SPIRV-Headers"; suffix = "KhronosGroup/SPIRV-Headers";
tag = "vulkan-sdk-"+version; tag = "vulkan-sdk-"+version;
checksum = "wFCZquDVL4HoE-kWbS_BHHb_d71EYR2A2kVp08oDutektpnQzhDP89wo821GgcpG"; checksum = "6cmvMCH5aiHykXcozckfMOVA0nm0am4Xr2g9swBB9FtOV1vYBHgats5aRv4uQ9Kq";
}; };
exec = cmake { exec = cmake {
+2 -2
View File
@@ -3,11 +3,11 @@ package vim {
website = "https://www.vim.org"; website = "https://www.vim.org";
anitya = 5092; anitya = 5092;
version# = "9.2.0461"; version# = "9.2.0707";
source = remoteGitHub { source = remoteGitHub {
suffix = "vim/vim"; suffix = "vim/vim";
tag = "v"+version; tag = "v"+version;
checksum = "18Rr_5oIf_PkKuqVkN4CMZIGkZEgpN1vamlrsvPLBjn4mN98CRuoJmhzRZ7MoVYM"; checksum = "lWJTTs_CxKsj-uOZqoPEDk3Rgac6bK8RtV32uizRxEcqRwBtBRlmCpAuhRZsSLiG";
}; };
writable = true; writable = true;
+2 -2
View File
@@ -654,12 +654,12 @@ package xkeyboard-config {
website = "https://www.freedesktop.org/wiki/Software/XKeyboardConfig"; website = "https://www.freedesktop.org/wiki/Software/XKeyboardConfig";
anitya = 5191; anitya = 5191;
version# = "2.47"; version# = "2.48";
source = remoteGitLab { source = remoteGitLab {
domain = "gitlab.freedesktop.org"; domain = "gitlab.freedesktop.org";
suffix = "xkeyboard-config/xkeyboard-config"; suffix = "xkeyboard-config/xkeyboard-config";
ref = "xkeyboard-config-"+version; ref = "xkeyboard-config-"+version;
checksum = "E03PsPIaRrxPAuKgDGSQyPiJB49wXtyyvdV0lVx3_G-pelMMlaFLkoTDHTHG_qgA"; checksum = "_CxeFtwCaki-ZzoVtBgLnJ1p6tDJTAsfGmWJy867ij7equ5-lTKMV2YheE_Lbpo4";
}; };
exec = meson {}; exec = meson {};
+13 -1
View File
@@ -7,6 +7,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"io" "io"
"io/fs" "io/fs"
"iter" "iter"
@@ -879,6 +880,7 @@ func (s *S) getFrame() azalea.Frame {
k("postInstall"): &attr.Script, k("postInstall"): &attr.Script,
k("setup"): &attr.Setup, k("setup"): &attr.Setup,
k("skipTest"): &attr.SkipTest, k("skipTest"): &attr.SkipTest,
k("skipTests"): &attr.SkipTests,
k("interactiveTest"): &attr.InteractiveTest, k("interactiveTest"): &attr.InteractiveTest,
}); err != nil { }); err != nil {
return return
@@ -1255,6 +1257,16 @@ func (s *S) RegisterFS(fsys fs.FS) error {
// The resulting IR is curable on the daemon. Must not be used concurrently with // The resulting IR is curable on the daemon. Must not be used concurrently with
// any other method. // any other method.
func (s *S) SetSource(fsys fs.FS) error { func (s *S) SetSource(fsys fs.FS) error {
var version string
if p, err := fs.ReadFile(fsys, "cmd/dist/VERSION"); err != nil {
return err
} else if len(p) < 2 {
return fmt.Errorf("invalid version string %q", string(p))
} else {
version = unsafe.String(unsafe.SliceData(p), len(p))
}
version = version[1:len(version)-1] + "-CURRENT"
var buf bytes.Buffer var buf bytes.Buffer
w, err := gzip.NewWriterLevel(&buf, gzip.BestSpeed) w, err := gzip.NewWriterLevel(&buf, gzip.BestSpeed)
if err != nil { if err != nil {
@@ -1326,7 +1338,7 @@ func (s *S) SetSource(fsys fs.FS) error {
return &Metadata{ return &Metadata{
Name: name, Name: name,
Description: "hakurei source tree (current)", Description: "hakurei source tree (current)",
Version: "1.0.0-CURRENT", Version: version,
Exclude: true, Exclude: true,
}, pkg.NewTar(pkg.NewDecompress(a, pkg.Gzip)) }, pkg.NewTar(pkg.NewDecompress(a, pkg.Gzip))
}), }),