diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..f899017 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,29 @@ +name: Continuous Integration + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + name: Build and Test + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.21.5 + + - name: Build + run: go build -v ./... + + - name: Run tests + run: go test -v ./... diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..49c0d6b --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,29 @@ +name: Release + +on: + push: + tags: + - '*' + workflow_dispatch: + inputs: + version: + description: 'Version (e.g., 1.0.0)' + required: true + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.21.5 + + - name: Run GoReleaser + if: github.event_name == 'workflow_dispatch' + run: goreleaser release --rm-dist --release-notes "Release version ${{ github.event.inputs.version }}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 3b735ec..1ef478b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,10 @@ -# If you prefer the allow list template instead of the deny list, see community template: -# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore -# # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib +osmon # Test binary, built with `go test -c` *.test @@ -19,3 +17,24 @@ # Go workspace file go.work + +# Ignore JetBrains IntelliJ IDEA project files +.idea/ + +# Ignore Visual Studio Code project files +.vscode/ + +# Ignore temporary directories and files +.idea/ +.vscode/ +.idea_modules/ +.idea_caches/ +out/ +.idea.vim/ +.idea_workspace/ + +# Ignore project files generated by IntelliJ IDEA +*.iml +*.ipr +*.iws +*.idea_modules \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..e3e4be8 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,54 @@ +# .goreleaser.yml +# This is the GoReleaser configuration file which defines how to build, package, and release your application. + +project_name: osmon # The name of your project. + +# Build section defines how your application should be built. +builds: + - id: osmon + main: . # Path to the main source file or directory of your program. + binary: osmon # Name of the compiled binary. + goos: # List of target operating systems. + - linux + - darwin + goarch: # List of target architectures. + - amd64 + - arm64 + env: # Environment variables to be set during the build process. + - CGO_ENABLED=0 + ldflags: # Flags to pass to the go compiler. + - -s -w + +# Archives section defines how to package your binary. +archives: + - id: archive + builds: + - osmon + format: tar.gz # Format for Linux binaries. + format_overrides: + - goos: darwin + format: zip # Format for macOS binaries. + wrap_in_directory: true + files: # Additional files to include in the archive. + - LICENSE + - README.md + +# Release section configures how to handle GitHub releases. +release: + github: + owner: debek + name: osmon + draft: true # Indicates whether the release should be a draft. + prerelease: auto # Automatically set whether the release is a prerelease based on the tag. + +# Checksum section generates checksums of your binaries. +checksum: + name_template: 'checksums.txt' + +# Snapshot section configures the creation of snapshots. +snapshot: + name_template: "{{ .Tag }}-next" # Template for naming snapshot releases. + +# Changelog section configures how the changelog should be handled. +changelog: + skip: false # Indicates whether to skip generating the changelog. diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/aws.xml b/.idea/aws.xml new file mode 100644 index 0000000..ec328d0 --- /dev/null +++ b/.idea/aws.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..919ce1f --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..639900d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..1867f5f --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/osmon.iml b/.idea/osmon.iml new file mode 100644 index 0000000..25ed3f6 --- /dev/null +++ b/.idea/osmon.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md new file mode 100644 index 0000000..eb39c02 --- /dev/null +++ b/CONTRIBUTE.md @@ -0,0 +1,32 @@ +# Contributing to osmon + +Thank you for your interest in contributing to osmon! All contributions, whether code, bug reports, or feature suggestions, are greatly appreciated. + +## How Can I Contribute? + +### Reporting Bugs and Suggesting Features +- If you encounter a bug or have an idea for a new feature, please create an issue in our GitHub repository. +- Describe the bug or feature as detailed as possible to help us understand your intentions. + +### Creating Pull Requests +- Fork the repository. +- Create a new branch for your changes (`git checkout -b my-new-feature`). +- Make your changes in the code. +- Ensure code is well-formatted and follows the project's coding standards. +- Commit your changes (`git commit -am 'Add some feature'`). +- Push to your fork (`git push origin my-new-feature`). +- Submit a Pull Request to our repository. + +### Guidelines +- Make sure your changes are well documented. +- Add tests for new features. +- Follow the established coding standards. + +## Coding and Testing +- We encourage clear and understandable coding. +- Unit tests are an essential part of development. Try to maintain high test coverage. + +## Questions? +If you have any questions or concerns, don't hesitate to ask. We are here to help! + +Thank you for your contributions to the osmon project! diff --git a/README.md b/README.md new file mode 100644 index 0000000..2ba1b90 --- /dev/null +++ b/README.md @@ -0,0 +1,107 @@ +# osmon + +**osmon** is a command-line tool written in Go that provides quick access to basic system information. With **osmon**, you can easily access details such as your host's IP address, system load, memory usage, logged-in users, and much more. + +## Features + +- Displaying your host's name and IP address. +- Showing the current system load averages. +- Overview of memory usage statistics, including total memory, used memory, and available memory. +- Listing logged-in users and their respective terminals. + +[//]: # (## Demo Video) + +[//]: # () +[//]: # (Check out this demo of **osmon** in action:) + +[//]: # () +[//]: # ([![Demo Video](http://img.youtube.com/vi/ID_TWOJEGO_FILMU/0.jpg)](http://www.youtube.com/watch?v=ID_TWOJEGO_FILMU "osmon Demo")) + +## Applications + +### Monitoring After SSH Login + +**osmon** is an ideal tool for a quick overview of a server's status upon SSH login. You can configure your shell to automatically run `osmon` every time you log in, providing an immediate snapshot of the server's status. + +Example: + +- Configure your `.bashrc` or `.zshrc` to run `osmon` upon every login, allowing you to immediately assess the state of the server. + +### Debugging + +**osmon** can be an invaluable tool for debugging performance issues. By running `osmon`, you can quickly identify if high system load is caused by memory, CPU, or network activity. + +Example: + +- If you notice that your application is running slower than usual, you can use `osmon` to check if the system is under heavy load. + +### Installation + +You can install **osmon** using the Go tool: + + +`go install github.com/yourusername/osmon@latest` + +After installation, add the Go binary path to your system's PATH to access the `osmon` command from anywhere: + + +`export PATH=$PATH:~/go/bin` + +### Usage + +**osmon** offers various command-line options for tailored usage. Below are the available flags and their descriptions: + +- `-h`: Display help information. \[...\] +- `-v`: Display the version of the application. \[...\] +- `-i` or `--interval`: Set the interval for refreshing the display in seconds. \[...\] + +#### Basic Usage + +To simply display the system information, run the `osmon` command without any flags: + + +`osmon` + +#### Continuous Monitoring + +If you want to continuously monitor your system's status with a specific refresh interval, use the `-i` flag with your desired interval in seconds: + + +```bash +osmon -i 5 +``` + +### Binary Release Installation (Optional) + +If you release a binary version of **osmon**, users can follow these steps: + +1. Download the binary from the "Releases" section of your GitHub repository. + +2. Move the binary to a directory in your PATH, for example, `/usr/local/bin`: + + +1. `mv osmon /usr/local/bin/osmon chmod +x /usr/local/bin/osmon` + + +## Compatibility + +- **osmon** is compatible with Linux and macOS systems. + +## Contributing + +If you're interested in contributing to the osmon project, please check our [contribution guidelines](https://chat.openai.com/g/g-yxXXjJ1If-it-gpt4/c/CONTRIBUTE.md). All contributions, from bug reporting to new feature suggestions, are highly appreciated. + +## License + +This project is available under the [MIT License](https://chat.openai.com/g/g-yxXXjJ1If-it-gpt4/c/0c1a0ed0-de56-4ded-8159-e19be6cae7bc). + +## Acknowledgments + +Special thanks to the Go community for creating excellent libraries like `github.com/shirou/gopsutil`, which make it easier to gather system information. + +## TO DO + +- **Information about IOBS and IOPS**: Include details about read and write operations, and possibly any limits. +- **Outgoing and Incoming Connections**: Display connections in kilobits or megabits. +- **Number of Connections**: Show the count of incoming and outgoing connections. +- **Kernel Version**: Add information about the kernel version. \ No newline at end of file diff --git a/display.go b/display.go new file mode 100644 index 0000000..14eb950 --- /dev/null +++ b/display.go @@ -0,0 +1,162 @@ +package main + +import ( + "fmt" + "github.com/shirou/gopsutil/disk" + "github.com/shirou/gopsutil/load" + "github.com/shirou/gopsutil/mem" + "net" + "os" + "os/exec" + "os/user" + "runtime" + "strings" + "time" +) + +func displaySystemInfoInInterval(interval time.Duration) { + for { + clearScreen() // Pozostawione w tej funkcji + displaySystemInfo() + time.Sleep(interval) + } +} + +func displaySystemInfo() { + // Usunięto wywołanie clearScreen() stąd + currentUser, err := user.Current() + if err != nil { + fmt.Printf("Failed to get current user: %s\n", err) + return + } + userName := currentUser.Username + + currentPTS, err := getCurrentPTS() + if err != nil { + fmt.Printf("Failed to get current PTS: %s\n", err) + return + } + + activeUsers, err := getActiveUsers() + if err != nil { + fmt.Printf("Error getting active users: %s\n", err) + return + } + + hostName, err := os.Hostname() + if err != nil { + fmt.Printf("Failed to get hostname: %s\n", err) + return + } + + conn, err := net.Dial("udp", "8.8.8.8:80") + if err != nil { + fmt.Printf("Failed to dial: %s\n", err) + return + } + localAddr := conn.LocalAddr().(*net.UDPAddr) + ip := localAddr.IP.String() + conn.Close() + + uptime, err := getUptime() + if err != nil { + fmt.Printf("Failed to get uptime: %s\n", err) + return + } + upDays := uptime / (24 * time.Hour) + upHours := (uptime % (24 * time.Hour)) / time.Hour + upMins := (uptime % time.Hour) / time.Minute + upSecs := (uptime % time.Minute) / time.Second + + vmStat, err := mem.VirtualMemory() + if err != nil { + fmt.Printf("Failed to get virtual memory info: %s\n", err) + return + } + memoryUsed := vmStat.Used / 1024 / 1024 + memoryTotal := vmStat.Total / 1024 / 1024 + memoryAvailable := vmStat.Available / 1024 / 1024 + usedMemoryPercent := (float64(memoryUsed) / float64(memoryTotal)) * 100 + freeMemoryPercent := 100 - usedMemoryPercent + + cpuInfo, err := getCPUInfo() + if err != nil { + fmt.Printf("Failed to get CPU info: %s\n", err) + return + } + + cpuCores, err := getCPUCores() + if err != nil { + fmt.Printf("Failed to get CPU cores: %s\n", err) + return + } + + loadAvg, err := load.Avg() + if err != nil { + fmt.Printf("Failed to get load average: %s\n", err) + return + } + + diskStat, err := disk.Usage("/") + if err != nil { + fmt.Printf("Failed to get disk usage: %s\n", err) + return + } + + memoryUsedGB := float64(memoryUsed) / 1024 + memoryTotalGB := float64(memoryTotal) / 1024 + memoryAvailableGB := float64(memoryAvailable) / 1024 + + diskFree := float64(diskStat.Free) / 1024 / 1024 / 1024 + diskTotal := float64(diskStat.Total) / 1024 / 1024 / 1024 + diskUsed := diskTotal - diskFree + freeDiskPercent := (diskFree / diskTotal) * 100 + + const ( + resetColor = "\033[0m" + bold = "\033[1m" + cyan = "\033[36m" + ) + fmt.Println(cyan + "======================================OSMON======================================" + resetColor) + fmt.Printf("%s%s - CPU.................:%s %s (%d cores)%s\n", bold, cyan, resetColor, cpuInfo, cpuCores, resetColor) + fmt.Printf("%s%s - Load................:%s %.2f %.2f %.2f%s\n", bold, cyan, resetColor, loadAvg.Load1, loadAvg.Load5, loadAvg.Load15, resetColor) + fmt.Printf("%s%s - Memory..............:%s %.2f GB / %.2f GB (%.2f GB remaining, %.2f%% free)%s\n", + bold, cyan, resetColor, memoryUsedGB, memoryTotalGB, memoryAvailableGB, freeMemoryPercent, resetColor) + fmt.Printf("%s%s - Disk space /........:%s %.2f GB / %.2f GB (%.2f GB remaining, %.2f%% free)%s\n", bold, cyan, resetColor, diskUsed, diskTotal, diskFree, freeDiskPercent, resetColor) + fmt.Printf("%s%s - Processes...........:%s %d running%s\n", bold, cyan, resetColor, getProcessCount(), resetColor) + fmt.Printf("%s%s - System uptime.......:%s %d days %d hours %d minutes %d seconds%s\n", bold, cyan, resetColor, upDays, upHours, upMins, upSecs, resetColor) + fmt.Printf("%s%s - Hostname / IP.......:%s %s / %s%s\n", bold, cyan, resetColor, hostName, ip, resetColor) + fmt.Printf("%s%s - Release.............:%s %s%s\n", bold, cyan, resetColor, getOSRelease(), resetColor) + fmt.Printf("%s%s - Current user........:%s [%s: %s]%s\n", bold, cyan, resetColor, currentPTS, userName, resetColor) + if runtime.GOOS == "linux" { + fmt.Printf("%s%s - Active users........:%s ", bold, cyan, resetColor) + for terminal, users := range activeUsers { + fmt.Printf("[%s: %s] ", terminal, strings.Join(users, ", ")) + } + fmt.Println() + } + fmt.Println(cyan + "=================================================================================" + resetColor) +} + +func clearScreen() { + if runtime.GOOS == "windows" { + cmd := exec.Command("cmd", "/c", "cls") + cmd.Stdout = os.Stdout + cmd.Run() + } else { + fmt.Print("\033[H\033[2J") + } +} + +func displayHelp() { + fmt.Println("OSInfo - Display system information with refresh interval") + fmt.Println("\nUsage:") + fmt.Println(" ./osmon Display system information once") + fmt.Println(" ./osmon -i Refresh system information every seconds") + fmt.Println(" ./osmon -h/--help Display this help information") + fmt.Println(" ./osmon -v/--version Display the version of the application") + fmt.Println("\nFlags:") + fmt.Println(" -i, --interval Set interval for refreshing the display in seconds") + fmt.Println(" -h, --help Display help information") + fmt.Println(" -v, --version Display the version of the application") +} diff --git a/flags.go b/flags.go new file mode 100644 index 0000000..ac3b412 --- /dev/null +++ b/flags.go @@ -0,0 +1,42 @@ +package main + +import ( + "flag" + "fmt" + "strconv" +) + +type intervalFlag struct { + set bool + value int +} + +// Set jest metodą wywoływaną przez pakiet flag, gdy flaga zostanie ustawiona. +func (f *intervalFlag) Set(s string) error { + f.set = true + var err error + f.value, err = strconv.Atoi(s) + if err != nil { + return fmt.Errorf("invalid format for interval: %v", err) + } + return nil +} + +// String zwraca reprezentację string flagi. +func (f *intervalFlag) String() string { + if !f.set { + return "not set" + } + return fmt.Sprintf("%d", f.value) +} + +func DefineFlags() (intervalFlag, *bool, *bool) { + var interval intervalFlag + var helpFlag = flag.Bool("h", false, "Display help information (shorthand)") + var versionFlag = flag.Bool("v", false, "Display the version of the application (shorthand)") + + flag.Var(&interval, "i", "Set interval for refreshing the display in seconds (shorthand)") + flag.Var(&interval, "interval", "Set interval for refreshing the display in seconds") + + return interval, helpFlag, versionFlag +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..dabab06 --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module github.com/debek/osmon + +go 1.21.5 + +require github.com/shirou/gopsutil v3.21.11+incompatible + +require ( + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/stretchr/testify v1.8.4 // indirect + github.com/tklauser/go-sysconf v0.3.13 // indirect + github.com/tklauser/numcpus v0.7.0 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + golang.org/x/sys v0.15.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..6554970 --- /dev/null +++ b/go.sum @@ -0,0 +1,21 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= +github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= +github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= +github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= +github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..c6129a7 --- /dev/null +++ b/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "flag" + "fmt" + "os" + "time" +) + +func main() { + var interval intervalFlag + var helpFlag bool + var versionFlag bool + + flag.Usage = displayHelp + flag.Var(&interval, "i", "Set interval for refreshing the display in seconds (shorthand)") + flag.Var(&interval, "interval", "Set interval for refreshing the display in seconds") + flag.BoolVar(&helpFlag, "h", false, "Display help information (shorthand)") + flag.BoolVar(&helpFlag, "help", false, "Display help information") + flag.BoolVar(&versionFlag, "v", false, "Display the version of the application (shorthand)") + flag.BoolVar(&versionFlag, "version", false, "Display the version of the application") + flag.Parse() + + if versionFlag { + appVersion := os.Getenv("APP_VERSION") + if appVersion == "" { + appVersion = "development" + } + fmt.Printf("OSInfo version %s\n", appVersion) + os.Exit(0) + } + + if helpFlag { + displayHelp() + os.Exit(0) + } + + if interval.set { + displaySystemInfoInInterval(time.Duration(interval.value) * time.Second) + } else { + displaySystemInfo() + } +} diff --git a/osmon b/osmon new file mode 100755 index 0000000..7b4507e Binary files /dev/null and b/osmon differ diff --git a/system_info.go b/system_info.go new file mode 100644 index 0000000..4923b28 --- /dev/null +++ b/system_info.go @@ -0,0 +1,120 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "runtime" + "strconv" + "strings" + "time" +) + +func getOSRelease() string { + if runtime.GOOS == "darwin" { + out, err := exec.Command("sw_vers", "-productVersion").Output() + if err != nil { + return "Unknown" + } + return "macOS " + strings.TrimSpace(string(out)) + } + if runtime.GOOS == "linux" { + file, err := os.Open("/etc/os-release") + if err != nil { + return "Unknown" + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "PRETTY_NAME=") { + return strings.Trim(line[13:], "\"") + } + } + } + return "Unknown" +} + +func getCPUInfo() (string, error) { + if runtime.GOOS == "darwin" { + out, err := exec.Command("sysctl", "-n", "machdep.cpu.brand_string").Output() + if err != nil { + return "", err + } + return strings.TrimSpace(string(out)), nil + } + if runtime.GOOS == "linux" { + out, err := exec.Command("cat", "/proc/cpuinfo").Output() + if err != nil { + return "", err + } + return parseLinuxCPUInfo(string(out)), nil + } + return "", fmt.Errorf("unsupported platform") +} + +func parseLinuxCPUInfo(info string) string { + lines := strings.Split(info, "\n") + for _, line := range lines { + if strings.HasPrefix(line, "model name") { + return strings.TrimSpace(strings.Split(line, ":")[1]) + } + } + return "Unknown" +} + +func getCPUCores() (int, error) { + if runtime.GOOS == "darwin" { + out, err := exec.Command("sysctl", "-n", "hw.ncpu").Output() + if err != nil { + return 0, err + } + cores, err := strconv.Atoi(strings.TrimSpace(string(out))) + if err != nil { + return 0, err + } + return cores, nil + } + if runtime.GOOS == "linux" { + out, err := exec.Command("grep", "-c", "processor", "/proc/cpuinfo").Output() + if err != nil { + return 0, err + } + cores, err := strconv.Atoi(strings.TrimSpace(string(out))) + if err != nil { + return 0, err + } + return cores, nil + } + return 0, fmt.Errorf("unsupported platform") +} + +func getUptime() (time.Duration, error) { + if runtime.GOOS == "darwin" { + out, err := exec.Command("sysctl", "-n", "kern.boottime").Output() + if err != nil { + return 0, err + } + uptimeString := strings.Split(strings.Split(string(out), "=")[1], ",")[0] + uptimeSec, err := strconv.ParseInt(strings.TrimSpace(uptimeString), 10, 64) + if err != nil { + return 0, err + } + return time.Since(time.Unix(uptimeSec, 0)), nil + } + if runtime.GOOS == "linux" { + out, err := exec.Command("cat", "/proc/uptime").Output() + if err != nil { + return 0, err + } + uptimeString := strings.Fields(string(out))[0] + uptimeSec, err := strconv.ParseFloat(uptimeString, 64) + if err != nil { + return 0, err + } + return time.Duration(uptimeSec) * time.Second, nil + } + return 0, fmt.Errorf("unsupported platform") +} diff --git a/user_info.go b/user_info.go new file mode 100644 index 0000000..bad3f2d --- /dev/null +++ b/user_info.go @@ -0,0 +1,91 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "regexp" + "runtime" + "strings" +) + +func getActiveUsers() (map[string][]string, error) { + out, err := exec.Command("ps", "aux").Output() + if err != nil { + return nil, err + } + + activeUsers := make(map[string][]string) + lines := strings.Split(string(out), "\n") + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) > 1 { + user := fields[0] + tty := fields[6] + if strings.HasPrefix(tty, "pts") { // Dodano sprawdzenie czy tty zaczyna się od "pts" + if _, found := activeUsers[tty]; !found { + activeUsers[tty] = []string{} + } + fullUserName, err := getFullUserName(user) + if err == nil { + user = fullUserName + } + if !contains(activeUsers[tty], user) { + activeUsers[tty] = append(activeUsers[tty], user) + } + } + } + } + return activeUsers, nil +} + +func getFullUserName(shortName string) (string, error) { + file, err := os.Open("/etc/passwd") + if err != nil { + return shortName, err + } + defer file.Close() + + trimmedShortName := strings.TrimSuffix(shortName, "+") + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + parts := strings.Split(line, ":") + if len(parts) > 0 { + userName := parts[0] + if strings.HasPrefix(userName, trimmedShortName) { + return userName, nil + } + } + } + if err := scanner.Err(); err != nil { + return shortName, err + } + return shortName, nil +} + +func getCurrentPTS() (string, error) { + if runtime.GOOS == "linux" { + tty, err := os.Readlink("/proc/self/fd/0") + if err != nil { + return "", err + } + re := regexp.MustCompile(`(pts/\d+|ttyS?\d*)`) + pts := re.FindString(tty) + if pts == "" { + return "", fmt.Errorf("could not parse PTS") + } + return pts, nil + } else if runtime.GOOS == "darwin" { + ttyNumber := os.Getenv("_P9K_SSH_TTY") + re := regexp.MustCompile(`(ttys?\d*)`) + pts := re.FindString(ttyNumber) + if ttyNumber == "" { + return "not available", nil + } + return pts, nil + } + return "", fmt.Errorf("unsupported platform") +} diff --git a/utility.go b/utility.go new file mode 100644 index 0000000..bc3dec5 --- /dev/null +++ b/utility.go @@ -0,0 +1,20 @@ +package main + +import "github.com/shirou/gopsutil/process" + +func contains(slice []string, item string) bool { + for _, s := range slice { + if s == item { + return true + } + } + return false +} + +func getProcessCount() int { + pids, err := process.Pids() + if err != nil { + return 0 + } + return len(pids) +}