2025-08-10 20:40:22 -05:00
### ------- helpers ---------------------------------------------------------
function _need --description "ensure a command exists"
for c in $argv
if not type -q $c
echo ( set_color red) "Error:" ( set_color normal) " missing required command: $c " > & 2
return 1
end
end
end
function _banner --description "pretty section header" -a MSG
set_color cyan
echo
echo " ==> $MSG "
set_color normal
end
# 32-char alphanumeric generator (fast, no shell-specials)
function _rand_alnum32
if type -q openssl
openssl rand -base64 48 2 > /dev/null | tr -dc 'A-Za-z0-9' | head -c 32
else
dd if = /dev/urandom bs = 48 count = 1 2 > /dev/null | base64 | tr -dc 'A-Za-z0-9' | head -c 32
end
end
# Convert any string to a k8s-safe name (RFC-1123 label-ish)
function _k8s_name --description "sanitize to RFC-1123 label" -a S
set s ( string lower -- $S )
set s ( string replace -ar -- '[^a-z0-9-]' '-' $s )
set s ( string replace -r -- '^-+' '' $s )
set s ( string replace -r -- '-+$' '' $s )
echo $s
end
# Simple non-empty check
function _require --description "require a non-empty value" -a WHAT VAL
if test -z " $VAL "
echo ( set_color red) "Error:" ( set_color normal) " $WHAT cannot be empty. Aborting. " > & 2
return 1
end
end
# List SCs in a stable way
function _sc_list --description "list storageclasses as menu"
kubectl get storageclass -o json | jq -r '
.items[ ]
| { name: .metadata.name,
provisioner : .provisioner,
reclaim : ( .reclaimPolicy // "Delete" ) ,
default : ( .metadata.annotations[ "storageclass.kubernetes.io/is-default-class" ] = = "true"
or .metadata.annotations[ "storageclass.beta.kubernetes.io/is-default-class" ] = = "true" ) }
| "\(.name)\t\(.provisioner)\t\(.reclaim)\t\((.default|tostring))"
' 2 > /dev/null
end
2025-08-13 01:00:20 -05:00
# 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
2025-08-10 20:40:22 -05:00
# Choose SC (menu -> stderr; selection -> stdout)
function _choose_sc --description "interactive StorageClass picker"
set lines ( _sc_list )
if test ( count $lines ) -eq 0
echo ( set_color red) "Error:" ( set_color normal) " No StorageClasses found." > & 2
return 1
end
echo "" > & 2
echo "==> Available StorageClasses" > & 2
echo " # | name | provisioner | reclaim | default" > & 2
echo "----+----------------------+------------------------+---------+--------" > & 2
set i 1
for l in $lines
set name ( echo $l | awk -F '\t' '{print $1}' )
set prov ( echo $l | awk -F '\t' '{print $2}' )
set rec ( echo $l | awk -F '\t' '{print $3}' )
set def ( echo $l | awk -F '\t' '{print $4}' )
printf " %2d | %-20s | %-22s | %-7s | %s\n" $i $name $prov $rec $def > & 2
set i ( math $i + 1 )
end
set prefer ( printf "%s\n" $lines | awk -F '\t' '$3=="Retain"{print $1}' | head -n1 )
if test -z " $prefer "
set prefer ( printf "%s\n" $lines | awk -F '\t' '$4=="true"{print $1}' | head -n1 )
end
if test -z " $prefer "
set prefer ( printf "%s\n" $lines | awk -F '\t' '{print $1}' | head -n1 )
end
read -P " Pick StorageClass by number or name (blank = $prefer ): " choice
if test -z " $choice "
echo " Using StorageClass: $prefer " > & 2
echo $prefer
return 0
end
if string match -qr '^[0-9]+$' -- $choice
set idx ( math $choice )
if test $idx -lt 1 -o $idx -gt ( count $lines )
echo ( set_color red) "Error:" ( set_color normal) " Choice out of range." > & 2
return 1
end
set sel ( printf "%s\n" $lines [ $idx ] | awk -F '\t' '{print $1}' )
echo " Using StorageClass: $sel " > & 2
echo $sel
return 0
end
set found ( printf "%s\n" $lines | awk -F '\t' -v want = " $choice " '$1==want{print $1}' )
if test -z " $found "
echo ( set_color red) "Error:" ( set_color normal) " No StorageClass named ' $choice '. " > & 2
return 1
end
echo " Using StorageClass: $found " > & 2
echo $found
end
# ---------- port-forward helpers (stderr logs) ----------
function _pf_start --description "start port-forward in bg: sets global PF_PID" -a NS SVC LOCAL REMOTE
set -g PF_PID ""
if test -z " $LOCAL " ; set LOCAL 18083 ; end
if test -z " $REMOTE " ; set REMOTE 18083 ; end
echo " Starting port-forward: svc/ $SVC $LOCAL : $REMOTE (ns= $NS )… " > & 2
kubectl -n $NS port-forward svc/$SVC $LOCAL :$REMOTE > /dev/null 2 > & 1 &
set -g PF_PID $last_pid
sleep 1
end
function _pf_stop --description "stop port-forward if running"
if test -n " $PF_PID "
echo " Stopping port-forward (pid $PF_PID )… " > & 2
kill $PF_PID 2 > /dev/null; or true
set -e PF_PID
end
end
# Minimal JSON-RPC caller for wallet RPC
function _rpc_call --description "call wallet JSON-RPC via localhost pf" -a RPCUSER RPCPASS METHOD PARAMS
set url "http://127.0.0.1:18083/json_rpc"
if test -z " $PARAMS "
set payload ( printf '{"jsonrpc":"2.0","id":"0","method":"%s"}' $METHOD )
else
set payload ( printf '{"jsonrpc":"2.0","id":"0","method":"%s","params":%s}' $METHOD " $PARAMS " )
end
curl -s --fail --digest -u " $RPCUSER : $RPCPASS " \
-H "Content-Type: application/json" \
-d " $payload " " $url "
end
# Probe a monerod and assert it looks like Monero mainnet (not test/stage), not "qubic".
# Usage: _probe_monerod host:port
function _probe_monerod -a ADDR
if test -z " $ADDR "
echo ( set_color red) "Error:" ( set_color normal) " _probe_monerod needs host:port" > & 2
return 1
end
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
2025-08-13 01:00:20 -05:00
# 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)
2025-08-10 20:40:22 -05:00
if test -n " $raw "
2025-08-13 01:00:20 -05:00
set mstatus ( echo $raw | jq -r '.status // "OK"' )
set testnet ( echo $raw | jq -r '.testnet // false' )
2025-08-10 20:40:22 -05:00
set stagenet ( echo $raw | jq -r '.stagenet // false' )
2025-08-13 01:00:20 -05:00
set height ( echo $raw | jq -r '.height // .target_height // 0' )
if test " $mstatus " = "OK"
2025-08-10 20:40:22 -05:00
set ok 1
end
end
2025-08-13 01:00:20 -05:00
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
2025-08-10 20:40:22 -05:00
end
2025-08-13 01:00:20 -05:00
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
2025-08-10 20:40:22 -05:00
return 1
end
2025-08-13 01:00:20 -05:00
_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
_pf_stop
echo ( set_color red) " RPC not ready after $SECS seconds. " ( set_color normal) > & 2
return 1
2025-08-10 20:40:22 -05:00
end
### ------- main workflow ---------------------------------------------------
2025-08-13 01:00:20 -05:00
function walletsvc_bootstrap --description "Interactive setup of monero-wallet-rpc with PVC+secrets; creates default wallet and prints recovery info"
2025-08-10 20:40:22 -05:00
_need kubectl jq curl awk; or return 1
_banner "Monero wallet RPC bootstrap"
# --- Namespace
2025-08-13 01:00:20 -05:00
read -P "Namespace [crypto]: " ns_raw
if test -z " $ns_raw " ; set ns_raw crypto; end
2025-08-10 20:40:22 -05:00
set ns ( _k8s_name $ns_raw )
_require "Namespace" $ns ; or return 1
echo " Ensuring namespace ' $ns ' exists… "
if kubectl get ns $ns > /dev/null 2 > & 1
echo " Namespace $ns already exists. "
else
kubectl create ns $ns ; or return 1
echo " Created namespace $ns . "
end
# --- StorageClass
kubectl get storageclass
echo "TIP: Prefer a class with reclaimPolicy=Retain so a PVC delete won't delete the PV."
set sc ( _choose_sc ) ; or return 1
2025-08-13 01:00:20 -05:00
# --- 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
2025-08-10 20:40:22 -05:00
2025-08-13 01:00:20 -05:00
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
2025-08-10 20:40:22 -05:00
end
# --- Node scheduling (worker-only by default)
read -P "Schedule only on worker nodes? (y/N) [y]: " pin_workers
if test -z " $pin_workers " ; set pin_workers y; end
set use_pin 0
if string match -qi 'y*' -- $pin_workers
set use_pin 1
read -P "Worker label key [node-role.kubernetes.io/worker]: " node_label_key
if test -z " $node_label_key " ; set node_label_key node-role.kubernetes.io/worker; end
read -P "Worker label value [true]: " node_label_val
if test -z " $node_label_val " ; set node_label_val true; end
end
2025-08-13 01:00:20 -05:00
# --- Daemon (auto-use local monerod)
2025-08-10 20:40:22 -05:00
echo
2025-08-13 01:00:20 -05:00
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 "
2025-08-14 00:34:13 -05:00
read -P "Override daemon address for this deployment? (host:port, blank to keep): " daemon_override
if test -n " $daemon_override "
set daemon_addr $daemon_override
end
2025-08-10 20:40:22 -05:00
if test -z " $WALLETSVC_SKIP_DAEMON_CHECK "
2025-08-13 01:00:20 -05:00
_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
2025-08-10 20:40:22 -05:00
else
echo "Skipping daemon probe due to WALLETSVC_SKIP_DAEMON_CHECK=1"
end
2025-08-14 00:34:13 -05:00
# Use your private image by default (in Zot)
read -P "Container image for wallet RPC [registry.bstein.dev/infra/monero-wallet-rpc:0.18.4.1]: " image
if test -z " $image " ; set image registry.bstein.dev/infra/monero-wallet-rpc:0 .18 .4 .1 ; end
2025-08-10 20:40:22 -05:00
_require "Container image" $image ; or return 1
2025-08-13 01:00:20 -05:00
# --- 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
2025-08-10 20:40:22 -05:00
_require "RPC username" $rpc_user ; or return 1
2025-08-13 01:00:20 -05:00
read -s -P "RPC password [leave blank to keep current or auto-generate if none]: " rpc_pass
2025-08-10 20:40:22 -05:00
echo
2025-08-13 01:00:20 -05:00
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
2025-08-10 20:40:22 -05:00
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
2025-08-13 01:00:20 -05:00
read -s -P "Wallet password [leave blank to keep current or auto-generate if none]: " wallet_pass
2025-08-10 20:40:22 -05:00
echo
2025-08-13 01:00:20 -05:00
if test -z " $wallet_pass "
if test -n " $existing_wpass "
set wallet_pass $existing_wpass
else
set wallet_pass ( _rand_alnum32 )
end
end
2025-08-10 20:40:22 -05:00
_banner "Summary"
echo " Namespace: $ns "
echo " StorageClass: $sc "
2025-08-13 01:00:20 -05:00
echo " Base name: $name "
echo " PVC: $pvc_name "
2025-08-10 20:40:22 -05:00
echo " App/Deployment: $app_name "
echo " Service: $svc_name "
echo " Daemon address: $daemon_addr "
echo " Image: $image "
echo " RPC user: $rpc_user "
echo " Wallet file: /data/ $wallet_file "
if test $use_pin -eq 1
echo " NodeSelector: $node_label_key = $node_label_val "
end
read -P "Proceed? [y/N]: " proceed
if not string match -qi 'y*' -- $proceed
echo ( set_color yellow) "Aborted by user at confirmation step." ( set_color normal)
return 1
end
_banner "Applying secrets"
kubectl -n $ns create secret generic $rpc_secret_name \
--from-literal = username = " $rpc_user " \
--from-literal = password = " $rpc_pass " \
--dry-run = client -o yaml | kubectl -n $ns apply -f -; or return 1
kubectl -n $ns create secret generic $wpass_secret_name \
--from-literal = password = " $wallet_pass " \
--dry-run = client -o yaml | kubectl -n $ns apply -f -; or return 1
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
2025-08-13 01:00:20 -05:00
echo " RPC creds: user=' $rpc_user ' pass=' $rpc_pass ' "
echo " Wallet pass: $wallet_pass "
2025-08-10 20:40:22 -05:00
_banner "Applying PVC"
2025-08-13 01:00:20 -05:00
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
2025-08-10 20:40:22 -05:00
_banner "Applying Deployment"
set dply ( mktemp -t { $app_name } -deploy.XXXX.yaml)
begin
echo "apiVersion: apps/v1"
echo "kind: Deployment"
echo "metadata:"
echo " name: $app_name "
echo " namespace: $ns "
echo " labels: { app: $app_name } "
echo "spec:"
echo " replicas: 1"
echo " strategy:"
echo " type: Recreate"
echo " selector:"
echo " matchLabels: { app: $app_name } "
echo " template:"
echo " metadata:"
echo " labels: { app: $app_name } "
echo " spec:"
if test $use_pin -eq 1
echo " nodeSelector:"
echo " $node_label_key : \" $node_label_val \" "
end
echo " securityContext:"
echo " fsGroup: 1000"
echo " fsGroupChangePolicy: OnRootMismatch"
echo " initContainers:"
echo " - name: volume-permissions"
echo " image: busybox:1.36"
echo " command: [\"/bin/sh\",\"-lc\",\"chown :1000 /data && chmod 0770 /data\"]"
echo " securityContext:"
echo " runAsUser: 0"
echo " volumeMounts:"
echo " - name: data"
echo " mountPath: /data"
echo " containers:"
echo " - name: wallet-rpc"
echo " image: $image "
echo " imagePullPolicy: IfNotPresent"
echo " command: [\"/bin/sh\",\"-lc\"]"
echo " args:"
echo " - |"
echo " RPCU=\$(cat /run/monero-secrets/rpc-user);"
echo " RPCP=\$(cat /run/monero-secrets/rpc-pass);"
echo " exec monero-wallet-rpc \\"
echo " --wallet-dir /data \\"
echo " --daemon-address $daemon_addr \\ "
echo " --rpc-bind-ip 0.0.0.0 --rpc-bind-port 18083 \\"
echo " --rpc-login \"\${RPCU}:\${RPCP}\" \\"
echo " --confirm-external-bind"
echo " ports:"
echo " - containerPort: 18083"
echo " volumeMounts:"
echo " - name: data"
echo " mountPath: /data"
echo " - name: rpc-auth"
echo " mountPath: /run/monero-secrets"
echo " readOnly: true"
echo " resources:"
echo " requests: { cpu: \"100m\", memory: \"128Mi\" }"
echo " limits: { cpu: \"1\", memory: \"512Mi\" }"
echo " volumes:"
echo " - name: data"
echo " persistentVolumeClaim: { claimName: $pvc_name } "
echo " - name: rpc-auth"
echo " secret:"
echo " secretName: $rpc_secret_name "
echo " items:"
echo " - key: username"
echo " path: rpc-user"
echo " - key: password"
echo " path: rpc-pass"
end > $dply
echo " --- Deployment manifest ( $dply ) --- "
head -n 70 $dply
echo "..."
kubectl apply -f $dply ; or return 1
_banner "Applying Service"
set svc ( mktemp -t { $app_name } -svc.XXXX.yaml)
begin
echo "apiVersion: v1"
echo "kind: Service"
echo "metadata:"
echo " name: $svc_name "
echo " namespace: $ns "
echo " labels: { app: $app_name } "
echo "spec:"
echo " type: ClusterIP"
echo " selector: { app: $app_name } "
echo " ports:"
echo " - name: rpc"
echo " port: 18083"
echo " targetPort: 18083"
end > $svc
echo " --- Service manifest ( $svc ) --- "
cat $svc
kubectl apply -f $svc ; or return 1
_banner "Applying NetworkPolicy (same-namespace-only)"
set np ( mktemp -t { $app_name } -netpol.XXXX.yaml)
begin
echo "apiVersion: networking.k8s.io/v1"
echo "kind: NetworkPolicy"
echo "metadata:"
printf " name: %s-ingress\n" $app_name
echo " namespace: $ns "
echo "spec:"
echo " podSelector: { matchLabels: { app: $app_name } } "
echo " policyTypes: [\"Ingress\"]"
echo " ingress:"
echo " - from:"
echo " - namespaceSelector:"
echo " matchLabels:"
echo " kubernetes.io/metadata.name: $ns "
echo " ports:"
echo " - port: 18083"
echo " protocol: TCP"
end > $np
echo " --- NetworkPolicy manifest ( $np ) --- "
cat $np
kubectl apply -f $np ; or return 1
_banner "Ensuring pod picks up latest secrets"
kubectl -n $ns rollout restart deploy/$app_name > /dev/null 2 > & 1 ; or true
_banner "Waiting for rollout"
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
2025-08-13 01:00:20 -05:00
echo ( set_color yellow) "Hint:" ( set_color normal) " if you see Multi-Attach for the PVC, delete any older pod."
2025-08-10 20:40:22 -05:00
return 1
end
2025-08-13 01:00:20 -05:00
_banner "Waiting for wallet RPC readiness"
walletsvc_wait_ready $ns $svc_name 60 ; or return 1
2025-08-10 20:40:22 -05:00
2025-08-13 01:00:20 -05:00
_banner "Creating/opening wallet via JSON-RPC"
2025-08-10 20:40:22 -05:00
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
2025-08-13 01:00:20 -05:00
# Always attempt create; if it exists, open it.
2025-08-10 20:40:22 -05:00
if not walletsvc_create_wallet $ns $svc_name $wallet_file $wallet_pass
2025-08-13 01:00:20 -05:00
echo ( set_color yellow) "create_wallet returned non-zero; attempting open anyway…" ( set_color normal)
2025-08-10 20:40:22 -05:00
end
if not walletsvc_open $ns $svc_name $wallet_file $wallet_pass
2025-08-13 01:00:20 -05:00
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)
2025-08-10 20:40:22 -05:00
return 1
end
2025-08-13 01:00:20 -05:00
# --- 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}' )
2025-08-10 20:40:22 -05:00
_banner "Done"
echo " Namespace: $ns "
2025-08-13 01:00:20 -05:00
echo " Base name: $name "
echo " Service (in-cluster): $svc_name :18083 "
2025-08-10 20:40:22 -05:00
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 "
2025-08-13 01:00:20 -05:00
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
2025-08-10 20:40:22 -05:00
echo
2025-08-13 01:00:20 -05:00
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 "
2025-08-10 20:40:22 -05:00
end
2025-08-13 01:00:20 -05:00
2025-08-10 20:40:22 -05:00
### ------- utilities -------------------------------------------------------
function walletsvc_portforward --description "port-forward RPC to localhost:18083" -a NS SVC
2025-08-13 01:00:20 -05:00
if test -z " $NS " ; set NS crypto; end
2025-08-10 20:40:22 -05:00
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
2025-08-13 01:00:20 -05:00
if test -z " $NS " ; set NS crypto; end
2025-08-10 20:40:22 -05:00
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
2025-08-13 01:00:20 -05:00
if test -z " $NS " ; set NS crypto; end
2025-08-10 20:40:22 -05:00
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
kubectl -n $NS delete svc $SVC --ignore-not-found
echo " Stopped $NS / $APP . PVC and secrets retained. "
end
function walletsvc_purge --description "IRREVERSIBLY delete PVC + secrets (keys on disk!)" -a NS APP PVC SVC
2025-08-13 01:00:20 -05:00
if test -z " $NS " ; set NS crypto; end
2025-08-10 20:40:22 -05:00
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
echo " Type to confirm: PURGE $NS $PVC "
read CONFIRM
if test " $CONFIRM " != " PURGE $NS $PVC "
echo "Aborted."
return 1
end
kubectl -n $NS delete deploy $APP --ignore-not-found
kubectl -n $NS delete svc $SVC --ignore-not-found
kubectl -n $NS delete pvc $PVC --ignore-not-found --wait = true
kubectl -n $NS delete secret $APP -wallet-pass --ignore-not-found
kubectl -n $NS delete secret $APP -rpc-auth --ignore-not-found
echo "Purged. Ensure you have the mnemonic backed up if you ever need to restore."
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
2025-08-13 01:00:20 -05:00
if test -z " $NS " ; set NS crypto; end
2025-08-10 20:40:22 -05:00
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 )
end
if test -z " $RPCPASS "
set RPCPASS ( kubectl -n $NS get secret { $SVC } -rpc-auth -o jsonpath = '{.data.password}' | base64 -d )
end
_pf_start $NS $SVC 18083 18083
set res ( _rpc_call $RPCUSER $RPCPASS query_key '{"key_type":"mnemonic"}' )
_pf_stop
set err ( echo $res | jq -r '.error.message // empty' )
if test -n " $err "
echo " query_key error: $err "
echo "Some images disable returning mnemonics via RPC. Consider backing up offline."
return 1
end
echo "Mnemonic seed:"
echo $res | jq -r '.result.key'
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
2025-08-13 01:00:20 -05:00
if test -z " $NS " ; set NS crypto; end
2025-08-10 20:40:22 -05:00
if test -z " $SVC " ; set SVC wallet-rpc; end
if test -z " $METHOD "
echo "Usage: walletsvc_rpc_call [ns] [svc] <method> [params_json]" > & 2
return 1
end
set RPCUSER ( kubectl -n $NS get secret { $SVC } -rpc-auth -o jsonpath = '{.data.username}' | base64 -d )
set RPCPASS ( kubectl -n $NS get secret { $SVC } -rpc-auth -o jsonpath = '{.data.password}' | base64 -d )
_pf_start $NS $SVC 18083 18083
if test -n " $PARAMS_JSON "
set payload ( printf '{"jsonrpc":"2.0","id":"0","method":"%s","params":%s}' $METHOD " $PARAMS_JSON " )
else
set payload ( printf '{"jsonrpc":"2.0","id":"0","method":"%s"}' $METHOD )
end
set tmp ( mktemp )
set code ( curl -s --digest -u " $RPCUSER : $RPCPASS " \
-H 'Content-Type: application/json' \
-o $tmp -w "%{http_code}" \
-d " $payload " "http://127.0.0.1:18083/json_rpc" )
set rc $status
_pf_stop
set body ( cat $tmp ) ; rm -f $tmp
if test $rc -ne 0
echo " RPC transport error ( $METHOD ): curl exit $rc " > & 2
echo $body
return $rc
end
if test " $code " != "200"
echo " RPC HTTP $code ( $METHOD ) " > & 2
echo $body
return 1
end
echo $body
return 0
end
function walletsvc_rpc_test --description "get_version using secrets" -a NS SVC
walletsvc_rpc_call $NS $SVC get_version | jq .
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
2025-08-13 01:00:20 -05:00
if test -z " $NS " ; set NS crypto; end
2025-08-10 20:40:22 -05:00
if test -z " $APP " ; set APP wallet-rpc; end
set SNAME { $APP } -rpc-auth
read -P "New RPC username [leave blank to keep current]: " NEWUSER
if test -z " $NEWUSER "
set NEWUSER ( kubectl -n $NS get secret $SNAME -o jsonpath = '{.data.username}' | base64 -d )
end
read -s -P "New RPC password [blank = auto-generate 32 alnum]: " NEWPASS
echo
if test -z " $NEWPASS " ; set NEWPASS ( _rand_alnum32 ) ; end
echo " Updating secret $NS / $SNAME and restarting $APP … "
kubectl -n $NS create secret generic $SNAME \
--from-literal = username = " $NEWUSER " \
--from-literal = password = " $NEWPASS " \
--dry-run = client -o yaml | kubectl -n $NS apply -f -; or return 1
kubectl -n $NS annotate secret $SNAME walletsvc.titan/update-ts = ( date -Is ) --overwrite > /dev/null 2 > & 1
kubectl -n $NS rollout restart deploy/$APP
kubectl -n $NS rollout status deploy/$APP --timeout = 180s; or return 1
echo "New RPC creds:"
echo " user: $NEWUSER "
echo " pass: $NEWPASS "
end
# Change wallet file password via RPC
function walletsvc_change_wallet_password --description "change password of a wallet file via RPC" -a NS SVC
2025-08-13 01:00:20 -05:00
if test -z " $NS " ; set NS crypto; end
2025-08-10 20:40:22 -05:00
if test -z " $SVC " ; set SVC wallet-rpc; end
read -P "Wallet filename (under /data): " WFILE
if test -z " $WFILE "
echo "Wallet filename required." > & 2
return 1
end
read -s -P "Current wallet password: " OLD
echo
read -s -P "New wallet password [blank = auto-generate 32 alnum]: " NEW
echo
if test -z " $NEW " ; set NEW ( _rand_alnum32 ) ; end
set res ( walletsvc_rpc_call $NS $SVC open_wallet ( printf '{"filename":"%s","password":"%s"}' $WFILE $OLD ) )
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
2025-08-13 01:00:20 -05:00
if test -z " $NS " ; set NS crypto; end
2025-08-10 20:40:22 -05:00
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
end
# --- list files in /data inside the pod ---
function walletsvc_wallet_ls -a NS APP
2025-08-13 01:00:20 -05:00
if test -z " $NS " ; set NS crypto; end
2025-08-10 20:40:22 -05:00
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'
end
# --- create a wallet (uses Secret password if PASS omitted) ---
function walletsvc_create_wallet -a NS SVC FILE PASS LANG
2025-08-13 01:00:20 -05:00
if test -z " $NS " ; set NS crypto; end
2025-08-10 20:40:22 -05:00
if test -z " $SVC " ; set SVC wallet-rpc; end
if test -z " $FILE "
echo "Usage: walletsvc_create_wallet [ns] [svc] <filename> [pass] [language]" > & 2
return 1
end
if test -z " $PASS "
set PASS ( kubectl -n $NS get secret { $SVC } -wallet-pass -o jsonpath = '{.data.password}' | base64 -d 2 > /dev/null)
end
if test -z " $LANG " ; set LANG English; end
set payload ( printf '{"filename":"%s","password":"%s","language":"%s"}' $FILE $PASS $LANG )
set res ( walletsvc_rpc_call $NS $SVC create_wallet $payload )
set rc $status
if test $rc -ne 0
echo ( set_color red) " create_wallet RPC transport failed (rc= $rc ). " ( set_color normal ) > & 2
return $rc
end
set err ( echo $res | jq -r '.error.message // empty' )
if test -n " $err "
if string match -q "*already exists*" -- $err
echo " Wallet $FILE already exists. "
return 0
end
echo " create_wallet error: $err " > & 2
return 1
end
echo " Created wallet: $FILE "
end
# --- improved opener: closes first, checks files, hints if password mismatch ---
function walletsvc_open -a NS SVC FILE PASS
2025-08-13 01:00:20 -05:00
if test -z " $NS " ; set NS crypto; end
2025-08-10 20:40:22 -05:00
if test -z " $SVC " ; set SVC wallet-rpc; end
if test -z " $FILE "
echo "Usage: walletsvc_open [ns] [svc] <filename> [password]" > & 2
return 1
end
if test -z " $PASS "
set PASS ( kubectl -n $NS get secret { $SVC } -wallet-pass -o jsonpath = '{.data.password}' | base64 -d 2 > /dev/null)
end
if test -z " $PASS "
read -s -P " Wallet password for $FILE : " PASS; echo
end
walletsvc_rpc_call $NS $SVC close_wallet > /dev/null 2 > & 1 ; or true
set res ( walletsvc_rpc_call $NS $SVC open_wallet ( printf '{"filename":"%s","password":"%s"}' $FILE $PASS ) )
set rc $status
if test $rc -ne 0
echo ( set_color red) " open_wallet RPC transport failed (rc= $rc ). " ( set_color normal ) > & 2
return $rc
end
set exists ( kubectl -n $NS exec deploy/$SVC -- sh -lc ( printf 'test -e /data/%s -o -e /data/%s.keys && echo yes || echo no' $FILE $FILE ) 2 > /dev/null)
if test " $exists " = "no"
echo " No wallet files at /data/ $FILE {,.keys}. If this should be a new wallet, run: " > & 2
echo " walletsvc_create_wallet $NS $SVC $FILE " > & 2
return 1
end
set tmp ( mktemp )
walletsvc_rpc_call $NS $SVC open_wallet ( printf '{"filename":"%s","password":"%s"}' $FILE $PASS ) > $tmp
set rc $status
set res ( cat $tmp ) ; rm -f $tmp
if test $rc -ne 0
echo " open_wallet transport/HTTP error (rc= $rc ) " > & 2
echo $res | jq . > & 2
return $rc
end
set err ( echo $res | jq -r '.error.message // empty' 2 > /dev/null)
if test -n " $err "
echo " open_wallet error: $err " > & 2
if string match -q "*password*" -- ( string lower -- $err )
echo " Hint: Secret { $SVC }-wallet-pass may not match the files on disk. Try supplying the pass explicitly. " > & 2
end
return 1
end
echo $res | jq .
end
function walletsvc_check_write -a NS APP
2025-08-13 01:00:20 -05:00
if test -z " $NS " ; set NS crypto; end
2025-08-10 20:40:22 -05:00
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
2025-08-13 01:00:20 -05:00
if test -z " $NS " ; set NS crypto; end
2025-08-10 20:40:22 -05:00
if test -z " $SVC " ; set SVC wallet-rpc; end
if test -z " $FILE " ; set FILE main; end
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
walletsvc_open $NS $SVC $FILE ; or return 1
end
2025-08-13 01:00:20 -05:00
# --- 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
2025-08-10 20:40:22 -05:00
# ---------- friendly overview ----------
function walletsvc_overview -a NS SVC ACCOUNT
2025-08-13 01:00:20 -05:00
if test -z " $NS " ; set NS crypto; end
2025-08-10 20:40:22 -05:00
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 )
set RPCPASS ( kubectl -n $NS get secret { $SVC } -rpc-auth -o jsonpath = '{.data.password}' | base64 -d )
_pf_start $NS $SVC 18083 18083
echo " == Wallet RPC overview (ns= $NS svc= $SVC acct= $ACCOUNT ) "
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)"'
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' )
2025-08-13 01:00:20 -05:00
echo "-- Height:"
2025-08-10 20:40:22 -05:00
if test -n " $err "
echo " ERROR: $err "
echo " (Tip: open your wallet: walletsvc_open $NS $SVC main) "
else
2025-08-13 01:00:20 -05:00
echo $resp | jq -r '" wallet_height=\(.result.height)"'
2025-08-10 20:40:22 -05:00
end
set resp ( curl -s --fail --digest -u " $RPCUSER : $RPCPASS " \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":"0","method":"get_accounts"}' \
http ://127 .0 .0 .1 :18083 /json_rpc)
set err ( echo $resp | jq -r '.error.message // empty' )
echo "-- Accounts:"
if test -n " $err "
echo " ERROR: $err "
else
echo $resp | jq -r '.result.subaddress_accounts[ ]
| " [\(.account_index)] \(.label) total=\(.balance/1000000000000) XMR unlocked=\(.unlocked_balance/1000000000000) XMR" '
end
set BODY ( printf '{"account_index":%s}' $ACCOUNT )
set resp ( curl -s --fail --digest -u " $RPCUSER : $RPCPASS " \
-H 'Content-Type: application/json' \
-d ( printf '{"jsonrpc":"2.0","id":"0","method":"get_balance","params":%s}' " $BODY " ) \
http ://127 .0 .0 .1 :18083 /json_rpc)
set err ( echo $resp | jq -r '.error.message // empty' )
echo "-- Account balance:"
if test -n " $err "
echo " ERROR: $err "
else
echo $resp | jq -r '" balance=\(.result.balance/1000000000000) XMR unlocked=\(.result.unlocked_balance/1000000000000) XMR"'
end
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 ) \
http ://127 .0 .0 .1 :18083 /json_rpc)
set err ( echo $resp | jq -r '.error.message // empty' )
echo "-- Primary address (account base):"
if test -n " $err "
echo " ERROR: $err "
else
echo $resp | jq -r '.result.address'
end
echo "-- First 5 subaddresses:"
if test -n " $err "
echo " (skipped due to error above)"
else
echo $resp | jq -r '.result.addresses[:5][] | " [\(.address_index)] \(.address) label=\(.label) used=\(.used)"'
end
set BODY ( printf '{"account_index":%s,"in":true}' $ACCOUNT )
set in ( curl -s --fail --digest -u " $RPCUSER : $RPCPASS " \
-H 'Content-Type: application/json' \
-d ( printf '{"jsonrpc":"2.0","id":"0","method":"get_transfers","params":%s}' " $BODY " ) \
http ://127 .0 .0 .1 :18083 /json_rpc)
echo "-- Recent transfers (incoming, last 5):"
echo $in | jq -r '.result.in // [] | (.[-5:] // [])[] | " + \(.amount/1000000000000) XMR conf=\(.confirmations) tx=\(.txid)"'
set BODY ( printf '{"account_index":%s,"out":true}' $ACCOUNT )
set out ( curl -s --fail --digest -u " $RPCUSER : $RPCPASS " \
-H 'Content-Type: application/json' \
-d ( printf '{"jsonrpc":"2.0","id":"0","method":"get_transfers","params":%s}' " $BODY " ) \
http ://127 .0 .0 .1 :18083 /json_rpc)
echo "-- Recent transfers (outgoing, last 5):"
echo $out | jq -r '.result.out // [] | (.[-5:] // [])[] | " - \(.amount/1000000000000) XMR conf=\(.confirmations) tx=\(.txid)"'
set BODY ( printf '{"account_index":%s,"pool":true}' $ACCOUNT )
set pool ( curl -s --fail --digest -u " $RPCUSER : $RPCPASS " \
-H 'Content-Type: application/json' \
-d ( printf '{"jsonrpc":"2.0","id":"0","method":"get_transfers","params":%s}' " $BODY " ) \
http ://127 .0 .0 .1 :18083 /json_rpc)
echo "-- Mempool (unconfirmed incoming):"
echo $pool | jq -r '.result.pool // [] | .[] | " ~ \(.amount/1000000000000) XMR tx=\(.txid)"'
_pf_stop
end
# ---------- list addresses ----------
function walletsvc_list_addresses -a NS SVC ACCOUNT
2025-08-13 01:00:20 -05:00
if test -z " $NS " ; set NS crypto; end
2025-08-10 20:40:22 -05:00
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 )
set RPCPASS ( kubectl -n $NS get secret { $SVC } -rpc-auth -o jsonpath = '{.data.password}' | base64 -d )
_pf_start $NS $SVC 18083 18083
set BODY ( printf '{"account_index":%s}' $ACCOUNT )
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":%s}' " $BODY " ) \
http ://127 .0 .0 .1 :18083 /json_rpc)
_pf_stop
set err ( echo $resp | jq -r '.error.message // empty' )
if test -n " $err "
echo " ERROR: $err " > & 2
return 1
end
echo $resp | jq -r '.result.addresses[] | "[\(.address_index)] \(.address) label=\(.label) used=\(.used)"'
end
# ---------- new subaddress ----------
function walletsvc_new_address -a NS SVC LABEL ACCOUNT
2025-08-13 01:00:20 -05:00
if test -z " $NS " ; set NS crypto; end
2025-08-10 20:40:22 -05:00
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
set RPCUSER ( kubectl -n $NS get secret { $SVC } -rpc-auth -o jsonpath = '{.data.username}' | base64 -d )
set RPCPASS ( kubectl -n $NS get secret { $SVC } -rpc-auth -o jsonpath = '{.data.password}' | base64 -d )
_pf_start $NS $SVC 18083 18083
set BODY ( printf '{"account_index":%s,"label":"%s"}' $ACCOUNT $LABEL )
set resp ( curl -s --fail --digest -u " $RPCUSER : $RPCPASS " \
-H 'Content-Type: application/json' \
-d ( printf '{"jsonrpc":"2.0","id":"0","method":"create_address","params":%s}' " $BODY " ) \
http ://127 .0 .0 .1 :18083 /json_rpc)
_pf_stop
set err ( echo $resp | jq -r '.error.message // empty' )
if test -n " $err "
echo " ERROR: $err " > & 2
return 1
end
echo $resp | jq -r --arg L " $LABEL " '"New address: \(.result.address) [index=\(.result.address_index)] label=" + $L'
end
2025-08-13 01:00:20 -05:00
# --- send a specific amount XMR to a single address (optionally payment_id)
# Usage: walletsvc_send [ns] [svc] <to_address> <amount_xmr> [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] <to_address> <amount_xmr> [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] <to_address>
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] <to_address>" > & 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
2025-08-10 20:40:22 -05:00
# Optional: non-sensitive cluster snapshot to help tune manifests.
function cluster_probe
set OUT cluster-snapshot.txt
2025-08-13 01:00:20 -05:00
printf "# Cluster snapshot %s\n" ( date -Is ) > $OUT
printf "\n## kubectl version\n" > > $OUT
2025-08-10 20:40:22 -05:00
kubectl version --short > > $OUT 2 > & 1
2025-08-13 01:00:20 -05:00
printf "\n## Nodes\n" > > $OUT
2025-08-10 20:40:22 -05:00
kubectl get nodes -o wide > > $OUT
2025-08-13 01:00:20 -05:00
printf "\n### Node resources\n" > > $OUT
2025-08-10 20:40:22 -05:00
kubectl describe nodes | egrep -i 'Name:|Roles:|Capacity:|Allocatable:|cpu|memory|ephemeral' > > $OUT
2025-08-13 01:00:20 -05:00
printf "\n## Namespaces\n" > > $OUT
2025-08-10 20:40:22 -05:00
kubectl get ns > > $OUT
2025-08-13 01:00:20 -05:00
printf "\n## StorageClasses (summary)\n" > > $OUT
2025-08-10 20:40:22 -05:00
kubectl get storageclass > > $OUT
2025-08-13 01:00:20 -05:00
printf "\n## StorageClasses (yaml)\n" > > $OUT
2025-08-10 20:40:22 -05:00
kubectl get storageclass -o yaml > > $OUT
2025-08-13 01:00:20 -05:00
printf "\n## PV/PVC\n" > > $OUT
kubectl get pv > > $OUT 2 > /dev/null
kubectl get pvc -A > > $OUT 2 > /dev/null
2025-08-10 20:40:22 -05:00
2025-08-13 01:00:20 -05:00
printf "\n## Ingress / Services\n" > > $OUT
kubectl get ingressclass > > $OUT 2 > /dev/null
kubectl get ingress -A > > $OUT 2 > /dev/null
2025-08-10 20:40:22 -05:00
kubectl get svc -A | egrep -i 'NAMESPACE|LoadBalancer|EXTERNAL-IP|traefik|ingress' > > $OUT
2025-08-13 01:00:20 -05:00
printf "\n## kube-system pods (CNI hint)\n" > > $OUT
kubectl -n kube-system get pods -o wide > > $OUT 2 > /dev/null
2025-08-10 20:40:22 -05:00
2025-08-13 01:00:20 -05:00
printf "\n## Default limits/quotas\n" > > $OUT
kubectl -n default get limitrange,resourcequota > > $OUT 2 > /dev/null
2025-08-10 20:40:22 -05:00
2025-08-13 01:00:20 -05:00
printf "\n## Tools present on your machine\n" > > $OUT
2025-08-10 20:40:22 -05:00
for t in kubectl helm jq curl awk envsubst fish
if type -q $t
2025-08-13 01:00:20 -05:00
printf "%s: OK\n" $t > > $OUT
2025-08-10 20:40:22 -05:00
else
2025-08-13 01:00:20 -05:00
printf "%s: MISSING\n" $t > > $OUT
2025-08-10 20:40:22 -05:00
end
end
echo " Wrote $OUT — paste the contents here. "
end
# --- one-line command reference (keep in sync) ---
function walletsvc_help
2025-08-13 01:00:20 -05:00
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)"
2025-08-10 20:40:22 -05:00
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)"
echo "walletsvc_rpc_test [ns] [svc] # JSON-RPC get_version (sanity check)"
echo "walletsvc_rpc_call [ns] [svc] <method> [params_json] # raw JSON (stdout) with digest auth"
echo "walletsvc_open [ns] [svc] <file> [pass] # close_wallet (ignore) -> open_wallet"
echo "walletsvc_create_wallet [ns] [svc] <file> [pass] [lang]# create wallet (defaults: pass from Secret, lang=English)"
echo "walletsvc_ensure_open [ns] [svc] [file] # create if missing, then open (uses Secret pass)"
echo "walletsvc_overview [ns] [svc] [acct=0] # version, height, balances, a few addresses, recent transfers"
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)"
2025-08-13 01:00:20 -05:00
echo "walletsvc_change_wallet_password [ns] [svc] # change wallet file password via RPC"
2025-08-10 20:40:22 -05:00
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
2025-08-13 01:00:20 -05:00
echo "Commands (defaults: ns=crypto, svc/app=wallet-rpc)"
2025-08-10 20:40:22 -05:00
echo
echo "walletsvc_bootstrap"
2025-08-13 01:00:20 -05:00
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)."
2025-08-10 20:40:22 -05:00
end
2025-08-13 01:00:20 -05:00