// ======================================================================================= go toolbox (mwx'2026) package main import ( "crypto/rand" "database/sql" "encoding/base64" "encoding/hex" "flag" "fmt" "io" "math/big" "net" "net/http" "os" "path/filepath" "regexp" "runtime" "strconv" "strings" "time" "github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2/core" "github.com/AlecAivazis/survey/v2/terminal" "github.com/Masterminds/semver/v3" "github.com/eknkc/basex" "github.com/fatih/color" "github.com/mgutz/ansi" "github.com/minio/selfupdate" "github.com/spf13/viper" "github.com/tidwall/gjson" ) var tbversion = "0.7.0" var HTTPTIMEOUT = 3 * time.Second // version.txt / checksums.txt - must never stall a build var DOWNLOADTIMEOUT = 10 * time.Minute // the binary itself, http.Client.Timeout covers the whole body var CHRS = "VW9IdGJ6eXh1T25DRHdrc2M5MlhOQVNQcEJFWnJhWVY2ZEowaFJLdmoxNUdxVDRJZkZpTTdRZW0zTFc4Z2w=" var LR = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") func checkforupdate(URL string) { // ----------------------------------------------------- check for new version prg := prgname() client := &http.Client{Timeout: HTTPTIMEOUT} body, err := fetch(client, URL+"/version.txt", 1<<20) // update server unreachable: never block the build if err != nil { return } lversion := strings.TrimSpace(string(body)) // exactly as published - it names the artifact sv_version, err := semver.NewVersion(version) if err != nil { return } sv_lversion, err := semver.NewVersion(lversion) if err != nil { return } if !sv_lversion.GreaterThan(sv_version) { return } if !Yesno(SF("new '%s' version found (%s -> %s), update now?",prg,sv_version,lversion),true,false) { return } target := SF("%s_%s_%s_%s",prg,lversion,runtime.GOOS,runtime.GOARCH) if runtime.GOOS == "windows" { target += ".exe" } sum, err := getchecksum(client, URL+"/checksums.txt", target) // no checksum -> no update if err != nil { PE("update aborted", err.Error()) os.Exit(1) } if err := doupdate(&http.Client{Timeout: DOWNLOADTIMEOUT}, URL+"/"+target, sum); err != nil { PE("update failed", err.Error()) os.Exit(1) } PO("update successful","please run your last command again") os.Exit(0) } func doupdate(client *http.Client, url string, checksum []byte) error { // --------------------------- do update resp, err := client.Get(url) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return SE("server returned status: %v", resp.Status) } return selfupdate.Apply(resp.Body, selfupdate.Options{Checksum: checksum}) } func getchecksum(client *http.Client, url string, name string) ([]byte, error) { // -- sha256 of a published file body, err := fetch(client, url, 1<<20) if err != nil { return nil, SE("no checksums published (%v)", err) } for _, line := range strings.Split(string(body), "\n") { f := strings.Fields(line) if len(f) == 2 && f[1] == name { sum, err := hex.DecodeString(f[0]) if err != nil { return nil, SE("bad checksum for %s", name) } return sum, nil } } return nil, SE("no checksum for %s", name) } func fetch(client *http.Client, url string, max int64) ([]byte, error) { // ------------------- fetch a small file resp, err := client.Get(url) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, SE("server returned status: %v", resp.Status) } return io.ReadAll(io.LimitReader(resp.Body, max)) } func checkaccess(NETS []string) { // ------------------------------------------------- check ip net based access match:=0; addrs, err := net.InterfaceAddrs() if err != nil { PE("Error getting addresses", err.Error()); os.Exit(1) } for _, validnet := range NETS { _, ipNet, err := net.ParseCIDR(validnet) if err != nil { PE("invalid network", validnet); continue } for _, address := range addrs { if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { if ipnet.IP.To4() != nil { if (ipNet.Contains(ipnet.IP)) { match++ } } } } } if (match==0) { PE("access violation, permission denied"); os.Exit(1) } } func Enc(str string) string { // ----------------------------------------------------------------- encode string enc, _ := basex.NewEncoding(Db64(CHRS)) return enc.Encode([]byte(Rndstr(2) + str)) } func Dec(str string) string { // ----------------------------------------------------------------- decode string enc, err := basex.NewEncoding(Db64(CHRS)) if err != nil { return "" } b, err := enc.Decode(str) if err != nil || len(b) < 2 { return "" } return string(b[2:]) } func Db64(txt string) string { // ------------------------------------------------------------- string to base64 d, _ := base64.StdEncoding.DecodeString(txt) return string(d) } func Rndstr(n int) string { // ------------------------------------------------------- random string with length b := make([]rune, n) for i := range b { n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(LR)))) b[i] = LR[n.Int64()] } return string(b) } func Input(msg string, def string) string { // -------------------------------- AlecAivazis/survey: input string tmp := "" err := survey.AskOne(&survey.Input{Message: msg, Default: def}, &tmp) if err != nil { if err == terminal.InterruptErr { P(Crb("Interrupted.")) os.Exit(0) } } return tmp } func Inputpw(msg string) string { // ---------------------------------------- AlecAivazis/survey: input password tmp := "" err := survey.AskOne(&survey.Password{Message: msg}, &tmp) if err != nil { if err == terminal.InterruptErr { P(Crb("Interrupted.")) os.Exit(0) } } return tmp } func Yesno(msg string, def bool, overwrite bool) bool { // -------------------------- AlecAivazis/survey: yes/no if (overwrite) { return true } var err error tmp := "" if def { err = survey.AskOne(&survey.Select{Message: msg, Options: []string{"Yes", "No"}}, &tmp) } else { err = survey.AskOne(&survey.Select{Message: msg, Options: []string{"No", "Yes"}}, &tmp) } if err != nil { if err == terminal.InterruptErr { P(Crb("Interrupted.")) os.Exit(0) } } if tmp == "Yes" { return true } else { return false } } func Getid(n int) string { // --------------------------------------------------------- get base62 random string const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ret := make([]byte, n) for i := 0; i < n; i++ { num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) ret[i] = letters[num.Int64()] } return string(ret) } func GETid(n int) string { // --------------------------------------------------------- get base36 random string const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ret := make([]byte, n) for i := 0; i < n; i++ { num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) ret[i] = letters[num.Int64()] } return string(ret) } func Mytable(rows *sql.Rows) []map[string]interface{} { // ----------------------------- load mysql result table defer rows.Close() columns, err := rows.Columns() if err != nil { return nil } count := len(columns) tableData := make([]map[string]interface{}, 0) values := make([]interface{}, count) valuePtrs := make([]interface{}, count) for rows.Next() { for i := 0; i < count; i++ { valuePtrs[i] = &values[i] } if err := rows.Scan(valuePtrs...); err != nil { break } entry := make(map[string]interface{}) for i, col := range columns { var v interface{} val := values[i] b, ok := val.([]byte) if ok { v = string(b) } else { v = val } entry[col] = v } tableData = append(tableData, entry) } return (tableData) } func Checkip(network string, ip string) bool { // ---------- check if ip is in range (cidr address or single ip) if net.ParseIP(ip) == nil { return false } _, subnet, err := net.ParseCIDR(network) if err == nil { if subnet.Contains(net.ParseIP(ip)) { return true } } else { if network == ip { return true } } return false } func Isflagpassed(name string) bool { // -------------------------------------------------- check if flag is set found := false flag.Visit(func(f *flag.Flag) { if f.Name == name { found = true } }) return found } func Body(r *http.Response) string { // ------------------------------------------------------------ http body defer r.Body.Close() body, err := io.ReadAll(r.Body) if err == nil { return string(body) } return "" } func Atoi(s string) int { // ------------------------------------------------------------------------------ atoi i, err := strconv.Atoi(s) if err != nil { return 0 } return i } func Itoa(i int) string { // ------------------------------------------------------------------------------ itoa return strconv.Itoa(i) } func GJA(j string, k string) []string { // -------------------------------------------------- convert gjson array var ret []string for _, c := range gjson.Get(j, k).Array() { ret = append(ret, c.String()) } return ret } // ------------------------------------------------------------------------------------------------ print simple func P(a ...any) (n int, err error) { return fmt.Fprintln(os.Stdout, a...) } func PN(a ...any) (n int, err error) { return fmt.Fprint(os.Stdout, a...) } func PF(format string, a ...any) (n int, err error) { return fmt.Fprintf(os.Stdout, format, a...) } func SF(format string, a ...any) string { return fmt.Sprintf(format, a...) } func SE(format string, a ...any) error { return fmt.Errorf(format, a...) } func PE(msg ...string) (n int, err error) { if (len(msg)==0) { return 0, nil } if (len(msg)>=2) { return fmt.Fprintf(os.Stdout, "%s: %s (%s)\n", Crb("ERROR"), Cwb(msg[0]),msg[1]) } return fmt.Fprintf(os.Stdout, "%s: %s\n", Crb("ERROR"), Cwb(msg[0])) } func PO(msg ...string) (n int, err error) { if (len(msg)==0) { return 0, nil } if (len(msg)>=2) { return fmt.Fprintf(os.Stdout, "%s: %s (%s)\n", Cgb("OK"), Cwb(msg[0]),msg[1]) } return fmt.Fprintf(os.Stdout, "%s: %s\n", Cgb("OK"), Cwb(msg[0])) } // ------------------------------------------------------------------------------------------ regular expression func ReplaceFirst(re *regexp.Regexp, str, replace string) string { loc := re.FindStringIndex(str) if loc == nil { return str } return str[:loc[0]] + replace + str[loc[1]:] } // -------------------------------------------------------------------------------------------- string functions func Shortstr(s string, length int) string { runes := []rune(s) if len(runes) <= length { return s } if length <= 2 { return ".." } return string(runes[:length-2]) + ".." } func SR(str string, n int) string { return strings.Repeat(str, n) } func RemoveAllMatches(slice []string, target string) []string { // remove matching string from array result := make([]string, 0, len(slice)) // never modify the callers slice for _, v := range slice { if v != target { result = append(result, v) } } return result } // -------------------------------------------------------------------------------------------- system functions func prgname() string { // program name exepath, err := os.Executable() if err != nil { PE(SF("Error getting executable path: %s", err)) return "" } exename := filepath.Base(exepath) return strings.TrimSuffix(exename, ".exe") // windows: gbld.exe -> gbld } // ------------------------------------------------------------------------------------------ viper abbrevations func VX(key string) bool { return viper.IsSet(key) } func VS(key string) string { return viper.GetString(key) } func VSS(key string) []string { return viper.GetStringSlice(key) } func VB(key string) bool { return viper.GetBool(key) } func VI(key string) int { return viper.GetInt(key) } // ------------------------------------------------------------------------------------------------- text colors // // catppuccin mocha - https://terminalcolors.com/themes/catppuccin/mocha/ // the palette is emitted as 24 bit color. terminals that do not announce truecolor via COLORTERM // fall back to the basic ansi colors, so their own theme keeps deciding. NO_COLOR is honoured. type rgb struct{ r, g, b int } var ( // ------------------------------------------------------------------------- catppuccin mocha palette CatRosewater = rgb{0xf5, 0xe0, 0xdc} CatFlamingo = rgb{0xf2, 0xcd, 0xcd} CatPink = rgb{0xf5, 0xc2, 0xe7} CatMauve = rgb{0xcb, 0xa6, 0xf7} CatRed = rgb{0xf3, 0x8b, 0xa8} CatMaroon = rgb{0xeb, 0xa0, 0xac} CatPeach = rgb{0xfa, 0xb3, 0x87} CatYellow = rgb{0xf9, 0xe2, 0xaf} CatGreen = rgb{0xa6, 0xe3, 0xa1} CatTeal = rgb{0x94, 0xe2, 0xd5} CatSky = rgb{0x89, 0xdc, 0xeb} CatSapphire = rgb{0x74, 0xc7, 0xec} CatBlue = rgb{0x89, 0xb4, 0xfa} CatLavender = rgb{0xb4, 0xbe, 0xfe} CatText = rgb{0xcd, 0xd6, 0xf4} CatSubtext1 = rgb{0xba, 0xc2, 0xde} CatSubtext0 = rgb{0xa6, 0xad, 0xc8} CatOverlay1 = rgb{0x7f, 0x84, 0x9c} CatSurface1 = rgb{0x45, 0x47, 0x5a} ) var catansi = map[string]rgb{ // ------------------------------- how mocha fills the ansi slots (bright = normal) "black": CatSurface1, "red": CatRed, "green": CatGreen, "yellow": CatYellow, "blue": CatBlue, "magenta": CatPink, "cyan": CatTeal, "white": CatSubtext1, "default": CatText, } func truecolor() bool { // ------------------------------------------ does the terminal understand 24 bit color ct := strings.ToLower(os.Getenv("COLORTERM")) return ct == "truecolor" || ct == "24bit" } func cfunc(c rgb, fallback color.Attribute, attrs ...color.Attribute) func(...interface{}) string { col := color.New(attrs...) if truecolor() { col.AddRGB(c.r, c.g, c.b) } else { col.Add(fallback) } return col.SprintFunc() } // mocha maps its ansi slots to red/green/yellow/blue/pink/teal, bright is identical to normal - // so the bold variants keep the hue and only add weight, exactly as before. var Cr func(...interface{}) string = cfunc(CatRed, color.FgRed) var Cg func(...interface{}) string = cfunc(CatGreen, color.FgGreen) var Cy func(...interface{}) string = cfunc(CatYellow, color.FgYellow) var Cb func(...interface{}) string = cfunc(CatBlue, color.FgBlue) var Cm func(...interface{}) string = cfunc(CatPink, color.FgMagenta) var Cc func(...interface{}) string = cfunc(CatTeal, color.FgCyan) var Cw func(...interface{}) string = cfunc(CatSubtext1, color.FgWhite) var Crb func(...interface{}) string = cfunc(CatRed, color.FgRed, color.Bold) var Cgb func(...interface{}) string = cfunc(CatGreen, color.FgGreen, color.Bold) var Cyb func(...interface{}) string = cfunc(CatYellow, color.FgYellow, color.Bold) var Cbb func(...interface{}) string = cfunc(CatBlue, color.FgBlue, color.Bold) var Cmb func(...interface{}) string = cfunc(CatPink, color.FgMagenta, color.Bold) var Ccb func(...interface{}) string = cfunc(CatTeal, color.FgCyan, color.Bold) var Cwb func(...interface{}) string = cfunc(CatText, color.FgWhite, color.Bold) // text: the themes foreground // ------------------------------------------------------------------------------------- survey prompt colors // // survey renders its prompts from templates that call {{color "green+hb"}} and friends, which normally // ends up in mgutz/ansi - and that only speaks 16/256 colors. replacing the template function is enough // to theme every prompt including its icons. survey computes its layout from a separate color free // rendering, so the longer truecolor sequences cannot disturb the cursor placement. func init() { core.TemplateFuncsWithColor["color"] = catcolor } // must run before the first prompt is parsed func catcolor(style string) string { // ------------------------- mgutz/ansi style string -> catppuccin mocha if style == "reset" { return "\033[0m" } if !truecolor() || strings.Contains(style, ":") { // background colors stay with the original mapping return ansi.ColorCode(style) } name, attrs, _ := strings.Cut(style, "+") c, ok := catansi[name] if !ok { return ansi.ColorCode(style) } seq := "" for _, a := range attrs { // 'h' (bright) is a no-op: mocha uses the same value for both switch a { case 'b': seq += "1;" case 'd': seq += "2;" case 'u': seq += "4;" case 'i': seq += "7;" } } return SF("\033[%s38;2;%d;%d;%dm", seq, c.r, c.g, c.b) } // ========================================================================================================= END