Enable P2Pool's --data-api/--local-api and add a small stdlib-only sidecar that copies an allowlist of numeric fields (node/sidechain hashrate, connected miners, shares, network difficulty) to /p2pool/stats.json. Worker IPs and wallet fragments in local/stratum are never served. An Ingress adds that single path to the website host (www.eom.dev by default), so the homepage can read it same-origin and reuse its TLS cert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018STDcc5BaYxPMNA9S8Wycs
410 lines
12 KiB
YAML
410 lines
12 KiB
YAML
---
|
|
# tasks file for monero
|
|
- name: Check required variables
|
|
ansible.builtin.assert:
|
|
that:
|
|
- monerod_user | length > 0
|
|
- monerod_password | length > 0
|
|
- monero_p2pool_wallet_address | length > 0
|
|
- monero_p2pool_chain in ['main', 'mini', 'nano']
|
|
quiet: true
|
|
no_log: true
|
|
|
|
- name: Create Monero namespace
|
|
k8s:
|
|
state: present
|
|
definition:
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: monero
|
|
|
|
- name: Create PVC for Monero
|
|
k8s:
|
|
state: present
|
|
definition:
|
|
apiVersion: v1
|
|
kind: PersistentVolumeClaim
|
|
metadata:
|
|
name: data
|
|
namespace: monero
|
|
spec:
|
|
storageClassName: r720-nfs-client
|
|
accessModes:
|
|
- ReadWriteOnce
|
|
resources:
|
|
requests:
|
|
storage: 512Gi
|
|
|
|
- name: Create PVC for P2Pool
|
|
k8s:
|
|
state: present
|
|
definition:
|
|
apiVersion: v1
|
|
kind: PersistentVolumeClaim
|
|
metadata:
|
|
name: p2pool
|
|
namespace: monero
|
|
spec:
|
|
storageClassName: r720-nfs-client
|
|
accessModes:
|
|
- ReadWriteOnce
|
|
resources:
|
|
requests:
|
|
storage: 64Gi
|
|
|
|
- name: Request a certificate for Monero
|
|
k8s:
|
|
state: present
|
|
definition:
|
|
apiVersion: cert-manager.io/v1
|
|
kind: Certificate
|
|
metadata:
|
|
name: monerotls
|
|
namespace: monero
|
|
spec:
|
|
secretName: monerotls
|
|
privateKey:
|
|
algorithm: RSA
|
|
encoding: PKCS1
|
|
size: 2048
|
|
duration: 2160h # 90d
|
|
renewBefore: 360h # 15d
|
|
isCA: false
|
|
usages:
|
|
- server auth
|
|
- client auth
|
|
subject:
|
|
organizations:
|
|
- EOM
|
|
commonName: monerod.eom.dev
|
|
dnsNames:
|
|
- monerod.eom.dev
|
|
issuerRef:
|
|
name: ca-issuer
|
|
kind: ClusterIssuer
|
|
|
|
- name: Request a certificate for p2pool
|
|
k8s:
|
|
state: present
|
|
definition:
|
|
apiVersion: cert-manager.io/v1
|
|
kind: Certificate
|
|
metadata:
|
|
name: tls
|
|
namespace: monero
|
|
spec:
|
|
secretName: tls
|
|
privateKey:
|
|
algorithm: RSA
|
|
encoding: PKCS1
|
|
size: 2048
|
|
duration: 2160h # 90d
|
|
renewBefore: 360h # 15d
|
|
isCA: false
|
|
usages:
|
|
- server auth
|
|
- client auth
|
|
subject:
|
|
organizations:
|
|
- EOM
|
|
commonName: p2pool.eom.dev
|
|
dnsNames:
|
|
- p2pool.eom.dev
|
|
issuerRef:
|
|
name: ca-issuer
|
|
kind: ClusterIssuer
|
|
|
|
# Kept out of the Deployments so the login doesn't show in their specs.
|
|
- name: Store the monerod RPC login
|
|
k8s:
|
|
state: present
|
|
definition:
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: "{{ monerod_rpc_secret_name }}"
|
|
namespace: monero
|
|
type: Opaque
|
|
stringData:
|
|
login: "{{ monerod_user }}:{{ monerod_password }}"
|
|
no_log: true
|
|
|
|
- name: Edit Deployment for Monerod
|
|
k8s:
|
|
definition:
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: monerod
|
|
namespace: monero
|
|
spec:
|
|
replicas: 1
|
|
selector:
|
|
matchLabels:
|
|
app: monerod
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: monerod
|
|
spec:
|
|
containers:
|
|
- name: monerod
|
|
image: ericomeehan/monerod:latest
|
|
imagePullPolicy: Always
|
|
command:
|
|
- monerod
|
|
args:
|
|
- "--p2p-bind-ip=0.0.0.0"
|
|
- "--p2p-bind-port=18080"
|
|
- "--rpc-bind-ip=0.0.0.0"
|
|
- "--rpc-bind-port=18081"
|
|
- "--restricted-rpc"
|
|
- "--rpc-login=$(MONEROD_RPC_LOGIN)"
|
|
- "--data-dir=/data"
|
|
- "--zmq-pub=tcp://0.0.0.0:18083" # P2Pool connects from its own pod
|
|
# autodetect: Shkeeper only speaks plain HTTP to monerod (it never sets
|
|
# protocol=https), so the port must accept both.
|
|
- "--rpc-ssl=autodetect"
|
|
- "--rpc-ssl-private-key=/etc/letsencrypt/tls.key"
|
|
- "--rpc-ssl-certificate=/etc/letsencrypt/tls.crt"
|
|
- "--non-interactive"
|
|
- "--no-igd"
|
|
- "--confirm-external-bind"
|
|
- "--bootstrap-daemon-address=auto"
|
|
env:
|
|
- name: MONEROD_RPC_LOGIN
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: "{{ monerod_rpc_secret_name }}"
|
|
key: login
|
|
resources:
|
|
requests:
|
|
memory: 2Gi
|
|
ports:
|
|
- containerPort: 18080
|
|
- containerPort: 18081
|
|
- containerPort: 18083
|
|
volumeMounts:
|
|
- name: monerod
|
|
mountPath: /data
|
|
- name: monerotls
|
|
mountPath: /etc/letsencrypt
|
|
volumes:
|
|
- name: monerod
|
|
persistentVolumeClaim:
|
|
claimName: data
|
|
- name: monerotls
|
|
secret:
|
|
secretName: monerotls
|
|
|
|
# Mounted into the stats sidecar; the pod annotation below rolls it on change.
|
|
- name: Store the P2Pool stats sidecar script
|
|
k8s:
|
|
state: present
|
|
definition:
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: p2pool-stats
|
|
namespace: monero
|
|
data:
|
|
p2pool_stats.py: "{{ lookup('ansible.builtin.file', 'p2pool_stats.py') }}"
|
|
|
|
- name: deploy p2pool
|
|
k8s:
|
|
definition:
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: p2pool
|
|
namespace: monero
|
|
spec:
|
|
replicas: 1
|
|
selector:
|
|
matchLabels:
|
|
app: p2pool
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: p2pool
|
|
annotations:
|
|
monero/stats-script: "{{ lookup('ansible.builtin.file', 'p2pool_stats.py') | hash('sha256') }}"
|
|
spec:
|
|
containers:
|
|
- name: p2pool
|
|
image: ericomeehan/p2pool:latest
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- p2pool
|
|
# P2Pool takes "--opt value" only; "--opt=value" makes it exit.
|
|
args: >-
|
|
{{
|
|
['--host', 'monerod',
|
|
'--rpc-port', '18081',
|
|
'--wallet', monero_p2pool_wallet_address,
|
|
'--stratum', '0.0.0.0:3333',
|
|
'--p2p', '0.0.0.0:37889',
|
|
# JSON stats for the sidecar below. local/stratum lists
|
|
# worker IPs, so /api is never served directly.
|
|
'--data-api', '/api', '--local-api']
|
|
+ ([] if monero_p2pool_chain == 'main' else ['--' ~ monero_p2pool_chain])
|
|
+ (['--addpeers', monero_p2pool_addpeers | join(',')] if monero_p2pool_addpeers else [])
|
|
+ ['--tls-cert', '/etc/letsencrypt/tls.crt',
|
|
'--tls-cert-key', '/etc/letsencrypt/tls.key',
|
|
'--rpc-ssl',
|
|
'--rpc-login', '$(MONEROD_RPC_LOGIN)']
|
|
}}
|
|
env:
|
|
- name: MONEROD_RPC_LOGIN
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: "{{ monerod_rpc_secret_name }}"
|
|
key: login
|
|
ports:
|
|
- containerPort: 3333
|
|
- containerPort: 37889
|
|
volumeMounts:
|
|
- name: p2pool
|
|
mountPath: /home/p2pool
|
|
- name: tls
|
|
mountPath: /etc/letsencrypt
|
|
- name: api
|
|
mountPath: /api
|
|
# Serves an allowlisted summary of /api at /p2pool/stats.json.
|
|
- name: stats
|
|
image: "{{ monero_p2pool_stats_image }}"
|
|
command: ["python3", "-I", "/app/p2pool_stats.py"]
|
|
env:
|
|
- name: P2POOL_API_DIR
|
|
value: /api
|
|
- name: P2POOL_CHAIN
|
|
value: "{{ monero_p2pool_chain }}"
|
|
- name: PYTHONDONTWRITEBYTECODE
|
|
value: "1"
|
|
ports:
|
|
- containerPort: 8080
|
|
name: stats
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /healthz
|
|
port: stats
|
|
resources:
|
|
requests:
|
|
cpu: 10m
|
|
memory: 32Mi
|
|
limits:
|
|
memory: 64Mi
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 65534
|
|
runAsGroup: 65534
|
|
readOnlyRootFilesystem: true
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
volumeMounts:
|
|
- name: api
|
|
mountPath: /api
|
|
readOnly: true
|
|
- name: stats-app
|
|
mountPath: /app
|
|
readOnly: true
|
|
volumes:
|
|
- name: p2pool
|
|
persistentVolumeClaim:
|
|
claimName: p2pool
|
|
- name: tls
|
|
secret:
|
|
secretName: tls
|
|
- name: api
|
|
emptyDir:
|
|
sizeLimit: 64Mi
|
|
- name: stats-app
|
|
configMap:
|
|
name: p2pool-stats
|
|
|
|
- name: monero service
|
|
k8s:
|
|
definition:
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: monerod
|
|
namespace: monero
|
|
spec:
|
|
selector:
|
|
app: monerod
|
|
ports:
|
|
- port: 18080
|
|
name: p2p
|
|
- port: 18081
|
|
name: rpc
|
|
- port: 18083
|
|
name: zmq
|
|
type: LoadBalancer
|
|
|
|
- name: p2pool service
|
|
k8s:
|
|
definition:
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: p2pool
|
|
namespace: monero
|
|
spec:
|
|
selector:
|
|
app: p2pool
|
|
ports:
|
|
- port: 3333
|
|
name: mine
|
|
- port: 37889
|
|
name: p2poolp2p
|
|
type: LoadBalancer
|
|
|
|
- name: p2pool stats service
|
|
k8s:
|
|
definition:
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: p2pool-stats
|
|
namespace: monero
|
|
spec:
|
|
selector:
|
|
app: p2pool
|
|
ports:
|
|
- port: 8080
|
|
targetPort: stats
|
|
name: http
|
|
type: ClusterIP
|
|
|
|
# Adds one exact path to the website's host. ingress-nginx merges it into that
|
|
# host's server block, so it reuses the website's TLS certificate and the
|
|
# homepage can fetch it same-origin. Nothing else on the host is affected.
|
|
- name: Publish P2Pool stats on the website host
|
|
k8s:
|
|
state: "{{ 'present' if monero_p2pool_stats_host | length > 0 else 'absent' }}"
|
|
definition:
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: Ingress
|
|
metadata:
|
|
name: p2pool-stats
|
|
namespace: monero
|
|
annotations:
|
|
nginx.ingress.kubernetes.io/enable-access-log: "false"
|
|
spec:
|
|
ingressClassName: "{{ monero_p2pool_stats_ingress_class }}"
|
|
rules:
|
|
- host: "{{ monero_p2pool_stats_host | default('unset', true) }}"
|
|
http:
|
|
paths:
|
|
- path: /p2pool/stats.json
|
|
pathType: Exact
|
|
backend:
|
|
service:
|
|
name: p2pool-stats
|
|
port:
|
|
number: 8080
|
|
|