Commit 178c4fb
Changed files (39)
cmd
internal
generate
shouldideploytoday
speedtest
stopwatch
timer
utils
cmd/generate/generate_key.go
@@ -9,8 +9,8 @@ var generateKeyCmd = &cobra.Command{
Use: "key",
Short: "Generate key",
Long: `Generate key. Re-implementation of "openssl rand -base64 48"". Result is copied to clipboard.`,
- Run: func(cmd *cobra.Command, args []string) {
- generate.Key()
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return generate.Key()
},
}
cmd/generate/generate_passphrase.go
@@ -9,8 +9,8 @@ var generatePassphraseCmd = &cobra.Command{
Use: "passphrase",
Short: "Generate passphrase",
Long: `Generate passphrase. Result is copied to clipboard.`,
- Run: func(cmd *cobra.Command, args []string) {
- generate.Passphrase()
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return generate.Passphrase()
},
}
cmd/generate/generate_password.go
@@ -9,8 +9,8 @@ var generatePasswordCmd = &cobra.Command{
Use: "password",
Short: "Generate password",
Long: `Generate password. Result is copied to clipboard.`,
- Run: func(cmd *cobra.Command, args []string) {
- generate.Password()
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return generate.Password()
},
}
cmd/generate/generate_qrcode.go
@@ -9,8 +9,8 @@ var generateQRCodeCmd = &cobra.Command{
Use: "qrcode",
Short: "Generate QR code",
Long: `Generate QR code from URL (either as an arg or from clipboard) and copy resulting image to clipboard.`,
- Run: func(cmd *cobra.Command, args []string) {
- generate.QRCode(args)
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return generate.QRCode(args)
},
}
cmd/generate/generate_ssh_key.go
@@ -9,8 +9,8 @@ var generateSSHKeyCmd = &cobra.Command{
Use: "ssh-key",
Short: "Create SSH key",
Long: `Create SSH key`,
- Run: func(cmd *cobra.Command, args []string) {
- generate.SSHKey(args)
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return generate.SSHKey(args)
},
}
cmd/get/get_hwinfo.go
@@ -8,8 +8,8 @@ import (
var getHwInfoCmd = &cobra.Command{
Use: "hwinfo",
Short: "Get hardware info",
- Run: func(cmd *cobra.Command, args []string) {
- get.HwInfo()
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return get.HwInfo()
},
}
cmd/get/get_iface.go
@@ -9,8 +9,8 @@ var getIfaceCmd = &cobra.Command{
Use: "iface",
Short: "Get iface",
Long: `Get iface used for public internet access`,
- Run: func(cmd *cobra.Command, args []string) {
- get.Iface()
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return get.Iface()
},
}
cmd/get/get_ip.go
@@ -9,8 +9,8 @@ var getIPCmd = &cobra.Command{
Use: "ip",
Short: "Get IP information",
Long: `Get IP information`,
- Run: func(cmd *cobra.Command, args []string) {
- get.IP()
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return get.IP()
},
}
cmd/get/get_ipinfo.go
@@ -9,8 +9,8 @@ var getIPInfoCmd = &cobra.Command{
Use: "ipinfo [ip]",
Short: "Get detailed IP information (location, ISP, etc.)",
Args: cobra.MaximumNArgs(1),
- Run: func(cmd *cobra.Command, args []string) {
- get.IPInfo(args)
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return get.IPInfo(args)
},
}
cmd/get/get_sensors.go
@@ -8,8 +8,8 @@ import (
var SensorsCmd = &cobra.Command{
Use: "sensors",
Short: "Get sensors info",
- Run: func(cmd *cobra.Command, args []string) {
- get.Sensors()
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return get.Sensors()
},
}
cmd/get/get_siteinfo.go
@@ -9,8 +9,8 @@ var siteinfoCmd = &cobra.Command{
Use: "siteinfo [url]",
Short: "Get website technology information",
Long: `Analyze a website and display the technologies it uses, categorized by type`,
- Run: func(cmd *cobra.Command, args []string) {
- get.GetSiteInfo(args)
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return get.GetSiteInfo(args)
},
}
cmd/get/get_smart.go
@@ -9,8 +9,8 @@ var getSmartCmd = &cobra.Command{
Use: "smart [disk]",
Short: "Get disk SMART info.",
Long: "Get disk SMART info. Equivalent of `sudo smartctl -a /dev/nvme0n1p`",
- Run: func(cmd *cobra.Command, args []string) {
- get.Smart(args)
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return get.Smart(args)
},
}
cmd/get/get_sysinfo.go
@@ -8,8 +8,8 @@ import (
var getSysInfoCmd = &cobra.Command{
Use: "sysinfo",
Short: "Get system info",
- Run: func(cmd *cobra.Command, args []string) {
- get.SysInfo()
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return get.SysInfo()
},
}
cmd/get/get_volumes.go
@@ -8,8 +8,8 @@ import (
var listVolumesCmd = &cobra.Command{
Use: "volumes",
Short: "List volumes",
- Run: func(cmd *cobra.Command, args []string) {
- get.Volumes()
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return get.Volumes()
},
}
cmd/shouldideploytoday.go
@@ -8,8 +8,8 @@ import (
var ShouldIDeployTodayCmd = &cobra.Command{
Use: "shouldideploytoday",
Short: "Should I deploy today?",
- Run: func(cmd *cobra.Command, args []string) {
- shouldideploytoday.ShouldIDeployToday()
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return shouldideploytoday.ShouldIDeployToday()
},
}
cmd/speedtest.go
@@ -8,8 +8,8 @@ import (
var SpeedTestCmd = &cobra.Command{
Use: "speedtest",
Short: "Speedtest",
- Run: func(cmd *cobra.Command, args []string) {
- speedtest.SpeedTest()
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return speedtest.SpeedTest()
},
}
cmd/stopwatch.go
@@ -8,8 +8,8 @@ import (
var StopwatchCmd = &cobra.Command{
Use: "stopwatch",
Short: "Create a stopwatch",
- Run: func(cmd *cobra.Command, args []string) {
- stopwatch.Stopwatch()
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return stopwatch.Stopwatch()
},
}
cmd/timer.go
@@ -8,8 +8,8 @@ import (
var TimerCmd = &cobra.Command{
Use: "timer",
Short: "Create a timer",
- Run: func(cmd *cobra.Command, args []string) {
- timer.Timer(args)
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return timer.Timer(args)
},
}
internal/generate/key.go
@@ -6,25 +6,32 @@ import (
"fmt"
"github.com/kahnwong/swissknife/internal/utils"
- "github.com/rs/zerolog/log"
)
-func generateKey(n int) string {
+func generateKey(n int) (string, error) {
// https://stackoverflow.com/questions/32349807/how-can-i-generate-a-random-int-using-the-crypto-rand-package/32351471#32351471
// generate random bytes
b := make([]byte, n)
_, err := rand.Read(b)
// Note that err == nil only if we read len(b) bytes.
if err != nil {
- log.Fatal().Msg("Failed to generate random bytes")
+ return "", fmt.Errorf("failed to generate random bytes: %w", err)
}
// convert to base64
- return base64.URLEncoding.EncodeToString(b)
+ return base64.URLEncoding.EncodeToString(b), nil
}
-func Key() {
- key := generateKey(48)
- utils.WriteToClipboard(key)
+func Key() error {
+ key, err := generateKey(48)
+ if err != nil {
+ return err
+ }
+
+ if err = utils.WriteToClipboard(key); err != nil {
+ return err
+ }
+
fmt.Printf("%s\n", key)
+ return nil
}
internal/generate/passphrase.go
@@ -5,24 +5,31 @@ import (
"strings"
"github.com/kahnwong/swissknife/internal/utils"
- "github.com/rs/zerolog/log"
"github.com/sethvargo/go-diceware/diceware"
)
-func generatePassphrase() string {
+func generatePassphrase() (string, error) {
// Generate 6 words using the diceware algorithm.
list, err := diceware.Generate(6)
if err != nil {
- log.Fatal().Msg("Failed to generate passphrases")
+ return "", fmt.Errorf("failed to generate passphrases: %w", err)
}
res := strings.Join(list, "-")
- return res
+ return res, nil
}
-func Passphrase() {
- passphrase := generatePassphrase()
- utils.WriteToClipboard(passphrase)
+func Passphrase() error {
+ passphrase, err := generatePassphrase()
+ if err != nil {
+ return err
+ }
+
+ if err = utils.WriteToClipboard(passphrase); err != nil {
+ return err
+ }
+
fmt.Printf("%s\n", passphrase)
+ return nil
}
internal/generate/password.go
@@ -4,23 +4,30 @@ import (
"fmt"
"github.com/kahnwong/swissknife/internal/utils"
- "github.com/rs/zerolog/log"
"github.com/sethvargo/go-password/password"
)
-func generatePassword() string {
+func generatePassword() (string, error) {
// Generate a password that is 64 characters long with 10 digits, 10 symbols,
// allowing upper and lower case letters, disallowing repeat characters.
res, err := password.Generate(32, 10, 0, false, false)
if err != nil {
- log.Fatal().Msg("Failed to generate password")
+ return "", fmt.Errorf("failed to generate password: %w", err)
}
- return res
+ return res, nil
}
-func Password() {
- psswd := generatePassword()
- utils.WriteToClipboard(psswd)
+func Password() error {
+ psswd, err := generatePassword()
+ if err != nil {
+ return err
+ }
+
+ if err = utils.WriteToClipboard(psswd); err != nil {
+ return err
+ }
+
fmt.Printf("%s\n", psswd)
+ return nil
}
internal/generate/qrcode.go
@@ -4,38 +4,45 @@ import (
"fmt"
"github.com/kahnwong/swissknife/internal/utils"
- "github.com/rs/zerolog/log"
qrcode "github.com/skip2/go-qrcode"
)
-func generateQRCode(url string) ([]byte, string) {
+func generateQRCode(url string) ([]byte, string, error) {
// init
var q *qrcode.QRCode
q, err := qrcode.New(url, qrcode.Medium)
if err != nil {
- log.Fatal().Msg("Failed to initialize QRCode object")
+ return nil, "", fmt.Errorf("failed to initialize QRCode object: %w", err)
}
// generate png
png, err := q.PNG(1024)
if err != nil {
- log.Fatal().Msg("Failed to generate QRCode PNG")
+ return nil, "", fmt.Errorf("failed to generate QRCode PNG: %w", err)
}
// for stdout
//stdout := q.ToString(false)
stdout := q.ToSmallString(false)
- return png, stdout
+ return png, stdout, nil
}
-func QRCode(args []string) {
+func QRCode(args []string) error {
// set URL
url := utils.SetURL(args)
fmt.Println(url)
// main
- png, stdout := generateQRCode(url)
- utils.WriteToClipboardImage(png)
+ png, stdout, err := generateQRCode(url)
+ if err != nil {
+ return err
+ }
+
+ if err = utils.WriteToClipboardImage(png); err != nil {
+ return fmt.Errorf("failed to write to clipboard: %w", err)
+ }
+
fmt.Println(stdout)
+ return nil
}
internal/generate/ssh_key.go
@@ -9,52 +9,51 @@ import (
"os"
"path/filepath"
- "github.com/rs/zerolog/log"
"golang.org/x/crypto/ssh"
)
// helpers
-func writeStringToFile(filePath string, data string, permission os.FileMode) {
+func writeStringToFile(filePath string, data string, permission os.FileMode) error {
file, err := os.Create(filePath)
if err != nil {
- log.Fatal().Msgf("Failed to create file %s", filePath)
+ return fmt.Errorf("failed to create file %s: %w", filePath, err)
}
- _, err = file.WriteString(data)
- if err != nil {
- log.Fatal().Msgf("Failed to write to file %s", filePath)
+ if _, err = file.WriteString(data); err != nil {
+ return fmt.Errorf("failed to write to file %s: %w", filePath, err)
}
- err = file.Chmod(permission)
- if err != nil {
- log.Fatal().Msgf("Failed to change file permissions")
+ if err = file.Chmod(permission); err != nil {
+ return fmt.Errorf("failed to change file permissions: %w", err)
}
+
+ return nil
}
-func returnKeyPath(fileName string) string {
+func returnKeyPath(fileName string) (string, error) {
currentDir, err := os.Getwd()
if err != nil {
- log.Fatal().Msgf("Failed to get current directory")
+ return "", fmt.Errorf("failed to get current directory: %w", err)
}
keyPath := filepath.Join(currentDir, fileName)
- return keyPath
+ return keyPath, nil
}
// main
-func generateSSHKeyEDSA() (string, string) {
+func generateSSHKeyEDSA() (string, string, error) {
// Generate a new Ed25519 private key
//// If rand is nil, crypto/rand.Reader will be used
public, private, err := ed25519.GenerateKey(nil)
if err != nil {
- log.Fatal().Msgf("Failed to generate ed25519 key")
+ return "", "", fmt.Errorf("failed to generate ed25519 key: %w", err)
}
// public key
publicKey, err := ssh.NewPublicKey(public)
if err != nil {
- log.Fatal().Msgf("Failed to create public key")
+ return "", "", fmt.Errorf("failed to create public key: %w", err)
}
publicKeyString := fmt.Sprintf("ssh-ed25519 %s", base64.StdEncoding.EncodeToString(publicKey.Marshal()))
@@ -62,30 +61,42 @@ func generateSSHKeyEDSA() (string, string) {
//// p stands for pem
p, err := ssh.MarshalPrivateKey(crypto.PrivateKey(private), "")
if err != nil {
- log.Fatal().Msgf("Failed to marshal private key")
+ return "", "", fmt.Errorf("failed to marshal private key: %w", err)
}
privateKeyPem := pem.EncodeToMemory(p)
privateKeyString := string(privateKeyPem)
- return publicKeyString, privateKeyString
+ return publicKeyString, privateKeyString, nil
}
-func SSHKey(args []string) {
+func SSHKey(args []string) error {
//init
if len(args) == 0 {
- fmt.Println("Please specify key name")
- os.Exit(1)
+ return fmt.Errorf("please specify key name")
}
// main
- publicKeyString, privateKeyString := generateSSHKeyEDSA()
+ publicKeyString, privateKeyString, err := generateSSHKeyEDSA()
+ if err != nil {
+ return err
+ }
// write key to file
publicKeyFilename := fmt.Sprintf("%s.pub", args[0])
- writeStringToFile(publicKeyFilename, publicKeyString, 0644)
+ if err = writeStringToFile(publicKeyFilename, publicKeyString, 0644); err != nil {
+ return err
+ }
privateKeyFilename := args[0]
- writeStringToFile(privateKeyFilename, privateKeyString, 0600)
+ if err = writeStringToFile(privateKeyFilename, privateKeyString, 0600); err != nil {
+ return err
+ }
+
+ keyPath, err := returnKeyPath(args[0])
+ if err != nil {
+ return err
+ }
- fmt.Printf("SSH key created at: %s\n", returnKeyPath(args[0]))
+ fmt.Printf("SSH key created at: %s\n", keyPath)
+ return nil
}
internal/get/hwinfo.go
@@ -7,44 +7,46 @@ import (
"github.com/jaypipes/ghw"
"github.com/kahnwong/swissknife/configs/color"
- "github.com/rs/zerolog/log"
"github.com/yumaojun03/dmidecode"
)
-func HwInfo() {
+func HwInfo() error {
// need to run as sudo
if os.Geteuid() != 0 {
- log.Fatal().Msg("Need to run as sudo")
+ return fmt.Errorf("need to run as sudo")
}
// cpu
- cpuModel, cpuThreads := getCpuInfo() // shared with `sysinfo.go`
+ cpuModel, cpuThreads, err := getCpuInfo() // shared with `sysinfo.go`
+ if err != nil {
+ return err
+ }
fmt.Printf("%s: %s (%v)\n", color.Green("CPU"), cpuModel, cpuThreads)
// gpu
gpu, err := ghw.GPU()
if err != nil {
- fmt.Printf("Error getting GPU info: %v", err)
- }
+ fmt.Printf("Error getting GPU info: %v\n", err)
+ } else {
+ fmt.Printf("%s:\n", color.Green("GPUs"))
- fmt.Printf("%s:\n", color.Green("GPUs"))
-
- for _, card := range gpu.GraphicsCards {
- fmt.Printf(" - %s: %s\n", color.Blue("Vendor"), card.DeviceInfo.Vendor.Name)
- fmt.Printf(" %s: %s\n", color.Blue("Model"), card.DeviceInfo.Product.Name)
+ for _, card := range gpu.GraphicsCards {
+ fmt.Printf(" - %s: %s\n", color.Blue("Vendor"), card.DeviceInfo.Vendor.Name)
+ fmt.Printf(" %s: %s\n", color.Blue("Model"), card.DeviceInfo.Product.Name)
+ }
}
// memory
dmi, err := dmidecode.New()
if err != nil {
- log.Fatal().Msg("Failed to get dmi info")
+ return fmt.Errorf("failed to get dmi info: %w", err)
}
fmt.Printf("%s:\n", color.Green("Memory"))
memoryDevices, err := dmi.MemoryDevice()
if err != nil {
- log.Fatal().Msg("Failed to get memory info")
+ return fmt.Errorf("failed to get memory info: %w", err)
}
for _, i := range memoryDevices {
@@ -60,7 +62,7 @@ func HwInfo() {
// disk
block, err := ghw.Block()
if err != nil {
- log.Fatal().Msg("Failed to get block storage info")
+ return fmt.Errorf("failed to get block storage info: %w", err)
}
fmt.Printf("%s:\n", color.Green("Disks"))
@@ -76,10 +78,11 @@ func HwInfo() {
//// mainboardInfo, err := dmi.BaseBoard()
baseboard, err := ghw.Baseboard()
if err != nil {
- log.Fatal().Msg("Failed to get baseboard info")
+ return fmt.Errorf("failed to get baseboard info: %w", err)
}
fmt.Printf("%s:\n", color.Green("Mainboard"))
fmt.Printf(" - %s: %s\n", color.Blue("Manufacturer"), baseboard.Vendor)
fmt.Printf(" %s: %s\n", color.Blue("Model"), baseboard.Product)
+ return nil
}
internal/get/iface.go
@@ -5,30 +5,33 @@ import (
"net"
"github.com/libp2p/go-netroute"
- "github.com/rs/zerolog/log"
)
// unix: `netstat -nr -f inet`
// get iface-owned app: `ifconfig -v utun3 | grep "agent domain"`
-func getIface() string {
+func getIface() (string, error) {
r, err := netroute.New()
if err != nil {
- log.Fatal().Msg("Error initializing netroute")
+ return "", fmt.Errorf("error initializing netroute: %w", err)
}
iface, _, _, err := r.Route(
net.IPv4(104, 16, 133, 229), // cloudflare
)
if err != nil {
- log.Fatal().Msg("Error retrieving net route")
+ return "", fmt.Errorf("error retrieving net route: %w", err)
}
//fmt.Printf("%v, %v, %v, %v\n", iface, gw, src, err)
- return iface.Name
+ return iface.Name, nil
}
-func Iface() {
- iface := getIface()
+func Iface() error {
+ iface, err := getIface()
+ if err != nil {
+ return err
+ }
fmt.Println(iface)
+ return nil
}
internal/get/ip.go
@@ -21,12 +21,12 @@ type IPLocationResponse struct {
RegionName string `json:"regionName"`
}
-func getInternalIP() string {
+func getInternalIP() (string, error) {
var internalIP string
ifaces, err := net.Interfaces()
if err != nil {
- panic(err)
+ return "", fmt.Errorf("failed to get network interfaces: %w", err)
}
for _, iface := range ifaces {
@@ -39,7 +39,7 @@ func getInternalIP() string {
addrs, err := iface.Addrs()
if err != nil {
- panic(err)
+ return "", fmt.Errorf("failed to get interface addresses: %w", err)
}
for _, addr := range addrs {
@@ -63,14 +63,20 @@ func getInternalIP() string {
}
}
- return internalIP
+ return internalIP, nil
}
-func getLocalIP() string {
+func getLocalIP() (string, error) {
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
- log.Fatal().Msg("Error on net.Dial")
+ return "", fmt.Errorf("error on net.Dial: %w", err)
}
+ defer func(conn net.Conn) {
+ err = conn.Close()
+ if err != nil {
+ log.Error().Err(err).Msg("error closing connection")
+ }
+ }(conn)
localAddr := conn.LocalAddr().(*net.UDPAddr)
@@ -81,13 +87,13 @@ func getLocalIP() string {
if len(parts) > 0 {
localIP = parts[0]
} else {
- log.Error().Msg("Invalid address format")
+ return "", fmt.Errorf("invalid address format")
}
- return localIP
+ return localIP, nil
}
-func getPublicIP() PublicIPResponse {
+func getPublicIP() (PublicIPResponse, error) {
var response PublicIPResponse
err := requests.
URL("https://api.ipify.org?format=json").
@@ -95,13 +101,13 @@ func getPublicIP() PublicIPResponse {
Fetch(context.Background())
if err != nil {
- log.Fatal().Msg("Error getting public ip")
+ return PublicIPResponse{}, fmt.Errorf("error getting public ip: %w", err)
}
- return response
+ return response, nil
}
-func getIPLocation(ip string) IPLocationResponse {
+func getIPLocation(ip string) (IPLocationResponse, error) {
var response IPLocationResponse
err := requests.
URL(fmt.Sprintf("http://ip-api.com/json/%s?fields=query,country,regionName", ip)).
@@ -109,20 +115,34 @@ func getIPLocation(ip string) IPLocationResponse {
Fetch(context.Background())
if err != nil {
- log.Fatal().Msg("Error getting ip location")
+ return IPLocationResponse{}, fmt.Errorf("error getting ip location: %w", err)
}
- return response
+ return response, nil
}
-func IP() {
- internalIP := getInternalIP()
+func IP() error {
+ internalIP, err := getInternalIP()
+ if err != nil {
+ return err
+ }
fmt.Printf("%s: %s\n", color.Green("Internal IP"), internalIP)
- localIP := getLocalIP()
+ localIP, err := getLocalIP()
+ if err != nil {
+ return err
+ }
fmt.Printf("%s: %s\n", color.Green("Local IP"), localIP)
- publicIP := getPublicIP()
- IPLocation := getIPLocation(publicIP.Ip)
+ publicIP, err := getPublicIP()
+ if err != nil {
+ return err
+ }
+
+ IPLocation, err := getIPLocation(publicIP.Ip)
+ if err != nil {
+ return err
+ }
fmt.Printf("%s: %s (%s, %s)\n", color.Green("Public IP"), publicIP.Ip, color.Blue(IPLocation.RegionName), color.Blue(IPLocation.Country))
+ return nil
}
internal/get/ipinfo.go
@@ -7,7 +7,6 @@ import (
"github.com/carlmjohnson/requests"
"github.com/kahnwong/swissknife/configs/color"
"github.com/kahnwong/swissknife/internal/utils"
- "github.com/rs/zerolog/log"
)
type IPInfoResponse struct {
@@ -27,7 +26,7 @@ type IPInfoResponse struct {
As string `json:"as"`
}
-func getIPInfo(ip string) IPInfoResponse {
+func getIPInfo(ip string) (IPInfoResponse, error) {
var response IPInfoResponse
err := requests.
URL(fmt.Sprintf("http://ip-api.com/json/%s", ip)).
@@ -35,27 +34,34 @@ func getIPInfo(ip string) IPInfoResponse {
Fetch(context.Background())
if err != nil {
- log.Fatal().Msg("Error getting detailed ip info")
+ return IPInfoResponse{}, fmt.Errorf("error getting detailed ip info: %w", err)
}
- return response
+ return response, nil
}
-func IPInfo(args []string) {
+func IPInfo(args []string) error {
ip := utils.SetIP(args)
var targetIP string
if ip == "" {
- publicIP := getPublicIP()
+ publicIP, err := getPublicIP()
+ if err != nil {
+ return err
+ }
targetIP = publicIP.Ip
} else {
targetIP = ip
}
- ipInfo := getIPInfo(targetIP)
+ ipInfo, err := getIPInfo(targetIP)
+ if err != nil {
+ return err
+ }
fmt.Printf("%s: %s\n", color.Green("IP Address"), ipInfo.Query)
fmt.Printf("%s: %s, %s, %s\n", color.Green("Location"), ipInfo.City, ipInfo.RegionName, color.Blue(ipInfo.Country))
fmt.Printf("%s: %s\n", color.Green("ISP"), ipInfo.Isp)
fmt.Printf("%s: %s\n", color.Green("Organization"), ipInfo.Org)
+ return nil
}
internal/get/sensors.go
@@ -12,20 +12,20 @@ import (
"fmt"
"github.com/kahnwong/swissknife/configs/color"
- "github.com/rs/zerolog/log"
)
-func Sensors() {
+func Sensors() error {
result := C.sensors()
switch result.error {
case C.SENSOR_SUCCESS:
fmt.Printf("%s: %.2f\n", color.Green("Temperature"), float64(result.temperature))
+ return nil
case C.SENSOR_NO_COMPONENTS:
- log.Fatal().Msg("No components found")
+ return fmt.Errorf("no components found")
case C.SENSOR_NO_TEMPERATURE:
- log.Fatal().Msg("No temperature reading available")
+ return fmt.Errorf("no temperature reading available")
default:
- log.Fatal().Msg("Unknown error occurred")
+ return fmt.Errorf("unknown error occurred")
}
}
internal/get/siteinfo.go
@@ -4,13 +4,13 @@ import (
"fmt"
"io"
"net/http"
+ "os"
"sort"
"strings"
"github.com/kahnwong/swissknife/configs/color"
"github.com/kahnwong/swissknife/internal/utils"
wappalyzer "github.com/projectdiscovery/wappalyzergo"
- "github.com/rs/zerolog/log"
)
// categoryMapping maps wappalyzer category names to our display categories
@@ -101,7 +101,7 @@ var categoryMapping = map[string]string{
"Webcams": "Media",
}
-func GetSiteInfo(args []string) {
+func GetSiteInfo(args []string) error {
// set URL
url := utils.SetURL(args)
fmt.Println(url)
@@ -109,24 +109,23 @@ func GetSiteInfo(args []string) {
// fetch site
resp, err := http.DefaultClient.Get(url)
if err != nil {
- log.Fatal().Err(err).Msgf("Failed to fetch URL: %s", url)
+ return fmt.Errorf("failed to fetch URL %s: %w", url, err)
}
defer func(Body io.ReadCloser) {
- err = Body.Close()
- if err != nil {
- log.Error().Err(err).Msg("Failed to close response body")
+ if err := Body.Close(); err != nil {
+ fmt.Fprintf(os.Stderr, "warning: failed to close response body: %v\n", err)
}
}(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
- log.Fatal().Err(err).Msg("Failed to read response body")
+ return fmt.Errorf("failed to read response body: %w", err)
}
// init wappalyzer
wappalyzerClient, err := wappalyzer.New()
if err != nil {
- log.Fatal().Err(err).Msg("Failed to initialize wappalyzer")
+ return fmt.Errorf("failed to initialize wappalyzer: %w", err)
}
appsInfo := wappalyzerClient.FingerprintWithInfo(resp.Header, data)
@@ -158,6 +157,7 @@ func GetSiteInfo(args []string) {
if len(categoryTechs) == 0 {
fmt.Println("No technologies detected")
+ return nil
}
categories := make([]string, 0, len(categoryTechs))
@@ -176,4 +176,5 @@ func GetSiteInfo(args []string) {
fmt.Printf("- %s\n", tech)
}
}
+ return nil
}
internal/get/smart.go
@@ -1,6 +1,7 @@
package get
import (
+ "fmt"
"os"
"reflect"
"runtime"
@@ -8,7 +9,6 @@ import (
"github.com/anatol/smart.go"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/kahnwong/swissknife/configs/color"
- "github.com/rs/zerolog/log"
"github.com/shirou/gopsutil/v4/disk"
)
@@ -93,32 +93,38 @@ func sataSmart(device string) error {
}
}
-func Smart(args []string) {
- if runtime.GOOS == "linux" {
- var device string
- if len(args) == 0 {
- device = getRootDiskVolume()
- } else if len(args) == 1 {
- device = args[0]
- } else {
- log.Error().Msg("Too many arguments")
- }
- err := nvmeSmart(device)
+func Smart(args []string) error {
+ if runtime.GOOS != "linux" {
+ return fmt.Errorf("%s is not supported", runtime.GOOS)
+ }
+
+ var device string
+ var err error
+ if len(args) == 0 {
+ device, err = getRootDiskVolume()
if err != nil {
- err = sataSmart(device)
- if err != nil {
- log.Error().Err(err).Msg("Unrecognized device type. It's not NVME or SATA, or you didn't run as sudo")
- }
+ return err
}
+ } else if len(args) == 1 {
+ device = args[0]
} else {
- log.Error().Msgf("%s is not supported\n", runtime.GOOS)
+ return fmt.Errorf("too many arguments")
+ }
+
+ err = nvmeSmart(device)
+ if err != nil {
+ err = sataSmart(device)
+ if err != nil {
+ return fmt.Errorf("unrecognized device type. It's not NVME or SATA, or you didn't run as sudo: %w", err)
+ }
}
+ return nil
}
-func getRootDiskVolume() string {
+func getRootDiskVolume() (string, error) {
partitions, err := disk.Partitions(false)
if err != nil {
- log.Fatal().Msg("Failed to get disk partitions")
+ return "", fmt.Errorf("failed to get disk partitions: %w", err)
}
var volume string
@@ -128,5 +134,5 @@ func getRootDiskVolume() string {
}
}
- return volume
+ return volume, nil
}
internal/get/sysinfo.go
@@ -19,7 +19,6 @@ import (
"github.com/shirou/gopsutil/v4/cpu"
"github.com/kahnwong/swissknife/configs/color"
- "github.com/rs/zerolog/log"
"github.com/shirou/gopsutil/v4/disk"
"github.com/shirou/gopsutil/v4/host"
"github.com/shirou/gopsutil/v4/mem"
@@ -33,10 +32,16 @@ type batteryStruct struct {
BatteryTimeToEmpty uint64
}
-func SysInfo() {
+func SysInfo() error {
// host
- username := getUsername()
- hostInfo := getHostInfo()
+ username, err := getUsername()
+ if err != nil {
+ return err
+ }
+ hostInfo, err := getHostInfo()
+ if err != nil {
+ return err
+ }
fmt.Printf("%s@%s\n", color.Green(username), color.Green(hostInfo.Hostname))
fmt.Println(strings.Repeat("-", len(username)+len(hostInfo.Hostname)+1))
@@ -44,11 +49,17 @@ func SysInfo() {
fmt.Printf("%s: %s %s\n", color.Green("OS"), hostInfo.Platform, hostInfo.PlatformVersion)
// cpu
- cpuModel, cpuThreads := getCpuInfo()
+ cpuModel, cpuThreads, err := getCpuInfo()
+ if err != nil {
+ return err
+ }
fmt.Printf("%s: %s (%v)\n", color.Green("CPU"), cpuModel, cpuThreads)
// memory
- memoryUsed, memoryTotal, memoryUsedPercent := getMemoryInfo()
+ memoryUsed, memoryTotal, memoryUsedPercent, err := getMemoryInfo()
+ if err != nil {
+ return err
+ }
fmt.Printf("%s: %.2f GB / %v GB (%s)\n",
color.Green("Memory"),
convertKBtoGB(memoryUsed, true), convertKBtoGB(memoryTotal, false),
@@ -56,7 +67,10 @@ func SysInfo() {
)
// disk
- diskUsed, diskTotal, diskUsedPercent := getDiskInfo()
+ diskUsed, diskTotal, diskUsedPercent, err := getDiskInfo()
+ if err != nil {
+ return err
+ }
fmt.Printf("%s: %v GB / %v GB (%s)\n",
color.Green("Disk"),
convertKBtoGiB(diskUsed), convertKBtoGiB(diskTotal),
@@ -64,7 +78,10 @@ func SysInfo() {
)
// battery
- batteryInfo := getBatteryInfo()
+ batteryInfo, err := getBatteryInfo()
+ if err != nil {
+ return err
+ }
// only print battery info if is a laptop
if batteryInfo.BatteryFull > 0 {
@@ -100,63 +117,63 @@ func SysInfo() {
color.Blue(batteryTimeToEmptyFormatted),
)
}
+ return nil
}
// ---- functions ----
-func getUsername() string {
+func getUsername() (string, error) {
username, err := user.Current()
if err != nil {
- log.Fatal().Msg("Failed to get current user info")
+ return "", fmt.Errorf("failed to get current user info: %w", err)
}
- return username.Username
+ return username.Username, nil
}
-func getHostInfo() *host.InfoStat {
+func getHostInfo() (*host.InfoStat, error) {
hostStat, err := host.Info()
if err != nil {
- log.Fatal().Msg("Failed to get host info")
+ return nil, fmt.Errorf("failed to get host info: %w", err)
}
- return hostStat
+ return hostStat, nil
}
-func getCpuInfo() (string, int) {
+func getCpuInfo() (string, int, error) {
cpuStat, err := cpu.Info()
if err != nil {
- log.Fatal().Msg("Failed to get cpu info")
+ return "", 0, fmt.Errorf("failed to get cpu info: %w", err)
}
cpuThreads, err := cpu.Counts(true)
if err != nil {
- log.Fatal().Msg("Failed to get cpu threads info")
+ return "", 0, fmt.Errorf("failed to get cpu threads info: %w", err)
}
- return cpuStat[0].ModelName, cpuThreads
+ return cpuStat[0].ModelName, cpuThreads, nil
}
-func getMemoryInfo() (uint64, uint64, int) {
+func getMemoryInfo() (uint64, uint64, int, error) {
vmStat, err := mem.VirtualMemory()
if err != nil {
- log.Fatal().Msg("Failed to get memory info")
+ return 0, 0, 0, fmt.Errorf("failed to get memory info: %w", err)
}
- return vmStat.Used, vmStat.Total, convertToPercent(float64(vmStat.Used) / float64(vmStat.Total))
+ return vmStat.Used, vmStat.Total, convertToPercent(float64(vmStat.Used) / float64(vmStat.Total)), nil
}
-func getDiskInfo() (uint64, uint64, int) {
+func getDiskInfo() (uint64, uint64, int, error) {
diskStat, err := disk.Usage("/")
if err != nil {
- log.Fatal().Msg("Failed to get disk info")
+ return 0, 0, 0, fmt.Errorf("failed to get disk info: %w", err)
}
- return diskStat.Used, diskStat.Total, convertToPercent(float64(diskStat.Used) / float64(diskStat.Total))
+ return diskStat.Used, diskStat.Total, convertToPercent(float64(diskStat.Used) / float64(diskStat.Total)), nil
}
-func getBatteryInfo() batteryStruct {
+func getBatteryInfo() (batteryStruct, error) {
// battery
batteries, err := battery.GetAll()
if err != nil {
- if strings.Contains(err.Error(), "no such file or directory") {
- // ignore this happens on [linux on mac devices]
- } else {
- log.Fatal().Msg("Error getting battery info")
+ if !strings.Contains(err.Error(), "no such file or directory") {
+ return batteryStruct{}, fmt.Errorf("error getting battery info: %w", err)
}
+ // ignore this happens on [linux on mac devices]
}
//// charge stats
@@ -181,9 +198,9 @@ func getBatteryInfo() batteryStruct {
case C.BATTERY_NO_CYCLE_COUNT:
batteryCycleCount = 0
case C.BATTERY_MANAGER_ERROR:
- log.Fatal().Msg("Battery manager error")
+ return batteryStruct{}, fmt.Errorf("battery manager error")
default:
- log.Fatal().Msg("Unknown error occurred")
+ return batteryStruct{}, fmt.Errorf("unknown error occurred")
}
//// time to empty
@@ -198,9 +215,9 @@ func getBatteryInfo() batteryStruct {
case C.BATTERY_TIME_TO_EMPTY_NO_TIME_TO_EMPTY:
batteryTimeToEmpty = 0
case C.BATTERY_TIME_TO_EMPTY_MANAGER_ERROR:
- log.Fatal().Msg("Battery manager error")
+ return batteryStruct{}, fmt.Errorf("battery manager error")
default:
- log.Fatal().Msg("Unknown error occurred")
+ return batteryStruct{}, fmt.Errorf("unknown error occurred")
}
// return
@@ -210,7 +227,7 @@ func getBatteryInfo() batteryStruct {
BatteryDesignCapacity: batteryDesignCapacity,
BatteryCycleCount: batteryCycleCount,
BatteryTimeToEmpty: batteryTimeToEmpty,
- }
+ }, nil
}
// ---- utils ----
internal/get/volumes.go
@@ -9,11 +9,10 @@ import (
human "github.com/dustin/go-humanize"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/kahnwong/swissknife/configs/color"
- "github.com/rs/zerolog/log"
"github.com/shirou/gopsutil/v4/disk"
)
-func listVolumes() {
+func Volumes() error {
// ref: <https://stackoverflow.com/a/64141403>
// layout inspired by `duf`
@@ -25,7 +24,7 @@ func listVolumes() {
// get volumes info
partitions, err := disk.Partitions(false)
if err != nil {
- log.Fatal().Msg("Error getting partitions info")
+ return fmt.Errorf("error getting partitions info: %w", err)
}
for _, partition := range partitions {
@@ -41,7 +40,7 @@ func listVolumes() {
device := partition.Mountpoint
stats, err := disk.Usage(device)
if err != nil {
- log.Fatal().Msg("Error getting disk info")
+ return fmt.Errorf("error getting disk info: %w", err)
}
if stats.Total == 0 {
@@ -94,8 +93,5 @@ func listVolumes() {
// render
t.Render()
-}
-
-func Volumes() {
- listVolumes()
+ return nil
}
internal/shouldideploytoday/shouldideploytoday.go
@@ -7,7 +7,6 @@ import (
"github.com/carlmjohnson/requests"
"github.com/kahnwong/swissknife/configs/color"
- "github.com/rs/zerolog/log"
)
type ShouldIDeploy struct {
@@ -17,7 +16,7 @@ type ShouldIDeploy struct {
Message string `json:"message"`
}
-func getResponse() ShouldIDeploy {
+func getResponse() (ShouldIDeploy, error) {
url := "https://shouldideploy.today"
var response ShouldIDeploy
@@ -30,18 +29,22 @@ func getResponse() ShouldIDeploy {
Fetch(context.Background())
if err != nil {
- log.Fatal().Msg("Error calling ShouldIDeploy API")
+ return ShouldIDeploy{}, fmt.Errorf("error calling ShouldIDeploy API: %w", err)
}
- return response
+ return response, nil
}
-func ShouldIDeployToday() {
- response := getResponse()
+func ShouldIDeployToday() error {
+ response, err := getResponse()
+ if err != nil {
+ return err
+ }
if response.ShouldIDeploy {
fmt.Printf("%s\n", color.Green(response.Message))
} else if !response.ShouldIDeploy {
fmt.Printf("%s\n", color.Red(response.Message))
}
+ return nil
}
internal/speedtest/speedtest.go
@@ -6,11 +6,10 @@ import (
"time"
"github.com/kahnwong/swissknife/configs/color"
- "github.com/rs/zerolog/log"
"github.com/showwin/speedtest-go/speedtest"
)
-func SpeedTest() {
+func SpeedTest() error {
// ref: <https://github.com/showwin/speedtest-go#api-usage>
var speedtestClient = speedtest.New()
serverList, _ := speedtestClient.FetchServers()
@@ -24,6 +23,9 @@ func SpeedTest() {
tests := make(chan struct{})
ctx, cancel := context.WithCancel(context.Background())
+ // Store error from goroutine
+ errChan := make(chan error, 1)
+
// -- background progress report -- //
go func(ctx context.Context) {
for {
@@ -45,29 +47,41 @@ func SpeedTest() {
fmt.Print("Pinging")
err := s.PingTest(nil)
if err != nil {
- log.Fatal().Msg("Error pinging server")
+ errChan <- fmt.Errorf("error pinging server: %w", err)
+ cancel()
+ return
}
fmt.Printf("\033[2K\r") // clear line
fmt.Print("Downloading")
err = s.DownloadTest()
if err != nil {
- log.Fatal().Msg("Error testing download speed")
+ errChan <- fmt.Errorf("error testing download speed: %w", err)
+ cancel()
+ return
}
fmt.Printf("\033[2K\r") // clear line
fmt.Print("Uploading")
err = s.UploadTest()
if err != nil {
- log.Fatal().Msg("Error testing upload speed")
+ errChan <- fmt.Errorf("error testing upload speed: %w", err)
+ cancel()
+ return
}
fmt.Printf("\033[2K\r") // clear line
}
+ errChan <- nil
cancel()
}()
<-tests
+ // Check for errors
+ if err := <-errChan; err != nil {
+ return err
+ }
+
// print results
fmt.Print(
fmt.Sprintf("%s: %s\n", color.Green("Latency"), s.Latency.Truncate(time.Millisecond)) +
@@ -76,4 +90,5 @@ func SpeedTest() {
)
s.Context.Reset()
+ return nil
}
internal/stopwatch/stopwatch.go
@@ -3,7 +3,6 @@ package stopwatch
import (
"fmt"
- "os"
"time"
"github.com/charmbracelet/bubbles/help"
@@ -71,7 +70,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, cmd
}
-func Stopwatch() {
+func Stopwatch() error {
m := model{
stopwatch: stopwatch.NewWithInterval(time.Millisecond),
keymap: keymap{
@@ -98,7 +97,7 @@ func Stopwatch() {
m.keymap.start.SetEnabled(false)
if _, err := tea.NewProgram(m).Run(); err != nil {
- fmt.Println("Oh no, it didn't work:", err)
- os.Exit(1)
+ return fmt.Errorf("stopwatch error: %w", err)
}
+ return nil
}
internal/timer/timer.go
@@ -3,13 +3,11 @@ package timer
import (
"fmt"
- "os"
"strings"
"time"
"github.com/charmbracelet/bubbles/progress"
"github.com/charmbracelet/lipgloss"
- "github.com/rs/zerolog/log"
tea "github.com/charmbracelet/bubbletea"
)
@@ -113,24 +111,23 @@ func NewModel() Model {
}
}
-func Timer(args []string) {
+func Timer(args []string) error {
// check if input exists
if len(args) == 0 {
- fmt.Println("Please provide a duration")
- os.Exit(1)
+ return fmt.Errorf("please provide a duration")
}
// parse input
duration, err := time.ParseDuration(args[0])
if err != nil {
- log.Fatal().Msg("Error parsing duration")
+ return fmt.Errorf("error parsing duration: %w", err)
}
// timer
m := NewModel()
m.duration = duration
- _, err = tea.NewProgram(&m).Run()
- if err != nil {
- log.Fatal().Err(err)
+ if _, err = tea.NewProgram(&m).Run(); err != nil {
+ return fmt.Errorf("error running timer: %w", err)
}
+ return nil
}
internal/utils/args.go
@@ -10,8 +10,8 @@ import (
func setValueFromArgsOrClipboard(args []string, validator func(string) bool, errorMsg string, allowEmpty bool) string {
var value string
if len(args) == 0 {
- clipboardValue := ReadFromClipboard()
- if clipboardValue != "" && validator(clipboardValue) {
+ clipboardValue, err := ReadFromClipboard()
+ if err == nil && clipboardValue != "" && validator(clipboardValue) {
value = strings.TrimSpace(clipboardValue)
}
}
internal/utils/clipboard.go
@@ -1,44 +1,47 @@
package utils
import (
+ "fmt"
"os"
"github.com/atotto/clipboard"
- "github.com/rs/zerolog/log"
clipboardImage "github.com/skanehira/clipboard-image/v2"
)
-func WriteToClipboard(text string) {
- err := clipboard.WriteAll(text)
- if err != nil {
- log.Error().Msg("Failed to write to clipboard")
+func WriteToClipboard(text string) error {
+ if err := clipboard.WriteAll(text); err != nil {
+ return fmt.Errorf("failed to write to clipboard: %w", err)
}
+ return nil
}
-func WriteToClipboardImage(bytes []byte) {
+func WriteToClipboardImage(bytes []byte) error {
tempFilename := "/tmp/qr-image.png"
- err := os.WriteFile(tempFilename, bytes, 0644)
- if err != nil {
- log.Fatal().Msg("Failed to write temp image for clipboard")
+ if err := os.WriteFile(tempFilename, bytes, 0644); err != nil {
+ return fmt.Errorf("failed to write temp image for clipboard: %w", err)
}
f, err := os.Open(tempFilename)
if err != nil {
- log.Fatal().Msg("Failed to open temp image for clipboard")
+ return fmt.Errorf("failed to open temp image for clipboard: %w", err)
}
defer func(f *os.File) {
- err := f.Close()
- if err != nil {
- log.Error().Msg("Error opening temp image for clipboard")
+ if err := f.Close(); err != nil {
+ // Log this error but don't override the main return error
+ fmt.Fprintf(os.Stderr, "warning: error closing temp image file: %v\n", err)
}
}(f)
if err = clipboardImage.Write(f); err != nil {
- log.Fatal().Msg("Failed to copy to clipboard")
+ return fmt.Errorf("failed to copy to clipboard: %w", err)
}
+ return nil
}
-func ReadFromClipboard() string {
- text, _ := clipboard.ReadAll()
- return text
+func ReadFromClipboard() (string, error) {
+ text, err := clipboard.ReadAll()
+ if err != nil {
+ return "", fmt.Errorf("failed to read from clipboard: %w", err)
+ }
+ return text, nil
}
go.sum
@@ -91,10 +91,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
-github.com/projectdiscovery/wappalyzergo v0.2.62 h1:SMZ70bLCj6jHnFgjanuiaQpqUXY6aiEC3YoM0ZSvYes=
-github.com/projectdiscovery/wappalyzergo v0.2.62/go.mod h1:8FtSVcmPRZU0g1euBpdSYEBHIvB7Zz9MOb754ZqZmfU=
-github.com/projectdiscovery/wappalyzergo v0.2.63 h1:iSIU2rfPkHcpBSTol7S3PqgfBXn+JD56s4BsVEGxJ+o=
-github.com/projectdiscovery/wappalyzergo v0.2.63/go.mod h1:8FtSVcmPRZU0g1euBpdSYEBHIvB7Zz9MOb754ZqZmfU=
github.com/projectdiscovery/wappalyzergo v0.2.64 h1:Y55sb5qUdFvMtR81m1hr54PdGh/hZ4XtuGPdCFAirEk=
github.com/projectdiscovery/wappalyzergo v0.2.64/go.mod h1:8FtSVcmPRZU0g1euBpdSYEBHIvB7Zz9MOb754ZqZmfU=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=