From a69273ab2a7040ba906876e6d7888b4edeb74ebc Mon Sep 17 00:00:00 2001 From: Ophestra Date: Sun, 5 Apr 2026 20:57:01 +0900 Subject: cmd/dist: replace dist/release.sh This is much more robust than a shell script. Signed-off-by: Ophestra --- .gitignore | 31 +------ all.sh | 6 ++ cmd/dist/comp/_hakurei | 85 ++++++++++++++++++ cmd/dist/main.go | 232 +++++++++++++++++++++++++++++++++++++++++++++++++ dist/comp/_hakurei | 85 ------------------ dist/hsurc.default | 1 - dist/install.sh | 12 --- dist/release.sh | 36 -------- flake.nix | 2 +- package.nix | 2 +- 10 files changed, 329 insertions(+), 163 deletions(-) create mode 100755 all.sh create mode 100644 cmd/dist/comp/_hakurei create mode 100644 cmd/dist/main.go delete mode 100644 dist/comp/_hakurei delete mode 100644 dist/hsurc.default delete mode 100755 dist/install.sh delete mode 100755 dist/release.sh diff --git a/.gitignore b/.gitignore index ed10d54f..fe160921 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,7 @@ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib -*.pkg -/hakurei - -# Test binary, built with `go test -c` +# produced by tools and text editors +*.qcow2 *.test - -# Output of the go coverage tool, specifically when used with LiteIDE *.out - -# Dependency directories (remove the comment below to include it) -# vendor/ - -# Go workspace file -go.work -go.work.sum - -# env file -.env .idea .vscode @@ -30,8 +10,5 @@ go.work.sum /internal/pkg/testdata/testtool /internal/rosa/hakurei_current.tar.gz -# release -/dist/hakurei-* - -# interactive nixos vm -nixos.qcow2 \ No newline at end of file +# cmd/dist default destination +/dist diff --git a/all.sh b/all.sh new file mode 100755 index 00000000..17b6b862 --- /dev/null +++ b/all.sh @@ -0,0 +1,6 @@ +#!/bin/sh -e + +TOOLCHAIN_VERSION="$(go version)" +cd "$(dirname -- "$0")/" +echo "# Building cmd/dist using ${TOOLCHAIN_VERSION}." +go run -v --tags=dist ./cmd/dist diff --git a/cmd/dist/comp/_hakurei b/cmd/dist/comp/_hakurei new file mode 100644 index 00000000..11424612 --- /dev/null +++ b/cmd/dist/comp/_hakurei @@ -0,0 +1,85 @@ +#compdef hakurei + +_hakurei_run() { + __hakurei_files + return $? +} + +_hakurei_exec() { + _arguments \ + '--id[Reverse-DNS style Application identifier, leave empty to inherit instance identifier]:id' \ + '-a[Application identity]: :_numbers' \ + '-g[Groups inherited by all container processes]: :_groups' \ + '-d[Container home directory]: :_files -/' \ + '-u[Passwd user name within sandbox]: :_users' \ + '--private-runtime[Do not share XDG_RUNTIME_DIR between containers under the same identity]' \ + '--private-tmpdir[Do not share TMPDIR between containers under the same identity]' \ + '--wayland[Enable connection to Wayland via security-context-v1]' \ + '-X[Enable direct connection to X11]' \ + '--dbus[Enable proxied connection to D-Bus]' \ + '--pipewire[Enable connection to PipeWire via SecurityContext]' \ + '--pulse[Enable PulseAudio compatibility daemon]' \ + '--dbus-config[Path to session bus proxy config file]: :_files -g "*.json"' \ + '--dbus-system[Path to system bus proxy config file]: :_files -g "*.json"' \ + '--mpris[Allow owning MPRIS D-Bus path]' \ + '--dbus-log[Force buffered logging in the D-Bus proxy]' +} + +_hakurei_ps() { + _arguments \ + '--short[List instances only]' +} + +_hakurei_show() { + _alternative \ + 'instances:domains:__hakurei_instances' \ + 'files:files:__hakurei_files' +} + +__hakurei_files() { + _files -g "*.(json|hakurei)" + return $? +} + +__hakurei_instances() { + local -a out + shift -p + out=( ${(f)"$(_call_program commands hakurei ps --short 2>&1)"} ) + if (( $#out == 0 )); then + _message "No active instances" + else + _describe "active instances" out + fi + return $? +} + +(( $+functions[_hakurei_commands] )) || _hakurei_commands() +{ + local -a _hakurei_cmds + _hakurei_cmds=( + "run:Load and start container from configuration file" + "exec:Configure and start a permissive container" + "show:Show live or local instance configuration" + "ps:List active instances" + "version:Display version information" + "license:Show full license text" + "template:Produce a config template" + "help:Show help message" + ) + if (( CURRENT == 1 )); then + _describe -t commands 'action' _hakurei_cmds || compadd "$@" + else + local curcontext="$curcontext" + cmd="${${_hakurei_cmds[(r)$words[1]:*]%%:*}}" + if (( $+functions[_hakurei_$cmd] )); then + _hakurei_$cmd + else + _message "no more options" + fi + fi +} + +_arguments -C \ + '-v[Increase log verbosity]' \ + '--json[Serialise output in JSON when applicable]' \ + '*::hakurei command:_hakurei_commands' \ No newline at end of file diff --git a/cmd/dist/main.go b/cmd/dist/main.go new file mode 100644 index 00000000..d3555e86 --- /dev/null +++ b/cmd/dist/main.go @@ -0,0 +1,232 @@ +//go:build dist + +package main + +import ( + "archive/tar" + "compress/gzip" + "context" + "crypto/sha512" + _ "embed" + "encoding/hex" + "fmt" + "io" + "io/fs" + "log" + "os" + "os/exec" + "os/signal" + "path/filepath" + "runtime" +) + +// getenv looks up an environment variable, and returns fallback if it is unset. +func getenv(key, fallback string) string { + if v, ok := os.LookupEnv(key); ok { + return v + } + return fallback +} + +// mustRun runs a command with the current process's environment and panics +// on error or non-zero exit code. +func mustRun(ctx context.Context, name string, arg ...string) { + cmd := exec.CommandContext(ctx, name, arg...) + cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + panic(err) + } +} + +//go:embed comp/_hakurei +var comp []byte + +func main() { + fmt.Println() + log.SetFlags(0) + log.SetPrefix("# ") + + version := getenv("HAKUREI_VERSION", "untagged") + prefix := getenv("PREFIX", "/usr") + destdir := getenv("DESTDIR", "dist") + + if err := os.MkdirAll(destdir, 0755); err != nil { + log.Fatal(err) + } + s, err := os.MkdirTemp(destdir, ".dist.*") + if err != nil { + log.Fatal(err) + } + defer func() { + var code int + + if err = os.RemoveAll(s); err != nil { + code = 1 + log.Println(err) + } + + if r := recover(); r != nil { + code = 1 + log.Println(r) + } + + os.Exit(code) + }() + + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) + defer cancel() + + log.Println("Building hakurei.") + mustRun(ctx, "go", "generate", "./...") + mustRun( + ctx, "go", "build", + "-trimpath", + "-v", "-o", s, + "-ldflags=-s -w "+ + "-buildid= -linkmode external -extldflags=-static "+ + "-X hakurei.app/internal/info.buildVersion="+version+" "+ + "-X hakurei.app/internal/info.hakureiPath="+prefix+"/bin/hakurei "+ + "-X hakurei.app/internal/info.hsuPath="+prefix+"/bin/hsu "+ + "-X main.hakureiPath="+prefix+"/bin/hakurei", + "./...", + ) + fmt.Println() + + log.Println("Testing Hakurei.") + mustRun( + ctx, "go", "test", + "-ldflags=-buildid= -linkmode external -extldflags=-static", + "./...", + ) + fmt.Println() + + log.Println("Creating distribution.") + const suffix = ".tar.gz" + distName := "hakurei-" + version + "-" + runtime.GOARCH + var f *os.File + if f, err = os.OpenFile( + filepath.Join(s, distName+suffix), + os.O_CREATE|os.O_EXCL|os.O_WRONLY, + 0644, + ); err != nil { + panic(err) + } + defer func() { + if f == nil { + return + } + if err = f.Close(); err != nil { + log.Println(err) + } + }() + + h := sha512.New() + gw := gzip.NewWriter(io.MultiWriter(f, h)) + tw := tar.NewWriter(gw) + + mustWriteHeader := func(name string, size int64, mode os.FileMode) { + header := tar.Header{ + Name: filepath.Join(distName, name), + Size: size, + Mode: int64(mode), + Uname: "root", + Gname: "root", + } + + if mode&os.ModeDir != 0 { + header.Typeflag = tar.TypeDir + fmt.Printf("%s %s\n", mode, name) + } else { + header.Typeflag = tar.TypeReg + fmt.Printf("%s %s (%d bytes)\n", mode, name, size) + } + + if err = tw.WriteHeader(&header); err != nil { + panic(err) + } + } + + mustWriteFile := func(name string, data []byte, mode os.FileMode) { + mustWriteHeader(name, int64(len(data)), mode) + if mode&os.ModeDir != 0 { + return + } + if _, err = tw.Write(data); err != nil { + panic(err) + } + } + + mustWriteFromPath := func(dst, src string) { + var r *os.File + if r, err = os.Open(src); err != nil { + panic(err) + } + + var fi os.FileInfo + if fi, err = r.Stat(); err != nil { + _ = r.Close() + panic(err) + } + + mustWriteHeader(dst, fi.Size(), fi.Mode()) + if _, err = io.Copy(tw, r); err != nil { + _ = r.Close() + panic(err) + } else if err = r.Close(); err != nil { + panic(err) + } + } + + mustWriteFile(".", nil, fs.ModeDir|0755) + mustWriteFile("comp/", nil, os.ModeDir|0755) + mustWriteFile("comp/_hakurei", comp, 0644) + mustWriteFile("install.sh", []byte(`#!/bin/sh -e +cd "$(dirname -- "$0")" || exit 1 + +install -vDm0755 "bin/hakurei" "${DESTDIR}`+prefix+`/bin/hakurei" +install -vDm0755 "bin/sharefs" "${DESTDIR}`+prefix+`/bin/sharefs" + +install -vDm4511 "bin/hsu" "${DESTDIR}`+prefix+`/bin/hsu" +if [ ! -f "${DESTDIR}/etc/hsurc" ]; then + install -vDm0400 "hsurc.default" "${DESTDIR}/etc/hsurc" +fi + +install -vDm0644 "comp/_hakurei" "${DESTDIR}`+prefix+`/share/zsh/site-functions/_hakurei" +`), 0755) + + mustWriteFromPath("README.md", "README.md") + mustWriteFile("hsurc.default", []byte("1000 0"), 0400) + for _, name := range []string{ + "hsu", + "hakurei", + "sharefs", + } { + mustWriteFromPath( + filepath.Join("bin", name), + filepath.Join(s, name), + ) + } + + if err = tw.Close(); err != nil { + panic(err) + } else if err = gw.Close(); err != nil { + panic(err) + } else if err = f.Close(); err != nil { + panic(err) + } + f = nil + + if err = os.WriteFile( + filepath.Join(destdir, distName+suffix+".sha512"), + append(hex.AppendEncode(nil, h.Sum(nil)), " "+distName+suffix+"\n"...), + 0644, + ); err != nil { + panic(err) + } + if err = os.Rename( + filepath.Join(s, distName+suffix), + filepath.Join(destdir, distName+suffix), + ); err != nil { + panic(err) + } +} diff --git a/dist/comp/_hakurei b/dist/comp/_hakurei deleted file mode 100644 index 11424612..00000000 --- a/dist/comp/_hakurei +++ /dev/null @@ -1,85 +0,0 @@ -#compdef hakurei - -_hakurei_run() { - __hakurei_files - return $? -} - -_hakurei_exec() { - _arguments \ - '--id[Reverse-DNS style Application identifier, leave empty to inherit instance identifier]:id' \ - '-a[Application identity]: :_numbers' \ - '-g[Groups inherited by all container processes]: :_groups' \ - '-d[Container home directory]: :_files -/' \ - '-u[Passwd user name within sandbox]: :_users' \ - '--private-runtime[Do not share XDG_RUNTIME_DIR between containers under the same identity]' \ - '--private-tmpdir[Do not share TMPDIR between containers under the same identity]' \ - '--wayland[Enable connection to Wayland via security-context-v1]' \ - '-X[Enable direct connection to X11]' \ - '--dbus[Enable proxied connection to D-Bus]' \ - '--pipewire[Enable connection to PipeWire via SecurityContext]' \ - '--pulse[Enable PulseAudio compatibility daemon]' \ - '--dbus-config[Path to session bus proxy config file]: :_files -g "*.json"' \ - '--dbus-system[Path to system bus proxy config file]: :_files -g "*.json"' \ - '--mpris[Allow owning MPRIS D-Bus path]' \ - '--dbus-log[Force buffered logging in the D-Bus proxy]' -} - -_hakurei_ps() { - _arguments \ - '--short[List instances only]' -} - -_hakurei_show() { - _alternative \ - 'instances:domains:__hakurei_instances' \ - 'files:files:__hakurei_files' -} - -__hakurei_files() { - _files -g "*.(json|hakurei)" - return $? -} - -__hakurei_instances() { - local -a out - shift -p - out=( ${(f)"$(_call_program commands hakurei ps --short 2>&1)"} ) - if (( $#out == 0 )); then - _message "No active instances" - else - _describe "active instances" out - fi - return $? -} - -(( $+functions[_hakurei_commands] )) || _hakurei_commands() -{ - local -a _hakurei_cmds - _hakurei_cmds=( - "run:Load and start container from configuration file" - "exec:Configure and start a permissive container" - "show:Show live or local instance configuration" - "ps:List active instances" - "version:Display version information" - "license:Show full license text" - "template:Produce a config template" - "help:Show help message" - ) - if (( CURRENT == 1 )); then - _describe -t commands 'action' _hakurei_cmds || compadd "$@" - else - local curcontext="$curcontext" - cmd="${${_hakurei_cmds[(r)$words[1]:*]%%:*}}" - if (( $+functions[_hakurei_$cmd] )); then - _hakurei_$cmd - else - _message "no more options" - fi - fi -} - -_arguments -C \ - '-v[Increase log verbosity]' \ - '--json[Serialise output in JSON when applicable]' \ - '*::hakurei command:_hakurei_commands' \ No newline at end of file diff --git a/dist/hsurc.default b/dist/hsurc.default deleted file mode 100644 index f770d5ef..00000000 --- a/dist/hsurc.default +++ /dev/null @@ -1 +0,0 @@ -1000 0 \ No newline at end of file diff --git a/dist/install.sh b/dist/install.sh deleted file mode 100755 index a4352288..00000000 --- a/dist/install.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh -cd "$(dirname -- "$0")" || exit 1 - -install -vDm0755 "bin/hakurei" "${DESTDIR}/usr/bin/hakurei" -install -vDm0755 "bin/sharefs" "${DESTDIR}/usr/bin/sharefs" - -install -vDm4511 "bin/hsu" "${DESTDIR}/usr/bin/hsu" -if [ ! -f "${DESTDIR}/etc/hsurc" ]; then - install -vDm0400 "hsurc.default" "${DESTDIR}/etc/hsurc" -fi - -install -vDm0644 "comp/_hakurei" "${DESTDIR}/usr/share/zsh/site-functions/_hakurei" \ No newline at end of file diff --git a/dist/release.sh b/dist/release.sh deleted file mode 100755 index 1cfcc721..00000000 --- a/dist/release.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh -e -cd "$(dirname -- "$0")/.." -VERSION="${HAKUREI_VERSION:-untagged}" -prefix="${PREFIX:-/usr}" -pname="hakurei-${VERSION}-$(go env GOARCH)" -out="${DESTDIR:-dist}/${pname}" - -destroy_workdir() { - rm -rf "${out}" -} -trap destroy_workdir EXIT - -echo '# Preparing distribution files.' -mkdir -p "${out}" -cp -v "README.md" "dist/hsurc.default" "dist/install.sh" "${out}" -cp -rv "dist/comp" "${out}" -echo - -echo '# Building hakurei.' -go generate ./... -go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w - -buildid= -linkmode external -extldflags=-static - -X hakurei.app/internal/info.buildVersion=${VERSION} - -X hakurei.app/internal/info.hakureiPath=${prefix}/bin/hakurei - -X hakurei.app/internal/info.hsuPath=${prefix}/bin/hsu - -X main.hakureiPath=${prefix}/bin/hakurei" ./... -echo - -echo '# Testing hakurei.' -go test -ldflags='-buildid= -linkmode external -extldflags=-static' ./... -echo - -echo '# Creating distribution.' -rm -f "${out}.tar.gz" && tar -C "${out}/.." -vczf "${out}.tar.gz" "${pname}" -(cd "${out}/.." && sha512sum "${pname}.tar.gz" > "${pname}.tar.gz.sha512") -echo diff --git a/flake.nix b/flake.nix index 48cf3421..3cca5975 100644 --- a/flake.nix +++ b/flake.nix @@ -141,7 +141,7 @@ PATH="${pkgs.pkgsStatic.musl.bin}/bin:$PATH" \ DESTDIR="$out" \ HAKUREI_VERSION="v${hakurei.version}" \ - ./dist/release.sh + ./all.sh ''; } ); diff --git a/package.nix b/package.nix index 32a2a884..bde2f0c9 100644 --- a/package.nix +++ b/package.nix @@ -116,7 +116,7 @@ buildGo126Module rec { ]; in '' - install -D --target-directory=$out/share/zsh/site-functions dist/comp/* + install -D --target-directory=$out/share/zsh/site-functions cmd/dist/comp/* mkdir "$out/libexec" mv "$out"/bin/* "$out/libexec/" -- cgit v1.3.1