From 75825c979443f802a83bfc03eabda127a1d48def Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Wed, 13 Aug 2025 01:00:20 -0500 Subject: [PATCH] add low priority mining --- ...rbor.yaml => kustomization-xmr-miner.yaml} | 12 +- scripts/gitea_recovery.fish | 8 +- scripts/monero_wallet_setup.fish | 628 ++++++++++++------ services/crypto/monerod/deployment.yaml | 8 + .../crypto/xmr-miner/configmap-sources.yaml | 21 + services/crypto/xmr-miner/deployment.yaml | 84 +++ services/crypto/xmr-miner/kustomization.yaml | 10 + services/crypto/xmr-miner/priority-class.yaml | 8 + services/crypto/xmr-miner/secret-wallet.yaml | 9 + services/crypto/xmr-miner/service.yaml | 23 + .../crypto/xmr-miner/xmrig-daemonset.yaml | 86 +++ services/harbor/certificate.yaml | 12 - services/harbor/helmrelease.yaml | 69 -- services/harbor/helmrepository.yaml | 8 - services/harbor/kustomization.yaml | 7 - services/harbor/namespace.yaml | 4 - 16 files changed, 667 insertions(+), 330 deletions(-) rename infrastructure/flux-system/{kustomization-harbor.yaml => kustomization-xmr-miner.yaml} (60%) mode change 100755 => 100644 scripts/gitea_recovery.fish create mode 100644 services/crypto/xmr-miner/configmap-sources.yaml create mode 100644 services/crypto/xmr-miner/deployment.yaml create mode 100644 services/crypto/xmr-miner/kustomization.yaml create mode 100644 services/crypto/xmr-miner/priority-class.yaml create mode 100644 services/crypto/xmr-miner/secret-wallet.yaml create mode 100644 services/crypto/xmr-miner/service.yaml create mode 100644 services/crypto/xmr-miner/xmrig-daemonset.yaml delete mode 100644 services/harbor/certificate.yaml delete mode 100644 services/harbor/helmrelease.yaml delete mode 100644 services/harbor/helmrepository.yaml delete mode 100644 services/harbor/kustomization.yaml delete mode 100644 services/harbor/namespace.yaml diff --git a/infrastructure/flux-system/kustomization-harbor.yaml b/infrastructure/flux-system/kustomization-xmr-miner.yaml similarity index 60% rename from infrastructure/flux-system/kustomization-harbor.yaml rename to infrastructure/flux-system/kustomization-xmr-miner.yaml index cae7062..87d8bb0 100644 --- a/infrastructure/flux-system/kustomization-harbor.yaml +++ b/infrastructure/flux-system/kustomization-xmr-miner.yaml @@ -1,16 +1,18 @@ -# infrastructure/kustomization-harbor.yaml apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: - name: harbor + name: xmr-miner namespace: flux-system spec: interval: 10m - path: ./services/harbor - targetNamespace: harbor + path: ./services/crypto/xmr-miner + targetNamespace: crypto prune: true sourceRef: kind: GitRepository name: flux-system namespace: flux-system - wait: true \ No newline at end of file + dependsOn: + - name: crypto + - name: monerod + wait: true diff --git a/scripts/gitea_recovery.fish b/scripts/gitea_recovery.fish old mode 100755 new mode 100644 index 8499859..3f0d612 --- a/scripts/gitea_recovery.fish +++ b/scripts/gitea_recovery.fish @@ -1,14 +1,11 @@ + #!/usr/bin/env fish -# run from your workstation -set jump titan-db # alias from ~/.ssh/config (192.168.22.10:2277) - -# grab host list from titan-db’s ~/.ssh/config +set jump titan-db set nodes (ssh $jump 'grep -E "^Host titan-" ~/.ssh/config | awk "{print \$2}"') for n in $nodes echo "=== $n ===" - # list volumes ssh $jump "ssh $n 'ls /dev/longhorn'" 2>/dev/null # look for app.ini or repos inside each volume @@ -19,4 +16,3 @@ for n in $nodes (ls /mnt/git/repositories 2>/dev/null || true); sudo umount /mnt'" 2>/dev/null end end - diff --git a/scripts/monero_wallet_setup.fish b/scripts/monero_wallet_setup.fish index bedf35d..2d6c656 100644 --- a/scripts/monero_wallet_setup.fish +++ b/scripts/monero_wallet_setup.fish @@ -55,6 +55,18 @@ function _sc_list --description "list storageclasses as menu" ' 2>/dev/null end +# convert XMR -> piconero (atomic units) with Python (exact +function _xmr_to_atomic -a AMT + _need python3; or return 1 + printf '%s' 'from decimal import Decimal, getcontext +import sys +getcontext().prec = 50 +amt = Decimal(sys.argv[1]) +atomic = int((amt * Decimal("1000000000000")).to_integral_value(rounding="ROUND_HALF_UP")) +print(atomic) +' | python3 - "$AMT" +end + # Choose SC (menu -> stderr; selection -> stdout) function _choose_sc --description "interactive StorageClass picker" set lines (_sc_list) @@ -141,8 +153,6 @@ function _rpc_call --description "call wallet JSON-RPC via localhost pf" -a RPCU else set payload (printf '{"jsonrpc":"2.0","id":"0","method":"%s","params":%s}' $METHOD "$PARAMS") end - # Use --digest so we satisfy the server's challenge - # Note: callers should check for an empty response (curl --fail returns nothing). curl -s --fail --digest -u "$RPCUSER:$RPCPASS" \ -H "Content-Type: application/json" \ -d "$payload" "$url" @@ -156,65 +166,97 @@ function _probe_monerod -a ADDR return 1 end - # Loud early block on suspicious names if string match -qi '*qubic*' -- $ADDR echo (set_color red)"Refusing daemon '$ADDR' (contains 'qubic'). This is NOT a Monero node."(set_color normal) >&2 return 1 end - # Try /get_info (HTTP) first (most public nodes support this) - set raw (curl -s --max-time 7 "http://$ADDR/get_info" 2>/dev/null) - set ok 0 - if test -n "$raw" - set status (echo $raw | jq -r '.status // "OK"') - set testnet (echo $raw | jq -r '.testnet // false') - set stagenet (echo $raw | jq -r '.stagenet // false') - set height (echo $raw | jq -r '.height // .target_height // 0') - if test "$status" = "OK" - set ok 1 - end - end - - # Fallback to JSON-RPC get_info (some nodes only expose /json_rpc) - if test $ok -eq 0 - set raw (curl -s --max-time 7 -H 'Content-Type: application/json' \ - -d '{"jsonrpc":"2.0","id":"0","method":"get_info"}' "http://$ADDR/json_rpc" \ - | jq -r '.result? | @json' 2>/dev/null) + # Try up to 3 short attempts + for t in (seq 1 3) + set ok 0 + set raw (curl -s --max-time 7 "http://$ADDR/get_info" 2>/dev/null) if test -n "$raw" - set status (echo $raw | jq -r '.status // "OK"') - set testnet (echo $raw | jq -r '.testnet // false') + set mstatus (echo $raw | jq -r '.status // "OK"') + set testnet (echo $raw | jq -r '.testnet // false') set stagenet (echo $raw | jq -r '.stagenet // false') - set height (echo $raw | jq -r '.height // .target_height // 0') - if test "$status" = "OK" + set height (echo $raw | jq -r '.height // .target_height // 0') + if test "$mstatus" = "OK" set ok 1 end end + + if test $ok -eq 0 + set raw (curl -s --max-time 7 -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","id":"0","method":"get_info"}' "http://$ADDR/json_rpc" \ + | jq -r '.result? | @json' 2>/dev/null) + if test -n "$raw" + set mstatus (echo $raw | jq -r '.status // "OK"') + set testnet (echo $raw | jq -r '.testnet // false') + set stagenet (echo $raw | jq -r '.stagenet // false') + set height (echo $raw | jq -r '.height // .target_height // 0') + if test "$mstatus" = "OK" + set ok 1 + end + end + end + + if test $ok -eq 1 + if test "$testnet" = "true" -o "$stagenet" = "true" + echo (set_color red)"Refusing non-mainnet daemon (testnet=$testnet stagenet=$stagenet) at $ADDR."(set_color normal) >&2 + return 1 + end + echo "Daemon OK: Monero mainnet, height=$height" + return 0 + end + sleep 1 end - if test $ok -eq 0 - echo (set_color red)"Could not speak Monero /get_info to $ADDR. Not a monerod (or blocked). Aborting."(set_color normal) >&2 - echo "Tip: run your own monerod and use its ClusterIP:Port, or set WALLETSVC_SKIP_DAEMON_CHECK=1 to bypass." >&2 + echo (set_color red)"Could not speak Monero /get_info to $ADDR. Not a monerod (or blocked)."(set_color normal) >&2 + echo "Tip: run your own monerod or set WALLETSVC_SKIP_DAEMON_CHECK=1 to bypass." >&2 + return 1 +end + +# Wait for wallet RPC to accept connections (digest auth get_version) +function walletsvc_wait_ready -a NS SVC SECS + if test -z "$NS"; set NS crypto; end + if test -z "$SVC"; set SVC wallet-rpc; end + if test -z "$SECS"; set SECS 60; end + + set RPCUSER (kubectl -n $NS get secret {$SVC}-rpc-auth -o jsonpath='{.data.username}' | base64 -d 2>/dev/null) + set RPCPASS (kubectl -n $NS get secret {$SVC}-rpc-auth -o jsonpath='{.data.password}' | base64 -d 2>/dev/null) + if test -z "$RPCUSER" -o -z "$RPCPASS" + echo (set_color red)"Error:"(set_color normal)" missing RPC creds in secret {$SVC}-rpc-auth" >&2 return 1 end - if test "$testnet" = "true" -o "$stagenet" = "true" - echo (set_color red)"Refusing non-mainnet daemon (testnet=$testnet stagenet=$stagenet) at $ADDR."(set_color normal) >&2 - return 1 + _pf_start $NS $SVC 18083 18083 + set tries (math "ceil($SECS/2)") + for i in (seq 1 $tries) + set code (curl -s -o /dev/null -w "%{http_code}" --digest -u "$RPCUSER:$RPCPASS" \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","id":"0","method":"get_version"}' \ + http://127.0.0.1:18083/json_rpc) + if test "$code" = "200" + _pf_stop + return 0 + end + sleep 2 end - - echo "Daemon OK: Monero mainnet, height=$height" + _pf_stop + echo (set_color red)"RPC not ready after $SECS seconds."(set_color normal) >&2 + return 1 end ### ------- main workflow --------------------------------------------------- -function walletsvc_bootstrap --description "Interactive setup of monero-wallet-rpc (multi-wallet) with PVC + secrets" +function walletsvc_bootstrap --description "Interactive setup of monero-wallet-rpc with PVC+secrets; creates default wallet and prints recovery info" _need kubectl jq curl awk; or return 1 _banner "Monero wallet RPC bootstrap" # --- Namespace - read -P "Namespace [monero]: " ns_raw - if test -z "$ns_raw"; set ns_raw monero; end + read -P "Namespace [crypto]: " ns_raw + if test -z "$ns_raw"; set ns_raw crypto; end set ns (_k8s_name $ns_raw) _require "Namespace" $ns; or return 1 @@ -231,28 +273,64 @@ function walletsvc_bootstrap --description "Interactive setup of monero-wallet-r echo "TIP: Prefer a class with reclaimPolicy=Retain so a PVC delete won't delete the PV." set sc (_choose_sc); or return 1 - # --- Names + sizes - read -P "PVC name (for wallet data) [wallet-data]: " pvc_name_raw - if test -z "$pvc_name_raw"; set pvc_name_raw wallet-data; end - set pvc_name (_k8s_name $pvc_name_raw) - _require "PVC name" $pvc_name; or return 1 - - read -P "PVC size [5Gi]: " pvc_size - if test -z "$pvc_size"; set pvc_size 5Gi; end - _require "PVC size" $pvc_size; or return 1 - - read -P "App label / Deployment name [wallet-rpc]: " app_raw - if test -z "$app_raw"; set app_raw wallet-rpc; end - set app_name (_k8s_name $app_raw) - _require "App/Deployment name" $app_name; or return 1 - - read -P "Service name (blank = same as app): " svc_raw - if test -z "$svc_raw" - set svc_name $app_name - else - set svc_name (_k8s_name $svc_raw) + # --- Unified name (prefix wallet-monero-) + read -P "Wallet name (no spaces, e.g. 'brad', base will be wallet-monero-brad): " wallet_raw + _require "Wallet name" $wallet_raw; or return 1 + if string match -rq '\s' -- $wallet_raw + echo (set_color red)"Error:"(set_color normal)" Wallet name cannot contain spaces." >&2 + return 1 + end + set base (_k8s_name $wallet_raw) + set base (string replace -r '^wallet-monero-' '' -- $base) + set name "wallet-monero-$base" + + # Bind all k8s object names to the unified base + set pvc_name $name + set app_name $name + set svc_name $name + set rpc_secret_name "$app_name-rpc-auth" + set wpass_secret_name "$app_name-wallet-pass" + + # --- Detect existing resources (all of them) + set exists_any 0 + set found_list + for r in "deploy/$app_name" "svc/$svc_name" "netpol/{$app_name}-ingress" + if kubectl -n $ns get $r >/dev/null 2>&1 + set -a found_list $r + set exists_any 1 + end + end + if kubectl -n $ns get pvc $pvc_name >/dev/null 2>&1 + set -a found_list "pvc/$pvc_name" + set exists_any 1 + end + if kubectl -n $ns get secret $rpc_secret_name >/dev/null 2>&1 + set -a found_list "secret/$rpc_secret_name" + set exists_any 1 + end + if kubectl -n $ns get secret $wpass_secret_name >/dev/null 2>&1 + set -a found_list "secret/$wpass_secret_name" + set exists_any 1 + end + + if test $exists_any -eq 1 + _banner "Existing resources detected for $name" + for r in $found_list + echo " - $ns/$r" + end + read -P "Remove EVERYTHING (deploy/svc/netpol/PVC/SECRETS) and recreate? (y/N): " wipe + if string match -qi 'y*' -- $wipe + kubectl -n $ns delete deploy $app_name --ignore-not-found --wait=true >/dev/null 2>&1; or true + kubectl -n $ns delete svc $svc_name --ignore-not-found --wait=true >/dev/null 2>&1; or true + kubectl -n $ns delete netpol {$app_name}-ingress --ignore-not-found --wait=true >/dev/null 2>&1; or true + kubectl -n $ns delete pvc $pvc_name --ignore-not-found --wait=true >/dev/null 2>&1; or true + kubectl -n $ns delete secret $rpc_secret_name --ignore-not-found >/dev/null 2>&1; or true + kubectl -n $ns delete secret $wpass_secret_name --ignore-not-found >/dev/null 2>&1; or true + echo "Cleaned up all previous resources." + else + echo "Reusing existing resources; they will be applied in-place." + end end - _require "Service name" $svc_name; or return 1 # --- Node scheduling (worker-only by default) read -P "Schedule only on worker nodes? (y/N) [y]: " pin_workers @@ -266,17 +344,25 @@ function walletsvc_bootstrap --description "Interactive setup of monero-wallet-r if test -z "$node_label_val"; set node_label_val true; end end - # --- Daemon + image + # --- Daemon (auto-use local monerod) echo - echo "Monero node (daemon) the wallet will talk to." - echo " - Best: your own monerod (trusted). Example (same ns): monerod:18081" - echo " - Remote nodes work, but reduce privacy and add trust." - read -P "Daemon address (host:port) [REQUIRED]: " daemon_addr - _require "Daemon address" $daemon_addr; or return 1 + echo "Monero daemon: using your cluster-local monerod by default." + if test "$ns" = "crypto" + set daemon_addr monerod:18081 + else + set daemon_addr monerod.crypto.svc.cluster.local:18081 + end + echo "Daemon address (for the Deployment): $daemon_addr" if test -z "$WALLETSVC_SKIP_DAEMON_CHECK" - _banner "Probing daemon" - _probe_monerod $daemon_addr; or return 1 + _banner "Probing daemon via temporary port-forward" + set PF_LOCAL 28081 + _pf_start crypto monerod $PF_LOCAL 18081 + if not _probe_monerod 127.0.0.1:$PF_LOCAL + _pf_stop + return 1 + end + _pf_stop else echo "Skipping daemon probe due to WALLETSVC_SKIP_DAEMON_CHECK=1" end @@ -286,28 +372,49 @@ function walletsvc_bootstrap --description "Interactive setup of monero-wallet-r if test -z "$image"; set image docker.io/sethsimmons/simple-monero-wallet-rpc:latest; end _require "Container image" $image; or return 1 - # --- RPC + wallet credentials - read -P "RPC username [walletrpc]: " rpc_user - if test -z "$rpc_user"; set rpc_user walletrpc; end + # --- Secrets (defaults: RPC user=wallet name, passwords auto if missing) + set existing_rpc_user (kubectl -n $ns get secret $rpc_secret_name -o jsonpath='{.data.username}' 2>/dev/null | base64 -d 2>/dev/null) + set existing_rpc_pass (kubectl -n $ns get secret $rpc_secret_name -o jsonpath='{.data.password}' 2>/dev/null | base64 -d 2>/dev/null) + set existing_wpass (kubectl -n $ns get secret $wpass_secret_name -o jsonpath='{.data.password}' 2>/dev/null | base64 -d 2>/dev/null) + + set rpc_user_default $base + if test -n "$existing_rpc_user" + set rpc_user_default $existing_rpc_user + end + read -P "RPC username [$rpc_user_default]: " rpc_user + if test -z "$rpc_user"; set rpc_user $rpc_user_default; end _require "RPC username" $rpc_user; or return 1 - read -s -P "RPC password [auto-generate 32 alnum if blank]: " rpc_pass + read -s -P "RPC password [leave blank to keep current or auto-generate if none]: " rpc_pass echo - if test -z "$rpc_pass"; set rpc_pass (_rand_alnum32); end + if test -z "$rpc_pass" + if test -n "$existing_rpc_pass" + set rpc_pass $existing_rpc_pass + else + set rpc_pass (_rand_alnum32) + end + end read -P "Wallet filename (no path) [main]: " wallet_file_raw if test -z "$wallet_file_raw"; set wallet_file_raw main; end set wallet_file (_k8s_name $wallet_file_raw) _require "Wallet filename" $wallet_file; or return 1 - read -s -P "Wallet password [auto-generate 32 alnum if blank]: " wallet_pass + read -s -P "Wallet password [leave blank to keep current or auto-generate if none]: " wallet_pass echo - if test -z "$wallet_pass"; set wallet_pass (_rand_alnum32); end + if test -z "$wallet_pass" + if test -n "$existing_wpass" + set wallet_pass $existing_wpass + else + set wallet_pass (_rand_alnum32) + end + end _banner "Summary" echo " Namespace: $ns" echo " StorageClass: $sc" - echo " PVC: $pvc_name ($pvc_size)" + echo " Base name: $name" + echo " PVC: $pvc_name" echo " App/Deployment: $app_name" echo " Service: $svc_name" echo " Daemon address: $daemon_addr" @@ -323,9 +430,6 @@ function walletsvc_bootstrap --description "Interactive setup of monero-wallet-r return 1 end - set rpc_secret_name "$app_name-rpc-auth" - set wpass_secret_name "$app_name-wallet-pass" - _banner "Applying secrets" kubectl -n $ns create secret generic $rpc_secret_name \ --from-literal=username="$rpc_user" \ @@ -336,29 +440,35 @@ function walletsvc_bootstrap --description "Interactive setup of monero-wallet-r --from-literal=password="$wallet_pass" \ --dry-run=client -o yaml | kubectl -n $ns apply -f -; or return 1 - # Annotate with timestamp so pod restarts when only secrets change set ts (date -Is) kubectl -n $ns annotate secret $rpc_secret_name walletsvc.titan/update-ts="$ts" --overwrite >/dev/null 2>&1 kubectl -n $ns annotate secret $wpass_secret_name walletsvc.titan/update-ts="$ts" --overwrite >/dev/null 2>&1 + echo "RPC creds: user='$rpc_user' pass='$rpc_pass'" + echo "Wallet pass: $wallet_pass" + _banner "Applying PVC" - set pvcfile (mktemp -t {$pvc_name}-pvc.XXXX.yaml) - begin - echo "apiVersion: v1" - echo "kind: PersistentVolumeClaim" - echo "metadata:" - echo " name: $pvc_name" - echo " namespace: $ns" - echo "spec:" - echo " accessModes: [\"ReadWriteOnce\"]" - echo " storageClassName: $sc" - echo " resources:" - echo " requests:" - echo " storage: $pvc_size" - end > $pvcfile - echo "--- PVC manifest ($pvcfile) ---" - cat $pvcfile - kubectl apply -f $pvcfile; or return 1 + if kubectl -n $ns get pvc $pvc_name >/dev/null 2>&1 + echo "PVC $ns/$pvc_name exists; keeping as-is." + else + set pvcfile (mktemp -t {$pvc_name}-pvc.XXXX.yaml) + begin + echo "apiVersion: v1" + echo "kind: PersistentVolumeClaim" + echo "metadata:" + echo " name: $pvc_name" + echo " namespace: $ns" + echo "spec:" + echo " accessModes: [\"ReadWriteOnce\"]" + echo " storageClassName: $sc" + echo " resources:" + echo " requests:" + echo " storage: 5Gi" + end > $pvcfile + echo "--- PVC manifest ($pvcfile) ---" + cat $pvcfile + kubectl apply -f $pvcfile; or return 1 + end _banner "Applying Deployment" set dply (mktemp -t {$app_name}-deploy.XXXX.yaml) @@ -483,7 +593,6 @@ function walletsvc_bootstrap --description "Interactive setup of monero-wallet-r cat $np kubectl apply -f $np; or return 1 - # If Deployment existed and only secrets changed, force a restart so new creds are read _banner "Ensuring pod picks up latest secrets" kubectl -n $ns rollout restart deploy/$app_name >/dev/null 2>&1; or true @@ -491,61 +600,80 @@ function walletsvc_bootstrap --description "Interactive setup of monero-wallet-r if not kubectl -n $ns rollout status deploy/$app_name --timeout=180s echo (set_color red)"Rollout did not finish in time. Recent events:"(set_color normal) >&2 kubectl -n $ns get events --sort-by=.lastTimestamp | tail -n 40 - echo (set_color yellow)"Hint:"(set_color normal)" if you see Multi-Attach for the PVC, delete any older pod (e.g. 'kubectl -n $ns delete pod ') and the new one will attach." + echo (set_color yellow)"Hint:"(set_color normal)" if you see Multi-Attach for the PVC, delete any older pod." return 1 end - _banner "Creating/opening wallet via JSON-RPC" + _banner "Waiting for wallet RPC readiness" + walletsvc_wait_ready $ns $svc_name 60; or return 1 - # sanity: verify /data is writable before we try to create anything + _banner "Creating/opening wallet via JSON-RPC" if not walletsvc_check_write $ns $app_name >/dev/null echo (set_color red)"PVC is not writable; aborting."(set_color normal) return 1 end - # create (idempotent) then open + # Always attempt create; if it exists, open it. if not walletsvc_create_wallet $ns $svc_name $wallet_file $wallet_pass - echo (set_color red)"Failed to create wallet $wallet_file. See pod logs."(set_color normal) + echo (set_color yellow)"create_wallet returned non-zero; attempting open anyway…"(set_color normal) + end + if not walletsvc_open $ns $svc_name $wallet_file $wallet_pass + echo (set_color red)"Failed to open wallet $wallet_file. If this wallet already exists, the on-disk password may differ from the Secret. Use walletsvc_open with the correct password or walletsvc_change_wallet_password."(set_color normal) return 1 end - if not walletsvc_open $ns $svc_name $wallet_file $wallet_pass - echo (set_color red)"Failed to open wallet $wallet_file. See pod logs."(set_color normal) - return 1 - end + # --- Gather summary data (address, seed if enabled) + set PRIMARY (walletsvc_primary_address $ns $svc_name 2>/dev/null) + set NEWADDR (walletsvc_new_address $ns $svc_name "bootstrap-deposit" 0 2>/dev/null | awk '/New address:/{print $3}' ) + set SEEDRAW (walletsvc_show_seed $ns $svc_name 2>&1) + set SEED (echo $SEEDRAW | awk '/Mnemonic seed:/,0{if($0!~/Mnemonic seed:/)print}' ) _banner "Done" echo "Namespace: $ns" - echo "App/Deployment: $app_name" - echo "Service: $svc_name (ClusterIP)" + echo "Base name: $name" + echo "Service (in-cluster): $svc_name:18083" echo "PVC: $pvc_name via StorageClass '$sc'" echo "RPC auth: user='$rpc_user'" echo "RPC pass: $rpc_pass" echo "Wallet pass: $wallet_pass" echo "Wallet file: /data/$wallet_file" + echo "Primary address (acct 0):" + echo " $PRIMARY" + if test -n "$NEWADDR" + echo "Fresh deposit subaddress (acct 0):" + echo " $NEWADDR" + end + if string match -qi '*Mnemonic seed:*' -- $SEEDRAW + echo (set_color yellow)"Mnemonic seed (WRITE THIS DOWN, OFFLINE):"(set_color normal) + echo $SEED + else + echo "Mnemonic seed not retrievable via RPC (image may disable it)." + echo "If needed and supported by your image: walletsvc_show_seed $ns $svc_name" + end echo - echo "Access locally: walletsvc_portforward $ns $svc_name" - echo "Show seed (if enabled by image): walletsvc_show_seed $ns $svc_name" + echo "Access locally: walletsvc_portforward $ns $svc_name # then http://127.0.0.1:18083/json_rpc" + echo "Quick overview: walletsvc_overview $ns $svc_name" end + ### ------- utilities ------------------------------------------------------- function walletsvc_portforward --description "port-forward RPC to localhost:18083" -a NS SVC - if test -z "$NS"; set NS monero; end + if test -z "$NS"; set NS crypto; end if test -z "$SVC"; set SVC wallet-rpc; end echo "Forwarding $NS/$SVC → http://127.0.0.1:18083 (Ctrl+C to stop)…" kubectl -n $NS port-forward svc/$SVC 18083:18083 end function walletsvc_status --description "show pod/service status" -a NS APP - if test -z "$NS"; set NS monero; end + if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-rpc; end kubectl -n $NS get deploy,po,svc,netpol -l app=$APP -o wide end function walletsvc_stop --description "stop the RPC but keep PVC + secrets" -a NS APP SVC - if test -z "$NS"; set NS monero; end + if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-rpc; end if test -z "$SVC"; set SVC $APP; end kubectl -n $NS delete deploy $APP --ignore-not-found @@ -554,7 +682,7 @@ function walletsvc_stop --description "stop the RPC but keep PVC + secrets" -a N end function walletsvc_purge --description "IRREVERSIBLY delete PVC + secrets (keys on disk!)" -a NS APP PVC SVC - if test -z "$NS"; set NS monero; end + if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-rpc; end if test -z "$PVC"; set PVC wallet-data; end if test -z "$SVC"; set SVC $APP; end @@ -574,7 +702,7 @@ end function walletsvc_show_seed --description "Try to fetch mnemonic via RPC query_key (wallet must be open)" -a NS SVC RPCUSER RPCPASS _need jq curl; or return 1 - if test -z "$NS"; set NS monero; end + if test -z "$NS"; set NS crypto; end if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$RPCUSER" set RPCUSER (kubectl -n $NS get secret {$SVC}-rpc-auth -o jsonpath='{.data.username}' | base64 -d) @@ -597,7 +725,7 @@ end # ---------- core JSON-RPC wrapper (raw JSON to stdout) ---------- function walletsvc_rpc_call --description "RPC call with secrets (auto port-forward)" -a NS SVC METHOD PARAMS_JSON - if test -z "$NS"; set NS monero; end + if test -z "$NS"; set NS crypto; end if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$METHOD" echo "Usage: walletsvc_rpc_call [ns] [svc] [params_json]" >&2 @@ -614,7 +742,6 @@ function walletsvc_rpc_call --description "RPC call with secrets (auto port-forw set payload (printf '{"jsonrpc":"2.0","id":"0","method":"%s"}' $METHOD) end - # capture HTTP code and body without using --fail so we always get the body set tmp (mktemp) set code (curl -s --digest -u "$RPCUSER:$RPCPASS" \ -H 'Content-Type: application/json' \ @@ -648,7 +775,7 @@ end # Rotate RPC username/password (updates Secret & restarts Deploy) function walletsvc_set_rpc_credentials --description "rotate RPC Basic creds" -a NS APP _need kubectl; or return 1 - if test -z "$NS"; set NS monero; end + if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-rpc; end set SNAME {$APP}-rpc-auth @@ -676,7 +803,7 @@ end # Change wallet file password via RPC function walletsvc_change_wallet_password --description "change password of a wallet file via RPC" -a NS SVC - if test -z "$NS"; set NS monero; end + if test -z "$NS"; set NS crypto; end if test -z "$SVC"; set SVC wallet-rpc; end read -P "Wallet filename (under /data): " WFILE @@ -690,18 +817,14 @@ function walletsvc_change_wallet_password --description "change password of a wa echo if test -z "$NEW"; set NEW (_rand_alnum32); end - # open the wallet (in case it's not already) set res (walletsvc_rpc_call $NS $SVC open_wallet (printf '{"filename":"%s","password":"%s"}' $WFILE $OLD)) - # ignore error here; we try change anyway so user can see the RPC error if wrong - - # change password walletsvc_rpc_call $NS $SVC change_wallet_password (printf '{"old_password":"%s","new_password":"%s"}' $OLD $NEW) echo "New wallet password: $NEW" end # --- logs from the deployment (quick tail) --- function walletsvc_logs -a NS APP LINES - if test -z "$NS"; set NS monero; end + if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-rpc; end if test -z "$LINES"; set LINES 200; end kubectl -n $NS logs deploy/$APP --tail=$LINES @@ -709,7 +832,7 @@ end # --- list files in /data inside the pod --- function walletsvc_wallet_ls -a NS APP - if test -z "$NS"; set NS monero; end + if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-rpc; end echo "Listing /data in $NS/$APP…" >&2 kubectl -n $NS exec deploy/$APP -- sh -lc 'ls -la /data || true' @@ -717,7 +840,7 @@ end # --- create a wallet (uses Secret password if PASS omitted) --- function walletsvc_create_wallet -a NS SVC FILE PASS LANG - if test -z "$NS"; set NS monero; end + if test -z "$NS"; set NS crypto; end if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$FILE" echo "Usage: walletsvc_create_wallet [ns] [svc] [pass] [language]" >&2 @@ -751,7 +874,7 @@ end # --- improved opener: closes first, checks files, hints if password mismatch --- function walletsvc_open -a NS SVC FILE PASS - if test -z "$NS"; set NS monero; end + if test -z "$NS"; set NS crypto; end if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$FILE" echo "Usage: walletsvc_open [ns] [svc] [password]" >&2 @@ -804,31 +927,84 @@ function walletsvc_open -a NS SVC FILE PASS end function walletsvc_check_write -a NS APP - if test -z "$NS"; set NS monero; end + if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-rpc; end kubectl -n $NS exec deploy/$APP -- sh -lc 'echo test-$(date +%s) > /data/.write-test && ls -l /data/.write-test && rm -f /data/.write-test' end # --- ensure wallet is open (create if missing) --- function walletsvc_ensure_open -a NS SVC FILE - if test -z "$NS"; set NS monero; end + if test -z "$NS"; set NS crypto; end if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$FILE"; set FILE main; end - # If files missing, create using secret password set exists (kubectl -n $NS exec deploy/$SVC -- sh -lc (printf 'if [ -e "/data/%s" ] || [ -e "/data/%s.keys" ]; then echo yes; else echo no; fi' $FILE $FILE) 2>/dev/null) if test "$exists" = "no" echo "No /data/$FILE found. Creating…" >&2 walletsvc_create_wallet $NS $SVC $FILE; or return 1 end - # Open with secret password walletsvc_open $NS $SVC $FILE; or return 1 end +# --- quick primary address print (account 0) +function walletsvc_primary_address -a NS SVC + if test -z "$NS"; set NS crypto; end + if test -z "$SVC"; set SVC wallet-rpc; end + _pf_start $NS $SVC 18083 18083 + set resp (curl -s --fail --digest \ + -u (kubectl -n $NS get secret {$SVC}-rpc-auth -o jsonpath='{.data.username}' | base64 -d):(kubectl -n $NS get secret {$SVC}-rpc-auth -o jsonpath='{.data.password}' | base64 -d) \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","id":"0","method":"get_address","params":{"account_index":0}}' \ + http://127.0.0.1:18083/json_rpc) + _pf_stop + echo $resp | jq -r '.result.address' +end + +# --- show unlocked balance quickly +function walletsvc_unlocked -a NS SVC + _need python3; or return 1 + if test -z "$NS"; set NS crypto; end + if test -z "$SVC"; set SVC wallet-rpc; end + set res (walletsvc_rpc_call $NS $SVC get_balance '{"account_index":0}') + set bal (echo $res | jq -r '.result.balance // 0') + set unl (echo $res | jq -r '.result.unlocked_balance // 0') + + # Convert piconero -> XMR using Python (fish-safe) + set balx (python3 -c 'from decimal import Decimal; import sys; print(Decimal(sys.argv[1]) / Decimal("1e12"))' -- $bal) + set unlx (python3 -c 'from decimal import Decimal; import sys; print(Decimal(sys.argv[1]) / Decimal("1e12"))' -- $unl) + + echo "balance=$balx XMR unlocked=$unlx XMR" +end + +# --- wait until unlocked balance >= threshold (XMR), timeout seconds +function walletsvc_wait_unlocked -a NS SVC XMR_MIN TIMEOUT + _need python3; or return 1 + if test -z "$NS"; set NS crypto; end + if test -z "$SVC"; set SVC wallet-rpc; end + if test -z "$XMR_MIN"; set XMR_MIN 0; end + if test -z "$TIMEOUT"; set TIMEOUT 300; end + set start (date +%s) + while true + set res (walletsvc_rpc_call $NS $SVC get_balance '{"account_index":0}') + set unl (echo $res | jq -r '.result.unlocked_balance // 0') + set unlx (python3 -c 'from decimal import Decimal; import sys; print(Decimal(sys.argv[1]) / Decimal("1e12"))' -- $unl) + set ge (python3 -c 'from decimal import Decimal; import sys; print(1 if Decimal(sys.argv[1]) >= Decimal(sys.argv[2]) else 0)' -- $unlx $XMR_MIN) + if test $ge -eq 1 + echo "Unlocked: $unlx XMR (≥ $XMR_MIN)" + return 0 + end + if test (math (date +%s) - $start) -ge $TIMEOUT + echo "Timed out waiting for unlocked balance ≥ $XMR_MIN XMR (current $unlx XMR)." + return 1 + end + sleep 6 + end +end + # ---------- friendly overview ---------- function walletsvc_overview -a NS SVC ACCOUNT - if test -z "$NS"; set NS monero; end + if test -z "$NS"; set NS crypto; end if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$ACCOUNT"; set ACCOUNT 0; end @@ -838,28 +1014,25 @@ function walletsvc_overview -a NS SVC ACCOUNT _pf_start $NS $SVC 18083 18083 echo "== Wallet RPC overview (ns=$NS svc=$SVC acct=$ACCOUNT)" - # Version set resp (curl -s --fail --digest -u "$RPCUSER:$RPCPASS" \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":"0","method":"get_version"}' \ http://127.0.0.1:18083/json_rpc) echo $resp | jq -r '"-- Version:\n version=\(.result.version) release=\(.result.release)"' - # Height (show error clearly if wallet not open) set resp (curl -s --fail --digest -u "$RPCUSER:$RPCPASS" \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":"0","method":"get_height"}' \ http://127.0.0.1:18083/json_rpc) set err (echo $resp | jq -r '.error.message // empty') + echo "-- Height:" if test -n "$err" - echo "-- Height:" echo " ERROR: $err" echo " (Tip: open your wallet: walletsvc_open $NS $SVC main)" else - echo $resp | jq -r '"-- Height:\n wallet_height=\(.result.height)"' + echo $resp | jq -r '" wallet_height=\(.result.height)"' end - # Accounts set resp (curl -s --fail --digest -u "$RPCUSER:$RPCPASS" \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":"0","method":"get_accounts"}' \ @@ -873,7 +1046,6 @@ function walletsvc_overview -a NS SVC ACCOUNT | " [\(.account_index)] \(.label) total=\(.balance/1000000000000) XMR unlocked=\(.unlocked_balance/1000000000000) XMR"' end - # Balance for selected account set BODY (printf '{"account_index":%s}' $ACCOUNT) set resp (curl -s --fail --digest -u "$RPCUSER:$RPCPASS" \ -H 'Content-Type: application/json' \ @@ -887,7 +1059,6 @@ function walletsvc_overview -a NS SVC ACCOUNT echo $resp | jq -r '" balance=\(.result.balance/1000000000000) XMR unlocked=\(.result.unlocked_balance/1000000000000) XMR"' end - # Primary address set resp (curl -s --fail --digest -u "$RPCUSER:$RPCPASS" \ -H 'Content-Type: application/json' \ -d (printf '{"jsonrpc":"2.0","id":"0","method":"get_address","params":{"account_index":%s}}' $ACCOUNT) \ @@ -900,7 +1071,6 @@ function walletsvc_overview -a NS SVC ACCOUNT echo $resp | jq -r '.result.address' end - # First 5 subaddresses echo "-- First 5 subaddresses:" if test -n "$err" echo " (skipped due to error above)" @@ -908,7 +1078,6 @@ function walletsvc_overview -a NS SVC ACCOUNT echo $resp | jq -r '.result.addresses[:5][] | " [\(.address_index)] \(.address) label=\(.label) used=\(.used)"' end - # Recent transfers set BODY (printf '{"account_index":%s,"in":true}' $ACCOUNT) set in (curl -s --fail --digest -u "$RPCUSER:$RPCPASS" \ -H 'Content-Type: application/json' \ @@ -938,7 +1107,7 @@ end # ---------- list addresses ---------- function walletsvc_list_addresses -a NS SVC ACCOUNT - if test -z "$NS"; set NS monero; end + if test -z "$NS"; set NS crypto; end if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$ACCOUNT"; set ACCOUNT 0; end set RPCUSER (kubectl -n $NS get secret {$SVC}-rpc-auth -o jsonpath='{.data.username}' | base64 -d) @@ -960,7 +1129,7 @@ end # ---------- new subaddress ---------- function walletsvc_new_address -a NS SVC LABEL ACCOUNT - if test -z "$NS"; set NS monero; end + if test -z "$NS"; set NS crypto; end if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$ACCOUNT"; set ACCOUNT 0; end if test -z "$LABEL"; set LABEL (printf "deposit-%s" (date +%Y%m%d-%H%M%S)); end @@ -981,47 +1150,116 @@ function walletsvc_new_address -a NS SVC LABEL ACCOUNT echo $resp | jq -r --arg L "$LABEL" '"New address: \(.result.address) [index=\(.result.address_index)] label=" + $L' end +# --- send a specific amount XMR to a single address (optionally payment_id) +# Usage: walletsvc_send [ns] [svc] [payment_id_hex] +function walletsvc_send -a NS SVC TO AMT_XMR PID + _need jq curl python3; or return 1 + if test -z "$NS"; set NS crypto; end + if test -z "$SVC"; set SVC wallet-rpc; end + if test -z "$TO" -o -z "$AMT_XMR" + echo "Usage: walletsvc_send [ns] [svc] [payment_id_hex]" >&2 + return 1 + end + + # Convert amount to atomic units + set ATOMIC (_xmr_to_atomic $AMT_XMR) + if test -z "$ATOMIC" + echo "Failed to convert amount '$AMT_XMR' to piconero." >&2 + return 1 + end + + # Build JSON + set body_base (printf '{"destinations":[{"amount":%s,"address":"%s"}],"account_index":0,"priority":1,"get_tx_key":true,"do_not_relay":false}' $ATOMIC $TO) + if test -n "$PID" + set body (string replace '}' ',"payment_id":"'$PID'"}' -- $body_base) + else + set body $body_base + end + + set res (walletsvc_rpc_call $NS $SVC transfer $body) + set err (echo $res | jq -r '.error.message // empty') + if test -n "$err" + echo "ERROR: $err" >&2 + return 1 + end + + set fee (echo $res | jq -r '.result.fee // 0') + set tx (echo $res | jq -r '.result.tx_hash // (.result.tx_hash_list[0] // "")') + + # piconero -> XMR without heredoc + set fee_xmr (python3 -c 'from decimal import Decimal; import sys; print(Decimal(sys.argv[1]) / Decimal("1e12"))' -- $fee) + + echo "Sent transaction:" + echo " tx: $tx" + echo " fee: $fee_xmr XMR ($fee piconero)" +end + +# --- sweep all unlocked funds to an address (account 0) +# Usage: walletsvc_sweep_all [ns] [svc] +function walletsvc_sweep_all -a NS SVC TO + _need jq curl; or return 1 + if test -z "$NS"; set NS crypto; end + if test -z "$SVC"; set SVC wallet-rpc; end + if test -z "$TO" + echo "Usage: walletsvc_sweep_all [ns] [svc] " >&2 + return 1 + end + + set body (printf '{"address":"%s","account_index":0,"priority":1,"do_not_relay":false,"get_tx_keys":true}' $TO) + set res (walletsvc_rpc_call $NS $SVC sweep_all $body) + set err (echo $res | jq -r '.error.message // empty') + if test -n "$err" + echo "ERROR: $err" >&2 + return 1 + end + + echo "Sweep transactions:" + echo $res | jq -r '.result.tx_hash_list[]? | " tx: " + .' +end + # Optional: non-sensitive cluster snapshot to help tune manifests. function cluster_probe set OUT cluster-snapshot.txt - echo "# Cluster snapshot "(date -Is) > $OUT - echo -e "\n## kubectl version" >> $OUT + printf "# Cluster snapshot %s\n" (date -Is) > $OUT + + printf "\n## kubectl version\n" >> $OUT kubectl version --short >> $OUT 2>&1 - echo -e "\n## Nodes" >> $OUT + printf "\n## Nodes\n" >> $OUT kubectl get nodes -o wide >> $OUT - echo -e "\n### Node resources" >> $OUT + + printf "\n### Node resources\n" >> $OUT kubectl describe nodes | egrep -i 'Name:|Roles:|Capacity:|Allocatable:|cpu|memory|ephemeral' >> $OUT - echo -e "\n## Namespaces" >> $OUT + printf "\n## Namespaces\n" >> $OUT kubectl get ns >> $OUT - echo -e "\n## StorageClasses (summary)" >> $OUT + printf "\n## StorageClasses (summary)\n" >> $OUT kubectl get storageclass >> $OUT - echo -e "\n## StorageClasses (yaml)" >> $OUT + printf "\n## StorageClasses (yaml)\n" >> $OUT kubectl get storageclass -o yaml >> $OUT - echo -e "\n## PV/PVC" >> $OUT - kubectl get pv >> $OUT 2>&1 - kubectl get pvc -A >> $OUT 2>&1 + printf "\n## PV/PVC\n" >> $OUT + kubectl get pv >> $OUT 2>/dev/null + kubectl get pvc -A >> $OUT 2>/dev/null - echo -e "\n## Ingress / Services" >> $OUT - kubectl get ingressclass >> $OUT 2>&1 - kubectl get ingress -A >> $OUT 2>&1 + printf "\n## Ingress / Services\n" >> $OUT + kubectl get ingressclass >> $OUT 2>/dev/null + kubectl get ingress -A >> $OUT 2>/dev/null kubectl get svc -A | egrep -i 'NAMESPACE|LoadBalancer|EXTERNAL-IP|traefik|ingress' >> $OUT - echo -e "\n## kube-system pods (CNI hint)" >> $OUT - kubectl -n kube-system get pods -o wide >> $OUT 2>&1 + printf "\n## kube-system pods (CNI hint)\n" >> $OUT + kubectl -n kube-system get pods -o wide >> $OUT 2>/dev/null - echo -e "\n## Default limits/quotas" >> $OUT - kubectl -n default get limitrange,resourcequota >> $OUT 2>&1 + printf "\n## Default limits/quotas\n" >> $OUT + kubectl -n default get limitrange,resourcequota >> $OUT 2>/dev/null - echo -e "\n## Tools present on your machine" >> $OUT + printf "\n## Tools present on your machine\n" >> $OUT for t in kubectl helm jq curl awk envsubst fish if type -q $t - echo "$t: OK" >> $OUT + printf "%s: OK\n" $t >> $OUT else - echo "$t: MISSING" >> $OUT + printf "%s: MISSING\n" $t >> $OUT end end @@ -1030,8 +1268,8 @@ end # --- one-line command reference (keep in sync) --- function walletsvc_help - echo "walletsvc_bootstrap # interactive deploy + secrets + PVC + first wallet (multiwallet RPC)" - echo "walletsvc_status [ns] [app] # show deploy, pods, service, netpol" + echo "walletsvc_bootstrap # interactive deploy + secrets + PVC + first wallet (multiwallet RPC), idempotent" + echo "walletsvc_status [ns] [app] # show deploy, pods, service, netpol (default ns=crypto)" echo "walletsvc_portforward [ns] [svc] # port-forward RPC to localhost:18083 (CTRL+C to stop)" echo "walletsvc_logs [ns] [app] [N] # tail N (default 200) lines of RPC pod logs" echo "walletsvc_wallet_ls [ns] [app] # list files under /data in the RPC pod (check wallet files)" @@ -1044,67 +1282,19 @@ function walletsvc_help echo "walletsvc_list_addresses [ns] [svc] [acct=0] # list all subaddresses for account" echo "walletsvc_new_address [ns] [svc] [label] [acct=0] # create labeled subaddress" echo "walletsvc_set_rpc_credentials [ns] [app] # rotate RPC basic auth (secret + rollout)" - echo "walletsvc_change_wallet_password [ns] [svc] # change password of a wallet file via RPC" + echo "walletsvc_change_wallet_password [ns] [svc] # change wallet file password via RPC" echo "walletsvc_stop [ns] [app] [svc] # delete deployment+service only" echo "walletsvc_purge [ns] [app] [pvc] [svc] # delete deploy+svc+PVC+secrets (danger!)" end function walletsvc_help_detailed - echo "Commands (defaults: ns=monero, svc/app=wallet-rpc)" + echo "Commands (defaults: ns=crypto, svc/app=wallet-rpc)" echo echo "walletsvc_bootstrap" - echo " Interactive deploy: creates namespace, secrets, PVC, Deployment (fsGroup + init perms), Service, NetPolicy." - echo " Prompts you for a *Monero* daemon (monerod) with NO default; probes it via get_info to ensure mainnet Monero." - echo " Set WALLETSVC_SKIP_DAEMON_CHECK=1 to bypass the probe (not recommended)." - echo - echo "walletsvc_status [ns] [app]" - echo " Show Deployment/Pod/Service/NetPolicy for the app label." - echo - echo "walletsvc_portforward [ns] [svc]" - echo " Port-forward RPC to http://127.0.0.1:18083 (Ctrl+C to stop)." - echo - echo "walletsvc_logs [ns] [app] [N]" - echo " Tail N lines (default 200) from the wallet RPC pod logs." - echo - echo "walletsvc_wallet_ls [ns] [app]" - echo " List /data contents inside the pod (quick sanity for wallet files)." - echo - echo "walletsvc_check_write [ns] [app]" - echo " Touch/remove a temp file under /data to prove the PVC is writable." - echo - echo "walletsvc_rpc_test [ns] [svc]" - echo " JSON-RPC get_version (sanity)." - echo - echo "walletsvc_rpc_call [ns] [svc] [params_json]" - echo " Raw JSON-RPC with digest auth; prints body; non-200 returns non-zero." - echo - echo "walletsvc_create_wallet [ns] [svc] [pass] [language]" - echo " Create /data/{,.keys}. Pass defaults to Secret -wallet-pass. Language defaults to English." - echo - echo "walletsvc_open [ns] [svc] [pass]" - echo " Close any open wallet, verify files exist, then open with the given (or Secret) password." - echo - echo "walletsvc_ensure_open [ns] [svc] [file]" - echo " If files missing, create with Secret pass, then open." - echo - echo "walletsvc_overview [ns] [svc] [acct=0]" - echo " Pretty view: version, height, accounts, balances, addresses, recent transfers." - echo - echo "walletsvc_list_addresses [ns] [svc] [acct=0]" - echo " List all subaddresses for the account." - echo - echo "walletsvc_new_address [ns] [svc] [label] [acct=0]" - echo " Create a new subaddress with optional label (default: timestamped)." - echo - echo "walletsvc_set_rpc_credentials [ns] [app]" - echo " Rotate RPC basic auth (updates Secret and restarts Deployment)." - echo - echo "walletsvc_change_wallet_password [ns] [svc]" - echo " Change wallet file password via RPC." - echo - echo "Notes:" - echo " - Your wallet must talk to a Monero daemon (monerod). p2pool is a mining pool, not a daemon." - echo " - For the most trust-minimized setup, run your own monerod and point the wallet at it." - echo " - This script blocks suspicious daemon addresses (e.g. containing 'qubic') and probes that the daemon" - echo " speaks Monero mainnet before proceeding." + echo " Interactive deploy: creates/updates secrets, PVC, Deployment (fsGroup+init perms), Service, NetPolicy." + echo " Idempotent: if deploy/svc/netpol already exist, you can clean them up (PVC+secrets kept) and continue." + echo " Uses your in-cluster monerod by default (monerod:18081 in ns=crypto)." + echo " Probes it via a temporary port-forward so it works from your workstation." + echo " Set WALLETSVC_SKIP_DAEMON_CHECK=1 to bypass the daemon probe (not recommended)." end + diff --git a/services/crypto/monerod/deployment.yaml b/services/crypto/monerod/deployment.yaml index ce2904a..118272a 100644 --- a/services/crypto/monerod/deployment.yaml +++ b/services/crypto/monerod/deployment.yaml @@ -57,9 +57,17 @@ spec: - --confirm-external-bind - --p2p-bind-ip=0.0.0.0 - --p2p-bind-port=18080 + - --zmq-pub=tcp://0.0.0.0:18083 + - --out-peers=32 + - --in-peers=64 + - --add-priority-node=p2pmd.xmrvsbeast.com:18080 + - --add-priority-node=nodes.hashvault.pro:18080 + - --disable-dns-checkpoints + - --enable-dns-blocklist ports: - { name: rpc, containerPort: 18081 } - { name: p2p, containerPort: 18080 } + - { name: p2pool, containerPort: 18083 } # securityContext: # allowPrivilegeEscalation: false # readOnlyRootFilesystem: true diff --git a/services/crypto/xmr-miner/configmap-sources.yaml b/services/crypto/xmr-miner/configmap-sources.yaml new file mode 100644 index 0000000..adbdae6 --- /dev/null +++ b/services/crypto/xmr-miner/configmap-sources.yaml @@ -0,0 +1,21 @@ +# services/crypto/xmr-miner/configmap-sources.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: xmr-miner-sources + namespace: crypto +data: + # REQUIRED: set to the official p2pool ARM64 tarball URL + P2POOL_URL: "https://EXAMPLE/p2pool-linux-arm64.tar.gz" + # OPTIONAL: p2pool SHA256 (exact 64-hex chars). Leave blank to skip verification. + P2POOL_SHA256: "" + + # REQUIRED: set to the official xmrig ARM64 tarball URL (static build preferred) + XMRIG_URL: "https://EXAMPLE/xmrig-linux-static-arm64.tar.gz" + # OPTIONAL: xmrig SHA256. Leave blank to skip verification. + XMRIG_SHA256: "" + + # Threads for xmrig (default 1 to avoid RAM spikes; override after testing) + XMRIG_THREADS: "1" + # Extra args for xmrig if you want (space-separated) + XMRIG_EXTRA_ARGS: "" diff --git a/services/crypto/xmr-miner/deployment.yaml b/services/crypto/xmr-miner/deployment.yaml new file mode 100644 index 0000000..aa903db --- /dev/null +++ b/services/crypto/xmr-miner/deployment.yaml @@ -0,0 +1,84 @@ +# services/crypto/xmr-miner/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: p2pool + namespace: crypto + labels: { app: p2pool } +spec: + replicas: 1 + selector: + matchLabels: { app: p2pool } + template: + metadata: + labels: { app: p2pool } + spec: + nodeSelector: + kubernetes.io/arch: arm64 + priorityClassName: scavenger + volumes: + - name: tools + emptyDir: {} + - name: payout + secret: + secretName: xmr-payout + - name: sources + configMap: + name: xmr-miner-sources + initContainers: + - name: fetch-tools + image: debian:bookworm-slim + command: ["/bin/sh","-lc"] + args: + - | + set -eux + apt-get update + apt-get install -y --no-install-recommends ca-certificates curl xz-utils tar coreutils + update-ca-certificates + + P2POOL_URL="$(cat /cfg/P2POOL_URL)"; : "${P2POOL_URL:?P2POOL_URL required}" + XMRIG_URL="$(cat /cfg/XMRIG_URL)"; : "${XMRIG_URL:?XMRIG_URL required}" + P2POOL_SHA="$(cat /cfg/P2POOL_SHA256)"; true + XMRIG_SHA="$(cat /cfg/XMRIG_SHA256)"; true + + mkdir -p /opt/bin + # --- p2pool --- + curl -fL "$P2POOL_URL" -o /tmp/p2pool.tgz + if [ -n "${P2POOL_SHA:-}" ]; then echo "${P2POOL_SHA} /tmp/p2pool.tgz" | sha256sum -c -; fi + tar -x -C /tmp -f /tmp/p2pool.tgz || true + # pick the p2pool binary from whatever layout the tarball uses + P2=$(find /tmp -maxdepth 3 -type f -name 'p2pool*' -perm -u+x | head -n1) + test -n "$P2" && cp "$P2" /opt/bin/p2pool && chmod 0755 /opt/bin/p2pool + + # --- xmrig (for quick local self-checks; DaemonSet also downloads its own) --- + curl -fL "$XMRIG_URL" -o /tmp/xmrig.tgz + if [ -n "${XMRIG_SHA:-}" ]; then echo "${XMRIG_SHA} /tmp/xmrig.tgz" | sha256sum -c -; fi + tar -x -C /tmp -f /tmp/xmrig.tgz || true + XR=$(find /tmp -maxdepth 3 -type f -name 'xmrig*' -perm -u+x | head -n1) + test -n "$XR" && cp "$XR" /opt/bin/xmrig && chmod 0755 /opt/bin/xmrig + + ls -l /opt/bin + volumeMounts: + - { name: tools, mountPath: /opt/bin } + - { name: sources, mountPath: /cfg, readOnly: true } + containers: + - name: p2pool + image: debian:bookworm-slim + command: ["/bin/sh","-lc"] + args: + - | + set -eu + ADDR="$(cat /run/xmr/address)" + # be nice to the node; p2pool will connect to your in-cluster monerod + exec nice -n 19 ionice -c3 /opt/bin/p2pool \ + --wallet "$ADDR" \ + --host monerod.crypto.svc.cluster.local \ + --rpc-port 18081 \ + --zmq-port 18083 \ + --stratum 0.0.0.0:3333 + ports: + - { containerPort: 3333, name: stratum, protocol: TCP } + volumeMounts: + - { name: tools, mountPath: /opt/bin } + - { name: payout, mountPath: /run/xmr, readOnly: true } + # BestEffort QoS: no requests/limits → yields CPU when others need it diff --git a/services/crypto/xmr-miner/kustomization.yaml b/services/crypto/xmr-miner/kustomization.yaml new file mode 100644 index 0000000..706a2fc --- /dev/null +++ b/services/crypto/xmr-miner/kustomization.yaml @@ -0,0 +1,10 @@ +# services/crypto/xmr-miner/kustomization/yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - secret-wallet.yaml + - configmap-sources.yaml + - deployment.yaml + - service.yaml + - xmrig-daemonset.yaml + - priority-class.yaml diff --git a/services/crypto/xmr-miner/priority-class.yaml b/services/crypto/xmr-miner/priority-class.yaml new file mode 100644 index 0000000..4682f19 --- /dev/null +++ b/services/crypto/xmr-miner/priority-class.yaml @@ -0,0 +1,8 @@ +# services/crypto/xmr-miner/priority-class.yaml +apiVersion: scheduling.k8s.io/v1 +kind: PriorityClass +metadata: + name: scavenger +value: -10 +globalDefault: false +description: "Preemptible background miners." \ No newline at end of file diff --git a/services/crypto/xmr-miner/secret-wallet.yaml b/services/crypto/xmr-miner/secret-wallet.yaml new file mode 100644 index 0000000..32857d0 --- /dev/null +++ b/services/crypto/xmr-miner/secret-wallet.yaml @@ -0,0 +1,9 @@ +# services/crypto/xmr-miner/secret-wallet.yaml +apiVersion: v1 +kind: Secret +metadata: + name: xmr-payout + namespace: crypto +type: Opaque +stringData: + address: "459owYDrJFESexUGTGgxoNZQ7VwWYrd8pfspdH15GStBeyr653N5qRHdqdVSaQ5UWCYQ42ueRNoNjPTeHR7LDisE67SrzDH" diff --git a/services/crypto/xmr-miner/service.yaml b/services/crypto/xmr-miner/service.yaml new file mode 100644 index 0000000..da4d265 --- /dev/null +++ b/services/crypto/xmr-miner/service.yaml @@ -0,0 +1,23 @@ +# services/crypto/xmr-miner/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: p2pool + namespace: crypto + labels: { app: p2pool } +spec: + type: ClusterIP + selector: { app: p2pool } + ports: + - name: stratum + port: 3333 + targetPort: 3333 + - name: rpc + port: 18081 + targetPort: 18081 + - name: p2p + port: 18080 + targetPort: 18080 + - name: zmq + port: 18083 + targetPort: 18083 diff --git a/services/crypto/xmr-miner/xmrig-daemonset.yaml b/services/crypto/xmr-miner/xmrig-daemonset.yaml new file mode 100644 index 0000000..b1fa82a --- /dev/null +++ b/services/crypto/xmr-miner/xmrig-daemonset.yaml @@ -0,0 +1,86 @@ +# services/crypto/xmr-miner/xmrig-daemonset.yaml +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: xmr-xmrig + namespace: crypto + labels: { app: xmr-xmrig } +spec: + selector: + matchLabels: { app: xmr-xmrig } + updateStrategy: + type: RollingUpdate + template: + metadata: + labels: { app: xmr-xmrig } + spec: + nodeSelector: + kubernetes.io/arch: arm64 + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/control-plane + operator: DoesNotExist + - key: node-role.kubernetes.io/master + operator: DoesNotExist + volumes: + - name: tools + emptyDir: {} + - name: payout + secret: + secretName: xmr-payout + - name: sources + configMap: + name: xmr-miner-sources + initContainers: + - name: fetch-xmrig + image: debian:bookworm-slim + command: ["/bin/sh","-lc"] + args: + - | + set -eux + apt-get update + apt-get install -y --no-install-recommends ca-certificates curl xz-utils tar coreutils + update-ca-certificates + + XMRIG_URL="$(cat /cfg/XMRIG_URL)"; : "${XMRIG_URL:?XMRIG_URL required}" + XMRIG_SHA="$(cat /cfg/XMRIG_SHA256)"; true + + mkdir -p /opt/bin + curl -fL "$XMRIG_URL" -o /tmp/xmrig.tgz + if [ -n "${XMRIG_SHA:-}" ]; then echo "${XMRIG_SHA} /tmp/xmrig.tgz" | sha256sum -c -; fi + tar -x -C /tmp -f /tmp/xmrig.tgz || true + XR=$(find /tmp -maxdepth 3 -type f -name 'xmrig*' -perm -u+x | head -n1) + test -n "$XR" && cp "$XR" /opt/bin/xmrig && chmod 0755 /opt/bin/xmrig + ls -l /opt/bin + volumeMounts: + - { name: tools, mountPath: /opt/bin } + - { name: sources, mountPath: /cfg, readOnly: true } + containers: + - name: xmrig + image: debian:bookworm-slim + env: + - { name: XMRIG_THREADS, valueFrom: { configMapKeyRef: { name: xmr-miner-sources, key: XMRIG_THREADS } } } + - { name: XMRIG_EXTRA_ARGS,valueFrom: { configMapKeyRef: { name: xmr-miner-sources, key: XMRIG_EXTRA_ARGS } } } + command: ["/bin/sh","-lc"] + args: + - | + set -eu + ADDR="$(cat /run/xmr/address)" + THR="${XMRIG_THREADS:-1}" + EXTRA="${XMRIG_EXTRA_ARGS:-}" + # Lowest CPU/IO priority; connect to cluster p2pool + exec nice -n 19 ionice -c3 /opt/bin/xmrig \ + -o p2pool.crypto.svc.cluster.local:3333 \ + -u x+10000 \ + -a rx \ + -k \ + --donate-level 0 \ + --cpu-priority 1 \ + --threads "${THR}" ${EXTRA} + volumeMounts: + - { name: tools, mountPath: /opt/bin } + - { name: payout, mountPath: /run/xmr, readOnly: true } + # BestEffort QoS: no requests/limits → yields CPU when others need it diff --git a/services/harbor/certificate.yaml b/services/harbor/certificate.yaml deleted file mode 100644 index 9276007..0000000 --- a/services/harbor/certificate.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: harbor-tls - namespace: harbor -spec: - secretName: harbor-tls - issuerRef: - kind: ClusterIssuer - name: letsencrypt-prod - dnsNames: - - registry.bstein.dev \ No newline at end of file diff --git a/services/harbor/helmrelease.yaml b/services/harbor/helmrelease.yaml deleted file mode 100644 index 46775e9..0000000 --- a/services/harbor/helmrelease.yaml +++ /dev/null @@ -1,69 +0,0 @@ -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: harbor - namespace: flux-system -spec: - interval: 15m - targetNamespace: harbor - install: - createNamespace: false - chart: - spec: - chart: harbor - version: 1.17.1 - sourceRef: - kind: HelmRepository - name: harbor - namespace: flux-system - values: - expose: - type: ingress - ingress: - className: traefik - hosts: - core: registry.bstein.dev - annotations: - cert-manager.io/cluster-issuer: letsencrypt-production - tls: - enabled: true - certSource: secret - secret: - secretName: harbor-tls - externalURL: https://registry.bstein.dev - notary: - enabled: false - harborAdminPassword: "ENCRYPT-ME-WITH-SOPS" - database: - type: external - external: - host: postgres-service.postgres.svc.cluster.local - port: 5432 - username: harbor - password: "ENCRYPT-ME-WITH-SOPS" - sslmode: disable - coreDatabase: harbor - redis: - type: internal - persistence: - persistentVolumeClaim: - registry: - storageClass: astreae - accessMode: ReadWriteOnce - size: 100Gi - jobservice: - storageClass: astreae - accessMode: ReadWriteOnce - size: 5Gi - redis: - storageClass: astreae - accessMode: ReadWriteOnce - size: 2Gi - trivy: - storageClass: astreae - accessMode: ReadWriteOnce - size: 5Gi - chartmuseum: - enabled: false - trivy: - enabled: true diff --git a/services/harbor/helmrepository.yaml b/services/harbor/helmrepository.yaml deleted file mode 100644 index 9231352..0000000 --- a/services/harbor/helmrepository.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: source.toolkit.fluxcd.io/v1 -kind: HelmRepository -metadata: - name: harbor - namespace: flux-system -spec: - interval: 1h - url: https://helm.goharbor.io \ No newline at end of file diff --git a/services/harbor/kustomization.yaml b/services/harbor/kustomization.yaml deleted file mode 100644 index 1d16bd8..0000000 --- a/services/harbor/kustomization.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - namespace.yaml - - certificate.yaml - - helmrepository.yaml - - helmrelease.yaml \ No newline at end of file diff --git a/services/harbor/namespace.yaml b/services/harbor/namespace.yaml deleted file mode 100644 index cbd1236..0000000 --- a/services/harbor/namespace.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: harbor \ No newline at end of file