17 Commits

Author SHA1 Message Date
mae
33e11856c6 cmd/pkgserver: add status endpoint 2026-03-09 04:09:18 -05:00
mae
0f944f7a0e cmd/pkgserver: add createPackageIndex 2026-03-09 01:27:46 -05:00
mae
223037e7c2 cmd/pkgserver: add command handler 2026-03-08 22:28:08 -05:00
mae
acecad7f75 Merge remote-tracking branch 'origin/pkgserver' into pkgserver 2026-03-08 13:29:49 -05:00
mae
4f82f28c73 cmd/pkgserver: replace favicon 2026-03-08 13:29:15 -05:00
mae
ae07e0127b cmd/pkgserver: pagination 2026-03-08 13:29:15 -05:00
mae
d2696a6f30 cmd/pkgserver: basic web ui 2026-03-08 13:29:10 -05:00
mae
17ba70771c cmd/pkgserver: replace favicon 2026-03-08 13:29:01 -05:00
mae
93984f29da cmd/pkgserver: pagination 2026-03-08 13:29:01 -05:00
mae
d7cd746b43 cmd/pkgserver: basic web ui 2026-03-08 13:29:01 -05:00
mae
b255f07b0f Merge remote-tracking branch 'origin/pkgserver' into pkgserver 2026-03-05 02:06:27 -06:00
mae
dec4cdd068 cmd/pkgserver: replace favicon 2026-03-05 02:06:07 -06:00
mae
73c620ecd5 cmd/pkgserver: pagination 2026-03-05 02:06:07 -06:00
mae
69467a1542 cmd/pkgserver: basic web ui 2026-03-05 02:06:07 -06:00
mae
1ae6a35bc8 cmd/pkgserver: replace favicon 2026-03-05 01:12:17 -06:00
mae
9ef5b52b85 cmd/pkgserver: pagination 2026-03-05 00:32:25 -06:00
mae
f93158cb3c cmd/pkgserver: basic web ui 2026-03-04 22:50:58 -06:00
95 changed files with 1080 additions and 1488 deletions

View File

@@ -1,5 +1,5 @@
<p align="center"> <p align="center">
<a href="https://git.gensokyo.uk/rosa/hakurei"> <a href="https://git.gensokyo.uk/security/hakurei">
<picture> <picture>
<img src="https://basement.gensokyo.uk/images/yukari1.png" width="200px" alt="Yukari"> <img src="https://basement.gensokyo.uk/images/yukari1.png" width="200px" alt="Yukari">
</picture> </picture>
@@ -8,16 +8,16 @@
<p align="center"> <p align="center">
<a href="https://pkg.go.dev/hakurei.app"><img src="https://pkg.go.dev/badge/hakurei.app.svg" alt="Go Reference" /></a> <a href="https://pkg.go.dev/hakurei.app"><img src="https://pkg.go.dev/badge/hakurei.app.svg" alt="Go Reference" /></a>
<a href="https://git.gensokyo.uk/rosa/hakurei/actions"><img src="https://git.gensokyo.uk/rosa/hakurei/actions/workflows/test.yml/badge.svg?branch=staging&style=flat-square" alt="Gitea Workflow Status" /></a> <a href="https://git.gensokyo.uk/security/hakurei/actions"><img src="https://git.gensokyo.uk/security/hakurei/actions/workflows/test.yml/badge.svg?branch=staging&style=flat-square" alt="Gitea Workflow Status" /></a>
<br/> <br/>
<a href="https://git.gensokyo.uk/rosa/hakurei/releases"><img src="https://img.shields.io/gitea/v/release/rosa/hakurei?gitea_url=https%3A%2F%2Fgit.gensokyo.uk&color=purple" alt="Release" /></a> <a href="https://git.gensokyo.uk/security/hakurei/releases"><img src="https://img.shields.io/gitea/v/release/security/hakurei?gitea_url=https%3A%2F%2Fgit.gensokyo.uk&color=purple" alt="Release" /></a>
<a href="https://goreportcard.com/report/hakurei.app"><img src="https://goreportcard.com/badge/hakurei.app" alt="Go Report Card" /></a> <a href="https://goreportcard.com/report/hakurei.app"><img src="https://goreportcard.com/badge/hakurei.app" alt="Go Report Card" /></a>
<a href="https://hakurei.app"><img src="https://img.shields.io/website?url=https%3A%2F%2Fhakurei.app" alt="Website" /></a> <a href="https://hakurei.app"><img src="https://img.shields.io/website?url=https%3A%2F%2Fhakurei.app" alt="Website" /></a>
</p> </p>
Hakurei is a tool for running sandboxed desktop applications as dedicated Hakurei is a tool for running sandboxed desktop applications as dedicated
subordinate users on the Linux kernel. It implements the application container subordinate users on the Linux kernel. It implements the application container
of [planterette (WIP)](https://git.gensokyo.uk/rosa/planterette), a of [planterette (WIP)](https://git.gensokyo.uk/security/planterette), a
self-contained Android-like package manager with modern security features. self-contained Android-like package manager with modern security features.
Interaction with hakurei happens entirely through structures described by Interaction with hakurei happens entirely through structures described by
@@ -62,4 +62,4 @@ are very likely to be rejected.
## NixOS Module (deprecated) ## NixOS Module (deprecated)
The NixOS module is in maintenance mode and will be removed once planterette is The NixOS module is in maintenance mode and will be removed once planterette is
feature-complete. Full module documentation can be found [here](options.md). feature-complete. Full module documentation can be found [here](options.md).

View File

@@ -4,7 +4,6 @@ import (
"log" "log"
"os" "os"
"runtime" "runtime"
"strings"
. "syscall" . "syscall"
) )
@@ -13,22 +12,6 @@ func main() {
log.SetFlags(0) log.SetFlags(0)
log.SetPrefix("earlyinit: ") log.SetPrefix("earlyinit: ")
var (
option map[string]string
flags []string
)
if len(os.Args) > 1 {
option = make(map[string]string)
for _, s := range os.Args[1:] {
key, value, ok := strings.Cut(s, "=")
if !ok {
flags = append(flags, s)
continue
}
option[key] = value
}
}
if err := Mount( if err := Mount(
"devtmpfs", "devtmpfs",
"/dev/", "/dev/",
@@ -72,56 +55,4 @@ func main() {
} }
} }
// staying in rootfs, these are no longer used
must(os.Remove("/root"))
must(os.Remove("/init"))
must(os.Mkdir("/proc", 0))
mustSyscall("mount proc", Mount(
"proc",
"/proc",
"proc",
MS_NOSUID|MS_NOEXEC|MS_NODEV,
"hidepid=1",
))
must(os.Mkdir("/sys", 0))
mustSyscall("mount sysfs", Mount(
"sysfs",
"/sys",
"sysfs",
0,
"",
))
// after top level has been set up
mustSyscall("remount root", Mount(
"",
"/",
"",
MS_REMOUNT|MS_BIND|
MS_RDONLY|MS_NODEV|MS_NOSUID|MS_NOEXEC,
"",
))
must(os.WriteFile(
"/sys/module/firmware_class/parameters/path",
[]byte("/system/lib/firmware"),
0,
))
}
// mustSyscall calls [log.Fatalln] if err is non-nil.
func mustSyscall(action string, err error) {
if err != nil {
log.Fatalln("cannot "+action+":", err)
}
}
// must calls [log.Fatal] with err if it is non-nil.
func must(err error) {
if err != nil {
log.Fatal(err)
}
} }

View File

