Skip to content

Commit 3d91012

Browse files
feat: handshake-node chart
Signed-off-by: Tripura Repalle <tripura@blinklabs.io>
1 parent a97dc4b commit 3d91012

13 files changed

Lines changed: 1010 additions & 0 deletions
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: publish-handshake-node-helm-chart
2+
3+
on:
4+
push:
5+
branches: ['main']
6+
paths:
7+
- 'charts/handshake-node/**'
8+
- '.github/workflows/publish-handshake-node-helm-chart.yml'
9+
10+
jobs:
11+
build-and-push-handshake-node-helm-chart:
12+
runs-on: ubuntu-latest
13+
permissions:
14+
contents: read
15+
packages: write
16+
steps:
17+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
18+
- name: Install Helm
19+
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
20+
- name: Package and upload chart
21+
shell: bash
22+
env:
23+
REGISTRY: "ghcr.io"
24+
REPOSITORY: "${{ github.repository }}"
25+
TOKEN: "${{ secrets.GITHUB_TOKEN }}"
26+
USER: "${{ github.repository_owner }}"
27+
run: |
28+
rm -rf dist
29+
mkdir dist
30+
helm package charts/handshake-node/ -d dist/
31+
echo "${TOKEN}" | helm registry login "${REGISTRY}" -u "${USER}" --password-stdin
32+
for file in dist/*; do
33+
helm push "$file" "oci://${REGISTRY}/${REPOSITORY,,}/charts"
34+
done

charts/handshake-node/Chart.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
apiVersion: v2
2+
name: handshake-node
3+
description: |
4+
Deploys handshake-node, the Go implementation of a Handshake (HNS) blockchain
5+
full node from Blink Labs. This is distinct from the JavaScript hsd chart.
6+
type: application
7+
version: 0.1.0
8+
appVersion: "0.1.1-rc1"
9+
kubeVersion: ">=1.24.0-0"
10+
home: https://github.com/blinklabs-io/handshake-node
11+
sources:
12+
- https://github.com/blinklabs-io/handshake-node
13+
keywords:
14+
- handshake
15+
- hns
16+
- blockchain
17+
- handshake-node
18+
- btcd
19+
maintainers:
20+
- name: wolf31o2
21+
email: wolf31o2@blinklabs.io
22+
- name: aurora
23+
email: aurora@blinklabs.io
24+
- name: verbotenj
25+
email: verbotenj@blinklabs.io

charts/handshake-node/README.md

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
# handshake-node
2+
3+
Deploys [handshake-node](https://github.com/blinklabs-io/handshake-node) — the
4+
Go implementation of a Handshake (HNS) blockchain full node from Blink Labs —
5+
as a Kubernetes StatefulSet.
6+
7+
This chart is distinct from the [`hsd`](../hsd) chart, which packages the
8+
JavaScript reference implementation from `handshake-org`.
9+
10+
## TL;DR
11+
12+
```console
13+
helm install my-handshake-node oci://ghcr.io/blinklabs-io/helm-charts/charts/handshake-node
14+
```
15+
16+
## Introduction
17+
18+
The chart deploys a single-replica StatefulSet running `handshake-node`
19+
against the Handshake mainnet by default. Persistent state is stored in a
20+
PersistentVolumeClaim mounted at `/home/handshake/.handshake-node`.
21+
22+
Security-relevant defaults:
23+
24+
- The container runs as the non-root `handshake` user baked into the upstream
25+
image with `runAsNonRoot: true`, all Linux capabilities dropped, no
26+
privilege escalation, `readOnlyRootFilesystem: true`, and the
27+
`RuntimeDefault` seccomp profile.
28+
- The peer-to-peer Service is `ClusterIP` by default. Public exposure via
29+
`NodePort` or `LoadBalancer` is an explicit operator choice.
30+
- The authenticated TLS RPC listener (port `12037`) is not exposed unless
31+
`rpc.enabled=true`. When enabled, the chart renders a `ClusterIP`-only
32+
Service that stays inside the cluster.
33+
- RPC credentials are read from an existing Kubernetes Secret via
34+
`secretKeyRef` — they are never taken from `values.yaml` or a ConfigMap.
35+
- The Prometheus metrics endpoint (`12039`) and Stratum server (`12040`) are
36+
disabled by default. When enabled they are kept on private `ClusterIP`
37+
Services.
38+
39+
## Prerequisites
40+
41+
- Kubernetes 1.24+
42+
- Helm 3.8+ (for OCI registry support)
43+
- A default StorageClass (or set `persistence.storageClass`)
44+
45+
## Installing the chart
46+
47+
```console
48+
helm install my-handshake-node \
49+
oci://ghcr.io/blinklabs-io/helm-charts/charts/handshake-node \
50+
--namespace handshake --create-namespace
51+
```
52+
53+
## Uninstalling the chart
54+
55+
```console
56+
helm uninstall my-handshake-node --namespace handshake
57+
```
58+
59+
The PVC is retained by default (`persistence.retention.whenDeleted=Retain`).
60+
Delete it manually if you no longer need the chain data:
61+
62+
```console
63+
kubectl -n handshake delete pvc data-my-handshake-node-0
64+
```
65+
66+
## Persistence
67+
68+
The chart provisions a PVC via `volumeClaimTemplates`:
69+
70+
```yaml
71+
persistence:
72+
enabled: true
73+
accessModes:
74+
- ReadWriteOnce
75+
size: 200Gi
76+
# storageClass: fast-ssd
77+
retention:
78+
whenDeleted: Retain
79+
whenScaled: Retain
80+
```
81+
82+
To bind an existing claim instead, set `persistence.existingClaim`:
83+
84+
```yaml
85+
persistence:
86+
enabled: true
87+
existingClaim: my-existing-handshake-pvc
88+
```
89+
90+
## RPC Secret
91+
92+
RPC is disabled by default. To enable it, create a Kubernetes Secret with the
93+
credentials, then reference it from `values.yaml`:
94+
95+
```console
96+
kubectl -n handshake create secret generic handshake-node-rpc \
97+
--from-literal=rpcuser=hnsuser \
98+
--from-literal=rpcpass='replace-with-a-strong-password'
99+
```
100+
101+
```yaml
102+
rpc:
103+
enabled: true
104+
existingSecret: handshake-node-rpc
105+
userKey: rpcuser
106+
passwordKey: rpcpass
107+
```
108+
109+
The chart injects these values as the `HANDSHAKE_NODE_RPCUSER` and
110+
`HANDSHAKE_NODE_RPCPASS` environment variables via `secretKeyRef`. It also
111+
renders a `ClusterIP`-only Service named `<release>-handshake-node-rpc` on
112+
port `12037`. If you need remote clients to reach RPC, port-forward the
113+
service or front it with an in-cluster gateway that terminates its own TLS —
114+
do not change the Service type in this chart.
115+
116+
## Metrics
117+
118+
Enable the Prometheus endpoint (disabled by default):
119+
120+
```yaml
121+
metrics:
122+
enabled: true
123+
# Keep this bound to a routable interface inside the pod. The daemon
124+
# rejects non-loopback binds unless allowPublic is true.
125+
listen: "0.0.0.0:12039"
126+
allowPublic: true
127+
serviceMonitor:
128+
enabled: true
129+
interval: 30s
130+
```
131+
132+
The metrics `Service` is always `ClusterIP`. Prometheus Operator users can
133+
turn on the `ServiceMonitor` with `metrics.serviceMonitor.enabled=true`.
134+
135+
## Stratum
136+
137+
Stratum is disabled by default. To turn it on for internal miners:
138+
139+
```console
140+
kubectl -n handshake create secret generic handshake-node-stratum \
141+
--from-literal=stratumuser=worker \
142+
--from-literal=stratumpass='replace-with-a-strong-password'
143+
```
144+
145+
```yaml
146+
stratum:
147+
enabled: true
148+
listen: "0.0.0.0:12040"
149+
allowPublic: true
150+
miningAddress: hs1qyourhandshakeaddress
151+
auth:
152+
existingSecret: handshake-node-stratum
153+
service:
154+
enabled: true
155+
type: ClusterIP
156+
```
157+
158+
Public exposure requires `stratum.allowPublic=true` and both
159+
`stratumuser`/`stratumpass` per the upstream daemon.
160+
161+
## Service exposure
162+
163+
The peer-to-peer Service defaults to `ClusterIP`. To advertise the node on the
164+
public Handshake network you must explicitly opt in:
165+
166+
```yaml
167+
service:
168+
p2p:
169+
type: LoadBalancer
170+
port: 12038
171+
loadBalancerSourceRanges:
172+
- 0.0.0.0/0
173+
```
174+
175+
Use `NodePort` if you route through an external load balancer:
176+
177+
```yaml
178+
service:
179+
p2p:
180+
type: NodePort
181+
port: 12038
182+
nodePort: 32038
183+
```
184+
185+
## Mainnet operations examples
186+
187+
Full mainnet node with private RPC, metrics enabled, and a beefier PVC:
188+
189+
```yaml
190+
network: main
191+
image:
192+
tag: "0.1.1-rc1"
193+
194+
persistence:
195+
size: 400Gi
196+
storageClass: fast-ssd
197+
198+
rpc:
199+
enabled: true
200+
existingSecret: handshake-node-rpc
201+
202+
metrics:
203+
enabled: true
204+
serviceMonitor:
205+
enabled: true
206+
207+
resources:
208+
requests:
209+
cpu: 1
210+
memory: 2Gi
211+
limits:
212+
cpu: 4
213+
memory: 8Gi
214+
215+
topologySpreadConstraints:
216+
- maxSkew: 1
217+
topologyKey: topology.kubernetes.io/zone
218+
whenUnsatisfiable: ScheduleAnyway
219+
labelSelector:
220+
matchLabels:
221+
app.kubernetes.io/name: handshake-node
222+
```
223+
224+
## Upgrade
225+
226+
```console
227+
helm upgrade my-handshake-node \
228+
oci://ghcr.io/blinklabs-io/helm-charts/charts/handshake-node \
229+
--namespace handshake \
230+
--reuse-values \
231+
--version <new-chart-version>
232+
```
233+
234+
The PVC survives an upgrade because retention defaults to `Retain`. Chart
235+
upgrades that only change the container image trigger a rolling restart of
236+
the single StatefulSet pod.
237+
238+
## Values reference
239+
240+
See [`values.yaml`](values.yaml) for the full list of tunables. Key knobs:
241+
242+
| Key | Description | Default |
243+
| -------------------------------- | ------------------------------------------------------------ | ------------------------------------ |
244+
| `image.repository` | Image name | `ghcr.io/blinklabs-io/handshake-node` |
245+
| `image.tag` | Image tag (never `latest`) | `0.1.1-rc1` |
246+
| `network` | Handshake network: `main`, `regtest`, `simnet` | `main` |
247+
| `persistence.size` | PVC size | `200Gi` |
248+
| `persistence.storageClass` | StorageClass name | cluster default |
249+
| `rpc.enabled` | Enable authenticated RPC | `false` |
250+
| `rpc.existingSecret` | Name of Secret with `rpcuser`/`rpcpass` | `""` |
251+
| `metrics.enabled` | Enable Prometheus endpoint | `false` |
252+
| `metrics.serviceMonitor.enabled` | Create ServiceMonitor | `false` |
253+
| `stratum.enabled` | Enable Stratum server | `false` |
254+
| `service.p2p.type` | P2P Service type | `ClusterIP` |
255+
| `podSecurityContext` | Pod-level security context | non-root, seccomp `RuntimeDefault` |
256+
| `securityContext` | Container security context | drop ALL, no privilege escalation |
257+
| `resources` | CPU/memory requests/limits | `{}` |

0 commit comments

Comments
 (0)