added vault

This commit is contained in:
Brad Stein 2025-08-19 01:06:45 -05:00
parent 968532cc55
commit 016cbab0f9
11 changed files with 889 additions and 195 deletions

View File

@ -1,3 +1,4 @@
# dockerfiles/Dockerfile.monero-p2pool
# syntax=docker/dockerfile:1.6 # syntax=docker/dockerfile:1.6
ARG DEBIAN_IMAGE=debian:bookworm-slim ARG DEBIAN_IMAGE=debian:bookworm-slim
ARG P2POOL_VERSION=v4.9 ARG P2POOL_VERSION=v4.9

View File

@ -1,3 +1,4 @@
# dockerfiles/Dockerfile.monero-wallet-rpc
# syntax=docker/dockerfile:1.6 # syntax=docker/dockerfile:1.6
ARG DEBIAN_IMAGE=debian:bookworm-slim ARG DEBIAN_IMAGE=debian:bookworm-slim
ARG MONERO_VERSION=v0.18.4.1 ARG MONERO_VERSION=v0.18.4.1

View File

@ -1,3 +1,4 @@
# dockerfiles/Dockerfile.monerod
# syntax=docker/dockerfile:1.6 # syntax=docker/dockerfile:1.6
FROM debian:bookworm-slim FROM debian:bookworm-slim

View File