@@ -16,7 +16,6 @@ import (
"hakurei.app/command" "hakurei.app/command"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/container/std"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/dbus" "hakurei.app/internal/dbus"
"hakurei.app/internal/env" "hakurei.app/internal/env"
@@ -90,9 +89,6 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
flagHomeDir string flagHomeDir string
flagUserName string flagUserName string
flagSchedPolicy string
flagSchedPriority int
flagPrivateRuntime, flagPrivateTmpdir bool flagPrivateRuntime, flagPrivateTmpdir bool
flagWayland, flagX11, flagDBus, flagPipeWire, flagPulse bool flagWayland, flagX11, flagDBus, flagPipeWire, flagPulse bool
@@ -135,7 +131,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
log.Fatal(optionalErrorUnwrap(err)) log.Fatal(optionalErrorUnwrap(err))
return err return err
} else if progPath, err = check.NewAbs(p); err != nil { } else if progPath, err = check.NewAbs(p); err != nil {
log.Fatal(err) log.Fatal(err.Error())
return err return err
} }
} }
@@ -154,7 +150,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
et |= hst.EPipeWire et |= hst.EPipeWire
} }
config := hst.Config{ config := &hst.Config{
ID: flagID, ID: flagID,
Identity: flagIdentity, Identity: flagIdentity,
Groups: flagGroups, Groups: flagGroups,
@@ -181,13 +177,6 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
}, },
} }
if err := config.SchedPolicy.UnmarshalText(
[]byte(flagSchedPolicy),
); err != nil {
log.Fatal(err)
}
config.SchedPriority = std.Int(flagSchedPriority)
// bind GPU stuff // bind GPU stuff
if et&(hst.EX11|hst.EWayland) != 0 { if et&(hst.EX11|hst.EWayland) != 0 {
config.Container.Filesystem = append(config.Container.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{ config.Container.Filesystem = append(config.Container.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
@@ -225,7 +214,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
homeDir = passwd.HomeDir homeDir = passwd.HomeDir
} }
if a, err := check.NewAbs(homeDir); err != nil { if a, err := check.NewAbs(homeDir); err != nil {
log.Fatal(err) log.Fatal(err.Error())
return err return err
} else { } else {
config.Container.Home = a config.Container.Home = a
@@ -245,11 +234,11 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
config.SessionBus = dbus.NewConfig(flagID, true, flagDBusMpris) config.SessionBus = dbus.NewConfig(flagID, true, flagDBusMpris)
} else { } else {
if f, err := os.Open(flagDBusConfigSession); err != nil { if f, err := os.Open(flagDBusConfigSession); err != nil {
log.Fatal(err) log.Fatal(err.Error())
} else { } else {
decodeJSON(log.Fatal, "load session bus proxy config", f, &config.SessionBus) decodeJSON(log.Fatal, "load session bus proxy config", f, &config.SessionBus)
if err = f.Close(); err != nil { if err = f.Close(); err != nil {
log.Fatal(err) log.Fatal(err.Error())
} }
} }
} }
@@ -257,11 +246,11 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
// system bus proxy is optional // system bus proxy is optional
if flagDBusConfigSystem != "nil" { if flagDBusConfigSystem != "nil" {
if f, err := os.Open(flagDBusConfigSystem); err != nil { if f, err := os.Open(flagDBusConfigSystem); err != nil {
log.Fatal(err) log.Fatal(err.Error())
} else { } else {
decodeJSON(log.Fatal, "load system bus proxy config", f, &config.SystemBus) decodeJSON(log.Fatal, "load system bus proxy config", f, &config.SystemBus)
if err = f.Close(); err != nil { if err = f.Close(); err != nil {
log.Fatal(err) log.Fatal(err.Error())
} }
} }
} }
@@ -277,7 +266,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
} }
} }
outcome.Main(ctx, msg, &config, -1) outcome.Main(ctx, msg, config, -1)
panic("unreachable") panic("unreachable")
}). }).
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"), Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
@@ -298,10 +287,6 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
"Container home directory"). "Container home directory").
Flag(&flagUserName, "u", command.StringFlag("chronos"), Flag(&flagUserName, "u", command.StringFlag("chronos"),
"Passwd user name within sandbox"). "Passwd user name within sandbox").
Flag(&flagSchedPolicy, "policy", command.StringFlag(""),
"Scheduling policy to set for the container").
Flag(&flagSchedPriority, "priority", command.IntFlag(0),
"Scheduling priority to set for the container").
Flag(&flagPrivateRuntime, "private-runtime", command.BoolFlag(false), Flag(&flagPrivateRuntime, "private-runtime", command.BoolFlag(false),
"Do not share XDG_RUNTIME_DIR between containers under the same identity"). "Do not share XDG_RUNTIME_DIR between containers under the same identity").
Flag(&flagPrivateTmpdir, "private-tmpdir", command.BoolFlag(false), Flag(&flagPrivateTmpdir, "private-tmpdir", command.BoolFlag(false),

View File

@@ -36,7 +36,7 @@ Commands:
}, },
{ {
"run", []string{"run", "-h"}, ` "run", []string{"run", "-h"}, `
Usage: hakurei run [-h | --help] [--dbus-config <value>] [--dbus-system <value>] [--mpris] [--dbus-log] [--id <value>] [-a <int>] [-g <value>] [-d <value>] [-u <value>] [--policy <value>] [--priority <int>] [--private-runtime] [--private-tmpdir] [--wayland] [-X] [--dbus] [--pipewire] [--pulse] COMMAND [OPTIONS] Usage: hakurei run [-h | --help] [--dbus-config <value>] [--dbus-system <value>] [--mpris] [--dbus-log] [--id <value>] [-a <int>] [-g <value>] [-d <value>] [-u <value>] [--private-runtime] [--private-tmpdir] [--wayland] [-X] [--dbus] [--pipewire] [--pulse] COMMAND [OPTIONS]
Flags: Flags:
-X Enable direct connection to X11 -X Enable direct connection to X11
@@ -60,10 +60,6 @@ Flags:
Allow owning MPRIS D-Bus path, has no effect if custom config is available Allow owning MPRIS D-Bus path, has no effect if custom config is available
-pipewire -pipewire
Enable connection to PipeWire via SecurityContext Enable connection to PipeWire via SecurityContext
-policy string
Scheduling policy to set for the container
-priority int
Scheduling priority to set for the container
-private-runtime -private-runtime
Do not share XDG_RUNTIME_DIR between containers under the same identity Do not share XDG_RUNTIME_DIR between containers under the same identity
-private-tmpdir -private-tmpdir

View File

@@ -87,7 +87,7 @@ func main() {
} }
if flagIdle { if flagIdle {
pkg.SetSchedIdle = true pkg.SchedPolicy = container.SCHED_IDLE
} }
return return
@@ -175,17 +175,6 @@ func main() {
fmt.Println("website : " + fmt.Println("website : " +
strings.TrimSuffix(meta.Website, "/")) strings.TrimSuffix(meta.Website, "/"))
} }
if len(meta.Dependencies) > 0 {
fmt.Print("depends on :")
for _, d := range meta.Dependencies {
s := rosa.GetMetadata(d).Name
if version := rosa.Std.Version(d); version != rosa.Unversioned {
s += "-" + version
}
fmt.Print(" " + s)
}
fmt.Println()
}
const statusPrefix = "status : " const statusPrefix = "status : "
if flagStatus { if flagStatus {
@@ -434,8 +423,7 @@ func main() {
{ {
var ( var (
flagDump string flagDump string
flagExport string
) )
c.NewCommand( c.NewCommand(
"cure", "cure",
@@ -448,34 +436,10 @@ func main() {
return fmt.Errorf("unknown artifact %q", args[0]) return fmt.Errorf("unknown artifact %q", args[0])
} else if flagDump == "" { } else if flagDump == "" {
pathname, _, err := cache.Cure(rosa.Std.Load(p)) pathname, _, err := cache.Cure(rosa.Std.Load(p))
if err != nil { if err == nil {
return err log.Println(pathname)
} }
log.Println(pathname) return err
if flagExport != "" {
msg.Verbosef("exporting %s to %s...", args[0], flagExport)
var f *os.File
if f, err = os.OpenFile(
flagExport,
os.O_WRONLY|os.O_CREATE|os.O_EXCL,
0400,
); err != nil {
return err
} else if _, err = pkg.Flatten(
os.DirFS(pathname.String()),
".",
f,
); err != nil {
_ = f.Close()
return err
} else if err = f.Close(); err != nil {
return err
}
}
return nil
} else { } else {
f, err := os.OpenFile( f, err := os.OpenFile(
flagDump, flagDump,
@@ -499,11 +463,6 @@ func main() {
&flagDump, &flagDump,
"dump", command.StringFlag(""), "dump", command.StringFlag(""),
"Write IR to specified pathname and terminate", "Write IR to specified pathname and terminate",
).
Flag(
&flagExport,
"export", command.StringFlag(""),
"Export cured artifact to specified pathname",
) )
} }
@@ -518,19 +477,17 @@ func main() {
"shell", "shell",
"Interactive shell in the specified Rosa OS environment", "Interactive shell in the specified Rosa OS environment",
func(args []string) error { func(args []string) error {
presets := make([]rosa.PArtifact, len(args)) root := make([]pkg.Artifact, 0, 6+len(args))
for i, arg := range args { for _, arg := range args {
p, ok := rosa.ResolveName(arg) p, ok := rosa.ResolveName(arg)
if !ok { if !ok {
return fmt.Errorf("unknown artifact %q", arg) return fmt.Errorf("unknown artifact %q", arg)
} }
presets[i] = p root = append(root, rosa.Std.Load(p))
} }
root := make(rosa.Collect, 0, 6+len(args))
root = rosa.Std.AppendPresets(root, presets...)
if flagWithToolchain { if flagWithToolchain {
musl, compilerRT, runtimes, clang := (rosa.Std - 1).NewLLVM() musl, compilerRT, runtimes, clang := rosa.Std.NewLLVM()
root = append(root, musl, compilerRT, runtimes, clang) root = append(root, musl, compilerRT, runtimes, clang)
} else { } else {
root = append(root, rosa.Std.Load(rosa.Musl)) root = append(root, rosa.Std.Load(rosa.Musl))
@@ -540,12 +497,6 @@ func main() {
rosa.Std.Load(rosa.Toybox), rosa.Std.Load(rosa.Toybox),
) )
if _, _, err := cache.Cure(&root); err == nil {
return errors.New("unreachable")
} else if !errors.Is(err, rosa.Collected{}) {
return err
}
type cureRes struct { type cureRes struct {
pathname *check.Absolute pathname *check.Absolute
checksum unique.Handle[pkg.Checksum] checksum unique.Handle[pkg.Checksum]

229
cmd/pkgserver/main.go Normal file
View File

@@ -0,0 +1,229 @@
package main
import (
"bytes"
"cmp"
"context"
"embed"
"fmt"
"io"
"log"
"net/http"
"os"
"os/signal"
"path"
"slices"
"strings"
"syscall"
"hakurei.app/command"
"hakurei.app/container/check"
"hakurei.app/internal/pkg"
"hakurei.app/internal/rosa"
"hakurei.app/message"
)
//go:generate sh -c "sass ui/static/dark.scss ui/static/dark.css && sass ui/static/light.scss ui/static/light.css && tsc ui/static/index.ts"
//go:embed ui/*
var content embed.FS
func serveWebUI(w http.ResponseWriter, r *http.Request) {
fmt.Printf("serveWebUI: %s\n", r.URL.Path)
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-XSS-Protection", "1")
w.Header().Set("X-Frame-Options", "DENY")
http.ServeFileFS(w, r, content, "ui/index.html")
}
func serveStaticContent(w http.ResponseWriter, r *http.Request) {
fmt.Printf("serveStaticContent: %s\n", r.URL.Path)
switch r.URL.Path {
case "/static/style.css":
darkTheme := r.CookiesNamed("dark_theme")
if len(darkTheme) > 0 && darkTheme[0].Value == "true" {
http.ServeFileFS(w, r, content, "ui/static/dark.css")
} else {
http.ServeFileFS(w, r, content, "ui/static/light.css")
}
break
case "/favicon.ico":
http.ServeFileFS(w, r, content, "ui/static/favicon.ico")
break
case "/static/index.js":
http.ServeFileFS(w, r, content, "ui/static/index.js")
break
default:
http.NotFound(w, r)
}
}
func serveAPI(index *PackageIndex) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {}
}
func serveStatus(index *PackageIndex) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if index == nil {
http.Error(w, "index is nil", http.StatusInternalServerError)
return
}
base := path.Base(r.URL.Path)
name := strings.TrimSuffix(base, ".log")
p, ok := rosa.ResolveName(name)
if !ok {
http.NotFound(w, r)
return
}
m := rosa.GetMetadata(p)
pk, ok := index.names[m.Name]
if !ok {
http.NotFound(w, r)
return
}
if len(pk.status) > 0 {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.WriteHeader(http.StatusOK)
_, err := io.Copy(w, bytes.NewReader(pk.status))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
} else {
http.NotFound(w, r)
}
}
}
type SortOrders int
const (
DeclarationAscending SortOrders = iota
DeclarationDescending
NameAscending
NameDescending
limitSortOrders
)
type PackageIndex struct {
sorts [limitSortOrders][rosa.PresetUnexportedStart]*PackageIndexEntry
names map[string]*PackageIndexEntry
}
type PackageIndexEntry struct {
Name string `json:"name"`
Description string `json:"description"`
Website string `json:"website"`
Version string `json:"version"`
status []byte
}
func createPackageIndex(cache *pkg.Cache, report *rosa.Report) (_ *PackageIndex, err error) {
index := new(PackageIndex)
index.names = make(map[string]*PackageIndexEntry, rosa.PresetUnexportedStart)
work := make([]PackageIndexEntry, rosa.PresetUnexportedStart)
defer report.HandleAccess(&err)()
for p := range rosa.PresetUnexportedStart {
m := rosa.GetMetadata(p)
v := rosa.Std.Version(p)
a := rosa.Std.Load(p)
id := cache.Ident(a)
st, n := report.ArtifactOf(id)
var status []byte
if n < 1 {
status = nil
} else {
status = st
}
log.Printf("Processing package %s...\n", m.Name)
entry := PackageIndexEntry{
Name: m.Name,
Description: m.Description,
Website: m.Website,
Version: v,
status: status,
}
work[p] = entry
index.names[m.Name] = &entry
}
for i, p := range work {
index.sorts[DeclarationAscending][i] = &p
}
slices.Reverse(work)
for i, p := range work {
index.sorts[DeclarationDescending][i] = &p
}
slices.SortFunc(work, func(a PackageIndexEntry, b PackageIndexEntry) int {
return cmp.Compare(a.Name, b.Name)
})
for i, p := range work {
index.sorts[NameAscending][i] = &p
}
slices.Reverse(work)
for i, p := range work {
index.sorts[NameDescending][i] = &p
}
return index, err
}
func main() {
log.SetFlags(0)
log.SetPrefix("pkgserver: ")
var (
flagBaseDir string
flagPort int
)
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
defer stop()
msg := message.New(log.Default())
c := command.New(os.Stderr, log.Printf, "pkgserver", func(args []string) error {
reportPath := args[0]
baseDir, err := check.NewAbs(flagBaseDir)
if err != nil {
return err
}
log.Println("baseDir:", baseDir)
cache, err := pkg.Open(ctx, msg, 0, baseDir)
if err != nil {
return err
}
report, err := rosa.OpenReport(reportPath)
if err != nil {
return err
}
log.Println("reportPath:", reportPath)
log.Println("indexing packages...")
index, err := createPackageIndex(cache, report)
if err != nil {
return err
}
log.Println("created package index")
http.HandleFunc("GET /{$}", serveWebUI)
http.HandleFunc("GET /favicon.ico", serveStaticContent)
http.HandleFunc("GET /static/", serveStaticContent)
http.HandleFunc("GET /api/", serveAPI(index))
http.HandleFunc("GET /api/status/", serveStatus(index))
log.Println("listening on", flagPort)
err = http.ListenAndServe(fmt.Sprintf(":%d", flagPort), nil)
if err != nil {
return err
}
return nil
}).Flag(
&flagBaseDir,
"b", command.StringFlag(""),
"base directory for cache",
).Flag(
&flagPort,
"p", command.IntFlag(8067),
"http listen port",
)
c.MustParse(os.Args[1:], func(e error) {
log.Fatal(e)
})
}

View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="static/style.css">
<title>Hakurei PkgServer</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="static/index.js"></script>
</head>
<body>
<h1>Hakurei PkgServer</h1>
<table id="pkg-list">
<tr><th>Status</th><th>Name</th><th>Version</th></tr>
</table>
<p>Showing entries <span id="entry-counter"></span>.</p>
<span class="bottom-nav"><a href="javascript:prevPage()">&laquo; Previous</a> <span id="page-number">1</span> <a href="javascript:nextPage()">Next &raquo;</a></span>
<span><label for="count">Entries per page:</label><select name="count" id="count">
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select></span>
</body>
<footer>&copy; <a href="https://hakurei.app/">Hakurei</a>. Licensed under the MIT license.</footer>
</html>

View File

View File

@@ -0,0 +1,6 @@
@use 'common';
html {
background-color: #2c2c2c;
color: ghostwhite; }
/*# sourceMappingURL=dark.css.map */

View File

@@ -0,0 +1,7 @@
{
"version": 3,
"mappings": "AAAA,aAAa;AAEb,IAAK;EACH,gBAAgB,EAAE,OAAO;EACzB,KAAK,EAAE,UAAU",
"sources": ["dark.scss"],
"names": [],
"file": "dark.css"
}

View File

@@ -0,0 +1,6 @@
@use 'common';
html {
background-color: #2c2c2c;
color: ghostwhite;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,67 @@
"use strict";
var PackageEntry = /** @class */ (function () {
function PackageEntry() {
}
return PackageEntry;
}());
var State = /** @class */ (function () {
function State() {
this.entriesPerPage = 10;
this.currentPage = 1;
this.entryIndex = 0;
this.loadedEntries = [];
}
State.prototype.getEntriesPerPage = function () {
return this.entriesPerPage;
};
State.prototype.setEntriesPerPage = function (entriesPerPage) {
this.entriesPerPage = entriesPerPage;
this.updateRange();
};
State.prototype.getCurrentPage = function () {
return this.currentPage;
};
State.prototype.setCurrentPage = function (page) {
this.currentPage = page;
document.getElementById("page-number").innerText = String(this.currentPage);
this.updateRange();
};
State.prototype.getEntryIndex = function () {
return this.entryIndex;
};
State.prototype.setEntryIndex = function (entryIndex) {
this.entryIndex = entryIndex;
this.updateRange();
};
State.prototype.getLoadedEntries = function () {
return this.loadedEntries;
};
State.prototype.getMaxPage = function () {
return this.loadedEntries.length / this.entriesPerPage;
};
State.prototype.updateRange = function () {
var max = Math.min(this.entryIndex + this.entriesPerPage, this.loadedEntries.length);
document.getElementById("entry-counter").innerText = "".concat(this.entryIndex, "-").concat(max, " of ").concat(this.loadedEntries.length);
};
return State;
}());
var STATE;
function prevPage() {
var current = STATE.getCurrentPage();
if (current > 1) {
STATE.setCurrentPage(STATE.getCurrentPage() - 1);
}
}
function nextPage() {
var current = STATE.getCurrentPage();
if (current < STATE.getMaxPage()) {
STATE.setCurrentPage(STATE.getCurrentPage() + 1);
}
}
document.addEventListener("DOMContentLoaded", function () {
STATE = new State();
STATE.updateRange();
document.getElementById("count").addEventListener("change", function (event) {
STATE.setEntriesPerPage(parseInt(event.target.value));
});
});

View File

@@ -0,0 +1,66 @@
"use strict"
class PackageEntry {
}
class State {
entriesPerPage: number = 10
currentPage: number = 1
entryIndex: number = 0
loadedEntries: PackageEntry[] = []
getEntriesPerPage(): number {
return this.entriesPerPage
}
setEntriesPerPage(entriesPerPage: number) {
this.entriesPerPage = entriesPerPage
this.updateRange()
}
getCurrentPage(): number {
return this.currentPage
}
setCurrentPage(page: number) {
this.currentPage = page
document.getElementById("page-number").innerText = String(this.currentPage)
this.updateRange()
}
getEntryIndex(): number {
return this.entryIndex
}
setEntryIndex(entryIndex: number) {
this.entryIndex = entryIndex
this.updateRange()
}
getLoadedEntries(): PackageEntry[] {
return this.loadedEntries
}
getMaxPage(): number {
return this.loadedEntries.length / this.entriesPerPage
}
updateRange() {
let max = Math.min(this.entryIndex + this.entriesPerPage, this.loadedEntries.length)
document.getElementById("entry-counter").innerText = `${this.entryIndex}-${max} of ${this.loadedEntries.length}`
}
}
let STATE: State
function prevPage() {
let current = STATE.getCurrentPage()
if (current > 1) {
STATE.setCurrentPage(STATE.getCurrentPage() - 1)
}
}
function nextPage() {
let current = STATE.getCurrentPage()
if (current < STATE.getMaxPage()) {
STATE.setCurrentPage(STATE.getCurrentPage() + 1)
}
}
document.addEventListener("DOMContentLoaded", () => {
STATE = new State()
STATE.updateRange()
document.getElementById("count").addEventListener("change", (event) => {
STATE.setEntriesPerPage(parseInt((event.target as HTMLSelectElement).value))
})
})

View File

@@ -0,0 +1,6 @@
@use 'common';
html {
background-color: #d3d3d3;
color: black; }
/*# sourceMappingURL=light.css.map */

View File

@@ -0,0 +1,7 @@
{
"version": 3,
"mappings": "AAAA,aAAa;AAEb,IAAK;EACH,gBAAgB,EAAE,OAAO;EACzB,KAAK,EAAE,KAAK",
"sources": ["light.scss"],
"names": [],
"file": "light.css"
}

View File

@@ -0,0 +1,6 @@
@use 'common';
html {
background-color: #d3d3d3;
color: black;
}

View File

@@ -38,13 +38,9 @@ type (
Container struct { Container struct {
// Whether the container init should stay alive after its parent terminates. // Whether the container init should stay alive after its parent terminates.
AllowOrphan bool AllowOrphan bool
// Whether to set SchedPolicy and SchedPriority via sched_setscheduler(2). // Scheduling policy to set via sched_setscheduler(2). The zero value
SetScheduler bool // skips this call. Supported policies are [SCHED_BATCH], [SCHED_IDLE].
// Scheduling policy to set via sched_setscheduler(2). SchedPolicy int
SchedPolicy std.SchedPolicy
// Scheduling priority to set via sched_setscheduler(2). The zero value
// implies the minimum value supported by the current SchedPolicy.
SchedPriority std.Int
// Cgroup fd, nil to disable. // Cgroup fd, nil to disable.
Cgroup *int Cgroup *int
// ExtraFiles passed through to initial process in the container, with // ExtraFiles passed through to initial process in the container, with
@@ -377,38 +373,16 @@ func (p *Container) Start() error {
// sched_setscheduler: thread-directed but acts on all processes // sched_setscheduler: thread-directed but acts on all processes
// created from the calling thread // created from the calling thread
if p.SetScheduler { if p.SchedPolicy > 0 {
if p.SchedPolicy < 0 || p.SchedPolicy > std.SCHED_LAST { p.msg.Verbosef("setting scheduling policy %d", p.SchedPolicy)
return &StartError{
Fatal: false,
Step: "set scheduling policy",
Err: EINVAL,
}
}
var param schedParam
if priority, err := p.SchedPolicy.GetPriorityMin(); err != nil {
return &StartError{
Fatal: true,
Step: "get minimum priority",
Err: err,
}
} else {
param.priority = max(priority, p.SchedPriority)
}
p.msg.Verbosef(
"setting scheduling policy %s priority %d",
p.SchedPolicy, param.priority,
)
if err := schedSetscheduler( if err := schedSetscheduler(
0, // calling thread 0, // calling thread
p.SchedPolicy, p.SchedPolicy,
&param, &schedParam{0},
); err != nil { ); err != nil {
return &StartError{ return &StartError{
Fatal: true, Fatal: true,
Step: "set scheduling policy", Step: "enforce landlock ruleset",
Err: err, Err: err,
} }
} }

View File

@@ -1,12 +1,6 @@
package std package std
import ( import "iter"
"encoding"
"iter"
"strconv"
"sync"
"syscall"
)
// Syscalls returns an iterator over all wired syscalls. // Syscalls returns an iterator over all wired syscalls.
func Syscalls() iter.Seq2[string, ScmpSyscall] { func Syscalls() iter.Seq2[string, ScmpSyscall] {
@@ -32,128 +26,3 @@ func SyscallResolveName(name string) (num ScmpSyscall, ok bool) {
num, ok = syscallNumExtra[name] num, ok = syscallNumExtra[name]
return return
} }
// SchedPolicy denotes a scheduling policy defined in include/uapi/linux/sched.h.
type SchedPolicy int
// include/uapi/linux/sched.h
const (
SCHED_NORMAL SchedPolicy = iota
SCHED_FIFO
SCHED_RR
SCHED_BATCH
_SCHED_ISO // SCHED_ISO: reserved but not implemented yet
SCHED_IDLE
SCHED_DEADLINE
SCHED_EXT
SCHED_LAST SchedPolicy = iota - 1
)
var _ encoding.TextMarshaler = SCHED_LAST
var _ encoding.TextUnmarshaler = new(SCHED_LAST)
// String returns a unique representation of policy, also used in encoding.
func (policy SchedPolicy) String() string {
switch policy {
case SCHED_NORMAL:
return ""
case SCHED_FIFO:
return "fifo"
case SCHED_RR:
return "rr"
case SCHED_BATCH:
return "batch"
case SCHED_IDLE:
return "idle"
case SCHED_DEADLINE:
return "deadline"
case SCHED_EXT:
return "ext"
default:
return "invalid policy " + strconv.Itoa(int(policy))
}
}
// MarshalText performs bounds checking and returns the result of String.
func (policy SchedPolicy) MarshalText() ([]byte, error) {
if policy == _SCHED_ISO || policy < 0 || policy > SCHED_LAST {
return nil, syscall.EINVAL
}
return []byte(policy.String()), nil
}
// InvalidSchedPolicyError is an invalid string representation of a [SchedPolicy].
type InvalidSchedPolicyError string
func (InvalidSchedPolicyError) Unwrap() error { return syscall.EINVAL }
func (e InvalidSchedPolicyError) Error() string {
return "invalid scheduling policy " + strconv.Quote(string(e))
}
// UnmarshalText is the inverse of MarshalText.
func (policy *SchedPolicy) UnmarshalText(text []byte) error {
switch string(text) {
case "fifo":
*policy = SCHED_FIFO
case "rr":
*policy = SCHED_RR
case "batch":
*policy = SCHED_BATCH
case "idle":
*policy = SCHED_IDLE
case "deadline":
*policy = SCHED_DEADLINE
case "ext":
*policy = SCHED_EXT
case "":
*policy = 0
return nil
default:
return InvalidSchedPolicyError(text)
}
return nil
}
// for sched_get_priority_max and sched_get_priority_min
var (
schedPriority [SCHED_LAST + 1][2]Int
schedPriorityErr [SCHED_LAST + 1][2]error
schedPriorityOnce [SCHED_LAST + 1][2]sync.Once
)
// GetPriorityMax returns the maximum priority value that can be used with the
// scheduling algorithm identified by policy.
func (policy SchedPolicy) GetPriorityMax() (Int, error) {
schedPriorityOnce[policy][0].Do(func() {
priority, _, errno := syscall.Syscall(
syscall.SYS_SCHED_GET_PRIORITY_MAX,
uintptr(policy),
0, 0,
)
schedPriority[policy][0] = Int(priority)
if errno != 0 {
schedPriorityErr[policy][0] = errno
}
})
return schedPriority[policy][0], schedPriorityErr[policy][0]
}
// GetPriorityMin returns the minimum priority value that can be used with the
// scheduling algorithm identified by policy.
func (policy SchedPolicy) GetPriorityMin() (Int, error) {
schedPriorityOnce[policy][1].Do(func() {
priority, _, errno := syscall.Syscall(
syscall.SYS_SCHED_GET_PRIORITY_MIN,
uintptr(policy),
0, 0,
)
schedPriority[policy][1] = Int(priority)
if errno != 0 {
schedPriorityErr[policy][1] = errno
}
})
return schedPriority[policy][1], schedPriorityErr[policy][1]
}

View File

@@ -1,11 +1,6 @@
package std_test package std_test
import ( import (
"encoding/json"
"errors"
"math"
"reflect"
"syscall"
"testing" "testing"
"hakurei.app/container/std" "hakurei.app/container/std"
@@ -24,90 +19,3 @@ func TestSyscallResolveName(t *testing.T) {
}) })
} }
} }
func TestSchedPolicyJSON(t *testing.T) {
t.Parallel()
testCases := []struct {
policy std.SchedPolicy
want string
encodeErr error
decodeErr error
}{
{std.SCHED_NORMAL, `""`, nil, nil},
{std.SCHED_FIFO, `"fifo"`, nil, nil},
{std.SCHED_RR, `"rr"`, nil, nil},
{std.SCHED_BATCH, `"batch"`, nil, nil},
{4, `"invalid policy 4"`, syscall.EINVAL, std.InvalidSchedPolicyError("invalid policy 4")},
{std.SCHED_IDLE, `"idle"`, nil, nil},
{std.SCHED_DEADLINE, `"deadline"`, nil, nil},
{std.SCHED_EXT, `"ext"`, nil, nil},
{math.MaxInt, `"iso"`, syscall.EINVAL, std.InvalidSchedPolicyError("iso")},
}
for _, tc := range testCases {
name := tc.policy.String()
if tc.policy == std.SCHED_NORMAL {
name = "normal"
}
t.Run(name, func(t *testing.T) {
t.Parallel()
got, err := json.Marshal(tc.policy)
if !errors.Is(err, tc.encodeErr) {
t.Fatalf("Marshal: error = %v, want %v", err, tc.encodeErr)
}
if err == nil && string(got) != tc.want {
t.Fatalf("Marshal: %s, want %s", string(got), tc.want)
}
var v std.SchedPolicy
if err = json.Unmarshal([]byte(tc.want), &v); !reflect.DeepEqual(err, tc.decodeErr) {
t.Fatalf("Unmarshal: error = %v, want %v", err, tc.decodeErr)
}
if err == nil && v != tc.policy {
t.Fatalf("Unmarshal: %d, want %d", v, tc.policy)
}
})
}
}
func TestSchedPolicyMinMax(t *testing.T) {
t.Parallel()
testCases := []struct {
policy std.SchedPolicy
min, max std.Int
err error
}{
{std.SCHED_NORMAL, 0, 0, nil},
{std.SCHED_FIFO, 1, 99, nil},
{std.SCHED_RR, 1, 99, nil},
{std.SCHED_BATCH, 0, 0, nil},
{4, -1, -1, syscall.EINVAL},
{std.SCHED_IDLE, 0, 0, nil},
{std.SCHED_DEADLINE, 0, 0, nil},
{std.SCHED_EXT, 0, 0, nil},
}
for _, tc := range testCases {
name := tc.policy.String()
if tc.policy == std.SCHED_NORMAL {
name = "normal"
}
t.Run(name, func(t *testing.T) {
t.Parallel()
if priority, err := tc.policy.GetPriorityMax(); !reflect.DeepEqual(err, tc.err) {
t.Fatalf("GetPriorityMax: error = %v, want %v", err, tc.err)
} else if priority != tc.max {
t.Fatalf("GetPriorityMax: %d, want %d", priority, tc.max)
}
if priority, err := tc.policy.GetPriorityMin(); !reflect.DeepEqual(err, tc.err) {
t.Fatalf("GetPriorityMin: error = %v, want %v", err, tc.err)
} else if priority != tc.min {
t.Fatalf("GetPriorityMin: %d, want %d", priority, tc.min)
}
})
}
}

View File

@@ -43,6 +43,18 @@ func Isatty(fd int) bool {
return r == 0 return r == 0
} }
// include/uapi/linux/sched.h
const (
SCHED_NORMAL = iota
SCHED_FIFO
SCHED_RR
SCHED_BATCH
_ // SCHED_ISO: reserved but not implemented yet
SCHED_IDLE
SCHED_DEADLINE
SCHED_EXT
)
// schedParam is equivalent to struct sched_param from include/linux/sched.h. // schedParam is equivalent to struct sched_param from include/linux/sched.h.
type schedParam struct { type schedParam struct {
// sched_priority // sched_priority
@@ -62,13 +74,13 @@ type schedParam struct {
// this if you do not have something similar in place! // this if you do not have something similar in place!
// //
// [very subtle to use correctly]: https://www.openwall.com/lists/musl/2016/03/01/4 // [very subtle to use correctly]: https://www.openwall.com/lists/musl/2016/03/01/4
func schedSetscheduler(tid int, policy std.SchedPolicy, param *schedParam) error { func schedSetscheduler(tid, policy int, param *schedParam) error {
if _, _, errno := Syscall( if r, _, errno := Syscall(
SYS_SCHED_SETSCHEDULER, SYS_SCHED_SETSCHEDULER,
uintptr(tid), uintptr(tid),
uintptr(policy), uintptr(policy),
uintptr(unsafe.Pointer(param)), uintptr(unsafe.Pointer(param)),
); errno != 0 { ); r < 0 {
return errno return errno
} }
return nil return nil

12
flake.lock generated
View File

@@ -7,11 +7,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1772985280, "lastModified": 1765384171,
"narHash": "sha256-FdrNykOoY9VStevU4zjSUdvsL9SzJTcXt4omdEDZDLk=", "narHash": "sha256-FuFtkJrW1Z7u+3lhzPRau69E0CNjADku1mLQQflUORo=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "8f736f007139d7f70752657dff6a401a585d6cbc", "rev": "44777152652bc9eacf8876976fa72cc77ca8b9d8",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -23,11 +23,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1772822230, "lastModified": 1765311797,
"narHash": "sha256-yf3iYLGbGVlIthlQIk5/4/EQDZNNEmuqKZkQssMljuw=", "narHash": "sha256-mSD5Ob7a+T2RNjvPvOA1dkJHGVrNVl8ZOrAwBjKBDQo=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "71caefce12ba78d84fe618cf61644dce01cf3a96", "rev": "09eb77e94fa25202af8f3e81ddc7353d9970ac1b",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -99,7 +99,7 @@
hakurei = pkgs.pkgsStatic.callPackage ./package.nix { hakurei = pkgs.pkgsStatic.callPackage ./package.nix {
inherit (pkgs) inherit (pkgs)
# passthru.buildInputs # passthru.buildInputs
go_1_26 go
clang clang
# nativeBuildInputs # nativeBuildInputs
@@ -182,7 +182,7 @@
let let
# this is used for interactive vm testing during development, where tests might be broken # this is used for interactive vm testing during development, where tests might be broken
package = self.packages.${pkgs.stdenv.hostPlatform.system}.hakurei.override { package = self.packages.${pkgs.stdenv.hostPlatform.system}.hakurei.override {
buildGo126Module = previousArgs: pkgs.pkgsStatic.buildGo126Module (previousArgs // { doCheck = false; }); buildGoModule = previousArgs: pkgs.pkgsStatic.buildGoModule (previousArgs // { doCheck = false; });
}; };
in in
{ {

2
go.mod
View File

@@ -1,3 +1,3 @@
module hakurei.app module hakurei.app
go 1.26 go 1.25

View File

@@ -6,137 +6,96 @@ import (
"strings" "strings"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/std"
) )
// Config configures an application container. // Config configures an application container, implemented in internal/app.
type Config struct { type Config struct {
// Reverse-DNS style configured arbitrary identifier string. // Reverse-DNS style configured arbitrary identifier string.
// // Passed to wayland security-context-v1 and used as part of defaults in dbus session proxy.
// This value is passed as is to Wayland security-context-v1 and used as
// part of defaults in D-Bus session proxy. The zero value causes a default
// value to be derived from the container instance.
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
// System services to make available in the container. // System services to make available in the container.
Enablements *Enablements `json:"enablements,omitempty"` Enablements *Enablements `json:"enablements,omitempty"`
// Session D-Bus proxy configuration. // Session D-Bus proxy configuration.
// // If set to nil, session bus proxy assume built-in defaults.
// Has no effect if [EDBus] but is not set in Enablements. The zero value
// assumes built-in defaults derived from ID.
SessionBus *BusConfig `json:"session_bus,omitempty"` SessionBus *BusConfig `json:"session_bus,omitempty"`
// System D-Bus proxy configuration. // System D-Bus proxy configuration.
// // If set to nil, system bus proxy is disabled.
// Has no effect if [EDBus] but is not set in Enablements. The zero value
// disables system bus proxy.
SystemBus *BusConfig `json:"system_bus,omitempty"` SystemBus *BusConfig `json:"system_bus,omitempty"`
// Direct access to Wayland socket, no attempt is made to attach // Direct access to wayland socket, no attempt is made to attach security-context-v1
// security-context-v1 and the bare socket is made available to the // and the bare socket is made available to the container.
// container.
// //
// This option is unsupported and will most likely enable full control over // This option is unsupported and most likely enables full control over the Wayland
// the Wayland session from within the container. Do not set this to true // session. Do not set this to true unless you are sure you know what you are doing.
// unless you are sure you know what you are doing.
DirectWayland bool `json:"direct_wayland,omitempty"` DirectWayland bool `json:"direct_wayland,omitempty"`
// Direct access to the PipeWire socket established via SecurityContext::Create, no
// Direct access to the PipeWire socket established via SecurityContext::Create, // attempt is made to start the pipewire-pulse server.
// no attempt is made to start the pipewire-pulse server.
// //
// The SecurityContext machinery is fatally flawed, it unconditionally sets // The SecurityContext machinery is fatally flawed, it blindly sets read and execute
// read and execute bits on all objects for clients with the lowest achievable // bits on all objects for clients with the lowest achievable privilege level (by
// privilege level (by setting PW_KEY_ACCESS to "restricted" or by satisfying // setting PW_KEY_ACCESS to "restricted"). This enables them to call any method
// all conditions of [the /.flatpak-info hack]). This enables them to call // targeting any object, and since Registry::Destroy checks for the read and execute bit,
// any method targeting any object, and since Registry::Destroy checks for // allows the destruction of any object other than PW_ID_CORE as well. This behaviour
// the read and execute bit, allows the destruction of any object other than // is implemented separately in media-session and wireplumber, with the wireplumber
// PW_ID_CORE as well. // implementation in Lua via an embedded Lua vm. In all known setups, wireplumber is
// in use, and there is no known way to change its behaviour and set permissions
// differently without replacing the Lua script. Also, since PipeWire relies on these
// permissions to work, reducing them is not possible.
// //
// This behaviour is implemented separately in media-session and wireplumber, // Currently, the only other sandboxed use case is flatpak, which is not aware of
// with the wireplumber implementation in Lua via an embedded Lua vm. In all // PipeWire and blindly exposes the bare PulseAudio socket to the container (behaves
// known setups, wireplumber is in use, and in that case, no option for // like DirectPulse). This socket is backed by the pipewire-pulse compatibility daemon,
// configuring this behaviour exists, without replacing the Lua script. // which obtains client pid via the SO_PEERCRED option. The PipeWire daemon, pipewire-pulse
// Also, since PipeWire relies on these permissions to work, reducing them // daemon and the session manager daemon then separately performs the /.flatpak-info hack
// was never possible in the first place. // described in https://git.gensokyo.uk/security/hakurei/issues/21. Under such use case,
// since the client has no direct access to PipeWire, insecure parts of the protocol are
// obscured by pipewire-pulse simply not implementing them, and thus hiding the flaws
// described above.
// //
// Currently, the only other sandboxed use case is flatpak, which is not // Hakurei does not rely on the /.flatpak-info hack. Instead, a socket is sets up via
// aware of PipeWire and blindly exposes the bare PulseAudio socket to the // SecurityContext. A pipewire-pulse server connected through it achieves the same
// container (behaves like DirectPulse). This socket is backed by the // permissions as flatpak does via the /.flatpak-info hack and is maintained for the
// pipewire-pulse compatibility daemon, which obtains client pid via the // life of the container.
// SO_PEERCRED option. The PipeWire daemon, pipewire-pulse daemon and the
// session manager daemon then separately performs [the /.flatpak-info hack].
// Under such use case, since the client has no direct access to PipeWire,
// insecure parts of the protocol are obscured by the absence of an
// equivalent API in PulseAudio, or pipewire-pulse simply not implementing
// them.
//
// Hakurei does not rely on [the /.flatpak-info hack]. Instead, a socket is
// sets up via SecurityContext. A pipewire-pulse server connected through it
// achieves the same permissions as flatpak does via [the /.flatpak-info hack]
// and is maintained for the life of the container.
//
// This option is unsupported and enables a denial-of-service attack as the
// sandboxed client is able to destroy any client object and thus
// disconnecting them from PipeWire, or destroy the SecurityContext object,
// preventing any further container creation.
// //
// This option is unsupported and enables a denial-of-service attack as the sandboxed
// client is able to destroy any client object and thus disconnecting them from PipeWire,
// or destroy the SecurityContext object preventing any further container creation.
// Do not set this to true, it is insecure under any configuration. // Do not set this to true, it is insecure under any configuration.
//
// [the /.flatpak-info hack]: https://git.gensokyo.uk/rosa/hakurei/issues/21
DirectPipeWire bool `json:"direct_pipewire,omitempty"` DirectPipeWire bool `json:"direct_pipewire,omitempty"`
// Direct access to PulseAudio socket, no attempt is made to establish pipewire-pulse
// Direct access to PulseAudio socket, no attempt is made to establish // server via a PipeWire socket with a SecurityContext attached and the bare socket
// pipewire-pulse server via a PipeWire socket with a SecurityContext // is made available to the container.
// attached, and the bare socket is made available to the container.
// //
// This option is unsupported and enables arbitrary code execution as the // This option is unsupported and enables arbitrary code execution as the PulseAudio
// PulseAudio server. // server. Do not set this to true, it is insecure under any configuration.
//
// Do not set this to true, it is insecure under any configuration.
DirectPulse bool `json:"direct_pulse,omitempty"` DirectPulse bool `json:"direct_pulse,omitempty"`
// Extra acl updates to perform before setuid. // Extra acl updates to perform before setuid.
ExtraPerms []ExtraPermConfig `json:"extra_perms,omitempty"` ExtraPerms []ExtraPermConfig `json:"extra_perms,omitempty"`
// Numerical application id, passed to hsu, used to derive init user // Numerical application id, passed to hsu, used to derive init user namespace credentials.
// namespace credentials.
Identity int `json:"identity"` Identity int `json:"identity"`
// Init user namespace supplementary groups inherited by all container processes. // Init user namespace supplementary groups inherited by all container processes.
Groups []string `json:"groups"` Groups []string `json:"groups"`
// Scheduling policy to set for the container.
//
// The zero value retains the current scheduling policy.
SchedPolicy std.SchedPolicy `json:"sched_policy,omitempty"`
// Scheduling priority to set for the container.
//
// The zero value implies the minimum priority of the current SchedPolicy.
// Has no effect if SchedPolicy is zero.
SchedPriority std.Int `json:"sched_priority,omitempty"`
// High level configuration applied to the underlying [container]. // High level configuration applied to the underlying [container].
Container *ContainerConfig `json:"container"` Container *ContainerConfig `json:"container"`
} }
var ( var (
// ErrConfigNull is returned by [Config.Validate] for an invalid configuration // ErrConfigNull is returned by [Config.Validate] for an invalid configuration that contains a null value for any
// that contains a null value for any field that must not be null. // field that must not be null.
ErrConfigNull = errors.New("unexpected null in config") ErrConfigNull = errors.New("unexpected null in config")
// ErrIdentityBounds is returned by [Config.Validate] for an out of bounds // ErrIdentityBounds is returned by [Config.Validate] for an out of bounds [Config.Identity] value.
// [Config.Identity] value.
ErrIdentityBounds = errors.New("identity out of bounds") ErrIdentityBounds = errors.New("identity out of bounds")
// ErrSchedPolicyBounds is returned by [Config.Validate] for an out of bounds // ErrEnviron is returned by [Config.Validate] if an environment variable name contains '=' or NUL.
// [Config.SchedPolicy] value.
ErrSchedPolicyBounds = errors.New("scheduling policy out of bounds")
// ErrEnviron is returned by [Config.Validate] if an environment variable
// name contains '=' or NUL.
ErrEnviron = errors.New("invalid environment variable name") ErrEnviron = errors.New("invalid environment variable name")
// ErrInsecure is returned by [Config.Validate] if the configuration is // ErrInsecure is returned by [Config.Validate] if the configuration is considered insecure.
// considered insecure.
ErrInsecure = errors.New("configuration is insecure") ErrInsecure = errors.New("configuration is insecure")
) )
@@ -153,13 +112,6 @@ func (config *Config) Validate() error {
Msg: "identity " + strconv.Itoa(config.Identity) + " out of range"} Msg: "identity " + strconv.Itoa(config.Identity) + " out of range"}
} }
if config.SchedPolicy < 0 || config.SchedPolicy > std.SCHED_LAST {
return &AppError{Step: "validate configuration", Err: ErrSchedPolicyBounds,
Msg: "scheduling policy " +
strconv.Itoa(int(config.SchedPolicy)) +
" out of range"}
}
if err := config.SessionBus.CheckInterfaces("session"); err != nil { if err := config.SessionBus.CheckInterfaces("session"); err != nil {
return err return err
} }

View File

@@ -22,10 +22,6 @@ func TestConfigValidate(t *testing.T) {
Msg: "identity -1 out of range"}}, Msg: "identity -1 out of range"}},
{"identity upper", &hst.Config{Identity: 10000}, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds, {"identity upper", &hst.Config{Identity: 10000}, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds,
Msg: "identity 10000 out of range"}}, Msg: "identity 10000 out of range"}},
{"sched lower", &hst.Config{SchedPolicy: -1}, &hst.AppError{Step: "validate configuration", Err: hst.ErrSchedPolicyBounds,
Msg: "scheduling policy -1 out of range"}},
{"sched upper", &hst.Config{SchedPolicy: 0xcafe}, &hst.AppError{Step: "validate configuration", Err: hst.ErrSchedPolicyBounds,
Msg: "scheduling policy 51966 out of range"}},
{"dbus session", &hst.Config{SessionBus: &hst.BusConfig{See: []string{""}}}, {"dbus session", &hst.Config{SessionBus: &hst.BusConfig{See: []string{""}}},
&hst.BadInterfaceError{Interface: "", Segment: "session"}}, &hst.BadInterfaceError{Interface: "", Segment: "session"}},
{"dbus system", &hst.Config{SystemBus: &hst.BusConfig{See: []string{""}}}, {"dbus system", &hst.Config{SystemBus: &hst.BusConfig{See: []string{""}}},

View File

@@ -16,20 +16,18 @@ const PrivateTmp = "/.hakurei"
var AbsPrivateTmp = check.MustAbs(PrivateTmp) var AbsPrivateTmp = check.MustAbs(PrivateTmp)
const ( const (
// WaitDelayDefault is used when WaitDelay has the zero value. // WaitDelayDefault is used when WaitDelay has its zero value.
WaitDelayDefault = 5 * time.Second WaitDelayDefault = 5 * time.Second
// WaitDelayMax is used when WaitDelay exceeds its value. // WaitDelayMax is used if WaitDelay exceeds its value.
WaitDelayMax = 30 * time.Second WaitDelayMax = 30 * time.Second
) )
const ( const (
// ExitFailure is returned if the container fails to start. // ExitFailure is returned if the container fails to start.
ExitFailure = iota + 1 ExitFailure = iota + 1
// ExitCancel is returned if the container is terminated by a shim-directed // ExitCancel is returned if the container is terminated by a shim-directed signal which cancels its context.
// signal which cancels its context.
ExitCancel ExitCancel
// ExitOrphan is returned when the shim is orphaned before priv side process // ExitOrphan is returned when the shim is orphaned before priv side delivers a signal.
// delivers a signal.
ExitOrphan ExitOrphan
// ExitRequest is returned when the priv side process requests shim exit. // ExitRequest is returned when the priv side process requests shim exit.
@@ -40,12 +38,10 @@ const (
type Flags uintptr type Flags uintptr
const ( const (
// FMultiarch unblocks system calls required for multiarch to work on // FMultiarch unblocks syscalls required for multiarch to work on applicable targets.
// multiarch-enabled targets (amd64, arm64).
FMultiarch Flags = 1 << iota FMultiarch Flags = 1 << iota
// FSeccompCompat changes emitted seccomp filter programs to be identical to // FSeccompCompat changes emitted seccomp filter programs to be identical to that of Flatpak.
// that of Flatpak in enabled rulesets.
FSeccompCompat FSeccompCompat
// FDevel unblocks ptrace and friends. // FDevel unblocks ptrace and friends.
FDevel FDevel
@@ -58,15 +54,12 @@ const (
// FTty unblocks dangerous terminal I/O (faking input). // FTty unblocks dangerous terminal I/O (faking input).
FTty FTty
// FMapRealUID maps the target user uid to the privileged user uid in the // FMapRealUID maps the target user uid to the privileged user uid in the container user namespace.
// container user namespace. // 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.
// 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.
FMapRealUID FMapRealUID
// 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.
FDevice FDevice
// FShareRuntime shares XDG_RUNTIME_DIR between containers under the same identity. // FShareRuntime shares XDG_RUNTIME_DIR between containers under the same identity.
@@ -119,37 +112,30 @@ func (flags Flags) String() string {
} }
} }
// ContainerConfig describes the container configuration to be applied to an // ContainerConfig describes the container configuration to be applied to an underlying [container].
// underlying [container]. It is validated by [Config.Validate].
type ContainerConfig struct { type ContainerConfig struct {
// Container UTS namespace hostname. // Container UTS namespace hostname.
Hostname string `json:"hostname,omitempty"` Hostname string `json:"hostname,omitempty"`
// Duration in nanoseconds to wait for after interrupting the initial process. // Duration in nanoseconds to wait for after interrupting the initial process.
// // Defaults to [WaitDelayDefault] if zero, or [WaitDelayMax] if greater than [WaitDelayMax].
// Defaults to [WaitDelayDefault] if zero, or [WaitDelayMax] if greater than // Values lesser than zero is equivalent to zero, bypassing [WaitDelayDefault].
// [WaitDelayMax]. Values lesser than zero is equivalent to zero, bypassing
// [WaitDelayDefault].
WaitDelay time.Duration `json:"wait_delay,omitempty"` WaitDelay time.Duration `json:"wait_delay,omitempty"`
// Initial process environment variables. // Initial process environment variables.
Env map[string]string `json:"env"` Env map[string]string `json:"env"`
// Container mount points. /* Container mount points.
//
// If the first element targets /, it is inserted early and excluded from If the first element targets /, it is inserted early and excluded from path hiding. */
// path hiding. Otherwise, an anonymous instance of tmpfs is set up on /.
Filesystem []FilesystemConfigJSON `json:"filesystem"` Filesystem []FilesystemConfigJSON `json:"filesystem"`
// String used as the username of the emulated user, validated against the // String used as the username of the emulated user, validated against the default NAME_REGEX from adduser.
// default NAME_REGEX from adduser.
//
// Defaults to passwd name of target uid or chronos. // Defaults to passwd name of target uid or chronos.
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
// Pathname of shell in the container filesystem to use for the emulated user. // Pathname of shell in the container filesystem to use for the emulated user.
Shell *check.Absolute `json:"shell"` Shell *check.Absolute `json:"shell"`
// Directory in the container filesystem to enter and use as the home // Directory in the container filesystem to enter and use as the home directory of the emulated user.
// directory of the emulated user.
Home *check.Absolute `json:"home"` Home *check.Absolute `json:"home"`
// Pathname to executable file in the container filesystem. // Pathname to executable file in the container filesystem.
@@ -162,7 +148,6 @@ type ContainerConfig struct {
} }
// ContainerConfigF is [ContainerConfig] stripped of its methods. // ContainerConfigF is [ContainerConfig] stripped of its methods.
//
// The [ContainerConfig.Flags] field does not survive a [json] round trip. // The [ContainerConfig.Flags] field does not survive a [json] round trip.
type ContainerConfigF ContainerConfig type ContainerConfigF ContainerConfig

View File

@@ -5,26 +5,8 @@ import (
"strings" "strings"
) )
// BadInterfaceError is returned when Interface fails an undocumented check in // BadInterfaceError is returned when Interface fails an undocumented check in xdg-dbus-proxy,
// xdg-dbus-proxy, which would have cause a silent failure. // which would have cause a silent failure.
//
// xdg-dbus-proxy fails without output when this condition is not met:
//
// char *dot = strrchr (filter->interface, '.');
// if (dot != NULL)
// {
// *dot = 0;
// if (strcmp (dot + 1, "*") != 0)
// filter->member = g_strdup (dot + 1);
// }
//
// trim ".*" since they are removed before searching for '.':
//
// if (g_str_has_suffix (name, ".*"))
// {
// name[strlen (name) - 2] = 0;
// wildcard = TRUE;
// }
type BadInterfaceError struct { type BadInterfaceError struct {
// Interface is the offending interface string. // Interface is the offending interface string.
Interface string Interface string
@@ -37,8 +19,7 @@ func (e *BadInterfaceError) Error() string {
if e == nil { if e == nil {
return "<nil>" return "<nil>"
} }
return "bad interface string " + strconv.Quote(e.Interface) + return "bad interface string " + strconv.Quote(e.Interface) + " in " + e.Segment + " bus configuration"
" in " + e.Segment + " bus configuration"
} }
// BusConfig configures the xdg-dbus-proxy process. // BusConfig configures the xdg-dbus-proxy process.
@@ -95,14 +76,31 @@ func (c *BusConfig) Interfaces(yield func(string) bool) {
} }
} }
// CheckInterfaces checks for invalid interface strings based on an undocumented // CheckInterfaces checks for invalid interface strings based on an undocumented check in xdg-dbus-error,
// check in xdg-dbus-error, returning [BadInterfaceError] if one is encountered. // returning [BadInterfaceError] if one is encountered.
func (c *BusConfig) CheckInterfaces(segment string) error { func (c *BusConfig) CheckInterfaces(segment string) error {
if c == nil { if c == nil {
return nil return nil
} }
for iface := range c.Interfaces { for iface := range c.Interfaces {
/*
xdg-dbus-proxy fails without output when this condition is not met:
char *dot = strrchr (filter->interface, '.');
if (dot != NULL)
{
*dot = 0;
if (strcmp (dot + 1, "*") != 0)
filter->member = g_strdup (dot + 1);
}
trim ".*" since they are removed before searching for '.':
if (g_str_has_suffix (name, ".*"))
{
name[strlen (name) - 2] = 0;
wildcard = TRUE;
}
*/
if strings.IndexByte(strings.TrimSuffix(iface, ".*"), '.') == -1 { if strings.IndexByte(strings.TrimSuffix(iface, ".*"), '.') == -1 {
return &BadInterfaceError{iface, segment} return &BadInterfaceError{iface, segment}
} }

View File

@@ -11,17 +11,15 @@ import (
type Enablement byte type Enablement byte
const ( const (
// EWayland exposes a Wayland pathname socket via security-context-v1. // EWayland exposes a wayland pathname socket via security-context-v1.
EWayland Enablement = 1 << iota EWayland Enablement = 1 << iota
// EX11 adds the target user via X11 ChangeHosts and exposes the X11 // EX11 adds the target user via X11 ChangeHosts and exposes the X11 pathname socket.
// pathname socket.
EX11 EX11
// EDBus enables the per-container xdg-dbus-proxy daemon. // EDBus enables the per-container xdg-dbus-proxy daemon.
EDBus EDBus
// EPipeWire exposes a pipewire pathname socket via SecurityContext. // EPipeWire exposes a pipewire pathname socket via SecurityContext.
EPipeWire EPipeWire
// EPulse copies the PulseAudio cookie to [hst.PrivateTmp] and exposes the // EPulse copies the PulseAudio cookie to [hst.PrivateTmp] and exposes the PulseAudio socket.
// PulseAudio socket.
EPulse EPulse
// EM is a noop. // EM is a noop.

View File

@@ -24,8 +24,7 @@ type FilesystemConfig interface {
fmt.Stringer fmt.Stringer
} }
// The Ops interface enables [FilesystemConfig] to queue container ops without // The Ops interface enables [FilesystemConfig] to queue container ops without depending on the container package.
// depending on the container package.
type Ops interface { type Ops interface {
// Tmpfs appends an op that mounts tmpfs on a container path. // Tmpfs appends an op that mounts tmpfs on a container path.
Tmpfs(target *check.Absolute, size int, perm os.FileMode) Ops Tmpfs(target *check.Absolute, size int, perm os.FileMode) Ops
@@ -42,15 +41,12 @@ type Ops interface {
// Link appends an op that creates a symlink in the container filesystem. // Link appends an op that creates a symlink in the container filesystem.
Link(target *check.Absolute, linkName string, dereference bool) Ops Link(target *check.Absolute, linkName string, dereference bool) Ops
// Root appends an op that expands a directory into a toplevel bind mount // Root appends an op that expands a directory into a toplevel bind mount mirror on container root.
// mirror on container root.
Root(host *check.Absolute, flags int) Ops Root(host *check.Absolute, flags int) Ops
// Etc appends an op that expands host /etc into a toplevel symlink mirror // Etc appends an op that expands host /etc into a toplevel symlink mirror with /etc semantics.
// with /etc semantics.
Etc(host *check.Absolute, prefix string) Ops Etc(host *check.Absolute, prefix string) Ops
// Daemon appends an op that starts a daemon in the container and blocks // Daemon appends an op that starts a daemon in the container and blocks until target appears.
// until target appears.
Daemon(target, path *check.Absolute, args ...string) Ops Daemon(target, path *check.Absolute, args ...string) Ops
} }
@@ -65,8 +61,7 @@ type ApplyState struct {
// ErrFSNull is returned by [json] on encountering a null [FilesystemConfig] value. // ErrFSNull is returned by [json] on encountering a null [FilesystemConfig] value.
var ErrFSNull = errors.New("unexpected null in mount point") var ErrFSNull = errors.New("unexpected null in mount point")
// FSTypeError is returned when [ContainerConfig.Filesystem] contains an entry // FSTypeError is returned when [ContainerConfig.Filesystem] contains an entry with invalid type.
// with invalid type.
type FSTypeError string type FSTypeError string
func (f FSTypeError) Error() string { return fmt.Sprintf("invalid filesystem type %q", string(f)) } func (f FSTypeError) Error() string { return fmt.Sprintf("invalid filesystem type %q", string(f)) }

View File

@@ -18,9 +18,7 @@ type FSLink struct {
Target *check.Absolute `json:"dst"` Target *check.Absolute `json:"dst"`
// Arbitrary linkname value store in the symlink. // Arbitrary linkname value store in the symlink.
Linkname string `json:"linkname"` Linkname string `json:"linkname"`
// Whether to treat Linkname as an absolute pathname and dereference before creating the link.
// Whether to treat Linkname as an absolute pathname and dereference before
// creating the link.
Dereference bool `json:"dereference,omitempty"` Dereference bool `json:"dereference,omitempty"`
} }

View File

@@ -19,11 +19,9 @@ type FSOverlay struct {
// Any filesystem, does not need to be on a writable filesystem, must not be nil. // Any filesystem, does not need to be on a writable filesystem, must not be nil.
Lower []*check.Absolute `json:"lower"` Lower []*check.Absolute `json:"lower"`
// The upperdir is normally on a writable filesystem, leave as nil to mount // The upperdir is normally on a writable filesystem, leave as nil to mount Lower readonly.
// Lower readonly.
Upper *check.Absolute `json:"upper,omitempty"` Upper *check.Absolute `json:"upper,omitempty"`
// The workdir needs to be an empty directory on the same filesystem as // The workdir needs to be an empty directory on the same filesystem as Upper, must not be nil if Upper is populated.
// Upper, must not be nil if Upper is populated.
Work *check.Absolute `json:"work,omitempty"` Work *check.Absolute `json:"work,omitempty"`
} }

View File

@@ -44,13 +44,11 @@ func (e *AppError) Message() string {
type Paths struct { type Paths struct {
// Temporary directory returned by [os.TempDir], usually equivalent to [fhs.AbsTmp]. // Temporary directory returned by [os.TempDir], usually equivalent to [fhs.AbsTmp].
TempDir *check.Absolute `json:"temp_dir"` TempDir *check.Absolute `json:"temp_dir"`
// Shared directory specific to the hsu userid, usually // Shared directory specific to the hsu userid, usually (`/tmp/hakurei.%d`, [Info.User]).
// (`/tmp/hakurei.%d`, [Info.User]).
SharePath *check.Absolute `json:"share_path"` SharePath *check.Absolute `json:"share_path"`
// Checked XDG_RUNTIME_DIR value, usually (`/run/user/%d`, uid). // Checked XDG_RUNTIME_DIR value, usually (`/run/user/%d`, uid).
RuntimePath *check.Absolute `json:"runtime_path"` RuntimePath *check.Absolute `json:"runtime_path"`
// Shared directory specific to the hsu userid located in RuntimePath, // Shared directory specific to the hsu userid located in RuntimePath, usually (`/run/user/%d/hakurei`, uid).
// usually (`/run/user/%d/hakurei`, uid).
RunDirPath *check.Absolute `json:"run_dir_path"` RunDirPath *check.Absolute `json:"run_dir_path"`
} }
@@ -76,23 +74,10 @@ func Template() *Config {
SessionBus: &BusConfig{ SessionBus: &BusConfig{
See: nil, See: nil,
Talk: []string{ Talk: []string{"org.freedesktop.Notifications", "org.freedesktop.FileManager1", "org.freedesktop.ScreenSaver",
"org.freedesktop.Notifications", "org.freedesktop.secrets", "org.kde.kwalletd5", "org.kde.kwalletd6", "org.gnome.SessionManager"},
"org.freedesktop.FileManager1", Own: []string{"org.chromium.Chromium.*", "org.mpris.MediaPlayer2.org.chromium.Chromium.*",
"org.freedesktop.ScreenSaver", "org.mpris.MediaPlayer2.chromium.*"},
"org.freedesktop.secrets",
"org.kde.kwalletd5",
"org.kde.kwalletd6",
"org.gnome.SessionManager",
},
Own: []string{
"org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.chromium.*",
},
Call: map[string]string{"org.freedesktop.portal.*": "*"}, Call: map[string]string{"org.freedesktop.portal.*": "*"},
Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"}, Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
Log: false, Log: false,
@@ -127,12 +112,7 @@ func Template() *Config {
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT", "GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT",
}, },
Filesystem: []FilesystemConfigJSON{ Filesystem: []FilesystemConfigJSON{
{&FSBind{ {&FSBind{Target: fhs.AbsRoot, Source: fhs.AbsVarLib.Append("hakurei/base/org.debian"), Write: true, Special: true}},
Target: fhs.AbsRoot,
Source: fhs.AbsVarLib.Append("hakurei/base/org.debian"),
Write: true,
Special: true,
}},
{&FSBind{Target: fhs.AbsEtc, Source: fhs.AbsEtc, Special: true}}, {&FSBind{Target: fhs.AbsEtc, Source: fhs.AbsEtc, Special: true}},
{&FSEphemeral{Target: fhs.AbsTmp, Write: true, Perm: 0755}}, {&FSEphemeral{Target: fhs.AbsTmp, Write: true, Perm: 0755}},
{&FSOverlay{ {&FSOverlay{
@@ -141,27 +121,11 @@ func Template() *Config {
Upper: fhs.AbsVarLib.Append("hakurei/nix/u0/org.chromium.Chromium/rw-store/upper"), Upper: fhs.AbsVarLib.Append("hakurei/nix/u0/org.chromium.Chromium/rw-store/upper"),
Work: fhs.AbsVarLib.Append("hakurei/nix/u0/org.chromium.Chromium/rw-store/work"), Work: fhs.AbsVarLib.Append("hakurei/nix/u0/org.chromium.Chromium/rw-store/work"),
}}, }},
{&FSLink{ {&FSLink{Target: fhs.AbsRun.Append("current-system"), Linkname: "/run/current-system", Dereference: true}},
Target: fhs.AbsRun.Append("current-system"), {&FSLink{Target: fhs.AbsRun.Append("opengl-driver"), Linkname: "/run/opengl-driver", Dereference: true}},
Linkname: "/run/current-system", {&FSBind{Source: fhs.AbsVarLib.Append("hakurei/u0/org.chromium.Chromium"),
Dereference: true, Target: check.MustAbs("/data/data/org.chromium.Chromium"), Write: true, Ensure: true}},
}}, {&FSBind{Source: fhs.AbsDev.Append("dri"), Device: true, Optional: true}},
{&FSLink{
Target: fhs.AbsRun.Append("opengl-driver"),
Linkname: "/run/opengl-driver",
Dereference: true,
}},
{&FSBind{
Source: fhs.AbsVarLib.Append("hakurei/u0/org.chromium.Chromium"),
Target: check.MustAbs("/data/data/org.chromium.Chromium"),
Write: true,
Ensure: true,
}},
{&FSBind{
Source: fhs.AbsDev.Append("dri"),
Device: true,
Optional: true,
}},
}, },
Username: "chronos", Username: "chronos",

View File

@@ -12,12 +12,10 @@ import (
// An ID is a unique identifier held by a running hakurei container. // An ID is a unique identifier held by a running hakurei container.
type ID [16]byte type ID [16]byte
// ErrIdentifierLength is returned when encountering a [hex] representation of // ErrIdentifierLength is returned when encountering a [hex] representation of [ID] with unexpected length.
// [ID] with unexpected length.
var ErrIdentifierLength = errors.New("identifier string has unexpected length") var ErrIdentifierLength = errors.New("identifier string has unexpected length")
// IdentifierDecodeError is returned by [ID.UnmarshalText] to provide relevant // IdentifierDecodeError is returned by [ID.UnmarshalText] to provide relevant error descriptions.
// error descriptions.
type IdentifierDecodeError struct{ Err error } type IdentifierDecodeError struct{ Err error }
func (e IdentifierDecodeError) Unwrap() error { return e.Err } func (e IdentifierDecodeError) Unwrap() error { return e.Err }
@@ -25,10 +23,7 @@ func (e IdentifierDecodeError) Error() string {
var invalidByteError hex.InvalidByteError var invalidByteError hex.InvalidByteError
switch { switch {
case errors.As(e.Err, &invalidByteError): case errors.As(e.Err, &invalidByteError):
return fmt.Sprintf( return fmt.Sprintf("got invalid byte %#U in identifier", rune(invalidByteError))
"got invalid byte %#U in identifier",
rune(invalidByteError),
)
case errors.Is(e.Err, hex.ErrLength): case errors.Is(e.Err, hex.ErrLength):
return "odd length identifier hex string" return "odd length identifier hex string"
@@ -46,9 +41,7 @@ func (a *ID) CreationTime() time.Time {
} }
// NewInstanceID creates a new unique [ID]. // NewInstanceID creates a new unique [ID].
func NewInstanceID(id *ID) error { func NewInstanceID(id *ID) error { return newInstanceID(id, uint64(time.Now().UnixNano())) }
return newInstanceID(id, uint64(time.Now().UnixNano()))
}
// newInstanceID creates a new unique [ID] with the specified timestamp. // newInstanceID creates a new unique [ID] with the specified timestamp.
func newInstanceID(id *ID, p uint64) error { func newInstanceID(id *ID, p uint64) error {

View File

69
internal/azalea/azalea.go Normal file
View File

@@ -0,0 +1,69 @@
//go:generate gocc -a azalea.bnf
package azalea
import (
"io"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
"hakurei.app/container/check"
)
type Parser struct {
Generator
}
func NewParser(gen Generator) *Parser {
return &Parser{
Generator: gen,
}
}
func (p Parser) Initialise() {
}
func (p Parser) Consume(ns string, file io.Reader) error {
return nil
}
// ConsumeDir walks a directory and consumes all Azalea source files within it and all its subdirectories, as long as they end with the .az extension.
func (p Parser) ConsumeDir(dir *check.Absolute) error {
ds := dir.String()
return filepath.WalkDir(ds, func(path string, d fs.DirEntry, err error) (e error) {
if err != nil {
return err
}
if d.IsDir() || !strings.HasSuffix(d.Name(), ".az") {
return
}
rel, e := filepath.Rel(ds, path)
ns := strings.TrimSuffix(rel, ".az")
f, e := os.Open(path)
return p.Consume(ns, f)
})
}
// ConsumeAll consumes all provided readers as Azalea source code, each given the namespace `r%d` where `%d` is the index of the reader in the provided arguments.
func (p Parser) ConsumeAll(in ...io.Reader) error {
for i, r := range in {
err := p.Consume("r"+strconv.FormatInt(int64(i), 10), r)
if err != nil {
return err
}
}
return nil
}
// ConsumeStrings consumes all provided strings as Azalea source code, each given the namespace `s%d` where `%d` is the index of the string in the provided arugments.
func (p Parser) ConsumeStrings(in ...string) error {
for i, s := range in {
err := p.Consume("s"+strconv.FormatInt(int64(i), 10), strings.NewReader(s))
if err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,36 @@
package azalea
import (
"io"
)
type Generator interface {
Finalise() (error, io.Writer)
}
type JsonGenerator struct {
t any
}
func NewJsonGenerator[T any]() JsonGenerator {
t := new(T)
return JsonGenerator{
t,
}
}
func (j *JsonGenerator) Finalise() (error, io.Writer) {
}
type PkgIRGenerator struct {
}
func NewPkgIRGenerator() PkgIRGenerator {
return PkgIRGenerator{}
}
func (p *PkgIRGenerator) Finalise() (error, io.Writer) {
}

View File

@@ -38,7 +38,6 @@ func (h *Hsu) ensureDispatcher() {
} }
// ID returns the current user hsurc identifier. // ID returns the current user hsurc identifier.
//
// [ErrHsuAccess] is returned if the current user is not in hsurc. // [ErrHsuAccess] is returned if the current user is not in hsurc.
func (h *Hsu) ID() (int, error) { func (h *Hsu) ID() (int, error) {
h.ensureDispatcher() h.ensureDispatcher()

View File

@@ -1,5 +1,4 @@
// Package outcome implements the outcome of the privileged and container sides // Package outcome implements the outcome of the privileged and container sides of a hakurei container.
// of a hakurei container.
package outcome package outcome
import ( import (
@@ -28,9 +27,8 @@ func Info() *hst.Info {
return &hi return &hi
} }
// envAllocSize is the initial size of the env map pre-allocated when the // envAllocSize is the initial size of the env map pre-allocated when the configured env map is nil.
// configured env map is nil. It should be large enough to fit all insertions by // It should be large enough to fit all insertions by outcomeOp.toContainer.
// outcomeOp.toContainer.
const envAllocSize = 1 << 6 const envAllocSize = 1 << 6
func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} } func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} }
@@ -45,8 +43,7 @@ func (s *stringPair[T]) unwrap() T { return s.v }
func (s *stringPair[T]) String() string { return s.s } func (s *stringPair[T]) String() string { return s.s }
// outcomeState is copied to the shim process and available while applying outcomeOp. // outcomeState is copied to the shim process and available while applying outcomeOp.
// This is transmitted from the priv side to the shim, so exported fields should // This is transmitted from the priv side to the shim, so exported fields should be kept to a minimum.
// be kept to a minimum.
type outcomeState struct { type outcomeState struct {
// Params only used by the shim process. Populated by populateEarly. // Params only used by the shim process. Populated by populateEarly.
Shim *shimParams Shim *shimParams
@@ -92,25 +89,14 @@ func (s *outcomeState) valid() bool {
s.Paths != nil s.Paths != nil
} }
// newOutcomeState returns the address of a new outcomeState with its exported // newOutcomeState returns the address of a new outcomeState with its exported fields populated via syscallDispatcher.
// fields populated via syscallDispatcher.
func newOutcomeState(k syscallDispatcher, msg message.Msg, id *hst.ID, config *hst.Config, hsu *Hsu) *outcomeState { func newOutcomeState(k syscallDispatcher, msg message.Msg, id *hst.ID, config *hst.Config, hsu *Hsu) *outcomeState {
s := outcomeState{ s := outcomeState{
Shim: &shimParams{ Shim: &shimParams{PrivPID: k.getpid(), Verbose: msg.IsVerbose()},
PrivPID: k.getpid(), ID: id,
Verbose: msg.IsVerbose(), Identity: config.Identity,
UserID: hsu.MustID(msg),
SchedPolicy: config.SchedPolicy, Paths: env.CopyPathsFunc(k.fatalf, k.tempdir, func(key string) string { v, _ := k.lookupEnv(key); return v }),
SchedPriority: config.SchedPriority,
},
ID: id,
Identity: config.Identity,
UserID: hsu.MustID(msg),
Paths: env.CopyPathsFunc(k.fatalf, k.tempdir, func(key string) string {
v, _ := k.lookupEnv(key)
return v
}),
Container: config.Container, Container: config.Container,
} }
@@ -135,7 +121,6 @@ func newOutcomeState(k syscallDispatcher, msg message.Msg, id *hst.ID, config *h
} }
// populateLocal populates unexported fields from transmitted exported fields. // populateLocal populates unexported fields from transmitted exported fields.
//
// These fields are cheaper to recompute per-process. // These fields are cheaper to recompute per-process.
func (s *outcomeState) populateLocal(k syscallDispatcher, msg message.Msg) error { func (s *outcomeState) populateLocal(k syscallDispatcher, msg message.Msg) error {
if !s.valid() || k == nil || msg == nil { if !s.valid() || k == nil || msg == nil {
@@ -151,10 +136,7 @@ func (s *outcomeState) populateLocal(k syscallDispatcher, msg message.Msg) error
s.id = &stringPair[hst.ID]{*s.ID, s.ID.String()} s.id = &stringPair[hst.ID]{*s.ID, s.ID.String()}
s.Copy(&s.sc, s.UserID) s.Copy(&s.sc, s.UserID)
msg.Verbosef( msg.Verbosef("process share directory at %q, runtime directory at %q", s.sc.SharePath, s.sc.RunDirPath)
"process share directory at %q, runtime directory at %q",
s.sc.SharePath, s.sc.RunDirPath,
)
s.identity = newInt(s.Identity) s.identity = newInt(s.Identity)
s.mapuid, s.mapgid = newInt(s.Mapuid), newInt(s.Mapgid) s.mapuid, s.mapgid = newInt(s.Mapuid), newInt(s.Mapgid)
@@ -164,25 +146,17 @@ func (s *outcomeState) populateLocal(k syscallDispatcher, msg message.Msg) error
} }
// instancePath returns a path formatted for outcomeStateSys.instance. // instancePath returns a path formatted for outcomeStateSys.instance.
//
// This method must only be called from outcomeOp.toContainer if // This method must only be called from outcomeOp.toContainer if
// outcomeOp.toSystem has already called outcomeStateSys.instance. // outcomeOp.toSystem has already called outcomeStateSys.instance.
func (s *outcomeState) instancePath() *check.Absolute { func (s *outcomeState) instancePath() *check.Absolute { return s.sc.SharePath.Append(s.id.String()) }
return s.sc.SharePath.Append(s.id.String())
}
// runtimePath returns a path formatted for outcomeStateSys.runtime. // runtimePath returns a path formatted for outcomeStateSys.runtime.
//
// This method must only be called from outcomeOp.toContainer if // This method must only be called from outcomeOp.toContainer if
// outcomeOp.toSystem has already called outcomeStateSys.runtime. // outcomeOp.toSystem has already called outcomeStateSys.runtime.
func (s *outcomeState) runtimePath() *check.Absolute { func (s *outcomeState) runtimePath() *check.Absolute { return s.sc.RunDirPath.Append(s.id.String()) }
return s.sc.RunDirPath.Append(s.id.String())
}
// outcomeStateSys wraps outcomeState and [system.I]. Used on the priv side only. // outcomeStateSys wraps outcomeState and [system.I]. Used on the priv side only.
// // Implementations of outcomeOp must not access fields other than sys unless explicitly stated.
// Implementations of outcomeOp must not access fields other than sys unless
// explicitly stated.
type outcomeStateSys struct { type outcomeStateSys struct {
// Whether XDG_RUNTIME_DIR is used post hsu. // Whether XDG_RUNTIME_DIR is used post hsu.
useRuntimeDir bool useRuntimeDir bool
@@ -245,7 +219,6 @@ func (state *outcomeStateSys) ensureRuntimeDir() {
} }
// instance returns the pathname to a process-specific directory within TMPDIR. // instance returns the pathname to a process-specific directory within TMPDIR.
//
// This directory must only hold entries bound to [system.Process]. // This directory must only hold entries bound to [system.Process].
func (state *outcomeStateSys) instance() *check.Absolute { func (state *outcomeStateSys) instance() *check.Absolute {
if state.sharePath != nil { if state.sharePath != nil {
@@ -257,7 +230,6 @@ func (state *outcomeStateSys) instance() *check.Absolute {
} }
// runtime returns the pathname to a process-specific directory within XDG_RUNTIME_DIR. // runtime returns the pathname to a process-specific directory within XDG_RUNTIME_DIR.
//
// This directory must only hold entries bound to [system.Process]. // This directory must only hold entries bound to [system.Process].
func (state *outcomeStateSys) runtime() *check.Absolute { func (state *outcomeStateSys) runtime() *check.Absolute {
if state.runtimeSharePath != nil { if state.runtimeSharePath != nil {
@@ -270,29 +242,22 @@ func (state *outcomeStateSys) runtime() *check.Absolute {
return state.runtimeSharePath return state.runtimeSharePath
} }
// outcomeStateParams wraps outcomeState and [container.Params]. // outcomeStateParams wraps outcomeState and [container.Params]. Used on the shim side only.
//
// Used on the shim side only.
type outcomeStateParams struct { type outcomeStateParams struct {
// Overrides the embedded [container.Params] in [container.Container]. // Overrides the embedded [container.Params] in [container.Container]. The Env field must not be used.
//
// The Env field must not be used.
params *container.Params params *container.Params
// Collapsed into the Env slice in [container.Params] by the final outcomeOp. // Collapsed into the Env slice in [container.Params] by the final outcomeOp.
env map[string]string env map[string]string
// Filesystems with the optional root sliced off if present. // Filesystems with the optional root sliced off if present. Populated by spParamsOp.
// // Safe for use by spFilesystemOp.
// Populated by spParamsOp. Safe for use by spFilesystemOp.
filesystem []hst.FilesystemConfigJSON filesystem []hst.FilesystemConfigJSON
// Inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` via mapped uid. // Inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` via mapped uid.
//
// Populated by spRuntimeOp. // Populated by spRuntimeOp.
runtimeDir *check.Absolute runtimeDir *check.Absolute
// Path to pipewire-pulse server. // Path to pipewire-pulse server.
//
// Populated by spPipeWireOp if DirectPipeWire is false. // Populated by spPipeWireOp if DirectPipeWire is false.
pipewirePulsePath *check.Absolute pipewirePulsePath *check.Absolute
@@ -300,32 +265,25 @@ type outcomeStateParams struct {
*outcomeState *outcomeState
} }
// errNotEnabled is returned by outcomeOp.toSystem and used internally to // errNotEnabled is returned by outcomeOp.toSystem and used internally to exclude an outcomeOp from transmission.
// exclude an outcomeOp from transmission.
var errNotEnabled = errors.New("op not enabled in the configuration") var errNotEnabled = errors.New("op not enabled in the configuration")
// An outcomeOp inflicts an outcome on [system.I] and contains enough // An outcomeOp inflicts an outcome on [system.I] and contains enough information to
// information to inflict it on [container.Params] in a separate process. // inflict it on [container.Params] in a separate process.
// // An implementation of outcomeOp must store cross-process states in exported fields only.
// An implementation of outcomeOp must store cross-process states in exported
// fields only.
type outcomeOp interface { type outcomeOp interface {
// toSystem inflicts the current outcome on [system.I] in the priv side process. // toSystem inflicts the current outcome on [system.I] in the priv side process.
toSystem(state *outcomeStateSys) error toSystem(state *outcomeStateSys) error
// toContainer inflicts the current outcome on [container.Params] in the // toContainer inflicts the current outcome on [container.Params] in the shim process.
// shim process. // The implementation must not write to the Env field of [container.Params] as it will be overwritten
// // by flattened env map.
// Implementations must not write to the Env field of [container.Params]
// as it will be overwritten by flattened env map.
toContainer(state *outcomeStateParams) error toContainer(state *outcomeStateParams) error
} }
// toSystem calls the outcomeOp.toSystem method on all outcomeOp implementations // toSystem calls the outcomeOp.toSystem method on all outcomeOp implementations and populates shimParams.Ops.
// and populates shimParams.Ops. // This function assumes the caller has already called the Validate method on [hst.Config]
// // and checked that it returns nil.
// This function assumes the caller has already called the Validate method on
// [hst.Config] and checked that it returns nil.
func (state *outcomeStateSys) toSystem() error { func (state *outcomeStateSys) toSystem() error {
if state.Shim == nil || state.Shim.Ops != nil { if state.Shim == nil || state.Shim.Ops != nil {
return newWithMessage("invalid ops state reached") return newWithMessage("invalid ops state reached")

View File

@@ -30,9 +30,7 @@ const (
) )
// NewStore returns the address of a new instance of [store.Store]. // NewStore returns the address of a new instance of [store.Store].
func NewStore(sc *hst.Paths) *store.Store { func NewStore(sc *hst.Paths) *store.Store { return store.New(sc.SharePath.Append("state")) }
return store.New(sc.SharePath.Append("state"))
}
// main carries out outcome and terminates. main does not return. // main carries out outcome and terminates. main does not return.
func (k *outcome) main(msg message.Msg, identifierFd int) { func (k *outcome) main(msg message.Msg, identifierFd int) {
@@ -118,11 +116,7 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
processStatePrev, processStateCur = processStateCur, processState processStatePrev, processStateCur = processStateCur, processState
if !processTime.IsZero() && processStatePrev != processLifecycle { if !processTime.IsZero() && processStatePrev != processLifecycle {
msg.Verbosef( msg.Verbosef("state %d took %.2f ms", processStatePrev, float64(time.Since(processTime).Nanoseconds())/1e6)
"state %d took %.2f ms",
processStatePrev,
float64(time.Since(processTime).Nanoseconds())/1e6,
)
} }
processTime = time.Now() processTime = time.Now()
@@ -147,10 +141,7 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
case processCommit: case processCommit:
if isBeforeRevert { if isBeforeRevert {
perrorFatal( perrorFatal(newWithMessage("invalid transition to commit state"), "commit", processLifecycle)
newWithMessage("invalid transition to commit state"),
"commit", processLifecycle,
)
continue continue
} }
@@ -247,26 +238,15 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
case <-func() chan struct{} { case <-func() chan struct{} {
w := make(chan struct{}) w := make(chan struct{})
// This ties processLifecycle to ctx with the additional // this ties processLifecycle to ctx with the additional compensated timeout duration
// compensated timeout duration to allow transition to the next // to allow transition to the next state on a locked up shim
// state on a locked up shim. go func() { <-ctx.Done(); time.Sleep(k.state.Shim.WaitDelay + shimWaitTimeout); close(w) }()
go func() {
<-ctx.Done()
time.Sleep(k.state.Shim.WaitDelay + shimWaitTimeout)
close(w)
}()
return w return w
}(): }():
// This is only reachable when wait did not return within // this is only reachable when wait did not return within shimWaitTimeout, after its WaitDelay has elapsed.
// shimWaitTimeout, after its WaitDelay has elapsed. This is // This is different from the container failing to terminate within its timeout period, as that is enforced
// different from the container failing to terminate within its // by the shim. This path is instead reached when there is a lockup in shim preventing it from completing.
// timeout period, as that is enforced by the shim. This path is msg.GetLogger().Printf("process %d did not terminate", shimCmd.Process.Pid)
// instead reached when there is a lockup in shim preventing it
// from completing.
msg.GetLogger().Printf(
"process %d did not terminate",
shimCmd.Process.Pid,
)
} }
msg.Resume() msg.Resume()
@@ -291,8 +271,8 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
ec := system.Process ec := system.Process
if entries, _, err := handle.Entries(); err != nil { if entries, _, err := handle.Entries(); err != nil {
// it is impossible to continue from this point, per-process // it is impossible to continue from this point,
// state will be reverted to limit damage // per-process state will be reverted to limit damage
perror(err, "read store segment entries") perror(err, "read store segment entries")
} else { } else {
// accumulate enablements of remaining instances // accumulate enablements of remaining instances
@@ -315,10 +295,7 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
if n == 0 { if n == 0 {
ec |= system.User ec |= system.User
} else { } else {
msg.Verbosef( msg.Verbosef("found %d instances, cleaning up without user-scoped operations", n)
"found %d instances, cleaning up without user-scoped operations",
n,
)
} }
ec |= rt ^ (hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse) ec |= rt ^ (hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse)
if msg.IsVerbose() { if msg.IsVerbose() {
@@ -358,9 +335,7 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
// start starts the shim via cmd/hsu. // start starts the shim via cmd/hsu.
// //
// If successful, a [time.Time] value for [hst.State] is stored in the value // If successful, a [time.Time] value for [hst.State] is stored in the value pointed to by startTime.
// pointed to by startTime.
//
// The resulting [exec.Cmd] and write end of the shim setup pipe is returned. // The resulting [exec.Cmd] and write end of the shim setup pipe is returned.
func (k *outcome) start(ctx context.Context, msg message.Msg, func (k *outcome) start(ctx context.Context, msg message.Msg,
hsuPath *check.Absolute, hsuPath *check.Absolute,

View File

@@ -37,12 +37,9 @@ const (
shimMsgBadPID = C.HAKUREI_SHIM_BAD_PID shimMsgBadPID = C.HAKUREI_SHIM_BAD_PID
) )
// setupContSignal sets up the SIGCONT signal handler for the cross-uid shim // setupContSignal sets up the SIGCONT signal handler for the cross-uid shim exit hack.
// exit hack. // The signal handler is implemented in C, signals can be processed by reading from the returned reader.
// // The returned function must be called after all signal processing concludes.
// The signal handler is implemented in C, signals can be processed by reading
// from the returned reader. The returned function must be called after all
// signal processing concludes.
func setupContSignal(pid int) (io.ReadCloser, func(), error) { func setupContSignal(pid int) (io.ReadCloser, func(), error) {
if r, w, err := os.Pipe(); err != nil { if r, w, err := os.Pipe(); err != nil {
return nil, nil, err return nil, nil, err
@@ -54,30 +51,22 @@ func setupContSignal(pid int) (io.ReadCloser, func(), error) {
} }
} }
// shimEnv is the name of the environment variable storing decimal representation // shimEnv is the name of the environment variable storing decimal representation of
// of setup pipe fd for [container.Receive]. // setup pipe fd for [container.Receive].
const shimEnv = "HAKUREI_SHIM" const shimEnv = "HAKUREI_SHIM"
// shimParams is embedded in outcomeState and transmitted from priv side to shim. // shimParams is embedded in outcomeState and transmitted from priv side to shim.
type shimParams struct { type shimParams struct {
// Priv side pid, checked against ppid in signal handler for the // Priv side pid, checked against ppid in signal handler for the syscall.SIGCONT hack.
// syscall.SIGCONT hack.
PrivPID int PrivPID int
// Duration to wait for after the initial process receives os.Interrupt // Duration to wait for after the initial process receives os.Interrupt before the container is killed.
// before the container is killed.
//
// Limits are enforced on the priv side. // Limits are enforced on the priv side.
WaitDelay time.Duration WaitDelay time.Duration
// Verbosity pass through from [message.Msg]. // Verbosity pass through from [message.Msg].
Verbose bool Verbose bool
// Copied from [hst.Config].
SchedPolicy std.SchedPolicy
// Copied from [hst.Config].
SchedPriority std.Int
// Outcome setup ops, contains setup state. Populated by outcome.finalise. // Outcome setup ops, contains setup state. Populated by outcome.finalise.
Ops []outcomeOp Ops []outcomeOp
} }
@@ -88,9 +77,7 @@ func (p *shimParams) valid() bool { return p != nil && p.PrivPID > 0 }
// shimName is the prefix used by log.std in the shim process. // shimName is the prefix used by log.std in the shim process.
const shimName = "shim" const shimName = "shim"
// Shim is called by the main function of the shim process and runs as the // Shim is called by the main function of the shim process and runs as the unconstrained target user.
// unconstrained target user.
//
// Shim does not return. // Shim does not return.
func Shim(msg message.Msg) { func Shim(msg message.Msg) {
if msg == nil { if msg == nil {
@@ -144,8 +131,7 @@ func (sp *shimPrivate) destroy() {
} }
const ( const (
// shimPipeWireTimeout is the duration pipewire-pulse is allowed to run // shimPipeWireTimeout is the duration pipewire-pulse is allowed to run before its socket becomes available.
// before its socket becomes available.
shimPipeWireTimeout = 5 * time.Second shimPipeWireTimeout = 5 * time.Second
) )
@@ -276,9 +262,6 @@ func shimEntrypoint(k syscallDispatcher) {
cancelContainer.Store(&stop) cancelContainer.Store(&stop)
sp := shimPrivate{k: k, id: state.id} sp := shimPrivate{k: k, id: state.id}
z := container.New(ctx, msg) z := container.New(ctx, msg)
z.SetScheduler = state.Shim.SchedPolicy > 0
z.SchedPolicy = state.Shim.SchedPolicy
z.SchedPriority = state.Shim.SchedPriority
z.Params = *stateParams.params z.Params = *stateParams.params
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr

View File

@@ -27,9 +27,7 @@ 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 optional root filesystem.
// optional root filesystem.
//
// This outcomeOp is hardcoded to always run first. // This outcomeOp is hardcoded to always run first.
type spParamsOp struct { type spParamsOp struct {
// Value of $TERM, stored during toSystem. // Value of $TERM, stored during toSystem.
@@ -69,8 +67,8 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error {
state.params.Args = state.Container.Args state.params.Args = state.Container.Args
} }
// The container is cancelled when shim is requested to exit or receives an // the container is canceled when shim is requested to exit or receives an interrupt or termination signal;
// interrupt or termination signal. This behaviour is implemented in the shim. // this behaviour is implemented in the shim
state.params.ForwardCancel = state.Shim.WaitDelay > 0 state.params.ForwardCancel = state.Shim.WaitDelay > 0
if state.Container.Flags&hst.FMultiarch != 0 { if state.Container.Flags&hst.FMultiarch != 0 {
@@ -117,8 +115,7 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error {
} else { } else {
state.params.Bind(fhs.AbsDev, fhs.AbsDev, std.BindWritable|std.BindDevice) state.params.Bind(fhs.AbsDev, fhs.AbsDev, std.BindWritable|std.BindDevice)
} }
// /dev is mounted readonly later on, this prevents /dev/shm from going // /dev is mounted readonly later on, this prevents /dev/shm from going readonly with it
// readonly with it
state.params.Tmpfs(fhs.AbsDevShm, 0, 01777) state.params.Tmpfs(fhs.AbsDevShm, 0, 01777)
return nil return nil
@@ -126,9 +123,7 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error {
func init() { gob.Register(new(spFilesystemOp)) } func init() { gob.Register(new(spFilesystemOp)) }
// spFilesystemOp applies configured filesystems to [container.Params], // spFilesystemOp applies configured filesystems to [container.Params], excluding the optional root filesystem.
// excluding the optional root filesystem.
//
// This outcomeOp is hardcoded to always run last. // This outcomeOp is hardcoded to always run last.
type spFilesystemOp struct { type spFilesystemOp struct {
// Matched paths to cover. Stored during toSystem. // Matched paths to cover. Stored during toSystem.
@@ -302,8 +297,8 @@ func (s *spFilesystemOp) toContainer(state *outcomeStateParams) error {
return nil return nil
} }
// resolveRoot handles the root filesystem special case for [hst.FilesystemConfig] // resolveRoot handles the root filesystem special case for [hst.FilesystemConfig] and additionally resolves autoroot
// and additionally resolves autoroot as it requires special handling during path hiding. // as it requires special handling during path hiding.
func resolveRoot(c *hst.ContainerConfig) (rootfs hst.FilesystemConfig, filesystem []hst.FilesystemConfigJSON, autoroot *hst.FSBind) { func resolveRoot(c *hst.ContainerConfig) (rootfs hst.FilesystemConfig, filesystem []hst.FilesystemConfigJSON, autoroot *hst.FSBind) {
// root filesystem special case // root filesystem special case
filesystem = c.Filesystem filesystem = c.Filesystem
@@ -321,8 +316,7 @@ func resolveRoot(c *hst.ContainerConfig) (rootfs hst.FilesystemConfig, filesyste
return return
} }
// evalSymlinks calls syscallDispatcher.evalSymlinks but discards errors // evalSymlinks calls syscallDispatcher.evalSymlinks but discards errors unwrapping to [fs.ErrNotExist].
// unwrapping to [fs.ErrNotExist].
func evalSymlinks(msg message.Msg, k syscallDispatcher, v *string) error { func evalSymlinks(msg message.Msg, k syscallDispatcher, v *string) error {
if p, err := k.evalSymlinks(*v); err != nil { if p, err := k.evalSymlinks(*v); err != nil {
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {

View File

@@ -12,7 +12,6 @@ import (
func init() { gob.Register(new(spDBusOp)) } func init() { gob.Register(new(spDBusOp)) }
// spDBusOp maintains an xdg-dbus-proxy instance for the container. // spDBusOp maintains an xdg-dbus-proxy instance for the container.
//
// Runs after spRuntimeOp. // Runs after spRuntimeOp.
type spDBusOp struct { type spDBusOp struct {
// Whether to bind the system bus socket. Populated during toSystem. // Whether to bind the system bus socket. Populated during toSystem.

View File

@@ -13,12 +13,9 @@ const pipewirePulseName = "pipewire-pulse"
func init() { gob.Register(new(spPipeWireOp)) } func init() { gob.Register(new(spPipeWireOp)) }
// spPipeWireOp exports the PipeWire server to the container via SecurityContext. // spPipeWireOp exports the PipeWire server to the container via SecurityContext.
//
// Runs after spRuntimeOp. // Runs after spRuntimeOp.
type spPipeWireOp struct { type spPipeWireOp struct {
// Path to pipewire-pulse server. // Path to pipewire-pulse server. Populated during toSystem if DirectPipeWire is false.
//
// Populated during toSystem if DirectPipeWire is false.
CompatServerPath *check.Absolute CompatServerPath *check.Absolute
} }

View File

@@ -20,7 +20,6 @@ const pulseCookieSizeMax = 1 << 8
func init() { gob.Register(new(spPulseOp)) } func init() { gob.Register(new(spPulseOp)) }
// spPulseOp exports the PulseAudio server to the container. // spPulseOp exports the PulseAudio server to the container.
//
// Runs after spRuntimeOp. // Runs after spRuntimeOp.
type spPulseOp struct { type spPulseOp struct {
// PulseAudio cookie data, populated during toSystem if a cookie is present. // PulseAudio cookie data, populated during toSystem if a cookie is present.
@@ -38,40 +37,24 @@ func (s *spPulseOp) toSystem(state *outcomeStateSys) error {
if _, err := state.k.stat(pulseRuntimeDir.String()); err != nil { if _, err := state.k.stat(pulseRuntimeDir.String()); err != nil {
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
return &hst.AppError{Step: fmt.Sprintf( return &hst.AppError{Step: fmt.Sprintf("access PulseAudio directory %q", pulseRuntimeDir), Err: err}
"access PulseAudio directory %q",
pulseRuntimeDir,
), Err: err}
} }
return newWithMessageError(fmt.Sprintf( return newWithMessageError(fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir), err)
"PulseAudio directory %q not found",
pulseRuntimeDir,
), err)
} }
if fi, err := state.k.stat(pulseSocket.String()); err != nil { if fi, err := state.k.stat(pulseSocket.String()); err != nil {
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
return &hst.AppError{Step: fmt.Sprintf( return &hst.AppError{Step: fmt.Sprintf("access PulseAudio socket %q", pulseSocket), Err: err}
"access PulseAudio socket %q",
pulseSocket,
), Err: err}
} }
return newWithMessageError(fmt.Sprintf( return newWithMessageError(fmt.Sprintf("PulseAudio directory %q found but socket does not exist", pulseRuntimeDir), err)
"PulseAudio directory %q found but socket does not exist",
pulseRuntimeDir,
), err)
} else { } else {
if m := fi.Mode(); m&0o006 != 0o006 { if m := fi.Mode(); m&0o006 != 0o006 {
return newWithMessage(fmt.Sprintf( return newWithMessage(fmt.Sprintf("unexpected permissions on %q: %s", pulseSocket, m))
"unexpected permissions on %q: %s",
pulseSocket, m,
))
} }
} }
// PulseAudio socket is world writable and its parent directory DAC // pulse socket is world writable and its parent directory DAC permissions prevents access;
// permissions prevents access. Hard link to target-executable share // hard link to target-executable share directory to grant access
// directory to grant access
state.sys.Link(pulseSocket, state.runtime().Append("pulse")) state.sys.Link(pulseSocket, state.runtime().Append("pulse"))
// load up to pulseCookieSizeMax bytes of pulse cookie for transmission to shim // load up to pulseCookieSizeMax bytes of pulse cookie for transmission to shim
@@ -79,13 +62,7 @@ func (s *spPulseOp) toSystem(state *outcomeStateSys) error {
return err return err
} else if a != nil { } else if a != nil {
s.Cookie = new([pulseCookieSizeMax]byte) s.Cookie = new([pulseCookieSizeMax]byte)
if s.CookieSize, err = loadFile( if s.CookieSize, err = loadFile(state.msg, state.k, "PulseAudio cookie", a.String(), s.Cookie[:]); err != nil {
state.msg,
state.k,
"PulseAudio cookie",
a.String(),
s.Cookie[:],
); err != nil {
return err return err
} }
} else { } else {
@@ -124,9 +101,8 @@ func (s *spPulseOp) commonPaths(state *outcomeState) (pulseRuntimeDir, pulseSock
return return
} }
// discoverPulseCookie attempts to discover the pathname of the PulseAudio // discoverPulseCookie attempts to discover the pathname of the PulseAudio cookie of the current user.
// cookie of the current user. If both returned pathname and error are nil, the // If both returned pathname and error are nil, the cookie is likely unavailable and can be silently skipped.
// cookie is likely unavailable and can be silently skipped.
func discoverPulseCookie(k syscallDispatcher) (*check.Absolute, error) { func discoverPulseCookie(k syscallDispatcher) (*check.Absolute, error) {
const paLocateStep = "locate PulseAudio cookie" const paLocateStep = "locate PulseAudio cookie"
@@ -210,10 +186,7 @@ func loadFile(
&os.PathError{Op: "stat", Path: pathname, Err: syscall.ENOMEM}, &os.PathError{Op: "stat", Path: pathname, Err: syscall.ENOMEM},
) )
} else if s < int64(n) { } else if s < int64(n) {
msg.Verbosef( msg.Verbosef("%s at %q is %d bytes shorter than expected", description, pathname, int64(n)-s)
"%s at %q is %d bytes shorter than expected",
description, pathname, int64(n)-s,
)
} else { } else {
msg.Verbosef("loading %d bytes from %q", n, pathname) msg.Verbosef("loading %d bytes from %q", n, pathname)
} }

View File

@@ -67,9 +67,7 @@ const (
// spRuntimeOp sets up XDG_RUNTIME_DIR inside the container. // spRuntimeOp sets up XDG_RUNTIME_DIR inside the container.
type spRuntimeOp struct { type spRuntimeOp struct {
// SessionType determines the value of envXDGSessionType. // SessionType determines the value of envXDGSessionType. Populated during toSystem.
//
// Populated during toSystem.
SessionType uintptr SessionType uintptr
} }

View File

@@ -12,12 +12,9 @@ import (
func init() { gob.Register(new(spWaylandOp)) } func init() { gob.Register(new(spWaylandOp)) }
// spWaylandOp exports the Wayland display server to the container. // spWaylandOp exports the Wayland display server to the container.
//
// Runs after spRuntimeOp. // Runs after spRuntimeOp.
type spWaylandOp struct { type spWaylandOp struct {
// Path to host wayland socket. // Path to host wayland socket. Populated during toSystem if DirectWayland is true.
//
// Populated during toSystem if DirectWayland is true.
SocketPath *check.Absolute SocketPath *check.Absolute
} }

View File

@@ -50,10 +50,7 @@ func (s *spX11Op) toSystem(state *outcomeStateSys) error {
if socketPath != nil { if socketPath != nil {
if _, err := state.k.stat(socketPath.String()); err != nil { if _, err := state.k.stat(socketPath.String()); err != nil {
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
return &hst.AppError{Step: fmt.Sprintf( return &hst.AppError{Step: fmt.Sprintf("access X11 socket %q", socketPath), Err: err}
"access X11 socket %q",
socketPath,
), Err: err}
} }
} else { } else {
state.sys.UpdatePermType(hst.EX11, socketPath, acl.Read, acl.Write, acl.Execute) state.sys.UpdatePermType(hst.EX11, socketPath, acl.Read, acl.Write, acl.Execute)

View File

@@ -39,8 +39,8 @@ type ExecPath struct {
W bool W bool
} }
// SetSchedIdle is whether to set [std.SCHED_IDLE] scheduling priority. // SchedPolicy is the [container] scheduling policy.
var SetSchedIdle bool var SchedPolicy int
// PromoteLayers returns artifacts with identical-by-content layers promoted to // PromoteLayers returns artifacts with identical-by-content layers promoted to
// the highest priority instance, as if mounted via [ExecPath]. // the highest priority instance, as if mounted via [ExecPath].
@@ -413,8 +413,7 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
z.ParentPerm = 0700 z.ParentPerm = 0700
z.HostNet = hostNet z.HostNet = hostNet
z.Hostname = "cure" z.Hostname = "cure"
z.SetScheduler = SetSchedIdle z.SchedPolicy = SchedPolicy
z.SchedPolicy = std.SCHED_IDLE
if z.HostNet { if z.HostNet {
z.Hostname = "cure-net" z.Hostname = "cure-net"
} }

View File

@@ -101,10 +101,6 @@ func init() {
Description: "Commands for Manipulating POSIX Access Control Lists", Description: "Commands for Manipulating POSIX Access Control Lists",
Website: "https://savannah.nongnu.org/projects/acl/", Website: "https://savannah.nongnu.org/projects/acl/",
Dependencies: P{
Attr,
},
ID: 16, ID: 16,
} }
} }

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"net/http" "net/http"
"strconv" "strconv"
"sync" "sync"
@@ -20,10 +19,8 @@ const (
LLVMRuntimes LLVMRuntimes
LLVMClang LLVMClang
// EarlyInit is the Rosa OS init program. // EarlyInit is the Rosa OS initramfs init program.
EarlyInit EarlyInit
// ImageSystem is the Rosa OS /system image.
ImageSystem
// ImageInitramfs is the Rosa OS initramfs archive. // ImageInitramfs is the Rosa OS initramfs archive.
ImageInitramfs ImageInitramfs
@@ -170,36 +167,6 @@ const (
PresetEnd PresetEnd
) )
// P represents multiple [PArtifact] and is stable through JSON.
type P []PArtifact
// MarshalJSON represents [PArtifact] by their [Metadata.Name].
func (s P) MarshalJSON() ([]byte, error) {
names := make([]string, len(s))
for i, p := range s {
names[i] = GetMetadata(p).Name
}
return json.Marshal(names)
}
// UnmarshalJSON resolves the value created by MarshalJSON back to [P].
func (s *P) UnmarshalJSON(data []byte) error {
var names []string
if err := json.Unmarshal(data, &names); err != nil {
return err
}
*s = make(P, len(names))
for i, name := range names {
if p, ok := ResolveName(name); !ok {
return fmt.Errorf("unknown artifact %q", name)
} else {
(*s)[i] = p
}
}
return nil
}
// Metadata is stage-agnostic information of a [PArtifact] not directly // Metadata is stage-agnostic information of a [PArtifact] not directly
// representable in the resulting [pkg.Artifact]. // representable in the resulting [pkg.Artifact].
type Metadata struct { type Metadata struct {
@@ -212,9 +179,6 @@ type Metadata struct {
// Project home page. // Project home page.
Website string `json:"website,omitempty"` Website string `json:"website,omitempty"`
// Runtime dependencies.
Dependencies P `json:"dependencies"`
// Project identifier on [Anitya]. // Project identifier on [Anitya].
// //
// [Anitya]: https://release-monitoring.org/ // [Anitya]: https://release-monitoring.org/
@@ -292,10 +256,9 @@ var (
artifactsM [PresetEnd]Metadata artifactsM [PresetEnd]Metadata
// artifacts stores the result of Metadata.f. // artifacts stores the result of Metadata.f.
artifacts [_toolchainEnd][len(artifactsM)]struct { artifacts [_toolchainEnd][len(artifactsM)]pkg.Artifact
a pkg.Artifact // versions stores the version of [PArtifact].
v string versions [_toolchainEnd][len(artifactsM)]string
}
// artifactsOnce is for lazy initialisation of artifacts. // artifactsOnce is for lazy initialisation of artifacts.
artifactsOnce [_toolchainEnd][len(artifactsM)]sync.Once artifactsOnce [_toolchainEnd][len(artifactsM)]sync.Once
) )
@@ -303,23 +266,20 @@ var (
// GetMetadata returns [Metadata] of a [PArtifact]. // GetMetadata returns [Metadata] of a [PArtifact].
func GetMetadata(p PArtifact) *Metadata { return &artifactsM[p] } func GetMetadata(p PArtifact) *Metadata { return &artifactsM[p] }
// construct constructs a [pkg.Artifact] corresponding to a [PArtifact] once.
func (t Toolchain) construct(p PArtifact) {
artifactsOnce[t][p].Do(func() {
artifacts[t][p].a, artifacts[t][p].v = artifactsM[p].f(t)
})
}
// Load returns the resulting [pkg.Artifact] of [PArtifact]. // Load returns the resulting [pkg.Artifact] of [PArtifact].
func (t Toolchain) Load(p PArtifact) pkg.Artifact { func (t Toolchain) Load(p PArtifact) pkg.Artifact {
t.construct(p) artifactsOnce[t][p].Do(func() {
return artifacts[t][p].a artifacts[t][p], versions[t][p] = artifactsM[p].f(t)
})
return artifacts[t][p]
} }
// Version returns the version string of [PArtifact]. // Version returns the version string of [PArtifact].
func (t Toolchain) Version(p PArtifact) string { func (t Toolchain) Version(p PArtifact) string {
t.construct(p) artifactsOnce[t][p].Do(func() {
return artifacts[t][p].v artifacts[t][p], versions[t][p] = artifactsM[p].f(t)
})
return versions[t][p]
} }
// ResolveName returns a [PArtifact] by name. // ResolveName returns a [PArtifact] by name.

View File

@@ -4,48 +4,24 @@ import "hakurei.app/internal/pkg"
func (t Toolchain) newCurl() (pkg.Artifact, string) { func (t Toolchain) newCurl() (pkg.Artifact, string) {
const ( const (
version = "8.19.0" version = "8.18.0"
checksum = "YHuVLVVp8q_Y7-JWpID5ReNjq2Zk6t7ArHB6ngQXilp_R5l3cubdxu3UKo-xDByv" checksum = "YpOolP_sx1DIrCEJ3elgVAu0wTLDS-EZMZFvOP0eha7FaLueZUlEpuMwDzJNyi7i"
) )
return t.NewPackage("curl", version, pkg.NewHTTPGetTar( return t.NewPackage("curl", version, pkg.NewHTTPGetTar(
nil, "https://curl.se/download/curl-"+version+".tar.bz2", nil, "https://curl.se/download/curl-"+version+".tar.bz2",
mustDecode(checksum), mustDecode(checksum),
pkg.TarBzip2, pkg.TarBzip2,
), &PackageAttr{ ), nil, &MakeHelper{
Patches: [][2]string{
{"test459-misplaced-line-break", `diff --git a/tests/data/test459 b/tests/data/test459
index 7a2e1db7b3..cc716aa65a 100644
--- a/tests/data/test459
+++ b/tests/data/test459
@@ -54,8 +54,8 @@ Content-Type: application/x-www-form-urlencoded
arg
</protocol>
<stderr mode="text">
-Warning: %LOGDIR/config:1 Option 'data' uses argument with unquoted whitespace.%SP
-Warning: This may cause side-effects. Consider double quotes.
+Warning: %LOGDIR/config:1 Option 'data' uses argument with unquoted%SP
+Warning: whitespace. This may cause side-effects. Consider double quotes.
</stderr>
</verify>
</testcase>
`},
},
}, &MakeHelper{
Configure: [][2]string{ Configure: [][2]string{
{"with-openssl"}, {"with-openssl"},
{"with-ca-bundle", "/system/etc/ssl/certs/ca-bundle.crt"}, {"with-ca-bundle", "/system/etc/ssl/certs/ca-bundle.crt"},
{"disable-smb"},
}, },
Check: []string{ Check: []string{
`TFLAGS="-j$(expr "$(nproc)" '*' 2)"`, "TFLAGS=-j256",
"test-nonflaky", "check",
}, },
}, },
Perl, Perl,
Python,
PkgConfig,
Diffutils,
Libpsl, Libpsl,
OpenSSL, OpenSSL,
@@ -59,11 +35,6 @@ func init() {
Description: "command line tool and library for transferring data with URLs", Description: "command line tool and library for transferring data with URLs",
Website: "https://curl.se/", Website: "https://curl.se/",
Dependencies: P{
Libpsl,
OpenSSL,
},
ID: 381, ID: 381,
} }
} }

View File

@@ -46,14 +46,6 @@ func init() {
Description: "utilities and libraries to handle ELF files and DWARF data", Description: "utilities and libraries to handle ELF files and DWARF data",
Website: "https://sourceware.org/elfutils/", Website: "https://sourceware.org/elfutils/",
Dependencies: P{
Zlib,
Bzip2,
Zstd,
MuslFts,
MuslObstack,
},
ID: 5679, ID: 5679,
} }
} }

View File

@@ -36,6 +36,9 @@ index f135ad9..85c784c 100644
// makes assumptions about /etc/passwd // makes assumptions about /etc/passwd
SkipCheck: true, SkipCheck: true,
}, },
M4,
Perl,
Autoconf,
Automake, Automake,
Libtool, Libtool,
PkgConfig, PkgConfig,

View File

@@ -24,6 +24,10 @@ func (t Toolchain) newFuse() (pkg.Artifact, string) {
// this project uses pytest // this project uses pytest
SkipTest: true, SkipTest: true,
}, },
PythonIniConfig,
PythonPackaging,
PythonPluggy,
PythonPygments,
PythonPyTest, PythonPyTest,
KernelHeaders, KernelHeaders,

View File

@@ -52,18 +52,16 @@ disable_test t2200-add-update
`GIT_PROVE_OPTS="--jobs 32 --failures"`, `GIT_PROVE_OPTS="--jobs 32 --failures"`,
"prove", "prove",
}, },
Install: `make \
"-j$(nproc)" \
DESTDIR=/work \
NO_INSTALL_HARDLINKS=1 \
install`,
}, },
Perl,
Diffutils, Diffutils,
M4,
Autoconf, Autoconf,
Gettext, Gettext,
Zlib, Zlib,
Curl, Curl,
OpenSSL,
Libexpat, Libexpat,
), version ), version
} }
@@ -75,12 +73,6 @@ func init() {
Description: "distributed version control system", Description: "distributed version control system",
Website: "https://www.git-scm.com/", Website: "https://www.git-scm.com/",
Dependencies: P{
Zlib,
Curl,
Libexpat,
},
ID: 5350, ID: 5350,
} }
} }
@@ -90,10 +82,14 @@ func (t Toolchain) NewViaGit(
name, url, rev string, name, url, rev string,
checksum pkg.Checksum, checksum pkg.Checksum,
) pkg.Artifact { ) pkg.Artifact {
return t.New(name+"-"+rev, 0, t.AppendPresets(nil, return t.New(name+"-"+rev, 0, []pkg.Artifact{
NSSCACert, t.Load(NSSCACert),
Git, t.Load(OpenSSL),
), &checksum, nil, ` t.Load(Libpsl),
t.Load(Curl),
t.Load(Libexpat),
t.Load(Git),
}, &checksum, nil, `
git \ git \
-c advice.detachedHead=false \ -c advice.detachedHead=false \
clone \ clone \

View File

@@ -117,11 +117,6 @@ func init() {
Description: "M4 macros to produce self-contained configure script", Description: "M4 macros to produce self-contained configure script",
Website: "https://www.gnu.org/software/autoconf/", Website: "https://www.gnu.org/software/autoconf/",
Dependencies: P{
M4,
Perl,
},
ID: 141, ID: 141,
} }
} }
@@ -148,6 +143,8 @@ test_disable '#!/bin/sh' t/distname.sh
test_disable '#!/bin/sh' t/pr9.sh test_disable '#!/bin/sh' t/pr9.sh
`, `,
}, (*MakeHelper)(nil), }, (*MakeHelper)(nil),
M4,
Perl,
Grep, Grep,
Gzip, Gzip,
Autoconf, Autoconf,
@@ -162,10 +159,6 @@ func init() {
Description: "a tool for automatically generating Makefile.in files", Description: "a tool for automatically generating Makefile.in files",
Website: "https://www.gnu.org/software/automake/", Website: "https://www.gnu.org/software/automake/",
Dependencies: P{
Autoconf,
},
ID: 144, ID: 144,
} }
} }
@@ -531,11 +524,6 @@ func init() {
Description: "the GNU square-wheel-reinvension of man pages", Description: "the GNU square-wheel-reinvension of man pages",
Website: "https://www.gnu.org/software/texinfo/", Website: "https://www.gnu.org/software/texinfo/",
Dependencies: P{
Perl,
Gawk,
},
ID: 4958, ID: 4958,
} }
} }
@@ -672,6 +660,7 @@ func (t Toolchain) newBC() (pkg.Artifact, string) {
Writable: true, Writable: true,
Chmod: true, Chmod: true,
}, (*MakeHelper)(nil), }, (*MakeHelper)(nil),
Perl,
Texinfo, Texinfo,
), version ), version
} }
@@ -773,10 +762,6 @@ func init() {
Description: "a shell tool for executing jobs in parallel using one or more computers", Description: "a shell tool for executing jobs in parallel using one or more computers",
Website: "https://www.gnu.org/software/parallel/", Website: "https://www.gnu.org/software/parallel/",
Dependencies: P{
Perl,
},
ID: 5448, ID: 5448,
} }
} }
@@ -854,10 +839,6 @@ func init() {
Description: "a C library for multiple-precision floating-point computations", Description: "a C library for multiple-precision floating-point computations",
Website: "https://www.mpfr.org/", Website: "https://www.mpfr.org/",
Dependencies: P{
GMP,
},
ID: 2019, ID: 2019,
} }
} }
@@ -873,6 +854,7 @@ func (t Toolchain) newMPC() (pkg.Artifact, string) {
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), nil, (*MakeHelper)(nil), ), nil, (*MakeHelper)(nil),
GMP,
MPFR, MPFR,
), version ), version
} }
@@ -884,10 +866,6 @@ func init() {
Description: "a C library for the arithmetic of complex numbers", Description: "a C library for the arithmetic of complex numbers",
Website: "https://www.multiprecision.org/", Website: "https://www.multiprecision.org/",
Dependencies: P{
MPFR,
},
ID: 1667, ID: 1667,
} }
} }
@@ -1085,7 +1063,10 @@ ln -s system/lib /work/
}, },
Binutils, Binutils,
GMP,
MPFR,
MPC, MPC,
Zlib, Zlib,
Libucontext, Libucontext,
KernelHeaders, KernelHeaders,
@@ -1099,14 +1080,6 @@ func init() {
Description: "The GNU Compiler Collection", Description: "The GNU Compiler Collection",
Website: "https://www.gnu.org/software/gcc/", Website: "https://www.gnu.org/software/gcc/",
Dependencies: P{
Binutils,
MPC,
Zlib,
Libucontext,
},
ID: 6502, ID: 6502,
} }
} }

View File

@@ -74,8 +74,22 @@ func (t Toolchain) newGoLatest() (pkg.Artifact, string) {
bootstrapExtra = append(bootstrapExtra, t.newGoBootstrap()) bootstrapExtra = append(bootstrapExtra, t.newGoBootstrap())
case "arm64": case "arm64":
bootstrapEnv = append(bootstrapEnv, "GOROOT_BOOTSTRAP=/system") bootstrapEnv = append(bootstrapEnv,
bootstrapExtra = t.AppendPresets(bootstrapExtra, gcc) "GOROOT_BOOTSTRAP=/system",
)
bootstrapExtra = append(bootstrapExtra,
t.Load(Binutils),
t.Load(GMP),
t.Load(MPFR),
t.Load(MPC),
t.Load(Zlib),
t.Load(Libucontext),
t.Load(gcc),
)
finalEnv = append(finalEnv, "CGO_ENABLED=0") finalEnv = append(finalEnv, "CGO_ENABLED=0")
default: default:

View File

@@ -9,8 +9,8 @@ import (
func (t Toolchain) newGLib() (pkg.Artifact, string) { func (t Toolchain) newGLib() (pkg.Artifact, string) {
const ( const (
version = "2.87.5" version = "2.87.3"
checksum = "L5jurSfyCTlcSTfx-1RBHbNZPL0HnNQakmFXidgAV1JFu0lbytowCCBAALTp-WGc" checksum = "iKSLpzZZVfmAZZmqfO1y6uHdlIks4hzPWrqeUCp4ZeQjrPFA3aAa4OmrBYMNS-Si"
) )
return t.NewPackage("glib", version, pkg.NewHTTPGet( return t.NewPackage("glib", version, pkg.NewHTTPGet(
nil, "https://download.gnome.org/sources/glib/"+ nil, "https://download.gnome.org/sources/glib/"+
@@ -56,12 +56,6 @@ func init() {
Description: "the GNU library of miscellaneous stuff", Description: "the GNU library of miscellaneous stuff",
Website: "https://developer.gnome.org/glib/", Website: "https://developer.gnome.org/glib/",
Dependencies: P{
PCRE2,
Libffi,
Zlib,
},
ID: 10024, ID: 10024,
} }
} }

View File

@@ -15,23 +15,29 @@ echo
hostname = "" hostname = ""
} }
return t.New("hakurei"+suffix+"-"+hakureiVersion, 0, t.AppendPresets(nil, return t.New("hakurei"+suffix+"-"+hakureiVersion, 0, []pkg.Artifact{
Go, t.Load(Go),
PkgConfig,
// dist tarball t.Load(Gzip),
Gzip, t.Load(PkgConfig),
// statically linked t.Load(KernelHeaders),
Libseccomp, t.Load(Libseccomp),
ACL, t.Load(ACL),
Fuse, t.Load(Attr),
XCB, t.Load(Fuse),
Wayland,
WaylandProtocols,
KernelHeaders, t.Load(Xproto),
), nil, []string{ t.Load(LibXau),
t.Load(XCBProto),
t.Load(XCB),
t.Load(Libffi),
t.Load(Libexpat),
t.Load(Libxml2),
t.Load(Wayland),
t.Load(WaylandProtocols),
}, nil, []string{
"CGO_ENABLED=1", "CGO_ENABLED=1",
"GOCACHE=/tmp/gocache", "GOCACHE=/tmp/gocache",
"CC=clang -O3 -Werror", "CC=clang -O3 -Werror",

View File

@@ -1,9 +1,6 @@
package rosa package rosa
import ( import "hakurei.app/internal/pkg"
"hakurei.app/container/fhs"
"hakurei.app/internal/pkg"
)
func init() { func init() {
artifactsM[EarlyInit] = Metadata{ artifactsM[EarlyInit] = Metadata{
@@ -27,36 +24,12 @@ echo
} }
} }
func (t Toolchain) newImageSystem() (pkg.Artifact, string) {
return t.New("system.img", TNoToolchain, t.AppendPresets(nil,
SquashfsTools,
), nil, nil, `
mksquashfs /mnt/system /work/system.img
`, pkg.Path(fhs.AbsRoot.Append("mnt"), false, t.AppendPresets(nil,
Musl,
Mksh,
Toybox,
Kmod,
Kernel,
Firmware,
)...)), Unversioned
}
func init() {
artifactsM[ImageSystem] = Metadata{
Name: "system-image",
Description: "Rosa OS system image",
f: Toolchain.newImageSystem,
}
}
func (t Toolchain) newImageInitramfs() (pkg.Artifact, string) { func (t Toolchain) newImageInitramfs() (pkg.Artifact, string) {
return t.New("initramfs", TNoToolchain, t.AppendPresets(nil, return t.New("initramfs", TNoToolchain, []pkg.Artifact{
Zstd, t.Load(Zstd),
EarlyInit, t.Load(EarlyInit),
GenInitCPIO, t.Load(GenInitCPIO),
), nil, nil, ` }, nil, nil, `
gen_init_cpio -t 4294967295 -c /usr/src/initramfs | zstd > /work/initramfs.zst gen_init_cpio -t 4294967295 -c /usr/src/initramfs | zstd > /work/initramfs.zst
`, pkg.Path(AbsUsrSrc.Append("initramfs"), false, pkg.NewFile("initramfs", []byte(` `, pkg.Path(AbsUsrSrc.Append("initramfs"), false, pkg.NewFile("initramfs", []byte(`
dir /dev 0755 0 0 dir /dev 0755 0 0

View File

@@ -82,11 +82,6 @@ install -Dm0500 \
echo "Installing linux $1..." echo "Installing linux $1..."
cp -av "$2" "$4" cp -av "$2" "$4"
cp -av "$3" "$4" cp -av "$3" "$4"
`))),
pkg.Path(AbsUsrSrc.Append(
".depmod",
), false, pkg.NewFile("depmod", []byte(`#!/bin/sh
exec /system/sbin/depmod -m /lib/modules "$@"
`))), `))),
}, },
@@ -1215,11 +1210,6 @@ cgit 1.2.3-korg
"all", "all",
}, },
Install: ` Install: `
# kernel is not aware of kmod moduledir
install -Dm0500 \
/usr/src/.depmod \
/sbin/depmod
make \ make \
"-j$(nproc)" \ "-j$(nproc)" \
-f /usr/src/kernel/Makefile \ -f /usr/src/kernel/Makefile \
@@ -1227,10 +1217,9 @@ make \
LLVM=1 \ LLVM=1 \
INSTALL_PATH=/work \ INSTALL_PATH=/work \
install \ install \
INSTALL_MOD_PATH=/work/system \ INSTALL_MOD_PATH=/work \
DEPMOD=/sbin/depmod \
modules_install modules_install
rm -v /work/system/lib/modules/` + kernelVersion + `/build rm -v /work/lib/modules/` + kernelVersion + `/build
`, `,
}, },
Flex, Flex,
@@ -1246,9 +1235,13 @@ rm -v /work/system/lib/modules/` + kernelVersion + `/build
Python, Python,
XZ, XZ,
Zlib,
Gzip, Gzip,
Bzip2,
Zstd,
Kmod, Kmod,
Elfutils, Elfutils,
OpenSSL,
UtilLinux, UtilLinux,
KernelHeaders, KernelHeaders,
), kernelVersion ), kernelVersion
@@ -1282,8 +1275,8 @@ func init() {
func (t Toolchain) newFirmware() (pkg.Artifact, string) { func (t Toolchain) newFirmware() (pkg.Artifact, string) {
const ( const (
version = "20260309" version = "20260221"
checksum = "M1az8BxSiOEH3LA11Trc5VAlakwAHhP7-_LKWg6k-SVIzU3xclMDO4Tiujw1gQrC" checksum = "vTENPW5rZ6yLVq7YKDLHkCVgKXvwUWigEx7T4LcxoKeBVYIyf1_sEExeV4mo-e46"
) )
return t.NewPackage("firmware", version, pkg.NewHTTPGetTar( return t.NewPackage("firmware", version, pkg.NewHTTPGetTar(
nil, "https://gitlab.com/kernel-firmware/linux-firmware/-/"+ nil, "https://gitlab.com/kernel-firmware/linux-firmware/-/"+
@@ -1311,7 +1304,9 @@ func (t Toolchain) newFirmware() (pkg.Artifact, string) {
SkipCheck: true, // requires pre-commit SkipCheck: true, // requires pre-commit
Install: `make "-j$(nproc)" DESTDIR=/work/system dedup`, Install: `make "-j$(nproc)" DESTDIR=/work/system dedup`,
}, },
Perl,
Parallel, Parallel,
Nettle,
Rdfind, Rdfind,
Zstd, Zstd,
Findutils, Findutils,

View File

@@ -2,15 +2,15 @@
# Automatically generated file; DO NOT EDIT. # Automatically generated file; DO NOT EDIT.
# Linux/x86 6.12.76 Kernel Configuration # Linux/x86 6.12.76 Kernel Configuration
# #
CONFIG_CC_VERSION_TEXT="clang version 22.1.1" CONFIG_CC_VERSION_TEXT="clang version 22.1.0"
CONFIG_GCC_VERSION=0 CONFIG_GCC_VERSION=0
CONFIG_CC_IS_CLANG=y CONFIG_CC_IS_CLANG=y
CONFIG_CLANG_VERSION=220101 CONFIG_CLANG_VERSION=220100
CONFIG_AS_IS_LLVM=y CONFIG_AS_IS_LLVM=y
CONFIG_AS_VERSION=220101 CONFIG_AS_VERSION=220100
CONFIG_LD_VERSION=0 CONFIG_LD_VERSION=0
CONFIG_LD_IS_LLD=y CONFIG_LD_IS_LLD=y
CONFIG_LLD_VERSION=220101 CONFIG_LLD_VERSION=220100
CONFIG_RUSTC_VERSION=0 CONFIG_RUSTC_VERSION=0
CONFIG_RUSTC_LLVM_VERSION=0 CONFIG_RUSTC_LLVM_VERSION=0
CONFIG_CC_HAS_ASM_GOTO_OUTPUT=y CONFIG_CC_HAS_ASM_GOTO_OUTPUT=y
@@ -2402,7 +2402,7 @@ CONFIG_PREVENT_FIRMWARE_BUILD=y
# #
# Firmware loader # Firmware loader
# #
CONFIG_FW_LOADER=y CONFIG_FW_LOADER=m
CONFIG_FW_LOADER_DEBUG=y CONFIG_FW_LOADER_DEBUG=y
CONFIG_FW_LOADER_PAGED_BUF=y CONFIG_FW_LOADER_PAGED_BUF=y
CONFIG_FW_LOADER_SYSFS=y CONFIG_FW_LOADER_SYSFS=y
@@ -2749,7 +2749,7 @@ CONFIG_BLK_DEV_NULL_BLK=m
CONFIG_BLK_DEV_FD=m CONFIG_BLK_DEV_FD=m
# CONFIG_BLK_DEV_FD_RAWCMD is not set # CONFIG_BLK_DEV_FD_RAWCMD is not set
CONFIG_CDROM=m CONFIG_CDROM=m
CONFIG_BLK_DEV_PCIESSD_MTIP32XX=y CONFIG_BLK_DEV_PCIESSD_MTIP32XX=m
CONFIG_ZRAM=m CONFIG_ZRAM=m
# CONFIG_ZRAM_BACKEND_LZ4 is not set # CONFIG_ZRAM_BACKEND_LZ4 is not set
# CONFIG_ZRAM_BACKEND_LZ4HC is not set # CONFIG_ZRAM_BACKEND_LZ4HC is not set
@@ -2775,9 +2775,9 @@ CONFIG_CDROM_PKTCDVD=m
CONFIG_CDROM_PKTCDVD_BUFFERS=8 CONFIG_CDROM_PKTCDVD_BUFFERS=8
# CONFIG_CDROM_PKTCDVD_WCACHE is not set # CONFIG_CDROM_PKTCDVD_WCACHE is not set
CONFIG_ATA_OVER_ETH=m CONFIG_ATA_OVER_ETH=m
CONFIG_XEN_BLKDEV_FRONTEND=y CONFIG_XEN_BLKDEV_FRONTEND=m
# CONFIG_XEN_BLKDEV_BACKEND is not set CONFIG_XEN_BLKDEV_BACKEND=m
CONFIG_VIRTIO_BLK=y CONFIG_VIRTIO_BLK=m
CONFIG_BLK_DEV_RBD=m CONFIG_BLK_DEV_RBD=m
CONFIG_BLK_DEV_UBLK=m CONFIG_BLK_DEV_UBLK=m
CONFIG_BLKDEV_UBLK_LEGACY_OPCODES=y CONFIG_BLKDEV_UBLK_LEGACY_OPCODES=y
@@ -2788,12 +2788,13 @@ CONFIG_BLK_DEV_RNBD_SERVER=m
# #
# NVME Support # NVME Support
# #
CONFIG_NVME_KEYRING=y CONFIG_NVME_KEYRING=m
CONFIG_NVME_AUTH=y CONFIG_NVME_AUTH=m
CONFIG_NVME_CORE=y CONFIG_NVME_CORE=m
CONFIG_BLK_DEV_NVME=y CONFIG_BLK_DEV_NVME=m
CONFIG_NVME_MULTIPATH=y CONFIG_NVME_MULTIPATH=y
# CONFIG_NVME_VERBOSE_ERRORS is not set # CONFIG_NVME_VERBOSE_ERRORS is not set
CONFIG_NVME_HWMON=y
CONFIG_NVME_FABRICS=m CONFIG_NVME_FABRICS=m
CONFIG_NVME_RDMA=m CONFIG_NVME_RDMA=m
CONFIG_NVME_FC=m CONFIG_NVME_FC=m
@@ -2910,10 +2911,10 @@ CONFIG_KEBA_CP500=m
# #
# SCSI device support # SCSI device support
# #
CONFIG_SCSI_MOD=y CONFIG_SCSI_MOD=m
CONFIG_RAID_ATTRS=m CONFIG_RAID_ATTRS=m
CONFIG_SCSI_COMMON=y CONFIG_SCSI_COMMON=m
CONFIG_SCSI=y CONFIG_SCSI=m
CONFIG_SCSI_DMA=y CONFIG_SCSI_DMA=y
CONFIG_SCSI_NETLINK=y CONFIG_SCSI_NETLINK=y
CONFIG_SCSI_PROC_FS=y CONFIG_SCSI_PROC_FS=y
@@ -2921,7 +2922,7 @@ CONFIG_SCSI_PROC_FS=y
# #
# SCSI support type (disk, tape, CD-ROM) # SCSI support type (disk, tape, CD-ROM)
# #
CONFIG_BLK_DEV_SD=y CONFIG_BLK_DEV_SD=m
CONFIG_CHR_DEV_ST=m CONFIG_CHR_DEV_ST=m
CONFIG_BLK_DEV_SR=m CONFIG_BLK_DEV_SR=m
CONFIG_CHR_DEV_SG=m CONFIG_CHR_DEV_SG=m
@@ -3041,7 +3042,7 @@ CONFIG_SCSI_DEBUG=m
CONFIG_SCSI_PMCRAID=m CONFIG_SCSI_PMCRAID=m
CONFIG_SCSI_PM8001=m CONFIG_SCSI_PM8001=m
CONFIG_SCSI_BFA_FC=m CONFIG_SCSI_BFA_FC=m
CONFIG_SCSI_VIRTIO=y CONFIG_SCSI_VIRTIO=m
CONFIG_SCSI_CHELSIO_FCOE=m CONFIG_SCSI_CHELSIO_FCOE=m
CONFIG_SCSI_LOWLEVEL_PCMCIA=y CONFIG_SCSI_LOWLEVEL_PCMCIA=y
CONFIG_PCMCIA_AHA152X=m CONFIG_PCMCIA_AHA152X=m
@@ -3051,7 +3052,7 @@ CONFIG_PCMCIA_SYM53C500=m
# CONFIG_SCSI_DH is not set # CONFIG_SCSI_DH is not set
# end of SCSI device support # end of SCSI device support
CONFIG_ATA=y CONFIG_ATA=m
CONFIG_SATA_HOST=y CONFIG_SATA_HOST=y
CONFIG_PATA_TIMINGS=y CONFIG_PATA_TIMINGS=y
CONFIG_ATA_VERBOSE_ERROR=y CONFIG_ATA_VERBOSE_ERROR=y
@@ -3063,39 +3064,39 @@ CONFIG_SATA_PMP=y
# #
# Controllers with non-SFF native interface # Controllers with non-SFF native interface
# #
CONFIG_SATA_AHCI=y CONFIG_SATA_AHCI=m
CONFIG_SATA_MOBILE_LPM_POLICY=3 CONFIG_SATA_MOBILE_LPM_POLICY=3
CONFIG_SATA_AHCI_PLATFORM=y CONFIG_SATA_AHCI_PLATFORM=m
CONFIG_AHCI_DWC=y CONFIG_AHCI_DWC=m
CONFIG_AHCI_CEVA=y CONFIG_AHCI_CEVA=m
CONFIG_SATA_INIC162X=m CONFIG_SATA_INIC162X=m
CONFIG_SATA_ACARD_AHCI=y CONFIG_SATA_ACARD_AHCI=m
CONFIG_SATA_SIL24=y CONFIG_SATA_SIL24=m
CONFIG_ATA_SFF=y CONFIG_ATA_SFF=y
# #
# SFF controllers with custom DMA interface # SFF controllers with custom DMA interface
# #
CONFIG_PDC_ADMA=y CONFIG_PDC_ADMA=m
CONFIG_SATA_QSTOR=y CONFIG_SATA_QSTOR=m
CONFIG_SATA_SX4=m CONFIG_SATA_SX4=m
CONFIG_ATA_BMDMA=y CONFIG_ATA_BMDMA=y
# #
# SATA SFF controllers with BMDMA # SATA SFF controllers with BMDMA
# #
CONFIG_ATA_PIIX=y CONFIG_ATA_PIIX=m
CONFIG_SATA_DWC=y CONFIG_SATA_DWC=m
# CONFIG_SATA_DWC_OLD_DMA is not set # CONFIG_SATA_DWC_OLD_DMA is not set
CONFIG_SATA_MV=y CONFIG_SATA_MV=m
CONFIG_SATA_NV=y CONFIG_SATA_NV=m
CONFIG_SATA_PROMISE=y CONFIG_SATA_PROMISE=m
CONFIG_SATA_SIL=y CONFIG_SATA_SIL=m
CONFIG_SATA_SIS=y CONFIG_SATA_SIS=m
CONFIG_SATA_SVW=y CONFIG_SATA_SVW=m
CONFIG_SATA_ULI=y CONFIG_SATA_ULI=m
CONFIG_SATA_VIA=y CONFIG_SATA_VIA=m
CONFIG_SATA_VITESSE=y CONFIG_SATA_VITESSE=m
# #
# PATA SFF controllers with BMDMA # PATA SFF controllers with BMDMA
@@ -3129,7 +3130,7 @@ CONFIG_PATA_RDC=m
CONFIG_PATA_SCH=m CONFIG_PATA_SCH=m
CONFIG_PATA_SERVERWORKS=m CONFIG_PATA_SERVERWORKS=m
CONFIG_PATA_SIL680=m CONFIG_PATA_SIL680=m
CONFIG_PATA_SIS=y CONFIG_PATA_SIS=m
CONFIG_PATA_TOSHIBA=m CONFIG_PATA_TOSHIBA=m
CONFIG_PATA_TRIFLEX=m CONFIG_PATA_TRIFLEX=m
CONFIG_PATA_VIA=m CONFIG_PATA_VIA=m
@@ -3171,8 +3172,8 @@ CONFIG_PATA_PARPORT_ON26=m
# #
# Generic fallback / legacy drivers # Generic fallback / legacy drivers
# #
CONFIG_PATA_ACPI=y CONFIG_PATA_ACPI=m
CONFIG_ATA_GENERIC=y CONFIG_ATA_GENERIC=m
CONFIG_PATA_LEGACY=m CONFIG_PATA_LEGACY=m
CONFIG_MD=y CONFIG_MD=y
CONFIG_BLK_DEV_MD=m CONFIG_BLK_DEV_MD=m
@@ -9620,11 +9621,11 @@ CONFIG_EFI_SECRET=m
CONFIG_SEV_GUEST=m CONFIG_SEV_GUEST=m
CONFIG_TDX_GUEST_DRIVER=m CONFIG_TDX_GUEST_DRIVER=m
CONFIG_VIRTIO_ANCHOR=y CONFIG_VIRTIO_ANCHOR=y
CONFIG_VIRTIO=y CONFIG_VIRTIO=m
CONFIG_VIRTIO_PCI_LIB=y CONFIG_VIRTIO_PCI_LIB=m
CONFIG_VIRTIO_PCI_LIB_LEGACY=y CONFIG_VIRTIO_PCI_LIB_LEGACY=m
CONFIG_VIRTIO_MENU=y CONFIG_VIRTIO_MENU=y
CONFIG_VIRTIO_PCI=y CONFIG_VIRTIO_PCI=m
CONFIG_VIRTIO_PCI_ADMIN_LEGACY=y CONFIG_VIRTIO_PCI_ADMIN_LEGACY=y
CONFIG_VIRTIO_PCI_LEGACY=y CONFIG_VIRTIO_PCI_LEGACY=y
CONFIG_VIRTIO_VDPA=m CONFIG_VIRTIO_VDPA=m

View File

@@ -2,15 +2,15 @@
# Automatically generated file; DO NOT EDIT. # Automatically generated file; DO NOT EDIT.
# Linux/arm64 6.12.76 Kernel Configuration # Linux/arm64 6.12.76 Kernel Configuration
# #
CONFIG_CC_VERSION_TEXT="clang version 22.1.1" CONFIG_CC_VERSION_TEXT="clang version 22.1.0"
CONFIG_GCC_VERSION=0 CONFIG_GCC_VERSION=0
CONFIG_CC_IS_CLANG=y CONFIG_CC_IS_CLANG=y
CONFIG_CLANG_VERSION=220101 CONFIG_CLANG_VERSION=220100
CONFIG_AS_IS_LLVM=y CONFIG_AS_IS_LLVM=y
CONFIG_AS_VERSION=220101 CONFIG_AS_VERSION=220100
CONFIG_LD_VERSION=0 CONFIG_LD_VERSION=0
CONFIG_LD_IS_LLD=y CONFIG_LD_IS_LLD=y
CONFIG_LLD_VERSION=220101 CONFIG_LLD_VERSION=220100
CONFIG_RUSTC_VERSION=0 CONFIG_RUSTC_VERSION=0
CONFIG_RUSTC_LLVM_VERSION=0 CONFIG_RUSTC_LLVM_VERSION=0
CONFIG_CC_HAS_ASM_GOTO_OUTPUT=y CONFIG_CC_HAS_ASM_GOTO_OUTPUT=y
@@ -2384,7 +2384,7 @@ CONFIG_PREVENT_FIRMWARE_BUILD=y
# #
# Firmware loader # Firmware loader
# #
CONFIG_FW_LOADER=y CONFIG_FW_LOADER=m
CONFIG_FW_LOADER_DEBUG=y CONFIG_FW_LOADER_DEBUG=y
CONFIG_FW_LOADER_PAGED_BUF=y CONFIG_FW_LOADER_PAGED_BUF=y
CONFIG_FW_LOADER_SYSFS=y CONFIG_FW_LOADER_SYSFS=y
@@ -2849,8 +2849,8 @@ CONFIG_CDROM_PKTCDVD=m
CONFIG_CDROM_PKTCDVD_BUFFERS=8 CONFIG_CDROM_PKTCDVD_BUFFERS=8
# CONFIG_CDROM_PKTCDVD_WCACHE is not set # CONFIG_CDROM_PKTCDVD_WCACHE is not set
CONFIG_ATA_OVER_ETH=m CONFIG_ATA_OVER_ETH=m
CONFIG_XEN_BLKDEV_FRONTEND=y CONFIG_XEN_BLKDEV_FRONTEND=m
# CONFIG_XEN_BLKDEV_BACKEND is not set CONFIG_XEN_BLKDEV_BACKEND=m
CONFIG_VIRTIO_BLK=m CONFIG_VIRTIO_BLK=m
CONFIG_BLK_DEV_RBD=m CONFIG_BLK_DEV_RBD=m
CONFIG_BLK_DEV_UBLK=m CONFIG_BLK_DEV_UBLK=m
@@ -2862,12 +2862,13 @@ CONFIG_BLK_DEV_RNBD_SERVER=m
# #
# NVME Support # NVME Support
# #
CONFIG_NVME_KEYRING=y CONFIG_NVME_KEYRING=m
CONFIG_NVME_AUTH=y CONFIG_NVME_AUTH=m
CONFIG_NVME_CORE=y CONFIG_NVME_CORE=m
CONFIG_BLK_DEV_NVME=y CONFIG_BLK_DEV_NVME=m
CONFIG_NVME_MULTIPATH=y CONFIG_NVME_MULTIPATH=y
# CONFIG_NVME_VERBOSE_ERRORS is not set # CONFIG_NVME_VERBOSE_ERRORS is not set
CONFIG_NVME_HWMON=y
CONFIG_NVME_FABRICS=m CONFIG_NVME_FABRICS=m
CONFIG_NVME_RDMA=m CONFIG_NVME_RDMA=m
CONFIG_NVME_FC=m CONFIG_NVME_FC=m
@@ -2976,10 +2977,10 @@ CONFIG_KEBA_CP500=m
# #
# SCSI device support # SCSI device support
# #
CONFIG_SCSI_MOD=y CONFIG_SCSI_MOD=m
CONFIG_RAID_ATTRS=m CONFIG_RAID_ATTRS=m
CONFIG_SCSI_COMMON=y CONFIG_SCSI_COMMON=m
CONFIG_SCSI=y CONFIG_SCSI=m
CONFIG_SCSI_DMA=y CONFIG_SCSI_DMA=y
CONFIG_SCSI_NETLINK=y CONFIG_SCSI_NETLINK=y
CONFIG_SCSI_PROC_FS=y CONFIG_SCSI_PROC_FS=y
@@ -2987,7 +2988,7 @@ CONFIG_SCSI_PROC_FS=y
# #
# SCSI support type (disk, tape, CD-ROM) # SCSI support type (disk, tape, CD-ROM)
# #
CONFIG_BLK_DEV_SD=y CONFIG_BLK_DEV_SD=m
CONFIG_CHR_DEV_ST=m CONFIG_CHR_DEV_ST=m
CONFIG_BLK_DEV_SR=m CONFIG_BLK_DEV_SR=m
CONFIG_CHR_DEV_SG=m CONFIG_CHR_DEV_SG=m
@@ -3107,7 +3108,7 @@ CONFIG_SCSI_DEBUG=m
CONFIG_SCSI_PMCRAID=m CONFIG_SCSI_PMCRAID=m
CONFIG_SCSI_PM8001=m CONFIG_SCSI_PM8001=m
CONFIG_SCSI_BFA_FC=m CONFIG_SCSI_BFA_FC=m
CONFIG_SCSI_VIRTIO=y CONFIG_SCSI_VIRTIO=m
CONFIG_SCSI_CHELSIO_FCOE=m CONFIG_SCSI_CHELSIO_FCOE=m
CONFIG_SCSI_LOWLEVEL_PCMCIA=y CONFIG_SCSI_LOWLEVEL_PCMCIA=y
CONFIG_PCMCIA_AHA152X=m CONFIG_PCMCIA_AHA152X=m
@@ -3117,7 +3118,7 @@ CONFIG_PCMCIA_SYM53C500=m
# CONFIG_SCSI_DH is not set # CONFIG_SCSI_DH is not set
# end of SCSI device support # end of SCSI device support
CONFIG_ATA=y CONFIG_ATA=m
CONFIG_SATA_HOST=y CONFIG_SATA_HOST=y
CONFIG_PATA_TIMINGS=y CONFIG_PATA_TIMINGS=y
CONFIG_ATA_VERBOSE_ERROR=y CONFIG_ATA_VERBOSE_ERROR=y
@@ -3129,23 +3130,23 @@ CONFIG_SATA_PMP=y
# #
# Controllers with non-SFF native interface # Controllers with non-SFF native interface
# #
CONFIG_SATA_AHCI=y CONFIG_SATA_AHCI=m
CONFIG_SATA_MOBILE_LPM_POLICY=3 CONFIG_SATA_MOBILE_LPM_POLICY=3
CONFIG_SATA_AHCI_PLATFORM=y CONFIG_SATA_AHCI_PLATFORM=m
CONFIG_AHCI_BRCM=y CONFIG_AHCI_BRCM=m
CONFIG_AHCI_DWC=y CONFIG_AHCI_DWC=m
CONFIG_AHCI_IMX=m CONFIG_AHCI_IMX=m
CONFIG_AHCI_CEVA=y CONFIG_AHCI_CEVA=m
CONFIG_AHCI_MTK=y CONFIG_AHCI_MTK=m
CONFIG_AHCI_MVEBU=y CONFIG_AHCI_MVEBU=m
CONFIG_AHCI_SUNXI=y CONFIG_AHCI_SUNXI=m
CONFIG_AHCI_TEGRA=y CONFIG_AHCI_TEGRA=m
CONFIG_AHCI_XGENE=m CONFIG_AHCI_XGENE=m
CONFIG_AHCI_QORIQ=y CONFIG_AHCI_QORIQ=m
CONFIG_SATA_AHCI_SEATTLE=y CONFIG_SATA_AHCI_SEATTLE=m
CONFIG_SATA_INIC162X=m CONFIG_SATA_INIC162X=m
CONFIG_SATA_ACARD_AHCI=y CONFIG_SATA_ACARD_AHCI=m
CONFIG_SATA_SIL24=y CONFIG_SATA_SIL24=m
CONFIG_ATA_SFF=y CONFIG_ATA_SFF=y
# #
@@ -3159,19 +3160,19 @@ CONFIG_ATA_BMDMA=y
# #
# SATA SFF controllers with BMDMA # SATA SFF controllers with BMDMA
# #
CONFIG_ATA_PIIX=y CONFIG_ATA_PIIX=m
CONFIG_SATA_DWC=y CONFIG_SATA_DWC=m
# CONFIG_SATA_DWC_OLD_DMA is not set # CONFIG_SATA_DWC_OLD_DMA is not set
CONFIG_SATA_MV=y CONFIG_SATA_MV=m
CONFIG_SATA_NV=y CONFIG_SATA_NV=m
CONFIG_SATA_PROMISE=y CONFIG_SATA_PROMISE=m
CONFIG_SATA_RCAR=y CONFIG_SATA_RCAR=m
CONFIG_SATA_SIL=y CONFIG_SATA_SIL=m
CONFIG_SATA_SIS=y CONFIG_SATA_SIS=m
CONFIG_SATA_SVW=y CONFIG_SATA_SVW=m
CONFIG_SATA_ULI=y CONFIG_SATA_ULI=m
CONFIG_SATA_VIA=y CONFIG_SATA_VIA=m
CONFIG_SATA_VITESSE=y CONFIG_SATA_VITESSE=m
# #
# PATA SFF controllers with BMDMA # PATA SFF controllers with BMDMA
@@ -3206,7 +3207,7 @@ CONFIG_PATA_RDC=m
CONFIG_PATA_SCH=m CONFIG_PATA_SCH=m
CONFIG_PATA_SERVERWORKS=m CONFIG_PATA_SERVERWORKS=m
CONFIG_PATA_SIL680=m CONFIG_PATA_SIL680=m
CONFIG_PATA_SIS=y CONFIG_PATA_SIS=m
CONFIG_PATA_TOSHIBA=m CONFIG_PATA_TOSHIBA=m
CONFIG_PATA_TRIFLEX=m CONFIG_PATA_TRIFLEX=m
CONFIG_PATA_VIA=m CONFIG_PATA_VIA=m
@@ -3248,8 +3249,8 @@ CONFIG_PATA_PARPORT_ON26=m
# #
# Generic fallback / legacy drivers # Generic fallback / legacy drivers
# #
CONFIG_PATA_ACPI=y CONFIG_PATA_ACPI=m
CONFIG_ATA_GENERIC=y CONFIG_ATA_GENERIC=m
CONFIG_PATA_LEGACY=m CONFIG_PATA_LEGACY=m
CONFIG_MD=y CONFIG_MD=y
CONFIG_BLK_DEV_MD=m CONFIG_BLK_DEV_MD=m
@@ -10435,11 +10436,11 @@ CONFIG_VMGENID=m
CONFIG_NITRO_ENCLAVES=m CONFIG_NITRO_ENCLAVES=m
CONFIG_ARM_PKVM_GUEST=y CONFIG_ARM_PKVM_GUEST=y
CONFIG_VIRTIO_ANCHOR=y CONFIG_VIRTIO_ANCHOR=y
CONFIG_VIRTIO=y CONFIG_VIRTIO=m
CONFIG_VIRTIO_PCI_LIB=y CONFIG_VIRTIO_PCI_LIB=m
CONFIG_VIRTIO_PCI_LIB_LEGACY=y CONFIG_VIRTIO_PCI_LIB_LEGACY=m
CONFIG_VIRTIO_MENU=y CONFIG_VIRTIO_MENU=y
CONFIG_VIRTIO_PCI=y CONFIG_VIRTIO_PCI=m
CONFIG_VIRTIO_PCI_LEGACY=y CONFIG_VIRTIO_PCI_LEGACY=y
CONFIG_VIRTIO_VDPA=m CONFIG_VIRTIO_VDPA=m
CONFIG_VIRTIO_PMEM=m CONFIG_VIRTIO_PMEM=m

View File

@@ -14,7 +14,6 @@ func (t Toolchain) newKmod() (pkg.Artifact, string) {
pkg.TarGzip, pkg.TarGzip,
), nil, &MesonHelper{ ), nil, &MesonHelper{
Setup: [][2]string{ Setup: [][2]string{
{"Dmoduledir", "/system/lib/modules"},
{"Dsysconfdir", "/system/etc"}, {"Dsysconfdir", "/system/etc"},
{"Dbashcompletiondir", "no"}, {"Dbashcompletiondir", "no"},
{"Dfishcompletiondir", "no"}, {"Dfishcompletiondir", "no"},
@@ -39,12 +38,6 @@ func init() {
Description: "a set of tools to handle common tasks with Linux kernel modules", Description: "a set of tools to handle common tasks with Linux kernel modules",
Website: "https://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git", Website: "https://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git",
Dependencies: P{
Zlib,
Zstd,
OpenSSL,
},
ID: 1517, ID: 1517,
} }
} }

View File

@@ -31,10 +31,6 @@ func init() {
Description: "an open source code library for the dynamic creation of images", Description: "an open source code library for the dynamic creation of images",
Website: "https://libgd.github.io/", Website: "https://libgd.github.io/",
Dependencies: P{
Zlib,
},
ID: 880, ID: 880,
} }
} }

View File

@@ -23,6 +23,7 @@ func (t Toolchain) newLibxslt() (pkg.Artifact, string) {
SkipCheck: true, SkipCheck: true,
}, },
XZ, XZ,
Zlib,
Python, Python,
PkgConfig, PkgConfig,
@@ -37,10 +38,6 @@ func init() {
Description: "an XSLT processor based on libxml2", Description: "an XSLT processor based on libxml2",
Website: "https://gitlab.gnome.org/GNOME/libxslt/", Website: "https://gitlab.gnome.org/GNOME/libxslt/",
Dependencies: P{
Libxml2,
},
ID: 13301, ID: 13301,
} }
} }

View File

@@ -73,8 +73,14 @@ func llvmFlagName(flag int) string {
} }
} }
const (
llvmVersionMajor = "22"
llvmVersion = llvmVersionMajor + ".1.0"
)
// newLLVMVariant returns a [pkg.Artifact] containing a LLVM variant. // newLLVMVariant returns a [pkg.Artifact] containing a LLVM variant.
func (t Toolchain) newLLVMVariant(variant string, attr *llvmAttr) pkg.Artifact { func (t Toolchain) newLLVMVariant(variant string, attr *llvmAttr) pkg.Artifact {
const checksum = "-_Tu5Lt8xkWoxm2VDVV7crh0WqZQbbblN3fYamMdPTDSy_54FAkD2ii7afSymPVV"
if attr == nil { if attr == nil {
panic("LLVM attr must be non-nil") panic("LLVM attr must be non-nil")
@@ -119,8 +125,6 @@ func (t Toolchain) newLLVMVariant(variant string, attr *llvmAttr) pkg.Artifact {
[2]string{"LLVM_INSTALL_BINUTILS_SYMLINKS", "ON"}, [2]string{"LLVM_INSTALL_BINUTILS_SYMLINKS", "ON"},
[2]string{"LLVM_INSTALL_CCTOOLS_SYMLINKS", "ON"}, [2]string{"LLVM_INSTALL_CCTOOLS_SYMLINKS", "ON"},
[2]string{"LLVM_LIT_ARGS", "'--verbose'"},
) )
} }
@@ -163,7 +167,7 @@ ln -s ld.lld /work/system/bin/ld
return t.NewPackage("llvm", llvmVersion, pkg.NewHTTPGetTar( return t.NewPackage("llvm", llvmVersion, pkg.NewHTTPGetTar(
nil, "https://github.com/llvm/llvm-project/archive/refs/tags/"+ nil, "https://github.com/llvm/llvm-project/archive/refs/tags/"+
"llvmorg-"+llvmVersion+".tar.gz", "llvmorg-"+llvmVersion+".tar.gz",
mustDecode(llvmChecksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &PackageAttr{ ), &PackageAttr{
Patches: attr.patches, Patches: attr.patches,
@@ -183,6 +187,8 @@ ln -s ld.lld /work/system/bin/ld
Append: cmakeAppend, Append: cmakeAppend,
Script: script + attr.script, Script: script + attr.script,
}, },
Zlib,
Libffi,
Python, Python,
Perl, Perl,
Diffutils, Diffutils,
@@ -310,7 +316,7 @@ ln -s clang++ /work/system/bin/c++
ninja check-all ninja check-all
`, `,
patches: slices.Concat([][2]string{ patches: [][2]string{
{"add-rosa-vendor", `diff --git a/llvm/include/llvm/TargetParser/Triple.h b/llvm/include/llvm/TargetParser/Triple.h {"add-rosa-vendor", `diff --git a/llvm/include/llvm/TargetParser/Triple.h b/llvm/include/llvm/TargetParser/Triple.h
index 9c83abeeb3b1..5acfe5836a23 100644 index 9c83abeeb3b1..5acfe5836a23 100644
--- a/llvm/include/llvm/TargetParser/Triple.h --- a/llvm/include/llvm/TargetParser/Triple.h
@@ -482,7 +488,7 @@ index 64324a3f8b01..15ce70b68217 100644
"/System/Library/Frameworks"}; "/System/Library/Frameworks"};
`}, `},
}, clangPatches), },
}) })
return return

View File

@@ -1,4 +0,0 @@
package rosa
// clangPatches are patches applied to the LLVM source tree for building clang.
var clangPatches [][2]string

View File

@@ -1,12 +0,0 @@
package rosa
// clangPatches are patches applied to the LLVM source tree for building clang.
var clangPatches [][2]string
// one version behind, latest fails 5 tests with 2 flaky on arm64
const (
llvmVersionMajor = "21"
llvmVersion = llvmVersionMajor + ".1.8"
llvmChecksum = "8SUpqDkcgwOPsqHVtmf9kXfFeVmjVxl4LMn-qSE1AI_Xoeju-9HaoPNGtidyxyka"
)

View File

@@ -1,11 +0,0 @@
//go:build !arm64
package rosa
// latest version of LLVM, conditional to temporarily avoid broken new releases
const (
llvmVersionMajor = "22"
llvmVersion = llvmVersionMajor + ".1.1"
llvmChecksum = "bQvV6D8AZvQykg7-uQb_saTbVavnSo1ykNJ3g57F5iE-evU3HuOYtcRnVIXTK76e"
)

View File

@@ -38,13 +38,6 @@ func init() {
Description: "an open source build system", Description: "an open source build system",
Website: "https://mesonbuild.com/", Website: "https://mesonbuild.com/",
Dependencies: P{
Python,
PkgConfig,
CMake,
Ninja,
},
ID: 6472, ID: 6472,
} }
} }
@@ -73,7 +66,15 @@ func (*MesonHelper) name(name, version string) string {
// extra returns hardcoded meson runtime dependencies. // extra returns hardcoded meson runtime dependencies.
func (*MesonHelper) extra(int) []PArtifact { func (*MesonHelper) extra(int) []PArtifact {
return []PArtifact{Meson} return []PArtifact{
Zlib,
Python,
Meson,
Ninja,
PkgConfig,
CMake,
}
} }
// wantsChmod returns false. // wantsChmod returns false.

View File

@@ -19,6 +19,9 @@ func (t Toolchain) newMuslFts() (pkg.Artifact, string) {
}, &MakeHelper{ }, &MakeHelper{
Generate: "./bootstrap.sh", Generate: "./bootstrap.sh",
}, },
M4,
Perl,
Autoconf,
Automake, Automake,
Libtool, Libtool,
PkgConfig, PkgConfig,

View File

@@ -19,6 +19,9 @@ func (t Toolchain) newMuslObstack() (pkg.Artifact, string) {
}, &MakeHelper{ }, &MakeHelper{
Generate: "./bootstrap.sh", Generate: "./bootstrap.sh",
}, },
M4,
Perl,
Autoconf,
Automake, Automake,
Libtool, Libtool,
PkgConfig, PkgConfig,

View File

@@ -26,10 +26,6 @@ func init() {
Description: "a low-level cryptographic library", Description: "a low-level cryptographic library",
Website: "https://www.lysator.liu.se/~nisse/nettle/", Website: "https://www.lysator.liu.se/~nisse/nettle/",
Dependencies: P{
GMP,
},
ID: 2073, ID: 2073,
} }
} }

View File

@@ -8,8 +8,8 @@ import (
func (t Toolchain) newPerl() (pkg.Artifact, string) { func (t Toolchain) newPerl() (pkg.Artifact, string) {
const ( const (
version = "5.42.1" version = "5.42.0"
checksum = "FsJVq5CZFA7nZklfUl1eC6z2ECEu02XaB1pqfHSKtRLZWpnaBjlB55QOhjKpjkQ2" checksum = "2KR7Jbpk-ZVn1a30LQRwbgUvg2AXlPQZfzrqCr31qD5-yEsTwVQ_W76eZH-EdxM9"
) )
return t.NewPackage("perl", version, pkg.NewHTTPGetTar( return t.NewPackage("perl", version, pkg.NewHTTPGetTar(
nil, "https://www.cpan.org/src/5.0/perl-"+version+".tar.gz", nil, "https://www.cpan.org/src/5.0/perl-"+version+".tar.gz",
@@ -68,14 +68,14 @@ func (t Toolchain) newViaPerlModuleBuild(
name, version string, name, version string,
source pkg.Artifact, source pkg.Artifact,
patches [][2]string, patches [][2]string,
extra ...PArtifact, extra ...pkg.Artifact,
) pkg.Artifact { ) pkg.Artifact {
if name == "" || version == "" { if name == "" || version == "" {
panic("names must be non-empty") panic("names must be non-empty")
} }
return t.New("perl-"+name, 0, t.AppendPresets(nil, return t.New("perl-"+name, 0, slices.Concat(extra, []pkg.Artifact{
slices.Concat(P{Perl}, extra)..., t.Load(Perl),
), nil, nil, ` }), nil, nil, `
cd /usr/src/`+name+` cd /usr/src/`+name+`
perl Build.PL --prefix=/system perl Build.PL --prefix=/system
./Build build ./Build build
@@ -105,10 +105,6 @@ func init() {
Name: "perl-Module::Build", Name: "perl-Module::Build",
Description: "build and install Perl modules", Description: "build and install Perl modules",
Website: "https://metacpan.org/release/Module-Build", Website: "https://metacpan.org/release/Module-Build",
Dependencies: P{
Perl,
},
} }
} }
@@ -271,10 +267,6 @@ func init() {
Name: "perl-Text::WrapI18N", Name: "perl-Text::WrapI18N",
Description: "line wrapping module", Description: "line wrapping module",
Website: "https://metacpan.org/release/Text-WrapI18N", Website: "https://metacpan.org/release/Text-WrapI18N",
Dependencies: P{
PerlTextCharWidth,
},
} }
} }
@@ -321,10 +313,6 @@ func init() {
Name: "perl-Unicode::GCString", Name: "perl-Unicode::GCString",
Description: "String as Sequence of UAX #29 Grapheme Clusters", Description: "String as Sequence of UAX #29 Grapheme Clusters",
Website: "https://metacpan.org/release/Unicode-LineBreak", Website: "https://metacpan.org/release/Unicode-LineBreak",
Dependencies: P{
PerlMIMECharset,
},
} }
} }

View File

@@ -18,6 +18,9 @@ func (t Toolchain) newProcps() (pkg.Artifact, string) {
{"without-ncurses"}, {"without-ncurses"},
}, },
}, },
M4,
Perl,
Autoconf,
Automake, Automake,
Gettext, Gettext,
Libtool, Libtool,

View File

@@ -53,11 +53,11 @@ func (t Toolchain) newPython() (pkg.Artifact, string) {
Check: []string{"test"}, Check: []string{"test"},
}, },
Zlib, Zlib,
Bzip2,
Libffi, Libffi,
OpenSSL,
PkgConfig, PkgConfig,
OpenSSL,
Bzip2,
XZ, XZ,
), version ), version
} }
@@ -69,13 +69,6 @@ func init() {
Description: "the Python programming language interpreter", Description: "the Python programming language interpreter",
Website: "https://www.python.org/", Website: "https://www.python.org/",
Dependencies: P{
Zlib,
Bzip2,
Libffi,
OpenSSL,
},
ID: 13254, ID: 13254,
} }
} }
@@ -88,9 +81,15 @@ func newViaPip(
wname := name + "-" + version + "-" + interpreter + "-" + abi + "-" + platform + ".whl" wname := name + "-" + version + "-" + interpreter + "-" + abi + "-" + platform + ".whl"
return Metadata{ return Metadata{
f: func(t Toolchain) (pkg.Artifact, string) { f: func(t Toolchain) (pkg.Artifact, string) {
return t.New(name+"-"+version, 0, t.AppendPresets(nil, extraRes := make([]pkg.Artifact, len(extra))
slices.Concat(P{Python}, extra)..., for i, p := range extra {
), nil, nil, ` extraRes[i] = t.Load(p)
}
return t.New(name+"-"+version, 0, slices.Concat([]pkg.Artifact{
t.Load(Zlib),
t.Load(Python),
}, extraRes), nil, nil, `
pip3 install \ pip3 install \
--no-index \ --no-index \
--prefix=/system \ --prefix=/system \
@@ -105,19 +104,18 @@ pip3 install \
Name: "python-" + name, Name: "python-" + name,
Description: description, Description: description,
Website: "https://pypi.org/project/" + name + "/", Website: "https://pypi.org/project/" + name + "/",
Dependencies: slices.Concat(P{Python}, extra),
} }
} }
func (t Toolchain) newSetuptools() (pkg.Artifact, string) { func (t Toolchain) newSetuptools() (pkg.Artifact, string) {
const ( const (
version = "82.0.1" version = "82.0.0"
checksum = "nznP46Tj539yqswtOrIM4nQgwLA1h-ApKX7z7ghazROCpyF5swtQGwsZoI93wkhc" checksum = "K9f8Yi7Gg95zjmQsE1LLw9UBb8NglI6EY6pQpdD6DM0Pmc_Td5w2qs1SMngTI6Jp"
) )
return t.New("setuptools-"+version, 0, t.AppendPresets(nil, return t.New("setuptools-"+version, 0, []pkg.Artifact{
Python, t.Load(Zlib),
), nil, nil, ` t.Load(Python),
}, nil, nil, `
pip3 install \ pip3 install \
--no-index \ --no-index \
--prefix=/system \ --prefix=/system \
@@ -134,14 +132,10 @@ func init() {
artifactsM[Setuptools] = Metadata{ artifactsM[Setuptools] = Metadata{
f: Toolchain.newSetuptools, f: Toolchain.newSetuptools,
Name: "python-setuptools", Name: "setuptools",
Description: "the autotools of the Python ecosystem", Description: "the autotools of the Python ecosystem",
Website: "https://pypi.org/project/setuptools/", Website: "https://pypi.org/project/setuptools/",
Dependencies: P{
Python,
},
ID: 4021, ID: 4021,
} }
} }
@@ -278,6 +272,8 @@ func init() {
"https://files.pythonhosted.org/packages/"+ "https://files.pythonhosted.org/packages/"+
"78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/", "78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/",
PythonDistlib, PythonDistlib,
PythonFilelock,
PythonPlatformdirs,
PythonDiscovery, PythonDiscovery,
) )
@@ -292,6 +288,10 @@ func init() {
PythonIdentify, PythonIdentify,
PythonNodeenv, PythonNodeenv,
PythonPyYAML, PythonPyYAML,
PythonDistlib,
PythonFilelock,
PythonPlatformdirs,
PythonDiscovery,
PythonVirtualenv, PythonVirtualenv,
) )
} }

View File

@@ -74,16 +74,21 @@ EOF
Bash, Bash,
Python, Python,
Ninja, Ninja,
Bzip2,
PkgConfig, PkgConfig,
Diffutils, Diffutils,
OpenSSL, OpenSSL,
Bzip2,
XZ, XZ,
Flex, Flex,
Bison, Bison,
M4, M4,
PCRE2,
Libffi,
Zlib,
GLib, GLib,
Zstd, Zstd,
DTC, DTC,
@@ -98,11 +103,6 @@ func init() {
Description: "a generic and open source machine emulator and virtualizer", Description: "a generic and open source machine emulator and virtualizer",
Website: "https://www.qemu.org/", Website: "https://www.qemu.org/",
Dependencies: P{
GLib,
Zstd,
},
ID: 13607, ID: 13607,
} }
} }

View File

@@ -28,10 +28,6 @@ func init() {
Description: "a program that finds duplicate files", Description: "a program that finds duplicate files",
Website: "https://rdfind.pauldreik.se/", Website: "https://rdfind.pauldreik.se/",
Dependencies: P{
Nettle,
},
ID: 231641, ID: 231641,
} }
} }

View File

@@ -8,7 +8,6 @@ import (
"slices" "slices"
"strconv" "strconv"
"strings" "strings"
"sync"
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/internal/pkg" "hakurei.app/internal/pkg"
@@ -20,9 +19,6 @@ const (
// kindBusyboxBin is the kind of [pkg.Artifact] of busyboxBin. // kindBusyboxBin is the kind of [pkg.Artifact] of busyboxBin.
kindBusyboxBin kindBusyboxBin
// kindCollection is the kind of [Collect]. It never cures successfully.
kindCollection
) )
// mustDecode is like [pkg.MustDecode], but replaces the zero value and prints // mustDecode is like [pkg.MustDecode], but replaces the zero value and prints
@@ -458,48 +454,6 @@ type PackageAttr struct {
Flag int Flag int
} }
// pa holds whether a [PArtifact] is present.
type pa = [PresetEnd]bool
// paPool holds addresses of pa.
var paPool = sync.Pool{New: func() any { return new(pa) }}
// paGet returns the address of a new pa.
func paGet() *pa { return paPool.Get().(*pa) }
// paPut returns a pa to paPool.
func paPut(pv *pa) { *pv = pa{}; paPool.Put(pv) }
// appendPreset recursively appends a [PArtifact] and its runtime dependencies.
func (t Toolchain) appendPreset(
a []pkg.Artifact,
pv *pa, p PArtifact,
) []pkg.Artifact {
if pv[p] {
return a
}
pv[p] = true
for _, d := range GetMetadata(p).Dependencies {
a = t.appendPreset(a, pv, d)
}
return append(a, t.Load(p))
}
// AppendPresets recursively appends multiple [PArtifact] and their runtime
// dependencies.
func (t Toolchain) AppendPresets(
a []pkg.Artifact,
presets ...PArtifact,
) []pkg.Artifact {
pv := paGet()
for _, p := range presets {
a = t.appendPreset(a, pv, p)
}
paPut(pv)
return a
}
// NewPackage constructs a [pkg.Artifact] via a build system helper. // NewPackage constructs a [pkg.Artifact] via a build system helper.
func (t Toolchain) NewPackage( func (t Toolchain) NewPackage(
name, version string, name, version string,
@@ -532,14 +486,12 @@ func (t Toolchain) NewPackage(
extraRes := make([]pkg.Artifact, 0, dc) extraRes := make([]pkg.Artifact, 0, dc)
extraRes = append(extraRes, attr.NonStage0...) extraRes = append(extraRes, attr.NonStage0...)
if !t.isStage0() { if !t.isStage0() {
pv := paGet()
for _, p := range helper.extra(attr.Flag) { for _, p := range helper.extra(attr.Flag) {
extraRes = t.appendPreset(extraRes, pv, p) extraRes = append(extraRes, t.Load(p))
} }
for _, p := range extra { for _, p := range extra {
extraRes = t.appendPreset(extraRes, pv, p) extraRes = append(extraRes, t.Load(p))
} }
paPut(pv)
} }
var scriptEarly string var scriptEarly string
@@ -591,29 +543,3 @@ cd '/usr/src/` + name + `/'
})..., })...,
) )
} }
// Collected is returned by [Collect.Cure] to indicate a successful collection.
type Collected struct{}
// Error returns a constant string to satisfy error, but should never be seen
// by the user.
func (Collected) Error() string { return "artifacts successfully collected" }
// Collect implements [pkg.FloodArtifact] to concurrently cure multiple
// [pkg.Artifact]. It returns [Collected].
type Collect []pkg.Artifact
// Cure returns [Collected].
func (*Collect) Cure(*pkg.FContext) error { return Collected{} }
// Kind returns the hardcoded [pkg.Kind] value.
func (*Collect) Kind() pkg.Kind { return kindCollection }
// Params does not write anything, dependencies are already represented in the header.
func (*Collect) Params(*pkg.IContext) {}
// Dependencies returns [Collect] as is.
func (c *Collect) Dependencies() []pkg.Artifact { return *c }
// IsExclusive returns false: Cure is a noop.
func (*Collect) IsExclusive() bool { return false }

