cgminer_api_client 0.2.6 → 0.3.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 +286 -0
- data/README.md +78 -46
- data/bin/cgminer_api_client +28 -9
- data/cgminer_api_client.gemspec +25 -6
- data/config/miners.yml.example +2 -1
- data/lib/cgminer_api_client/errors.rb +23 -0
- data/lib/cgminer_api_client/miner/commands.rb +23 -20
- data/lib/cgminer_api_client/miner.rb +60 -39
- data/lib/cgminer_api_client/miner_pool.rb +72 -26
- 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: 129410d116f95439ef734a1ae6265c047eb7f95007755e5788fc416e78741388
|
|
4
|
+
data.tar.gz: 386bd4f91ce093e290777a1b00be6efaa173f03f951b8bcc00d4b8683ce0c576
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d7ebd6316421e95755768cf0f77dd672e3860c2fd508fd2314cc898f46b9c76d39efc78d3b1f4ac422c873871e1896b5fb8859e681c8ff3712f5f60f12c85351
|
|
7
|
+
data.tar.gz: 99a4578907f13668e8f0bf5cf466298f40774e318d3f470ad38df7f0cfa2455b51c028866084a264201156b6e30505daf7849e1d6a12aec4122ec5092d788fed
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
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
|
+
## [0.3.0] - 2026-04-07
|
|
9
|
+
|
|
10
|
+
### Removed
|
|
11
|
+
- Support for Ruby 2.x and Ruby 3.1. The gem now requires Ruby 3.2 or higher.
|
|
12
|
+
- `Miner#query`'s short-circuit on `!available?`. `query` now calls
|
|
13
|
+
`perform_request` directly and lets `ConnectionError` propagate to
|
|
14
|
+
the caller. Previously an unreachable miner caused `query` to
|
|
15
|
+
return `nil` silently.
|
|
16
|
+
- `Miner#available?`'s `force_reload` parameter. The method no
|
|
17
|
+
longer caches, so the parameter is meaningless.
|
|
18
|
+
- `MinerPool#available_miners` and `MinerPool#unavailable_miners`
|
|
19
|
+
`force_reload` parameter. Same reason.
|
|
20
|
+
- The host:port-prefixed stderr warning inside `MinerPool#query`
|
|
21
|
+
(added earlier in the 0.3.0 cycle). Library code should not
|
|
22
|
+
print unsolicited; the error is now structurally available via
|
|
23
|
+
`PoolResult` and the CLI prints it explicitly.
|
|
24
|
+
- `pry` from the development dependencies (it was unused).
|
|
25
|
+
- `.travis.yml` (Travis CI is effectively deprecated for OSS Ruby).
|
|
26
|
+
- `.whitesource` (Mend Bolt for GitHub was sunset; if SCA is wanted
|
|
27
|
+
later, configure Dependabot via `.github/dependabot.yml`).
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- **`CgminerApiClient::MinerResult`** — immutable value object
|
|
31
|
+
(backed by `Data.define`) wrapping a single per-miner outcome.
|
|
32
|
+
Has `miner`, `value`, `error`, `ok?`, `failed?`, `raise!`,
|
|
33
|
+
plus free `==`, `hash`, `eql?`, `inspect`, `to_h`, and
|
|
34
|
+
`deconstruct_keys` from `Data.define` for pattern matching.
|
|
35
|
+
- **`CgminerApiClient::PoolResult`** — Enumerable wrapper around
|
|
36
|
+
`Array<MinerResult>` returned from every `MinerPool` query.
|
|
37
|
+
Preserves miner order. High-level helpers: `#values`, `#errors`,
|
|
38
|
+
`#successful`, `#failed`, `#all_successful?`, `#any_succeeded?`,
|
|
39
|
+
`#any_failed?`, plus `#[](key)` for lookup by index, Miner
|
|
40
|
+
instance, or `"host:port"` string.
|
|
41
|
+
- `CgminerApiClient::Error`, `CgminerApiClient::ConnectionError`,
|
|
42
|
+
`CgminerApiClient::TimeoutError` (subclass of ConnectionError),
|
|
43
|
+
and `CgminerApiClient::ApiError` exception classes. All subclass
|
|
44
|
+
the base `Error` which itself subclasses `StandardError`, so
|
|
45
|
+
existing rescues keep working.
|
|
46
|
+
- `respond_to_missing?` on `Miner` and `MinerPool` so introspection
|
|
47
|
+
(`respond_to?`, `Object#method`, etc.) reflects the dynamic API
|
|
48
|
+
surface while excluding internal probes (`to_*`, `_*`).
|
|
49
|
+
- `# frozen_string_literal: true` pragma on every Ruby file.
|
|
50
|
+
- `required_ruby_version >= 3.2` in the gemspec.
|
|
51
|
+
- Gemspec metadata (`source_code_uri`, `changelog_uri`,
|
|
52
|
+
`bug_tracker_uri`, `rubygems_mfa_required`).
|
|
53
|
+
- RuboCop with `rubocop-rspec` and `rubocop-rake` plugins,
|
|
54
|
+
integrated into the default Rake task.
|
|
55
|
+
- GitHub Actions CI matrix testing Ruby 3.2, 3.3, 3.4, and 4.0
|
|
56
|
+
(plus `head` as an early-warning, allowed to fail).
|
|
57
|
+
- `.ruby-version` file pinning local development to 4.0.2 (the
|
|
58
|
+
gem itself supports 3.2+; the pin is only for contributors).
|
|
59
|
+
- `CHANGELOG.md` (this file).
|
|
60
|
+
- Unit test coverage brought to 99.66% on `lib/` (was 97.4% before
|
|
61
|
+
the 0.3.0 work). The entire `SocketWithTimeout#open_socket`
|
|
62
|
+
method, `MinerPool#available_miners`, `#unavailable_miners`,
|
|
63
|
+
the thread-rescue branch in `MinerPool#query`, the control-
|
|
64
|
+
character escape path in `Miner#perform_request`, and the new
|
|
65
|
+
`MinerResult` / `PoolResult` value objects all have dedicated
|
|
66
|
+
specs.
|
|
67
|
+
- End-to-end integration test suite at `spec/integration/`.
|
|
68
|
+
`miner_integration_spec.rb` exercises the full request → TCP
|
|
69
|
+
socket → response → parse → result path against a `FakeCgminer`
|
|
70
|
+
server running in a background thread. `cli_spec.rb` spawns
|
|
71
|
+
the real binary via `Open3` and asserts on exit codes,
|
|
72
|
+
stdout/stderr split, and `DEBUG=1` backtrace behavior.
|
|
73
|
+
- `script/fake_cgminer` for manual sandbox testing. Starts the
|
|
74
|
+
same fake cgminer server on a fixed port (default 4028) in the
|
|
75
|
+
foreground, so you can run the CLI against it without hardware.
|
|
76
|
+
Intentionally lives in `script/` rather than `bin/` so it isn't
|
|
77
|
+
packaged with the gem.
|
|
78
|
+
|
|
79
|
+
### Changed
|
|
80
|
+
- **`MinerPool#query` now returns a `PoolResult`** instead of an
|
|
81
|
+
`Array` with `[]` sentinels for failed miners. Callers iterate
|
|
82
|
+
`MinerResult` instances and explicitly distinguish success from
|
|
83
|
+
failure. Failed miners are no longer confused with successful
|
|
84
|
+
ones returning empty arrays.
|
|
85
|
+
- **`MinerPool` now overrides `summary`, `coin`, `config`,
|
|
86
|
+
`version`, and `check`** to return a `PoolResult` of unwrapped
|
|
87
|
+
hashes. Previously these convenience methods called
|
|
88
|
+
`query(:name)[0]`, which silently returned only the *first*
|
|
89
|
+
miner's hash on multi-miner pools (a pre-existing latent bug).
|
|
90
|
+
- **`Miner#query` now raises `ConnectionError`** instead of
|
|
91
|
+
returning `nil` when a miner is unreachable. Single-Miner
|
|
92
|
+
callers need to rescue; MinerPool callers get the error
|
|
93
|
+
captured in a `MinerResult.failure` automatically.
|
|
94
|
+
- **`Miner#available?` is now a true reachability probe.** No
|
|
95
|
+
cache, narrow rescue list (only `SocketError`,
|
|
96
|
+
`SystemCallError`, and `TimeoutError` — bugs like `ArgumentError`
|
|
97
|
+
propagate). Always re-checks.
|
|
98
|
+
- `SocketWithTimeout` raises `CgminerApiClient::TimeoutError`
|
|
99
|
+
instead of a bare `RuntimeError` on connect timeout.
|
|
100
|
+
- `Miner#perform_request` raises `ConnectionError` (not
|
|
101
|
+
`RuntimeError`) on socket open failure, with a message that
|
|
102
|
+
includes the original error class and message.
|
|
103
|
+
- `Miner#check_status` raises `ApiError` (not `RuntimeError`) for
|
|
104
|
+
cgminer status codes `E` and `F`.
|
|
105
|
+
- **`bin/cgminer_api_client` redesigned**: errors go to stderr
|
|
106
|
+
(not stdout), exit codes follow shell conventions (0 on any
|
|
107
|
+
success, 1 on all-failure, 64/EX_USAGE on unknown command),
|
|
108
|
+
`DEBUG=1` env var prints full backtraces via
|
|
109
|
+
`Exception#full_message`, and output is structured per-miner
|
|
110
|
+
with `host:port:` headers.
|
|
111
|
+
- `YAML.load_file` → `YAML.safe_load_file` for the miners config.
|
|
112
|
+
- `IO.select(nil, [socket], nil, timeout)` →
|
|
113
|
+
`socket.wait_writable(timeout)` in `SocketWithTimeout` —
|
|
114
|
+
Fiber-scheduler compatible.
|
|
115
|
+
- `String#match` → `String#match?` where the result is only used
|
|
116
|
+
as a boolean.
|
|
117
|
+
- `length == 0` → `empty?`, `'%04x' %` → `format`, and other
|
|
118
|
+
small modernizations.
|
|
119
|
+
- Bare `rescue` clauses now specify `StandardError` explicitly.
|
|
120
|
+
- Gemspec file list switched from `git ls-files` to explicit
|
|
121
|
+
`Dir.glob` patterns; `spec/` is no longer packaged in the gem.
|
|
122
|
+
- Bumped minimum versions of `rake`, `rspec`, and `simplecov`.
|
|
123
|
+
|
|
124
|
+
### Fixed
|
|
125
|
+
- **`Miner#query` parameter escape was a silent no-op for
|
|
126
|
+
backslashes.** The intent was to double literal backslashes so
|
|
127
|
+
they round-trip through cgminer's comma-separated parameter
|
|
128
|
+
syntax, but `gsub('\\', '\\\\')` is parsed as "replace `\` with
|
|
129
|
+
`\`" because in gsub's replacement-string DSL, `\\` denotes a
|
|
130
|
+
single literal backslash. Fixed by switching to
|
|
131
|
+
`gsub('\\') { '\\\\' }` (block form bypasses replacement-string
|
|
132
|
+
interpretation). Verified empirically; locked down by four
|
|
133
|
+
explicit specs.
|
|
134
|
+
- **`Miner::Commands#privileged` mislabeled connection errors as
|
|
135
|
+
access denied.** Previously rescued every `StandardError` and
|
|
136
|
+
returned `false`, which propagated through `access_denied?` to
|
|
137
|
+
raise `'access_denied'` from every privileged command
|
|
138
|
+
(`addpool`, `restart`, `quit`, `save`, `ascset`, etc.) during a
|
|
139
|
+
transient network outage — sending operators chasing phantom
|
|
140
|
+
auth/whitelist bugs. Now rescues only `ApiError` so
|
|
141
|
+
connection-layer failures surface as `ConnectionError` instead.
|
|
142
|
+
- **`MinerPool#summary` / `#coin` / `#config` / `#version` /
|
|
143
|
+
`#check` silently returned only the first miner's hash on
|
|
144
|
+
multi-miner pools.** The inherited `Miner::Commands::ReadOnly`
|
|
145
|
+
methods do `query(:name)[0]` to unwrap cgminer's single-element
|
|
146
|
+
response arrays, which works on a single Miner but drops every
|
|
147
|
+
result except the first on a pool. Fixed by overriding all five
|
|
148
|
+
on `MinerPool` to return a `PoolResult` of unwrapped hashes.
|
|
149
|
+
- **`Miner#available?` permanently cached `false` after any
|
|
150
|
+
transient failure.** Once a brief network blip had marked a
|
|
151
|
+
miner unavailable, the gem refused to talk to it for the
|
|
152
|
+
lifetime of the process. Fixed by dropping the cache entirely.
|
|
153
|
+
- Mismatched indentation in `def privileged` that was producing a
|
|
154
|
+
Ruby parser warning.
|
|
155
|
+
|
|
156
|
+
## Migration guide: 0.2.x → 0.3.0
|
|
157
|
+
|
|
158
|
+
This release intentionally contains breaking changes to finish
|
|
159
|
+
cleanups that were overdue. The gem is still on 0.x and only
|
|
160
|
+
~2,600 of 35,000 total downloads are on 0.2.6, so the
|
|
161
|
+
migration surface is small. Here's what to update.
|
|
162
|
+
|
|
163
|
+
### Ruby 3.2+
|
|
164
|
+
|
|
165
|
+
The gemspec now requires Ruby 3.2. If you're on 3.1 or older, you
|
|
166
|
+
need to upgrade Ruby before you can use this release.
|
|
167
|
+
|
|
168
|
+
### `MinerPool#query` (and all pool commands) return `PoolResult`
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
# Before (0.2.x)
|
|
172
|
+
pool.summary.each { |s| puts s[:mhs_av] }
|
|
173
|
+
pool.summary.first[:mhs_av]
|
|
174
|
+
|
|
175
|
+
# After (0.3.0)
|
|
176
|
+
pool.summary.values.each { |s| puts s[:mhs_av] }
|
|
177
|
+
pool.summary.values.first[:mhs_av]
|
|
178
|
+
|
|
179
|
+
# Or iterate per-miner with failure handling:
|
|
180
|
+
pool.summary.each do |result|
|
|
181
|
+
if result.ok?
|
|
182
|
+
puts "#{result.miner.host}: #{result.value[:mhs_av]}"
|
|
183
|
+
else
|
|
184
|
+
warn "#{result.miner.host}: #{result.error.message}"
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`PoolResult` includes `Enumerable`, so `.first`, `.map`, `.count`,
|
|
190
|
+
`.find` etc. all work — but now they yield `MinerResult`
|
|
191
|
+
instances, not raw hashes. Use `.values` to get just the
|
|
192
|
+
successful values as an Array.
|
|
193
|
+
|
|
194
|
+
### `Miner#query` raises `ConnectionError` instead of returning `nil`
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
# Before (0.2.x)
|
|
198
|
+
result = miner.summary
|
|
199
|
+
return unless result # nil meant unreachable
|
|
200
|
+
puts result[:mhs_av]
|
|
201
|
+
|
|
202
|
+
# After (0.3.0)
|
|
203
|
+
begin
|
|
204
|
+
result = miner.summary
|
|
205
|
+
puts result[:mhs_av]
|
|
206
|
+
rescue CgminerApiClient::ConnectionError => e
|
|
207
|
+
warn "miner unreachable: #{e.message}"
|
|
208
|
+
end
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
If you're using `MinerPool` instead of `Miner` directly, you
|
|
212
|
+
don't need to change anything — `MinerPool` catches the
|
|
213
|
+
`ConnectionError` per miner and turns it into a
|
|
214
|
+
`MinerResult.failure`.
|
|
215
|
+
|
|
216
|
+
### `MinerPool#summary` etc. now return every miner, not just the first
|
|
217
|
+
|
|
218
|
+
If you were calling `pool.summary` on a multi-miner pool and
|
|
219
|
+
relying on getting back a single Hash, you were actually being
|
|
220
|
+
bitten by a pre-existing bug (the result was silently dropping
|
|
221
|
+
every miner except the first). Now you get a `PoolResult` with
|
|
222
|
+
one entry per miner. Use `.values.first` if you really want
|
|
223
|
+
just the first miner.
|
|
224
|
+
|
|
225
|
+
### `force_reload` parameter removed
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
# Before (0.2.x)
|
|
229
|
+
pool.available_miners(true) # force re-check
|
|
230
|
+
miner.available?(true)
|
|
231
|
+
|
|
232
|
+
# After (0.3.0)
|
|
233
|
+
pool.available_miners # always re-checks
|
|
234
|
+
miner.available?
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
There's no cache to flush anymore.
|
|
238
|
+
|
|
239
|
+
### `MinerPool#query` no longer writes to stderr
|
|
240
|
+
|
|
241
|
+
Previously, a failed per-miner query would print
|
|
242
|
+
`[host:port] ErrorClass: message` to stderr as a side effect of
|
|
243
|
+
calling `pool.query`. That was an earlier 0.3.0 addition; it's
|
|
244
|
+
removed because library code shouldn't print. The error is now
|
|
245
|
+
carried structurally in the `MinerResult.failure` inside the
|
|
246
|
+
`PoolResult`, and you can display it however you want. The CLI
|
|
247
|
+
(`bin/cgminer_api_client`) does display it on stderr.
|
|
248
|
+
|
|
249
|
+
### CLI exit codes changed
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
# Before (0.2.x)
|
|
253
|
+
cgminer_api_client summary # exit 0 whether or not it worked
|
|
254
|
+
# errors written to stdout
|
|
255
|
+
|
|
256
|
+
# After (0.3.0)
|
|
257
|
+
cgminer_api_client summary # exit 0 if any miner succeeded,
|
|
258
|
+
# 1 if all failed,
|
|
259
|
+
# 64 for unknown command.
|
|
260
|
+
# errors on stderr.
|
|
261
|
+
DEBUG=1 cgminer_api_client summary # also prints full backtraces
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
If you had a shell pipeline that captured the CLI's stdout
|
|
265
|
+
expecting the whole data blob (including errors), your script
|
|
266
|
+
needs updating. Errors now go to stderr like they should.
|
|
267
|
+
|
|
268
|
+
### Exception class hierarchy
|
|
269
|
+
|
|
270
|
+
All gem-specific errors now inherit from
|
|
271
|
+
`CgminerApiClient::Error < StandardError`:
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
CgminerApiClient::Error
|
|
275
|
+
├── CgminerApiClient::ConnectionError
|
|
276
|
+
│ └── CgminerApiClient::TimeoutError
|
|
277
|
+
└── CgminerApiClient::ApiError
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Existing `rescue StandardError` clauses keep working. If you
|
|
281
|
+
want to catch everything the gem raises, `rescue CgminerApiClient::Error`
|
|
282
|
+
now works.
|
|
283
|
+
|
|
284
|
+
## [0.2.6] - earlier
|
|
285
|
+
|
|
286
|
+
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.
|
|
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,29 +62,74 @@ Restart cgminer:
|
|
|
75
62
|
|
|
76
63
|
## Gem Usage
|
|
77
64
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
+
```
|
|
101
133
|
|
|
102
134
|
## CLI Usage
|
|
103
135
|
|
|
@@ -181,7 +213,7 @@ Any cgminer API commands not explictly defined above are implemented using `meth
|
|
|
181
213
|
|
|
182
214
|
If you find this gem useful, please consider donating.
|
|
183
215
|
|
|
184
|
-
BTC: `
|
|
216
|
+
BTC: `bc1q00genlpcpcglgd4rezqcurf4t4taz0acmm9vea`
|
|
185
217
|
|
|
186
218
|
## License
|
|
187
219
|
|
data/bin/cgminer_api_client
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib/")
|
|
4
5
|
|
|
@@ -8,15 +9,33 @@ require 'pp'
|
|
|
8
9
|
command = ARGV.shift&.to_sym
|
|
9
10
|
commands = CgminerApiClient::Miner::Commands.instance_methods
|
|
10
11
|
|
|
11
|
-
unless command
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
unless command && commands.include?(command)
|
|
13
|
+
warn 'USAGE: cgminer_api_client command (arguments)'
|
|
14
|
+
warn "commands: #{commands.sort.join(', ')}"
|
|
15
|
+
warn ''
|
|
16
|
+
warn 'Set DEBUG=1 to see full backtraces on errors.'
|
|
17
|
+
exit 64 # EX_USAGE
|
|
15
18
|
end
|
|
16
19
|
|
|
17
20
|
begin
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
pool = CgminerApiClient::MinerPool.new
|
|
22
|
+
result = ARGV.empty? ? pool.query(command) : pool.query(command, *ARGV)
|
|
23
|
+
|
|
24
|
+
result.each do |r|
|
|
25
|
+
if r.ok?
|
|
26
|
+
puts "#{r.miner.host}:#{r.miner.port}:"
|
|
27
|
+
pp r.value
|
|
28
|
+
else
|
|
29
|
+
warn "#{r.miner.host}:#{r.miner.port}: #{r.error.class}: #{r.error.message}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Exit 0 if at least one miner succeeded; 1 only if every miner
|
|
34
|
+
# failed. Matches how fleet-oriented CLIs (ping, ssh -o) typically
|
|
35
|
+
# behave — partial failures don't trigger alerts.
|
|
36
|
+
exit(result.any_succeeded? ? 0 : 1)
|
|
37
|
+
rescue StandardError => e
|
|
38
|
+
warn "cgminer_api_client: #{e.class}: #{e.message}"
|
|
39
|
+
warn e.full_message(highlight: false) if ENV['DEBUG']
|
|
40
|
+
exit 1
|
|
41
|
+
end
|
data/cgminer_api_client.gemspec
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
5
|
require 'cgminer_api_client/version'
|
|
5
6
|
|
|
@@ -8,13 +9,31 @@ Gem::Specification.new do |spec|
|
|
|
8
9
|
spec.version = CgminerApiClient::VERSION
|
|
9
10
|
spec.authors = ["Justin Ramos"]
|
|
10
11
|
spec.email = ["justin.ramos@gmail.com"]
|
|
11
|
-
spec.summary =
|
|
12
|
-
spec.description =
|
|
12
|
+
spec.summary = "A gem that allows sending API commands to a pool of cgminer instances"
|
|
13
|
+
spec.description = "Ruby client for the cgminer JSON API. Supports querying a single miner " \
|
|
14
|
+
"or a pool of miners in parallel, with full coverage of read-only and " \
|
|
15
|
+
"privileged commands."
|
|
13
16
|
spec.homepage = "https://github.com/jramos/cgminer_api_client"
|
|
14
17
|
spec.license = "MIT"
|
|
15
18
|
|
|
16
|
-
spec.
|
|
19
|
+
spec.required_ruby_version = ">= 3.2"
|
|
20
|
+
|
|
21
|
+
spec.metadata = {
|
|
22
|
+
"source_code_uri" => spec.homepage,
|
|
23
|
+
"changelog_uri" => "#{spec.homepage}/blob/master/CHANGELOG.md",
|
|
24
|
+
"bug_tracker_uri" => "#{spec.homepage}/issues",
|
|
25
|
+
"rubygems_mfa_required" => "true"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
spec.files = Dir.glob([
|
|
29
|
+
"lib/**/*.rb",
|
|
30
|
+
"bin/*",
|
|
31
|
+
"config/*.example",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE.txt",
|
|
34
|
+
"CHANGELOG.md",
|
|
35
|
+
"cgminer_api_client.gemspec"
|
|
36
|
+
])
|
|
17
37
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
38
|
spec.require_paths = ["lib"]
|
|
20
39
|
end
|
data/config/miners.yml.example
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CgminerApiClient
|
|
4
|
+
# Base class for all errors raised by the gem. Catch this if you want
|
|
5
|
+
# to handle every cgminer-specific failure together.
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
|
|
8
|
+
# Raised when the gem cannot reach a miner: socket open failure, DNS
|
|
9
|
+
# failure, connect timeout, or any other transport-level problem.
|
|
10
|
+
# Distinct from ApiError so callers can tell "I never spoke to the
|
|
11
|
+
# miner" apart from "the miner spoke to me and refused."
|
|
12
|
+
class ConnectionError < Error; end
|
|
13
|
+
|
|
14
|
+
# Raised specifically for connect-timeout failures, as a subclass
|
|
15
|
+
# of ConnectionError. Lets callers distinguish "the miner took too
|
|
16
|
+
# long to answer the SYN" from other connection-layer problems.
|
|
17
|
+
class TimeoutError < ConnectionError; end
|
|
18
|
+
|
|
19
|
+
# Raised when the miner returned a response whose STATUS field
|
|
20
|
+
# indicates an error (cgminer status code 'E' or 'F'). The message
|
|
21
|
+
# contains the cgminer code and message verbatim.
|
|
22
|
+
class ApiError < Error; end
|
|
23
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module CgminerApiClient
|
|
2
4
|
class Miner
|
|
3
5
|
module Commands
|
|
@@ -43,11 +45,14 @@ module CgminerApiClient
|
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
def privileged
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
query(:privileged)
|
|
49
|
+
true
|
|
50
|
+
rescue CgminerApiClient::ApiError
|
|
51
|
+
# The miner answered and rejected: not privileged.
|
|
52
|
+
false
|
|
53
|
+
# ConnectionError and any other StandardError propagate so
|
|
54
|
+
# callers don't misinterpret a transient network blip as
|
|
55
|
+
# "access denied".
|
|
51
56
|
end
|
|
52
57
|
|
|
53
58
|
def notify
|
|
@@ -86,9 +91,9 @@ module CgminerApiClient
|
|
|
86
91
|
end
|
|
87
92
|
|
|
88
93
|
def ascset(number, option, value = nil)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
94
|
+
return if access_denied?
|
|
95
|
+
|
|
96
|
+
value ? query(:ascset, number, option, value) : query(:ascset, number, option)
|
|
92
97
|
end
|
|
93
98
|
end
|
|
94
99
|
|
|
@@ -106,9 +111,9 @@ module CgminerApiClient
|
|
|
106
111
|
end
|
|
107
112
|
|
|
108
113
|
def pgaset(number, option, value = nil)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
return if access_denied?
|
|
115
|
+
|
|
116
|
+
value ? query(:pgaset, number, option, value) : query(:pgaset, number, option)
|
|
112
117
|
end
|
|
113
118
|
end
|
|
114
119
|
|
|
@@ -164,9 +169,9 @@ module CgminerApiClient
|
|
|
164
169
|
end
|
|
165
170
|
|
|
166
171
|
def save(filename = nil)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
172
|
+
return if access_denied?
|
|
173
|
+
|
|
174
|
+
filename ? query(:save, filename) : query(:save)
|
|
170
175
|
end
|
|
171
176
|
|
|
172
177
|
def setconfig(name, value)
|
|
@@ -181,11 +186,9 @@ module CgminerApiClient
|
|
|
181
186
|
private
|
|
182
187
|
|
|
183
188
|
def access_denied?
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return false
|
|
188
|
-
end
|
|
189
|
+
raise 'access_denied' unless privileged
|
|
190
|
+
|
|
191
|
+
false
|
|
189
192
|
end
|
|
190
193
|
|
|
191
194
|
include Miner::Commands::Privileged::Asc
|
|
@@ -198,4 +201,4 @@ module CgminerApiClient
|
|
|
198
201
|
include Miner::Commands::Privileged
|
|
199
202
|
end
|
|
200
203
|
end
|
|
201
|
-
end
|
|
204
|
+
end
|