diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000..5ed1c4d --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,50 @@ +# CodeRabbit Configuration +# https://docs.coderabbit.ai/guides/configure-coderabbit + +language: en-US + +reviews: + auto_review: + enabled: true + drafts: false + base_branches: + - main + - master + - develop + + request_changes_workflow: false + high_level_summary: true + poem: false + review_status: true + collapse_walkthrough: false + + path_filters: + - "!**/*.md" + - "!**/*.txt" + - "!**/testdata/**" + + path_instructions: + - path: "**/*.go" + instructions: | + - Follow Go best practices and idiomatic patterns + - Ensure proper error handling + - Check for goroutine leaks and race conditions + - Verify nil pointer checks + - Ensure proper resource cleanup (defer statements) + + - path: "**/*_test.go" + instructions: | + - Verify test coverage is adequate + - Check for table-driven tests where appropriate + - Ensure proper test cleanup + - Validate edge cases are tested + +chat: + auto_reply: true + +knowledge_base: + learnings: + scope: auto + +early_access: false +enable_free_tier: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..890dec7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out +coverage.html + +# Dependency directories (remove the comment below to include it) +vendor/ + +# Go workspace file +go.work +go.work.sum + +# Build artifacts +dist/ +build/ +bin/ +*.tar.gz +*.zip +mining + +# IDE specific files +.idea/ +.vscode/ +*.swp +*.swo +*~ +.DS_Store + +# Temporary files +tmp/ +temp/ +*.tmp + +# Environment files +.env +.env.local +.env.*.local + +# goreleaser +.goreleaser.yaml.bak diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..2ff3b6f --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,100 @@ +# GoReleaser configuration +# https://goreleaser.com + +version: 2 + +before: + hooks: + - go mod tidy + - go test ./... + +builds: + - id: mining-cli + main: ./cmd/mining + binary: mining + env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + goarch: + - amd64 + - arm64 + goarm: + - "7" + ldflags: + - -s -w + - -X github.com/Snider/Mining/pkg/mining.version={{.Version}} + - -X github.com/Snider/Mining/pkg/mining.commit={{.Commit}} + - -X github.com/Snider/Mining/pkg/mining.date={{.Date}} + +archives: + - id: mining + name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + format_overrides: + - goos: windows + format: zip + files: + - README.md + - LICENSE + +checksum: + name_template: 'checksums.txt' + +snapshot: + version_template: "{{ incpatch .Version }}-next" + +changelog: + sort: asc + use: github + filters: + exclude: + - '^docs:' + - '^test:' + - '^chore:' + - 'merge conflict' + - Merge pull request + - Merge remote-tracking branch + - Merge branch + groups: + - title: 'New Features' + regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$' + order: 0 + - title: 'Bug fixes' + regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$' + order: 1 + - title: 'Performance Improvements' + regexp: '^.*?perf(\([[:word:]]+\))??!?:.+$' + order: 2 + - title: 'Documentation updates' + regexp: '^.*?docs(\([[:word:]]+\))??!?:.+$' + order: 3 + - title: Other work + order: 999 + +release: + github: + owner: Snider + name: Mining + draft: false + prerelease: auto + mode: replace + header: | + ## Mining Release {{ .Tag }} + + **Full Changelog**: https://github.com/Snider/Mining/compare/{{ .PreviousTag }}...{{ .Tag }} + +# Generate SBOM (Software Bill of Materials) +sboms: + - artifacts: archive + +# Announce releases +announce: + skip: false diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..36a9328 --- /dev/null +++ b/LICENSE @@ -0,0 +1,267 @@ +European Union Public Licence V. 1.2 +EUPL © the European Union 2007, 2016 + +This European Union Public Licence (the 'EUPL') applies to the Work (as defined below) +which is provided under the terms of this Licence. Any use of the Work, other than as +authorised under this Licence is prohibited (to the extent such use is covered by a +right of the copyright holder of the Work). + +The Work is provided under the terms of this Licence when the Licensor (as defined +below) has placed the following notice immediately following the copyright notice for +the Work: + + Licensed under the EUPL + +or has expressed by any other means his willingness to license under the EUPL. + +1. Definitions + +In this Licence, the following terms have the following meaning: + +— 'The Licence': this Licence. +— 'The Original Work': the work or software distributed or communicated by the + Licensor under this Licence, available as Source Code and also as Executable Code as + the case may be. +— 'Derivative Works': the works or software that could be created by the Licensee, + based upon the Original Work or modifications thereof. This Licence does not define + the extent of modification or dependence on the Original Work required in order to + classify a work as a Derivative Work; this extent is determined by copyright law + applicable in the country mentioned in Article 15. +— 'The Work': the Original Work or its Derivative Works. +— 'The Source Code': the human-readable form of the Work which is the most convenient + for people to study and modify. +— 'The Executable Code': any code which has generally been compiled and which is meant + to be interpreted by a computer as a program. +— 'The Licensor': the natural or legal person that distributes or communicates the + Work under the Licence. +— 'Contributor(s)': any natural or legal person who modifies the Work under the + Licence, or otherwise contributes to the creation of a Derivative Work. +— 'The Licensee' or 'You': any natural or legal person who makes any usage of the Work + under the terms of the Licence. +— 'Distribution' or 'Communication': any act of selling, giving, lending, renting, + distributing, communicating, transmitting, or otherwise making available, online or + offline, copies of the Work or providing access to its essential functionalities at + the disposal of any other natural or legal person. + +2. Scope of the rights granted by the Licence + +The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, sublicensable +licence to do the following, for the duration of copyright vested in the Original Work: + +— use the Work in any circumstance and for all usage, +— reproduce the Work, +— modify the Work, and make Derivative Works based upon the Work, +— communicate to the public, including the right to make available or display the Work + or copies thereof to the public and perform publicly, as the case may be, the Work, +— distribute the Work or copies thereof, +— lend and rent the Work or copies thereof, +— sublicense rights in the Work or copies thereof. + +Those rights can be exercised on any media, supports and formats, whether now known or +later invented, as far as the applicable law permits so. + +In the countries where moral rights apply, the Licensor waives his right to exercise +his moral right to the extent allowed by law in order to make effective the licence of +the economic rights here above listed. + +The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to any +patents held by the Licensor, to the extent necessary to make use of the rights +granted on the Work under this Licence. + +3. Communication of the Source Code + +The Licensor may provide the Work either in its Source Code form, or as Executable +Code. If the Work is provided as Executable Code, the Licensor provides in addition a +machine-readable copy of the Source Code of the Work along with each copy of the Work +that the Licensor distributes or indicates, in a notice following the copyright notice +attached to the Work, a repository where the Source Code is easily and freely +accessible for as long as the Licensor continues to distribute or communicate the Work. + +4. Limitations on copyright + +Nothing in this Licence is intended to deprive the Licensee of the benefits from any +exception or limitation to the exclusive rights of the rights owners in the Work, of +the exhaustion of those rights or of other applicable limitations thereto. + +5. Obligations of the Licensee + +The grant of the rights mentioned above is subject to some restrictions and obligations +imposed on the Licensee. Those obligations are the following: + +Attribution right: The Licensee shall keep intact all copyright, patent or trademarks +notices and all notices that refer to the Licence and to the disclaimer of warranties. +The Licensee must include a copy of such notices and a copy of the Licence with every +copy of the Work he/she distributes or communicates. The Licensee must cause any +Derivative Work to carry prominent notices stating that the Work has been modified and +the date of modification. + +Copyleft clause: If the Licensee distributes or communicates copies of the Original +Works or Derivative Works, this Distribution or Communication will be done under the +terms of this Licence or of a later version of this Licence unless the Original Work is +expressly distributed only under this version of the Licence — for example by +communicating 'EUPL v. 1.2 only'. The Licensee (becoming Licensor) cannot offer or +impose any additional terms or conditions on the Work or Derivative Work that alter or +restrict the terms of the Licence. + +Compatibility clause: If the Licensee Distributes or Communicates Derivative Works or +copies thereof based upon both the Work and another work licensed under a Compatible +Licence, this Distribution or Communication can be done under the terms of this +Compatible Licence. For the sake of this clause, 'Compatible Licence' refers to the +licences listed in the appendix attached to this Licence. Should the Licensee's +obligations under the Compatible Licence conflict with his/her obligations under this +Licence, the obligations of the Compatible Licence shall prevail. + +Provision of Source Code: When distributing or communicating copies of the Work, the +Licensee will provide a machine-readable copy of the Source Code or indicate a +repository where this Source will be easily and freely available for as long as the +Licensee continues to distribute or communicate the Work. + +Legal Protection: This Licence does not grant permission to use the trade names, +trademarks, service marks, or names of the Licensor, except as required for reasonable +and customary use in describing the origin of the Work and reproducing the content of +the copyright notice. + +6. Chain of Authorship + +The original Licensor warrants that the copyright in the Original Work granted +hereunder is owned by him/her or licensed to him/her and that he/she has the power and +authority to grant the Licence. + +Each Contributor warrants that the copyright in the modifications he/she brings to the +Work are owned by him/her or licensed to him/her and that he/she has the power and +authority to grant the Licence. + +Each time You accept the Licence, the original Licensor and subsequent Contributors +grant You a licence to their contributions to the Work, under the terms of this Licence. + +7. Disclaimer of Warranty + +The Work is a work in progress, which is continuously improved by numerous Contributors. +It is not a finished work and may therefore contain defects or 'bugs' inherent to this +type of development. + +For the above reason, the Work is provided under the Licence on an 'as is' basis and +without warranties of any kind concerning the Work, including without limitation +merchantability, fitness for a particular purpose, absence of defects or errors, +accuracy, non-infringement of intellectual property rights other than copyright as +stated in Article 6 of this Licence. + +This disclaimer of warranty is an essential part of the Licence and a condition for the +grant of any rights to the Work. + +8. Disclaimer of Liability + +Except in the cases of wilful misconduct or damages directly caused to natural persons, +the Licensor will in no event be liable for any direct or indirect, material or moral, +damages of any kind, arising out of the Licence or of the use of the Work, including +without limitation, damages for loss of goodwill, work stoppage, computer failure or +malfunction, loss of data or any commercial damage, even if the Licensor has been +advised of the possibility of such damage. However, the Licensor will be liable under +statutory product liability laws as far such laws apply to the Work. + +9. Additional agreements + +While distributing the Work, You may choose to conclude an additional agreement, +defining obligations or services consistent with this Licence. However, if accepting +obligations, You may act only on your own behalf and on your sole responsibility, not +on behalf of the original Licensor or any other Contributor, and only if You agree to +indemnify, defend, and hold each Contributor harmless for any liability incurred by, or +claims asserted against such Contributor by the fact You have accepted any warranty or +additional liability. + +10. Acceptance of the Licence + +The provisions of this Licence can be accepted by clicking on an icon 'I agree' placed +under the bottom of a window displaying the text of this Licence or by affirming +consent in any other similar way, in accordance with the rules of applicable law. Clicking +on that icon indicates your clear and irrevocable acceptance of this Licence and all of +its terms and conditions. + +Similarly, you irrevocably accept this Licence and all of its terms and conditions by +exercising any rights granted to You by Article 2 of this Licence, such as the use of +the Work, the creation by You of a Derivative Work or the Distribution or Communication +by You of the Work or copies thereof. + +11. Information to the public + +In case of any Distribution or Communication of the Work by means of electronic +communication by You (for example, by offering to download the Work from a remote +location) the distribution channel or media (for example, a website) must at least +provide to the public the information requested by the applicable law regarding the +Licensor, the Licence and the way it may be accessible, concluded, stored and +reproduced by the Licensee. + +12. Termination of the Licence + +The Licence and the rights granted hereunder will terminate automatically upon any +breach by the Licensee of the terms of the Licence. + +Such a termination will not terminate the licences of any person who has received the +Work from the Licensee under the Licence, provided such persons remain in full +compliance with the Licence. + +13. Miscellaneous + +Without prejudice of Article 9 above, the Licence represents the complete agreement +between the Parties as to the Work. + +If any provision of the Licence is invalid or unenforceable under applicable law, this +will not affect the validity or enforceability of the Licence as a whole. Such provision +will be construed or reformed so as necessary to make it valid and enforceable. + +The European Commission may publish other linguistic versions or new versions of this +Licence or updated versions of the Appendix, so far this is required and reasonable, +without reducing the scope of the rights granted by the Licence. New versions of the +Licence will be published with a unique version number. + +All linguistic versions of this Licence, approved by the European Commission, have +identical value. Parties can take advantage of the linguistic version of their choice. + +14. Jurisdiction + +Without prejudice to specific agreement between parties, + +— any litigation resulting from the interpretation of this License, arising between the + European Union institutions, bodies, offices or agencies, as a Licensor, and any + Licensee, will be subject to the jurisdiction of the Court of Justice of the European + Union, as laid down in article 272 of the Treaty on the Functioning of the European + Union, + +— any litigation arising between other parties and resulting from the interpretation of + this License, will be subject to the exclusive jurisdiction of the competent court + where the Licensor resides or conducts its primary business. + +15. Applicable Law + +Without prejudice to specific agreement between parties, + +— this Licence shall be governed by the law of the European Union Member State where + the Licensor has his seat, resides or has his registered office, + +— this licence shall be governed by Belgian law if the Licensor has no seat, residence + or registered office inside a European Union Member State. + +Appendix + +'Compatible Licences' according to Article 5 EUPL are: + +— GNU General Public License (GPL) v. 2, v. 3 +— GNU Affero General Public License (AGPL) v. 3 +— Open Software License (OSL) v. 2.1, v. 3.0 +— Eclipse Public License (EPL) v. 1.0 +— CeCILL v. 2.0, v. 2.1 +— Mozilla Public Licence (MPL) v. 2 +— GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 +— Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for works + other than software +— European Union Public Licence (EUPL) v. 1.1, v. 1.2 +— Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong Reciprocity + (LiLiQ-R+) + +The European Commission may update this Appendix to later versions of the above +licences without producing a new version of the EUPL, as long as they provide the +rights granted in Article 2 of this Licence and protect the covered Source Code from +exclusive appropriation. + +All other changes or additions to this Appendix require the production of a new EUPL +version. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2d86046 --- /dev/null +++ b/Makefile @@ -0,0 +1,103 @@ +.PHONY: all build test clean install run demo help lint fmt vet + +# Variables +BINARY_NAME=mining +MAIN_PACKAGE=./cmd/mining +GO=go +GOFLAGS=-v + +all: test build + +# Build the CLI binary +build: + @echo "Building $(BINARY_NAME)..." + $(GO) build $(GOFLAGS) -o $(BINARY_NAME) $(MAIN_PACKAGE) + +# Build for multiple platforms +build-all: + @echo "Building for multiple platforms..." + GOOS=linux GOARCH=amd64 $(GO) build -o dist/$(BINARY_NAME)-linux-amd64 $(MAIN_PACKAGE) + GOOS=linux GOARCH=arm64 $(GO) build -o dist/$(BINARY_NAME)-linux-arm64 $(MAIN_PACKAGE) + GOOS=darwin GOARCH=amd64 $(GO) build -o dist/$(BINARY_NAME)-darwin-amd64 $(MAIN_PACKAGE) + GOOS=darwin GOARCH=arm64 $(GO) build -o dist/$(BINARY_NAME)-darwin-arm64 $(MAIN_PACKAGE) + GOOS=windows GOARCH=amd64 $(GO) build -o dist/$(BINARY_NAME)-windows-amd64.exe $(MAIN_PACKAGE) + +# Install the binary +install: + @echo "Installing $(BINARY_NAME)..." + $(GO) install $(MAIN_PACKAGE) + +# Run tests +test: + @echo "Running tests..." + $(GO) test -v -race -coverprofile=coverage.out ./... + +# Run tests with coverage report +coverage: test + @echo "Generating coverage report..." + $(GO) tool cover -html=coverage.out -o coverage.html + +# Run demo +demo: + @echo "Running demo..." + $(GO) run main.go + +# Run the CLI +run: build + ./$(BINARY_NAME) + +# Clean build artifacts +clean: + @echo "Cleaning..." + rm -f $(BINARY_NAME) + rm -rf dist/ + rm -f coverage.out coverage.html + $(GO) clean + +# Format code +fmt: + @echo "Formatting code..." + $(GO) fmt ./... + +# Run go vet +vet: + @echo "Running go vet..." + $(GO) vet ./... + +# Run linters +lint: fmt vet + @echo "Running linters..." + @if command -v golangci-lint >/dev/null 2>&1; then \ + golangci-lint run; \ + else \ + echo "golangci-lint not installed. Run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest"; \ + fi + +# Tidy dependencies +tidy: + @echo "Tidying dependencies..." + $(GO) mod tidy + +# Download dependencies +deps: + @echo "Downloading dependencies..." + $(GO) mod download + +# Help +help: + @echo "Available targets:" + @echo " all - Run tests and build" + @echo " build - Build the CLI binary" + @echo " build-all - Build for multiple platforms" + @echo " install - Install the binary" + @echo " test - Run tests" + @echo " coverage - Run tests with coverage report" + @echo " demo - Run the demo" + @echo " run - Build and run the CLI" + @echo " clean - Clean build artifacts" + @echo " fmt - Format code" + @echo " vet - Run go vet" + @echo " lint - Run linters" + @echo " tidy - Tidy dependencies" + @echo " deps - Download dependencies" + @echo " help - Show this help message" diff --git a/README.md b/README.md index 088fba5..efca268 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,220 @@ # Mining -GoLang Miner management with restful control + +[![Go Version](https://img.shields.io/badge/go-1.24+-blue.svg)](https://golang.org) +[![GoDoc](https://pkg.go.dev/badge/github.com/Snider/Mining.svg)](https://pkg.go.dev/github.com/Snider/Mining) +[![Go Report Card](https://goreportcard.com/badge/github.com/Snider/Mining)](https://goreportcard.com/report/github.com/Snider/Mining) +[![License: EUPL-1.2](https://img.shields.io/badge/License-EUPL--1.2-blue.svg)](https://opensource.org/license/eupl-1-2) +[![Release](https://img.shields.io/github/release/Snider/Mining.svg)](https://github.com/Snider/Mining/releases) +[![codecov](https://codecov.io/gh/Snider/Mining/branch/main/graph/badge.svg)](https://codecov.io/gh/Snider/Mining) + +GoLang Miner management with RESTful control - A modern, modular package for managing cryptocurrency miners. + +## Overview + +Mining is a Go package designed to provide comprehensive miner management capabilities. It can be used both as a standalone CLI tool and as a module/plugin in other Go projects. The package offers: + +- **Miner Lifecycle Management**: Start, stop, and monitor miners +- **Status Tracking**: Real-time status and hash rate monitoring +- **CLI Interface**: Easy-to-use command-line interface built with Cobra +- **Modular Design**: Import as a package in your own projects +- **RESTful Ready**: Designed for integration with RESTful control systems + +## Features + +- ✅ Start and stop miners programmatically +- ✅ Monitor miner status and performance +- ✅ Track hash rates +- ✅ List all active miners +- ✅ CLI for easy management +- ✅ Designed as a reusable Go module +- ✅ Comprehensive test coverage +- ✅ Standards-compliant configuration (CodeRabbit, GoReleaser) + +## Installation + +### As a CLI Tool + +```bash +go install github.com/Snider/Mining/cmd/mining@latest +``` + +### As a Go Module + +```bash +go get github.com/Snider/Mining +``` + +## Usage + +### CLI Usage + +```bash +# Start a miner +mining start --name bitcoin-miner-1 --algorithm sha256 --pool pool.bitcoin.com + +# List all miners +mining list + +# Get miner status +mining status + +# Stop a miner +mining stop + +# Show version +mining --version + +# Show help +mining --help +``` + +### As a Go Package + +```go +package main + +import ( + "fmt" + "github.com/Snider/Mining/pkg/mining" +) + +func main() { + // Create a manager + manager := mining.NewManager() + + // Start a miner + config := mining.MinerConfig{ + Name: "my-miner", + Algorithm: "sha256", + Pool: "pool.example.com", + Wallet: "your-wallet-address", + } + + miner, err := manager.StartMiner(config) + if err != nil { + panic(err) + } + + fmt.Printf("Started miner: %s\n", miner.ID) + + // Update hash rate + manager.UpdateHashRate(miner.ID, 150.5) + + // List all miners + miners := manager.ListMiners() + for _, m := range miners { + fmt.Printf("%s: %s (%.2f H/s)\n", m.ID, m.Name, m.HashRate) + } + + // Stop the miner + manager.StopMiner(miner.ID) +} +``` + +## Development + +### Prerequisites + +- Go 1.24 or higher +- Make (optional, for using Makefile targets) + +### Build + +```bash +# Build the CLI +go build -o mining ./cmd/mining + +# Run demo +go run main.go + +# Run tests +go test ./... + +# Run tests with coverage +go test -cover ./... +``` + +### Project Structure + +``` +. +├── cmd/ +│ └── mining/ # CLI application +│ ├── main.go # CLI entry point +│ └── cmd/ # Cobra commands +├── pkg/ +│ └── mining/ # Core mining package +│ ├── mining.go # Main package code +│ └── mining_test.go +├── main.go # Demo/development main +├── .coderabbit.yaml # CodeRabbit configuration +├── .goreleaser.yaml # GoReleaser configuration +├── .gitignore +├── go.mod +├── LICENSE +└── README.md +``` + +## Configuration + +### CodeRabbit + +The project uses CodeRabbit for automated code reviews. Configuration is in `.coderabbit.yaml`. + +### GoReleaser + +Releases are managed with GoReleaser. Configuration is in `.goreleaser.yaml`. To create a release: + +```bash +# Tag a version +git tag -a v0.1.0 -m "Release v0.1.0" +git push origin v0.1.0 + +# GoReleaser will automatically build and publish +``` + +## API Reference + +### Types + +#### `Manager` +Main manager for miner operations. + +#### `Miner` +Represents a mining instance with ID, name, status, and performance metrics. + +#### `MinerConfig` +Configuration for starting a new miner. + +### Functions + +- `NewManager() *Manager` - Create a new miner manager +- `StartMiner(config MinerConfig) (*Miner, error)` - Start a new miner +- `StopMiner(id string) error` - Stop a running miner +- `GetMiner(id string) (*Miner, error)` - Get miner by ID +- `ListMiners() []*Miner` - List all miners +- `UpdateHashRate(id string, hashRate float64) error` - Update miner hash rate + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'Add some amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +## License + +This project is licensed under the EUPL-1.2 License - see the [LICENSE](LICENSE) file for details. + +## Acknowledgments + +- Built with [Cobra](https://github.com/spf13/cobra) for CLI functionality +- Configured for [CodeRabbit](https://coderabbit.ai) automated reviews +- Releases managed with [GoReleaser](https://goreleaser.com) + +## Support + +For issues, questions, or contributions, please open an issue on GitHub. diff --git a/cmd/mining/cmd/list.go b/cmd/mining/cmd/list.go new file mode 100644 index 0000000..36e68fe --- /dev/null +++ b/cmd/mining/cmd/list.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// listCmd represents the list command +var listCmd = &cobra.Command{ + Use: "list", + Short: "List all miners", + Long: `List all miners and their current status.`, + RunE: func(cmd *cobra.Command, args []string) error { + miners := getManager().ListMiners() + + if len(miners) == 0 { + fmt.Println("No miners found") + return nil + } + + fmt.Printf("%-20s %-20s %-10s %-12s\n", "ID", "Name", "Status", "Hash Rate") + fmt.Println("--------------------------------------------------------------------------------") + for _, miner := range miners { + fmt.Printf("%-20s %-20s %-10s %-12.2f\n", + miner.ID, + miner.Name, + miner.Status, + miner.HashRate, + ) + } + return nil + }, +} + +func init() { + rootCmd.AddCommand(listCmd) +} diff --git a/cmd/mining/cmd/root.go b/cmd/mining/cmd/root.go new file mode 100644 index 0000000..17fd39a --- /dev/null +++ b/cmd/mining/cmd/root.go @@ -0,0 +1,44 @@ +package cmd + +import ( + "github.com/Snider/Mining/pkg/mining" + "github.com/spf13/cobra" +) + +var ( + manager *mining.Manager +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "mining", + Short: "Mining CLI - Manage miners with RESTful control", + Long: `Mining is a CLI tool for managing cryptocurrency miners. +It provides commands to start, stop, list, and manage miners with RESTful control capabilities.`, + Version: mining.GetVersion(), +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() error { + return rootCmd.Execute() +} + +func init() { + cobra.OnInitialize(initManager) +} + +// initManager initializes the miner manager +func initManager() { + if manager == nil { + manager = mining.NewManager() + } +} + +// getManager returns the singleton manager instance +func getManager() *mining.Manager { + if manager == nil { + manager = mining.NewManager() + } + return manager +} diff --git a/cmd/mining/cmd/start.go b/cmd/mining/cmd/start.go new file mode 100644 index 0000000..6bc9793 --- /dev/null +++ b/cmd/mining/cmd/start.go @@ -0,0 +1,50 @@ +package cmd + +import ( + "fmt" + + "github.com/Snider/Mining/pkg/mining" + "github.com/spf13/cobra" +) + +var ( + minerName string + minerAlgorithm string + minerPool string + minerWallet string +) + +// startCmd represents the start command +var startCmd = &cobra.Command{ + Use: "start", + Short: "Start a new miner", + Long: `Start a new miner with the specified configuration.`, + RunE: func(cmd *cobra.Command, args []string) error { + config := mining.MinerConfig{ + Name: minerName, + Algorithm: minerAlgorithm, + Pool: minerPool, + Wallet: minerWallet, + } + + miner, err := getManager().StartMiner(config) + if err != nil { + return fmt.Errorf("failed to start miner: %w", err) + } + + fmt.Printf("Miner started successfully:\n") + fmt.Printf(" ID: %s\n", miner.ID) + fmt.Printf(" Name: %s\n", miner.Name) + fmt.Printf(" Status: %s\n", miner.Status) + return nil + }, +} + +func init() { + rootCmd.AddCommand(startCmd) + startCmd.Flags().StringVarP(&minerName, "name", "n", "", "Miner name (required)") + startCmd.Flags().StringVarP(&minerAlgorithm, "algorithm", "a", "sha256", "Mining algorithm") + startCmd.Flags().StringVarP(&minerPool, "pool", "p", "", "Mining pool address") + startCmd.Flags().StringVarP(&minerWallet, "wallet", "w", "", "Wallet address") + startCmd.MarkFlagRequired("name") +} diff --git a/cmd/mining/cmd/status.go b/cmd/mining/cmd/status.go new file mode 100644 index 0000000..e94dc51 --- /dev/null +++ b/cmd/mining/cmd/status.go @@ -0,0 +1,35 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// statusCmd represents the status command +var statusCmd = &cobra.Command{ + Use: "status [miner-id]", + Short: "Get status of a miner", + Long: `Get detailed status information for a specific miner.`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + minerID := args[0] + + miner, err := getManager().GetMiner(minerID) + if err != nil { + return fmt.Errorf("failed to get miner: %w", err) + } + + fmt.Printf("Miner Status:\n") + fmt.Printf(" ID: %s\n", miner.ID) + fmt.Printf(" Name: %s\n", miner.Name) + fmt.Printf(" Status: %s\n", miner.Status) + fmt.Printf(" Start Time: %s\n", miner.StartTime.Format("2006-01-02 15:04:05")) + fmt.Printf(" Hash Rate: %.2f H/s\n", miner.HashRate) + return nil + }, +} + +func init() { + rootCmd.AddCommand(statusCmd) +} diff --git a/cmd/mining/cmd/stop.go b/cmd/mining/cmd/stop.go new file mode 100644 index 0000000..708c8df --- /dev/null +++ b/cmd/mining/cmd/stop.go @@ -0,0 +1,30 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// stopCmd represents the stop command +var stopCmd = &cobra.Command{ + Use: "stop [miner-id]", + Short: "Stop a running miner", + Long: `Stop a running miner by its ID.`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + minerID := args[0] + + err := getManager().StopMiner(minerID) + if err != nil { + return fmt.Errorf("failed to stop miner: %w", err) + } + + fmt.Printf("Miner %s stopped successfully\n", minerID) + return nil + }, +} + +func init() { + rootCmd.AddCommand(stopCmd) +} diff --git a/cmd/mining/main.go b/cmd/mining/main.go new file mode 100644 index 0000000..2427edd --- /dev/null +++ b/cmd/mining/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" + "os" + + "github.com/Snider/Mining/cmd/mining/cmd" +) + +func main() { + if err := cmd.Execute(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b8c4d33 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/Snider/Mining + +go 1.24 + +require github.com/spf13/cobra v1.8.1 + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..912390a --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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..384761e --- /dev/null +++ b/main.go @@ -0,0 +1,102 @@ +// Demo main.go for development and testing +package main + +import ( + "fmt" + "log" + "time" + + "github.com/Snider/Mining/pkg/mining" +) + +func main() { + fmt.Println("Mining Package Demo") + fmt.Println("===================") + fmt.Println() + + // Create a new manager + manager := mining.NewManager() + fmt.Println("✓ Created new mining manager") + + // Start a few miners + configs := []mining.MinerConfig{ + { + Name: "bitcoin-miner-1", + Algorithm: "sha256", + Pool: "pool.bitcoin.com", + Wallet: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", + }, + { + Name: "ethereum-miner-1", + Algorithm: "ethash", + Pool: "pool.ethereum.org", + Wallet: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", + }, + } + + var minerIDs []string + for _, config := range configs { + miner, err := manager.StartMiner(config) + if err != nil { + log.Fatalf("Failed to start miner: %v", err) + } + minerIDs = append(minerIDs, miner.ID) + fmt.Printf("✓ Started miner: %s (ID: %s)\n", miner.Name, miner.ID) + time.Sleep(10 * time.Millisecond) // Small delay for unique IDs + } + + fmt.Println() + + // Update hash rates + hashRates := []float64{150.5, 320.75} + for i, id := range minerIDs { + err := manager.UpdateHashRate(id, hashRates[i]) + if err != nil { + log.Fatalf("Failed to update hash rate: %v", err) + } + fmt.Printf("✓ Updated hash rate for %s: %.2f H/s\n", id, hashRates[i]) + } + + fmt.Println() + + // List all miners + fmt.Println("Active Miners:") + fmt.Println("--------------") + miners := manager.ListMiners() + for _, miner := range miners { + fmt.Printf(" %s: %s (%.2f H/s, %s)\n", + miner.ID, + miner.Name, + miner.HashRate, + miner.Status, + ) + } + + fmt.Println() + + // Get specific miner status + if len(minerIDs) > 0 { + miner, err := manager.GetMiner(minerIDs[0]) + if err != nil { + log.Fatalf("Failed to get miner: %v", err) + } + fmt.Printf("Detailed Status for %s:\n", miner.Name) + fmt.Printf(" ID: %s\n", miner.ID) + fmt.Printf(" Status: %s\n", miner.Status) + fmt.Printf(" Start Time: %s\n", miner.StartTime.Format("2006-01-02 15:04:05")) + fmt.Printf(" Hash Rate: %.2f H/s\n", miner.HashRate) + fmt.Println() + } + + // Stop a miner + if len(minerIDs) > 0 { + err := manager.StopMiner(minerIDs[0]) + if err != nil { + log.Fatalf("Failed to stop miner: %v", err) + } + fmt.Printf("✓ Stopped miner: %s\n", minerIDs[0]) + } + + fmt.Println() + fmt.Printf("Demo completed successfully! Version: %s\n", mining.GetVersion()) +} diff --git a/pkg/mining/mining.go b/pkg/mining/mining.go new file mode 100644 index 0000000..8195d7e --- /dev/null +++ b/pkg/mining/mining.go @@ -0,0 +1,104 @@ +// Package mining provides core functionality for miner management +package mining + +import ( + "fmt" + "time" +) + +// Miner represents a mining instance +type Miner struct { + ID string + Name string + Status string + StartTime time.Time + HashRate float64 +} + +// MinerConfig holds configuration for a miner +type MinerConfig struct { + Name string + Algorithm string + Pool string + Wallet string +} + +// Manager handles miner lifecycle and operations +type Manager struct { + miners map[string]*Miner +} + +// NewManager creates a new miner manager +func NewManager() *Manager { + return &Manager{ + miners: make(map[string]*Miner), + } +} + +// StartMiner starts a new miner with the given configuration +func (m *Manager) StartMiner(config MinerConfig) (*Miner, error) { + if config.Name == "" { + return nil, fmt.Errorf("miner name is required") + } + + miner := &Miner{ + ID: generateID(), + Name: config.Name, + Status: "running", + StartTime: time.Now(), + HashRate: 0.0, + } + + m.miners[miner.ID] = miner + return miner, nil +} + +// StopMiner stops a running miner +func (m *Manager) StopMiner(id string) error { + miner, exists := m.miners[id] + if !exists { + return fmt.Errorf("miner not found: %s", id) + } + + miner.Status = "stopped" + return nil +} + +// GetMiner retrieves a miner by ID +func (m *Manager) GetMiner(id string) (*Miner, error) { + miner, exists := m.miners[id] + if !exists { + return nil, fmt.Errorf("miner not found: %s", id) + } + return miner, nil +} + +// ListMiners returns all miners +func (m *Manager) ListMiners() []*Miner { + miners := make([]*Miner, 0, len(m.miners)) + for _, miner := range m.miners { + miners = append(miners, miner) + } + return miners +} + +// UpdateHashRate updates the hash rate for a miner +func (m *Manager) UpdateHashRate(id string, hashRate float64) error { + miner, exists := m.miners[id] + if !exists { + return fmt.Errorf("miner not found: %s", id) + } + + miner.HashRate = hashRate + return nil +} + +// generateID generates a unique ID for a miner +func generateID() string { + return fmt.Sprintf("miner-%d", time.Now().UnixNano()) +} + +// GetVersion returns the package version +func GetVersion() string { + return "0.1.0" +} diff --git a/pkg/mining/mining_test.go b/pkg/mining/mining_test.go new file mode 100644 index 0000000..b89213d --- /dev/null +++ b/pkg/mining/mining_test.go @@ -0,0 +1,154 @@ +package mining + +import ( + "testing" + "time" +) + +func TestNewManager(t *testing.T) { + manager := NewManager() + if manager == nil { + t.Fatal("NewManager returned nil") + } + if manager.miners == nil { + t.Error("Manager miners map is nil") + } +} + +func TestStartMiner(t *testing.T) { + manager := NewManager() + + config := MinerConfig{ + Name: "test-miner", + Algorithm: "sha256", + Pool: "pool.example.com", + Wallet: "wallet123", + } + + miner, err := manager.StartMiner(config) + if err != nil { + t.Fatalf("StartMiner failed: %v", err) + } + + if miner.Name != config.Name { + t.Errorf("Expected name %s, got %s", config.Name, miner.Name) + } + if miner.Status != "running" { + t.Errorf("Expected status 'running', got %s", miner.Status) + } + if miner.ID == "" { + t.Error("Miner ID is empty") + } +} + +func TestStartMinerWithoutName(t *testing.T) { + manager := NewManager() + + config := MinerConfig{ + Algorithm: "sha256", + } + + _, err := manager.StartMiner(config) + if err == nil { + t.Error("Expected error for miner without name") + } +} + +func TestStopMiner(t *testing.T) { + manager := NewManager() + + config := MinerConfig{Name: "test-miner"} + miner, _ := manager.StartMiner(config) + + err := manager.StopMiner(miner.ID) + if err != nil { + t.Fatalf("StopMiner failed: %v", err) + } + + if miner.Status != "stopped" { + t.Errorf("Expected status 'stopped', got %s", miner.Status) + } +} + +func TestStopNonExistentMiner(t *testing.T) { + manager := NewManager() + + err := manager.StopMiner("non-existent") + if err == nil { + t.Error("Expected error for stopping non-existent miner") + } +} + +func TestGetMiner(t *testing.T) { + manager := NewManager() + + config := MinerConfig{Name: "test-miner"} + startedMiner, _ := manager.StartMiner(config) + + retrievedMiner, err := manager.GetMiner(startedMiner.ID) + if err != nil { + t.Fatalf("GetMiner failed: %v", err) + } + + if retrievedMiner.ID != startedMiner.ID { + t.Errorf("Expected ID %s, got %s", startedMiner.ID, retrievedMiner.ID) + } +} + +func TestGetNonExistentMiner(t *testing.T) { + manager := NewManager() + + _, err := manager.GetMiner("non-existent") + if err == nil { + t.Error("Expected error for getting non-existent miner") + } +} + +func TestListMiners(t *testing.T) { + manager := NewManager() + + // Start multiple miners + for i := 0; i < 3; i++ { + config := MinerConfig{Name: "test-miner"} + _, _ = manager.StartMiner(config) + time.Sleep(time.Millisecond) // Ensure unique IDs + } + + miners := manager.ListMiners() + if len(miners) != 3 { + t.Errorf("Expected 3 miners, got %d", len(miners)) + } +} + +func TestUpdateHashRate(t *testing.T) { + manager := NewManager() + + config := MinerConfig{Name: "test-miner"} + miner, _ := manager.StartMiner(config) + + newHashRate := 123.45 + err := manager.UpdateHashRate(miner.ID, newHashRate) + if err != nil { + t.Fatalf("UpdateHashRate failed: %v", err) + } + + if miner.HashRate != newHashRate { + t.Errorf("Expected hash rate %f, got %f", newHashRate, miner.HashRate) + } +} + +func TestUpdateHashRateNonExistent(t *testing.T) { + manager := NewManager() + + err := manager.UpdateHashRate("non-existent", 100.0) + if err == nil { + t.Error("Expected error for updating non-existent miner") + } +} + +func TestGetVersion(t *testing.T) { + version := GetVersion() + if version == "" { + t.Error("Version is empty") + } +}