View File

@@ -48,12 +48,6 @@ func init() {
Description: "tools to create and extract Squashfs filesystems", Description: "tools to create and extract Squashfs filesystems",
Website: "https://github.com/plougher/squashfs-tools", Website: "https://github.com/plougher/squashfs-tools",
Dependencies: P{
Zstd,
Gzip,
Zlib,
},
ID: 4879, ID: 4879,
} }
} }

View File

@@ -75,10 +75,6 @@ func init() {
Description: "Network Security Services", Description: "Network Security Services",
Website: "https://firefox-source-docs.mozilla.org/security/nss/index.html", Website: "https://firefox-source-docs.mozilla.org/security/nss/index.html",
Dependencies: P{
Zlib,
},
ID: 2503, ID: 2503,
} }
} }
@@ -96,12 +92,14 @@ func init() {
} }
func (t Toolchain) newNSSCACert() (pkg.Artifact, string) { func (t Toolchain) newNSSCACert() (pkg.Artifact, string) {
return t.New("nss-cacert", 0, t.AppendPresets(nil, return t.New("nss-cacert", 0, []pkg.Artifact{
Bash, t.Load(Zlib),
t.Load(Bash),
t.Load(Python),
NSS, t.Load(NSS),
buildcatrust, t.Load(buildcatrust),
), nil, nil, ` }, nil, nil, `
mkdir -p /work/system/etc/ssl/{certs/unbundled,certs/hashed,trust-source} mkdir -p /work/system/etc/ssl/{certs/unbundled,certs/hashed,trust-source}
buildcatrust \ buildcatrust \
--certdata_input /system/nss/certdata.txt \ --certdata_input /system/nss/certdata.txt \

View File

@@ -8,13 +8,13 @@ import (
func (t Toolchain) newTamaGo() (pkg.Artifact, string) { func (t Toolchain) newTamaGo() (pkg.Artifact, string) {
const ( const (
version = "1.26.1" version = "1.26.0"
checksum = "fimZnklQcYWGsTQU8KepLn-yCYaTfNdMI9DCg6NJVQv-3gOJnUEO9mqRCMAHnEXZ" checksum = "5XkfbpTpSdPJfwtTfUegfdu4LUy8nuZ7sCondiRIxTJI9eQONi8z_O_dq9yDkjw8"
) )
return t.New("tamago-go"+version, 0, t.AppendPresets(nil, return t.New("tamago-go"+version, 0, []pkg.Artifact{
Bash, t.Load(Bash),
Go, t.Load(Go),
), nil, []string{ }, nil, []string{
"CC=cc", "CC=cc",
"GOCACHE=/tmp/gocache", "GOCACHE=/tmp/gocache",
}, ` }, `

View File

@@ -11,10 +11,10 @@ func (t Toolchain) newUnzip() (pkg.Artifact, string) {
version = "6.0" version = "6.0"
checksum = "fcqjB1IOVRNJ16K5gTGEDt3zCJDVBc7EDSra9w3H93stqkNwH1vaPQs_QGOpQZu1" checksum = "fcqjB1IOVRNJ16K5gTGEDt3zCJDVBc7EDSra9w3H93stqkNwH1vaPQs_QGOpQZu1"
) )
return t.New("unzip-"+version, 0, t.AppendPresets(nil, return t.New("unzip-"+version, 0, []pkg.Artifact{
Make, t.Load(Make),
Coreutils, t.Load(Coreutils),
), nil, nil, ` }, nil, nil, `
cd /usr/src/unzip/ cd /usr/src/unzip/
unix/configure unix/configure
make -f unix/Makefile generic1 make -f unix/Makefile generic1

View File

@@ -42,12 +42,6 @@ func init() {
Description: "core Wayland window system code and protocol", Description: "core Wayland window system code and protocol",
Website: "https://wayland.freedesktop.org/", Website: "https://wayland.freedesktop.org/",
Dependencies: P{
Libffi,
Libexpat,
Libxml2,
},
ID: 10061, ID: 10061,
} }
} }
@@ -118,6 +112,9 @@ GitLab
}, },
}, (*MesonHelper)(nil), }, (*MesonHelper)(nil),
Wayland, Wayland,
Libffi,
Libexpat,
Libxml2,
), version ), version
} }
func init() { func init() {

View File

@@ -40,6 +40,9 @@ func (t Toolchain) newXproto() (pkg.Artifact, string) {
// ancient configure script // ancient configure script
Generate: "autoreconf -if", Generate: "autoreconf -if",
}, },
M4,
Perl,
Autoconf,
Automake, Automake,
PkgConfig, PkgConfig,
@@ -72,6 +75,9 @@ func (t Toolchain) newLibXau() (pkg.Artifact, string) {
// ancient configure script // ancient configure script
Generate: "autoreconf -if", Generate: "autoreconf -if",
}, },
M4,
Perl,
Autoconf,
Automake, Automake,
Libtool, Libtool,
PkgConfig, PkgConfig,
@@ -88,10 +94,6 @@ func init() {
Description: "functions for handling Xauthority files and entries", Description: "functions for handling Xauthority files and entries",
Website: "https://gitlab.freedesktop.org/xorg/lib/libxau", Website: "https://gitlab.freedesktop.org/xorg/lib/libxau",
Dependencies: P{
Xproto,
},
ID: 1765, ID: 1765,
} }
} }

