From 016cbab0f93ae885814ec4cedcc52c31952bc2ef Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Tue, 19 Aug 2025 01:06:45 -0500 Subject: [PATCH] added vault --- dockerfiles/Dockerfile.monero-p2pool | 1 + dockerfiles/Dockerfile.monero-wallet-rpc | 1 + dockerfiles/Dockerfile.monerod | 1 + dockerfiles/Dockerfile.sui-tools | 54 ++ .../flux-system/kustomization-vault.yaml | 12 + scripts/wallet_monero_setup.fish | 246 +++--- scripts/wallet_sui_setup.fish | 711 +++++++++++++++--- services/vault/helmrelease.yaml | 39 + services/vault/helmrepo.yaml | 8 + services/vault/kustomization.yaml | 7 + services/vault/namespace.yaml | 4 + 11 files changed, 889 insertions(+), 195 deletions(-) create mode 100644 dockerfiles/Dockerfile.sui-tools create mode 100644 infrastructure/flux-system/kustomization-vault.yaml create mode 100644 services/vault/helmrelease.yaml create mode 100644 services/vault/helmrepo.yaml create mode 100644 services/vault/kustomization.yaml create mode 100644 services/vault/namespace.yaml diff --git a/dockerfiles/Dockerfile.monero-p2pool b/dockerfiles/Dockerfile.monero-p2pool index 08faea3..de94269 100644 --- a/dockerfiles/Dockerfile.monero-p2pool +++ b/dockerfiles/Dockerfile.monero-p2pool @@ -1,3 +1,4 @@ +# dockerfiles/Dockerfile.monero-p2pool # syntax=docker/dockerfile:1.6 ARG DEBIAN_IMAGE=debian:bookworm-slim ARG P2POOL_VERSION=v4.9 diff --git a/dockerfiles/Dockerfile.monero-wallet-rpc b/dockerfiles/Dockerfile.monero-wallet-rpc index 7f65d71..ca1d90f 100644 --- a/dockerfiles/Dockerfile.monero-wallet-rpc +++ b/dockerfiles/Dockerfile.monero-wallet-rpc @@ -1,3 +1,4 @@ +# dockerfiles/Dockerfile.monero-wallet-rpc # syntax=docker/dockerfile:1.6 ARG DEBIAN_IMAGE=debian:bookworm-slim ARG MONERO_VERSION=v0.18.4.1 diff --git a/dockerfiles/Dockerfile.monerod b/dockerfiles/Dockerfile.monerod index 26b82d3..21eed12 100644 --- a/dockerfiles/Dockerfile.monerod +++ b/dockerfiles/Dockerfile.monerod @@ -1,3 +1,4 @@ +# dockerfiles/Dockerfile.monerod # syntax=docker/dockerfile:1.6 FROM debian:bookworm-slim diff --git a/dockerfiles/Dockerfile.sui-tools b/dockerfiles/Dockerfile.sui-tools new file mode 100644 index 0000000..86ed33d --- /dev/null +++ b/dockerfiles/Dockerfile.sui-tools @@ -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"] diff --git a/infrastructure/flux-system/kustomization-vault.yaml b/infrastructure/flux-system/kustomization-vault.yaml new file mode 100644 index 0000000..88806fd --- /dev/null +++ b/infrastructure/flux-system/kustomization-vault.yaml @@ -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 diff --git a/scripts/wallet_monero_setup.fish b/scripts/wallet_monero_setup.fish index 27fac29..86c746a 100644 --- a/scripts/wallet_monero_setup.fish +++ b/scripts/wallet_monero_setup.fish @@ -212,12 +212,12 @@ function _probe_monerod -a ADDR end 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 end # 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 "$SVC"; set SVC wallet-rpc; end if test -z "$SECS"; set SECS 60; end @@ -249,7 +249,7 @@ end ### ------- 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 _banner "Monero wallet RPC bootstrap" @@ -358,7 +358,7 @@ function walletsvc_bootstrap --description "Interactive setup of monero-wallet-r set daemon_addr $daemon_override end - if test -z "$WALLETSVC_SKIP_DAEMON_CHECK" + if test -z "$xmrwallet_SKIP_DAEMON_CHECK" _banner "Probing daemon via temporary port-forward" set PF_LOCAL 28081 _pf_start crypto monerod $PF_LOCAL 18081 @@ -368,7 +368,7 @@ function walletsvc_bootstrap --description "Interactive setup of monero-wallet-r end _pf_stop else - echo "Skipping daemon probe due to WALLETSVC_SKIP_DAEMON_CHECK=1" + echo "Skipping daemon probe due to xmrwallet_SKIP_DAEMON_CHECK=1" end # 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 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 + 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 xmrwallet.titan/update-ts="$ts" --overwrite >/dev/null 2>&1 echo "RPC creds: user='$rpc_user' pass='$rpc_pass'" echo "Wallet pass: $wallet_pass" @@ -609,27 +609,27 @@ function walletsvc_bootstrap --description "Interactive setup of monero-wallet-r end _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" - 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) return 1 end # 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) end - if not walletsvc_open $ns $svc_name $wallet_file $wallet_pass - echo (set_color red)"Failed to open wallet $wallet_file. If this wallet already exists, the on-disk password may differ from the Secret. Use walletsvc_open with the correct password or walletsvc_change_wallet_password."(set_color normal) + 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 xmrwallet_open with the correct password or xmrwallet_change_wallet_password."(set_color normal) return 1 end # --- Gather summary data (address, seed if enabled) - set PRIMARY (walletsvc_primary_address $ns $svc_name 2>/dev/null) - set NEWADDR (walletsvc_new_address $ns $svc_name "bootstrap-deposit" 0 2>/dev/null | awk '/New address:/{print $3}' ) - set SEEDRAW (walletsvc_show_seed $ns $svc_name 2>&1) + set PRIMARY (xmrwallet_primary_address $ns $svc_name 2>/dev/null) + set NEWADDR (xmrwallet_new_address $ns $svc_name "bootstrap-deposit" 0 2>/dev/null | awk '/New address:/{print $3}' ) + set SEEDRAW (xmrwallet_show_seed $ns $svc_name 2>&1) set SEED (echo $SEEDRAW | awk '/Mnemonic seed:/,0{if($0!~/Mnemonic seed:/)print}' ) _banner "Done" @@ -652,31 +652,31 @@ function walletsvc_bootstrap --description "Interactive setup of monero-wallet-r 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" + echo "If needed and supported by your image: xmrwallet_show_seed $ns $svc_name" end echo - 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" + echo "Access locally: xmrwallet_portforward $ns $svc_name # then http://127.0.0.1:18083/json_rpc" + echo "Quick overview: xmrwallet_overview $ns $svc_name" end ### ------- 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 "$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 +function xmrwallet_status --description "show pod/service status" -a NS APP if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-rpc; end kubectl -n $NS get deploy,po,svc,netpol -l app=$APP -o wide end -function walletsvc_stop --description "stop the RPC but keep PVC + secrets" -a NS APP SVC +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 "$APP"; set APP wallet-rpc; 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." 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 "$APP"; set APP wallet-rpc; 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." 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 if test -z "$NS"; set NS crypto; 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 # ---------- 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 "$SVC"; set SVC wallet-rpc; end if test -z "$METHOD" - echo "Usage: walletsvc_rpc_call [ns] [svc] [params_json]" >&2 + echo "Usage: xmrwallet_rpc_call [ns] [svc] [params_json]" >&2 return 1 end @@ -772,12 +772,12 @@ function walletsvc_rpc_call --description "RPC call with secrets (auto port-forw return 0 end -function walletsvc_rpc_test --description "get_version using secrets" -a NS SVC - walletsvc_rpc_call $NS $SVC get_version | jq . +function xmrwallet_rpc_test --description "get_version using secrets" -a NS SVC + xmrwallet_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 +function xmrwallet_set_rpc_credentials --description "rotate RPC Basic creds" -a NS APP _need kubectl; or return 1 if test -z "$NS"; set NS crypto; 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=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 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 status deploy/$APP --timeout=180s; or return 1 @@ -806,7 +806,7 @@ function walletsvc_set_rpc_credentials --description "rotate RPC Basic creds" -a end # 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 "$SVC"; set SVC wallet-rpc; end @@ -821,13 +821,13 @@ function walletsvc_change_wallet_password --description "change password of a wa 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) + set res (xmrwallet_rpc_call $NS $SVC open_wallet (printf '{"filename":"%s","password":"%s"}' $WFILE $OLD)) + xmrwallet_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 +function xmrwallet_logs -a NS APP LINES if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-rpc; end if test -z "$LINES"; set LINES 200; end @@ -835,7 +835,7 @@ function walletsvc_logs -a NS APP LINES end # --- 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 "$APP"; set APP wallet-rpc; end echo "Listing /data in $NS/$APP…" >&2 @@ -843,11 +843,11 @@ function walletsvc_wallet_ls -a NS APP end # --- 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 "$SVC"; set SVC wallet-rpc; end if test -z "$FILE" - echo "Usage: walletsvc_create_wallet [ns] [svc] [pass] [language]" >&2 + echo "Usage: xmrwallet_create_wallet [ns] [svc] [pass] [language]" >&2 return 1 end 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 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 if test $rc -ne 0 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 # --- 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 "$SVC"; set SVC wallet-rpc; end if test -z "$FILE" - echo "Usage: walletsvc_open [ns] [svc] [password]" >&2 + echo "Usage: xmrwallet_open [ns] [svc] [password]" >&2 return 1 end 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 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 if test $rc -ne 0 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) 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 + echo " xmrwallet_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 + xmrwallet_rpc_call $NS $SVC open_wallet (printf '{"filename":"%s","password":"%s"}' $FILE $PASS) > $tmp set rc $status set res (cat $tmp); rm -f $tmp @@ -930,14 +930,14 @@ function walletsvc_open -a NS SVC FILE PASS echo $res | jq . 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 "$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 +function xmrwallet_ensure_open -a NS SVC FILE 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 @@ -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) if test "$exists" = "no" 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 - walletsvc_open $NS $SVC $FILE; or return 1 + xmrwallet_open $NS $SVC $FILE; or return 1 end # --- 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 "$SVC"; set SVC wallet-rpc; end _pf_start $NS $SVC 18083 18083 @@ -966,11 +966,11 @@ function walletsvc_primary_address -a NS SVC end # --- show unlocked balance quickly -function walletsvc_unlocked -a NS SVC +function xmrwallet_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 res (xmrwallet_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') @@ -982,7 +982,7 @@ function walletsvc_unlocked -a NS SVC end # --- 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 if test -z "$NS"; set NS crypto; 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 set start (date +%s) 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 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) @@ -1007,7 +1007,7 @@ function walletsvc_wait_unlocked -a NS SVC XMR_MIN TIMEOUT end # ---------- 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 "$SVC"; set SVC wallet-rpc; end if test -z "$ACCOUNT"; set ACCOUNT 0; end @@ -1032,7 +1032,7 @@ function walletsvc_overview -a NS SVC ACCOUNT echo "-- Height:" if test -n "$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 echo $resp | jq -r '" wallet_height=\(.result.height)"' end @@ -1110,7 +1110,7 @@ function walletsvc_overview -a NS SVC ACCOUNT end # ---------- 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 "$SVC"; set SVC wallet-rpc; end if test -z "$ACCOUNT"; set ACCOUNT 0; end @@ -1132,7 +1132,7 @@ function walletsvc_list_addresses -a NS SVC ACCOUNT end # ---------- 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 "$SVC"; set SVC wallet-rpc; end if test -z "$ACCOUNT"; set ACCOUNT 0; end @@ -1155,13 +1155,13 @@ function walletsvc_new_address -a NS SVC LABEL ACCOUNT end # --- send a specific amount XMR to a single address (optionally payment_id) -# Usage: walletsvc_send [ns] [svc] [payment_id_hex] -function walletsvc_send -a NS SVC TO AMT_XMR PID +# Usage: xmrwallet_send [ns] [svc] [payment_id_hex] +function xmrwallet_send -a NS SVC TO AMT_XMR PID _need jq curl python3; or return 1 if test -z "$NS"; set NS crypto; end if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$TO" -o -z "$AMT_XMR" - echo "Usage: walletsvc_send [ns] [svc] [payment_id_hex]" >&2 + echo "Usage: xmrwallet_send [ns] [svc] [payment_id_hex]" >&2 return 1 end @@ -1180,7 +1180,7 @@ function walletsvc_send -a NS SVC TO AMT_XMR PID set body $body_base 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') if test -n "$err" echo "ERROR: $err" >&2 @@ -1199,18 +1199,18 @@ function walletsvc_send -a NS SVC TO AMT_XMR PID end # --- sweep all unlocked funds to an address (account 0) -# Usage: walletsvc_sweep_all [ns] [svc] -function walletsvc_sweep_all -a NS SVC TO +# Usage: xmrwallet_sweep_all [ns] [svc] +function xmrwallet_sweep_all -a NS SVC TO _need jq curl; or return 1 if test -z "$NS"; set NS crypto; end if test -z "$SVC"; set SVC wallet-rpc; end if test -z "$TO" - echo "Usage: walletsvc_sweep_all [ns] [svc] " >&2 + echo "Usage: xmrwallet_sweep_all [ns] [svc] " >&2 return 1 end set body (printf '{"address":"%s","account_index":0,"priority":1,"do_not_relay":false,"get_tx_keys":true}' $TO) - set res (walletsvc_rpc_call $NS $SVC sweep_all $body) + set res (xmrwallet_rpc_call $NS $SVC sweep_all $body) set err (echo $res | jq -r '.error.message // empty') if test -n "$err" 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: " + .' end -# Optional: non-sensitive cluster snapshot to help tune manifests. +# Return total size (bytes) of /data/ + /data/.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 set OUT cluster-snapshot.txt printf "# Cluster snapshot %s\n" (date -Is) > $OUT @@ -1271,34 +1345,34 @@ function cluster_probe end # --- one-line command reference (keep in sync) --- -function walletsvc_help - echo "walletsvc_bootstrap # interactive deploy + secrets + PVC + first wallet (multiwallet RPC), idempotent" - echo "walletsvc_status [ns] [app] # show deploy, pods, service, netpol (default ns=crypto)" - echo "walletsvc_portforward [ns] [svc] # port-forward RPC to localhost:18083 (CTRL+C to stop)" - echo "walletsvc_logs [ns] [app] [N] # tail N (default 200) lines of RPC pod logs" - echo "walletsvc_wallet_ls [ns] [app] # list files under /data in the RPC pod (check wallet files)" - echo "walletsvc_rpc_test [ns] [svc] # JSON-RPC get_version (sanity check)" - echo "walletsvc_rpc_call [ns] [svc] [params_json] # raw JSON (stdout) with digest auth" - echo "walletsvc_open [ns] [svc] [pass] # close_wallet (ignore) -> open_wallet" - echo "walletsvc_create_wallet [ns] [svc] [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)" - echo "walletsvc_change_wallet_password [ns] [svc] # change wallet file password via RPC" - echo "walletsvc_stop [ns] [app] [svc] # delete deployment+service only" - echo "walletsvc_purge [ns] [app] [pvc] [svc] # delete deploy+svc+PVC+secrets (danger!)" +function xmrwallet_help + echo "xmrwallet_bootstrap # interactive deploy + secrets + PVC + first wallet (multiwallet RPC), idempotent" + echo "xmrwallet_status [ns] [app] # show deploy, pods, service, netpol (default ns=crypto)" + echo "xmrwallet_portforward [ns] [svc] # port-forward RPC to localhost:18083 (CTRL+C to stop)" + echo "xmrwallet_logs [ns] [app] [N] # tail N (default 200) lines of RPC pod logs" + echo "xmrwallet_wallet_ls [ns] [app] # list files under /data in the RPC pod (check wallet files)" + echo "xmrwallet_rpc_test [ns] [svc] # JSON-RPC get_version (sanity check)" + echo "xmrwallet_rpc_call [ns] [svc] [params_json] # raw JSON (stdout) with digest auth" + echo "xmrwallet_open [ns] [svc] [pass] # close_wallet (ignore) -> open_wallet" + echo "xmrwallet_create_wallet [ns] [svc] [pass] [lang]# create wallet (defaults: pass from Secret, lang=English)" + echo "xmrwallet_ensure_open [ns] [svc] [file] # create if missing, then open (uses Secret pass)" + echo "xmrwallet_overview [ns] [svc] [acct=0] # version, height, balances, a few addresses, recent transfers" + echo "xmrwallet_list_addresses [ns] [svc] [acct=0] # list all subaddresses for account" + echo "xmrwallet_new_address [ns] [svc] [label] [acct=0] # create labeled subaddress" + echo "xmrwallet_set_rpc_credentials [ns] [app] # rotate RPC basic auth (secret + rollout)" + echo "xmrwallet_change_wallet_password [ns] [svc] # change wallet file password via RPC" + echo "xmrwallet_stop [ns] [app] [svc] # delete deployment+service only" + echo "xmrwallet_purge [ns] [app] [pvc] [svc] # delete deploy+svc+PVC+secrets (danger!)" end -function walletsvc_help_detailed +function xmrwallet_help_detailed echo "Commands (defaults: ns=crypto, svc/app=wallet-rpc)" echo - echo "walletsvc_bootstrap" + echo "xmrwallet_bootstrap" 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)." + echo " Set xmrwallet_SKIP_DAEMON_CHECK=1 to bypass the daemon probe (not recommended)." end diff --git a/scripts/wallet_sui_setup.fish b/scripts/wallet_sui_setup.fish index db6eb95..5883a7c 100644 --- a/scripts/wallet_sui_setup.fish +++ b/scripts/wallet_sui_setup.fish @@ -1,5 +1,5 @@ ### --------- helpers ---------- -function _need +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 command: $c" >&2 @@ -8,41 +8,61 @@ function _need end end -function _banner -a MSG +function _banner --description "pretty section header" -a MSG set_color cyan; echo; echo "==> $MSG"; set_color normal 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 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 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" - 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 end end -function _sc_list - kubectl get storageclass -o json 2>/dev/null | jq -r ' - .items[] | [ - .metadata.name, - .provisioner, - (.reclaimPolicy // "Delete"), - ( .metadata.annotations["storageclass.kubernetes.io/is-default-class"] == "true" - or .metadata.annotations["storageclass.beta.kubernetes.io/is-default-class"] == "true") - ] | @tsv' +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 -function _choose_sc +# Choose StorageClass by index or name +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 @@ -56,52 +76,156 @@ function _choose_sc 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' '$4=="true"{print $1}' | head -n1) 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 - printf "%s\n" $lines[$idx] | awk -F'\t' '{print $1}' - else - printf "%s\n" $lines | awk -F'\t' -v want="$choice" '$1==want{print $1}' + 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 + +# ---- 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 ### --------- main workflow ---------- -function suiwallet_bootstrap --description "Create a Sui wallet (PVC+Deployment) and generate a new address" - _need kubectl jq awk; or return 1 +function suiwallet_bootstrap --description "Interactive setup of a Sui wallet pod with PVC; creates address and prints recovery phrase" + _need kubectl jq curl awk; or return 1 _banner "Sui wallet bootstrap" - read -P "Namespace [crypto]: " ns - if test -z "$ns"; set ns crypto; end - set ns (_k8s_name $ns) + # --- Namespace + read -P "Namespace [crypto]: " ns_raw + if test -z "$ns_raw"; set ns_raw crypto; end + set ns (_k8s_name $ns_raw) + _require "Namespace" $ns; or return 1 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 + echo "Created namespace $ns." end - kubectl get storageclass 2>/dev/null - echo "TIP: Prefer a Retain StorageClass so deleting the pod won't delete keys." + # --- 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 - 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 + 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 app "wallet-sui-$base" - set pvc $app + set base (string replace -r '^wallet-sui-' '' -- $base) + 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 if test -z "$net"; set net mainnet; end 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 end - read -P "Container image [mysten/sui-tools:latest]: " image - if test -z "$image"; set image mysten/sui-tools:latest; end + _banner "Probing Sui RPC endpoint" + 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 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 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" echo " Namespace: $ns" - echo " App name: $app" echo " StorageClass: $sc" - echo " PVC: $pvc" + echo " Base name: $name" + echo " PVC: $pvc_name" + echo " App/Deployment:$app_name" echo " Image: $image" echo " Network: $net" echo " RPC URL: $rpc_url" echo " New alias: $alias (word length: $wl)" - read -P "Proceed? [y/N]: " ok - if not string match -qi 'y*' -- $ok - echo "Aborted."; return 1 + if test $use_pin -eq 1 + echo " NodeSelector: $node_label_key=$node_label_val" + 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 _banner "Applying PVC" - kubectl -n $ns get pvc $pvc >/dev/null 2>&1; or begin - printf "%s\n" \ -"apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: $pvc - namespace: $ns -spec: - accessModes: [\"ReadWriteOnce\"] - storageClassName: $sc - resources: - requests: - storage: 1Gi" | kubectl apply -f -; or return 1 + if kubectl -n $ns get pvc $pvc_name >/dev/null 2>&1 + echo "PVC $ns/$pvc_name exists; keeping as-is." + else + set pvcfile (mktemp -t $pvc_name-pvc.XXXX.yaml) + begin + echo "apiVersion: v1" + echo "kind: PersistentVolumeClaim" + echo "metadata:" + echo " name: $pvc_name" + echo " namespace: $ns" + echo "spec:" + echo " accessModes: [\"ReadWriteOnce\"]" + echo " storageClassName: $sc" + echo " resources:" + echo " requests:" + echo " storage: 1Gi" + end > $pvcfile + echo "--- PVC manifest ($pvcfile) ---" + cat $pvcfile + kubectl apply -f $pvcfile; or return 1 end _banner "Applying Deployment" - printf "%s\n" \ -"apiVersion: apps/v1 -kind: Deployment -metadata: - name: $app - namespace: $ns - labels: { app: $app } -spec: - replicas: 1 - selector: { matchLabels: { app: $app } } - template: - metadata: - labels: { app: $app } - spec: - containers: - - name: sui-tools - image: $image - imagePullPolicy: IfNotPresent - command: [\"bash\",\"-lc\",\"sleep infinity\"] - volumeMounts: - - name: data - mountPath: /root/.sui - resources: - requests: { cpu: 50m, memory: 128Mi } - limits: { cpu: 1, memory: 512Mi } - volumes: - - name: data - persistentVolumeClaim: { claimName: $pvc }" | kubectl apply -f -; or return 1 + 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: { type: Recreate }" + echo " selector: { 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\",\"mkdir -p /home/sui/.sui/sui_config && chown -R 1000:1000 /home/sui/.sui && chmod -R 0770 /home/sui/.sui\"]" + 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" - 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" - # new-env: writes client.yaml; new-address: writes to sui.keystore and prints the phrase - set OUT (mktemp) - kubectl -n $ns exec deploy/$app -- bash -lc \ - (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 + # Create/switch env, then create a new address with alias. + # Try JSON first; fall back to parsing text. + set TMP (mktemp) - set ADDR (kubectl -n $ns exec deploy/$app -- bash -lc \ - (printf 'sui client addresses | awk "/%s/{print \$NF}" | tail -n1' $alias)) + # Ensure config dir exists (inside the pod) and env points to your chosen RPC. + 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 (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 "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" - set MN (kubectl -n $ns exec deploy/$app -- bash -lc \ - (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")) - if test -n "$MN" -a "$MN" != "null" - kubectl -n $ns create secret generic {$app}-mnemonic \ + if test -n "$MN" + kubectl -n $ns create secret generic "$app_name-mnemonic" \ --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) - # 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 + echo (set_color yellow)"Stored mnemonic in Secret $ns/{$app_name}-mnemonic. Treat your cluster as sensitive."(set_color normal) 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 echo 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 "Useful commands:" - echo " suiwallet_addresses $ns $app" - echo " suiwallet_export_keystore $ns $app ./sui.keystore.$base" - echo " kubectl -n $ns exec -it deploy/$app -- bash # interactive CLI" + echo " suiwallet_addresses $ns $app_name" + echo " suiwallet_gas $ns $app_name" + 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 ### --------- 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 if test -z "$NS"; set NS crypto; 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 function suiwallet_export_keystore -a NS APP DEST if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-sui-main; end if test -z "$DEST"; set DEST ./sui.keystore; end - echo "Copying keystore to $DEST (keep it safe!)" - kubectl -n $NS cp deploy/$APP:/root/.sui/sui_config/sui.keystore $DEST + set POD (kubectl -n $NS get pod -l app=$APP -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + 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 -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 "$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] [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] [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 function suiwallet_stop -a NS APP if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-sui-main; end 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 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" read CONFIRM if test "$CONFIRM" != "PURGE $NS $PVC" - echo "Aborted."; return 1 + echo "Aborted." + return 1 end kubectl -n $NS delete deploy $APP --ignore-not-found 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] [timeout_s=300]" + echo "suiwallet_transfer_sui [ns] [app] [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 diff --git a/services/vault/helmrelease.yaml b/services/vault/helmrelease.yaml new file mode 100644 index 0000000..4d99ff9 --- /dev/null +++ b/services/vault/helmrelease.yaml @@ -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 diff --git a/services/vault/helmrepo.yaml b/services/vault/helmrepo.yaml new file mode 100644 index 0000000..bb9756a --- /dev/null +++ b/services/vault/helmrepo.yaml @@ -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 diff --git a/services/vault/kustomization.yaml b/services/vault/kustomization.yaml new file mode 100644 index 0000000..eefbc9b --- /dev/null +++ b/services/vault/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: vault +resources: + - namespace.yaml + - helmrepo.yaml + - helmrelease.yaml diff --git a/services/vault/namespace.yaml b/services/vault/namespace.yaml new file mode 100644 index 0000000..0158c8f --- /dev/null +++ b/services/vault/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: vault