cgminer_api_client 0.2.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +385 -0
- data/README.md +124 -43
- data/bin/cgminer_api_client +70 -9
- data/cgminer_api_client.gemspec +25 -6
- data/config/miners.yml.example +2 -1
- data/lib/cgminer_api_client/errors.rb +94 -0
- data/lib/cgminer_api_client/miner/commands.rb +31 -20
- data/lib/cgminer_api_client/miner.rb +123 -44
- data/lib/cgminer_api_client/miner_pool.rb +84 -31
- data/lib/cgminer_api_client/miner_result.rb +51 -0
- data/lib/cgminer_api_client/pool_result.rb +94 -0
- data/lib/cgminer_api_client/socket_with_timeout.rb +11 -5
- data/lib/cgminer_api_client/version.rb +3 -1
- data/lib/cgminer_api_client.rb +9 -7
- metadata +17 -26
- data/.gitignore +0 -16
- data/.rspec +0 -3
- data/.travis.yml +0 -5
- data/.whitesource +0 -8
- data/Gemfile +0 -10
- data/Rakefile +0 -7
- data/spec/cgminer_api_client/miner/commands_spec.rb +0 -501
- data/spec/cgminer_api_client/miner_pool_spec.rb +0 -134
- data/spec/cgminer_api_client/miner_spec.rb +0 -296
- data/spec/cgminer_api_client_spec.rb +0 -41
- data/spec/spec_helper.rb +0 -25
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 97da0040fa9d47c7d6a8157e53a30decf6e62ca216e3fb50af638dbcc6189d7c
|
|
4
|
+
data.tar.gz: aa30a0192cbf07991b90d904ecb476558ad509d15f001d0cc1f5b3daefc0f835
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 73458d32d4079dc3483967d9329cf5bbbb1be4a73cd6ef8e202e3872b36ae9c18eac60bfb5ce81befe575cb0085ec1fd6f49e7df28fd7386e432ad40f83d71a3
|
|
7
|
+
data.tar.gz: 7bcde1758bd4ce3a8f111c7d21fe931683158a284de7055d3eed1be3b91bafc1ee2f8694dbd53a14173383da35e5691f2be64091dd4dbf317e3a65dc66f70bbf
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.4.0] - 2026-04-25
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **`CgminerApiClient::AccessDeniedError`**, a subclass of `ApiError`
|
|
14
|
+
for the most commonly dispatched-on case (cgminer Code 45 + the
|
|
15
|
+
`access_denied?` local guard). Inherits from `ApiError` so existing
|
|
16
|
+
`rescue ApiError` clauses still catch it; callers wanting finer
|
|
17
|
+
dispatch use `rescue AccessDeniedError`. Constructor pins
|
|
18
|
+
`code: :access_denied` so the symbolic tag is consistent. The
|
|
19
|
+
wire-side `Miner#check_status` delegates to a new factory
|
|
20
|
+
`ApiError.for_status(c, msg)` that picks the right subclass
|
|
21
|
+
based on the integer Code.
|
|
22
|
+
- **Structured error codes on `CgminerApiClient::ApiError`.** Two new
|
|
23
|
+
reader methods alongside the existing `#message`:
|
|
24
|
+
`#cgminer_code` (the integer Code from cgminer's STATUS hash —
|
|
25
|
+
e.g., `45` for access denied — or `nil` if the wire integer was
|
|
26
|
+
discarded, as happens when the `access_denied?` local guard's
|
|
27
|
+
call to `privileged` hits the wire and the rescue inside
|
|
28
|
+
`privileged` drops the integer before re-raising) and `#code`
|
|
29
|
+
(a symbolic tag derived from the integer via
|
|
30
|
+
`ApiError::CGMINER_CODES`, falling back to `:unknown` for codes
|
|
31
|
+
not in the map). Callers can now `case e.code; when :access_denied`
|
|
32
|
+
instead of parsing English error strings. **Prefer `e.code` for
|
|
33
|
+
dispatch over `e.cgminer_code`** — `cgminer_code` can be nil even
|
|
34
|
+
when the symbolic tag is set. The map is intentionally
|
|
35
|
+
conservative — `14 → :invalid_command`, `45 → :access_denied`,
|
|
36
|
+
the codes observed against real cgminer wire fixtures.
|
|
37
|
+
Backward-compatible: `raise ApiError, "msg"` still works and
|
|
38
|
+
`e.message` is unchanged.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
- `ApiError` constructor now validates `cgminer_code:` is `Integer`
|
|
42
|
+
or `nil` and `code:` is `Symbol`, `String`, or `nil`. Bad input
|
|
43
|
+
raises `ArgumentError` with a clear message instead of silently
|
|
44
|
+
producing `code: :unknown` (string `cgminer_code`) or
|
|
45
|
+
`NoMethodError` deep in the constructor (`code: 42`). Wire-side
|
|
46
|
+
callers go through `ApiError.for_status` which coerces a
|
|
47
|
+
non-numeric Code to `nil` so dispatch falls through to `:unknown`.
|
|
48
|
+
- **`docs/logging.md`** — short stub stating that `cgminer_api_client`
|
|
49
|
+
is intentionally silent: no `Logger` module, no structured log
|
|
50
|
+
events. The library raises on failure and returns result objects
|
|
51
|
+
on success; callers (`cgminer_monitor`, `cgminer_manager`, the
|
|
52
|
+
operator CLIs) own the log call sites. Points at
|
|
53
|
+
`cgminer_monitor/docs/log_schema.md` for the cross-repo schema
|
|
54
|
+
contract and names the events (`poll.miner_failed`,
|
|
55
|
+
`poll.unexpected_error`) that surface api_client exception classes.
|
|
56
|
+
- **`bundle-audit` in CI** (`.github/workflows/ci.yml`). New `audit`
|
|
57
|
+
job runs `bundle exec bundle-audit check --update` on every push
|
|
58
|
+
and PR, gating merges on known CVEs in `Gemfile.lock`. Advisory
|
|
59
|
+
DB is refreshed on each run from `rubysec/ruby-advisory-db`. Also
|
|
60
|
+
available locally as `bundle exec rake audit`.
|
|
61
|
+
- **Dependabot config** (`.github/dependabot.yml`). Weekly bump PRs
|
|
62
|
+
for Bundler and GitHub Actions, with `open-pull-requests-limit: 3`
|
|
63
|
+
per ecosystem. `versioning-strategy: lockfile-only` on bundler, so
|
|
64
|
+
Gemfile / gemspec constraints are never auto-widened — humans
|
|
65
|
+
widen `~>` bounds intentionally. Targets `develop` so bumps flow
|
|
66
|
+
through the normal release cycle alongside feature work.
|
|
67
|
+
- **`-v` / `--verbose` flag on the `cgminer_api_client` CLI.** Logs the
|
|
68
|
+
JSON request and raw response to stderr, one line each, with a
|
|
69
|
+
`host:port` prefix so multi-miner fan-out output stays grep-able. The
|
|
70
|
+
formatted result still goes to stdout unchanged. Parsed via
|
|
71
|
+
`OptionParser#permute!`, so the flag works before or after the
|
|
72
|
+
command. Passwords in `addpool`, `setconfig`, `ascset`, and `pgaset`
|
|
73
|
+
are replaced with `[REDACTED]` in the log output (wire bytes are
|
|
74
|
+
unaffected).
|
|
75
|
+
- **`on_wire:` kwarg on `Miner#initialize` and `MinerPool#initialize`.**
|
|
76
|
+
Library-level hook used by the CLI's `-v` flag. Accepts a Proc of
|
|
77
|
+
shape `(direction, host, port, payload)` where direction is
|
|
78
|
+
`:request`, `:response`, or `:response_repaired`. Default `nil` is a
|
|
79
|
+
no-op; library code does not write to stderr itself.
|
|
80
|
+
|
|
81
|
+
### Changed
|
|
82
|
+
- **`Miner::Commands::Privileged#access_denied?`** now raises
|
|
83
|
+
`CgminerApiClient::ApiError` with message `'access denied'`
|
|
84
|
+
instead of a bare `RuntimeError` with message `'access_denied'`.
|
|
85
|
+
Callers using `rescue CgminerApiClient::Error` or
|
|
86
|
+
`rescue StandardError` are unaffected; only code that specifically
|
|
87
|
+
pattern-matched on `RuntimeError` from a privileged command on an
|
|
88
|
+
unprivileged miner needs to update.
|
|
89
|
+
- **`MinerPool#load_miners!`** now raises `CgminerApiClient::Error`
|
|
90
|
+
(was `RuntimeError`) when `config/miners.yml` is missing. Existing
|
|
91
|
+
`rescue StandardError` clauses still work.
|
|
92
|
+
- Test-support code (FakeCgminer, CgminerFixtures) extracted to the
|
|
93
|
+
shared `cgminer_test_support` gem. Spec references now use
|
|
94
|
+
`CgminerTestSupport::FakeCgminer` and
|
|
95
|
+
`CgminerTestSupport::Fixtures::SUMMARY` etc. `script/fake_cgminer`
|
|
96
|
+
is now a thin shim that delegates to `bundle exec fake_cgminer`;
|
|
97
|
+
operator muscle memory unchanged.
|
|
98
|
+
|
|
99
|
+
### Fixed
|
|
100
|
+
- **`MinerPool` no longer silently defaults a miners.yml entry
|
|
101
|
+
missing `host` to `CgminerApiClient.default_host`.** A typo'd
|
|
102
|
+
config entry like `{port: 4028}` used to quietly produce a Miner
|
|
103
|
+
pointing at `127.0.0.1`; now raises
|
|
104
|
+
`CgminerApiClient::Error "config/miners.yml: entry N is missing 'host'"`
|
|
105
|
+
on `MinerPool.new`.
|
|
106
|
+
|
|
107
|
+
## [0.3.0] - 2026-04-07
|
|
108
|
+
|
|
109
|
+
### Removed
|
|
110
|
+
- Support for Ruby 2.x and Ruby 3.1. The gem now requires Ruby 3.2 or higher.
|
|
111
|
+
- `Miner#query`'s short-circuit on `!available?`. `query` now calls
|
|
112
|
+
`perform_request` directly and lets `ConnectionError` propagate to
|
|
113
|
+
the caller. Previously an unreachable miner caused `query` to
|
|
114
|
+
return `nil` silently.
|
|
115
|
+
- `Miner#available?`'s `force_reload` parameter. The method no
|
|
116
|
+
longer caches, so the parameter is meaningless.
|
|
117
|
+
- `MinerPool#available_miners` and `MinerPool#unavailable_miners`
|
|
118
|
+
`force_reload` parameter. Same reason.
|
|
119
|
+
- The host:port-prefixed stderr warning inside `MinerPool#query`
|
|
120
|
+
(added earlier in the 0.3.0 cycle). Library code should not
|
|
121
|
+
print unsolicited; the error is now structurally available via
|
|
122
|
+
`PoolResult` and the CLI prints it explicitly.
|
|
123
|
+
- `pry` from the development dependencies (it was unused).
|
|
124
|
+
- `.travis.yml` (Travis CI is effectively deprecated for OSS Ruby).
|
|
125
|
+
- `.whitesource` (Mend Bolt for GitHub was sunset; if SCA is wanted
|
|
126
|
+
later, configure Dependabot via `.github/dependabot.yml`).
|
|
127
|
+
|
|
128
|
+
### Added
|
|
129
|
+
- **`CgminerApiClient::MinerResult`** — immutable value object
|
|
130
|
+
(backed by `Data.define`) wrapping a single per-miner outcome.
|
|
131
|
+
Has `miner`, `value`, `error`, `ok?`, `failed?`, `raise!`,
|
|
132
|
+
plus free `==`, `hash`, `eql?`, `inspect`, `to_h`, and
|
|
133
|
+
`deconstruct_keys` from `Data.define` for pattern matching.
|
|
134
|
+
- **`CgminerApiClient::PoolResult`** — Enumerable wrapper around
|
|
135
|
+
`Array<MinerResult>` returned from every `MinerPool` query.
|
|
136
|
+
Preserves miner order. High-level helpers: `#values`, `#errors`,
|
|
137
|
+
`#successful`, `#failed`, `#all_successful?`, `#any_succeeded?`,
|
|
138
|
+
`#any_failed?`, plus `#[](key)` for lookup by index, Miner
|
|
139
|
+
instance, or `"host:port"` string.
|
|
140
|
+
- `CgminerApiClient::Error`, `CgminerApiClient::ConnectionError`,
|
|
141
|
+
`CgminerApiClient::TimeoutError` (subclass of ConnectionError),
|
|
142
|
+
and `CgminerApiClient::ApiError` exception classes. All subclass
|
|
143
|
+
the base `Error` which itself subclasses `StandardError`, so
|
|
144
|
+
existing rescues keep working.
|
|
145
|
+
- `respond_to_missing?` on `Miner` and `MinerPool` so introspection
|
|
146
|
+
(`respond_to?`, `Object#method`, etc.) reflects the dynamic API
|
|
147
|
+
surface while excluding internal probes (`to_*`, `_*`).
|
|
148
|
+
- `# frozen_string_literal: true` pragma on every Ruby file.
|
|
149
|
+
- `required_ruby_version >= 3.2` in the gemspec.
|
|
150
|
+
- Gemspec metadata (`source_code_uri`, `changelog_uri`,
|
|
151
|
+
`bug_tracker_uri`, `rubygems_mfa_required`).
|
|
152
|
+
- RuboCop with `rubocop-rspec` and `rubocop-rake` plugins,
|
|
153
|
+
integrated into the default Rake task.
|
|
154
|
+
- GitHub Actions CI matrix testing Ruby 3.2, 3.3, 3.4, and 4.0
|
|
155
|
+
(plus `head` as an early-warning, allowed to fail).
|
|
156
|
+
- `.ruby-version` file pinning local development to 4.0.2 (the
|
|
157
|
+
gem itself supports 3.2+; the pin is only for contributors).
|
|
158
|
+
- `CHANGELOG.md` (this file).
|
|
159
|
+
- Unit test coverage brought to 99.66% on `lib/` (was 97.4% before
|
|
160
|
+
the 0.3.0 work). The entire `SocketWithTimeout#open_socket`
|
|
161
|
+
method, `MinerPool#available_miners`, `#unavailable_miners`,
|
|
162
|
+
the thread-rescue branch in `MinerPool#query`, the control-
|
|
163
|
+
character escape path in `Miner#perform_request`, and the new
|
|
164
|
+
`MinerResult` / `PoolResult` value objects all have dedicated
|
|
165
|
+
specs.
|
|
166
|
+
- End-to-end integration test suite at `spec/integration/`.
|
|
167
|
+
`miner_integration_spec.rb` exercises the full request → TCP
|
|
168
|
+
socket → response → parse → result path against a `FakeCgminer`
|
|
169
|
+
server running in a background thread. `cli_spec.rb` spawns
|
|
170
|
+
the real binary via `Open3` and asserts on exit codes,
|
|
171
|
+
stdout/stderr split, and `DEBUG=1` backtrace behavior.
|
|
172
|
+
- `script/fake_cgminer` for manual sandbox testing. Starts the
|
|
173
|
+
same fake cgminer server on a fixed port (default 4028) in the
|
|
174
|
+
foreground, so you can run the CLI against it without hardware.
|
|
175
|
+
Intentionally lives in `script/` rather than `bin/` so it isn't
|
|
176
|
+
packaged with the gem.
|
|
177
|
+
|
|
178
|
+
### Changed
|
|
179
|
+
- **`MinerPool#query` now returns a `PoolResult`** instead of an
|
|
180
|
+
`Array` with `[]` sentinels for failed miners. Callers iterate
|
|
181
|
+
`MinerResult` instances and explicitly distinguish success from
|
|
182
|
+
failure. Failed miners are no longer confused with successful
|
|
183
|
+
ones returning empty arrays.
|
|
184
|
+
- **`MinerPool` now overrides `summary`, `coin`, `config`,
|
|
185
|
+
`version`, and `check`** to return a `PoolResult` of unwrapped
|
|
186
|
+
hashes. Previously these convenience methods called
|
|
187
|
+
`query(:name)[0]`, which silently returned only the *first*
|
|
188
|
+
miner's hash on multi-miner pools (a pre-existing latent bug).
|
|
189
|
+
- **`Miner#query` now raises `ConnectionError`** instead of
|
|
190
|
+
returning `nil` when a miner is unreachable. Single-Miner
|
|
191
|
+
callers need to rescue; MinerPool callers get the error
|
|
192
|
+
captured in a `MinerResult.failure` automatically.
|
|
193
|
+
- **`Miner#available?` is now a true reachability probe.** No
|
|
194
|
+
cache, narrow rescue list (only `SocketError`,
|
|
195
|
+
`SystemCallError`, and `TimeoutError` — bugs like `ArgumentError`
|
|
196
|
+
propagate). Always re-checks.
|
|
197
|
+
- `SocketWithTimeout` raises `CgminerApiClient::TimeoutError`
|
|
198
|
+
instead of a bare `RuntimeError` on connect timeout.
|
|
199
|
+
- `Miner#perform_request` raises `ConnectionError` (not
|
|
200
|
+
`RuntimeError`) on socket open failure, with a message that
|
|
201
|
+
includes the original error class and message.
|
|
202
|
+
- `Miner#check_status` raises `ApiError` (not `RuntimeError`) for
|
|
203
|
+
cgminer status codes `E` and `F`.
|
|
204
|
+
- **`bin/cgminer_api_client` redesigned**: errors go to stderr
|
|
205
|
+
(not stdout), exit codes follow shell conventions (0 on any
|
|
206
|
+
success, 1 on all-failure, 64/EX_USAGE on unknown command),
|
|
207
|
+
`DEBUG=1` env var prints full backtraces via
|
|
208
|
+
`Exception#full_message`, and output is structured per-miner
|
|
209
|
+
with `host:port:` headers.
|
|
210
|
+
- `YAML.load_file` → `YAML.safe_load_file` for the miners config.
|
|
211
|
+
- `IO.select(nil, [socket], nil, timeout)` →
|
|
212
|
+
`socket.wait_writable(timeout)` in `SocketWithTimeout` —
|
|
213
|
+
Fiber-scheduler compatible.
|
|
214
|
+
- `String#match` → `String#match?` where the result is only used
|
|
215
|
+
as a boolean.
|
|
216
|
+
- `length == 0` → `empty?`, `'%04x' %` → `format`, and other
|
|
217
|
+
small modernizations.
|
|
218
|
+
- Bare `rescue` clauses now specify `StandardError` explicitly.
|
|
219
|
+
- Gemspec file list switched from `git ls-files` to explicit
|
|
220
|
+
`Dir.glob` patterns; `spec/` is no longer packaged in the gem.
|
|
221
|
+
- Bumped minimum versions of `rake`, `rspec`, and `simplecov`.
|
|
222
|
+
|
|
223
|
+
### Fixed
|
|
224
|
+
- **`Miner#query` parameter escape was a silent no-op for
|
|
225
|
+
backslashes.** The intent was to double literal backslashes so
|
|
226
|
+
they round-trip through cgminer's comma-separated parameter
|
|
227
|
+
syntax, but `gsub('\\', '\\\\')` is parsed as "replace `\` with
|
|
228
|
+
`\`" because in gsub's replacement-string DSL, `\\` denotes a
|
|
229
|
+
single literal backslash. Fixed by switching to
|
|
230
|
+
`gsub('\\') { '\\\\' }` (block form bypasses replacement-string
|
|
231
|
+
interpretation). Verified empirically; locked down by four
|
|
232
|
+
explicit specs.
|
|
233
|
+
- **`Miner::Commands#privileged` mislabeled connection errors as
|
|
234
|
+
access denied.** Previously rescued every `StandardError` and
|
|
235
|
+
returned `false`, which propagated through `access_denied?` to
|
|
236
|
+
raise `'access_denied'` from every privileged command
|
|
237
|
+
(`addpool`, `restart`, `quit`, `save`, `ascset`, etc.) during a
|
|
238
|
+
transient network outage — sending operators chasing phantom
|
|
239
|
+
auth/whitelist bugs. Now rescues only `ApiError` so
|
|
240
|
+
connection-layer failures surface as `ConnectionError` instead.
|
|
241
|
+
- **`MinerPool#summary` / `#coin` / `#config` / `#version` /
|
|
242
|
+
`#check` silently returned only the first miner's hash on
|
|
243
|
+
multi-miner pools.** The inherited `Miner::Commands::ReadOnly`
|
|
244
|
+
methods do `query(:name)[0]` to unwrap cgminer's single-element
|
|
245
|
+
response arrays, which works on a single Miner but drops every
|
|
246
|
+
result except the first on a pool. Fixed by overriding all five
|
|
247
|
+
on `MinerPool` to return a `PoolResult` of unwrapped hashes.
|
|
248
|
+
- **`Miner#available?` permanently cached `false` after any
|
|
249
|
+
transient failure.** Once a brief network blip had marked a
|
|
250
|
+
miner unavailable, the gem refused to talk to it for the
|
|
251
|
+
lifetime of the process. Fixed by dropping the cache entirely.
|
|
252
|
+
- Mismatched indentation in `def privileged` that was producing a
|
|
253
|
+
Ruby parser warning.
|
|
254
|
+
|
|
255
|
+
## Migration guide: 0.2.x → 0.3.0
|
|
256
|
+
|
|
257
|
+
This release intentionally contains breaking changes to finish
|
|
258
|
+
cleanups that were overdue. The gem is still on 0.x and only
|
|
259
|
+
~2,600 of 35,000 total downloads are on 0.2.6, so the
|
|
260
|
+
migration surface is small. Here's what to update.
|
|
261
|
+
|
|
262
|
+
### Ruby 3.2+
|
|
263
|
+
|
|
264
|
+
The gemspec now requires Ruby 3.2. If you're on 3.1 or older, you
|
|
265
|
+
need to upgrade Ruby before you can use this release.
|
|
266
|
+
|
|
267
|
+
### `MinerPool#query` (and all pool commands) return `PoolResult`
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
# Before (0.2.x)
|
|
271
|
+
pool.summary.each { |s| puts s[:mhs_av] }
|
|
272
|
+
pool.summary.first[:mhs_av]
|
|
273
|
+
|
|
274
|
+
# After (0.3.0)
|
|
275
|
+
pool.summary.values.each { |s| puts s[:mhs_av] }
|
|
276
|
+
pool.summary.values.first[:mhs_av]
|
|
277
|
+
|
|
278
|
+
# Or iterate per-miner with failure handling:
|
|
279
|
+
pool.summary.each do |result|
|
|
280
|
+
if result.ok?
|
|
281
|
+
puts "#{result.miner.host}: #{result.value[:mhs_av]}"
|
|
282
|
+
else
|
|
283
|
+
warn "#{result.miner.host}: #{result.error.message}"
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`PoolResult` includes `Enumerable`, so `.first`, `.map`, `.count`,
|
|
289
|
+
`.find` etc. all work — but now they yield `MinerResult`
|
|
290
|
+
instances, not raw hashes. Use `.values` to get just the
|
|
291
|
+
successful values as an Array.
|
|
292
|
+
|
|
293
|
+
### `Miner#query` raises `ConnectionError` instead of returning `nil`
|
|
294
|
+
|
|
295
|
+
```ruby
|
|
296
|
+
# Before (0.2.x)
|
|
297
|
+
result = miner.summary
|
|
298
|
+
return unless result # nil meant unreachable
|
|
299
|
+
puts result[:mhs_av]
|
|
300
|
+
|
|
301
|
+
# After (0.3.0)
|
|
302
|
+
begin
|
|
303
|
+
result = miner.summary
|
|
304
|
+
puts result[:mhs_av]
|
|
305
|
+
rescue CgminerApiClient::ConnectionError => e
|
|
306
|
+
warn "miner unreachable: #{e.message}"
|
|
307
|
+
end
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
If you're using `MinerPool` instead of `Miner` directly, you
|
|
311
|
+
don't need to change anything — `MinerPool` catches the
|
|
312
|
+
`ConnectionError` per miner and turns it into a
|
|
313
|
+
`MinerResult.failure`.
|
|
314
|
+
|
|
315
|
+
### `MinerPool#summary` etc. now return every miner, not just the first
|
|
316
|
+
|
|
317
|
+
If you were calling `pool.summary` on a multi-miner pool and
|
|
318
|
+
relying on getting back a single Hash, you were actually being
|
|
319
|
+
bitten by a pre-existing bug (the result was silently dropping
|
|
320
|
+
every miner except the first). Now you get a `PoolResult` with
|
|
321
|
+
one entry per miner. Use `.values.first` if you really want
|
|
322
|
+
just the first miner.
|
|
323
|
+
|
|
324
|
+
### `force_reload` parameter removed
|
|
325
|
+
|
|
326
|
+
```ruby
|
|
327
|
+
# Before (0.2.x)
|
|
328
|
+
pool.available_miners(true) # force re-check
|
|
329
|
+
miner.available?(true)
|
|
330
|
+
|
|
331
|
+
# After (0.3.0)
|
|
332
|
+
pool.available_miners # always re-checks
|
|
333
|
+
miner.available?
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
There's no cache to flush anymore.
|
|
337
|
+
|
|
338
|
+
### `MinerPool#query` no longer writes to stderr
|
|
339
|
+
|
|
340
|
+
Previously, a failed per-miner query would print
|
|
341
|
+
`[host:port] ErrorClass: message` to stderr as a side effect of
|
|
342
|
+
calling `pool.query`. That was an earlier 0.3.0 addition; it's
|
|
343
|
+
removed because library code shouldn't print. The error is now
|
|
344
|
+
carried structurally in the `MinerResult.failure` inside the
|
|
345
|
+
`PoolResult`, and you can display it however you want. The CLI
|
|
346
|
+
(`bin/cgminer_api_client`) does display it on stderr.
|
|
347
|
+
|
|
348
|
+
### CLI exit codes changed
|
|
349
|
+
|
|
350
|
+
```
|
|
351
|
+
# Before (0.2.x)
|
|
352
|
+
cgminer_api_client summary # exit 0 whether or not it worked
|
|
353
|
+
# errors written to stdout
|
|
354
|
+
|
|
355
|
+
# After (0.3.0)
|
|
356
|
+
cgminer_api_client summary # exit 0 if any miner succeeded,
|
|
357
|
+
# 1 if all failed,
|
|
358
|
+
# 64 for unknown command.
|
|
359
|
+
# errors on stderr.
|
|
360
|
+
DEBUG=1 cgminer_api_client summary # also prints full backtraces
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
If you had a shell pipeline that captured the CLI's stdout
|
|
364
|
+
expecting the whole data blob (including errors), your script
|
|
365
|
+
needs updating. Errors now go to stderr like they should.
|
|
366
|
+
|
|
367
|
+
### Exception class hierarchy
|
|
368
|
+
|
|
369
|
+
All gem-specific errors now inherit from
|
|
370
|
+
`CgminerApiClient::Error < StandardError`:
|
|
371
|
+
|
|
372
|
+
```
|
|
373
|
+
CgminerApiClient::Error
|
|
374
|
+
├── CgminerApiClient::ConnectionError
|
|
375
|
+
│ └── CgminerApiClient::TimeoutError
|
|
376
|
+
└── CgminerApiClient::ApiError
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Existing `rescue StandardError` clauses keep working. If you
|
|
380
|
+
want to catch everything the gem raises, `rescue CgminerApiClient::Error`
|
|
381
|
+
now works.
|
|
382
|
+
|
|
383
|
+
## [0.2.6] - earlier
|
|
384
|
+
|
|
385
|
+
See git history for changes prior to 0.3.0.
|
data/README.md
CHANGED
|
@@ -1,28 +1,15 @@
|
|
|
1
|
-
# CgminerApiClient
|
|
1
|
+
# CgminerApiClient
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/jramos/cgminer_api_client/actions/workflows/ci.yml)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- Ruby (~> 2.0.0, ~> 2.1.0)
|
|
8
|
-
- YAML
|
|
9
|
-
- JSON
|
|
10
|
-
- Socket
|
|
11
|
-
- Thread
|
|
12
|
-
- cgminer (~> 3.12.0)
|
|
5
|
+
A gem that allows sending API commands to a pool of [cgminer](https://github.com/ckolivas/cgminer) instances. Ships as both a Ruby library and a CLI (`cgminer_api_client <command>`). Zero runtime dependencies beyond the Ruby standard library.
|
|
13
6
|
|
|
14
|
-
##
|
|
7
|
+
## Requirements
|
|
15
8
|
|
|
16
|
-
|
|
9
|
+
Ruby 3.2 or higher.
|
|
17
10
|
|
|
18
11
|
## Installation Options
|
|
19
12
|
|
|
20
|
-
### Bundler
|
|
21
|
-
|
|
22
|
-
Add the following to your `Gemfile`:
|
|
23
|
-
|
|
24
|
-
gem 'cgminer_api_client', '~> 0.2.6'
|
|
25
|
-
|
|
26
13
|
### RubyGems
|
|
27
14
|
|
|
28
15
|
$ gem install cgminer_api_client
|
|
@@ -32,18 +19,18 @@ Add the following to your `Gemfile`:
|
|
|
32
19
|
$ git clone git@github.com:jramos/cgminer_api_client.git
|
|
33
20
|
$ cd cgminer_api_client
|
|
34
21
|
$ gem build cgminer_api_client.gemspec
|
|
35
|
-
$ gem install cgminer_api_client
|
|
22
|
+
$ gem install cgminer_api_client-<VERSION>.gem
|
|
36
23
|
|
|
37
24
|
## Configuration
|
|
38
25
|
|
|
39
26
|
Copy [`config/miners.yml.example`](https://github.com/jramos/cgminer_api_client/blob/master/config/miners.yml.example) to `config/miners.yml` and update with the IP addresses (and optional ports and timeouts) of your cgminer instances. E.g.:
|
|
40
27
|
|
|
41
|
-
# connect to localhost on
|
|
28
|
+
# connect to localhost on default port (4028) with default timeout (5 seconds)
|
|
42
29
|
- host: 127.0.0.1
|
|
43
|
-
# connect to 192.168.1.1 on
|
|
30
|
+
# connect to 192.168.1.1 on port (1234) with custom timeout (3 seconds)
|
|
44
31
|
- host: 192.168.1.1
|
|
45
32
|
port: 1234
|
|
46
|
-
timeout:
|
|
33
|
+
timeout: 3
|
|
47
34
|
|
|
48
35
|
### Remote API Access
|
|
49
36
|
|
|
@@ -75,35 +62,122 @@ Restart cgminer:
|
|
|
75
62
|
|
|
76
63
|
## Gem Usage
|
|
77
64
|
|
|
78
|
-
|
|
65
|
+
```ruby
|
|
66
|
+
require 'cgminer_api_client'
|
|
67
|
+
|
|
68
|
+
# Change the defaults for any miners whose config doesn't set them.
|
|
69
|
+
CgminerApiClient.config do |config|
|
|
70
|
+
config.default_port = 4028
|
|
71
|
+
config.default_timeout = 3
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
pool = CgminerApiClient::MinerPool.new
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Pool queries return a `PoolResult`
|
|
78
|
+
|
|
79
|
+
Every pool query returns a `PoolResult` — an `Enumerable` wrapper
|
|
80
|
+
around one `MinerResult` per miner, in pool order. Each
|
|
81
|
+
`MinerResult` is either a success (carrying a parsed value) or a
|
|
82
|
+
failure (carrying the exception). Callers choose how much detail
|
|
83
|
+
they care about.
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
# Just give me the data, ignore failures:
|
|
87
|
+
pool.summary.values.each do |s|
|
|
88
|
+
puts "hashrate: #{s[:mhs_av]}"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Handle successes and failures explicitly:
|
|
92
|
+
pool.summary.each do |result|
|
|
93
|
+
if result.ok?
|
|
94
|
+
puts "#{result.miner.host}: #{result.value[:mhs_av]}"
|
|
95
|
+
else
|
|
96
|
+
warn "#{result.miner.host}: #{result.error.message}"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Quick checks:
|
|
101
|
+
pool.summary.all_successful? # true if every miner responded
|
|
102
|
+
pool.summary.any_failed? # true if any miner failed
|
|
103
|
+
pool.summary.errors # [<ConnectionError>, ...]
|
|
104
|
+
pool.summary['10.0.0.5:4028'] # lookup by host:port string
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Single-miner access
|
|
108
|
+
|
|
109
|
+
If you want to talk to one specific miner without the pool
|
|
110
|
+
wrapping, use `Miner` directly. Unreachable miners raise
|
|
111
|
+
`CgminerApiClient::ConnectionError`:
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
miner = CgminerApiClient::Miner.new('10.0.0.5', 4028)
|
|
115
|
+
begin
|
|
116
|
+
puts miner.summary[:mhs_av]
|
|
117
|
+
rescue CgminerApiClient::ConnectionError => e
|
|
118
|
+
warn "miner unreachable: #{e.message}"
|
|
119
|
+
end
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Privileged commands
|
|
123
|
+
|
|
124
|
+
Commands like `restart`, `quit`, `save`, `addpool`, `removepool`,
|
|
125
|
+
`ascset`, etc. require privileged API access on the cgminer side.
|
|
126
|
+
They propagate `CgminerApiClient::ApiError` on rejection and
|
|
127
|
+
`CgminerApiClient::ConnectionError` on network failure — two
|
|
128
|
+
distinct conditions, unlike in 0.2.x where they were conflated.
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
pool.restart # PoolResult of per-miner outcomes
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Errors
|
|
135
|
+
|
|
136
|
+
All gem-specific errors descend from `CgminerApiClient::Error < StandardError`:
|
|
137
|
+
|
|
138
|
+
- `CgminerApiClient::ConnectionError` — transport-level: the miner
|
|
139
|
+
was unreachable (DNS failure, connection refused, etc.).
|
|
140
|
+
- `CgminerApiClient::TimeoutError` — a `ConnectionError` subclass
|
|
141
|
+
for connect timeouts specifically.
|
|
142
|
+
- `CgminerApiClient::ApiError` — protocol-level: the miner answered
|
|
143
|
+
and returned a `STATUS=E`/`F` response.
|
|
144
|
+
|
|
145
|
+
`rescue CgminerApiClient::Error` catches everything gem-specific.
|
|
146
|
+
`rescue CgminerApiClient::ConnectionError` catches both generic
|
|
147
|
+
transport failures and connect timeouts. A `MinerPool` query never
|
|
148
|
+
raises per-miner errors — they land on the corresponding
|
|
149
|
+
`MinerResult.failure` inside the returned `PoolResult`.
|
|
79
150
|
|
|
80
|
-
|
|
81
|
-
CgminerApiClient.config do |config|
|
|
82
|
-
config.default_port = 4023
|
|
83
|
-
config.default_timeout = 3
|
|
84
|
-
end
|
|
151
|
+
## CLI Usage
|
|
85
152
|
|
|
86
|
-
|
|
153
|
+
API commands can be sent to your miner pool from the command line.
|
|
87
154
|
|
|
88
|
-
|
|
89
|
-
devices = pool.devs
|
|
155
|
+
$ cgminer_api_client <command> (<arguments>)
|
|
90
156
|
|
|
91
|
-
|
|
92
|
-
summaries = pool.summary
|
|
157
|
+
### Exit Codes and Streams
|
|
93
158
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
end
|
|
159
|
+
- exit `0` — at least one miner's command succeeded.
|
|
160
|
+
- exit `1` — every miner failed, or a top-level exception bubbled up.
|
|
161
|
+
- exit `64` — unknown command or missing command argument (`EX_USAGE`).
|
|
98
162
|
|
|
99
|
-
|
|
100
|
-
|
|
163
|
+
Per-miner responses are printed to stdout with a `host:port:` header.
|
|
164
|
+
Per-miner errors are printed to stderr as
|
|
165
|
+
`host:port: ErrorClass: message`. Set `DEBUG=1` to also print full
|
|
166
|
+
backtraces for any top-level exception:
|
|
101
167
|
|
|
102
|
-
|
|
168
|
+
$ DEBUG=1 cgminer_api_client summary
|
|
103
169
|
|
|
104
|
-
|
|
170
|
+
Pass `-v` / `--verbose` to log the JSON request and raw response to
|
|
171
|
+
stderr for wire-level debugging. Each line carries a `host:port`
|
|
172
|
+
prefix so fan-out across multiple miners stays grep-able:
|
|
105
173
|
|
|
106
|
-
$ cgminer_api_client
|
|
174
|
+
$ cgminer_api_client -v summary
|
|
175
|
+
>>> 10.0.0.1:4028 {"command":"summary"}
|
|
176
|
+
<<< 10.0.0.1:4028 {"STATUS":[{"STATUS":"S",...}],"SUMMARY":[...]}
|
|
177
|
+
|
|
178
|
+
Password-bearing arguments to `addpool`, `setconfig`, `ascset`, and
|
|
179
|
+
`pgaset` are replaced with `[REDACTED]` in the log output (the real
|
|
180
|
+
value is still sent on the wire).
|
|
107
181
|
|
|
108
182
|
### Commands & Arguments
|
|
109
183
|
|
|
@@ -169,6 +243,13 @@ The following privileged miner and pool commands are currently available:
|
|
|
169
243
|
|
|
170
244
|
Any cgminer API commands not explictly defined above are implemented using `method_missing`. A complete list of available API commands and options can be found in the [cgminer API-README](https://github.com/ckolivas/cgminer/blob/master/API-README).
|
|
171
245
|
|
|
246
|
+
## Further Reading
|
|
247
|
+
|
|
248
|
+
- [`CHANGELOG.md`](CHANGELOG.md) — release history and the 0.2.x → 0.3.0 migration guide.
|
|
249
|
+
- [`AGENTS.md`](AGENTS.md) — context for AI coding assistants; also a useful conventions-and-extension guide for human contributors.
|
|
250
|
+
- [`docs/`](docs/) — topic-split deep dives on architecture, components, interfaces, data models, workflows, and dependencies. Start with [`docs/index.md`](docs/index.md).
|
|
251
|
+
- [cgminer API-README](https://github.com/ckolivas/cgminer/blob/master/API-README) — upstream documentation for the JSON API surface this gem wraps.
|
|
252
|
+
|
|
172
253
|
## Contributing
|
|
173
254
|
|
|
174
255
|
1. Fork it ( <https://github.com/jramos/cgminer_api_client/fork> )
|
|
@@ -181,7 +262,7 @@ Any cgminer API commands not explictly defined above are implemented using `meth
|
|
|
181
262
|
|
|
182
263
|
If you find this gem useful, please consider donating.
|
|
183
264
|
|
|
184
|
-
BTC: `
|
|
265
|
+
BTC: `bc1q00genlpcpcglgd4rezqcurf4t4taz0acmm9vea`
|
|
185
266
|
|
|
186
267
|
## License
|
|
187
268
|
|