View File

@@ -41,6 +41,7 @@ func (t Toolchain) newXCB() (pkg.Artifact, string) {
PkgConfig, PkgConfig,
XCBProto, XCBProto,
Xproto,
LibXau, LibXau,
), version ), version
} }
@@ -52,11 +53,6 @@ func init() {
Description: "The X protocol C-language Binding", Description: "The X protocol C-language Binding",
Website: "https://xcb.freedesktop.org/", Website: "https://xcb.freedesktop.org/",
Dependencies: P{
XCBProto,
LibXau,
},
ID: 1767, ID: 1767,
} }
} }

View File

@@ -139,8 +139,6 @@ in
inherit (app) identity groups enablements; inherit (app) identity groups enablements;
inherit (dbusConfig) session_bus system_bus; inherit (dbusConfig) session_bus system_bus;
direct_wayland = app.insecureWayland; direct_wayland = app.insecureWayland;
sched_policy = app.schedPolicy;
sched_priority = app.schedPriority;
container = { container = {
inherit (app) inherit (app)

View File

@@ -98,7 +98,6 @@ in
ints ints
str str
bool bool
enum
package package
anything anything
submodule submodule
@@ -238,29 +237,6 @@ in
}; };
hostAbstract = mkEnableOption "share abstract unix socket scope"; hostAbstract = mkEnableOption "share abstract unix socket scope";
schedPolicy = mkOption {
type = nullOr (enum [
"fifo"
"rr"
"batch"
"idle"
"deadline"
"ext"
]);
default = null;
description = ''
Scheduling policy to set for the container.
The zero value retains the current scheduling policy.
'';
};
schedPriority = mkOption {
type = nullOr (ints.between 1 99);
default = null;
description = ''
Scheduling priority to set for the container.
'';
};
nix = mkEnableOption "nix daemon access"; nix = mkEnableOption "nix daemon access";
mapRealUid = mkEnableOption "mapping to priv-user uid"; mapRealUid = mkEnableOption "mapping to priv-user uid";
device = mkEnableOption "access to all devices"; device = mkEnableOption "access to all devices";

