Merge pull request #1 from Snider/copilot/create-go-package-cli

Create modular Go package with CLI for miner management
This commit is contained in:
Snider 2025-11-08 17:02:11 +00:00 committed by GitHub
commit 00ca76f6f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 1380 additions and 1 deletions

50
.coderabbit.yaml Normal file
View file

@ -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

49
.gitignore vendored Normal file
View file

@ -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

100
.goreleaser.yaml Normal file
View file

@ -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

267
LICENSE Normal file
View file

@ -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.

103
Makefile Normal file
View file

@ -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"

220
README.md
View file

@ -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 <miner-id>
# Stop a miner
mining stop <miner-id>
# 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.

38
cmd/mining/cmd/list.go Normal file
View file

@ -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)
}

44
cmd/mining/cmd/root.go Normal file
View file

@ -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
}

50
cmd/mining/cmd/start.go Normal file
View file

@ -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")
}

35
cmd/mining/cmd/status.go Normal file
View file

@ -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)
}

30
cmd/mining/cmd/stop.go Normal file
View file

@ -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)
}

15
cmd/mining/main.go Normal file
View file

@ -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)
}
}

10
go.mod Normal file
View file

@ -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
)

10
go.sum Normal file
View file

@ -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=

102
main.go Normal file
View file

@ -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())
}

104
pkg/mining/mining.go Normal file
View file

@ -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"
}

154
pkg/mining/mining_test.go Normal file
View file

@ -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")
}
}