From 9d336dc0b7f9cd890c21a7a69e7bb6eb4817efbf Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Sun, 10 Aug 2025 20:40:22 -0500 Subject: [PATCH] added harbor --- .../flux-system/kustomization-harbor.yaml | 16 + infrastructure/flux-system/kustomization.yaml | 1 + scripts/monero_wallet_setup.fish | 1110 +++++++++++++++++ services/harbor/certificate.yaml | 12 + services/harbor/helmrelease.yaml | 69 + services/harbor/helmrepository.yaml | 8 + services/harbor/kustomization.yaml | 7 + services/harbor/namespace.yaml | 4 + 8 files changed, 1227 insertions(+) create mode 100644 infrastructure/flux-system/kustomization-harbor.yaml create mode 100644 scripts/monero_wallet_setup.fish create mode 100644 services/harbor/certificate.yaml create mode 100644 services/harbor/helmrelease.yaml create mode 100644 services/harbor/helmrepository.yaml create mode 100644 services/harbor/kustomization.yaml create mode 100644 services/harbor/namespace.yaml diff --git a/infrastructure/flux-system/kustomization-harbor.yaml b/infrastructure/flux-system/kustomization-harbor.yaml new file mode 100644 index 0000000..561065c --- /dev/null +++ b/infrastructure/flux-system/kustomization-harbor.yaml @@ -0,0 +1,16 @@ +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: harbor + namespace: flux-system +spec: + interval: 10m + path: ./services/harbor + targetNamespace: harbor + createNamespace: true + prune: true + sourceRef: + kind: GitRepository + name: flux-system + namespace: flux-system + wait: true \ No newline at end of file diff --git a/infrastructure/flux-system/kustomization.yaml b/infrastructure/flux-system/kustomization.yaml index 0cfd9fc..8aae868 100644 --- a/infrastructure/flux-system/kustomization.yaml +++ b/infrastructure/flux-system/kustomization.yaml @@ -4,3 +4,4 @@ resources: - gotk-components.yaml - gotk-sync.yaml - kustomization-gitea.yaml +- kustomization-harbor.yaml diff --git a/scripts/monero_wallet_setup.fish b/scripts/monero_wallet_setup.fish new file mode 100644 index 0000000..bedf35d --- /dev/null +++ b/scripts/monero_wallet_setup.fish @@ -0,0 +1,1110 @@ +### ------- helpers --------------------------------------------------------- + +function _need --description "ensure a command exists" + for c in $argv + if not type -q $c + echo (set_color red)"Error:"(set_color normal)" missing required command: $c" >&2 + return 1 + end + end +end + +function _banner --description "pretty section header" -a MSG + set_color cyan + echo + echo "==> $MSG" + set_color normal +end + +# 32-char alphanumeric generator (fast, no shell-specials) +function _rand_alnum32 + if type -q openssl + openssl rand -base64 48 2>/dev/null | tr -dc 'A-Za-z0-9' | head -c 32 + else + dd if=/dev/urandom bs=48 count=1 2>/dev/null | base64 | tr -dc 'A-Za-z0-9' | head -c 32 + end +end + +# Convert any string to a k8s-safe name (RFC-1123 label-ish) +function _k8s_name --description "sanitize to RFC-1123 label" -a S + set s (string lower -- $S) + set s (string replace -ar -- '[^a-z0-9-]' '-' $s) + set s (string replace -r -- '^-+' '' $s) + set s (string replace -r -- '-+$' '' $s) + echo $s +end + +# Simple non-empty check +function _require --description "require a non-empty value" -a WHAT VAL + if test -z "$VAL" + echo (set_color red)"Error:"(set_color normal)" $WHAT cannot be empty. Aborting." >&2 + return 1 + end +end + +# List SCs in a stable way +function _sc_list --description "list storageclasses as menu" + kubectl get storageclass -o json | jq -r ' + .items[] + | {name: .metadata.name, + provisioner: .provisioner, + reclaim: (.reclaimPolicy // "Delete"), + default: ( .metadata.annotations["storageclass.kubernetes.io/is-default-class"] == "true" + or .metadata.annotations["storageclass.beta.kubernetes.io/is-default-class"] == "true") } + | "\(.name)\t\(.provisioner)\t\(.reclaim)\t\((.default|tostring))" + ' 2>/dev/null +end + +# Choose SC (menu -> stderr; selection -> stdout) +function _choose_sc --description "interactive StorageClass picker" + set lines (_sc_list) + if test (count $lines) -eq 0 + echo (set_color red)"Error:"(set_color normal)" No StorageClasses found." >&2 + return 1 + end + + echo "" >&2 + echo "==> Available StorageClasses" >&2 + echo " # | name | provisioner | reclaim | default" >&2 + echo "----+----------------------+------------------------+---------+--------" >&2 + set i 1 + for l in $lines + set name (echo $l | awk -F'\t' '{print $1}') + set prov (echo $l | awk -F'\t' '{print $2}') + set rec (echo $l | awk -F'\t' '{print $3}') + set def (echo $l | awk -F'\t' '{print $4}') + printf " %2d | %-20s | %-22s | %-7s | %s\n" $i $name $prov $rec $def >&2 + set i (math $i + 1) + end + + set prefer (printf "%s\n" $lines | awk -F'\t' '$3=="Retain"{print $1}' | head -n1) + if test -z "$prefer" + set prefer (printf "%s\n" $lines | awk -F'\t' '$4=="true"{print $1}' | head -n1) + end + if test -z "$prefer" + set prefer (printf "%s\n" $lines | awk -F'\t' '{print $1}' | head -n1) + end + + read -P "Pick StorageClass by number or name (blank = $prefer): " choice + + if test -z "$choice" + echo "Using StorageClass: $prefer" >&2 + echo $prefer + return 0 + end + + if string match -qr '^[0-9]+$' -- $choice + set idx (math $choice) + if test $idx -lt 1 -o $idx -gt (count $lines) + echo (set_color red)"Error:"(set_color normal)" Choice out of range." >&2 + return 1 + end + set sel (printf "%s\n" $lines[$idx] | awk -F'\t' '{print $1}') + echo "Using StorageClass: $sel" >&2 + echo $sel + return 0 + end + + set found (printf "%s\n" $lines | awk -F'\t' -v want="$choice" '$1==want{print $1}') + if test -z "$found" + echo (set_color red)"Error:"(set_color normal)" No StorageClass named '$choice'." >&2 + return 1 + end + echo "Using StorageClass: $found" >&2 + echo $found +end + +# ---------- port-forward helpers (stderr logs) ---------- +function _pf_start --description "start port-forward in bg: sets global PF_PID" -a NS SVC LOCAL REMOTE + set -g PF_PID "" + if test -z "$LOCAL"; set LOCAL 18083; end + if test -z "$REMOTE"; set REMOTE 18083; end + echo "Starting port-forward: svc/$SVC $LOCAL:$REMOTE (ns=$NS)…" >&2 + kubectl -n $NS port-forward svc/$SVC $LOCAL:$REMOTE >/dev/null 2>&1 & + set -g PF_PID $last_pid + sleep 1 +end + +function _pf_stop --description "stop port-forward if running" + if test -n "$PF_PID" + echo "Stopping port-forward (pid $PF_PID)…" >&2 + kill $PF_PID 2>/dev/null; or true + set -e PF_PID + end +end + +# Minimal JSON-RPC caller for wallet RPC +function _rpc_call --description "call wallet JSON-RPC via localhost pf" -a RPCUSER RPCPASS METHOD PARAMS + set url "http://127.0.0.1:18083/json_rpc" + if test -z "$PARAMS" + set payload (printf '{"jsonrpc":"2.0","id":"0","method":"%s"}' $METHOD) + else + set payload (printf '{"jsonrpc":"2.0","id":"0","method":"%s","params":%s}' $METHOD "$PARAMS") + end + # Use --digest so we satisfy the server's challenge + # Note: callers should check for an empty response (curl --fail returns nothing). + curl -s --fail --digest -u "$RPCUSER:$RPCPASS" \ + -H "Content-Type: application/json" \ + -d "$payload" "$url" +end + +# Probe a monerod and assert it looks like Monero mainnet (not test/stage), not "qubic". +# Usage: _probe_monerod host:port +function _probe_monerod -a ADDR + if test -z "$ADDR" + echo (set_color red)"Error:"(set_color normal)" _probe_monerod needs host:port" >&2 + return 1 + end + + # Loud early block on suspicious names + if string match -qi '*qubic*' -- $ADDR + echo (set_color red)"Refusing daemon '$ADDR' (contains 'qubic'). This is NOT a Monero node."(set_color normal) >&2 + return 1 + end + + # Try /get_info (HTTP) first (most public nodes support this) + set raw (curl -s --max-time 7 "http://$ADDR/get_info" 2>/dev/null) + set ok 0 + if test -n "$raw" + set status (echo $raw | jq -r '.status // "OK"') + set testnet (echo $raw | jq -r '.testnet // false') + set stagenet (echo $raw | jq -r '.stagenet // false') + set height (echo $raw | jq -r '.height // .target_height // 0') + if test "$status" = "OK" + set ok 1 + end + end + + # Fallback to JSON-RPC get_info (some nodes only expose /json_rpc) + if test $ok -eq 0 + set raw (curl -s --max-time 7 -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","id":"0","method":"get_info"}' "http://$ADDR/json_rpc" \ + | jq -r '.result? | @json' 2>/dev/null) + if test -n "$raw" + set status (echo $raw | jq -r '.status // "OK"') + set testnet (echo $raw | jq -r '.testnet // false') + set stagenet (echo $raw | jq -r '.stagenet // false') + set height (echo $raw | jq -r '.height // .target_height // 0') + if test "$status" = "OK" + set ok 1 + end + end + end + + if test $ok -eq 0 + echo (set_color red)"Could not speak Monero /get_info to $ADDR. Not a monerod (or blocked). Aborting."(set_color normal) >&2 + echo "Tip: run your own monerod and use its ClusterIP:Port, or set WALLETSVC_SKIP_DAEMON_CHECK=1 to bypass." >&2 + return 1 + end + + if test "$testnet" = "true" -o "$stagenet" = "true" + echo (set_color red)"Refusing non-mainnet daemon (testnet=$testnet stagenet=$stagenet) at $ADDR."(set_color normal) >&2 + return 1 + end + + echo "Daemon OK: Monero mainnet, height=$height" +end + +### ------- main workflow --------------------------------------------------- + +function walletsvc_bootstrap --description "Interactive setup of monero-wallet-rpc (multi-wallet) with PVC + secrets" + _need kubectl jq curl awk; or return 1 + + _banner "Monero wallet RPC bootstrap" + + # --- Namespace + read -P "Namespace [monero]: " ns_raw + if test -z "$ns_raw"; set ns_raw monero; end + set ns (_k8s_name $ns_raw) + _require "Namespace" $ns; or return 1 + + echo "Ensuring namespace '$ns' exists…" + if kubectl get ns $ns >/dev/null 2>&1 + echo "Namespace $ns already exists." + else + kubectl create ns $ns; or return 1 + echo "Created namespace $ns." + end + + # --- StorageClass + kubectl get storageclass + echo "TIP: Prefer a class with reclaimPolicy=Retain so a PVC delete won't delete the PV." + set sc (_choose_sc); or return 1 + + # --- Names + sizes + read -P "PVC name (for wallet data) [wallet-data]: " pvc_name_raw + if test -z "$pvc_name_raw"; set pvc_name_raw wallet-data; end + set pvc_name (_k8s_name $pvc_name_raw) + _require "PVC name" $pvc_name; or return 1 + + read -P "PVC size [5Gi]: " pvc_size + if test -z "$pvc_size"; set pvc_size 5Gi; end + _require "PVC size" $pvc_size; or return 1 + + read -P "App label / Deployment name [wallet-rpc]: " app_raw + if test -z "$app_raw"; set app_raw wallet-rpc; end + set app_name (_k8s_name $app_raw) + _require "App/Deployment name" $app_name; or return 1 + + read -P "Service name (blank = same as app): " svc_raw + if test -z "$svc_raw" + set svc_name $app_name + else + set svc_name (_k8s_name $svc_raw) + end + _require "Service name" $svc_name; or return 1 + + # --- Node scheduling (worker-only by default) + read -P "Schedule only on worker nodes? (y/N) [y]: " pin_workers + 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 + + # --- Daemon + image + echo + echo "Monero node (daemon) the wallet will talk to." + echo " - Best: your own monerod (trusted). Example (same ns): monerod:18081" + echo " - Remote nodes work, but reduce privacy and add trust." + read -P "Daemon address (host:port) [REQUIRED]: " daemon_addr + _require "Daemon address" $daemon_addr; or return 1 + + if test -z "$WALLETSVC_SKIP_DAEMON_CHECK" + _banner "Probing daemon" + _probe_monerod $daemon_addr; or return 1 + else + echo "Skipping daemon probe due to WALLETSVC_SKIP_DAEMON_CHECK=1" + end + + # Public image that includes monero-wallet-rpc + read -P "Container image for wallet RPC [docker.io/sethsimmons/simple-monero-wallet-rpc:latest]: " image + if test -z "$image"; set image docker.io/sethsimmons/simple-monero-wallet-rpc:latest; end + _require "Container image" $image; or return 1 + + # --- RPC + wallet credentials + read -P "RPC username [walletrpc]: " rpc_user + if test -z "$rpc_user"; set rpc_user walletrpc; end + _require "RPC username" $rpc_user; or return 1 + + read -s -P "RPC password [auto-generate 32 alnum if blank]: " rpc_pass + echo + if test -z "$rpc_pass"; set rpc_pass (_rand_alnum32); end + + read -P "Wallet filename (no path) [main]: " wallet_file_raw + if test -z "$wallet_file_raw"; set wallet_file_raw main; end + set wallet_file (_k8s_name $wallet_file_raw) + _require "Wallet filename" $wallet_file; or return 1 + + read -s -P "Wallet password [auto-generate 32 alnum if blank]: " wallet_pass + echo + if test -z "$wallet_pass"; set wallet_pass (_rand_alnum32); end + + _banner "Summary" + echo " Namespace: $ns" + echo " StorageClass: $sc" + echo " PVC: $pvc_name ($pvc_size)" + echo " App/Deployment: $app_name" + echo " Service: $svc_name" + echo " Daemon address: $daemon_addr" + echo " Image: $image" + echo " RPC user: $rpc_user" + echo " Wallet file: /data/$wallet_file" + if test $use_pin -eq 1 + echo " NodeSelector: $node_label_key=$node_label_val" + end + read -P "Proceed? [y/N]: " proceed + if not string match -qi 'y*' -- $proceed + echo (set_color yellow)"Aborted by user at confirmation step."(set_color normal) + return 1 + end + + set rpc_secret_name "$app_name-rpc-auth" + set wpass_secret_name "$app_name-wallet-pass" + + _banner "Applying secrets" + kubectl -n $ns create secret generic $rpc_secret_name \ + --from-literal=username="$rpc_user" \ + --from-literal=password="$rpc_pass" \ + --dry-run=client -o yaml | kubectl -n $ns apply -f -; or return 1 + + kubectl -n $ns create secret generic $wpass_secret_name \ + --from-literal=password="$wallet_pass" \ + --dry-run=client -o yaml | kubectl -n $ns apply -f -; or return 1 + + # Annotate with timestamp so pod restarts when only secrets change + set ts (date -Is) + kubectl -n $ns annotate secret $rpc_secret_name walletsvc.titan/update-ts="$ts" --overwrite >/dev/null 2>&1 + kubectl -n $ns annotate secret $wpass_secret_name walletsvc.titan/update-ts="$ts" --overwrite >/dev/null 2>&1 + + _banner "Applying PVC" + set pvcfile (mktemp -t {$pvc_name}-pvc.XXXX.yaml) + begin + echo "apiVersion: v1" + echo "kind: PersistentVolumeClaim" + echo "metadata:" + echo " name: $pvc_name" + echo " namespace: $ns" + echo "spec:" + echo " accessModes: [\"ReadWriteOnce\"]" + echo " storageClassName: $sc" + echo " resources:" + echo " requests:" + echo " storage: $pvc_size" + end > $pvcfile + echo "--- PVC manifest ($pvcfile) ---" + cat $pvcfile + kubectl apply -f $pvcfile; or return 1 + + _banner "Applying Deployment" + set dply (mktemp -t {$app_name}-deploy.XXXX.yaml) + begin + echo "apiVersion: apps/v1" + echo "kind: Deployment" + echo "metadata:" + echo " name: $app_name" + echo " namespace: $ns" + echo " labels: { app: $app_name }" + echo "spec:" + echo " replicas: 1" + echo " strategy:" + echo " type: Recreate" + echo " selector:" + echo " matchLabels: { app: $app_name }" + echo " template:" + echo " metadata:" + echo " labels: { app: $app_name }" + echo " spec:" + if test $use_pin -eq 1 + echo " nodeSelector:" + echo " $node_label_key: \"$node_label_val\"" + end + echo " securityContext:" + echo " fsGroup: 1000" + echo " fsGroupChangePolicy: OnRootMismatch" + echo " initContainers:" + echo " - name: volume-permissions" + echo " image: busybox:1.36" + echo " command: [\"/bin/sh\",\"-lc\",\"chown :1000 /data && chmod 0770 /data\"]" + echo " securityContext:" + echo " runAsUser: 0" + echo " volumeMounts:" + echo " - name: data" + echo " mountPath: /data" + echo " containers:" + echo " - name: wallet-rpc" + echo " image: $image" + echo " imagePullPolicy: IfNotPresent" + echo " command: [\"/bin/sh\",\"-lc\"]" + echo " args:" + echo " - |" + echo " RPCU=\$(cat /run/monero-secrets/rpc-user);" + echo " RPCP=\$(cat /run/monero-secrets/rpc-pass);" + echo " exec monero-wallet-rpc \\" + echo " --wallet-dir /data \\" + echo " --daemon-address $daemon_addr \\" + echo " --rpc-bind-ip 0.0.0.0 --rpc-bind-port 18083 \\" + echo " --rpc-login \"\${RPCU}:\${RPCP}\" \\" + echo " --confirm-external-bind" + echo " ports:" + echo " - containerPort: 18083" + echo " volumeMounts:" + echo " - name: data" + echo " mountPath: /data" + echo " - name: rpc-auth" + echo " mountPath: /run/monero-secrets" + echo " readOnly: true" + echo " resources:" + echo " requests: { cpu: \"100m\", memory: \"128Mi\" }" + echo " limits: { cpu: \"1\", memory: \"512Mi\" }" + echo " volumes:" + echo " - name: data" + echo " persistentVolumeClaim: { claimName: $pvc_name }" + echo " - name: rpc-auth" + echo " secret:" + echo " secretName: $rpc_secret_name" + echo " items:" + echo " - key: username" + echo " path: rpc-user" + echo " - key: password" + echo " path: rpc-pass" + end > $dply + echo "--- Deployment manifest ($dply) ---" + head -n 70 $dply + echo "..." + kubectl apply -f $dply; or return 1 + + _banner "Applying Service" + set svc (mktemp -t {$app_name}-svc.XXXX.yaml) + begin + echo "apiVersion: v1" + echo "kind: Service" + echo "metadata:" + echo " name: $svc_name" + echo " namespace: $ns" + echo " labels: { app: $app_name }" + echo "spec:" + echo " type: ClusterIP" + echo " selector: { app: $app_name }" + echo " ports:" + echo " - name: rpc" + echo " port: 18083" + echo " targetPort: 18083" + end > $svc + echo "--- Service manifest ($svc) ---" + cat $svc + kubectl apply -f $svc; or return 1 + + _banner "Applying NetworkPolicy (same-namespace-only)" + set np (mktemp -t {$app_name}-netpol.XXXX.yaml) + begin + echo "apiVersion: networking.k8s.io/v1" + echo "kind: NetworkPolicy" + echo "metadata:" + printf " name: %s-ingress\n" $app_name + echo " namespace: $ns" + echo "spec:" + echo " podSelector: { matchLabels: { app: $app_name } }" + echo " policyTypes: [\"Ingress\"]" + echo " ingress:" + echo " - from:" + echo " - namespaceSelector:" + echo " matchLabels:" + echo " kubernetes.io/metadata.name: $ns" + echo " ports:" + echo " - port: 18083" + echo " protocol: TCP" + end > $np + echo "--- NetworkPolicy manifest ($np) ---" + cat $np + kubectl apply -f $np; or return 1 + + # If Deployment existed and only secrets changed, force a restart so new creds are read + _banner "Ensuring pod picks up latest secrets" + kubectl -n $ns rollout restart deploy/$app_name >/dev/null 2>&1; or true + + _banner "Waiting for rollout" + if not kubectl -n $ns rollout status deploy/$app_name --timeout=180s + echo (set_color red)"Rollout did not finish in time. Recent events:"(set_color normal) >&2 + kubectl -n $ns get events --sort-by=.lastTimestamp | tail -n 40 + echo (set_color yellow)"Hint:"(set_color normal)" if you see Multi-Attach for the PVC, delete any older pod (e.g. 'kubectl -n $ns delete pod ') and the new one will attach." + return 1 + end + + _banner "Creating/opening wallet via JSON-RPC" + + # sanity: verify /data is writable before we try to create anything + if not walletsvc_check_write $ns $app_name >/dev/null + echo (set_color red)"PVC is not writable; aborting."(set_color normal) + return 1 + end + + # create (idempotent) then open + if not walletsvc_create_wallet $ns $svc_name $wallet_file $wallet_pass + echo (set_color red)"Failed to create wallet $wallet_file. See pod logs."(set_color normal) + return 1 + end + + if not walletsvc_open $ns $svc_name $wallet_file $wallet_pass + echo (set_color red)"Failed to open wallet $wallet_file. See pod logs."(set_color normal) + return 1 + end + + _banner "Done" + echo "Namespace: $ns" + echo "App/Deployment: $app_name" + echo "Service: $svc_name (ClusterIP)" + echo "PVC: $pvc_name via StorageClass '$sc'" + echo "RPC auth: user='$rpc_user'" + echo "RPC pass: $rpc_pass" + echo "Wallet pass: $wallet_pass" + echo "Wallet file: /data/$wallet_file" + echo + echo "Access locally: walletsvc_portforward $ns $svc_name" + echo "Show seed (if enabled by image): walletsvc_show_seed $ns $svc_name" +end + + +### ------- utilities ------------------------------------------------------- + +function walletsvc_portforward --description "port-forward RPC to localhost:18083" -a NS SVC + if test -z "$NS"; set NS monero; end + if test -z "$SVC"; set SVC wallet-rpc; end + echo "Forwarding $NS/$SVC → http://127.0.0.1:18083 (Ctrl+C to stop)…" + kubectl -n $NS port-forward svc/$SVC 18083:18083 +end + +function walletsvc_status --description "show pod/service status" -a NS APP + if test -z "$NS"; set NS monero; end + if test -z "$APP"; set APP wallet-rpc; end + kubectl -n $NS get deploy,po,svc,netpol -l app=$APP -o wide +end + +function walletsvc_stop --description "stop the RPC but keep PVC + secrets" -a NS APP SVC + if test -z "$NS"; set NS monero; end + if test -z "$APP"; set APP wallet-rpc; end + if test -z "$SVC"; set SVC $APP; end + kubectl -n $NS delete deploy $APP --ignore-not-found + kubectl -n $NS delete svc $SVC --ignore-not-found + echo "Stopped $NS/$APP. PVC and secrets retained." +end + +function walletsvc_purge --description "IRREVERSIBLY delete PVC + secrets (keys on disk!)" -a NS APP PVC SVC + if test -z "$NS"; set NS monero; end + if test -z "$APP"; set APP wallet-rpc; end + if test -z "$PVC"; set PVC wallet-data; end + if test -z "$SVC"; set SVC $APP; end + echo "Type to confirm: PURGE $NS $PVC" + read CONFIRM + if test "$CONFIRM" != "PURGE $NS $PVC" + echo "Aborted." + return 1 + end + kubectl -n $NS delete deploy $APP --ignore-not-found + kubectl -n $NS delete svc $SVC --ignore-not-found + kubectl -n $NS delete pvc $PVC --ignore-not-found --wait=true + kubectl -n $NS delete secret $APP-wallet-pass --ignore-not-found + kubectl -n $NS delete secret $APP-rpc-auth --ignore-not-found + echo "Purged. Ensure you have the mnemonic backed up if you ever need to restore." +end + +function walletsvc_show_seed --description "Try to fetch mnemonic via RPC query_key (wallet must be open)" -a NS SVC RPCUSER RPCPASS + _need jq curl; or return 1 + if test -z "$NS"; set NS monero; end + if test -z "$SVC"; set SVC wallet-rpc; end + if test -z "$RPCUSER" + set RPCUSER (kubectl -n $NS get secret {$SVC}-rpc-auth -o jsonpath='{.data.username}' | base64 -d) + end + if test -z "$RPCPASS" + set RPCPASS (kubectl -n $NS get secret {$SVC}-rpc-auth -o jsonpath='{.data.password}' | base64 -d) + end + _pf_start $NS $SVC 18083 18083 + set res (_rpc_call $RPCUSER $RPCPASS query_key '{"key_type":"mnemonic"}') + _pf_stop + set err (echo $res | jq -r '.error.message // empty') + if test -n "$err" + echo "query_key error: $err" + echo "Some images disable returning mnemonics via RPC. Consider backing up offline." + return 1 + end + echo "Mnemonic seed:" + echo $res | jq -r '.result.key' +end + +# ---------- core JSON-RPC wrapper (raw JSON to stdout) ---------- +function walletsvc_rpc_call --description "RPC call with secrets (auto port-forward)" -a NS SVC METHOD PARAMS_JSON + if test -z "$NS"; set NS monero; end + if test -z "$SVC"; set SVC wallet-rpc; end + if test -z "$METHOD" + echo "Usage: walletsvc_rpc_call [ns] [svc] [params_json]" >&2 + return 1 + end + + set RPCUSER (kubectl -n $NS get secret {$SVC}-rpc-auth -o jsonpath='{.data.username}' | base64 -d) + set RPCPASS (kubectl -n $NS get secret {$SVC}-rpc-auth -o jsonpath='{.data.password}' | base64 -d) + + _pf_start $NS $SVC 18083 18083 + if test -n "$PARAMS_JSON" + set payload (printf '{"jsonrpc":"2.0","id":"0","method":"%s","params":%s}' $METHOD "$PARAMS_JSON") + else + set payload (printf '{"jsonrpc":"2.0","id":"0","method":"%s"}' $METHOD) + end + + # capture HTTP code and body without using --fail so we always get the body + set tmp (mktemp) + set code (curl -s --digest -u "$RPCUSER:$RPCPASS" \ + -H 'Content-Type: application/json' \ + -o $tmp -w "%{http_code}" \ + -d "$payload" "http://127.0.0.1:18083/json_rpc") + set rc $status + _pf_stop + + set body (cat $tmp); rm -f $tmp + + if test $rc -ne 0 + echo "RPC transport error ($METHOD): curl exit $rc" >&2 + echo $body + return $rc + end + + if test "$code" != "200" + echo "RPC HTTP $code ($METHOD)" >&2 + echo $body + return 1 + end + + echo $body + return 0 +end + +function walletsvc_rpc_test --description "get_version using secrets" -a NS SVC + walletsvc_rpc_call $NS $SVC get_version | jq . +end + +# Rotate RPC username/password (updates Secret & restarts Deploy) +function walletsvc_set_rpc_credentials --description "rotate RPC Basic creds" -a NS APP + _need kubectl; or return 1 + if test -z "$NS"; set NS monero; end + if test -z "$APP"; set APP wallet-rpc; end + set SNAME {$APP}-rpc-auth + + read -P "New RPC username [leave blank to keep current]: " NEWUSER + if test -z "$NEWUSER" + set NEWUSER (kubectl -n $NS get secret $SNAME -o jsonpath='{.data.username}' | base64 -d) + end + read -s -P "New RPC password [blank = auto-generate 32 alnum]: " NEWPASS + echo + if test -z "$NEWPASS"; set NEWPASS (_rand_alnum32); end + + echo "Updating secret $NS/$SNAME and restarting $APP…" + kubectl -n $NS create secret generic $SNAME \ + --from-literal=username="$NEWUSER" \ + --from-literal=password="$NEWPASS" \ + --dry-run=client -o yaml | kubectl -n $NS apply -f -; or return 1 + kubectl -n $NS annotate secret $SNAME walletsvc.titan/update-ts=(date -Is) --overwrite >/dev/null 2>&1 + kubectl -n $NS rollout restart deploy/$APP + kubectl -n $NS rollout status deploy/$APP --timeout=180s; or return 1 + + echo "New RPC creds:" + echo " user: $NEWUSER" + echo " pass: $NEWPASS" +end + +# Change wallet file password via RPC +function walletsvc_change_wallet_password --description "change password of a wallet file via RPC" -a NS SVC + if test -z "$NS"; set NS monero; end + if test -z "$SVC"; set SVC wallet-rpc; end + + read -P "Wallet filename (under /data): " WFILE + if test -z "$WFILE" + echo "Wallet filename required." >&2 + return 1 + end + read -s -P "Current wallet password: " OLD + echo + read -s -P "New wallet password [blank = auto-generate 32 alnum]: " NEW + echo + if test -z "$NEW"; set NEW (_rand_alnum32); end + + # open the wallet (in case it's not already) + set res (walletsvc_rpc_call $NS $SVC open_wallet (printf '{"filename":"%s","password":"%s"}' $WFILE $OLD)) + # ignore error here; we try change anyway so user can see the RPC error if wrong + + # change password + walletsvc_rpc_call $NS $SVC change_wallet_password (printf '{"old_password":"%s","new_password":"%s"}' $OLD $NEW) + echo "New wallet password: $NEW" +end + +# --- logs from the deployment (quick tail) --- +function walletsvc_logs -a NS APP LINES + if test -z "$NS"; set NS monero; end + if test -z "$APP"; set APP wallet-rpc; end + if test -z "$LINES"; set LINES 200; end + kubectl -n $NS logs deploy/$APP --tail=$LINES +end + +# --- list files in /data inside the pod --- +function walletsvc_wallet_ls -a NS APP + if test -z "$NS"; set NS monero; end + if test -z "$APP"; set APP wallet-rpc; end + echo "Listing /data in $NS/$APP…" >&2 + kubectl -n $NS exec deploy/$APP -- sh -lc 'ls -la /data || true' +end + +# --- create a wallet (uses Secret password if PASS omitted) --- +function walletsvc_create_wallet -a NS SVC FILE PASS LANG + if test -z "$NS"; set NS monero; end + if test -z "$SVC"; set SVC wallet-rpc; end + if test -z "$FILE" + echo "Usage: walletsvc_create_wallet [ns] [svc] [pass] [language]" >&2 + return 1 + end + if test -z "$PASS" + set PASS (kubectl -n $NS get secret {$SVC}-wallet-pass -o jsonpath='{.data.password}' | base64 -d 2>/dev/null) + end + if test -z "$LANG"; set LANG English; end + + set payload (printf '{"filename":"%s","password":"%s","language":"%s"}' $FILE $PASS $LANG) + set res (walletsvc_rpc_call $NS $SVC create_wallet $payload) + set rc $status + if test $rc -ne 0 + echo (set_color red)"create_wallet RPC transport failed (rc=$rc)."(set_color normal) >&2 + return $rc + end + + set err (echo $res | jq -r '.error.message // empty') + if test -n "$err" + if string match -q "*already exists*" -- $err + echo "Wallet $FILE already exists." + return 0 + end + echo "create_wallet error: $err" >&2 + return 1 + end + + echo "Created wallet: $FILE" +end + +# --- improved opener: closes first, checks files, hints if password mismatch --- +function walletsvc_open -a NS SVC FILE PASS + if test -z "$NS"; set NS monero; end + if test -z "$SVC"; set SVC wallet-rpc; end + if test -z "$FILE" + echo "Usage: walletsvc_open [ns] [svc] [password]" >&2 + return 1 + end + if test -z "$PASS" + set PASS (kubectl -n $NS get secret {$SVC}-wallet-pass -o jsonpath='{.data.password}' | base64 -d 2>/dev/null) + end + if test -z "$PASS" + read -s -P "Wallet password for $FILE: " PASS; echo + end + + walletsvc_rpc_call $NS $SVC close_wallet >/dev/null 2>&1; or true + + set res (walletsvc_rpc_call $NS $SVC open_wallet (printf '{"filename":"%s","password":"%s"}' $FILE $PASS)) + set rc $status + if test $rc -ne 0 + echo (set_color red)"open_wallet RPC transport failed (rc=$rc)."(set_color normal) >&2 + return $rc + end + + set exists (kubectl -n $NS exec deploy/$SVC -- sh -lc (printf 'test -e /data/%s -o -e /data/%s.keys && echo yes || echo no' $FILE $FILE) 2>/dev/null) + if test "$exists" = "no" + echo "No wallet files at /data/$FILE{,.keys}. If this should be a new wallet, run:" >&2 + echo " walletsvc_create_wallet $NS $SVC $FILE" >&2 + return 1 + end + + set tmp (mktemp) + walletsvc_rpc_call $NS $SVC open_wallet (printf '{"filename":"%s","password":"%s"}' $FILE $PASS) > $tmp + set rc $status + set res (cat $tmp); rm -f $tmp + + if test $rc -ne 0 + echo "open_wallet transport/HTTP error (rc=$rc)" >&2 + echo $res | jq . >&2 + return $rc + end + + set err (echo $res | jq -r '.error.message // empty' 2>/dev/null) + if test -n "$err" + echo "open_wallet error: $err" >&2 + if string match -q "*password*" -- (string lower -- $err) + echo "Hint: Secret {$SVC}-wallet-pass may not match the files on disk. Try supplying the pass explicitly." >&2 + end + return 1 + end + + echo $res | jq . +end + +function walletsvc_check_write -a NS APP + if test -z "$NS"; set NS monero; end + if test -z "$APP"; set APP wallet-rpc; end + kubectl -n $NS exec deploy/$APP -- sh -lc 'echo test-$(date +%s) > /data/.write-test && ls -l /data/.write-test && rm -f /data/.write-test' +end + +# --- ensure wallet is open (create if missing) --- +function walletsvc_ensure_open -a NS SVC FILE + if test -z "$NS"; set NS monero; end + if test -z "$SVC"; set SVC wallet-rpc; end + if test -z "$FILE"; set FILE main; end + + # If files missing, create using secret password + set exists (kubectl -n $NS exec deploy/$SVC -- sh -lc (printf 'if [ -e "/data/%s" ] || [ -e "/data/%s.keys" ]; then echo yes; else echo no; fi' $FILE $FILE) 2>/dev/null) + if test "$exists" = "no" + echo "No /data/$FILE found. Creating…" >&2 + walletsvc_create_wallet $NS $SVC $FILE; or return 1 + end + + # Open with secret password + walletsvc_open $NS $SVC $FILE; or return 1 +end + +# ---------- friendly overview ---------- +function walletsvc_overview -a NS SVC ACCOUNT + if test -z "$NS"; set NS monero; end + if test -z "$SVC"; set SVC wallet-rpc; end + if test -z "$ACCOUNT"; set ACCOUNT 0; end + + set RPCUSER (kubectl -n $NS get secret {$SVC}-rpc-auth -o jsonpath='{.data.username}' | base64 -d) + set RPCPASS (kubectl -n $NS get secret {$SVC}-rpc-auth -o jsonpath='{.data.password}' | base64 -d) + + _pf_start $NS $SVC 18083 18083 + echo "== Wallet RPC overview (ns=$NS svc=$SVC acct=$ACCOUNT)" + + # Version + set resp (curl -s --fail --digest -u "$RPCUSER:$RPCPASS" \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","id":"0","method":"get_version"}' \ + http://127.0.0.1:18083/json_rpc) + echo $resp | jq -r '"-- Version:\n version=\(.result.version) release=\(.result.release)"' + + # Height (show error clearly if wallet not open) + set resp (curl -s --fail --digest -u "$RPCUSER:$RPCPASS" \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","id":"0","method":"get_height"}' \ + http://127.0.0.1:18083/json_rpc) + set err (echo $resp | jq -r '.error.message // empty') + if test -n "$err" + echo "-- Height:" + echo " ERROR: $err" + echo " (Tip: open your wallet: walletsvc_open $NS $SVC main)" + else + echo $resp | jq -r '"-- Height:\n wallet_height=\(.result.height)"' + end + + # Accounts + set resp (curl -s --fail --digest -u "$RPCUSER:$RPCPASS" \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","id":"0","method":"get_accounts"}' \ + http://127.0.0.1:18083/json_rpc) + set err (echo $resp | jq -r '.error.message // empty') + echo "-- Accounts:" + if test -n "$err" + echo " ERROR: $err" + else + echo $resp | jq -r '.result.subaddress_accounts[] + | " [\(.account_index)] \(.label) total=\(.balance/1000000000000) XMR unlocked=\(.unlocked_balance/1000000000000) XMR"' + end + + # Balance for selected account + set BODY (printf '{"account_index":%s}' $ACCOUNT) + set resp (curl -s --fail --digest -u "$RPCUSER:$RPCPASS" \ + -H 'Content-Type: application/json' \ + -d (printf '{"jsonrpc":"2.0","id":"0","method":"get_balance","params":%s}' "$BODY") \ + http://127.0.0.1:18083/json_rpc) + set err (echo $resp | jq -r '.error.message // empty') + echo "-- Account balance:" + if test -n "$err" + echo " ERROR: $err" + else + echo $resp | jq -r '" balance=\(.result.balance/1000000000000) XMR unlocked=\(.result.unlocked_balance/1000000000000) XMR"' + end + + # Primary address + set resp (curl -s --fail --digest -u "$RPCUSER:$RPCPASS" \ + -H 'Content-Type: application/json' \ + -d (printf '{"jsonrpc":"2.0","id":"0","method":"get_address","params":{"account_index":%s}}' $ACCOUNT) \ + http://127.0.0.1:18083/json_rpc) + set err (echo $resp | jq -r '.error.message // empty') + echo "-- Primary address (account base):" + if test -n "$err" + echo " ERROR: $err" + else + echo $resp | jq -r '.result.address' + end + + # First 5 subaddresses + echo "-- First 5 subaddresses:" + if test -n "$err" + echo " (skipped due to error above)" + else + echo $resp | jq -r '.result.addresses[:5][] | " [\(.address_index)] \(.address) label=\(.label) used=\(.used)"' + end + + # Recent transfers + set BODY (printf '{"account_index":%s,"in":true}' $ACCOUNT) + set in (curl -s --fail --digest -u "$RPCUSER:$RPCPASS" \ + -H 'Content-Type: application/json' \ + -d (printf '{"jsonrpc":"2.0","id":"0","method":"get_transfers","params":%s}' "$BODY") \ + http://127.0.0.1:18083/json_rpc) + echo "-- Recent transfers (incoming, last 5):" + echo $in | jq -r '.result.in // [] | (.[-5:] // [])[] | " + \(.amount/1000000000000) XMR conf=\(.confirmations) tx=\(.txid)"' + + set BODY (printf '{"account_index":%s,"out":true}' $ACCOUNT) + set out (curl -s --fail --digest -u "$RPCUSER:$RPCPASS" \ + -H 'Content-Type: application/json' \ + -d (printf '{"jsonrpc":"2.0","id":"0","method":"get_transfers","params":%s}' "$BODY") \ + http://127.0.0.1:18083/json_rpc) + echo "-- Recent transfers (outgoing, last 5):" + echo $out | jq -r '.result.out // [] | (.[-5:] // [])[] | " - \(.amount/1000000000000) XMR conf=\(.confirmations) tx=\(.txid)"' + + set BODY (printf '{"account_index":%s,"pool":true}' $ACCOUNT) + set pool (curl -s --fail --digest -u "$RPCUSER:$RPCPASS" \ + -H 'Content-Type: application/json' \ + -d (printf '{"jsonrpc":"2.0","id":"0","method":"get_transfers","params":%s}' "$BODY") \ + http://127.0.0.1:18083/json_rpc) + echo "-- Mempool (unconfirmed incoming):" + echo $pool | jq -r '.result.pool // [] | .[] | " ~ \(.amount/1000000000000) XMR tx=\(.txid)"' + + _pf_stop +end + +# ---------- list addresses ---------- +function walletsvc_list_addresses -a NS SVC ACCOUNT + if test -z "$NS"; set NS monero; end + if test -z "$SVC"; set SVC wallet-rpc; end + if test -z "$ACCOUNT"; set ACCOUNT 0; end + set RPCUSER (kubectl -n $NS get secret {$SVC}-rpc-auth -o jsonpath='{.data.username}' | base64 -d) + set RPCPASS (kubectl -n $NS get secret {$SVC}-rpc-auth -o jsonpath='{.data.password}' | base64 -d) + _pf_start $NS $SVC 18083 18083 + set BODY (printf '{"account_index":%s}' $ACCOUNT) + set resp (curl -s --fail --digest -u "$RPCUSER:$RPCPASS" \ + -H 'Content-Type: application/json' \ + -d (printf '{"jsonrpc":"2.0","id":"0","method":"get_address","params":%s}' "$BODY") \ + http://127.0.0.1:18083/json_rpc) + _pf_stop + set err (echo $resp | jq -r '.error.message // empty') + if test -n "$err" + echo "ERROR: $err" >&2 + return 1 + end + echo $resp | jq -r '.result.addresses[] | "[\(.address_index)] \(.address) label=\(.label) used=\(.used)"' +end + +# ---------- new subaddress ---------- +function walletsvc_new_address -a NS SVC LABEL ACCOUNT + if test -z "$NS"; set NS monero; end + if test -z "$SVC"; set SVC wallet-rpc; end + if test -z "$ACCOUNT"; set ACCOUNT 0; end + if test -z "$LABEL"; set LABEL (printf "deposit-%s" (date +%Y%m%d-%H%M%S)); end + set RPCUSER (kubectl -n $NS get secret {$SVC}-rpc-auth -o jsonpath='{.data.username}' | base64 -d) + set RPCPASS (kubectl -n $NS get secret {$SVC}-rpc-auth -o jsonpath='{.data.password}' | base64 -d) + _pf_start $NS $SVC 18083 18083 + set BODY (printf '{"account_index":%s,"label":"%s"}' $ACCOUNT $LABEL) + set resp (curl -s --fail --digest -u "$RPCUSER:$RPCPASS" \ + -H 'Content-Type: application/json' \ + -d (printf '{"jsonrpc":"2.0","id":"0","method":"create_address","params":%s}' "$BODY") \ + http://127.0.0.1:18083/json_rpc) + _pf_stop + set err (echo $resp | jq -r '.error.message // empty') + if test -n "$err" + echo "ERROR: $err" >&2 + return 1 + end + echo $resp | jq -r --arg L "$LABEL" '"New address: \(.result.address) [index=\(.result.address_index)] label=" + $L' +end + +# Optional: non-sensitive cluster snapshot to help tune manifests. +function cluster_probe + set OUT cluster-snapshot.txt + echo "# Cluster snapshot "(date -Is) > $OUT + echo -e "\n## kubectl version" >> $OUT + kubectl version --short >> $OUT 2>&1 + + echo -e "\n## Nodes" >> $OUT + kubectl get nodes -o wide >> $OUT + echo -e "\n### Node resources" >> $OUT + kubectl describe nodes | egrep -i 'Name:|Roles:|Capacity:|Allocatable:|cpu|memory|ephemeral' >> $OUT + + echo -e "\n## Namespaces" >> $OUT + kubectl get ns >> $OUT + + echo -e "\n## StorageClasses (summary)" >> $OUT + kubectl get storageclass >> $OUT + echo -e "\n## StorageClasses (yaml)" >> $OUT + kubectl get storageclass -o yaml >> $OUT + + echo -e "\n## PV/PVC" >> $OUT + kubectl get pv >> $OUT 2>&1 + kubectl get pvc -A >> $OUT 2>&1 + + echo -e "\n## Ingress / Services" >> $OUT + kubectl get ingressclass >> $OUT 2>&1 + kubectl get ingress -A >> $OUT 2>&1 + kubectl get svc -A | egrep -i 'NAMESPACE|LoadBalancer|EXTERNAL-IP|traefik|ingress' >> $OUT + + echo -e "\n## kube-system pods (CNI hint)" >> $OUT + kubectl -n kube-system get pods -o wide >> $OUT 2>&1 + + echo -e "\n## Default limits/quotas" >> $OUT + kubectl -n default get limitrange,resourcequota >> $OUT 2>&1 + + echo -e "\n## Tools present on your machine" >> $OUT + for t in kubectl helm jq curl awk envsubst fish + if type -q $t + echo "$t: OK" >> $OUT + else + echo "$t: MISSING" >> $OUT + end + end + + echo "Wrote $OUT — paste the contents here." +end + +# --- one-line command reference (keep in sync) --- +function walletsvc_help + echo "walletsvc_bootstrap # interactive deploy + secrets + PVC + first wallet (multiwallet RPC)" + echo "walletsvc_status [ns] [app] # show deploy, pods, service, netpol" + echo "walletsvc_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 password of a wallet file via RPC" + echo "walletsvc_stop [ns] [app] [svc] # delete deployment+service only" + echo "walletsvc_purge [ns] [app] [pvc] [svc] # delete deploy+svc+PVC+secrets (danger!)" +end + +function walletsvc_help_detailed + echo "Commands (defaults: ns=monero, svc/app=wallet-rpc)" + echo + echo "walletsvc_bootstrap" + echo " Interactive deploy: creates namespace, secrets, PVC, Deployment (fsGroup + init perms), Service, NetPolicy." + echo " Prompts you for a *Monero* daemon (monerod) with NO default; probes it via get_info to ensure mainnet Monero." + echo " Set WALLETSVC_SKIP_DAEMON_CHECK=1 to bypass the probe (not recommended)." + echo + echo "walletsvc_status [ns] [app]" + echo " Show Deployment/Pod/Service/NetPolicy for the app label." + echo + echo "walletsvc_portforward [ns] [svc]" + echo " Port-forward RPC to http://127.0.0.1:18083 (Ctrl+C to stop)." + echo + echo "walletsvc_logs [ns] [app] [N]" + echo " Tail N lines (default 200) from the wallet RPC pod logs." + echo + echo "walletsvc_wallet_ls [ns] [app]" + echo " List /data contents inside the pod (quick sanity for wallet files)." + echo + echo "walletsvc_check_write [ns] [app]" + echo " Touch/remove a temp file under /data to prove the PVC is writable." + echo + echo "walletsvc_rpc_test [ns] [svc]" + echo " JSON-RPC get_version (sanity)." + echo + echo "walletsvc_rpc_call [ns] [svc] [params_json]" + echo " Raw JSON-RPC with digest auth; prints body; non-200 returns non-zero." + echo + echo "walletsvc_create_wallet [ns] [svc] [pass] [language]" + echo " Create /data/{,.keys}. Pass defaults to Secret -wallet-pass. Language defaults to English." + echo + echo "walletsvc_open [ns] [svc] [pass]" + echo " Close any open wallet, verify files exist, then open with the given (or Secret) password." + echo + echo "walletsvc_ensure_open [ns] [svc] [file]" + echo " If files missing, create with Secret pass, then open." + echo + echo "walletsvc_overview [ns] [svc] [acct=0]" + echo " Pretty view: version, height, accounts, balances, addresses, recent transfers." + echo + echo "walletsvc_list_addresses [ns] [svc] [acct=0]" + echo " List all subaddresses for the account." + echo + echo "walletsvc_new_address [ns] [svc] [label] [acct=0]" + echo " Create a new subaddress with optional label (default: timestamped)." + echo + echo "walletsvc_set_rpc_credentials [ns] [app]" + echo " Rotate RPC basic auth (updates Secret and restarts Deployment)." + echo + echo "walletsvc_change_wallet_password [ns] [svc]" + echo " Change wallet file password via RPC." + echo + echo "Notes:" + echo " - Your wallet must talk to a Monero daemon (monerod). p2pool is a mining pool, not a daemon." + echo " - For the most trust-minimized setup, run your own monerod and point the wallet at it." + echo " - This script blocks suspicious daemon addresses (e.g. containing 'qubic') and probes that the daemon" + echo " speaks Monero mainnet before proceeding." +end diff --git a/services/harbor/certificate.yaml b/services/harbor/certificate.yaml new file mode 100644 index 0000000..9276007 --- /dev/null +++ b/services/harbor/certificate.yaml @@ -0,0 +1,12 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: harbor-tls + namespace: harbor +spec: + secretName: harbor-tls + issuerRef: + kind: ClusterIssuer + name: letsencrypt-prod + dnsNames: + - registry.bstein.dev \ No newline at end of file diff --git a/services/harbor/helmrelease.yaml b/services/harbor/helmrelease.yaml new file mode 100644 index 0000000..46775e9 --- /dev/null +++ b/services/harbor/helmrelease.yaml @@ -0,0 +1,69 @@ +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: harbor + namespace: flux-system +spec: + interval: 15m + targetNamespace: harbor + install: + createNamespace: false + chart: + spec: + chart: harbor + version: 1.17.1 + sourceRef: + kind: HelmRepository + name: harbor + namespace: flux-system + values: + expose: + type: ingress + ingress: + className: traefik + hosts: + core: registry.bstein.dev + annotations: + cert-manager.io/cluster-issuer: letsencrypt-production + tls: + enabled: true + certSource: secret + secret: + secretName: harbor-tls + externalURL: https://registry.bstein.dev + notary: + enabled: false + harborAdminPassword: "ENCRYPT-ME-WITH-SOPS" + database: + type: external + external: + host: postgres-service.postgres.svc.cluster.local + port: 5432 + username: harbor + password: "ENCRYPT-ME-WITH-SOPS" + sslmode: disable + coreDatabase: harbor + redis: + type: internal + persistence: + persistentVolumeClaim: + registry: + storageClass: astreae + accessMode: ReadWriteOnce + size: 100Gi + jobservice: + storageClass: astreae + accessMode: ReadWriteOnce + size: 5Gi + redis: + storageClass: astreae + accessMode: ReadWriteOnce + size: 2Gi + trivy: + storageClass: astreae + accessMode: ReadWriteOnce + size: 5Gi + chartmuseum: + enabled: false + trivy: + enabled: true diff --git a/services/harbor/helmrepository.yaml b/services/harbor/helmrepository.yaml new file mode 100644 index 0000000..9231352 --- /dev/null +++ b/services/harbor/helmrepository.yaml @@ -0,0 +1,8 @@ +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: harbor + namespace: flux-system +spec: + interval: 1h + url: https://helm.goharbor.io \ No newline at end of file diff --git a/services/harbor/kustomization.yaml b/services/harbor/kustomization.yaml new file mode 100644 index 0000000..1d16bd8 --- /dev/null +++ b/services/harbor/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - namespace.yaml + - certificate.yaml + - helmrepository.yaml + - helmrelease.yaml \ No newline at end of file diff --git a/services/harbor/namespace.yaml b/services/harbor/namespace.yaml new file mode 100644 index 0000000..cbd1236 --- /dev/null +++ b/services/harbor/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: harbor \ No newline at end of file