All checks were successful
		
		
	
	Test / Create distribution (push) Successful in 34s
				
			Test / Sandbox (push) Successful in 2m13s
				
			Test / Hakurei (push) Successful in 3m15s
				
			Test / Hpkg (push) Successful in 4m7s
				
			Test / Sandbox (race detector) (push) Successful in 4m14s
				
			Test / Hakurei (race detector) (push) Successful in 5m7s
				
			Test / Flake checks (push) Successful in 1m37s
				
			Should have done this when relocating this from container. Now is a good time to rename it before v0.3.x. Signed-off-by: Ophestra <cat@gensokyo.uk>
		
			
				
	
	
		
			336 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			336 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
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")
 | 
						|
}
 |