276 lines
9.1 KiB
Fish
276 lines
9.1 KiB
Fish
|
|
### --------- helpers ----------
|
||
|
|
function _need
|
||
|
|
for c in $argv
|
||
|
|
if not type -q $c
|
||
|
|
echo (set_color red)"Error:"(set_color normal)" missing command: $c" >&2
|
||
|
|
return 1
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function _banner -a MSG
|
||
|
|
set_color cyan; echo; echo "==> $MSG"; set_color normal
|
||
|
|
end
|
||
|
|
|
||
|
|
function _k8s_name -a S
|
||
|
|
set s (string lower -- $S)
|
||
|
|
set s (string replace -ar -- '[^a-z0-9-]' '-' $s)
|
||
|
|
set s (string replace -r -- '^-+|(-)+$' '$1' $s)
|
||
|
|
echo $s
|
||
|
|
end
|
||
|
|
|
||
|
|
function _require -a WHAT VAL
|
||
|
|
if test -z "$VAL"
|
||
|
|
echo (set_color red)"Error:"(set_color normal)" $WHAT cannot be empty." >&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'
|
||
|
|
end
|
||
|
|
|
||
|
|
function _choose_sc
|
||
|
|
set lines (_sc_list)
|
||
|
|
if test (count $lines) -eq 0
|
||
|
|
echo (set_color red)"Error:"(set_color normal)" No StorageClasses found." >&2
|
||
|
|
return 1
|
||
|
|
end
|
||
|
|
echo "" >&2
|
||
|
|
echo "==> Available StorageClasses" >&2
|
||
|
|
echo " # | name | provisioner | reclaim | default" >&2
|
||
|
|
echo "----+----------------------+------------------------+---------+--------" >&2
|
||
|
|
set i 1
|
||
|
|
for l in $lines
|
||
|
|
set name (echo $l | awk -F'\t' '{print $1}')
|
||
|
|
set prov (echo $l | awk -F'\t' '{print $2}')
|
||
|
|
set rec (echo $l | awk -F'\t' '{print $3}')
|
||
|
|
set def (echo $l | awk -F'\t' '{print $4}')
|
||
|
|
printf " %2d | %-20s | %-22s | %-7s | %s\n" $i $name $prov $rec $def >&2
|
||
|
|
set i (math $i + 1)
|
||
|
|
end
|
||
|
|
set prefer (printf "%s\n" $lines | awk -F'\t' '$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 $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}'
|
||
|
|
end
|
||
|
|
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
|
||
|
|
|
||
|
|
_banner "Sui wallet bootstrap"
|
||
|
|
|
||
|
|
read -P "Namespace [crypto]: " ns
|
||
|
|
if test -z "$ns"; set ns crypto; end
|
||
|
|
set ns (_k8s_name $ns)
|
||
|
|
|
||
|
|
echo "Ensuring namespace '$ns' exists…"
|
||
|
|
if not kubectl get ns $ns >/dev/null 2>&1
|
||
|
|
kubectl create ns $ns; or return 1
|
||
|
|
end
|
||
|
|
|
||
|
|
kubectl get storageclass 2>/dev/null
|
||
|
|
echo "TIP: Prefer a Retain StorageClass so deleting the pod won't delete keys."
|
||
|
|
set sc (_choose_sc); or return 1
|
||
|
|
|
||
|
|
read -P "Wallet name (e.g. 'brad' → wallet-sui-brad): " wallet_raw
|
||
|
|
_require "Wallet name" $wallet_raw; or return 1
|
||
|
|
set base (_k8s_name $wallet_raw)
|
||
|
|
set app "wallet-sui-$base"
|
||
|
|
set pvc $app
|
||
|
|
|
||
|
|
read -P "Network [mainnet|testnet|devnet] [mainnet]: " net
|
||
|
|
if test -z "$net"; set net mainnet; end
|
||
|
|
switch $net
|
||
|
|
case mainnet
|
||
|
|
set rpc_url https://fullnode.mainnet.sui.io:443
|
||
|
|
case testnet
|
||
|
|
set rpc_url https://fullnode.testnet.sui.io:443
|
||
|
|
case devnet
|
||
|
|
set rpc_url https://fullnode.devnet.sui.io:443
|
||
|
|
case '*'
|
||
|
|
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
|
||
|
|
|
||
|
|
read -P "Address alias to create [main]: " alias
|
||
|
|
if test -z "$alias"; set alias main; end
|
||
|
|
|
||
|
|
read -P "Mnemonic word length [24]: " wl
|
||
|
|
if test -z "$wl"; set wl 24; end
|
||
|
|
|
||
|
|
read -P "Store mnemonic as a Kubernetes Secret? (NOT recommended) (y/N): " store_mn
|
||
|
|
set store_mn (string lower -- $store_mn)
|
||
|
|
|
||
|
|
_banner "Summary"
|
||
|
|
echo " Namespace: $ns"
|
||
|
|
echo " App name: $app"
|
||
|
|
echo " StorageClass: $sc"
|
||
|
|
echo " PVC: $pvc"
|
||
|
|
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
|
||
|
|
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
|
||
|
|
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
|
||
|
|
|
||
|
|
_banner "Waiting for rollout"
|
||
|
|
kubectl -n $ns rollout status deploy/$app --timeout=120s; or return 1
|
||
|
|
|
||
|
|
_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
|
||
|
|
|
||
|
|
set ADDR (kubectl -n $ns exec deploy/$app -- bash -lc \
|
||
|
|
(printf 'sui client addresses | awk "/%s/{print \$NF}" | tail -n1' $alias))
|
||
|
|
|
||
|
|
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\+//'
|
||
|
|
echo
|
||
|
|
echo "Address alias '$alias' appears to be: $ADDR"
|
||
|
|
rm -f $OUT
|
||
|
|
|
||
|
|
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 \
|
||
|
|
--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
|
||
|
|
else
|
||
|
|
echo (set_color red)"Warning:"(set_color normal)" could not capture mnemonic via --json, 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'
|
||
|
|
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"
|
||
|
|
end
|
||
|
|
|
||
|
|
### --------- utilities ----------
|
||
|
|
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'
|
||
|
|
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
|
||
|
|
end
|
||
|
|
|
||
|
|
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 -l app=$APP -o wide
|
||
|
|
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)."
|
||
|
|
end
|
||
|
|
|
||
|
|
function suiwallet_purge -a NS APP PVC
|
||
|
|
if test -z "$NS"; set NS crypto; end
|
||
|
|
if test -z "$APP"; set APP wallet-sui-main; end
|
||
|
|
if test -z "$PVC"; set PVC $APP; end
|
||
|
|
echo "Type to confirm: PURGE $NS $PVC"
|
||
|
|
read CONFIRM
|
||
|
|
if test "$CONFIRM" != "PURGE $NS $PVC"
|
||
|
|
echo "Aborted."; return 1
|
||
|
|
end
|
||
|
|
kubectl -n $NS delete deploy $APP --ignore-not-found
|
||
|
|
kubectl -n $NS delete pvc $PVC --ignore-not-found --wait=true
|
||
|
|
end
|