cmd: remove hpkg
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m30s
Test / Hakurei (push) Successful in 3m40s
Test / ShareFS (push) Successful in 3m48s
Test / Sandbox (race detector) (push) Successful in 5m3s
Test / Hakurei (race detector) (push) Successful in 5m54s
Test / Flake checks (push) Successful in 1m28s
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m30s
Test / Hakurei (push) Successful in 3m40s
Test / ShareFS (push) Successful in 3m48s
Test / Sandbox (race detector) (push) Successful in 5m3s
Test / Hakurei (race detector) (push) Successful in 5m54s
Test / Flake checks (push) Successful in 1m28s
This proof-of-concept was abandoned long ago. Its test suite is flaky, heavy on I/O and does not increase test coverage. This change fully removes hpkg and supporting code. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -89,23 +89,6 @@ jobs:
|
||||
path: result/*
|
||||
retention-days: 1
|
||||
|
||||
hpkg:
|
||||
name: Hpkg
|
||||
runs-on: nix
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run NixOS test
|
||||
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.hpkg
|
||||
|
||||
- name: Upload test output
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "hpkg-vm-output"
|
||||
path: result/*
|
||||
retention-days: 1
|
||||
|
||||
check:
|
||||
name: Flake checks
|
||||
needs:
|
||||
@@ -114,7 +97,6 @@ jobs:
|
||||
- sandbox
|
||||
- sandbox-race
|
||||
- sharefs
|
||||
- hpkg
|
||||
runs-on: nix
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
This program is a proof of concept and is now deprecated. It is only kept
|
||||
around for API demonstration purposes and to make the most out of the test
|
||||
suite.
|
||||
|
||||
This program is replaced by planterette, which can be found at
|
||||
https://git.gensokyo.uk/security/planterette. Development effort should be
|
||||
focused there instead.
|
||||
173
cmd/hpkg/app.go
173
cmd/hpkg/app.go
@@ -1,173 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
type appInfo struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
|
||||
// passed through to [hst.Config]
|
||||
ID string `json:"id"`
|
||||
// passed through to [hst.Config]
|
||||
Identity int `json:"identity"`
|
||||
// passed through to [hst.Config]
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
// passed through to [hst.Config]
|
||||
Devel bool `json:"devel,omitempty"`
|
||||
// passed through to [hst.Config]
|
||||
Userns bool `json:"userns,omitempty"`
|
||||
// passed through to [hst.Config]
|
||||
HostNet bool `json:"net,omitempty"`
|
||||
// passed through to [hst.Config]
|
||||
HostAbstract bool `json:"abstract,omitempty"`
|
||||
// passed through to [hst.Config]
|
||||
Device bool `json:"dev,omitempty"`
|
||||
// passed through to [hst.Config]
|
||||
Tty bool `json:"tty,omitempty"`
|
||||
// passed through to [hst.Config]
|
||||
MapRealUID bool `json:"map_real_uid,omitempty"`
|
||||
// passed through to [hst.Config]
|
||||
DirectWayland bool `json:"direct_wayland,omitempty"`
|
||||
// passed through to [hst.Config]
|
||||
SystemBus *hst.BusConfig `json:"system_bus,omitempty"`
|
||||
// passed through to [hst.Config]
|
||||
SessionBus *hst.BusConfig `json:"session_bus,omitempty"`
|
||||
// passed through to [hst.Config]
|
||||
Enablements *hst.Enablements `json:"enablements,omitempty"`
|
||||
|
||||
// passed through to [hst.Config]
|
||||
Multiarch bool `json:"multiarch,omitempty"`
|
||||
// passed through to [hst.Config]
|
||||
Bluetooth bool `json:"bluetooth,omitempty"`
|
||||
|
||||
// allow gpu access within sandbox
|
||||
GPU bool `json:"gpu"`
|
||||
// store path to nixGL mesa wrappers
|
||||
Mesa string `json:"mesa,omitempty"`
|
||||
// store path to nixGL source
|
||||
NixGL string `json:"nix_gl,omitempty"`
|
||||
// store path to activate-and-exec script
|
||||
Launcher *check.Absolute `json:"launcher"`
|
||||
// store path to /run/current-system
|
||||
CurrentSystem *check.Absolute `json:"current_system"`
|
||||
// store path to home-manager activation package
|
||||
ActivationPackage string `json:"activation_package"`
|
||||
}
|
||||
|
||||
func (app *appInfo) toHst(pathSet *appPathSet, pathname *check.Absolute, argv []string, flagDropShell bool) *hst.Config {
|
||||
config := &hst.Config{
|
||||
ID: app.ID,
|
||||
|
||||
Enablements: app.Enablements,
|
||||
|
||||
SystemBus: app.SystemBus,
|
||||
SessionBus: app.SessionBus,
|
||||
DirectWayland: app.DirectWayland,
|
||||
|
||||
Identity: app.Identity,
|
||||
Groups: app.Groups,
|
||||
|
||||
Container: &hst.ContainerConfig{
|
||||
Hostname: formatHostname(app.Name),
|
||||
Filesystem: []hst.FilesystemConfigJSON{
|
||||
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: pathSet.cacheDir.Append("etc"), Special: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath.Append("store"), Target: pathNixStore}},
|
||||
{FilesystemConfig: &hst.FSLink{Target: pathCurrentSystem, Linkname: app.CurrentSystem.String()}},
|
||||
{FilesystemConfig: &hst.FSLink{Target: pathBin, Linkname: pathSwBin.String()}},
|
||||
{FilesystemConfig: &hst.FSLink{Target: fhs.AbsUsrBin, Linkname: pathSwBin.String()}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: pathSet.metaPath, Target: hst.AbsPrivateTmp.Append("app")}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsEtc.Append("resolv.conf"), Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("block"), Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("bus"), Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("class"), Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("dev"), Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("devices"), Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID), Source: pathSet.homeDir, Write: true, Ensure: true}},
|
||||
},
|
||||
|
||||
Username: "hakurei",
|
||||
Shell: pathShell,
|
||||
Home: pathDataData.Append(app.ID),
|
||||
|
||||
Path: pathname,
|
||||
Args: argv,
|
||||
},
|
||||
ExtraPerms: []hst.ExtraPermConfig{
|
||||
{Path: dataHome, Execute: true},
|
||||
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
||||
},
|
||||
}
|
||||
|
||||
if app.Devel {
|
||||
config.Container.Flags |= hst.FDevel
|
||||
}
|
||||
if app.Userns {
|
||||
config.Container.Flags |= hst.FUserns
|
||||
}
|
||||
if app.HostNet {
|
||||
config.Container.Flags |= hst.FHostNet
|
||||
}
|
||||
if app.HostAbstract {
|
||||
config.Container.Flags |= hst.FHostAbstract
|
||||
}
|
||||
if app.Device {
|
||||
config.Container.Flags |= hst.FDevice
|
||||
}
|
||||
if app.Tty || flagDropShell {
|
||||
config.Container.Flags |= hst.FTty
|
||||
}
|
||||
if app.MapRealUID {
|
||||
config.Container.Flags |= hst.FMapRealUID
|
||||
}
|
||||
if app.Multiarch {
|
||||
config.Container.Flags |= hst.FMultiarch
|
||||
}
|
||||
config.Container.Flags |= hst.FShareRuntime | hst.FShareTmpdir
|
||||
return config
|
||||
}
|
||||
|
||||
func loadAppInfo(name string, beforeFail func()) *appInfo {
|
||||
bundle := new(appInfo)
|
||||
if f, err := os.Open(name); err != nil {
|
||||
beforeFail()
|
||||
log.Fatalf("cannot open bundle: %v", err)
|
||||
} else if err = json.NewDecoder(f).Decode(&bundle); err != nil {
|
||||
beforeFail()
|
||||
log.Fatalf("cannot parse bundle metadata: %v", err)
|
||||
} else if err = f.Close(); err != nil {
|
||||
log.Printf("cannot close bundle metadata: %v", err)
|
||||
// not fatal
|
||||
}
|
||||
|
||||
if bundle.ID == "" {
|
||||
beforeFail()
|
||||
log.Fatal("application identifier must not be empty")
|
||||
}
|
||||
if bundle.Launcher == nil {
|
||||
beforeFail()
|
||||
log.Fatal("launcher must not be empty")
|
||||
}
|
||||
if bundle.CurrentSystem == nil {
|
||||
beforeFail()
|
||||
log.Fatal("current-system must not be empty")
|
||||
}
|
||||
|
||||
return bundle
|
||||
}
|
||||
|
||||
func formatHostname(name string) string {
|
||||
if h, err := os.Hostname(); err != nil {
|
||||
log.Printf("cannot get hostname: %v", err)
|
||||
return "hakurei-" + name
|
||||
} else {
|
||||
return h + "-" + name
|
||||
}
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
{
|
||||
nixpkgsFor,
|
||||
system,
|
||||
nixpkgs,
|
||||
home-manager,
|
||||
}:
|
||||
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
closureInfo,
|
||||
writeScript,
|
||||
runtimeShell,
|
||||
writeText,
|
||||
symlinkJoin,
|
||||
vmTools,
|
||||
runCommand,
|
||||
fetchFromGitHub,
|
||||
|
||||
zstd,
|
||||
nix,
|
||||
sqlite,
|
||||
|
||||
name ? throw "name is required",
|
||||
version ? throw "version is required",
|
||||
pname ? "${name}-${version}",
|
||||
modules ? [ ],
|
||||
nixosModules ? [ ],
|
||||
script ? ''
|
||||
exec "$SHELL" "$@"
|
||||
'',
|
||||
|
||||
id ? name,
|
||||
identity ? throw "identity is required",
|
||||
groups ? [ ],
|
||||
userns ? false,
|
||||
net ? true,
|
||||
dev ? false,
|
||||
no_new_session ? false,
|
||||
map_real_uid ? false,
|
||||
direct_wayland ? false,
|
||||
system_bus ? null,
|
||||
session_bus ? null,
|
||||
|
||||
allow_wayland ? true,
|
||||
allow_x11 ? false,
|
||||
allow_dbus ? true,
|
||||
allow_audio ? true,
|
||||
gpu ? allow_wayland || allow_x11,
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) optionals;
|
||||
|
||||
homeManagerConfiguration = home-manager.lib.homeManagerConfiguration {
|
||||
pkgs = nixpkgsFor.${system};
|
||||
modules = modules ++ [
|
||||
{
|
||||
home = {
|
||||
username = "hakurei";
|
||||
homeDirectory = "/data/data/${id}";
|
||||
stateVersion = "22.11";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
launcher = writeScript "hakurei-${pname}" ''
|
||||
#!${runtimeShell} -el
|
||||
${script}
|
||||
'';
|
||||
|
||||
extraNixOSConfig =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
environment = {
|
||||
etc.nixpkgs.source = nixpkgs.outPath;
|
||||
systemPackages = [ pkgs.nix ];
|
||||
};
|
||||
|
||||
imports = nixosModules;
|
||||
};
|
||||
nixos = nixpkgs.lib.nixosSystem {
|
||||
inherit system;
|
||||
modules = [
|
||||
extraNixOSConfig
|
||||
{ nix.settings.experimental-features = [ "flakes" ]; }
|
||||
{ nix.settings.experimental-features = [ "nix-command" ]; }
|
||||
{ boot.isContainer = true; }
|
||||
{ system.stateVersion = "22.11"; }
|
||||
];
|
||||
};
|
||||
|
||||
etc = vmTools.runInLinuxVM (
|
||||
runCommand "etc" { } ''
|
||||
mkdir -p /etc
|
||||
${nixos.config.system.build.etcActivationCommands}
|
||||
|
||||
# remove unused files
|
||||
rm -rf /etc/sudoers
|
||||
|
||||
mkdir -p $out
|
||||
tar -C /etc -cf "$out/etc.tar" .
|
||||
''
|
||||
);
|
||||
|
||||
extendSessionDefault = id: ext: {
|
||||
filter = true;
|
||||
|
||||
talk = [ "org.freedesktop.Notifications" ] ++ ext.talk;
|
||||
own =
|
||||
(optionals (id != null) [
|
||||
"${id}.*"
|
||||
"org.mpris.MediaPlayer2.${id}.*"
|
||||
])
|
||||
++ ext.own;
|
||||
|
||||
inherit (ext) call broadcast;
|
||||
};
|
||||
|
||||
nixGL = fetchFromGitHub {
|
||||
owner = "nix-community";
|
||||
repo = "nixGL";
|
||||
rev = "310f8e49a149e4c9ea52f1adf70cdc768ec53f8a";
|
||||
hash = "sha256-lnzZQYG0+EXl/6NkGpyIz+FEOc/DSEG57AP1VsdeNrM=";
|
||||
};
|
||||
|
||||
mesaWrappers =
|
||||
let
|
||||
isIntelX86Platform = system == "x86_64-linux";
|
||||
nixGLPackages = import (nixGL + "/default.nix") {
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
enable32bits = isIntelX86Platform;
|
||||
enableIntelX86Extensions = isIntelX86Platform;
|
||||
};
|
||||
in
|
||||
symlinkJoin {
|
||||
name = "nixGL-mesa";
|
||||
paths = with nixGLPackages; [
|
||||
nixGLIntel
|
||||
nixVulkanIntel
|
||||
];
|
||||
};
|
||||
|
||||
info = builtins.toJSON {
|
||||
inherit
|
||||
name
|
||||
version
|
||||
id
|
||||
identity
|
||||
launcher
|
||||
groups
|
||||
userns
|
||||
net
|
||||
dev
|
||||
no_new_session
|
||||
map_real_uid
|
||||
direct_wayland
|
||||
system_bus
|
||||
gpu
|
||||
;
|
||||
|
||||
session_bus =
|
||||
if session_bus != null then
|
||||
(session_bus (extendSessionDefault id))
|
||||
else
|
||||
(extendSessionDefault id {
|
||||
talk = [ ];
|
||||
own = [ ];
|
||||
call = { };
|
||||
broadcast = { };
|
||||
});
|
||||
|
||||
enablements = {
|
||||
wayland = allow_wayland;
|
||||
x11 = allow_x11;
|
||||
dbus = allow_dbus;
|
||||
pipewire = allow_audio;
|
||||
};
|
||||
|
||||
mesa = if gpu then mesaWrappers else null;
|
||||
nix_gl = if gpu then nixGL else null;
|
||||
current_system = nixos.config.system.build.toplevel;
|
||||
activation_package = homeManagerConfiguration.activationPackage;
|
||||
};
|
||||
in
|
||||
|
||||
stdenv.mkDerivation {
|
||||
name = "${pname}.pkg";
|
||||
inherit version;
|
||||
__structuredAttrs = true;
|
||||
|
||||
nativeBuildInputs = [
|
||||
zstd
|
||||
nix
|
||||
sqlite
|
||||
];
|
||||
|
||||
buildCommand = ''
|
||||
NIX_ROOT="$(mktemp -d)"
|
||||
export USER="nobody"
|
||||
|
||||
# create bootstrap store
|
||||
bootstrapClosureInfo="${
|
||||
closureInfo {
|
||||
rootPaths = [
|
||||
nix
|
||||
nixos.config.system.build.toplevel
|
||||
];
|
||||
}
|
||||
}"
|
||||
echo "copying bootstrap store paths..."
|
||||
mkdir -p "$NIX_ROOT/nix/store"
|
||||
xargs -n 1 -a "$bootstrapClosureInfo/store-paths" cp -at "$NIX_ROOT/nix/store/"
|
||||
NIX_REMOTE="local?root=$NIX_ROOT" nix-store --load-db < "$bootstrapClosureInfo/registration"
|
||||
NIX_REMOTE="local?root=$NIX_ROOT" nix-store --optimise
|
||||
sqlite3 "$NIX_ROOT/nix/var/nix/db/db.sqlite" "UPDATE ValidPaths SET registrationTime = ''${SOURCE_DATE_EPOCH}"
|
||||
chmod -R +r "$NIX_ROOT/nix/var"
|
||||
|
||||
# create binary cache
|
||||
closureInfo="${
|
||||
closureInfo {
|
||||
rootPaths = [
|
||||
homeManagerConfiguration.activationPackage
|
||||
launcher
|
||||
]
|
||||
++ optionals gpu [
|
||||
mesaWrappers
|
||||
nixGL
|
||||
];
|
||||
}
|
||||
}"
|
||||
echo "copying application paths..."
|
||||
TMP_STORE="$(mktemp -d)"
|
||||
mkdir -p "$TMP_STORE/nix/store"
|
||||
xargs -n 1 -a "$closureInfo/store-paths" cp -at "$TMP_STORE/nix/store/"
|
||||
NIX_REMOTE="local?root=$TMP_STORE" nix-store --load-db < "$closureInfo/registration"
|
||||
sqlite3 "$TMP_STORE/nix/var/nix/db/db.sqlite" "UPDATE ValidPaths SET registrationTime = ''${SOURCE_DATE_EPOCH}"
|
||||
NIX_REMOTE="local?root=$TMP_STORE" nix --offline --extra-experimental-features nix-command \
|
||||
--verbose --log-format raw-with-logs \
|
||||
copy --all --no-check-sigs --to \
|
||||
"file://$NIX_ROOT/res?compression=zstd&compression-level=19¶llel-compression=true"
|
||||
|
||||
# package /etc
|
||||
mkdir -p "$NIX_ROOT/etc"
|
||||
tar -C "$NIX_ROOT/etc" -xf "${etc}/etc.tar"
|
||||
|
||||
# write metadata
|
||||
cp "${writeText "bundle.json" info}" "$NIX_ROOT/bundle.json"
|
||||
|
||||
# create an intermediate file to improve zstd performance
|
||||
INTER="$(mktemp)"
|
||||
tar -C "$NIX_ROOT" -cf "$INTER" .
|
||||
zstd -T0 -19 -fo "$out" "$INTER"
|
||||
'';
|
||||
}
|
||||
335
cmd/hpkg/main.go
335
cmd/hpkg/main.go
@@ -1,335 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/command"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
var (
|
||||
errSuccess = errors.New("success")
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetPrefix("hpkg: ")
|
||||
log.SetFlags(0)
|
||||
msg := message.New(log.Default())
|
||||
|
||||
if err := os.Setenv("SHELL", pathShell.String()); err != nil {
|
||||
log.Fatalf("cannot set $SHELL: %v", err)
|
||||
}
|
||||
|
||||
if os.Geteuid() == 0 {
|
||||
log.Fatal("this program must not run as root")
|
||||
}
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(),
|
||||
syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop() // unreachable
|
||||
|
||||
var (
|
||||
flagVerbose bool
|
||||
flagDropShell bool
|
||||
)
|
||||
c := command.New(os.Stderr, log.Printf, "hpkg", func([]string) error { msg.SwapVerbose(flagVerbose); return nil }).
|
||||
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
|
||||
Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next hakurei action")
|
||||
|
||||
{
|
||||
var (
|
||||
flagDropShellActivate bool
|
||||
)
|
||||
c.NewCommand("install", "Install an application from its package", func(args []string) error {
|
||||
if len(args) != 1 {
|
||||
log.Println("invalid argument")
|
||||
return syscall.EINVAL
|
||||
}
|
||||
pkgPath := args[0]
|
||||
if !path.IsAbs(pkgPath) {
|
||||
if dir, err := os.Getwd(); err != nil {
|
||||
log.Printf("cannot get current directory: %v", err)
|
||||
return err
|
||||
} else {
|
||||
pkgPath = path.Join(dir, pkgPath)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Look up paths to programs started by hpkg.
|
||||
This is done here to ease error handling as cleanup is not yet required.
|
||||
*/
|
||||
|
||||
var (
|
||||
_ = lookPath("zstd")
|
||||
tar = lookPath("tar")
|
||||
chmod = lookPath("chmod")
|
||||
rm = lookPath("rm")
|
||||
)
|
||||
|
||||
/*
|
||||
Extract package and set up for cleanup.
|
||||
*/
|
||||
|
||||
var workDir *check.Absolute
|
||||
if p, err := os.MkdirTemp("", "hpkg.*"); err != nil {
|
||||
log.Printf("cannot create temporary directory: %v", err)
|
||||
return err
|
||||
} else if workDir, err = check.NewAbs(p); err != nil {
|
||||
log.Printf("invalid temporary directory: %v", err)
|
||||
return err
|
||||
}
|
||||
cleanup := func() {
|
||||
// should be faster than a native implementation
|
||||
mustRun(msg, chmod, "-R", "+w", workDir.String())
|
||||
mustRun(msg, rm, "-rf", workDir.String())
|
||||
}
|
||||
beforeRunFail.Store(&cleanup)
|
||||
|
||||
mustRun(msg, tar, "-C", workDir.String(), "-xf", pkgPath)
|
||||
|
||||
/*
|
||||
Parse bundle and app metadata, do pre-install checks.
|
||||
*/
|
||||
|
||||
bundle := loadAppInfo(path.Join(workDir.String(), "bundle.json"), cleanup)
|
||||
pathSet := pathSetByApp(bundle.ID)
|
||||
|
||||
a := bundle
|
||||
if s, err := os.Stat(pathSet.metaPath.String()); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
cleanup()
|
||||
log.Printf("cannot access %q: %v", pathSet.metaPath, err)
|
||||
return err
|
||||
}
|
||||
// did not modify app, clean installation condition met later
|
||||
} else if s.IsDir() {
|
||||
cleanup()
|
||||
log.Printf("metadata path %q is not a file", pathSet.metaPath)
|
||||
return syscall.EBADMSG
|
||||
} else {
|
||||
a = loadAppInfo(pathSet.metaPath.String(), cleanup)
|
||||
if a.ID != bundle.ID {
|
||||
cleanup()
|
||||
log.Printf("app %q claims to have identifier %q",
|
||||
bundle.ID, a.ID)
|
||||
return syscall.EBADE
|
||||
}
|
||||
// sec: should verify credentials
|
||||
}
|
||||
|
||||
if a != bundle {
|
||||
// do not try to re-install
|
||||
if a.NixGL == bundle.NixGL &&
|
||||
a.CurrentSystem == bundle.CurrentSystem &&
|
||||
a.Launcher == bundle.Launcher &&
|
||||
a.ActivationPackage == bundle.ActivationPackage {
|
||||
cleanup()
|
||||
log.Printf("package %q is identical to local application %q",
|
||||
pkgPath, a.ID)
|
||||
return errSuccess
|
||||
}
|
||||
|
||||
// identity determines uid
|
||||
if a.Identity != bundle.Identity {
|
||||
cleanup()
|
||||
log.Printf("package %q identity %d differs from installed %d",
|
||||
pkgPath, bundle.Identity, a.Identity)
|
||||
return syscall.EBADE
|
||||
}
|
||||
|
||||
// sec: should compare version string
|
||||
msg.Verbosef("installing application %q version %q over local %q",
|
||||
bundle.ID, bundle.Version, a.Version)
|
||||
} else {
|
||||
msg.Verbosef("application %q clean installation", bundle.ID)
|
||||
// sec: should install credentials
|
||||
}
|
||||
|
||||
/*
|
||||
Setup steps for files owned by the target user.
|
||||
*/
|
||||
|
||||
withCacheDir(ctx, msg, "install", []string{
|
||||
// export inner bundle path in the environment
|
||||
"export BUNDLE=" + hst.PrivateTmp + "/bundle",
|
||||
// replace inner /etc
|
||||
"mkdir -p etc",
|
||||
"chmod -R +w etc",
|
||||
"rm -rf etc",
|
||||
"cp -dRf $BUNDLE/etc etc",
|
||||
// replace inner /nix
|
||||
"mkdir -p nix",
|
||||
"chmod -R +w nix",
|
||||
"rm -rf nix",
|
||||
"cp -dRf /nix nix",
|
||||
// copy from binary cache
|
||||
"nix copy --offline --no-check-sigs --all --from file://$BUNDLE/res --to $PWD",
|
||||
// deduplicate nix store
|
||||
"nix store --offline --store $PWD optimise",
|
||||
// make cache directory world-readable for autoetc
|
||||
"chmod 0755 .",
|
||||
}, workDir, bundle, pathSet, flagDropShell, cleanup)
|
||||
|
||||
if bundle.GPU {
|
||||
withCacheDir(ctx, msg, "mesa-wrappers", []string{
|
||||
// link nixGL mesa wrappers
|
||||
"mkdir -p nix/.nixGL",
|
||||
"ln -s " + bundle.Mesa + "/bin/nixGLIntel nix/.nixGL/nixGL",
|
||||
"ln -s " + bundle.Mesa + "/bin/nixVulkanIntel nix/.nixGL/nixVulkan",
|
||||
}, workDir, bundle, pathSet, false, cleanup)
|
||||
}
|
||||
|
||||
/*
|
||||
Activate home-manager generation.
|
||||
*/
|
||||
|
||||
withNixDaemon(ctx, msg, "activate", []string{
|
||||
// clean up broken links
|
||||
"mkdir -p .local/state/{nix,home-manager}",
|
||||
"chmod -R +w .local/state/{nix,home-manager}",
|
||||
"rm -rf .local/state/{nix,home-manager}",
|
||||
// run activation script
|
||||
bundle.ActivationPackage + "/activate",
|
||||
}, false, func(config *hst.Config) *hst.Config { return config },
|
||||
bundle, pathSet, flagDropShellActivate, cleanup)
|
||||
|
||||
/*
|
||||
Installation complete. Write metadata to block re-installs or downgrades.
|
||||
*/
|
||||
|
||||
// serialise metadata to ensure consistency
|
||||
if f, err := os.OpenFile(pathSet.metaPath.String()+"~", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil {
|
||||
cleanup()
|
||||
log.Printf("cannot create metadata file: %v", err)
|
||||
return err
|
||||
} else if err = json.NewEncoder(f).Encode(bundle); err != nil {
|
||||
cleanup()
|
||||
log.Printf("cannot write metadata: %v", err)
|
||||
return err
|
||||
} else if err = f.Close(); err != nil {
|
||||
log.Printf("cannot close metadata file: %v", err)
|
||||
// not fatal
|
||||
}
|
||||
|
||||
if err := os.Rename(pathSet.metaPath.String()+"~", pathSet.metaPath.String()); err != nil {
|
||||
cleanup()
|
||||
log.Printf("cannot rename metadata file: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
cleanup()
|
||||
return errSuccess
|
||||
}).
|
||||
Flag(&flagDropShellActivate, "s", command.BoolFlag(false), "Drop to a shell on activation")
|
||||
}
|
||||
|
||||
{
|
||||
var (
|
||||
flagDropShellNixGL bool
|
||||
flagAutoDrivers bool
|
||||
)
|
||||
c.NewCommand("start", "Start an application", func(args []string) error {
|
||||
if len(args) < 1 {
|
||||
log.Println("invalid argument")
|
||||
return syscall.EINVAL
|
||||
}
|
||||
|
||||
/*
|
||||
Parse app metadata.
|
||||
*/
|
||||
|
||||
id := args[0]
|
||||
pathSet := pathSetByApp(id)
|
||||
a := loadAppInfo(pathSet.metaPath.String(), func() {})
|
||||
if a.ID != id {
|
||||
log.Printf("app %q claims to have identifier %q", id, a.ID)
|
||||
return syscall.EBADE
|
||||
}
|
||||
|
||||
/*
|
||||
Prepare nixGL.
|
||||
*/
|
||||
|
||||
if a.GPU && flagAutoDrivers {
|
||||
withNixDaemon(ctx, msg, "nix-gl", []string{
|
||||
"mkdir -p /nix/.nixGL/auto",
|
||||
"rm -rf /nix/.nixGL/auto",
|
||||
"export NIXPKGS_ALLOW_UNFREE=1",
|
||||
"nix build --impure " +
|
||||
"--out-link /nix/.nixGL/auto/opengl " +
|
||||
"--override-input nixpkgs path:/etc/nixpkgs " +
|
||||
"path:" + a.NixGL,
|
||||
"nix build --impure " +
|
||||
"--out-link /nix/.nixGL/auto/vulkan " +
|
||||
"--override-input nixpkgs path:/etc/nixpkgs " +
|
||||
"path:" + a.NixGL + "#nixVulkanNvidia",
|
||||
}, true, func(config *hst.Config) *hst.Config {
|
||||
config.Container.Filesystem = append(config.Container.Filesystem, []hst.FilesystemConfigJSON{
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsEtc.Append("resolv.conf"), Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("block"), Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("bus"), Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("class"), Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("dev"), Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("devices"), Optional: true}},
|
||||
}...)
|
||||
appendGPUFilesystem(config)
|
||||
return config
|
||||
}, a, pathSet, flagDropShellNixGL, func() {})
|
||||
}
|
||||
|
||||
/*
|
||||
Create app configuration.
|
||||
*/
|
||||
|
||||
pathname := a.Launcher
|
||||
argv := make([]string, 1, len(args))
|
||||
if flagDropShell {
|
||||
pathname = pathShell
|
||||
argv[0] = bash
|
||||
} else {
|
||||
argv[0] = a.Launcher.String()
|
||||
}
|
||||
argv = append(argv, args[1:]...)
|
||||
config := a.toHst(pathSet, pathname, argv, flagDropShell)
|
||||
|
||||
/*
|
||||
Expose GPU devices.
|
||||
*/
|
||||
|
||||
if a.GPU {
|
||||
config.Container.Filesystem = append(config.Container.Filesystem,
|
||||
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath.Append(".nixGL"), Target: hst.AbsPrivateTmp.Append("nixGL")}})
|
||||
appendGPUFilesystem(config)
|
||||
}
|
||||
|
||||
/*
|
||||
Spawn app.
|
||||
*/
|
||||
|
||||
mustRunApp(ctx, msg, config, func() {})
|
||||
return errSuccess
|
||||
}).
|
||||
Flag(&flagDropShellNixGL, "s", command.BoolFlag(false), "Drop to a shell on nixGL build").
|
||||
Flag(&flagAutoDrivers, "auto-drivers", command.BoolFlag(false), "Attempt automatic opengl driver detection")
|
||||
}
|
||||
|
||||
c.MustParse(os.Args[1:], func(err error) {
|
||||
msg.Verbosef("command returned %v", err)
|
||||
if errors.Is(err, errSuccess) {
|
||||
msg.BeforeExit()
|
||||
os.Exit(0)
|
||||
}
|
||||
})
|
||||
log.Fatal("unreachable")
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
const bash = "bash"
|
||||
|
||||
var (
|
||||
dataHome *check.Absolute
|
||||
)
|
||||
|
||||
func init() {
|
||||
// dataHome
|
||||
if a, err := check.NewAbs(os.Getenv("HAKUREI_DATA_HOME")); err == nil {
|
||||
dataHome = a
|
||||
} else {
|
||||
dataHome = fhs.AbsVarLib.Append("hakurei/" + strconv.Itoa(os.Getuid()))
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
pathBin = fhs.AbsRoot.Append("bin")
|
||||
|
||||
pathNix = check.MustAbs("/nix/")
|
||||
pathNixStore = pathNix.Append("store/")
|
||||
pathCurrentSystem = fhs.AbsRun.Append("current-system")
|
||||
pathSwBin = pathCurrentSystem.Append("sw/bin/")
|
||||
pathShell = pathSwBin.Append(bash)
|
||||
|
||||
pathData = check.MustAbs("/data")
|
||||
pathDataData = pathData.Append("data")
|
||||
)
|
||||
|
||||
func lookPath(file string) string {
|
||||
if p, err := exec.LookPath(file); err != nil {
|
||||
log.Fatalf("%s: command not found", file)
|
||||
return ""
|
||||
} else {
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
var beforeRunFail = new(atomic.Pointer[func()])
|
||||
|
||||
func mustRun(msg message.Msg, name string, arg ...string) {
|
||||
msg.Verbosef("spawning process: %q %q", name, arg)
|
||||
cmd := exec.Command(name, arg...)
|
||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
if f := beforeRunFail.Swap(nil); f != nil {
|
||||
(*f)()
|
||||
}
|
||||
log.Fatalf("%s: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
type appPathSet struct {
|
||||
// ${dataHome}/${id}
|
||||
baseDir *check.Absolute
|
||||
// ${baseDir}/app
|
||||
metaPath *check.Absolute
|
||||
// ${baseDir}/files
|
||||
homeDir *check.Absolute
|
||||
// ${baseDir}/cache
|
||||
cacheDir *check.Absolute
|
||||
// ${baseDir}/cache/nix
|
||||
nixPath *check.Absolute
|
||||
}
|
||||
|
||||
func pathSetByApp(id string) *appPathSet {
|
||||
pathSet := new(appPathSet)
|
||||
pathSet.baseDir = dataHome.Append(id)
|
||||
pathSet.metaPath = pathSet.baseDir.Append("app")
|
||||
pathSet.homeDir = pathSet.baseDir.Append("files")
|
||||
pathSet.cacheDir = pathSet.baseDir.Append("cache")
|
||||
pathSet.nixPath = pathSet.cacheDir.Append("nix")
|
||||
return pathSet
|
||||
}
|
||||
|
||||
func appendGPUFilesystem(config *hst.Config) {
|
||||
config.Container.Filesystem = append(config.Container.Filesystem, []hst.FilesystemConfigJSON{
|
||||
// flatpak commit 763a686d874dd668f0236f911de00b80766ffe79
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("dri"), Device: true, Optional: true}},
|
||||
// mali
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("mali"), Device: true, Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("mali0"), Device: true, Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("umplock"), Device: true, Optional: true}},
|
||||
// nvidia
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidiactl"), Device: true, Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia-modeset"), Device: true, Optional: true}},
|
||||
// nvidia OpenCL/CUDA
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia-uvm"), Device: true, Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia-uvm-tools"), Device: true, Optional: true}},
|
||||
|
||||
// flatpak commit d2dff2875bb3b7e2cd92d8204088d743fd07f3ff
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia0"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia1"), Device: true, Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia2"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia3"), Device: true, Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia4"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia5"), Device: true, Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia6"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia7"), Device: true, Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia8"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia9"), Device: true, Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia10"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia11"), Device: true, Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia12"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia13"), Device: true, Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia14"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia15"), Device: true, Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia16"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia17"), Device: true, Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia18"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia19"), Device: true, Optional: true}},
|
||||
}...)
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/info"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
var hakureiPathVal = info.MustHakureiPath().String()
|
||||
|
||||
func mustRunApp(ctx context.Context, msg message.Msg, config *hst.Config, beforeFail func()) {
|
||||
var (
|
||||
cmd *exec.Cmd
|
||||
st io.WriteCloser
|
||||
)
|
||||
|
||||
if r, w, err := os.Pipe(); err != nil {
|
||||
beforeFail()
|
||||
log.Fatalf("cannot pipe: %v", err)
|
||||
} else {
|
||||
if msg.IsVerbose() {
|
||||
cmd = exec.CommandContext(ctx, hakureiPathVal, "-v", "app", "3")
|
||||
} else {
|
||||
cmd = exec.CommandContext(ctx, hakureiPathVal, "app", "3")
|
||||
}
|
||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||
cmd.ExtraFiles = []*os.File{r}
|
||||
st = w
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := json.NewEncoder(st).Encode(config); err != nil {
|
||||
beforeFail()
|
||||
log.Fatalf("cannot send configuration: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
beforeFail()
|
||||
log.Fatalf("cannot start hakurei: %v", err)
|
||||
}
|
||||
if err := cmd.Wait(); err != nil {
|
||||
var exitError *exec.ExitError
|
||||
if errors.As(err, &exitError) {
|
||||
beforeFail()
|
||||
msg.BeforeExit()
|
||||
os.Exit(exitError.ExitCode())
|
||||
} else {
|
||||
beforeFail()
|
||||
log.Fatalf("cannot wait: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
users.users = {
|
||||
alice = {
|
||||
isNormalUser = true;
|
||||
description = "Alice Foobar";
|
||||
password = "foobar";
|
||||
uid = 1000;
|
||||
};
|
||||
};
|
||||
|
||||
home-manager.users.alice.home.stateVersion = "24.11";
|
||||
|
||||
# Automatically login on tty1 as a normal user:
|
||||
services.getty.autologinUser = "alice";
|
||||
|
||||
environment = {
|
||||
variables = {
|
||||
SWAYSOCK = "/tmp/sway-ipc.sock";
|
||||
WLR_RENDERER = "pixman";
|
||||
};
|
||||
};
|
||||
|
||||
# Automatically configure and start Sway when logging in on tty1:
|
||||
programs.bash.loginShellInit = ''
|
||||
if [ "$(tty)" = "/dev/tty1" ]; then
|
||||
set -e
|
||||
|
||||
mkdir -p ~/.config/sway
|
||||
(sed s/Mod4/Mod1/ /etc/sway/config &&
|
||||
echo 'output * bg ${pkgs.nixos-artwork.wallpapers.simple-light-gray.gnomeFilePath} fill' &&
|
||||
echo 'output Virtual-1 res 1680x1050') > ~/.config/sway/config
|
||||
|
||||
sway --validate
|
||||
systemd-cat --identifier=session sway && touch /tmp/sway-exit-ok
|
||||
fi
|
||||
'';
|
||||
|
||||
programs.sway.enable = true;
|
||||
|
||||
virtualisation = {
|
||||
diskSize = 6 * 1024;
|
||||
|
||||
qemu.options = [
|
||||
# Need to switch to a different GPU driver than the default one (-vga std) so that Sway can launch:
|
||||
"-vga none -device virtio-gpu-pci"
|
||||
|
||||
# Increase zstd performance:
|
||||
"-smp 8"
|
||||
];
|
||||
};
|
||||
|
||||
environment.hakurei = {
|
||||
enable = true;
|
||||
stateDir = "/var/lib/hakurei";
|
||||
users.alice = 0;
|
||||
|
||||
extraHomeConfig = {
|
||||
home.stateVersion = "23.05";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
testers,
|
||||
callPackage,
|
||||
|
||||
system,
|
||||
self,
|
||||
}:
|
||||
let
|
||||
buildPackage = self.buildPackage.${system};
|
||||
in
|
||||
testers.nixosTest {
|
||||
name = "hpkg";
|
||||
nodes.machine = {
|
||||
environment.etc = {
|
||||
"foot.pkg".source = callPackage ./foot.nix { inherit buildPackage; };
|
||||
};
|
||||
|
||||
imports = [
|
||||
./configuration.nix
|
||||
|
||||
self.nixosModules.hakurei
|
||||
self.inputs.home-manager.nixosModules.home-manager
|
||||
];
|
||||
};
|
||||
|
||||
# adapted from nixos sway integration tests
|
||||
|
||||
# testScriptWithTypes:49: error: Cannot call function of unknown type
|
||||
# (machine.succeed if succeed else machine.execute)(
|
||||
# ^
|
||||
# Found 1 error in 1 file (checked 1 source file)
|
||||
skipTypeCheck = true;
|
||||
testScript = builtins.readFile ./test.py;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
lib,
|
||||
buildPackage,
|
||||
foot,
|
||||
wayland-utils,
|
||||
inconsolata,
|
||||
}:
|
||||
|
||||
buildPackage {
|
||||
name = "foot";
|
||||
inherit (foot) version;
|
||||
|
||||
identity = 2;
|
||||
id = "org.codeberg.dnkl.foot";
|
||||
|
||||
modules = [
|
||||
{
|
||||
home.packages = [
|
||||
foot
|
||||
|
||||
# For wayland-info:
|
||||
wayland-utils
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
nixosModules = [
|
||||
{
|
||||
# To help with OCR:
|
||||
environment.etc."xdg/foot/foot.ini".text = lib.generators.toINI { } {
|
||||
main = {
|
||||
font = "inconsolata:size=14";
|
||||
};
|
||||
colors = rec {
|
||||
foreground = "000000";
|
||||
background = "ffffff";
|
||||
regular2 = foreground;
|
||||
};
|
||||
};
|
||||
|
||||
fonts.packages = [ inconsolata ];
|
||||
}
|
||||
];
|
||||
|
||||
script = ''
|
||||
exec foot "$@"
|
||||
'';
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
import json
|
||||
import shlex
|
||||
|
||||
q = shlex.quote
|
||||
NODE_GROUPS = ["nodes", "floating_nodes"]
|
||||
|
||||
|
||||
def swaymsg(command: str = "", succeed=True, type="command"):
|
||||
assert command != "" or type != "command", "Must specify command or type"
|
||||
shell = q(f"swaymsg -t {q(type)} -- {q(command)}")
|
||||
with machine.nested(
|
||||
f"sending swaymsg {shell!r}" + " (allowed to fail)" * (not succeed)
|
||||
):
|
||||
ret = (machine.succeed if succeed else machine.execute)(
|
||||
f"su - alice -c {shell}"
|
||||
)
|
||||
|
||||
# execute also returns a status code, but disregard.
|
||||
if not succeed:
|
||||
_, ret = ret
|
||||
|
||||
if not succeed and not ret:
|
||||
return None
|
||||
|
||||
parsed = json.loads(ret)
|
||||
return parsed
|
||||
|
||||
|
||||
def walk(tree):
|
||||
yield tree
|
||||
for group in NODE_GROUPS:
|
||||
for node in tree.get(group, []):
|
||||
yield from walk(node)
|
||||
|
||||
|
||||
def wait_for_window(pattern):
|
||||
def func(last_chance):
|
||||
nodes = (node["name"] for node in walk(swaymsg(type="get_tree")))
|
||||
|
||||
if last_chance:
|
||||
nodes = list(nodes)
|
||||
machine.log(f"Last call! Current list of windows: {nodes}")
|
||||
|
||||
return any(pattern in name for name in nodes)
|
||||
|
||||
retry(func)
|
||||
|
||||
|
||||
def collect_state_ui(name):
|
||||
swaymsg(f"exec hakurei ps > '/tmp/{name}.ps'")
|
||||
machine.copy_from_vm(f"/tmp/{name}.ps", "")
|
||||
swaymsg(f"exec hakurei --json ps > '/tmp/{name}.json'")
|
||||
machine.copy_from_vm(f"/tmp/{name}.json", "")
|
||||
machine.screenshot(name)
|
||||
|
||||
|
||||
def check_state(name, enablements):
|
||||
instances = json.loads(machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei --json ps"))
|
||||
if len(instances) != 1:
|
||||
raise Exception(f"unexpected state length {len(instances)}")
|
||||
instance = instances[0]
|
||||
|
||||
if len(instance['container']['args']) != 1 or not (instance['container']['args'][0].startswith("/nix/store/")) or f"hakurei-{name}-" not in (instance['container']['args'][0]):
|
||||
raise Exception(f"unexpected args {instance['container']['args']}")
|
||||
|
||||
if instance['enablements'] != enablements:
|
||||
raise Exception(f"unexpected enablements {instance['enablements']}")
|
||||
|
||||
|
||||
start_all()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
|
||||
# To check hakurei's version:
|
||||
print(machine.succeed("sudo -u alice -i hakurei version"))
|
||||
|
||||
# Wait for Sway to complete startup:
|
||||
machine.wait_for_file("/run/user/1000/wayland-1")
|
||||
machine.wait_for_file("/tmp/sway-ipc.sock")
|
||||
|
||||
# Prepare hpkg directory:
|
||||
machine.succeed("install -dm 0700 -o alice -g users /var/lib/hakurei/1000")
|
||||
|
||||
# Install hpkg app:
|
||||
swaymsg("exec hpkg -v install /etc/foot.pkg && touch /tmp/hpkg-install-ok")
|
||||
machine.wait_for_file("/tmp/hpkg-install-ok")
|
||||
|
||||
# Start app (foot) with Wayland enablement:
|
||||
swaymsg("exec hpkg -v start org.codeberg.dnkl.foot")
|
||||
wait_for_window("hakurei@machine-foot")
|
||||
machine.send_chars("clear; wayland-info && touch /tmp/success-client\n")
|
||||
machine.wait_for_file("/tmp/hakurei.0/tmpdir/2/success-client")
|
||||
collect_state_ui("app_wayland")
|
||||
check_state("foot", {"wayland": True, "dbus": True, "pipewire": True})
|
||||
# Verify acl on XDG_RUNTIME_DIR:
|
||||
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10002"))
|
||||
machine.send_chars("exit\n")
|
||||
machine.wait_until_fails("pgrep foot")
|
||||
# Verify acl cleanup on XDG_RUNTIME_DIR:
|
||||
machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10002")
|
||||
|
||||
# Exit Sway and verify process exit status 0:
|
||||
swaymsg("exit", succeed=False)
|
||||
machine.wait_for_file("/tmp/sway-exit-ok")
|
||||
|
||||
# Print hakurei share and rundir contents:
|
||||
print(machine.succeed("find /tmp/hakurei.0 "
|
||||
+ "-path '/tmp/hakurei.0/runtime/*/*' -prune -o "
|
||||
+ "-path '/tmp/hakurei.0/tmpdir/*/*' -prune -o "
|
||||
+ "-print"))
|
||||
print(machine.fail("ls /run/user/1000/hakurei"))
|
||||
130
cmd/hpkg/with.go
130
cmd/hpkg/with.go
@@ -1,130 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
func withNixDaemon(
|
||||
ctx context.Context,
|
||||
msg message.Msg,
|
||||
action string, command []string, net bool, updateConfig func(config *hst.Config) *hst.Config,
|
||||
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
|
||||
) {
|
||||
flags := hst.FMultiarch | hst.FUserns // nix sandbox requires userns
|
||||
if net {
|
||||
flags |= hst.FHostNet
|
||||
}
|
||||
if dropShell {
|
||||
flags |= hst.FTty
|
||||
}
|
||||
|
||||
mustRunAppDropShell(ctx, msg, updateConfig(&hst.Config{
|
||||
ID: app.ID,
|
||||
|
||||
ExtraPerms: []hst.ExtraPermConfig{
|
||||
{Path: dataHome, Execute: true},
|
||||
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
||||
},
|
||||
|
||||
Identity: app.Identity,
|
||||
|
||||
Container: &hst.ContainerConfig{
|
||||
Hostname: formatHostname(app.Name) + "-" + action,
|
||||
|
||||
Filesystem: []hst.FilesystemConfigJSON{
|
||||
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: pathSet.cacheDir.Append("etc"), Special: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath, Target: pathNix, Write: true}},
|
||||
{FilesystemConfig: &hst.FSLink{Target: pathCurrentSystem, Linkname: app.CurrentSystem.String()}},
|
||||
{FilesystemConfig: &hst.FSLink{Target: pathBin, Linkname: pathSwBin.String()}},
|
||||
{FilesystemConfig: &hst.FSLink{Target: fhs.AbsUsrBin, Linkname: pathSwBin.String()}},
|
||||
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID), Source: pathSet.homeDir, Write: true, Ensure: true}},
|
||||
},
|
||||
|
||||
Username: "hakurei",
|
||||
Shell: pathShell,
|
||||
Home: pathDataData.Append(app.ID),
|
||||
|
||||
Path: pathShell,
|
||||
Args: []string{bash, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " +
|
||||
// start nix-daemon
|
||||
"nix-daemon --store / & " +
|
||||
// wait for socket to appear
|
||||
"(while [ ! -S /nix/var/nix/daemon-socket/socket ]; do sleep 0.01; done) && " +
|
||||
// create directory so nix stops complaining
|
||||
"mkdir -p /nix/var/nix/profiles/per-user/root/channels && " +
|
||||
strings.Join(command, " && ") +
|
||||
// terminate nix-daemon
|
||||
" && pkill nix-daemon",
|
||||
},
|
||||
|
||||
Flags: flags,
|
||||
},
|
||||
}), dropShell, beforeFail)
|
||||
}
|
||||
|
||||
func withCacheDir(
|
||||
ctx context.Context,
|
||||
msg message.Msg,
|
||||
action string, command []string, workDir *check.Absolute,
|
||||
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
|
||||
) {
|
||||
flags := hst.FMultiarch
|
||||
if dropShell {
|
||||
flags |= hst.FTty
|
||||
}
|
||||
|
||||
mustRunAppDropShell(ctx, msg, &hst.Config{
|
||||
ID: app.ID,
|
||||
|
||||
ExtraPerms: []hst.ExtraPermConfig{
|
||||
{Path: dataHome, Execute: true},
|
||||
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
||||
{Path: workDir, Execute: true},
|
||||
},
|
||||
|
||||
Identity: app.Identity,
|
||||
|
||||
Container: &hst.ContainerConfig{
|
||||
Hostname: formatHostname(app.Name) + "-" + action,
|
||||
|
||||
Filesystem: []hst.FilesystemConfigJSON{
|
||||
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: workDir.Append(fhs.Etc), Special: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: workDir.Append("nix"), Target: pathNix}},
|
||||
{FilesystemConfig: &hst.FSLink{Target: pathCurrentSystem, Linkname: app.CurrentSystem.String()}},
|
||||
{FilesystemConfig: &hst.FSLink{Target: pathBin, Linkname: pathSwBin.String()}},
|
||||
{FilesystemConfig: &hst.FSLink{Target: fhs.AbsUsrBin, Linkname: pathSwBin.String()}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: workDir, Target: hst.AbsPrivateTmp.Append("bundle")}},
|
||||
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID, "cache"), Source: pathSet.cacheDir, Write: true, Ensure: true}},
|
||||
},
|
||||
|
||||
Username: "nixos",
|
||||
Shell: pathShell,
|
||||
Home: pathDataData.Append(app.ID, "cache"),
|
||||
|
||||
Path: pathShell,
|
||||
Args: []string{bash, "-lc", strings.Join(command, " && ")},
|
||||
|
||||
Flags: flags,
|
||||
},
|
||||
}, dropShell, beforeFail)
|
||||
}
|
||||
|
||||
func mustRunAppDropShell(ctx context.Context, msg message.Msg, config *hst.Config, dropShell bool, beforeFail func()) {
|
||||
if dropShell {
|
||||
if config.Container != nil {
|
||||
config.Container.Args = []string{bash, "-l"}
|
||||
}
|
||||
mustRunApp(ctx, msg, config, beforeFail)
|
||||
beforeFail()
|
||||
msg.BeforeExit()
|
||||
os.Exit(0)
|
||||
}
|
||||
mustRunApp(ctx, msg, config, beforeFail)
|
||||
}
|
||||
4
dist/release.sh
vendored
4
dist/release.sh
vendored
@@ -13,7 +13,7 @@ echo
|
||||
echo '# Building hakurei.'
|
||||
go generate ./...
|
||||
go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w
|
||||
-buildid= -extldflags '-static'
|
||||
-buildid= -linkmode external -extldflags=-static
|
||||
-X hakurei.app/internal/info.buildVersion=${VERSION}
|
||||
-X hakurei.app/internal/info.hakureiPath=/usr/bin/hakurei
|
||||
-X hakurei.app/internal/info.hsuPath=/usr/bin/hsu
|
||||
@@ -21,7 +21,7 @@ go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w
|
||||
echo
|
||||
|
||||
echo '# Testing hakurei.'
|
||||
go test -ldflags='-buildid= -extldflags=-static' ./...
|
||||
go test -ldflags='-buildid= -linkmode external -extldflags=-static' ./...
|
||||
echo
|
||||
|
||||
echo '# Creating distribution.'
|
||||
|
||||
23
flake.nix
23
flake.nix
@@ -29,20 +29,6 @@
|
||||
{
|
||||
nixosModules.hakurei = import ./nixos.nix self.packages;
|
||||
|
||||
buildPackage = forAllSystems (
|
||||
system:
|
||||
nixpkgsFor.${system}.callPackage (
|
||||
import ./cmd/hpkg/build.nix {
|
||||
inherit
|
||||
nixpkgsFor
|
||||
system
|
||||
nixpkgs
|
||||
home-manager
|
||||
;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
checks = forAllSystems (
|
||||
system:
|
||||
let
|
||||
@@ -71,8 +57,6 @@
|
||||
|
||||
sharefs = callPackage ./cmd/sharefs/test { inherit system self; };
|
||||
|
||||
hpkg = callPackage ./cmd/hpkg/test { inherit system self; };
|
||||
|
||||
formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; } ''
|
||||
cd ${./.}
|
||||
|
||||
@@ -127,11 +111,6 @@
|
||||
glibc
|
||||
xdg-dbus-proxy
|
||||
|
||||
# hpkg
|
||||
zstd
|
||||
gnutar
|
||||
coreutils
|
||||
|
||||
# for check
|
||||
util-linux
|
||||
nettools
|
||||
@@ -219,7 +198,7 @@
|
||||
./test/interactive/trace.nix
|
||||
|
||||
self.nixosModules.hakurei
|
||||
self.inputs.home-manager.nixosModules.home-manager
|
||||
home-manager.nixosModules.home-manager
|
||||
];
|
||||
};
|
||||
in
|
||||
|
||||
@@ -75,7 +75,7 @@ go build -trimpath -v -o /work/system/libexec/hakurei -ldflags="-s -w
|
||||
echo
|
||||
|
||||
echo '# Testing hakurei.'
|
||||
go test -ldflags='-buildid= -extldflags=-static' ./...
|
||||
go test -ldflags='-buildid= -linkmode external -extldflags=-static' ./...
|
||||
echo
|
||||
|
||||
mkdir -p /work/system/bin/
|
||||
|
||||
17
package.nix
17
package.nix
@@ -16,11 +16,6 @@
|
||||
# for sharefs
|
||||
fuse3,
|
||||
|
||||
# for hpkg
|
||||
zstd,
|
||||
gnutar,
|
||||
coreutils,
|
||||
|
||||
# for passthru.buildInputs
|
||||
go,
|
||||
clang,
|
||||
@@ -128,18 +123,6 @@ buildGoModule rec {
|
||||
|
||||
makeBinaryWrapper "$out/libexec/hakurei" "$out/bin/hakurei" \
|
||||
--inherit-argv0 --prefix PATH : ${lib.makeBinPath appPackages}
|
||||
|
||||
makeBinaryWrapper "$out/libexec/hpkg" "$out/bin/hpkg" \
|
||||
--inherit-argv0 --prefix PATH : ${
|
||||
lib.makeBinPath (
|
||||
appPackages
|
||||
++ [
|
||||
zstd
|
||||
gnutar
|
||||
coreutils
|
||||
]
|
||||
)
|
||||
}
|
||||
'';
|
||||
|
||||
passthru.targetPkgs = [
|
||||
|
||||
Reference in New Issue
Block a user