Replace the Python bootstrap script with a Go implementation that cross-compiles to native binaries for Linux and macOS on x86_64 and aarch64. The Go port preserves all sections of the original (system packages, optional Flatpak GUI apps, custom downloads, and the macOS firecracker VM bridge) and adds first-class pacman support so the tool works on Arch-family distros alongside Debian, RHEL, and macOS. A Makefile produces a host binary via `make build` and the full four-target matrix under dist/ via `make build-all`.
175 lines
4.2 KiB
Go
175 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
)
|
|
|
|
// OS / architecture detection.
|
|
//
|
|
// Vendors disagree on canonical OS/arch tokens used in download URLs, so we
|
|
// keep our own normalized values ("linux"/"macos", "x86_64"/"aarch64") and
|
|
// translate at URL-construction time.
|
|
|
|
var (
|
|
osName string // "linux" or "macos"
|
|
archName string // "x86_64" or "aarch64"
|
|
isMacOS bool
|
|
isRHELFamily bool
|
|
isArchFamily bool
|
|
)
|
|
|
|
func init() {
|
|
osName = detectOS()
|
|
archName = detectArch()
|
|
isMacOS = osName == "macos"
|
|
}
|
|
|
|
func detectOS() string {
|
|
switch runtime.GOOS {
|
|
case "linux":
|
|
return "linux"
|
|
case "darwin":
|
|
return "macos"
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "Unsupported OS: %s (supports Linux, Darwin)\n", runtime.GOOS)
|
|
os.Exit(1)
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func detectArch() string {
|
|
switch runtime.GOARCH {
|
|
case "amd64":
|
|
return "x86_64"
|
|
case "arm64":
|
|
return "aarch64"
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "Unsupported architecture: %s (supports x86_64, aarch64)\n", runtime.GOARCH)
|
|
os.Exit(1)
|
|
return ""
|
|
}
|
|
}
|
|
|
|
var (
|
|
archGo = map[string]string{"x86_64": "amd64", "aarch64": "arm64"}
|
|
archMinikube = map[string]string{"x86_64": "amd64", "aarch64": "arm64"}
|
|
archDeb = map[string]string{"x86_64": "amd64", "aarch64": "arm64"}
|
|
archNvim = map[string]string{"x86_64": "x86_64", "aarch64": "arm64"}
|
|
archPulumi = map[string]string{"x86_64": "x64", "aarch64": "arm64"}
|
|
|
|
osGo = map[string]string{"linux": "linux", "macos": "darwin"}
|
|
osZig = map[string]string{"linux": "linux", "macos": "macos"}
|
|
osNvim = map[string]string{"linux": "linux", "macos": "macos"}
|
|
|
|
archTokens = map[string][]string{
|
|
"x86_64": {"x86_64", "amd64", "x64"},
|
|
"aarch64": {"aarch64", "arm64"},
|
|
}
|
|
)
|
|
|
|
// formatURL interpolates {version}, {arch}, {arch_go}, {os}, {os_go},
|
|
// {os_zig}, {os_nvim} into a download URL template.
|
|
func formatURL(template, version string) string {
|
|
r := strings.NewReplacer(
|
|
"{version}", version,
|
|
"{arch}", archName,
|
|
"{arch_go}", archGo[archName],
|
|
"{os}", osName,
|
|
"{os_go}", osGo[osName],
|
|
"{os_zig}", osZig[osName],
|
|
"{os_nvim}", osNvim[osName],
|
|
)
|
|
return r.Replace(template)
|
|
}
|
|
|
|
func otherArch() string {
|
|
if archName == "x86_64" {
|
|
return "aarch64"
|
|
}
|
|
return "x86_64"
|
|
}
|
|
|
|
func archMatches(name, arch string) bool {
|
|
n := strings.ToLower(name)
|
|
for _, tok := range archTokens[arch] {
|
|
if strings.Contains(n, tok) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func hasOtherArchToken(name string) bool {
|
|
n := strings.ToLower(name)
|
|
for _, tok := range archTokens[otherArch()] {
|
|
if strings.Contains(n, tok) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// osReleaseField returns the value of /etc/os-release's NAME=value pair,
|
|
// stripped of surrounding quotes. Returns "" if the file is missing or the
|
|
// field is absent.
|
|
func osReleaseField(field string) string {
|
|
data, err := os.ReadFile("/etc/os-release")
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
prefix := field + "="
|
|
for _, line := range strings.Split(string(data), "\n") {
|
|
if strings.HasPrefix(line, prefix) {
|
|
return strings.Trim(strings.TrimPrefix(line, prefix), `"`)
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func detectRHELFamily() bool {
|
|
data, err := os.ReadFile("/etc/os-release")
|
|
if err != nil {
|
|
return pkgMgr == "dnf"
|
|
}
|
|
tokens := []string{}
|
|
for _, line := range strings.Split(string(data), "\n") {
|
|
if strings.HasPrefix(line, "ID=") || strings.HasPrefix(line, "ID_LIKE=") {
|
|
_, val, _ := strings.Cut(line, "=")
|
|
val = strings.Trim(val, `"`)
|
|
tokens = append(tokens, strings.Fields(val)...)
|
|
}
|
|
}
|
|
rhel := map[string]bool{"rhel": true, "fedora": true, "centos": true, "rocky": true, "almalinux": true}
|
|
for _, t := range tokens {
|
|
if rhel[t] {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func detectArchFamily() bool {
|
|
data, err := os.ReadFile("/etc/os-release")
|
|
if err != nil {
|
|
return pkgMgr == "pacman"
|
|
}
|
|
tokens := []string{}
|
|
for _, line := range strings.Split(string(data), "\n") {
|
|
if strings.HasPrefix(line, "ID=") || strings.HasPrefix(line, "ID_LIKE=") {
|
|
_, val, _ := strings.Cut(line, "=")
|
|
val = strings.Trim(val, `"`)
|
|
tokens = append(tokens, strings.Fields(val)...)
|
|
}
|
|
}
|
|
arch := map[string]bool{"arch": true, "manjaro": true, "endeavouros": true, "artix": true}
|
|
for _, t := range tokens {
|
|
if arch[t] {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|