View File

@@ -1,7 +1,7 @@
{ {
lib, lib,
stdenv, stdenv,
buildGo126Module, buildGoModule,
makeBinaryWrapper, makeBinaryWrapper,
xdg-dbus-proxy, xdg-dbus-proxy,
pkg-config, pkg-config,
@@ -17,7 +17,7 @@
fuse3, fuse3,
# for passthru.buildInputs # for passthru.buildInputs
go_1_26, go,
clang, clang,
# for check # for check
@@ -28,9 +28,9 @@
withStatic ? stdenv.hostPlatform.isStatic, withStatic ? stdenv.hostPlatform.isStatic,
}: }:
buildGo126Module rec { buildGoModule rec {
pname = "hakurei"; pname = "hakurei";
version = "0.3.7"; version = "0.3.6";
srcFiltered = builtins.path { srcFiltered = builtins.path {
name = "${pname}-src"; name = "${pname}-src";
@@ -51,7 +51,7 @@ buildGo126Module rec {
]; ];
nativeBuildInputs = [ nativeBuildInputs = [
go_1_26 go
pkg-config pkg-config
wayland-scanner wayland-scanner
]; ];
@@ -125,20 +125,16 @@ buildGo126Module rec {
--inherit-argv0 --prefix PATH : ${lib.makeBinPath appPackages} --inherit-argv0 --prefix PATH : ${lib.makeBinPath appPackages}
''; '';
passthru = { passthru.targetPkgs = [
go = go_1_26; go
clang
xorg.xorgproto
util-linux
targetPkgs = [ # for go generate
go_1_26 wayland-protocols
clang wayland-scanner
xorg.xorgproto ]
util-linux ++ buildInputs
++ nativeBuildInputs;
# for go generate
wayland-protocols
wayland-scanner
]
++ buildInputs
++ nativeBuildInputs;
};
} }

View File

@@ -28,15 +28,6 @@
# Automatically login on tty1 as a normal user: # Automatically login on tty1 as a normal user:
services.getty.autologinUser = "alice"; services.getty.autologinUser = "alice";
security.pam.loginLimits = [
{
domain = "@users";
item = "rtprio";
type = "-";
value = 1;
}
];
environment = { environment = {
systemPackages = with pkgs; [ systemPackages = with pkgs; [
# For D-Bus tests: # For D-Bus tests:

View File

@@ -34,7 +34,7 @@ testers.nixosTest {
(writeShellScriptBin "hakurei-test" '' (writeShellScriptBin "hakurei-test" ''
# Assert hst CGO_ENABLED=0: ${ # Assert hst CGO_ENABLED=0: ${
with pkgs; with pkgs;
runCommand "hakurei-hst-cgo" { nativeBuildInputs = [ self.packages.${system}.hakurei.go ]; } '' runCommand "hakurei-hst-cgo" { nativeBuildInputs = [ go ]; } ''
cp -r ${options.environment.hakurei.package.default.src} "$out" cp -r ${options.environment.hakurei.package.default.src} "$out"
chmod -R +w "$out" chmod -R +w "$out"
cp ${writeText "hst_cgo_test.go" ''package hakurei_test;import("testing";"hakurei.app/hst");func TestTemplate(t *testing.T){hst.Template()}''} "$out/hst_cgo_test.go" cp ${writeText "hst_cgo_test.go" ''package hakurei_test;import("testing";"hakurei.app/hst");func TestTemplate(t *testing.T){hst.Template()}''} "$out/hst_cgo_test.go"

View File

@@ -23,14 +23,6 @@
security = { security = {
sudo.wheelNeedsPassword = false; sudo.wheelNeedsPassword = false;
rtkit.enable = true; rtkit.enable = true;
pam.loginLimits = [
{
domain = "@users";
item = "rtprio";
type = "-";
value = 1;
}
];
}; };
services = { services = {

View File

@@ -206,17 +206,6 @@ machine.wait_until_fails("pgrep foot", timeout=5)
machine.wait_for_file("/tmp/shim-cont-unexpected-pid") machine.wait_for_file("/tmp/shim-cont-unexpected-pid")
print(machine.succeed('grep "shim: got SIGCONT from unexpected process$" /tmp/shim-cont-unexpected-pid')) print(machine.succeed('grep "shim: got SIGCONT from unexpected process$" /tmp/shim-cont-unexpected-pid'))
# Check setscheduler:
sched_unset = int(machine.succeed("sudo -u alice -i hakurei -v run cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2"))
if sched_unset != 0:
raise Exception(f"unexpected unset policy: {sched_unset}")
sched_idle = int(machine.succeed("sudo -u alice -i hakurei -v run --policy=idle cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2"))
if sched_idle != 5:
raise Exception(f"unexpected idle policy: {sched_idle}")
sched_rr = int(machine.succeed("sudo -u alice -i hakurei -v run --policy=rr cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2"))
if sched_rr != 2:
raise Exception(f"unexpected round-robin policy: {sched_idle}")
# Start app (foot) with Wayland enablement: # Start app (foot) with Wayland enablement:
swaymsg("exec ne-foot") swaymsg("exec ne-foot")
wait_for_window(f"u0_a{hakurei_identity(0)}@machine") wait_for_window(f"u0_a{hakurei_identity(0)}@machine")