Compare commits

..

No commits in common. "main" and "feature/test-coverage-audit-7324697122677518365" have entirely different histories.

30 changed files with 4396 additions and 7156 deletions

View file

@ -1,127 +0,0 @@
# Code Complexity and Maintainability Audit
This document analyzes the code quality of the codebase, identifies maintainability issues, and provides recommendations for improvement. The audit focuses on cyclomatic and cognitive complexity, code duplication, and other maintainability metrics.
## 1. God Class: `Manager`
### Finding
The `Manager` struct in `pkg/mining/manager.go` is a "God Class" that violates the Single Responsibility Principle. It handles multiple, unrelated responsibilities, including:
- Miner lifecycle management (`StartMiner`, `StopMiner`)
- Configuration management (`syncMinersConfig`, `updateMinerConfig`)
- Database interactions (`initDatabase`, `startDBCleanup`)
- Statistics collection (`startStatsCollection`, `collectMinerStats`)
This centralization of concerns makes the `Manager` class difficult to understand, test, and maintain. The presence of multiple mutexes (`mu`, `eventHubMu`) to prevent deadlocks is a clear indicator of its high cognitive complexity.
### Recommendation
Refactor the `Manager` class into smaller, more focused components, each with a single responsibility.
- **`MinerRegistry`**: Manages the lifecycle of miner instances.
- **`StatsCollector`**: Gathers and aggregates statistics from miners.
- **`ConfigService`**: Handles loading, saving, and updating miner configurations.
- **`DBManager`**: Manages all database-related operations.
This separation of concerns will improve modularity, reduce complexity, and make the system easier to reason about and test.
## 2. Code Duplication: Miner Installation
### Finding
The `Install` and `CheckInstallation` methods in `pkg/mining/xmrig.go` and `pkg/mining/ttminer.go` contain nearly identical logic for downloading, extracting, and verifying miner installations. This copy-paste pattern violates the DRY (Don't Repeat Yourself) principle and creates a significant maintenance burden. Any change to the installation process must be manually duplicated across all miner implementations.
### Recommendation
Refactor the duplicated logic into the `BaseMiner` struct using the **Template Method Pattern**. The base struct will define the skeleton of the installation algorithm, while subclasses will override specific steps (like providing the download URL format) that vary between miners.
#### Example
The `BaseMiner` can provide a generic `Install` method that relies on a new, unexported method, `getDownloadURL`, which each miner implementation must provide.
**`pkg/mining/miner.go` (BaseMiner)**
```go
// Install orchestrates the download and extraction process.
func (b *BaseMiner) Install() error {
version, err := b.GetLatestVersion()
if err != nil {
return err
}
b.Version = version
url, err := b.getDownloadURL(version)
if err != nil {
return err
}
return b.InstallFromURL(url)
}
// getDownloadURL is a template method to be implemented by subclasses.
func (b *BaseMiner) getDownloadURL(version string) (string, error) {
// This will be overridden by specific miner types
return "", errors.New("getDownloadURL not implemented")
}
```
**`pkg/mining/xmrig.go` (XMRigMiner)**
```go
// getDownloadURL implements the template method for XMRig.
func (m *XMRigMiner) getDownloadURL(version string) (string, error) {
v := strings.TrimPrefix(version, "v")
switch runtime.GOOS {
case "windows":
return fmt.Sprintf("https://.../xmrig-%s-windows-x64.zip", v), nil
case "linux":
return fmt.Sprintf("https://.../xmrig-%s-linux-static-x64.tar.gz", v), nil
default:
return "", errors.New("unsupported OS")
}
}
```
## 3. Long and Complex Methods
### Finding
Several methods in the codebase are overly long and have high cognitive complexity, making them difficult to read, understand, and maintain.
- **`manager.StartMiner`**: This method is responsible for creating, configuring, and starting a miner. It mixes validation, port finding, instance name generation, and state management, making it hard to follow.
- **`manager.collectMinerStats`**: This function orchestrates the parallel collection of stats, but the logic for handling timeouts, retries, and database persistence is deeply nested.
- **`miner.ReduceHashrateHistory`**: The logic for aggregating high-resolution hashrate data into a low-resolution format is convoluted and hard to reason about.
### Recommendation
Apply the **Extract Method** refactoring to break down these long methods into smaller, well-named functions, each with a single, clear purpose.
#### Example: Refactoring `manager.StartMiner`
The `StartMiner` method could be refactored into several smaller helper functions.
**`pkg/mining/manager.go` (Original `StartMiner`)**
```go
func (m *Manager) StartMiner(ctx context.Context, minerType string, config *Config) (Miner, error) {
// ... (20+ lines of setup, validation, port finding)
// ... (10+ lines of miner-specific configuration)
// ... (10+ lines of starting and saving logic)
}
```
**`pkg/mining/manager.go` (Refactored `StartMiner`)**
```go
func (m *Manager) StartMiner(ctx context.Context, minerType string, config *Config) (Miner, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
instanceName, err := m.generateInstanceName(minerType, config)
if err != nil {
return nil, err
}
miner, err := m.configureMiner(minerType, instanceName, config)
if err != nil {
return nil, err
}
if err := m.launchAndRegisterMiner(miner, config); err != nil {
return nil, err
}
return miner, nil
}
```

View file

@ -1,60 +0,0 @@
# Concurrency and Race Condition Audit
## 1. Executive Summary
This audit examined the concurrency safety of the mining operations within the `pkg/mining` package. The assessment involved a combination of automated race detection using `go test -race` and a manual code review of the key components responsible for managing miner lifecycles and statistics collection.
**The primary finding is that the core concurrency logic is well-designed and appears to be free of race conditions.** The code demonstrates a strong understanding of Go's concurrency patterns, with proper use of mutexes to protect shared state.
The most significant risk identified is the **lack of complete test coverage** for code paths that interact with live miner processes. This limitation prevented the Go race detector from analyzing these sections, leaving a gap in the automated verification.
## 2. Methodology
The audit was conducted in two phases:
1. **Automated Race Detection**: The test suite for the `pkg/mining` package was executed with the `-race` flag enabled (`go test -race ./pkg/mining/...`). This tool instrumented the code to detect and report any data races that occurred during the execution of the tests.
2. **Manual Code Review**: A thorough manual inspection of the source code was performed, focusing on `manager.go`, `miner.go`, and the `xmrig` and `ttminer` implementations. The review prioritized areas with shared mutable state, goroutine management, and I/O operations.
## 3. Findings
### 3.1. Automated Race Detection (`go test -race`)
The Go race detector **did not report any race conditions** in the code paths that were executed by the test suite. This provides a good level of confidence in the concurrency safety of the `Manager`'s core logic for adding, removing, and listing miners, as these operations are well-covered by the existing tests.
However, a number of tests related to live miner interaction (e.g., `TestCPUThrottleSingleMiner`) were skipped because they require the `xmrig` binary to be present in the test environment. As a result, the race detector could not analyze the code executed in these tests.
### 3.2. Manual Code Review
The manual review confirmed the findings of the race detector and extended the analysis to the code paths that were not covered by the tests.
#### 3.2.1. `Manager` (`manager.go`)
* **Shared State**: The `miners` map is the primary shared resource.
* **Protection**: A `sync.RWMutex` is used to protect all access to the `miners` map.
* **Analysis**: The `collectMinerStats` function is the most critical concurrent operation. It correctly uses a read lock to create a snapshot of the active miners and then releases the lock before launching concurrent goroutines to collect stats from each miner. This is a robust pattern that minimizes lock contention and delegates thread safety to the individual `Miner` implementations. All other methods on the `Manager` use the mutex correctly.
#### 3.2.2. `BaseMiner` (`miner.go`)
* **Shared State**: The `BaseMiner` struct contains several fields that are accessed and modified concurrently, including `Running`, `cmd`, and `HashrateHistory`.
* **Protection**: A `sync.RWMutex` is used to protect all shared fields.
* **Analysis**: Methods like `Stop`, `AddHashratePoint`, and `ReduceHashrateHistory` correctly acquire and release the mutex. The locking is fine-grained and properly scoped.
#### 3.2.3. `XMRigMiner` and `TTMiner`
* **`GetStats` Method**: This is the most important method for concurrency in the miner implementations. Both `XMRigMiner` and `TTMiner` follow an excellent pattern:
1. Acquire a read lock to safely read the API configuration.
2. Release the lock *before* making the blocking HTTP request.
3. After the request completes, acquire a write lock to update the `FullStats` field.
This prevents holding a lock during a potentially long I/O operation, which is a common cause of performance bottlenecks and deadlocks.
* **`Start` Method**: Both implementations launch a goroutine to wait for the miner process to exit. This goroutine correctly captures a local copy of the `exec.Cmd` pointer. When updating the `Running` and `cmd` fields after the process exits, it checks if the current `m.cmd` is still the same as the one it was started with. This correctly handles the case where a miner might be stopped and restarted quickly, preventing the old goroutine from incorrectly modifying the state of the new process.
## 4. Conclusion and Recommendations
The mining operations in this codebase are implemented with a high degree of concurrency safety. The use of mutexes is consistent and correct, and the patterns used for handling I/O in concurrent contexts are exemplary.
The primary recommendation is to **improve the test coverage** to allow the Go race detector to provide a more complete analysis.
* **Recommendation 1 (High Priority)**: Modify the test suite to use a mock or simulated miner process. The existing tests already use a dummy script for some installation checks. This could be extended to create a mock HTTP server that simulates the miner's API. This would allow the skipped tests to run, enabling the race detector to analyze the `GetStats` methods and other live interaction code paths.
* **Recommendation 2 (Low Priority)**: The `httpClient` in `xmrig.go` is a global variable protected by a mutex. While the default `http.Client` is thread-safe, and the mutex provides protection for testing, it would be slightly cleaner to make the HTTP client a field on the `XMRigMiner` struct. This would avoid the global state and make the dependencies of the miner more explicit. However, this is a minor architectural point and not a critical concurrency issue.
Overall, the risk of race conditions in the current codebase is low, but shoring up the test suite would provide even greater confidence in its robustness.

View file

@ -1,72 +0,0 @@
# Documentation Audit Report
## README Assessment
| Category | Status | Notes |
|---|---|---|
| **Project Description** | ✅ Pass | The README provides a clear and concise description of the project's purpose. |
| **Quick Start** | ✅ Pass | The "Quick Start" section is excellent, offering a Docker command for immediate setup. |
| **Installation** | ✅ Pass | Multiple installation methods are documented (Docker, CLI, source). |
| **Configuration** | ✅ Pass | Configuration is explained with a clear example of a JSON profile. |
| **Examples** | ✅ Pass | The README includes usage examples for Docker, the CLI, and the web component. |
| **Badges** | ✅ Pass | A comprehensive set of badges is present, covering build status, coverage, and versioning. |
**Overall:** The `README.md` is comprehensive and user-friendly.
## Code Documentation
| Category | Status | Notes |
|---|---|---|
| **Function Docs** | ✅ Pass | Public APIs are well-documented with clear explanations. |
| **Parameter Types** | ✅ Pass | Go's static typing ensures parameter types are documented. |
| **Return Values** | ✅ Pass | Return values are documented in the function comments. |
| **Examples** | ❌ Fail | There are no runnable examples in the Go docstrings. |
| **Outdated Docs** | ✅ Pass | The documentation appears to be up-to-date with the code. |
**Overall:** The code is well-documented, but could be improved by adding runnable examples in the docstrings, which would be automatically included in the GoDoc.
## Architecture Documentation
| Category | Status | Notes |
|---|---|---|
| **System Overview** | ✅ Pass | `docs/ARCHITECTURE.md` provides a high-level overview of the system. |
| **Data Flow** | ✅ Pass | The architecture document includes a sequence diagram illustrating data flow. |
| **Component Diagram** | ✅ Pass | A Mermaid diagram visually represents the system's components. |
| **Decision Records** | ❌ Fail | There are no Architecture Decision Records (ADRs) present. |
**Overall:** The architecture is well-documented, but would benefit from ADRs to track key decisions.
## Developer Documentation
| Category | Status | Notes |
|---|---|---|
| **Contributing Guide** | ✅ Pass | The `README.md` and `docs/DEVELOPMENT.md` provide clear contribution instructions. |
| **Development Setup** | ✅ Pass | Prerequisites and setup steps are documented. |
| **Testing Guide** | ✅ Pass | The `docs/DEVELOPMENT.md` file explains how to run tests. |
| **Code Style** | 🟠 Partial | A formal code style guide is missing, but `make lint` and `make fmt` are provided. |
**Overall:** Developer documentation is good, but a formal style guide would be a useful addition.
## User Documentation
| Category | Status | Notes |
|---|---|---|
| **User Guide** | ✅ Pass | The MkDocs site serves as a comprehensive user guide. |
| **FAQ** | ❌ Fail | A dedicated FAQ section is missing. |
| **Troubleshooting** | ✅ Pass | A troubleshooting guide is available in the documentation. |
| **Changelog** | ✅ Pass | `CHANGELOG.md` is present and well-maintained. |
**Overall:** User documentation is strong, but could be improved with a FAQ section.
## Summary of Documentation Gaps
The following documentation gaps have been identified:
- **Code Documentation:**
- Add runnable examples to Go docstrings to improve GoDoc.
- **Architecture Documentation:**
- Introduce Architecture Decision Records (ADRs) to document key architectural decisions.
- **Developer Documentation:**
- Create a formal code style guide to ensure consistency.
- **User Documentation:**
- Add a Frequently Asked Questions (FAQ) section to the user guide.

View file

@ -1,49 +0,0 @@
# Error Handling and Logging Audit
## 1. Error Handling
### Exception Handling & Error Recovery
- **Graceful Degradation**: The application demonstrates graceful degradation in `pkg/mining/service.go`, where the `NewService` function continues to operate with a minimal in-memory profile manager if the primary one fails to initialize. This ensures core functionality remains available.
- **Inconsistent Top-Level Handling**: Error handling at the application's entry points is inconsistent.
- In `cmd/desktop/mining-desktop/main.go`, errors from `fs.Sub` and `app.Run` are handled with `log.Fatal`, which abruptly terminates the application without using the project's structured logger.
- In `cmd/mining/main.go`, errors from `cmd.Execute` are printed to `stderr` with `fmt.Fprintf`, and the application exits with a status code of 1. This is a standard CLI pattern but bypasses the custom logging framework.
- **No Retry or Circuit Breaker Patterns**: The codebase does not currently implement explicit retry logic with backoff or circuit breaker patterns for handling failures in external dependencies or services. However, the API error response includes a `Retryable` field, which correctly signals to clients when a retry is appropriate (e.g., for `503 Service Unavailable`).
### User-Facing & API Errors
- **Standard API Error Response**: The API service (`pkg/mining/service.go`) excels at providing consistent, user-friendly error responses.
- It uses a well-defined `APIError` struct that includes a machine-readable `code`, a human-readable `message`, and an optional `suggestion` to guide the user.
- The `respondWithError` and `respondWithMiningError` functions centralize error response logic, ensuring all API errors follow a consistent format.
- **Appropriate HTTP Status Codes**: The API correctly maps application errors to standard HTTP status codes (e.g., `404 Not Found` for missing miners, `400 Bad Request` for invalid input, `500 Internal Server Error` for server-side issues).
- **Controlled Information Leakage**: The `sanitizeErrorDetails` function prevents the leakage of sensitive internal error details in production environments (`GIN_MODE=release`), enhancing security. Debug information is only exposed when `DEBUG_ERRors` is enabled.
## 2. Logging
### Log Content and Quality
- **Custom Structured Logger**: The project includes a custom logger in `pkg/logging/logger.go` that supports standard log levels (Debug, Info, Warn, Error) and allows for structured logging by attaching key-value fields.
- **No JSON Output**: The logger's output is a custom string format (`timestamp [LEVEL] [component] message | key=value`), not structured JSON. This makes logs less machine-readable and harder to parse, filter, and analyze with modern log management tools.
- **Good Context in Error Logs**: The existing usage of `logging.Error` throughout the `pkg/mining` module is effective, consistently including relevant context (e.g., `miner`, `panic`, `error`) as structured fields.
- **Request Correlation**: The API service (`pkg/mining/service.go`) implements a `requestIDMiddleware` that assigns a unique `X-Request-ID` to each request, which is then included in logs. This is excellent practice for tracing and debugging.
### What is Not Logged
- **No Sensitive Data**: Based on a review of `logging.Error` usage, the application appears to correctly avoid logging sensitive information such as passwords, tokens, or personally identifiable information (PII).
### Inconsistencies
- **Inconsistent Adoption**: The custom logger is not used consistently across the project. The `main` packages for both the desktop and CLI applications (`cmd/desktop/mining-desktop/main.go` and `cmd/mining/main.go`) use the standard `log` and `fmt` packages for error handling, bypassing the structured logger.
- **No Centralized Configuration**: There is no centralized logger initialization in `main` or `root.go`. The global logger is used with its default configuration (Info level, stderr output), and there is no clear mechanism for configuring the log level or output via command-line flags or a configuration file.
## 3. Recommendations
1. **Adopt Structured JSON Logging**: Modify the logger in `pkg/logging/logger.go` to output logs in JSON format. This will significantly improve the logs' utility by making them machine-readable and compatible with log analysis platforms like Splunk, Datadog, or the ELK stack.
2. **Centralize Logger Configuration**:
* In `cmd/mining/cmd/root.go`, add persistent flags for `--log-level` and `--log-format` (e.g., `text`, `json`).
* In an `init` function, parse these flags and configure the global `logging.Logger` instance accordingly.
* Do the same for the desktop application in `cmd/desktop/mining-desktop/main.go`, potentially reading from a configuration file or environment variables.
3. **Standardize on the Global Logger**:
* Replace all instances of `log.Fatal` in `cmd/desktop/mining-desktop/main.go` with `logging.Error` followed by `os.Exit(1)`.
* Replace `fmt.Fprintf(os.Stderr, ...)` in `cmd/mining/main.go` with a call to `logging.Error`.
4. **Enrich API Error Logs**: In `pkg/mining/service.go`, enhance the `respondWithError` function to log every API error it handles using the structured logger. This will ensure that all error conditions, including client-side errors like bad requests, are recorded for monitoring and analysis. Include the `request_id` in every log entry.
5. **Review Log Levels**: Conduct a codebase-wide review of log levels. Ensure that `Debug` is used for verbose, development-time information, `Info` for significant operational events, `Warn` for recoverable issues, and `Error` for critical, action-required failures.

View file

@ -1,204 +0,0 @@
# Security Audit: Input Validation
This document outlines the findings of a security audit focused on input validation and sanitization within the mining application.
## Input Entry Points Inventory
### API Endpoints
The primary entry points for untrusted input are the API handlers defined in `pkg/mining/service.go`. The following handlers process user-controllable data from URL path parameters, query strings, and request bodies:
- **System & Miner Management:**
- `POST /miners/:miner_name/install`: `miner_name` from path.
- `DELETE /miners/:miner_name/uninstall`: `miner_name` from path.
- `DELETE /miners/:miner_name`: `miner_name` from path.
- `POST /miners/:miner_name/stdin`: `miner_name` from path and JSON body (`input`).
- **Statistics & History:**
- `GET /miners/:miner_name/stats`: `miner_name` from path.
- `GET /miners/:miner_name/hashrate-history`: `miner_name` from path.
- `GET /miners/:miner_name/logs`: `miner_name` from path.
- `GET /history/miners/:miner_name`: `miner_name` from path.
- `GET /history/miners/:miner_name/hashrate`: `miner_name` from path, `since` and `until` from query.
- **Profiles:**
- `POST /profiles`: JSON body (`MiningProfile`).
- `GET /profiles/:id`: `id` from path.
- `PUT /profiles/:id`: `id` from path and JSON body (`MiningProfile`).
- `DELETE /profiles/:id`: `id` from path.
- `POST /profiles/:id/start`: `id` from path.
### WebSocket Events
The WebSocket endpoint provides another significant entry point for untrusted input:
- **`GET /ws/events`**: Establishes a WebSocket connection. While the primary flow is server-to-client, the initial handshake and any client-to-server messages must be considered untrusted input. The `wsUpgrader` in `pkg/mining/service.go` has an origin check, which is a good security measure.
## Validation Gaps Found
The `Config.Validate()` method in `pkg/mining/mining.go` provides a solid baseline for input validation but has several gaps:
### Strengths
- **Core Fields Validated**: The most critical fields for command-line construction (`Pool`, `Wallet`, `Algo`, `CLIArgs`) have validation checks.
- **Denylist for Shell Characters**: The `containsShellChars` function attempts to block a wide range of characters that could be used for shell injection.
- **Range Checks**: Numeric fields like `Threads`, `Intensity`, and `DonateLevel` are correctly checked to ensure they fall within a sane range.
- **Allowlist for Algorithm**: The `isValidAlgo` function uses a strict allowlist for the `Algo` field, which is a security best practice.
### Weaknesses and Gaps
- **Incomplete Field Coverage**: A significant number of fields in the `Config` struct are not validated at all. An attacker could potentially abuse these fields if they are used in command-line arguments or other sensitive operations in the future. Unvalidated fields include:
- `Coin`
- `Password`
- `UserPass`
- `Proxy`
- `RigID`
- `LogFile` (potential for path traversal)
- `CPUAffinity`
- `Devices`
- Many others.
- **Denylist Approach**: The primary validation mechanism, `containsShellChars`, relies on a denylist of dangerous characters. This approach is inherently brittle because it is impossible to foresee all possible malicious inputs. A determined attacker might find ways to bypass the filter using alternative encodings or unlisted characters. An allowlist approach, accepting only known-good characters, is much safer.
- **No Path Traversal Protection**: The `LogFile` field is not validated. An attacker could provide a value like `../../../../etc/passwd` to attempt to write files in arbitrary locations on the filesystem.
- **Inconsistent Numeric Validation**: While some numeric fields are validated, others like `Retries`, `RetryPause`, `CPUPriority`, etc., are not checked for negative values or reasonable upper bounds.
## Injection Vectors Discovered
The primary injection vector discovered is through the `Config.CLIArgs` field, which is used to pass additional command-line arguments to the miner executables.
### XMRig Miner (`pkg/mining/xmrig_start.go`)
- **Unused in `xmrig_start.go`**: The `addCliArgs` function in `xmrig_start.go` does not actually use the `CLIArgs` field. It constructs arguments from other validated fields. This is good, but the presence of the field in the `Config` struct is misleading and could be used in the future, creating a vulnerability if not handled carefully.
### TT-Miner (`pkg/mining/ttminer_start.go`)
- **Direct Command Injection via `CLIArgs`**: The `addTTMinerCliArgs` function directly appends the contents of `Config.CLIArgs` to the command-line arguments. Although it uses a denylist-based `isValidCLIArg` function to filter out some dangerous characters, this approach is not foolproof.
- **Vulnerability**: An attacker can bypass the filter by crafting a malicious string that is not on the denylist but is still interpreted by the shell. For example, if a new shell feature or a different shell is used on the system, the denylist may become ineffective.
- **Example**: While the current filter blocks most common injection techniques, an attacker could still pass arguments that might cause unexpected behavior in the miner, such as `--algo some-exploitable-algo`, if the miner itself has vulnerabilities in how it parses certain arguments.
### Path Traversal in Config File Creation
- **Vulnerability**: The `getXMRigConfigPath` function in `xmrig.go` uses the `instanceName` to construct a config file path. The `instanceName` is derived from the user-provided `config.Algo`. While the `instanceNameRegex` in `manager.go` sanitizes the algorithm name, it still allows forward slashes (`/`).
- **Example**: If an attacker provides a crafted `algo` like `../../../../tmp/myconfig`, the `instanceNameRegex` will not sanitize it, and the application could write a config file to an arbitrary location. This could be used to overwrite critical files or place malicious configuration files in sensitive locations.
## Remediation Recommendations
To address the identified vulnerabilities, the following remediation actions are recommended:
### 1. Strengthen `Config.Validate()` with an Allowlist Approach
Instead of relying on a denylist of dangerous characters, the validation should be updated to use a strict allowlist of known-good characters for each field.
**Code Example (`pkg/mining/mining.go`):**
\`\`\`go
// isValidInput checks if a string contains only allowed characters.
// This should be used for fields like Wallet, Password, Pool, etc.
func isValidInput(s string, allowedChars string) bool {
for _, r := range s {
if !strings.ContainsRune(allowedChars, r) {
return false
}
}
return true
}
// In Config.Validate():
func (c *Config) Validate() error {
// Example for Wallet field
if c.Wallet != "" {
// Allow alphanumeric, plus common address characters like '-' and '_'
allowedChars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
if !isValidInput(c.Wallet, allowedChars) {
return fmt.Errorf("wallet address contains invalid characters")
}
}
// Apply similar allowlist validation to all other string fields.
// ...
return nil
}
\`\`\`
### 2. Sanitize File Paths to Prevent Path Traversal
Sanitize any user-controllable input that is used to construct file paths. The `filepath.Clean` function and checks to ensure the path stays within an expected directory are essential.
**Code Example (`pkg/mining/manager.go`):**
\`\`\`go
import "path/filepath"
// In Manager.StartMiner():
// ...
instanceName := miner.GetName()
if config.Algo != "" {
// Sanitize algo to prevent directory traversal
sanitizedAlgo := instanceNameRegex.ReplaceAllString(config.Algo, "_")
// Also, explicitly remove any path-related characters that the regex might miss
sanitizedAlgo = strings.ReplaceAll(sanitizedAlgo, "/", "")
sanitizedAlgo = strings.ReplaceAll(sanitizedAlgo, "..", "")
instanceName = fmt.Sprintf("%s-%s", instanceName, sanitizedAlgo)
}
// ...
\`\`\`
### 3. Avoid Passing Raw CLI Arguments to `exec.Command`
The `CLIArgs` field is inherently dangerous. If it must be supported, it should be parsed and validated argument by argument, rather than being passed directly to the shell.
**Code Example (`pkg/mining/ttminer_start.go`):**
\`\`\`go
// In addTTMinerCliArgs():
func addTTMinerCliArgs(config *Config, args *[]string) {
if config.CLIArgs != "" {
// A safer approach is to define a list of allowed arguments
allowedArgs := map[string]bool{
"--list-devices": true,
"--no-watchdog": true,
// Add other safe, non-sensitive arguments here
}
extraArgs := strings.Fields(config.CLIArgs)
for _, arg := range extraArgs {
if allowedArgs[arg] {
*args = append(*args, arg)
} else {
logging.Warn("skipping potentially unsafe CLI argument", logging.Fields{"arg": arg})
}
}
}
}
\`\`\`
### 4. Expand Validation Coverage in `Config.Validate()`
All fields in the `Config` struct should have some form of validation. For string fields, this should be allowlist-based character validation. For numeric fields, this should be range checking.
**Code Example (`pkg/mining/mining.go`):**
\`\`\`go
// In Config.Validate():
// ...
// Example for LogFile
if c.LogFile != "" {
// Basic validation: ensure it's just a filename, not a path
if strings.Contains(c.LogFile, "/") || strings.Contains(c.LogFile, "\\") {
return fmt.Errorf("LogFile cannot be a path")
}
// Use an allowlist for the filename itself
allowedChars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_."
if !isValidInput(c.LogFile, allowedChars) {
return fmt.Errorf("LogFile contains invalid characters")
}
}
// Example for CPUPriority
if c.CPUPriority < 0 || c.CPUPriority > 5 {
return fmt.Errorf("CPUPriority must be between 0 and 5")
}
// ...
\`\`\`

View file

@ -1,71 +0,0 @@
# Memory and Resource Management Audit
This audit examines the application's memory and resource management based on a review of the codebase, with a focus on `pkg/mining/manager.go`, `pkg/mining/service.go`, and `pkg/database/database.go`.
## 1. Goroutine Leak Analysis
The application uses several long-running goroutines for background tasks. Overall, goroutine lifecycle management is robust, but there are minor areas for improvement.
### Findings:
- **Stats Collection (`manager.go`):** The `startStatsCollection` goroutine runs in a `for` loop with a `time.Ticker`. It reliably terminates when the `stopChan` is closed during `Manager.Stop()`.
- **Database Cleanup (`manager.go`):** The `startDBCleanup` goroutine also uses a `time.Ticker` and correctly listens for the `stopChan` signal, ensuring it exits cleanly.
- **WebSocket Event Hub (`service.go`):** The `EventHub.Run` method is launched as a goroutine and manages client connections. It terminates when its internal `quit` channel is closed, which is triggered by the `EventHub.Stop()` method.
### Recommendations:
- **No major issues found.** The use of `stopChan` and `sync.WaitGroup` in `Manager` provides a solid foundation for graceful shutdowns.
## 2. Memory Leak Analysis
The primary areas of concern for memory leaks are in-memory data structures that could grow indefinitely.
### Findings:
- **`Manager.miners` Map:** The `miners` map in the `Manager` struct stores active miner processes. Entries are added in `StartMiner` and removed in `StopMiner` and `UninstallMiner`. If a miner process were to crash or become unresponsive without `StopMiner` being called, its entry would persist in the map, causing a minor memory leak.
- **In-Memory Hashrate History:** Each miner maintains an in-memory `HashrateHistory`. The `ReduceHashrateHistory` method is called periodically to trim this data, preventing unbounded growth. This is a good practice.
- **Request Body Size Limit:** The `service.go` file correctly implements a 1MB request body size limit, which helps prevent memory exhaustion from large API requests.
### Recommendations:
- **Implement a health check for miners.** A periodic health check could detect unresponsive miner processes and trigger their removal from the `miners` map, preventing memory leaks from orphaned entries.
## 3. Database Resource Management
The application uses an SQLite database for persisting historical data.
### Findings:
- **Connection Pooling:** The `database.go` file configures the connection pool with `SetMaxOpenConns(1)`. This is appropriate for SQLite's single-writer model and prevents connection-related issues.
- **`hashrate_history` Cleanup:** The `Cleanup` function in `database.go` correctly removes old records from the `hashrate_history` table based on the configured retention period.
- **`miner_sessions` Table:** The `miner_sessions` table tracks miner uptime but has no corresponding cleanup mechanism. This table will grow indefinitely, leading to a gradual increase in database size and a potential performance degradation over time.
### Recommendations:
- **Add a cleanup mechanism for `miner_sessions`.** Extend the `Cleanup` function to also remove old records from the `miner_sessions` table based on the retention period.
## 4. File Handle and Process Management
The application manages external miner processes, which requires careful handling of file descriptors and process handles.
### Findings:
- **Process Lifecycle:** The `Stop` method on miner implementations (`xmrig.go`, `ttminer.go`) is responsible for terminating the `exec.Cmd` process. This appears to be handled correctly.
- **I/O Pipes:** The miner's `stdout`, `stderr`, and `stdin` pipes are created and managed. The code does not show any obvious leaks of these file handles.
### Recommendations:
- **No major issues found.** The process management logic appears to be sound.
## 5. Network Connection Handling
The application's API server and WebSocket endpoint are critical areas for resource management.
### Findings:
- **HTTP Server Timeouts:** The `service.go` file correctly configures `ReadTimeout`, `WriteTimeout`, and `IdleTimeout` for the HTTP server, which is a best practice for preventing slow client attacks and connection exhaustion.
- **WebSocket Connections:** The `wsUpgrader` has a `CheckOrigin` function that restricts connections to `localhost` origins, providing a layer of security. The `EventHub` manages the lifecycle of WebSocket connections.
### Recommendations:
- **No major issues found.** The network connection handling is well-configured.

View file

@ -1,40 +0,0 @@
# Performance Audit Report
This report details the findings of a performance audit conducted on the codebase. It covers several areas, including database performance, memory usage, concurrency, API performance, and build/deploy performance.
## Database Performance
The application uses SQLite with WAL (Write-Ahead Logging) enabled, which is a good choice for the application's needs, as it allows for concurrent reads and writes. The database schema is well-defined, and the indexes on the `hashrate_history` and `miner_sessions` tables are appropriate for the queries being performed.
- **N+1 Queries:** No evidence of N+1 queries was found. The database interactions are straightforward and do not involve complex object relational mapping.
- **Missing Indexes:** The existing indexes are well-suited for the application's queries. No missing indexes were identified.
- **Large Result Sets:** The history endpoints could potentially return large result sets. Implementing pagination would be a good proactive measure to prevent performance degradation as the data grows.
- **Inefficient Joins:** The database schema is simple and does not involve complex joins. No inefficient joins were identified.
- **Connection Pooling:** The connection pool is configured to use a single connection, which is appropriate for SQLite.
## Memory Usage
- **Memory Leaks:** No obvious memory leaks were identified. The application's memory usage appears to be stable.
- **Large Object Loading:** The log and history endpoints could potentially load large amounts of data into memory. Implementing streaming for these endpoints would be a good way to mitigate this.
- **Cache Efficiency:** The API uses a simple time-based cache for some endpoints, which is effective but could be improved. A more sophisticated caching mechanism, such as an LRU cache, could be used to improve cache efficiency.
- **Garbage Collection:** No issues with garbage collection were identified.
## Concurrency
- **Blocking Operations:** The `CheckInstallation` function in `xmrig.go` shells out to the command line, which is a blocking operation. This could be optimized by using a different method to check for the miner's presence.
- **Lock Contention:** The `Manager` uses a mutex to protect the `miners` map, which is good for preventing race conditions. However, the stats collection iterates over all miners and collects stats sequentially, which could be a bottleneck. This could be improved by collecting stats in parallel.
- **Thread Pool Sizing:** The application does not use a thread pool.
- **Async Opportunities:** The `build-all` target in the `Makefile` builds for multiple platforms sequentially. This could be parallelized to reduce build times. Similarly, the `before` hook in `.goreleaser.yaml` runs tests and UI builds sequentially, which could also be parallelized.
## API Performance
- **Response Times:** The API response times are generally good.
- **Payload Sizes:** The log and history endpoints could potentially return large payloads. Implementing response compression would be a good way to reduce payload sizes.
- **Caching Headers:** The API uses `Cache-Control` headers, which is good.
- **Rate Limiting:** The API has rate limiting in place, which is good.
## Build/Deploy Performance
- **Build Time:** The `build-all` target in the `Makefile` builds for multiple platforms sequentially. This could be parallelized to reduce build times. The `before` hook in `.goreleaser.yaml` runs tests and UI builds sequentially, which could also be parallelized.
- **Asset Size:** The UI assets are not minified or compressed, which could increase load times.
- **Cold Start:** The application has a fast cold start time.

View file

@ -1,72 +0,0 @@
# Mining Protocol Security Audit: AUDIT-PROTOCOL.md
## 1. Stratum Protocol Security
**Findings:**
- **Insecure Default Connections:** The miner defaults to `stratum+tcp`, transmitting data in plaintext. This exposes sensitive information, such as wallet addresses and passwords, to interception. An attacker with network access could easily capture and exploit this data.
- **Lack of Certificate Pinning:** Although TLS is an option, there is no mechanism for certificate pinning. Without it, the client cannot verify the authenticity of the pool's certificate, leaving it vulnerable to man-in-the-middle attacks where a malicious actor could impersonate the mining pool.
- **Vulnerability to Protocol-Level Attacks:** The Stratum protocol implementation does not adequately protect against attacks like share hijacking or difficulty manipulation. An attacker could potentially modify Stratum messages to redirect shares or disrupt the mining process.
**Recommendations:**
- **Enforce TLS by Default:** Mandate the use of `stratum+ssl` to ensure all communication between the miner and the pool is encrypted.
- **Implement Certificate Pinning:** Add support for certificate pinning to allow users to specify the expected certificate, preventing man-in-the-middle attacks.
- **Add Protocol-Level Integrity Checks:** Implement checksums or signatures for Stratum messages to ensure their integrity and prevent tampering.
## 2. Pool Authentication
**Findings:**
- **Credentials in Plaintext:** Authentication credentials, including the worker's username and password, are sent in plaintext over unencrypted connections. This makes them highly susceptible to theft.
- **Weak Password Hashing:** The `config.json` file stores the password as `"x"`, which is a weak default. While users can change this, there is no enforcement of strong password policies.
- **Risk of Brute-Force Attacks:** The absence of rate limiting or account lockout mechanisms on the pool side exposes the authentication process to brute-force attacks, where an attacker could repeatedly guess passwords until they gain access.
**Recommendations:**
- **Mandate Encrypted Authentication:** Require all authentication attempts to be transmitted over a TLS-encrypted connection.
- **Enforce Strong Password Policies:** Encourage the use of strong, unique passwords and consider implementing a password strength meter.
- **Implement Secure Authentication Mechanisms:** Support more secure authentication methods, such as token-based authentication, to reduce the reliance on passwords.
## 3. Share Validation
**Findings:**
- **Lack of Share Signatures:** Shares submitted by the miner are not cryptographically signed, making it possible for an attacker to intercept and modify them. This could lead to share stealing, where an attacker redirects a legitimate miner's work to their own account.
- **Vulnerability to Replay Attacks:** There is no protection against replay attacks, where an attacker could resubmit old shares. While pools may have some defenses, the client-side implementation lacks measures to prevent this.
**Recommendations:**
- **Implement Share Signing:** Introduce a mechanism for miners to sign each share with a unique key, allowing the pool to verify its authenticity.
- **Add Nonces to Shares:** Include a unique, single-use nonce in each share submission to prevent replay attacks.
## 4. Block Template Handling
**Findings:**
- **Centralized Block Template Distribution:** The miner relies on a centralized pool for block templates, creating a single point of failure. If the pool is compromised, an attacker could distribute malicious or inefficient templates.
- **No Template Validation:** The miner does not independently validate the block templates received from the pool. This makes it vulnerable to block withholding attacks, where a malicious pool sends invalid templates, causing the miner to waste resources on unsolvable blocks.
**Recommendations:**
- **Support Decentralized Template Distribution:** Explore decentralized alternatives for block template distribution to reduce reliance on a single pool.
- **Implement Independent Template Validation:** Add a mechanism for the miner to validate block templates against the network's consensus rules before starting to mine.
## 5. Network Message Validation
**Findings:**
- **Insufficient Input Sanitization:** Network messages from the pool are not consistently sanitized, creating a risk of denial-of-service attacks. An attacker could send malformed messages to crash the miner.
- **Lack of Rate Limiting:** The client does not implement rate limiting for incoming messages, making it vulnerable to flooding attacks that could overwhelm its resources.
**Recommendations:**
- **Implement Robust Message Sanitization:** Sanitize all incoming network messages to ensure they conform to the expected format and do not contain malicious payloads.
- **Add Rate Limiting:** Introduce rate limiting for incoming messages to prevent a single source from overwhelming the miner.

View file

@ -1,44 +0,0 @@
# Security Audit: Secrets & Configuration
This document outlines the findings of a security audit focused on exposed secrets and insecure configurations.
## 1. Secret Detection
### 1.1. Hardcoded Credentials & Sensitive Information
- **Placeholder Wallet Addresses:**
- `miner/core/src/config.json`: Contains the placeholder `"YOUR_WALLET_ADDRESS"`.
- `miner/proxy/src/config.json`: Contains the placeholder `"YOUR_WALLET"`.
- `miner/core/doc/api/1/config.json`: Contains a hardcoded wallet address.
- **Default Passwords:**
- `miner/core/src/config.json`: The `"pass"` field is set to `"x"`.
- `miner/proxy/src/config.json`: The `"pass"` field is set to `"x"`.
- `miner/core/doc/api/1/config.json`: The `"pass"` field is set to `"x"`.
- **Placeholder API Tokens:**
- `miner/core/doc/api/1/config.json`: The `"access-token"` is set to the placeholder `"TOKEN"`.
## 2. Configuration Security
### 2.1. Insecure Default Configurations
- **`null` API Access Tokens:**
- `miner/core/src/config.json`: The `http.access-token` is `null` by default. If the HTTP API is enabled without setting a token, it could allow unauthorized access.
- `miner/proxy/src/config.json`: The `http.access-token` is `null` by default, posing a similar risk.
- **TLS Disabled by Default:**
- `miner/core/src/config.json`: The `tls.enabled` flag is `false` by default. If services are exposed, communication would be unencrypted.
- `miner/proxy/src/config.json`: While `tls.enabled` is `true`, the `cert` and `cert_key` fields are `null`, preventing a secure TLS connection from being established.
### 2.2. Verbose Error Messages
No instances of overly verbose error messages leaking sensitive information were identified during this audit.
### 2.3. CORS Policy
The CORS policy could not be audited as it was not explicitly defined in the scanned files.
### 2.4. Security Headers
No security headers (e.g., CSP, HSTS) were identified in the configuration files.

File diff suppressed because it is too large Load diff

View file

@ -26,7 +26,7 @@
"private": true,
"dependencies": {
"@angular/common": "^20.3.0",
"@angular/compiler": "^20.3.16",
"@angular/compiler": "^20.3.0",
"@angular/core": "^20.3.0",
"@angular/forms": "^20.3.0",
"@angular/platform-browser": "^20.3.0",
@ -41,7 +41,7 @@
},
"devDependencies": {
"@angular/build": "^20.3.6",
"@angular/cli": "^20.3.13",
"@angular/cli": "^20.3.6",
"@angular/compiler-cli": "^20.3.0",
"@types/express": "^5.0.1",
"@types/jasmine": "~5.1.0",

View file

@ -74,8 +74,8 @@ require (
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.57.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.54.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/samber/lo v1.49.1 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
@ -91,9 +91,10 @@ require (
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/mock v0.5.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/crypto v0.44.0 // indirect
golang.org/x/mod v0.30.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect

View file

@ -167,10 +167,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.57.0 h1:AsSSrrMs4qI/hLrKlTH/TGQeTMY0ib1pAOX7vA3AdqE=
github.com/quic-go/quic-go v0.57.0/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@ -221,8 +221,8 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
@ -230,8 +230,8 @@ golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs=
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@ -278,8 +278,6 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

11
go.mod
View file

@ -7,7 +7,6 @@ require (
github.com/Snider/Borg v0.0.2
github.com/Snider/Poindexter v0.0.0-20251229183216-e182d4f49741
github.com/adrg/xdg v0.5.3
github.com/ckanthony/gin-mcp v0.0.0-20251107113615-3c631c4fa9f4
github.com/gin-contrib/cors v1.7.6
github.com/gin-gonic/gin v1.11.0
github.com/google/uuid v1.6.0
@ -18,7 +17,6 @@ require (
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.6
golang.org/x/text v0.31.0
)
require (
@ -27,6 +25,7 @@ require (
github.com/Snider/Enchantrix v0.0.2 // indirect
github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/ckanthony/gin-mcp v0.0.0-20251107113615-3c631c4fa9f4 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/ebitengine/purego v0.9.0 // indirect
@ -58,8 +57,8 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.57.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.54.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/pflag v1.0.9 // indirect
@ -68,13 +67,15 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/mock v0.5.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/crypto v0.44.0 // indirect
golang.org/x/mod v0.30.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/tools v0.38.0 // indirect
google.golang.org/protobuf v1.36.9 // indirect
)

18
go.sum
View file

@ -113,10 +113,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.57.0 h1:AsSSrrMs4qI/hLrKlTH/TGQeTMY0ib1pAOX7vA3AdqE=
github.com/quic-go/quic-go v0.57.0/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@ -155,16 +155,16 @@ github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2W
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
@ -200,8 +200,6 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

View file

@ -42,7 +42,7 @@
"history": "4.7.2",
"html-webpack-plugin": "3.1.0",
"immutability-helper": "2.6.6",
"lodash": "4.17.23",
"lodash": "4.17.15",
"random-id": "0.0.2",
"react": "16.2.0",
"react-autosize-textarea": "3.0.2",

View file

@ -22,15 +22,6 @@ else()
list(FILTER MINER_SOURCES EXCLUDE REGEX ".*_win\\.cpp$")
endif()
# Apply necessary compiler flags for specific files (copied from core/CMakeLists.txt)
if (CMAKE_CXX_COMPILER_ID MATCHES GNU OR CMAKE_CXX_COMPILER_ID MATCHES Clang)
set_source_files_properties(${CMAKE_SOURCE_DIR}/src/crypto/cn/CnHash.cpp PROPERTIES COMPILE_FLAGS "-Ofast -fno-tree-vectorize")
if (WITH_VAES)
set_source_files_properties(${CMAKE_SOURCE_DIR}/src/crypto/cn/CryptoNight_x86_vaes.cpp PROPERTIES COMPILE_FLAGS "-Ofast -fno-tree-vectorize -mavx2 -mvaes")
endif()
endif()
# Create a library with common test utilities and miner components
add_library(miner_test_lib STATIC
${MINER_SOURCES}

File diff suppressed because it is too large Load diff

View file

@ -57,7 +57,7 @@
"sass-loader": "^11.0.1",
"webpack": "^5.35.1",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^5.2.2",
"webpack-dev-server": "^3.11.2",
"webpack-subresource-integrity": "^1.5.2",
"whatwg-fetch": "^3.6.2"
}

View file

@ -6,6 +6,7 @@ import (
"path/filepath"
"runtime"
"testing"
"time"
)
// setupTestManager creates a new Manager and a dummy executable for tests.
@ -18,12 +19,12 @@ func setupTestManager(t *testing.T) *Manager {
}
dummyPath := filepath.Join(dummyDir, executableName)
// Create a script that prints version and exits
// Create a script that does nothing but exit, to simulate the miner executable
var script []byte
if runtime.GOOS == "windows" {
script = []byte("@echo off\necho XMRig 6.24.0\n")
script = []byte("@echo off\r\nexit 0")
} else {
script = []byte("#!/bin/sh\necho 'XMRig 6.24.0'\n")
script = []byte("#!/bin/sh\nexit 0")
}
if err := os.WriteFile(dummyPath, script, 0755); err != nil {
@ -42,7 +43,26 @@ func setupTestManager(t *testing.T) *Manager {
// TestStartMiner tests the StartMiner function
func TestStartMiner_Good(t *testing.T) {
t.Skip("Skipping test that runs miner process as per request")
m := setupTestManager(t)
defer m.Stop()
config := &Config{
HTTPPort: 9001, // Use a different port to avoid conflict
Pool: "test:1234",
Wallet: "testwallet",
}
// Case 1: Successfully start a supported miner
miner, err := m.StartMiner(context.Background(), "xmrig", config)
if err != nil {
t.Fatalf("Expected to start miner, but got error: %v", err)
}
if miner == nil {
t.Fatal("Expected miner to be non-nil, but it was")
}
if _, exists := m.miners[miner.GetName()]; !exists {
t.Errorf("Miner %s was not added to the manager's list", miner.GetName())
}
}
func TestStartMiner_Bad(t *testing.T) {
@ -63,12 +83,49 @@ func TestStartMiner_Bad(t *testing.T) {
}
func TestStartMiner_Ugly(t *testing.T) {
t.Skip("Skipping test that runs miner process")
m := setupTestManager(t)
defer m.Stop()
// Use an algorithm to get consistent instance naming (xmrig-test_algo)
// Without algo, each start gets a random suffix and won't be detected as duplicate
config := &Config{
HTTPPort: 9001, // Use a different port to avoid conflict
Pool: "test:1234",
Wallet: "testwallet",
Algo: "test_algo", // Consistent algo = consistent instance name
}
// Case 1: Successfully start a supported miner
_, err := m.StartMiner(context.Background(), "xmrig", config)
if err != nil {
t.Fatalf("Expected to start miner, but got error: %v", err)
}
// Case 3: Attempt to start a duplicate miner (same algo = same instance name)
_, err = m.StartMiner(context.Background(), "xmrig", config)
if err == nil {
t.Error("Expected an error when starting a duplicate miner, but got nil")
}
}
// TestStopMiner tests the StopMiner function
func TestStopMiner_Good(t *testing.T) {
t.Skip("Skipping test that runs miner process")
m := setupTestManager(t)
defer m.Stop()
config := &Config{
HTTPPort: 9002,
Pool: "test:1234",
Wallet: "testwallet",
}
// Case 1: Stop a running miner
miner, _ := m.StartMiner(context.Background(), "xmrig", config)
err := m.StopMiner(context.Background(), miner.GetName())
if err != nil {
t.Fatalf("Expected to stop miner, but got error: %v", err)
}
if _, exists := m.miners[miner.GetName()]; exists {
t.Errorf("Miner %s was not removed from the manager's list", miner.GetName())
}
}
func TestStopMiner_Bad(t *testing.T) {
@ -87,21 +144,20 @@ func TestGetMiner_Good(t *testing.T) {
m := setupTestManager(t)
defer m.Stop()
// Case 1: Get an existing miner (manually injected)
miner := NewXMRigMiner()
// Set name to match what StartMiner would produce usually ("xmrig")
// Since we inject it, we can use the default name or set one.
miner.Name = "xmrig-test"
m.mu.Lock()
m.miners["xmrig-test"] = miner
m.mu.Unlock()
config := &Config{
HTTPPort: 9003,
Pool: "test:1234",
Wallet: "testwallet",
}
retrievedMiner, err := m.GetMiner("xmrig-test")
// Case 1: Get an existing miner
startedMiner, _ := m.StartMiner(context.Background(), "xmrig", config)
retrievedMiner, err := m.GetMiner(startedMiner.GetName())
if err != nil {
t.Fatalf("Expected to get miner, but got error: %v", err)
}
if retrievedMiner.GetName() != "xmrig-test" {
t.Errorf("Expected to get miner 'xmrig-test', but got %s", retrievedMiner.GetName())
if retrievedMiner.GetName() != startedMiner.GetName() {
t.Errorf("Expected to get miner %s, but got %s", startedMiner.GetName(), retrievedMiner.GetName())
}
}
@ -125,16 +181,144 @@ func TestListMiners_Good(t *testing.T) {
initialMiners := m.ListMiners()
initialCount := len(initialMiners)
// Case 2: List miners when not empty (manually injected)
miner := NewXMRigMiner()
miner.Name = "xmrig-test"
m.mu.Lock()
m.miners["xmrig-test"] = miner
m.mu.Unlock()
finalMiners := m.ListMiners()
expectedCount := initialCount + 1
if len(finalMiners) != expectedCount {
t.Errorf("Expected %d miners, but got %d", expectedCount, len(finalMiners))
// Case 2: List miners after starting one - should have one more
config := &Config{
HTTPPort: 9004,
Pool: "test:1234",
Wallet: "testwallet",
}
_, _ = m.StartMiner(context.Background(), "xmrig", config)
miners := m.ListMiners()
if len(miners) != initialCount+1 {
t.Errorf("Expected %d miners (initial %d + 1), but got %d", initialCount+1, initialCount, len(miners))
}
}
// TestManagerStop_Idempotent tests that Stop() can be called multiple times safely
func TestManagerStop_Idempotent(t *testing.T) {
m := setupTestManager(t)
// Start a miner
config := &Config{
HTTPPort: 9010,
Pool: "test:1234",
Wallet: "testwallet",
}
_, _ = m.StartMiner(context.Background(), "xmrig", config)
// Call Stop() multiple times - should not panic
defer func() {
if r := recover(); r != nil {
t.Errorf("Stop() panicked: %v", r)
}
}()
m.Stop()
m.Stop()
m.Stop()
// If we got here without panicking, the test passes
}
// TestStartMiner_CancelledContext tests that StartMiner respects context cancellation
func TestStartMiner_CancelledContext(t *testing.T) {
m := setupTestManager(t)
defer m.Stop()
ctx, cancel := context.WithCancel(context.Background())
cancel() // Cancel immediately
config := &Config{
HTTPPort: 9011,
Pool: "test:1234",
Wallet: "testwallet",
}
_, err := m.StartMiner(ctx, "xmrig", config)
if err == nil {
t.Error("Expected error when starting miner with cancelled context")
}
if err != context.Canceled {
t.Errorf("Expected context.Canceled error, got: %v", err)
}
}
// TestStopMiner_CancelledContext tests that StopMiner respects context cancellation
func TestStopMiner_CancelledContext(t *testing.T) {
m := setupTestManager(t)
defer m.Stop()
ctx, cancel := context.WithCancel(context.Background())
cancel() // Cancel immediately
err := m.StopMiner(ctx, "nonexistent")
if err == nil {
t.Error("Expected error when stopping miner with cancelled context")
}
if err != context.Canceled {
t.Errorf("Expected context.Canceled error, got: %v", err)
}
}
// TestManagerEventHub tests that SetEventHub works correctly
func TestManagerEventHub(t *testing.T) {
m := setupTestManager(t)
defer m.Stop()
eventHub := NewEventHub()
go eventHub.Run()
defer eventHub.Stop()
m.SetEventHub(eventHub)
// Get initial miner count (may have autostarted miners)
initialCount := len(m.ListMiners())
// Start a miner - should emit events
config := &Config{
HTTPPort: 9012,
Pool: "test:1234",
Wallet: "testwallet",
}
_, err := m.StartMiner(context.Background(), "xmrig", config)
if err != nil {
t.Fatalf("Failed to start miner: %v", err)
}
// Give time for events to be processed
time.Sleep(50 * time.Millisecond)
// Verify miner count increased by 1
miners := m.ListMiners()
if len(miners) != initialCount+1 {
t.Errorf("Expected %d miners, got %d", initialCount+1, len(miners))
}
}
// TestManagerShutdownTimeout tests the graceful shutdown timeout
func TestManagerShutdownTimeout(t *testing.T) {
m := setupTestManager(t)
// Start a miner
config := &Config{
HTTPPort: 9013,
Pool: "test:1234",
Wallet: "testwallet",
}
_, _ = m.StartMiner(context.Background(), "xmrig", config)
// Stop should complete within a reasonable time
done := make(chan struct{})
go func() {
m.Stop()
close(done)
}()
select {
case <-done:
// Success - stopped in time
case <-time.After(15 * time.Second):
t.Error("Manager.Stop() took too long - possible shutdown issue")
}
}

View file

@ -1,6 +1,7 @@
package mining
import (
"context"
"testing"
)
@ -17,7 +18,25 @@ func TestNewManager(t *testing.T) {
}
func TestStartAndStopMiner(t *testing.T) {
t.Skip("Skipping test that attempts to run miner process")
manager := NewManager()
defer manager.Stop()
config := &Config{
Pool: "pool.example.com",
Wallet: "wallet123",
}
// We can't fully test StartMiner without a mock miner,
// but we can test the manager's behavior.
// This will fail because the miner executable is not present,
// which is expected in a test environment.
_, err := manager.StartMiner(context.Background(), "xmrig", config)
if err == nil {
t.Log("StartMiner did not fail as expected in test environment")
}
// Since we can't start a miner, we can't test stop either.
// A more complete test suite would use a mock miner.
}
func TestGetNonExistentMiner(t *testing.T) {

View file

@ -110,56 +110,68 @@ func TestXMRigMiner_GetLatestVersion_Bad(t *testing.T) {
}
func TestXMRigMiner_Start_Stop_Good(t *testing.T) {
t.Skip("Skipping test that runs miner process as per request")
}
func TestXMRigMiner_Start_Stop_Bad(t *testing.T) {
t.Skip("Skipping test that attempts to spawn miner process")
}
func TestXMRigMiner_CheckInstallation(t *testing.T) {
// Create a temporary directory for the dummy executable
tmpDir := t.TempDir()
// Use "miner" since that's what NewXMRigMiner() sets as ExecutableName
executableName := "miner"
dummyExePath := filepath.Join(tmpDir, "xmrig")
if runtime.GOOS == "windows" {
executableName += ".exe"
}
dummyExePath := filepath.Join(tmpDir, executableName)
if runtime.GOOS == "windows" {
// Create a dummy batch file that prints version
if err := os.WriteFile(dummyExePath, []byte("@echo off\necho XMRig 6.24.0\n"), 0755); err != nil {
dummyExePath += ".bat"
// Create a dummy batch file for Windows
if err := os.WriteFile(dummyExePath, []byte("@echo off\n"), 0755); err != nil {
t.Fatalf("failed to create dummy executable: %v", err)
}
} else {
// Create a dummy shell script that prints version
if err := os.WriteFile(dummyExePath, []byte("#!/bin/sh\necho 'XMRig 6.24.0'\n"), 0755); err != nil {
// Create a dummy shell script for other OSes
if err := os.WriteFile(dummyExePath, []byte("#!/bin/sh\n"), 0755); err != nil {
t.Fatalf("failed to create dummy executable: %v", err)
}
}
// Prepend tmpDir to PATH so findMinerBinary can find it
originalPath := os.Getenv("PATH")
t.Cleanup(func() { os.Setenv("PATH", originalPath) })
os.Setenv("PATH", tmpDir+string(os.PathListSeparator)+originalPath)
miner := NewXMRigMiner()
// Clear any binary path to force search
miner.MinerBinary = ""
miner.MinerBinary = dummyExePath
details, err := miner.CheckInstallation()
config := &Config{
Pool: "test:1234",
Wallet: "testwallet",
HTTPPort: 9999, // Required for API port assignment
}
err := miner.Start(config)
if err != nil {
t.Fatalf("CheckInstallation failed: %v", err)
t.Fatalf("Start() returned an error: %v", err)
}
if !details.IsInstalled {
t.Error("Expected IsInstalled to be true")
if !miner.Running {
t.Fatal("Miner is not running after Start()")
}
if details.Version != "6.24.0" {
t.Errorf("Expected version '6.24.0', got '%s'", details.Version)
err = miner.Stop()
if err != nil {
// On some systems, stopping a process that quickly exits can error. We log but don't fail.
t.Logf("Stop() returned an error (often benign in tests): %v", err)
}
// On Windows, the path might be canonicalized differently (e.g. 8.3 names), so checking Base is safer or full path equality if we trust os.Path
if filepath.Base(details.MinerBinary) != executableName {
t.Errorf("Expected binary name '%s', got '%s'", executableName, filepath.Base(details.MinerBinary))
// Give a moment for the process to be marked as not running
time.Sleep(100 * time.Millisecond)
miner.mu.Lock()
if miner.Running {
miner.mu.Unlock()
t.Fatal("Miner is still running after Stop()")
}
miner.mu.Unlock()
}
func TestXMRigMiner_Start_Stop_Bad(t *testing.T) {
miner := NewXMRigMiner()
miner.MinerBinary = "nonexistent"
config := &Config{
Pool: "test:1234",
Wallet: "testwallet",
}
err := miner.Start(config)
if err == nil {
t.Fatalf("Start() did not return an error")
}
}

View file

@ -1,39 +1,34 @@
package node
// pkg/node/dispatcher.go
/*
func (n *NodeManager) DispatchUEPS(pkt *ueps.ParsedPacket) error {
// 1. The "Threat" Circuit Breaker (L5 Guard)
if pkt.Header.ThreatScore > 50000 {
// High threat? Drop it. Don't even parse the payload.
// This protects the Agent from "semantic viruses"
return fmt.Errorf("packet rejected: threat score %d exceeds safety limit", pkt.Header.ThreatScore)
}
// 1. The "Threat" Circuit Breaker (L5 Guard)
if pkt.Header.ThreatScore > 50000 {
// High threat? Drop it. Don't even parse the payload.
// This protects the Agent from "semantic viruses"
return fmt.Errorf("packet rejected: threat score %d exceeds safety limit", pkt.Header.ThreatScore)
}
// 2. The "Intent" Router (L9 Semantic)
switch pkt.Header.IntentID {
case 0x01: // Handshake / Hello
// return n.handleHandshake(pkt)
return n.handleHandshake(pkt)
case 0x20: // Compute / Job Request
// "Hey, can you run this Docker container?"
// Check local resources first (Self-Validation)
// return n.handleComputeRequest(pkt.Payload)
return n.handleComputeRequest(pkt.Payload)
case 0x30: // Rehab / Intervention
// "Violet says you are hallucinating. Pause execution."
// This is the "Benevolent Intervention" Axiom.
// return n.enterRehabMode(pkt.Payload)
return n.enterRehabMode(pkt.Payload)
case 0xFF: // Extended / Custom
// Check the payload for specific sub-protocols (e.g. your JSON blobs)
// return n.handleApplicationData(pkt.Payload)
return n.handleApplicationData(pkt.Payload)
default:
return fmt.Errorf("unknown intent ID: 0x%X", pkt.Header.IntentID)
}
return nil
}
*/

View file

@ -706,3 +706,23 @@ func (r *PeerRegistry) load() error {
}
// Example usage inside a connection handler
func (n *NodeManager) SendEthicalPacket(peerID string, intent uint8, data []byte) error {
// 1. Get the shared secret for this specific peer (derived from ECDH)
secret, err := n.DeriveSharedSecret(peerID)
if err != nil {
return err
}
// 2. Construct the UEPS frame
// Intent 0x20 = e.g., "Distributed Compute"
pkt := ueps.NewBuilder(intent, data)
// 3. Seal it
wireBytes, err := pkt.MarshalAndSign(secret)
if err != nil {
return err
}
// 4. Send wireBytes over your TCP connection...
return nil
}

View file

@ -1,14 +1,11 @@
package node
import (
"encoding/base64"
"encoding/json"
"fmt"
"path/filepath"
"time"
"github.com/Snider/Mining/pkg/logging"
"github.com/adrg/xdg"
)
// MinerManager interface for the mining package integration.
@ -79,7 +76,7 @@ func (w *Worker) HandleMessage(conn *PeerConnection, msg *Message) {
case MsgGetLogs:
response, err = w.handleGetLogs(msg)
case MsgDeploy:
response, err = w.handleDeploy(conn, msg)
response, err = w.handleDeploy(msg)
default:
// Unknown message type - ignore or send error
return
@ -294,42 +291,24 @@ func (w *Worker) handleGetLogs(msg *Message) (*Message, error) {
}
// handleDeploy handles deployment of profiles or miner bundles.
func (w *Worker) handleDeploy(conn *PeerConnection, msg *Message) (*Message, error) {
func (w *Worker) handleDeploy(msg *Message) (*Message, error) {
var payload DeployPayload
if err := msg.ParsePayload(&payload); err != nil {
return nil, fmt.Errorf("invalid deploy payload: %w", err)
}
// Reconstruct Bundle object from payload
bundle := &Bundle{
Type: BundleType(payload.BundleType),
Name: payload.Name,
Data: payload.Data,
Checksum: payload.Checksum,
}
// Use shared secret as password (base64 encoded)
password := ""
if conn != nil && len(conn.SharedSecret) > 0 {
password = base64.StdEncoding.EncodeToString(conn.SharedSecret)
}
switch bundle.Type {
case BundleProfile:
// TODO: Implement STIM bundle decryption and installation
// For now, just handle raw profile JSON
switch payload.BundleType {
case "profile":
if w.profileManager == nil {
return nil, fmt.Errorf("profile manager not configured")
}
// Decrypt and extract profile data
profileData, err := ExtractProfileBundle(bundle, password)
if err != nil {
return nil, fmt.Errorf("failed to extract profile bundle: %w", err)
}
// Unmarshal into interface{} to pass to ProfileManager
// Decode the profile from the data
var profile interface{}
if err := json.Unmarshal(profileData, &profile); err != nil {
return nil, fmt.Errorf("invalid profile data JSON: %w", err)
if err := json.Unmarshal(payload.Data, &profile); err != nil {
return nil, fmt.Errorf("invalid profile data: %w", err)
}
if err := w.profileManager.SaveProfile(profile); err != nil {
@ -347,49 +326,13 @@ func (w *Worker) handleDeploy(conn *PeerConnection, msg *Message) (*Message, err
}
return msg.Reply(MsgDeployAck, ack)
case BundleMiner, BundleFull:
// Determine installation directory
// We use xdg.DataHome/lethean-desktop/miners/<bundle_name>
minersDir := filepath.Join(xdg.DataHome, "lethean-desktop", "miners")
installDir := filepath.Join(minersDir, payload.Name)
case "miner":
// TODO: Implement miner binary deployment via TIM bundles
return nil, fmt.Errorf("miner bundle deployment not yet implemented")
logging.Info("deploying miner bundle", logging.Fields{
"name": payload.Name,
"path": installDir,
"type": payload.BundleType,
})
// Extract miner bundle
minerPath, profileData, err := ExtractMinerBundle(bundle, password, installDir)
if err != nil {
return nil, fmt.Errorf("failed to extract miner bundle: %w", err)
}
// If the bundle contained a profile config, save it
if len(profileData) > 0 && w.profileManager != nil {
var profile interface{}
if err := json.Unmarshal(profileData, &profile); err != nil {
logging.Warn("failed to parse profile from miner bundle", logging.Fields{"error": err})
} else {
if err := w.profileManager.SaveProfile(profile); err != nil {
logging.Warn("failed to save profile from miner bundle", logging.Fields{"error": err})
}
}
}
// Success response
ack := DeployAckPayload{
Success: true,
Name: payload.Name,
}
// Log the installation
logging.Info("miner bundle installed successfully", logging.Fields{
"name": payload.Name,
"miner_path": minerPath,
})
return msg.Reply(MsgDeployAck, ack)
case "full":
// TODO: Implement full deployment (miner + profiles)
return nil, fmt.Errorf("full bundle deployment not yet implemented")
default:
return nil, fmt.Errorf("unknown bundle type: %s", payload.BundleType)

View file

@ -372,7 +372,7 @@ func TestWorker_HandleDeploy_Profile(t *testing.T) {
}
// Without profile manager, should return error
_, err = worker.handleDeploy(nil, msg)
_, err = worker.handleDeploy(msg)
if err == nil {
t.Error("expected error when profile manager is nil")
}
@ -413,7 +413,7 @@ func TestWorker_HandleDeploy_UnknownType(t *testing.T) {
t.Fatalf("failed to create deploy message: %v", err)
}
_, err = worker.handleDeploy(nil, msg)
_, err = worker.handleDeploy(msg)
if err == nil {
t.Error("expected error for unknown bundle type")
}

1053
ui/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -28,9 +28,9 @@
"private": true,
"dependencies": {
"@angular-devkit/build-angular": "^20.3.9",
"@angular/common": "^20.3.14",
"@angular/common": "^20.3.0",
"@angular/compiler": "^20.3.0",
"@angular/core": "^20.3.16",
"@angular/core": "^20.3.0",
"@angular/elements": "^20.3.10",
"@angular/forms": "^20.3.0",
"@angular/platform-browser": "^20.3.0",
@ -45,7 +45,7 @@
},
"devDependencies": {
"@angular/build": "^20.3.6",
"@angular/cli": "^20.3.13",
"@angular/cli": "^20.3.6",
"@angular/compiler-cli": "^20.3.0",
"@playwright/test": "^1.40.0",
"@tailwindcss/forms": "^0.5.11",

View file

@ -127,7 +127,8 @@ export class MainLayoutComponent implements AfterViewInit {
}
navigateToProfiles(profileId: string) {
this.router.navigate(['/', 'profiles'], { queryParams: { id: profileId } });
// TODO: Could pass profileId via query params or state
this.router.navigate(['/', 'profiles']);
}
navigateToConsole(minerName: string) {

View file

@ -1,6 +1,5 @@
import { Component, inject, signal, OnInit } from '@angular/core';
import { Component, inject, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ActivatedRoute } from '@angular/router';
import { MinerService } from '../../miner.service';
import { NotificationService } from '../../notification.service';
import { ProfileCreateComponent } from '../../profile-create.component';
@ -556,10 +555,9 @@ import { ProfileCreateComponent } from '../../profile-create.component';
}
`]
})
export class ProfilesComponent implements OnInit {
export class ProfilesComponent {
private minerService = inject(MinerService);
private notifications = inject(NotificationService);
private route = inject(ActivatedRoute);
state = this.minerService.state;
showCreateForm = signal(false);
@ -573,14 +571,6 @@ export class ProfilesComponent implements OnInit {
profiles = () => this.state().profiles;
ngOnInit() {
this.route.queryParams.subscribe(params => {
if (params['id']) {
this.editingProfileId.set(params['id']);
}
});
}
isRunning(profileId: string): boolean {
return this.state().runningMiners.some(m => m.profile_id === profileId);
}