Prepare for publishing

This commit is contained in:
satoshi committed 2026-09-04 23:06:59 +03:00
1 parent be31a2fe58
commit 9fd8e6ec8e
4 files changed
+315 -113

No files matched your search

+202 -83
View File
@@ -1,95 +1,214 @@
# Kamado Pool, StartOS 0.4.0 Packaging
<p align="center">
<img src="icon.png" alt="Kamado Pool" width="160" />
</p>
StartOS **0.4.0** wrapper for [Kamado Pool](../KamadoPool), a modern solo Bitcoin mining pool built on a patched fork of CKPool-solo with a Go middleware API and Svelte real-time dashboard.
<h1 align="center">Kamado Pool</h1>
This is the 0.4.0 port of the [0.3.5.1 wrapper](../KamadoPool-StartOS-0351), rebuilt on the TypeScript `@start9labs/start-sdk` and the revised 0.4.0 service architecture.
Kamado Pool is a solo Bitcoin mining pool: a patched CKPool-solo stratum server, a Go middleware API, and a Svelte dashboard, packaged as one StartOS service. Block rewards are paid by the coinbase directly to the address each miner connects with, so the service holds no balances and performs no payouts.
## What changed from the 0.3.5.1 package
Everything not listed in this document should behave the same as upstream.
| 0.3.5.1 | 0.4.0 |
## Image and Container Runtime
The package builds a single image from the repository Dockerfile and runs every process inside one shared subcontainer, so the API can reach CKPool's Unix socket and tail its log file without cross-container plumbing.
| Property | Value |
|---|---|
| `manifest.yaml` + Deno `embassy.js` procedures | `startos/` TypeScript package on `@start9labs/start-sdk` (npm), bundled with ncc |
| Single container, `docker_entrypoint.sh` supervises ckpool + kamado-api + stunnel | One shared subcontainer, three daemons (`api`, `ckpool`, `stunnel`) + oneshots, individually health-checked and ordered by the SDK daemon runtime |
| Config form (`getConfig`/`setConfig`) writing `start9/config.yaml` | `store.json` file model + **Configure** action with input spec; settings changes restart daemons reactively |
| RPC credentials via config **pointers** to `bitcoind`/`bitcoind-testnet` | bitcoind **cookie** auth read from a read-only dependency mount; RPC/ZMQ reached over the LXC bridge address resolved reactively (no `.embassy` DNS) |
| Two dependency variants (`bitcoind`, `bitcoind-testnet`) | Single `bitcoind` dependency; the active chain is detected at runtime from `getblockchaininfo` (the coinbase self-test address is chosen accordingly) |
| No LAN TCP forwarding, users needed router forwards / simpleproxy for stratum | Stratum (and TLS stratum) exposed as **raw TCP host bindings**, directly reachable on the LAN. In-container ports are fixed constants; the configured value sets only the interface's `preferredExternalPort`, so a port change is a pure rebind that neither orphans a binding nor restarts the pool |
| `properties` for TLS fingerprint/PEM | **Stratum TLS Certificate** action (fingerprint + PEM, copyable) |
| Health checks: web / ckpool / bitcoin / submit-gap | Same four, plus an optional **ZMQ Block Feed** check, as daemon `ready` checks + standalone health checks |
| duplicity backup of both volumes | `sdk.setupBackups` of both volumes (`main`, `ckpool`) |
| compat migrations | `VersionGraph` with an `up` migration that converts a 0.3.5.1 `config.yaml` into `store.json` and preserves the SQLite DB, TLS cert, and ckpool state |
|, | i18n (en, es, de, pl, fr) for all user-facing strings |
| Image id | `main` |
| Source | `Dockerfile` at the repository root |
| Architectures | `x86_64`, `aarch64` |
| Base | Debian slim |
| Subcontainer | one shared instance for all processes |
Retained behavior: the ckpool restart loop gated on bitcoind reachability (now a daemon wrapper script), dual block-detection (ZMQ + 100 ms blockpoll), loopback stratum binds for TLS tagging (now one per certificate, declared to kamado-api via `STRATUM_SERVERS` so the dashboard names the certificate on hover), the v4 self-signed certificate with broad SANs, and ckpool loglevel 6 with `--log-shares`.
The image is assembled in four stages: CKPool compiled from upstream source with the Kamado patch series applied, the dashboard built to static assets, the Go API cross compiled with those assets embedded in the binary, and a runtime stage carrying the two binaries plus `curl`, `jq`, `stunnel4` and `openssl`.
Stratum is exposed on **three** endpoints, each a separate interface and port. Plaintext, and two TLS paths that differ in who terminates them: **Stratum (TLS, Public Domain)** declares `addSsl`, so StartOS terminates it and issues/renews a Let's Encrypt certificate for any domain attached to it, this is the only way to get a publicly trusted certificate, because StartOS provisions ACME only for endpoints it terminates TLS for, and a raw TCP binding is offered no certificate authority at all. **Stratum (TLS, Local Network)** stays raw TCP terminated in-container by stunnel with the persisted self-signed certificate, for LAN miners that pin it, public CAs cannot sign a bare IP or `.local` name. Each forwards to its own ckpool bind, so `serverurl` index identifies the path and the dashboard padlock names the certificate in use.
Processes, in start order:
## Prerequisites
| Name | Kind | Purpose |
|---|---|---|
| `dirs` | oneshot | Creates the data, TLS, log and socket directories on the volumes |
| `api` | daemon | Serves the dashboard, REST API and WebSocket feed; aggregates CKPool and Bitcoin Core state |
| `ckpool` | daemon | Stratum server; waits for Bitcoin Core, detects the active chain, then execs CKPool |
| `tls-cert` | oneshot | Generates the self-signed stratum certificate when absent or outdated |
| `stunnel` | daemon | Terminates stratum TLS and forwards to CKPool's loopback bind |
- **Node.js + npm**
- **Docker** with buildx
- **start-cli**: from the [StartOS packaging guide](https://docs.start9.com/packaging/0.4.0.x/environment-setup.html)
- **rsync**
- **KamadoPool source**: local sibling checkout at `../KamadoPool` (override with `KAMADO_SRC=/path/to/KamadoPool`)
## Volume and Data Layout
## Building
Two volumes separate the service's own state from CKPool's, because they have different lifetimes and different restore semantics.
```sh
npm ci # once, installs the SDK and bitcoin-core-startos (for typed dependency wiring)
make setup # once per boot, qemu binfmt + a multi-platform buildx builder
make # kamado-pool.s9pk, the universal package (ship this)
| Volume | Mount point | Contents |
|---|---|---|
| `main` | `/root/.kamado` | SQLite database (found blocks, share statistics, accelerated transactions, log cursor), persisted stratum TLS certificate and key |
| `ckpool` | `/root/.ckpool` | CKPool's own state directory and its log files |
Bitcoin Core's data directory is mounted read only at `/mnt/bitcoind`, solely to read the RPC cookie. Nothing is written there.
The rendered CKPool configuration and the stunnel configuration live on the subcontainer root filesystem rather than on a volume. Both are regenerated on every start, because both depend on values that are not known until the service runs: the active chain and the ports the OS actually assigned.
## File Models
The package owns one persisted settings file, and everything else it writes is regenerated from that file plus runtime state.
| File | Location | Seeded | Rewritten by |
|---|---|---|---|
| `store.json` | `main` volume | Defaults on first start, or migrated from the previous package format | The Configure action |
| CKPool configuration | subcontainer rootfs | Rendered on every start | Service start, from `store.json` and the detected chain |
| stunnel configuration | subcontainer rootfs | Rendered on every start | Service start, from `store.json` and the assigned ports |
| TLS certificate and key | `main` volume | Generated on first start | The Regenerate TLS Certificate action, or automatically when the certificate format changes |
Hand edits do not survive. The two rendered configurations are overwritten on every start, and `store.json` is owned by the Configure action. Change settings through Configure rather than by editing files.
## Dependencies
Bitcoin Core is required. Kamado builds block templates from it, validates miner payout addresses against it, and submits solved blocks to it, so the service cannot mine without it.
| Dependency | Requirement | Health checks required | Mounts |
|---|---|---|---|
| Bitcoin Core | Required, must be running | Its own health check and its sync progress | Its data directory, read only, for the RPC cookie |
Sync progress is required deliberately. Mining against an unsynced node produces work on a stale chain tip, so an unsynced node is surfaced as an unsatisfied dependency rather than as a warning.
## Network Access and Interfaces
Four interfaces are published. The dashboard is a web interface; the three stratum interfaces are raw TCP endpoints that miners connect to directly, with no proxy in front of them.
| Interface | Type | Protocol | Purpose |
|---|---|---|---|
| Web Dashboard | ui | http | Dashboard, REST API and WebSocket feed |
| Stratum | api | tcp | Plaintext stratum, the default for miners on a trusted network |
| Stratum (TLS, Local Network) | api | tcp | Stratum over TLS using the package's self-signed certificate |
| Stratum (TLS, Public Domain) | api | tcp | Stratum over TLS using a certificate StartOS issues for an attached domain |
All three stratum endpoints run at the same time on separate ports. The stratum ports are configurable, but the numbers set in Configure are requests rather than guarantees: the OS assigns a different port when the requested one is already claimed, and the Pool Status action reports the ports actually in use.
The two TLS interfaces exist because they are secured differently. The local-network one serves a self-signed certificate that miners must be told to trust, since no public certificate authority will sign a bare LAN address. The public-domain one relies on StartOS issuing and renewing a publicly trusted certificate for a domain attached to that interface, which requires that domain to be reachable for the issuance challenge.
The dashboard's API refuses cross-origin browser requests on its WebSocket and on every request that changes state, so another site cannot drive it using a signed-in browser session.
## Installation and First-Run Flow
Installation differs from upstream mainly in that there is no payout address to configure and no pool account to create.
1. Install and start Bitcoin Core and let it finish syncing. The dependency stays unsatisfied until it does.
2. Accept the suggested task to enable ZMQ on Bitcoin Core. It is recommended rather than required; without it the service falls back to RPC polling.
3. Start Kamado Pool. On first start it creates its directories, generates a self-signed stratum certificate, detects which chain Bitcoin Core is running, and renders the CKPool configuration for that chain.
4. Open the Web Dashboard and point miners at the stratum port.
Miners authenticate with a Bitcoin address as the stratum username, optionally followed by a worker name for labelling. The service validates that username against Bitcoin Core and refuses to authorise a worker whose username is not a valid address on the active chain, so a misconfigured miner fails loudly instead of mining to nowhere.
## Actions
Every action is user-facing; none are hidden. Configure is the only one that changes settings, and the rest are diagnostic or maintenance operations.
| Action | Run it when | What it changes | Cost | Safe to repeat |
|---|---|---|---|---|
| Configure | Changing ports, vardiff, the coinbase tag, ZMQ, local TLS, log level or the explorer URL | Writes `store.json` | Rebinds interfaces; a port change does not restart the pool | Yes |
| Pool Status | Diagnosing anything | Nothing | One pass of internal queries | Yes |
| Stratum TLS Certificate | Setting up a miner that must trust the self-signed certificate | Nothing | Reads the stored certificate | Yes |
| Regenerate TLS Certificate | Rotating an expired or distrusted certificate | Deletes the stored certificate and key | A fresh certificate is generated on the next start | Yes, but miners pinning the old fingerprint must be updated |
| Reset Block Latency | After tuning ZMQ or the node, to start fresh measurements | Zeroes the block-update latency counters | Immediate | Yes |
| Rebuild Share Statistics | The all-time difficulty distribution or rejection reasons look wrong | Replaces the stored totals, but only when the log accounts for more shares than they hold | Reads the entire CKPool log, which can run to millions of lines | Yes; a second run is refused while one is in progress |
Pool Status returns a full text report: dependency and sync state, pool health, connected miners, hashrate, found blocks, the gap between attempted and confirmed block submissions, the endpoints and any domains actually in use, and the state of proof-of-work header capture. Stratum TLS Certificate returns both the SHA-256 fingerprint, for firmware that pins fingerprints, and the full PEM, for firmware that accepts a custom root. Rebuild Share Statistics reports how many shares the log accounted for and whether the stored totals were replaced.
## Tasks
One task is raised, and only when the operator has asked for ZMQ.
| Task | Raised when | Severity | Cleared when |
|---|---|---|---|
| Enable ZMQ on Bitcoin Core | ZMQ is enabled in Configure and Bitcoin Core does not have its ZMQ publishers on | Important | Bitcoin Core's configuration matches, or ZMQ is turned off in Configure |
It is important rather than critical because the service degrades gracefully. Without ZMQ it polls the RPC for new blocks instead, at the cost of slower stale-work detection.
## Health Checks
Six checks cover the two daemons, the dependency, and three failure modes that process state alone cannot reveal.
| Check | Probes | Grace period | Failure means | Remediation |
|---|---|---|---|---|
| Web Dashboard | The dashboard port is accepting connections | 15 seconds | The API is not serving | Check the service logs |
| Stratum Server | The stratum port is accepting connections | 30 seconds | Miners cannot connect | Check the service logs; CKPool waits for Bitcoin Core before it binds |
| Bitcoin Core RPC | The API can reach Bitcoin Core and it reports itself synced | Startup only | No usable templates, so no useful work is served | Check that Bitcoin Core is running and synced |
| Block Submission | Every attempted block submission reached confirmation | Startup only | A solved block was submitted but never confirmed | Check the Bitcoin Core logs; of these checks this is the one worth investigating immediately |
| ZMQ Block Feed | Block notifications are still arriving, when ZMQ is enabled | Startup only | Notifications have gone stale and detection has fallen back to polling | Re-run the ZMQ task, or check Bitcoin Core's publishers |
| Stratum TLS | The TLS stratum port is accepting connections, when local TLS is enabled | Startup only | Miners using TLS cannot connect | Confirm the certificate exists; regenerate it if not |
## Backups and Restore
Both volumes are backed up in full, because the service's history has no other source.
The `main` volume carries the SQLite database and the stratum TLS certificate, so a restore preserves found blocks, share statistics, best-share records, and the certificate that miners may have pinned. The `ckpool` volume carries CKPool's state and its logs. The logs are included deliberately rather than treated as disposable: the database holds a read cursor into them and the share statistics can be rebuilt from them, so restoring one without the other leaves the two inconsistent.
Nothing is excluded. There is no wallet, no key material beyond the stratum certificate, and no accounting state to rebuild, because rewards are paid by the coinbase directly to miners.
## Limitations and Differences
These are the places where the packaged service is narrower than upstream, or where the OS rather than the package has the final say. None of them prevent normal solo mining.
1. Only the chain Bitcoin Core is already running is supported. The service detects it at start; there is no chain selector.
2. The stratum ports set in Configure are requests, not guarantees. The OS assigns a different port when the requested one is taken.
3. The domain used for public-domain TLS is not a package setting. It follows whatever domain is attached to that interface, and only its port is configurable.
4. Certificates for a public domain are issued by StartOS rather than by the package. If issuance fails, that endpoint serves the self-signed certificate instead, which public clients reject.
5. The dashboard's read-only endpoints are not individually authenticated. Anything that can reach the dashboard port on the network can read pool data, including payout addresses and per-worker hashrates.
6. Proof-of-work header capture covers only shares accepted after that feature was introduced. Earlier best shares cannot be reconstructed, because CKPool discards job data once work moves on.
7. Share statistics can only be rebuilt as far back as the current CKPool log reaches.
8. The service kills CKPool when Bitcoin Core becomes unreachable, so miners fail over to a backup pool instead of mining stale work. CKPool restarts automatically once Bitcoin Core recovers.
## Quick Reference for AI Consumers
The block below summarises the operable surface in one place, for tooling and support agents that need the shape of the service without reading the sections above.
```yaml
service:
purpose: solo Bitcoin mining pool; rewards paid by coinbase directly to the miner
holds_funds: false
payout_address: supplied by each miner as its stratum username
image:
id: main
architectures: [x86_64, aarch64]
subcontainer: single, shared by all processes
processes:
oneshots: [dirs, tls-cert]
daemons: [api, ckpool, stunnel]
volumes:
main: {mount: /root/.kamado, holds: [sqlite database, tls certificate]}
ckpool: {mount: /root/.ckpool, holds: [ckpool state, logs]}
bitcoind: {mount: /mnt/bitcoind, mode: read-only, purpose: rpc cookie}
dependencies:
bitcoind: {required: true, must_be: [running, synced]}
interfaces:
ui: {name: Web Dashboard, protocol: http}
stratum: {protocol: tcp, tls: false}
stratum-tls: {protocol: tcp, tls: self-signed}
stratum-tls-public: {protocol: tcp, tls: issued-by-startos}
actions:
configure: {mutates: settings}
pool-status: {mutates: nothing, use_for: diagnosis}
stratum-tls-certificate: {mutates: nothing, returns: [fingerprint, pem]}
regenerate-tls-certificate: {mutates: tls certificate}
reset-block-latency: {mutates: latency counters}
rebuild-share-statistics: {mutates: all-time share statistics, cost: full log scan}
health_checks:
[web dashboard, stratum server, bitcoin core rpc, block submission,
zmq block feed, stratum tls]
tasks:
enable-zmq: {target: bitcoind, severity: important, optional: true}
backups:
volumes: [main, ckpool]
excluded: none
first_run:
requires: bitcoin core running and synced
generates: self-signed stratum certificate
configuration_needed: none for mining; miners supply their own payout address
```
`make` produces a **universal `kamado-pool.s9pk`** carrying both the x86_64 and aarch64 images, so a single file installs on any StartOS machine. For faster iteration during development, `make x86` and `make arm` emit single-arch packages (`kamado-pool_x86_64.s9pk` / `kamado-pool_aarch64.s9pk`) and skip the other architecture entirely.
The Makefile syncs `../KamadoPool`'s `api/`, `ui/`, and `ckpool/` into `./kamado-src/` (gitignored) before packing; `start-cli s9pk pack` then builds the Docker image per architecture from the local `Dockerfile` (ckpool is cloned from the pinned upstream commit and patched with Kamado's patch series, the Svelte UI is embedded into the Go binary).
Only the ckpool stage and the runtime stage are built for the target architecture, the aarch64 half of those runs under qemu emulation on an x86 host, which is why `make setup` is required. The UI and API stages pin `--platform=$BUILDPLATFORM` and cross-compile instead: the Svelte build emits architecture-independent static assets, and `kamado-api` is `CGO_ENABLED=0` with a pure-Go SQLite driver, so `GOARCH` cross-compilation is exact and avoids emulating the two slowest stages.
**Note:** `start-cli s9pk pack` records a `gitHash` in the manifest and therefore requires this directory to be a git repository with at least one commit. Until you make one, you can point git at a throwaway repo just for the pack step:
```sh
git init /tmp/stub && cd /tmp/stub && touch .stub && git add .stub && git commit -m stub
GIT_DIR=/tmp/stub/.git make
```
**Troubleshooting:** if the Docker build fails with DNS errors like `lookup registry-1.docker.io … connection refused` from inside buildkit, your buildx builder container cannot resolve DNS (common with systemd-resolved hosts). The `network=host` driver option in `make setup` is what avoids this.
### Install to your server
Configure `~/.startos/config.yaml` with your server, then:
```sh
make install
```
Or sideload the produced `kamado-pool.s9pk` through the StartOS web UI.
### Overrides
```sh
make KAMADO_SRC=/path/to/KamadoPool
```
## Package layout
```text
startos/
manifest/ id, images (local Dockerfile build), volumes, bitcoind dependency
main.ts subcontainer, ckpool.conf + stunnel.conf rendering, daemons + health checks (fixed internal ports)
interfaces.ts Web UI (http 8080), Stratum (raw TCP), Stratum TLS Local (raw TCP, stunnel/self-signed), Stratum TLS Public (addSsl, OS-terminated/Let's Encrypt)
fileModels/ store.json (service settings, incl. stratum ports)
actions/ Configure, Pool Status, Stratum TLS Certificate, Regenerate TLS Certificate, Reset Block Latency
dependencies.ts bitcoind (running, synced) + ZMQ autoconfig task
backups.ts volumes: main, ckpool
versions/ 0.2.2:0, graph back to 0.2.0 with migration from the 0.3.5.1 wrapper
init/ store seeding, init ordering
i18n/ dictionaries (en, es, de, pl, fr)
assets/
scripts/ kamado-ckpool-run.sh (bitcoind wait + chain detect + exec ckpool), kamado-tls-init.sh (cert v4)
Dockerfile ckpool (patched) + Svelte UI + Go API + runtime stage
```
## License
GPL-3.0, matches upstream Kamado and CKPool.