@ -0,0 +1,54 @@
# syntax=docker/dockerfile:1.6
FROM --platform=$TARGETPLATFORM ubuntu:24.04
ARG TARGETARCH
ARG SUI_REF=mainnet-v1.53.2
# minimal tools + jq + age (optional lock/unlock helpers)
RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates curl jq tar age && \
rm -rf /var/lib/apt/lists/*
# Fetch the right prebuilt tarball for this arch and install 'sui'
RUN set -eux; \
case "$TARGETARCH" in \
amd64) want='(x86_64|amd64)' ;; \
arm64) want='(aarch64|arm64)' ;; \
*) echo "unsupported arch: $TARGETARCH" >&2; exit 1 ;; \
esac; \
api="https://api.github.com/repos/MystenLabs/sui/releases/tags/${SUI_REF}"; \
url="$(curl -fsSL --http1.1 --retry 5 --retry-connrefused --retry-delay 2 "$api" \
| jq -r --arg want "$want" '.assets[] | select(.name|test("ubuntu.*" + $want)) | .browser_download_url' \
| head -n1)"; \
test -n "$url"; \
echo "Downloading: $url"; \
curl -fL --http1.1 --retry 5 --retry-connrefused --retry-delay 2 -o /tmp/sui.tgz "$url"; \
mkdir -p /opt/sui; \
tar -xzf /tmp/sui.tgz -C /opt/sui; \
# find and install the 'sui' binary from the extracted tree
f="$(find /opt/sui -type f -name sui -perm -u+x | head -n1)"; \
test -n "$f"; install -m 0755 "$f" /usr/local/bin/sui; \
# sanity check in the image
/usr/local/bin/sui --version
# runtime user and homedir setup
RUN set -eux; \
uid=1000; gid=1000; \
gname="$(getent group "$gid" | cut -d: -f1 || true)"; \
if [ -z "$gname" ]; then \
groupadd -g "$gid" sui; \
gname=sui; \
fi; \
if getent passwd "$uid" >/dev/null; then \
# UID 1000 already exists; create 'sui' with next available UID but keep primary group = gid 1000
useradd -m -g "$gid" sui; \
else \
useradd -m -u "$uid" -g "$gid" sui; \
fi; \
install -d -m 0770 -o sui -g "$gid" /home/sui/.sui/sui_config
USER sui
WORKDIR /home/sui
# keep the container ready to be exec'd into by your scripts
CMD ["/bin/sh","-lc","sleep infinity"]

View File

@ -0,0 +1,12 @@
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: vault
namespace: flux-system
spec:
interval: 10m
path: ./services/vault
prune: true
sourceRef:
kind: GitRepository
name: flux-system

View File

@ -212,12 +212,12 @@ function _probe_monerod -a ADDR
end end
echo (set_color red)"Could not speak Monero /get_info to $ADDR. Not a monerod (or blocked)."(set_color normal) >&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 echo "Tip: run your own monerod or set xmrwallet_SKIP_DAEMON_CHECK=1 to bypass." >&2
return 1 return 1
end end
# Wait for wallet RPC to accept connections (digest auth get_version) # Wait for wallet RPC to accept connections (digest auth get_version)
function walletsvc_wait_ready -a NS SVC SECS function xmrwallet_wait_ready -a NS SVC SECS
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$SVC"; set SVC wallet-rpc; end
if test -z "$SECS"; set SECS 60; end if test -z "$SECS"; set SECS 60; end
@ -249,7 +249,7 @@ end
### ------- main workflow --------------------------------------------------- ### ------- main workflow ---------------------------------------------------
function walletsvc_bootstrap --description "Interactive setup of monero-wallet-rpc with PVC+secrets; creates default wallet and prints recovery info" function xmrwallet_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 _need kubectl jq curl awk; or return 1
_banner "Monero wallet RPC bootstrap" _banner "Monero wallet RPC bootstrap"
@ -358,7 +358,7 @@ function walletsvc_bootstrap --description "Interactive setup of monero-wallet-r
set daemon_addr $daemon_override set daemon_addr $daemon_override
end end
if test -z "$WALLETSVC_SKIP_DAEMON_CHECK" if test -z "$xmrwallet_SKIP_DAEMON_CHECK"
_banner "Probing daemon via temporary port-forward" _banner "Probing daemon via temporary port-forward"
set PF_LOCAL 28081 set PF_LOCAL 28081
_pf_start crypto monerod $PF_LOCAL 18081 _pf_start crypto monerod $PF_LOCAL 18081
@ -368,7 +368,7 @@ function walletsvc_bootstrap --description "Interactive setup of monero-wallet-r
end end
_pf_stop _pf_stop
else else
echo "Skipping daemon probe due to WALLETSVC_SKIP_DAEMON_CHECK=1" echo "Skipping daemon probe due to xmrwallet_SKIP_DAEMON_CHECK=1"
end end
# Use your private image by default (in Zot) # Use your private image by default (in Zot)
@ -445,8 +445,8 @@ function walletsvc_bootstrap --description "Interactive setup of monero-wallet-r
--dry-run=client -o yaml | kubectl -n $ns apply -f -; or return 1 --dry-run=client -o yaml | kubectl -n $ns apply -f -; or return 1
set ts (date -Is) 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 $rpc_secret_name xmrwallet.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 kubectl -n $ns annotate secret $wpass_secret_name xmrwallet.titan/update-ts="$ts" --overwrite >/dev/null 2>&1
echo "RPC creds: user='$rpc_user' pass='$rpc_pass'" echo "RPC creds: user='$rpc_user' pass='$rpc_pass'"
echo "Wallet pass: $wallet_pass" echo "Wallet pass: $wallet_pass"
@ -609,27 +609,27 @@ function walletsvc_bootstrap --description "Interactive setup of monero-wallet-r
end end
_banner "Waiting for wallet RPC readiness" _banner "Waiting for wallet RPC readiness"
walletsvc_wait_ready $ns $svc_name 60; or return 1 xmrwallet_wait_ready $ns $svc_name 60; or return 1
_banner "Creating/opening wallet via JSON-RPC" _banner "Creating/opening wallet via JSON-RPC"
if not walletsvc_check_write $ns $app_name >/dev/null if not xmrwallet_check_write $ns $app_name >/dev/null
echo (set_color red)"PVC is not writable; aborting."(set_color normal) echo (set_color red)"PVC is not writable; aborting."(set_color normal)
return 1 return 1
end end
# Always attempt create; if it exists, open it. # Always attempt create; if it exists, open it.
if not walletsvc_create_wallet $ns $svc_name $wallet_file $wallet_pass if not xmrwallet_create_wallet $ns $svc_name $wallet_file $wallet_pass
echo (set_color yellow)"create_wallet returned non-zero; attempting open anyway…"(set_color normal) echo (set_color yellow)"create_wallet returned non-zero; attempting open anyway…"(set_color normal)
end end
if not walletsvc_open $ns $svc_name $wallet_file $wallet_pass if not xmrwallet_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) 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 xmrwallet_open with the correct password or xmrwallet_change_wallet_password."(set_color normal)
return 1 return 1
end end
# --- Gather summary data (address, seed if enabled) # --- Gather summary data (address, seed if enabled)
set PRIMARY (walletsvc_primary_address $ns $svc_name 2>/dev/null) set PRIMARY (xmrwallet_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 NEWADDR (xmrwallet_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 SEEDRAW (xmrwallet_show_seed $ns $svc_name 2>&1)
set SEED (echo $SEEDRAW | awk '/Mnemonic seed:/,0{if($0!~/Mnemonic seed:/)print}' ) set SEED (echo $SEEDRAW | awk '/Mnemonic seed:/,0{if($0!~/Mnemonic seed:/)print}' )
_banner "Done" _banner "Done"
@ -652,31 +652,31 @@ function walletsvc_bootstrap --description "Interactive setup of monero-wallet-r
echo $SEED echo $SEED
else else
echo "Mnemonic seed not retrievable via RPC (image may disable it)." 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" echo "If needed and supported by your image: xmrwallet_show_seed $ns $svc_name"
end end
echo echo
echo "Access locally: walletsvc_portforward $ns $svc_name # then http://127.0.0.1:18083/json_rpc" echo "Access locally: xmrwallet_portforward $ns $svc_name # then http://127.0.0.1:18083/json_rpc"
echo "Quick overview: walletsvc_overview $ns $svc_name" echo "Quick overview: xmrwallet_overview $ns $svc_name"
end end
### ------- utilities ------------------------------------------------------- ### ------- utilities -------------------------------------------------------
function walletsvc_portforward --description "port-forward RPC to localhost:18083" -a NS SVC function xmrwallet_portforward --description "port-forward RPC to localhost:18083" -a NS SVC
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$SVC"; set SVC wallet-rpc; end
echo "Forwarding $NS/$SVC → http://127.0.0.1:18083 (Ctrl+C to stop)…" echo "Forwarding $NS/$SVC → http://127.0.0.1:18083 (Ctrl+C to stop)…"
kubectl -n $NS port-forward svc/$SVC 18083:18083 kubectl -n $NS port-forward svc/$SVC 18083:18083
end end
function walletsvc_status --description "show pod/service status" -a NS APP function xmrwallet_status --description "show pod/service status" -a NS APP
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-rpc; end if test -z "$APP"; set APP wallet-rpc; end
kubectl -n $NS get deploy,po,svc,netpol -l app=$APP -o wide kubectl -n $NS get deploy,po,svc,netpol -l app=$APP -o wide
end end
function walletsvc_stop --description "stop the RPC but keep PVC + secrets" -a NS APP SVC function xmrwallet_stop --description "stop the RPC but keep PVC + secrets" -a NS APP SVC
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-rpc; end if test -z "$APP"; set APP wallet-rpc; end
if test -z "$SVC"; set SVC $APP; end if test -z "$SVC"; set SVC $APP; end
@ -685,7 +685,7 @@ function walletsvc_stop --description "stop the RPC but keep PVC + secrets" -a N
echo "Stopped $NS/$APP. PVC and secrets retained." echo "Stopped $NS/$APP. PVC and secrets retained."
end end
function walletsvc_purge --description "IRREVERSIBLY delete PVC + secrets (keys on disk!)" -a NS APP PVC SVC function xmrwallet_purge --description "IRREVERSIBLY delete PVC + secrets (keys on disk!)" -a NS APP PVC SVC
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-rpc; end if test -z "$APP"; set APP wallet-rpc; end
if test -z "$PVC"; set PVC wallet-data; end if test -z "$PVC"; set PVC wallet-data; end
@ -704,7 +704,7 @@ function walletsvc_purge --description "IRREVERSIBLY delete PVC + secrets (keys
echo "Purged. Ensure you have the mnemonic backed up if you ever need to restore." echo "Purged. Ensure you have the mnemonic backed up if you ever need to restore."
end end
function walletsvc_show_seed --description "Try to fetch mnemonic via RPC query_key (wallet must be open)" -a NS SVC RPCUSER RPCPASS function xmrwallet_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 _need jq curl; or return 1
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$SVC"; set SVC wallet-rpc; end
@ -728,11 +728,11 @@ function walletsvc_show_seed --description "Try to fetch mnemonic via RPC query_
end end
# ---------- core JSON-RPC wrapper (raw JSON to stdout) ---------- # ---------- 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 function xmrwallet_rpc_call --description "RPC call with secrets (auto port-forward)" -a NS SVC METHOD PARAMS_JSON
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$SVC"; set SVC wallet-rpc; end
if test -z "$METHOD" if test -z "$METHOD"
echo "Usage: walletsvc_rpc_call [ns] [svc] <method> [params_json]" >&2 echo "Usage: xmrwallet_rpc_call [ns] [svc] <method> [params_json]" >&2
return 1 return 1
end end
@ -772,12 +772,12 @@ function walletsvc_rpc_call --description "RPC call with secrets (auto port-forw
return 0 return 0
end end
function walletsvc_rpc_test --description "get_version using secrets" -a NS SVC function xmrwallet_rpc_test --description "get_version using secrets" -a NS SVC
walletsvc_rpc_call $NS $SVC get_version | jq . xmrwallet_rpc_call $NS $SVC get_version | jq .
end end
# Rotate RPC username/password (updates Secret & restarts Deploy) # Rotate RPC username/password (updates Secret & restarts Deploy)
function walletsvc_set_rpc_credentials --description "rotate RPC Basic creds" -a NS APP function xmrwallet_set_rpc_credentials --description "rotate RPC Basic creds" -a NS APP
_need kubectl; or return 1 _need kubectl; or return 1
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-rpc; end if test -z "$APP"; set APP wallet-rpc; end
@ -796,7 +796,7 @@ function walletsvc_set_rpc_credentials --description "rotate RPC Basic creds" -a
--from-literal=username="$NEWUSER" \ --from-literal=username="$NEWUSER" \
--from-literal=password="$NEWPASS" \ --from-literal=password="$NEWPASS" \
--dry-run=client -o yaml | kubectl -n $NS apply -f -; or return 1 --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 annotate secret $SNAME xmrwallet.titan/update-ts=(date -Is) --overwrite >/dev/null 2>&1
kubectl -n $NS rollout restart deploy/$APP kubectl -n $NS rollout restart deploy/$APP
kubectl -n $NS rollout status deploy/$APP --timeout=180s; or return 1 kubectl -n $NS rollout status deploy/$APP --timeout=180s; or return 1
@ -806,7 +806,7 @@ function walletsvc_set_rpc_credentials --description "rotate RPC Basic creds" -a
end end
# Change wallet file password via RPC # Change wallet file password via RPC
function walletsvc_change_wallet_password --description "change password of a wallet file via RPC" -a NS SVC function xmrwallet_change_wallet_password --description "change password of a wallet file via RPC" -a NS SVC
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$SVC"; set SVC wallet-rpc; end
@ -821,13 +821,13 @@ function walletsvc_change_wallet_password --description "change password of a wa
echo echo
if test -z "$NEW"; set NEW (_rand_alnum32); end 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)) set res (xmrwallet_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) xmrwallet_rpc_call $NS $SVC change_wallet_password (printf '{"old_password":"%s","new_password":"%s"}' $OLD $NEW)
echo "New wallet password: $NEW" echo "New wallet password: $NEW"
end end
# --- logs from the deployment (quick tail) --- # --- logs from the deployment (quick tail) ---
function walletsvc_logs -a NS APP LINES function xmrwallet_logs -a NS APP LINES
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-rpc; end if test -z "$APP"; set APP wallet-rpc; end
if test -z "$LINES"; set LINES 200; end if test -z "$LINES"; set LINES 200; end
@ -835,7 +835,7 @@ function walletsvc_logs -a NS APP LINES
end end
# --- list files in /data inside the pod --- # --- list files in /data inside the pod ---
function walletsvc_wallet_ls -a NS APP function xmrwallet_wallet_ls -a NS APP
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-rpc; end if test -z "$APP"; set APP wallet-rpc; end
echo "Listing /data in $NS/$APP" >&2 echo "Listing /data in $NS/$APP" >&2
@ -843,11 +843,11 @@ function walletsvc_wallet_ls -a NS APP
end end
# --- create a wallet (uses Secret password if PASS omitted) --- # --- create a wallet (uses Secret password if PASS omitted) ---
function walletsvc_create_wallet -a NS SVC FILE PASS LANG function xmrwallet_create_wallet -a NS SVC FILE PASS LANG
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$SVC"; set SVC wallet-rpc; end
if test -z "$FILE" if test -z "$FILE"
echo "Usage: walletsvc_create_wallet [ns] [svc] <filename> [pass] [language]" >&2 echo "Usage: xmrwallet_create_wallet [ns] [svc] <filename> [pass] [language]" >&2
return 1 return 1
end end
if test -z "$PASS" if test -z "$PASS"
@ -856,7 +856,7 @@ function walletsvc_create_wallet -a NS SVC FILE PASS LANG
if test -z "$LANG"; set LANG English; end if test -z "$LANG"; set LANG English; end
set payload (printf '{"filename":"%s","password":"%s","language":"%s"}' $FILE $PASS $LANG) set payload (printf '{"filename":"%s","password":"%s","language":"%s"}' $FILE $PASS $LANG)
set res (walletsvc_rpc_call $NS $SVC create_wallet $payload) set res (xmrwallet_rpc_call $NS $SVC create_wallet $payload)
set rc $status set rc $status
if test $rc -ne 0 if test $rc -ne 0
echo (set_color red)"create_wallet RPC transport failed (rc=$rc)."(set_color normal) >&2 echo (set_color red)"create_wallet RPC transport failed (rc=$rc)."(set_color normal) >&2
@ -877,11 +877,11 @@ function walletsvc_create_wallet -a NS SVC FILE PASS LANG
end end
# --- improved opener: closes first, checks files, hints if password mismatch --- # --- improved opener: closes first, checks files, hints if password mismatch ---
function walletsvc_open -a NS SVC FILE PASS function xmrwallet_open -a NS SVC FILE PASS
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$SVC"; set SVC wallet-rpc; end
if test -z "$FILE" if test -z "$FILE"
echo "Usage: walletsvc_open [ns] [svc] <filename> [password]" >&2 echo "Usage: xmrwallet_open [ns] [svc] <filename> [password]" >&2
return 1 return 1
end end
if test -z "$PASS" if test -z "$PASS"
@ -891,9 +891,9 @@ function walletsvc_open -a NS SVC FILE PASS
read -s -P "Wallet password for $FILE: " PASS; echo read -s -P "Wallet password for $FILE: " PASS; echo
end end
walletsvc_rpc_call $NS $SVC close_wallet >/dev/null 2>&1; or true xmrwallet_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 res (xmrwallet_rpc_call $NS $SVC open_wallet (printf '{"filename":"%s","password":"%s"}' $FILE $PASS))
set rc $status set rc $status
if test $rc -ne 0 if test $rc -ne 0
echo (set_color red)"open_wallet RPC transport failed (rc=$rc)."(set_color normal) >&2 echo (set_color red)"open_wallet RPC transport failed (rc=$rc)."(set_color normal) >&2
@ -903,12 +903,12 @@ function walletsvc_open -a NS SVC FILE PASS
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) 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" if test "$exists" = "no"
echo "No wallet files at /data/$FILE{,.keys}. If this should be a new wallet, run:" >&2 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 echo " xmrwallet_create_wallet $NS $SVC $FILE" >&2
return 1 return 1
end end
set tmp (mktemp) set tmp (mktemp)
walletsvc_rpc_call $NS $SVC open_wallet (printf '{"filename":"%s","password":"%s"}' $FILE $PASS) > $tmp xmrwallet_rpc_call $NS $SVC open_wallet (printf '{"filename":"%s","password":"%s"}' $FILE $PASS) > $tmp
set rc $status set rc $status
set res (cat $tmp); rm -f $tmp set res (cat $tmp); rm -f $tmp
@ -930,14 +930,14 @@ function walletsvc_open -a NS SVC FILE PASS
echo $res | jq . echo $res | jq .
end end
function walletsvc_check_write -a NS APP function xmrwallet_check_write -a NS APP
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-rpc; 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' 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 end
# --- ensure wallet is open (create if missing) --- # --- ensure wallet is open (create if missing) ---
function walletsvc_ensure_open -a NS SVC FILE function xmrwallet_ensure_open -a NS SVC FILE
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$SVC"; set SVC wallet-rpc; end
if test -z "$FILE"; set FILE main; end if test -z "$FILE"; set FILE main; end
@ -945,14 +945,14 @@ function walletsvc_ensure_open -a NS SVC FILE
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) 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" if test "$exists" = "no"
echo "No /data/$FILE found. Creating…" >&2 echo "No /data/$FILE found. Creating…" >&2
walletsvc_create_wallet $NS $SVC $FILE; or return 1 xmrwallet_create_wallet $NS $SVC $FILE; or return 1
end end
walletsvc_open $NS $SVC $FILE; or return 1 xmrwallet_open $NS $SVC $FILE; or return 1
end end
# --- quick primary address print (account 0) # --- quick primary address print (account 0)
function walletsvc_primary_address -a NS SVC function xmrwallet_primary_address -a NS SVC
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$SVC"; set SVC wallet-rpc; end
_pf_start $NS $SVC 18083 18083 _pf_start $NS $SVC 18083 18083
@ -966,11 +966,11 @@ function walletsvc_primary_address -a NS SVC
end end
# --- show unlocked balance quickly # --- show unlocked balance quickly
function walletsvc_unlocked -a NS SVC function xmrwallet_unlocked -a NS SVC
_need python3; or return 1 _need python3; or return 1
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$SVC"; set SVC wallet-rpc; end
set res (walletsvc_rpc_call $NS $SVC get_balance '{"account_index":0}') set res (xmrwallet_rpc_call $NS $SVC get_balance '{"account_index":0}')
set bal (echo $res | jq -r '.result.balance // 0') set bal (echo $res | jq -r '.result.balance // 0')
set unl (echo $res | jq -r '.result.unlocked_balance // 0') set unl (echo $res | jq -r '.result.unlocked_balance // 0')
@ -982,7 +982,7 @@ function walletsvc_unlocked -a NS SVC
end end
# --- wait until unlocked balance >= threshold (XMR), timeout seconds # --- wait until unlocked balance >= threshold (XMR), timeout seconds
function walletsvc_wait_unlocked -a NS SVC XMR_MIN TIMEOUT function xmrwallet_wait_unlocked -a NS SVC XMR_MIN TIMEOUT
_need python3; or return 1 _need python3; or return 1
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$SVC"; set SVC wallet-rpc; end
@ -990,7 +990,7 @@ function walletsvc_wait_unlocked -a NS SVC XMR_MIN TIMEOUT
if test -z "$TIMEOUT"; set TIMEOUT 300; end if test -z "$TIMEOUT"; set TIMEOUT 300; end
set start (date +%s) set start (date +%s)
while true while true
set res (walletsvc_rpc_call $NS $SVC get_balance '{"account_index":0}') set res (xmrwallet_rpc_call $NS $SVC get_balance '{"account_index":0}')
set unl (echo $res | jq -r '.result.unlocked_balance // 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 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) 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)
@ -1007,7 +1007,7 @@ function walletsvc_wait_unlocked -a NS SVC XMR_MIN TIMEOUT
end end
# ---------- friendly overview ---------- # ---------- friendly overview ----------
function walletsvc_overview -a NS SVC ACCOUNT function xmrwallet_overview -a NS SVC ACCOUNT
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$SVC"; set SVC wallet-rpc; end
if test -z "$ACCOUNT"; set ACCOUNT 0; end if test -z "$ACCOUNT"; set ACCOUNT 0; end
@ -1032,7 +1032,7 @@ function walletsvc_overview -a NS SVC ACCOUNT
echo "-- Height:" echo "-- Height:"
if test -n "$err" if test -n "$err"
echo " ERROR: $err" echo " ERROR: $err"
echo " (Tip: open your wallet: walletsvc_open $NS $SVC main)" echo " (Tip: open your wallet: xmrwallet_open $NS $SVC main)"
else else
echo $resp | jq -r '" wallet_height=\(.result.height)"' echo $resp | jq -r '" wallet_height=\(.result.height)"'
end end
@ -1110,7 +1110,7 @@ function walletsvc_overview -a NS SVC ACCOUNT
end end
# ---------- list addresses ---------- # ---------- list addresses ----------
function walletsvc_list_addresses -a NS SVC ACCOUNT function xmrwallet_list_addresses -a NS SVC ACCOUNT
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$SVC"; set SVC wallet-rpc; end
if test -z "$ACCOUNT"; set ACCOUNT 0; end if test -z "$ACCOUNT"; set ACCOUNT 0; end
@ -1132,7 +1132,7 @@ function walletsvc_list_addresses -a NS SVC ACCOUNT
end end
# ---------- new subaddress ---------- # ---------- new subaddress ----------
function walletsvc_new_address -a NS SVC LABEL ACCOUNT function xmrwallet_new_address -a NS SVC LABEL ACCOUNT
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$SVC"; set SVC wallet-rpc; end
if test -z "$ACCOUNT"; set ACCOUNT 0; end if test -z "$ACCOUNT"; set ACCOUNT 0; end
@ -1155,13 +1155,13 @@ function walletsvc_new_address -a NS SVC LABEL ACCOUNT
end end
# --- send a specific amount XMR to a single address (optionally payment_id) # --- send a specific amount XMR to a single address (optionally payment_id)
# Usage: walletsvc_send [ns] [svc] <to_address> <amount_xmr> [payment_id_hex] # Usage: xmrwallet_send [ns] [svc] <to_address> <amount_xmr> [payment_id_hex]
function walletsvc_send -a NS SVC TO AMT_XMR PID function xmrwallet_send -a NS SVC TO AMT_XMR PID
_need jq curl python3; or return 1 _need jq curl python3; or return 1
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$SVC"; set SVC wallet-rpc; end
if test -z "$TO" -o -z "$AMT_XMR" if test -z "$TO" -o -z "$AMT_XMR"
echo "Usage: walletsvc_send [ns] [svc] <to_address> <amount_xmr> [payment_id_hex]" >&2 echo "Usage: xmrwallet_send [ns] [svc] <to_address> <amount_xmr> [payment_id_hex]" >&2
return 1 return 1
end end
@ -1180,7 +1180,7 @@ function walletsvc_send -a NS SVC TO AMT_XMR PID
set body $body_base set body $body_base
end end
set res (walletsvc_rpc_call $NS $SVC transfer $body) set res (xmrwallet_rpc_call $NS $SVC transfer $body)
set err (echo $res | jq -r '.error.message // empty') set err (echo $res | jq -r '.error.message // empty')
if test -n "$err" if test -n "$err"
echo "ERROR: $err" >&2 echo "ERROR: $err" >&2
@ -1199,18 +1199,18 @@ function walletsvc_send -a NS SVC TO AMT_XMR PID
end end
# --- sweep all unlocked funds to an address (account 0) # --- sweep all unlocked funds to an address (account 0)
# Usage: walletsvc_sweep_all [ns] [svc] <to_address> # Usage: xmrwallet_sweep_all [ns] [svc] <to_address>
function walletsvc_sweep_all -a NS SVC TO function xmrwallet_sweep_all -a NS SVC TO
_need jq curl; or return 1 _need jq curl; or return 1
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$SVC"; set SVC wallet-rpc; end
if test -z "$TO" if test -z "$TO"
echo "Usage: walletsvc_sweep_all [ns] [svc] <to_address>" >&2 echo "Usage: xmrwallet_sweep_all [ns] [svc] <to_address>" >&2
return 1 return 1
end end
set body (printf '{"address":"%s","account_index":0,"priority":1,"do_not_relay":false,"get_tx_keys":true}' $TO) 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 res (xmrwallet_rpc_call $NS $SVC sweep_all $body)
set err (echo $res | jq -r '.error.message // empty') set err (echo $res | jq -r '.error.message // empty')
if test -n "$err" if test -n "$err"
echo "ERROR: $err" >&2 echo "ERROR: $err" >&2
@ -1221,7 +1221,81 @@ function walletsvc_sweep_all -a NS SVC TO
echo $res | jq -r '.result.tx_hash_list[]? | " tx: " + .' echo $res | jq -r '.result.tx_hash_list[]? | " tx: " + .'
end end
# Optional: non-sensitive cluster snapshot to help tune manifests. # Return total size (bytes) of /data/<file> + /data/<file>.keys inside the RPC pod
# Usage: xmrwallet_wallet_size_bytes [ns] [app] [file]
function xmrwallet_wallet_size_bytes -a NS APP FILE
if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-rpc; end
if test -z "$FILE"; set FILE main; end
set CMD (printf 'f="/data/%s"; k="/data/%s.keys"; s=0; [ -e "$f" ] && s=$(stat -c %%s "$f"); s2=0; [ -e "$k" ] && s2=$(stat -c %%s "$k"); echo $((s+s2))' $FILE $FILE)
set SUM (kubectl -n $NS exec deploy/$APP -- sh -lc "$CMD" 2>/dev/null)
if test -z "$SUM"; set SUM 0; end
echo $SUM
end
# Prometheus-friendly metric line (total bytes)
# Usage: xmrwallet_metrics [ns] [app] [file]
function xmrwallet_metrics -a NS APP FILE
if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-rpc; end
if test -z "$FILE"; set FILE main; end
set BYTES (xmrwallet_wallet_size_bytes $NS $APP $FILE)
printf "wallet_size_bytes{chain=\"monero\",namespace=\"%s\",app=\"%s\",wallet=\"%s\"} %s\n" $NS $APP $FILE $BYTES
end
# Restore a wallet file via wallet-RPC from a 25-word seed
# Usage: xmrwallet_restore [ns] [svc] [wallet_file] [restore_height] [language=English]
function xmrwallet_restore -a NS SVC FILE RH LANG
_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 "$FILE"; set FILE main; end
if test -z "$RH"; set RH 0; end
if test -z "$LANG"; set LANG English; end
# Get RPC creds and prompt for wallet password + seed (hidden)
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)
echo (set_color yellow)"New wallet password (input hidden):"(set_color normal)
read -s WPASS; echo
if test -z "$WPASS"
echo (set_color red)"Error:"(set_color normal)" empty wallet password."; return 1
end
echo (set_color yellow)"Paste your 25-word Monero seed (input hidden):"(set_color normal)
read -s SEED; echo
if test -z "$SEED"
echo (set_color red)"Error:"(set_color normal)" empty seed."; return 1
end
# Open a port-forward for the RPC, call restore_deterministic_wallet
function _pf_start_local
kubectl -n $NS port-forward svc/$SVC 18083:18083 >/dev/null 2>&1 &
set -g PF_PID $last_pid
sleep 1
end
function _pf_stop_local
if test -n "$PF_PID"; kill $PF_PID 2>/dev/null; set -e PF_PID; end
end
_pf_start_local
set body (printf '{"seed":"%s","password":"%s","filename":"%s","language":"%s","restore_height":%s}' $SEED $WPASS $FILE $LANG $RH)
set resp (curl -s --digest -u "$RPCUSER:$RPCPASS" -H 'Content-Type: application/json' \
-d (printf '{"jsonrpc":"2.0","id":"0","method":"restore_deterministic_wallet","params":%s}' "$body") \
http://127.0.0.1:18083/json_rpc)
_pf_stop_local
set err (echo $resp | jq -r '.error.message // empty')
if test -n "$err"
echo (set_color red)"restore_deterministic_wallet error:"(set_color normal) $err >&2
return 1
end
echo "Restored wallet /data/$FILE (from seed). Beginning blockchain sync…"
echo (set_color yellow)"Keep your seed offline and secure."(set_color normal)
end
function cluster_probe function cluster_probe
set OUT cluster-snapshot.txt set OUT cluster-snapshot.txt
printf "# Cluster snapshot %s\n" (date -Is) > $OUT printf "# Cluster snapshot %s\n" (date -Is) > $OUT
@ -1271,34 +1345,34 @@ function cluster_probe
end end
# --- one-line command reference (keep in sync) --- # --- one-line command reference (keep in sync) ---
function walletsvc_help function xmrwallet_help
echo "walletsvc_bootstrap # interactive deploy + secrets + PVC + first wallet (multiwallet RPC), idempotent" echo "xmrwallet_bootstrap # interactive deploy + secrets + PVC + first wallet (multiwallet RPC), idempotent"
echo "walletsvc_status [ns] [app] # show deploy, pods, service, netpol (default ns=crypto)" echo "xmrwallet_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 "xmrwallet_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 "xmrwallet_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 "xmrwallet_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 "xmrwallet_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 "xmrwallet_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 "xmrwallet_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 "xmrwallet_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 "xmrwallet_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 "xmrwallet_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 "xmrwallet_list_addresses [ns] [svc] [acct=0] # list all subaddresses for account"
echo "walletsvc_new_address [ns] [svc] [label] [acct=0] # create labeled subaddress" echo "xmrwallet_new_address [ns] [svc] [label] [acct=0] # create labeled subaddress"
echo "walletsvc_set_rpc_credentials [ns] [app] # rotate RPC basic auth (secret + rollout)" echo "xmrwallet_set_rpc_credentials [ns] [app] # rotate RPC basic auth (secret + rollout)"
echo "walletsvc_change_wallet_password [ns] [svc] # change wallet file password via RPC" echo "xmrwallet_change_wallet_password [ns] [svc] # change wallet file password via RPC"
echo "walletsvc_stop [ns] [app] [svc] # delete deployment+service only" echo "xmrwallet_stop [ns] [app] [svc] # delete deployment+service only"
echo "walletsvc_purge [ns] [app] [pvc] [svc] # delete deploy+svc+PVC+secrets (danger!)" echo "xmrwallet_purge [ns] [app] [pvc] [svc] # delete deploy+svc+PVC+secrets (danger!)"
end end
function walletsvc_help_detailed function xmrwallet_help_detailed
echo "Commands (defaults: ns=crypto, svc/app=wallet-rpc)" echo "Commands (defaults: ns=crypto, svc/app=wallet-rpc)"
echo echo
echo "walletsvc_bootstrap" echo "xmrwallet_bootstrap"
echo " Interactive deploy: creates/updates secrets, PVC, Deployment (fsGroup+init perms), Service, NetPolicy." 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 " 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 " 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 " 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)." echo " Set xmrwallet_SKIP_DAEMON_CHECK=1 to bypass the daemon probe (not recommended)."
end end

View File

@ -1,5 +1,5 @@
### --------- helpers ---------- ### --------- helpers ----------
function _need function _need --description "ensure a command exists"
for c in $argv for c in $argv
if not type -q $c if not type -q $c
echo (set_color red)"Error:"(set_color normal)" missing command: $c" >&2 echo (set_color red)"Error:"(set_color normal)" missing command: $c" >&2
@ -8,41 +8,61 @@ function _need
end end
end end
function _banner -a MSG function _banner --description "pretty section header" -a MSG
set_color cyan; echo; echo "==> $MSG"; set_color normal set_color cyan; echo; echo "==> $MSG"; set_color normal
end end
function _k8s_name -a S # 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
# Default image chooser (you should override with your own multi-arch image)
function _sui_default_image -a NET
echo registry.bstein.dev/infra/sui-tools:1.53.2
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 lower -- $S)
set s (string replace -ar -- '[^a-z0-9-]' '-' $s) set s (string replace -ar -- '[^a-z0-9-]' '-' $s)
set s (string replace -r -- '^-+|(-)+$' '$1' $s) set s (string replace -r -- '^-+' '' $s)
set s (string replace -r -- '-+$' '' $s)
echo $s echo $s
end end
function _require -a WHAT VAL # Simple non-empty check
function _require --description "require a non-empty value" -a WHAT VAL
if test -z "$VAL" if test -z "$VAL"
echo (set_color red)"Error:"(set_color normal)" $WHAT cannot be empty." >&2 echo (set_color red)"Error:"(set_color normal)" $WHAT cannot be empty. Aborting." >&2
return 1 return 1
end end
end end
function _sc_list function _sc_list --description "list storageclasses as menu"
kubectl get storageclass -o json 2>/dev/null | jq -r ' kubectl get storageclass -o json | jq -r '
.items[] | [ .items[]
.metadata.name, | {name: .metadata.name,
.provisioner, provisioner: .provisioner,
(.reclaimPolicy // "Delete"), reclaim: (.reclaimPolicy // "Delete"),
( .metadata.annotations["storageclass.kubernetes.io/is-default-class"] == "true" default: ( .metadata.annotations["storageclass.kubernetes.io/is-default-class"] == "true"
or .metadata.annotations["storageclass.beta.kubernetes.io/is-default-class"] == "true") or .metadata.annotations["storageclass.beta.kubernetes.io/is-default-class"] == "true") }
] | @tsv' | "\(.name)\t\(.provisioner)\t\(.reclaim)\t\((.default|tostring))"
' 2>/dev/null
end end
function _choose_sc # Choose StorageClass by index or name
function _choose_sc --description "interactive StorageClass picker"
set lines (_sc_list) set lines (_sc_list)
if test (count $lines) -eq 0 if test (count $lines) -eq 0
echo (set_color red)"Error:"(set_color normal)" No StorageClasses found." >&2 echo (set_color red)"Error:"(set_color normal)" No StorageClasses found." >&2
return 1 return 1
end end
echo "" >&2 echo "" >&2
echo "==> Available StorageClasses" >&2 echo "==> Available StorageClasses" >&2
echo " # | name | provisioner | reclaim | default" >&2 echo " # | name | provisioner | reclaim | default" >&2
@ -56,52 +76,156 @@ function _choose_sc
printf " %2d | %-20s | %-22s | %-7s | %s\n" $i $name $prov $rec $def >&2 printf " %2d | %-20s | %-22s | %-7s | %s\n" $i $name $prov $rec $def >&2
set i (math $i + 1) set i (math $i + 1)
end end
set prefer (printf "%s\n" $lines | awk -F'\t' '$4=="true"{print $1}' | head -n1) set prefer (printf "%s\n" $lines | awk -F'\t' '$4=="true"{print $1}' | head -n1)
if test -z "$prefer" if test -z "$prefer"
set prefer (printf "%s\n" $lines | awk -F'\t' '{print $1}' | head -n1) set prefer (printf "%s\n" $lines | awk -F'\t' '{print $1}' | head -n1)
end end
read -P "Pick StorageClass by number or name (blank = $prefer): " choice read -P "Pick StorageClass by number or name (blank = $prefer): " choice
if test -z "$choice" if test -z "$choice"
echo "Using StorageClass: $prefer" >&2
echo $prefer echo $prefer
return 0 return 0
end end
if string match -qr '^[0-9]+$' -- $choice if string match -qr '^[0-9]+$' -- $choice
set idx (math $choice) set idx (math $choice)
if test $idx -lt 1 -o $idx -gt (count $lines) if test $idx -lt 1 -o $idx -gt (count $lines)
echo (set_color red)"Error:"(set_color normal)" Choice out of range." >&2 echo (set_color red)"Error:"(set_color normal)" Choice out of range." >&2
return 1 return 1
end end
printf "%s\n" $lines[$idx] | awk -F'\t' '{print $1}' set sel (printf "%s\n" $lines[$idx] | awk -F'\t' '{print $1}')
else echo "Using StorageClass: $sel" >&2
printf "%s\n" $lines | awk -F'\t' -v want="$choice" '$1==want{print $1}' echo $sel
return 0
end 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
# ---- probe Sui JSON-RPC endpoint
function _probe_sui_rpc --description "probe Sui JSON-RPC endpoint" -a URL
if test -z "$URL"
echo (set_color red)"Error:"(set_color normal)" _probe_sui_rpc needs an URL" >&2
return 1
end
set payload '{"jsonrpc":"2.0","id":1,"method":"sui_getLatestCheckpointSequenceNumber","params":[]}'
set tmp (mktemp)
set code (curl -sS -o $tmp -w "%{http_code}" -H "Content-Type: application/json" -d "$payload" "$URL")
set rc $status
set body (cat $tmp); rm -f $tmp
if test $rc -ne 0
echo (set_color red)"Transport error probing $URL (curl rc=$rc)."(set_color normal) >&2
return 1
end
if test "$code" != "200"
echo (set_color red)"HTTP $code from $URL"(set_color normal) >&2
echo $body >&2
return 1
end
set seq (printf "%s" $body | jq -r '.result // empty' 2>/dev/null)
if test -z "$seq"
echo (set_color red)"No result from RPC probe at $URL."(set_color normal) >&2
return 1
end
echo "Sui RPC OK: latest checkpoint seq=$seq"
end end
### --------- main workflow ---------- ### --------- main workflow ----------
function suiwallet_bootstrap --description "Create a Sui wallet (PVC+Deployment) and generate a new address" function suiwallet_bootstrap --description "Interactive setup of a Sui wallet pod with PVC; creates address and prints recovery phrase"
_need kubectl jq awk; or return 1 _need kubectl jq curl awk; or return 1
_banner "Sui wallet bootstrap" _banner "Sui wallet bootstrap"
read -P "Namespace [crypto]: " ns # --- Namespace
if test -z "$ns"; set ns crypto; end read -P "Namespace [crypto]: " ns_raw
set ns (_k8s_name $ns) if test -z "$ns_raw"; set ns_raw crypto; end
set ns (_k8s_name $ns_raw)
_require "Namespace" $ns; or return 1
echo "Ensuring namespace '$ns' exists…" echo "Ensuring namespace '$ns' exists…"
if not kubectl get ns $ns >/dev/null 2>&1 if kubectl get ns $ns >/dev/null 2>&1
echo "Namespace $ns already exists."
else
kubectl create ns $ns; or return 1 kubectl create ns $ns; or return 1
echo "Created namespace $ns."
end end
kubectl get storageclass 2>/dev/null # --- StorageClass
echo "TIP: Prefer a Retain StorageClass so deleting the pod won't delete keys." 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 set sc (_choose_sc); or return 1
read -P "Wallet name (e.g. 'brad' → wallet-sui-brad): " wallet_raw # --- Unified name (prefix wallet-sui-)
read -P "Wallet name (no spaces, e.g. 'brad', base will be wallet-sui-brad): " wallet_raw
_require "Wallet name" $wallet_raw; or return 1 _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 (_k8s_name $wallet_raw)
set app "wallet-sui-$base" set base (string replace -r '^wallet-sui-' '' -- $base)
set pvc $app set name "wallet-sui-$base"
# Bind all k8s object names to the unified base
set pvc_name $name
set app_name $name
# --- Detect existing resources (idempotence)
set exists_any 0
set found_list
for r in "deploy/$app_name" (printf "netpol/%s-deny-ingress" $app_name)
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 (printf "%s-mnemonic" $app_name) >/dev/null 2>&1
set -a found_list (printf "secret/%s-mnemonic" $app_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/netpol/PVC/secret) 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 netpol (printf "%s-deny-ingress" $app_name) --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 (printf "%s-mnemonic" $app_name) --ignore-not-found >/dev/null 2>&1; or true
echo "Cleaned up previous resources."
else
echo "Reusing existing resources; they will be applied in-place."
end
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
# --- Network and RPC
read -P "Network [mainnet|testnet|devnet] [mainnet]: " net read -P "Network [mainnet|testnet|devnet] [mainnet]: " net
if test -z "$net"; set net mainnet; end if test -z "$net"; set net mainnet; end
switch $net switch $net
@ -115,8 +239,17 @@ function suiwallet_bootstrap --description "Create a Sui wallet (PVC+Deployment)
echo (set_color red)"Error:"(set_color normal)" network must be mainnet|testnet|devnet"; return 1 echo (set_color red)"Error:"(set_color normal)" network must be mainnet|testnet|devnet"; return 1
end end
read -P "Container image [mysten/sui-tools:latest]: " image _banner "Probing Sui RPC endpoint"
if test -z "$image"; set image mysten/sui-tools:latest; end if not _probe_sui_rpc $rpc_url
echo (set_color red)"Aborting due to RPC probe failure."(set_color normal)
return 1
end
# --- Image and address alias
set image_default (_sui_default_image $net)
echo "NOTE: Prefer a multi-arch image you built (see instructions below)."
read -P "Container image [$image_default]: " image
if test -z "$image"; set image $image_default; end
read -P "Address alias to create [main]: " alias read -P "Address alias to create [main]: " alias
if test -z "$alias"; set alias main; end if test -z "$alias"; set alias main; end
@ -127,138 +260,479 @@ function suiwallet_bootstrap --description "Create a Sui wallet (PVC+Deployment)
read -P "Store mnemonic as a Kubernetes Secret? (NOT recommended) (y/N): " store_mn read -P "Store mnemonic as a Kubernetes Secret? (NOT recommended) (y/N): " store_mn
set store_mn (string lower -- $store_mn) set store_mn (string lower -- $store_mn)
# --- Optional NetworkPolicy
read -P "Add NetworkPolicy (deny all ingress; allow egress) ? (y/N): " want_np
set want_np (string lower -- $want_np)
_banner "Summary" _banner "Summary"
echo " Namespace: $ns" echo " Namespace: $ns"
echo " App name: $app"
echo " StorageClass: $sc" echo " StorageClass: $sc"
echo " PVC: $pvc" echo " Base name: $name"
echo " PVC: $pvc_name"
echo " App/Deployment:$app_name"
echo " Image: $image" echo " Image: $image"
echo " Network: $net" echo " Network: $net"
echo " RPC URL: $rpc_url" echo " RPC URL: $rpc_url"
echo " New alias: $alias (word length: $wl)" echo " New alias: $alias (word length: $wl)"
read -P "Proceed? [y/N]: " ok if test $use_pin -eq 1
if not string match -qi 'y*' -- $ok echo " NodeSelector: $node_label_key=$node_label_val"
echo "Aborted."; return 1 end
if string match -qi 'y*' -- $want_np
echo " NetworkPolicy: deny-all ingress (egress allowed)"
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 end
_banner "Applying PVC" _banner "Applying PVC"
kubectl -n $ns get pvc $pvc >/dev/null 2>&1; or begin if kubectl -n $ns get pvc $pvc_name >/dev/null 2>&1
printf "%s\n" \ echo "PVC $ns/$pvc_name exists; keeping as-is."
"apiVersion: v1 else
kind: PersistentVolumeClaim set pvcfile (mktemp -t $pvc_name-pvc.XXXX.yaml)
metadata: begin
name: $pvc echo "apiVersion: v1"
namespace: $ns echo "kind: PersistentVolumeClaim"
spec: echo "metadata:"
accessModes: [\"ReadWriteOnce\"] echo " name: $pvc_name"
storageClassName: $sc echo " namespace: $ns"
resources: echo "spec:"
requests: echo " accessModes: [\"ReadWriteOnce\"]"
storage: 1Gi" | kubectl apply -f -; or return 1 echo " storageClassName: $sc"
echo " resources:"
echo " requests:"
echo " storage: 1Gi"
end > $pvcfile
echo "--- PVC manifest ($pvcfile) ---"
cat $pvcfile
kubectl apply -f $pvcfile; or return 1
end end
_banner "Applying Deployment" _banner "Applying Deployment"
printf "%s\n" \ set dply (mktemp -t $app_name-deploy.XXXX.yaml)
"apiVersion: apps/v1 begin
kind: Deployment echo "apiVersion: apps/v1"
metadata: echo "kind: Deployment"
name: $app echo "metadata:"
namespace: $ns echo " name: $app_name"
labels: { app: $app } echo " namespace: $ns"
spec: echo " labels: { app: $app_name }"
replicas: 1 echo "spec:"
selector: { matchLabels: { app: $app } } echo " replicas: 1"
template: echo " strategy: { type: Recreate }"
metadata: echo " selector: { matchLabels: { app: $app_name } }"
labels: { app: $app } echo " template:"
spec: echo " metadata:"
containers: echo " labels: { app: $app_name }"
- name: sui-tools echo " spec:"
image: $image if test $use_pin -eq 1
imagePullPolicy: IfNotPresent echo " nodeSelector:"
command: [\"bash\",\"-lc\",\"sleep infinity\"] echo " $node_label_key: \"$node_label_val\""
volumeMounts: end
- name: data echo " securityContext:"
mountPath: /root/.sui echo " fsGroup: 1000"
resources: echo " fsGroupChangePolicy: OnRootMismatch"
requests: { cpu: 50m, memory: 128Mi } echo " initContainers:"
limits: { cpu: 1, memory: 512Mi } echo " - name: volume-permissions"
volumes: echo " image: busybox:1.36"
- name: data echo " command: [\"/bin/sh\",\"-lc\",\"mkdir -p /home/sui/.sui/sui_config && chown -R 1000:1000 /home/sui/.sui && chmod -R 0770 /home/sui/.sui\"]"
persistentVolumeClaim: { claimName: $pvc }" | kubectl apply -f -; or return 1 echo " securityContext: { runAsUser: 0 }"
echo " volumeMounts: [ { name: data, mountPath: /home/sui/.sui } ]"
echo " containers:"
echo " - name: sui-tools"
echo " image: $image"
echo " imagePullPolicy: IfNotPresent"
echo " command: [\"/bin/sh\",\"-lc\"]"
echo " args:"
echo " - |"
echo " # keep container ready for execs"
echo " sleep infinity"
echo " volumeMounts:"
echo " - name: data"
echo " mountPath: /home/sui/.sui"
echo " resources:"
echo " requests: { cpu: \"50m\", memory: \"128Mi\" }"
echo " limits: { cpu: \"1\", memory: \"512Mi\" }"
echo " volumes:"
echo " - name: data"
echo " persistentVolumeClaim: { claimName: $pvc_name }"
end > $dply
echo "--- Deployment manifest ($dply) ---"
head -n 70 $dply
echo "..."
kubectl apply -f $dply; or return 1
if string match -qi 'y*' -- $want_np
_banner "Applying NetworkPolicy (deny-all ingress; allow all egress)"
set np (mktemp -t $app_name-netpol.XXXX.yaml)
begin
echo "apiVersion: networking.k8s.io/v1"
echo "kind: NetworkPolicy"
echo "metadata:"
printf " name: %s-deny-ingress\n" $app_name
echo " namespace: $ns"
echo "spec:"
echo " podSelector: { matchLabels: { app: $app_name } }"
echo " policyTypes: [\"Ingress\",\"Egress\"]"
echo " ingress: []"
echo " egress:"
echo " - {}"
end > $np
echo "--- NetworkPolicy manifest ($np) ---"
cat $np
kubectl apply -f $np; or return 1
end
_banner "Waiting for rollout" _banner "Waiting for rollout"
kubectl -n $ns rollout status deploy/$app --timeout=120s; or return 1 if not kubectl -n $ns rollout status deploy/$app_name --timeout=300s
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
return 1
end
_banner "Validating Sui CLI inside the pod"
set ARCH (kubectl -n $ns exec deploy/$app_name -- sh -lc 'uname -m' 2>/dev/null)
set GLIBC (kubectl -n $ns exec deploy/$app_name -- sh -lc 'ldd --version 2>&1 | head -n1 || true' 2>/dev/null)
set SUIV (kubectl -n $ns exec deploy/$app_name -- sh -lc 'sui --version 2>&1 || true' 2>/dev/null)
echo "Node arch: $ARCH"
echo "GLIBC: $GLIBC"
echo "sui: $SUIV"
if test -z "$SUIV"; or string match -q "*not found*" -- $SUIV
echo (set_color red)"Error:"(set_color normal)" 'sui' did not run inside the pod."
echo "If you see GLIBC < 2.38 here, rebuild the image on Ubuntu 24.04 as shown in section A."
return 1
end
_banner "Initializing Sui client env ($net) and creating a new address" _banner "Initializing Sui client env ($net) and creating a new address"
# new-env: writes client.yaml; new-address: writes to sui.keystore and prints the phrase # Create/switch env, then create a new address with alias.
set OUT (mktemp) # Try JSON first; fall back to parsing text.
kubectl -n $ns exec deploy/$app -- bash -lc \ set TMP (mktemp)
(printf 'mkdir -p /root/.sui/sui_config ;
sui client new-env --alias %s --rpc %s >/dev/null 2>&1 || true ;
sui client new-address ed25519 --word-length %s --alias %s' \
$net $rpc_url $wl $alias) | tee $OUT
set ADDR (kubectl -n $ns exec deploy/$app -- bash -lc \ # Ensure config dir exists (inside the pod) and env points to your chosen RPC.
(printf 'sui client addresses | awk "/%s/{print \$NF}" | tail -n1' $alias)) kubectl -n $ns exec deploy/$app_name -- sh -lc \
(printf 'mkdir -p /home/sui/.sui/sui_config ;
sui client new-env --alias %s --rpc %s >/dev/null 2>&1 || true ;
sui client switch --env %s >/dev/null 2>&1 || true' $net $rpc_url $net)
# Try JSON (preferred)
set JSON (kubectl -n $ns exec deploy/$app_name -- sh -lc \
(printf 'sui client new-address ed25519 --word-length %s --alias %s --json 2>/dev/null || true' $wl $alias))
set MN (printf "%s" $JSON | jq -r '.secretRecoveryPhrase // .mnemonic // .mnemonicPhrase // empty' 2>/dev/null)
set ADDR (printf "%s" $JSON | jq -r '.address // empty' 2>/dev/null)
if test -n "$MN"
kubectl -n $ns logs deploy/$app_name --since=10s >/dev/null 2>&1 || true
kubectl -n $ns exec deploy/$app_name -- sh -lc \
(printf 'echo "SECRET RECOVERY PHRASE: %s" >> /home/sui/README_MNEMONIC && chmod 0600 /home/sui/README_MNEMONIC' "$MN")
end
if test -z "$MN" -o -z "$ADDR"
# Fall back to plain text (robust across CLI changes)
kubectl -n $ns exec deploy/$app_name -- sh -lc \
(printf 'sui client new-address ed25519 --word-length %s --alias %s 2>&1 || true' $wl $alias) | tee $TMP >/dev/null
if test -z "$MN"
# Usually phrase is printed on the line right after the "Secret Recovery Phrase" header
set MN (awk '/Secret Recovery Phrase/ {getline; print; exit}' $TMP)
end
if test -z "$ADDR"
# Typical line: "Address: 0xabc..."; extract the RHS
set ADDR (awk -F': ' '/^Address:/ {print $2; exit}' $TMP)
end
end
# Make the generated address active (address wins; alias as backup)
if test -n "$ADDR"
kubectl -n $ns exec deploy/$app_name -- sh -lc (printf 'sui client switch --address %s >/dev/null 2>&1 || true' $ADDR)
else
kubectl -n $ns exec deploy/$app_name -- sh -lc (printf 'sui client switch --alias %s >/dev/null 2>&1 || true' $alias)
end
echo echo
echo (set_color yellow)"IMPORTANT — Secret Recovery Phrase (write down & store offline):"(set_color normal) echo (set_color yellow)"IMPORTANT — Secret Recovery Phrase (write down & store offline):"(set_color normal)
grep -E 'Secret Recovery Phrase|Mnemon' $OUT -A2 | sed -e 's/^\s\+//' if test -n "$MN"
echo $MN
else
echo "(mnemonic not detected — check 'kubectl -n $ns logs deploy/$app_name' output)"
end
echo echo
echo "Address alias '$alias' appears to be: $ADDR" echo "Address alias '$alias' appears to be: $ADDR"
rm -f $OUT
# Optional: store mnemonic in a Secret (not recommended)
if test "$store_mn" = "y" -o "$store_mn" = "yes" if test "$store_mn" = "y" -o "$store_mn" = "yes"
set MN (kubectl -n $ns exec deploy/$app -- bash -lc \ if test -n "$MN"
(printf 'sui client addresses >/dev/null 2>&1 ; sui client new-address ed25519 --word-length %s --alias %s --json 2>/dev/null | jq -r .secretRecoveryPhrase || true' $wl "${alias}-dup")) kubectl -n $ns create secret generic "$app_name-mnemonic" \
if test -n "$MN" -a "$MN" != "null"
kubectl -n $ns create secret generic {$app}-mnemonic \
--from-literal=mnemonic="$MN" --dry-run=client -o yaml | kubectl -n $ns apply -f - --from-literal=mnemonic="$MN" --dry-run=client -o yaml | kubectl -n $ns apply -f -
echo (set_color yellow)"Stored mnemonic in Secret $ns/{$app}-mnemonic. Treat your cluster as sensitive."(set_color normal) echo (set_color yellow)"Stored mnemonic in Secret $ns/{$app_name}-mnemonic. Treat your cluster as sensitive."(set_color normal)
# clean the duplicate alias we created to harvest JSON
kubectl -n $ns exec deploy/$app -- bash -lc "sui client remove-address --alias ${alias}-dup >/dev/null 2>&1 || true" >/dev/null 2>&1
else else
echo (set_color red)"Warning:"(set_color normal)" could not capture mnemonic via --json, not storing." echo (set_color red)"Warning:"(set_color normal)" mnemonic empty; not storing."
end end
end end
echo echo
echo "Done. Files inside the pod:" echo "Done. Files inside the pod:"
kubectl -n $ns exec deploy/$app -- bash -lc 'ls -l /root/.sui/sui_config || true' kubectl -n $ns exec deploy/$app_name -- sh -lc 'ls -l /home/sui/.sui/sui_config || true'
echo echo
echo "Useful commands:" echo "Useful commands:"
echo " suiwallet_addresses $ns $app" echo " suiwallet_addresses $ns $app_name"
echo " suiwallet_export_keystore $ns $app ./sui.keystore.$base" echo " suiwallet_gas $ns $app_name"
echo " kubectl -n $ns exec -it deploy/$app -- bash # interactive CLI" echo " suiwallet_export_keystore $ns $app_name ./sui.keystore.$base"
echo " suiwallet_metrics $ns $app_name $base"
echo " kubectl -n $ns exec -it deploy/$app_name -- sh # interactive CLI"
end end
### --------- utilities ---------- ### --------- utilities ----------
function suiwallet_status -a NS APP
if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-sui-main; end
kubectl -n $NS get deploy,po,pvc,netpol -l app=$APP -o wide
end
function suiwallet_logs -a NS APP LINES
if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-sui-main; end
if test -z "$LINES"; set LINES 200; end
kubectl -n $NS logs deploy/$APP --tail=$LINES
end
function suiwallet_wallet_ls -a NS APP
if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-sui-main; end
echo "Listing /home/sui/.sui/sui_config in $NS/$APP" >&2
kubectl -n $NS exec deploy/$APP -- sh -lc 'ls -la /home/sui/.sui/sui_config || true'
end
function suiwallet_addresses -a NS APP function suiwallet_addresses -a NS APP
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-sui-main; end if test -z "$APP"; set APP wallet-sui-main; end
kubectl -n $NS exec deploy/$APP -- bash -lc 'sui client addresses || true' kubectl -n $NS exec deploy/$APP -- sh -lc 'sui client addresses || true'
end end
function suiwallet_export_keystore -a NS APP DEST function suiwallet_export_keystore -a NS APP DEST
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-sui-main; end if test -z "$APP"; set APP wallet-sui-main; end
if test -z "$DEST"; set DEST ./sui.keystore; end if test -z "$DEST"; set DEST ./sui.keystore; end
echo "Copying keystore to $DEST (keep it safe!)" set POD (kubectl -n $NS get pod -l app=$APP -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
kubectl -n $NS cp deploy/$APP:/root/.sui/sui_config/sui.keystore $DEST if test -z "$POD"
echo (set_color red)"Error:"(set_color normal)" no pod found for app=$APP in ns=$NS" >&2
return 1
end
echo "Copying keystore from $POD to $DEST (keep it safe!)"
kubectl -n $NS cp $POD:/home/sui/.sui/sui_config/sui.keystore $DEST
end end
function suiwallet_status -a NS APP # Summarize gas coins and total balance; tolerant to schema differences
function suiwallet_gas -a NS APP
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-sui-main; end if test -z "$APP"; set APP wallet-sui-main; end
kubectl -n $NS get deploy,po,pvc -l app=$APP -o wide set J (kubectl -n $NS exec deploy/$APP -- sh -lc 'sui client gas --json' 2>/dev/null)
if test -z "$J"
echo "No JSON from 'sui client gas' (is the address set and env working?)" >&2
return 1
end
set NUM (printf "%s" $J | jq -r '[.. | .gasObjects? // empty | length] | add // 0')
if test -z "$NUM" -o "$NUM" = "null"
set NUM (printf "%s" $J | jq -r '[.. | .coins? // empty | length] | add // 0')
end
set SUM (printf "%s" $J | jq -r '
(.. | .totalBalance? // empty) as $t
| if $t != null then ($t|tonumber)
else [.. | .gasObjects? // empty | .[]? | .balance? | tonumber] | add
end // 0
')
# Convert Mist -> SUI using python -c
set SUM_SUI (python3 -c 'from decimal import Decimal; import sys; q=Decimal(sys.argv[1]) if sys.argv[1] else Decimal(0); print((q/Decimal("1e9")).quantize(Decimal("0.000000001")))' -- $SUM)
echo "Gas coins: $NUM Total: $SUM ($SUM_SUI SUI)"
end
# Wait until balance >= threshold (SUI), timeout seconds
function suiwallet_wait_balance -a NS APP MIN_SUI TIMEOUT
_need python3; or return 1
if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-sui-main; end
if test -z "$MIN_SUI"; set MIN_SUI 0; end
if test -z "$TIMEOUT"; set TIMEOUT 300; end
set start (date +%s)
while true
set J (kubectl -n $NS exec deploy/$APP -- sh -lc 'sui client gas --json' 2>/dev/null)
set SUM (printf "%s" $J | jq -r '
(.. | .totalBalance? // empty) as $t
| if $t != null then ($t|tonumber)
else [.. | .gasObjects? // empty | .[]? | .balance? | tonumber] | add
end // 0
')
set SUM_SUI (python3 -c 'from decimal import Decimal; import sys; q=Decimal(sys.argv[1]) if sys.argv[1] else Decimal(0); print(q/Decimal("1e9"))' -- $SUM)
set ge (python3 -c 'from decimal import Decimal; import sys; print(1 if Decimal(sys.argv[1]) >= Decimal(sys.argv[2]) else 0)' -- $SUM_SUI $MIN_SUI)
if test "$ge" = "1"
echo "Balance ready: $SUM_SUI SUI (>= $MIN_SUI)"
return 0
end
if test (math (date +%s) - $start) -ge $TIMEOUT
echo "Timed out waiting for balance >= $MIN_SUI SUI (current $SUM_SUI SUI)."
return 1
end
sleep 6
end
end
# Send SUI using `transfer-sui` picking the largest coin
# Usage: suiwallet_transfer_sui [ns] [app] <to_addr> <amount_sui> [gas_budget_mist=10000000]
function suiwallet_transfer_sui -a NS APP TO AMT_SUI GAS_BUDGET
_need jq python3; or return 1
if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-sui-main; end
if test -z "$TO" -o -z "$AMT_SUI"
echo "Usage: suiwallet_transfer_sui [ns] [app] <to_address> <amount_sui> [gas_budget_mist]" >&2
return 1
end
if test -z "$GAS_BUDGET"; set GAS_BUDGET 10000000; end
set AMT_MIST (python3 -c 'from decimal import Decimal; import sys; print(int((Decimal(sys.argv[1])*Decimal("1e9")).to_integral_value()))' -- $AMT_SUI)
set J (kubectl -n $NS exec deploy/$APP -- sh -lc 'sui client gas --json' 2>/dev/null)
set COIN (printf "%s" $J | jq -r '
([.. | .gasObjects? // empty | .[]?] // [])
| sort_by(.balance|tonumber) | last | .coinObjectId // empty
')
if test -z "$COIN"
echo (set_color red)"No gas coins found to send from."(set_color normal) >&2
return 1
end
echo "Using coin: $COIN"
kubectl -n $NS exec deploy/$APP -- sh -lc \
(printf 'sui client transfer-sui --to %s --sui-coin-object-id %s --amount %s --gas-budget %s' $TO $COIN $AMT_MIST $GAS_BUDGET)
end
# Merge all gas coins into the largest one
function suiwallet_merge_gas -a NS APP
_need jq; or return 1
if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-sui-main; end
set J (kubectl -n $NS exec deploy/$APP -- sh -lc 'sui client gas --json' 2>/dev/null)
set IDS (printf "%s" $J | jq -r '([.. | .gasObjects? // empty | .[]?] // []) | sort_by(.balance|tonumber) | reverse | .[].coinObjectId')
set coins $IDS
if test (count $coins) -lt 2
echo "Nothing to merge (need >=2 gas coins)."
return 0
end
set primary $coins[1]
for i in (seq 2 (count $coins))
set c $coins[$i]
echo "Merging $c -> $primary"
kubectl -n $NS exec deploy/$APP -- sh -lc (printf 'sui client merge-coin --primary-coin %s --coin-to-merge %s --gas-budget 10000000' $primary $c)
end
end
# Restore an address from a Secret Recovery Phrase into the running pod
# Usage: suiwallet_restore [ns] [app] [alias] [network=mainnet]
function suiwallet_restore -a NS APP ALIAS NET
_need kubectl jq; or return 1
if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-sui-main; end
if test -z "$ALIAS"; set ALIAS restored; end
if test -z "$NET"; set NET mainnet; end
switch $NET
case mainnet
set RPC https://fullnode.mainnet.sui.io:443
case testnet
set RPC https://fullnode.testnet.sui.io:443
case devnet
set RPC https://fullnode.devnet.sui.io:443
case '*'
echo (set_color red)"Error:"(set_color normal)" NET must be mainnet|testnet|devnet"; return 1
end
echo (set_color yellow)"Paste your 12/24-word Sui Secret Recovery Phrase (input hidden):"(set_color normal)
read -s MNEMONIC
echo
if test -z "$MNEMONIC"
echo (set_color red)"Error:"(set_color normal)" empty mnemonic."; return 1
end
# Ensure env is configured and active
kubectl -n $NS exec deploy/$APP -- sh -lc \
(printf 'mkdir -p /home/sui/.sui/sui_config ;
sui client new-env --alias %s --rpc %s >/dev/null 2>&1 || true ;
sui client switch --env %s >/dev/null 2>&1 || true' $NET $RPC $NET); or return 1
# Import mnemonic into keystore (non-interactive; enter scheme then phrase)
# keytool is interactive; we feed "ed25519" then the phrase via stdin.
set TMP (mktemp)
printf "ed25519\n%s\n" "$MNEMONIC" \
| kubectl -n $NS exec -i deploy/$APP -- sh -lc 'sui keytool import 2>&1' \
| tee $TMP >/dev/null
# Create a named alias pointing to one of the imported keys by re-using new-address --recover
# (new-address --recover will prompt for phrase; we feed it again to bind an alias)
set OUT (mktemp)
printf "%s\n" "$MNEMONIC" \
| kubectl -n $NS exec -i deploy/$APP -- sh -lc \
(printf 'sui client new-address ed25519 --alias %s --recover 2>&1' $ALIAS) \
| tee $OUT >/dev/null
# Try to extract the resulting address
set ADDR (awk -F': ' '/^Address:/ {print $2; exit}' $OUT)
if test -z "$ADDR"
# fallback: pick the address for the alias from the address table
set ADDR (kubectl -n $NS exec deploy/$APP -- sh -lc (printf 'sui client addresses | awk "/ %s\\s*$/ {print \\$NF}" | tail -n1' $ALIAS) 2>/dev/null)
end
echo
echo "Restored alias: $ALIAS"
echo "Address: $ADDR"
echo (set_color yellow)"IMPORTANT:"(set_color normal)" store your recovery phrase offline. Do NOT keep it in the cluster."
# Make it active for the client
if test -n "$ADDR"
kubectl -n $NS exec deploy/$APP -- sh -lc (printf 'sui client switch --address %s >/dev/null 2>&1 || true' $ADDR)
end
end end
function suiwallet_stop -a NS APP function suiwallet_stop -a NS APP
if test -z "$NS"; set NS crypto; end if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-sui-main; end if test -z "$APP"; set APP wallet-sui-main; end
kubectl -n $NS delete deploy $APP --ignore-not-found kubectl -n $NS delete deploy $APP --ignore-not-found
echo "Stopped $NS/$APP (PVC kept)." echo "Stopped $NS/$APP. PVC retained."
end
# Print the primary (first) address by alias search
function suiwallet_primary_address -a NS APP
if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-sui-main; end
kubectl -n $NS exec deploy/$APP -- sh -lc \
"sui client addresses | awk 'NF{print \$NF}' | head -n1" 2>/dev/null
end
# Quick overview
function suiwallet_overview -a NS APP
if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-sui-main; end
echo "== Sui wallet overview (ns=$NS app=$APP)"
kubectl -n $NS exec deploy/$APP -- sh -lc 'echo "-- Addresses:"; sui client addresses || true'
kubectl -n $NS exec deploy/$APP -- sh -lc 'echo "-- Gas objects:"; sui client gas 2>/dev/null || true'
end
# Sui keystore size (for Grafana/Prometheus)
function suiwallet_keystore_size_bytes -a NS APP
if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-sui-main; end
set SZ (kubectl -n $NS exec deploy/$APP -- sh -lc \
'test -e /home/sui/.sui/sui_config/sui.keystore && stat -c %s /home/sui/.sui/sui_config/sui.keystore || echo 0' 2>/dev/null)
echo $SZ
end
# Prometheus-friendly one-liner metric
# Example usage: suiwallet_metrics crypto wallet-sui-test mywallet
function suiwallet_metrics -a NS APP WALLET_NAME
if test -z "$NS"; set NS crypto; end
if test -z "$APP"; set APP wallet-sui-main; end
if test -z "$WALLET_NAME"; set WALLET_NAME main; end
set BYTES (suiwallet_keystore_size_bytes $NS $APP)
printf "wallet_size_bytes{chain=\"sui\",namespace=\"%s\",app=\"%s\",wallet=\"%s\"} %s\n" $NS $APP $WALLET_NAME $BYTES
end end
function suiwallet_purge -a NS APP PVC function suiwallet_purge -a NS APP PVC
@ -268,8 +742,27 @@ function suiwallet_purge -a NS APP PVC
echo "Type to confirm: PURGE $NS $PVC" echo "Type to confirm: PURGE $NS $PVC"
read CONFIRM read CONFIRM
if test "$CONFIRM" != "PURGE $NS $PVC" if test "$CONFIRM" != "PURGE $NS $PVC"
echo "Aborted."; return 1 echo "Aborted."
return 1
end end
kubectl -n $NS delete deploy $APP --ignore-not-found kubectl -n $NS delete deploy $APP --ignore-not-found
kubectl -n $NS delete pvc $PVC --ignore-not-found --wait=true kubectl -n $NS delete pvc $PVC --ignore-not-found --wait=true
kubectl -n $NS delete netpol (printf "%s-deny-ingress" $APP) --ignore-not-found >/dev/null 2>&1
echo "Purged. Ensure you have the mnemonic backed up if you ever need to restore."
end
function suiwallet_help
echo "suiwallet_bootstrap # interactive deploy + PVC + optional NP; creates env+address, prints mnemonic"
echo "suiwallet_status [ns] [app] # show deploy/pods/pvc/netpol"
echo "suiwallet_logs [ns] [app] [N] # tail N (default 200) lines of pod logs"
echo "suiwallet_wallet_ls [ns] [app] # list ~/.sui/sui_config inside pod"
echo "suiwallet_addresses [ns] [app] # list addresses"
echo "suiwallet_gas [ns] [app] # summarize gas coins and total (Mist & SUI)"
echo "suiwallet_wait_balance [ns] [app] <min_sui> [timeout_s=300]"
echo "suiwallet_transfer_sui [ns] [app] <to> <amount_sui> [gas_budget_mist]"
echo "suiwallet_merge_gas [ns] [app] # merge many gas coins into one"
echo "suiwallet_export_keystore [ns] [app] [dest=./sui.keystore]"
echo "suiwallet_metrics [ns] [app] [walletname] # Prometheus line: wallet_size_bytes{chain=\"sui\",...}"
echo "suiwallet_stop [ns] [app] # delete deployment (keep PVC)"
echo "suiwallet_purge [ns] [app] [pvc] # delete deployment + PVC (danger!)"
end end

View File

@ -0,0 +1,39 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
kind: HelmRelease
metadata:
name: vault
namespace: vault
spec:
interval: 15m
chart:
spec:
chart: vault
version: "0.28.x"
sourceRef:
kind: HelmRepository
name: hashicorp
namespace: flux-system
values:
global:
enabled: true
server:
ha:
enabled: true
replicas: 3
raft:
enabled: true
dataStorage:
enabled: true
size: 5Gi
storageClassName: astreae
service:
type: ClusterIP
ingress:
enabled: false
resources:
requests: { cpu: "100m", memory: "256Mi" }
limits: { cpu: "500m", memory: "512Mi" }
injector:
enabled: true
csi:
enabled: true

View File

@ -0,0 +1,8 @@
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
name: hashicorp
namespace: vault
spec:
interval: 1h
url: https://helm.releases.hashicorp.com

View File

@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: vault
resources:
- namespace.yaml
- helmrepo.yaml
- helmrelease.yaml

View File

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: vault