# Executes a command inside the Redis master pod. # $@ (...): command and arguments to execute. function redisExec() { k3sRun exec pod/"$redisKube"-0 -c "$redisKube" -- "$@" } # Executes a command inside a specific Redis or Redis Sentinel pod. # Container is selected automatically based on the pod name prefix. # $1 (pod): pod name. # $2+ (...): command and arguments to execute. function redisPodExec() { local pod="$1" [[ -n "$pod" ]] || { appError "Pod not specified"; return 1; } shift || true local container="$redisKube" [[ "$pod" == "$redisSentinelKube-"* ]] && container="$redisSentinelKube" k3sRun exec pod/"$pod" -c "$container" -- "$@" } # Runs redis-cli against the master pod, with authentication if configured. # $@ (...): redis-cli arguments. function redisExecCli() { local -a cmd=(redis-cli --no-auth-warning) [[ -n "$redisRootPass" ]] && cmd+=(-a "$redisRootPass") redisExec "${cmd[@]}" "$@" } # Runs redis-cli on a specific pod, with authentication and port selected automatically. # Uses port 26379 for Sentinel pods. # $1 (pod): pod name. # $2+ (...): redis-cli arguments. function redisPodExecCli() { local pod="$1" shift || true local -a cmd=(redis-cli --no-auth-warning) [[ -n "$redisRootPass" ]] && cmd+=(-a "$redisRootPass") [[ "$pod" == "$redisSentinelKube-"* ]] && cmd+=(-p 26379) redisPodExec "$pod" "${cmd[@]}" "$@" } # Converts a domain name into a Redis-safe identifier (max 64 chars). # $1 (domain): domain name. function redisDomain2id() { domainToRandom "$1" 64 } # Reads the Redis root password from the Kubernetes secret. function redisRootPassSecretGet() { k3sRun get secret "$redisKube-secret" -o jsonpath="{.data.root-password}" --ignore-not-found | base64 -d } # Saves the Redis root password from the Kubernetes secret to a local file. function redisRootPassSecretSave() { local password password="$(redisRootPassSecretGet)" [[ -n "$password" ]] && printf '%s\n' "$password" > "$appDataPath/$hostName.$redisKube" } # Updates the Redis root password across pods. Not yet implemented. function redisRootPassUpdate() { printWarning "In progress..." # Add update pass in RedisSentinel and HAProxy !!!... # k3sRun create secret generic "$redisKube-secret" --from-literal=root-password="$redisRootPass" --dry-run=client -o yaml | k3sRun apply -f - # k3sRun delete pod "$redisKube-0" # sleep 1 # k3sRun delete pod "$redisKube-1" # k3sRun rollout restart "sts/$redisSentinelKube" # return 1 } # List Redis users via ACL LIST (names only). function redisUserListGet() { local output error run output error redisExecCli ACL LIST || { appError "$error"; return 1; } printf '%s' "$output" | grep -oP 'user \K\w+' } # Flushes all keys from the current Redis database. function redisDatabaseFlush() { runFail redisExecCli FLUSHDB } # Persists the ACL user list to disk. function redisUserListSave() { runFail redisExecCli ACL SAVE } # Creates or updates a Redis ACL user with password and key pattern. # $1 (username): ACL username. # $2 (password): ACL password. # [$3] (keyPattern): key access pattern (defaults to "username:*"). function redisUserSet() { local username="$1" [[ -n "$username" ]] || { appError "Username not specified"; return 1; } local password="$2" [[ -n "$password" ]] || { appError "Password not specified"; return 1; } local keyPattern="${3:-${username}:*}" local podList error run podList error k3sPodList "$redisKube" || { appError "Get pods list: $error"; return 1; } [[ -n "$podList" ]] || { appError "Pods not found"; return 1; } while read -r pod; do runFail redisPodExecCli "$pod" ACL SETUSER "$username" reset on sanitize-payload resetchannels ">$password" "~$keyPattern" -@all +@connection +@string +@keyspace +@sortedset +@scripting +@transaction +info -keys -flushdb -flushall '-script|flush' '-client|kill' '-client|pause' || return 1 runFail redisPodExecCli "$pod" ACL SAVE || return 1 done <<< "$podList" } # Removes a Redis ACL user from all pods. # $1 (username): ACL username. function redisUserRemove() { local username="$1" [[ -n "$username" ]] || { appError "Username not specified"; return 1; } local podList error run podList error k3sPodList "$redisKube" || { appError "Get pods list: $error"; return 1; } [[ -n "$podList" ]] || { appError "Pods not found"; return 1; } while read -r pod; do runFail redisPodExecCli "$pod" ACL DELUSER "$username" || return 1 runFail redisPodExecCli "$pod" ACL SAVE || return 1 done <<< "$podList" } # Deletes all Redis keys matching the given prefix. # $1 (prefixKey): key prefix to match (e.g. "user:"). function redisKeysRemove() { local prefixKey="$1" [[ -n "$prefixKey" ]] || { appError "PrefixKey not specified"; return 1; } local output error run output error redisExecCli --scan --pattern "${prefixKey}*" || { appError "$error"; return 1; } [[ -z "$output" ]] && return 0 local -a keys mapfile -t keys <<< "$output" runFail redisExecCli DEL "${keys[@]}" } # Removes all Redis keys belonging to a site's user. # $1 (domain): site domain name. function redisDomainClean() { local domain="$1" domain=$(domainPrepare "$domain") domainCheck "$domain" || return 1 local redisUser redisUser=$(siteConfigGet "$domain" "redisUser") [[ -n "$redisUser" ]] && redisKeysRemove "$redisUser:" } # Creates or updates the Redis ACL user and key pattern for a site. # $1 (domain): site domain name. function redisConfigRebuild() { local domain domain=$(domainPrepare "$1") domainCheck "$domain" || return 1 fileBackup "$redisPath/$redisFileUsersAcl" local redisUser redisPass redisUser=$(siteConfigGetOrSet "$domain" "redisUser" "$(redisDomain2id "$domain")") redisPass=$(siteConfigGetOrCreate "$domain" "redisPass") redisUserSet "$redisUser" "$redisPass" || return 1 } # Parses raw SENTINEL REPLICAS output into tab-separated lines (name, ip, port, master-host, runid). # Skips disconnected/s_down replicas and deduplicates by runid. # $1 (raw): raw output from `SENTINEL replicas `. function redisSentinelParseReplicas() { local raw="$1" local key value flags local -A slaveData=() local -A seenRunIds=() while read -r key && read -r value; do if [[ "$key" == "name" && ${#slaveData[@]} -gt 0 ]]; then flags="${slaveData[flags]}" if [[ -n "${slaveData[runid]}" && "$flags" != *disconnected* && "$flags" != *s_down* ]]; then if [[ -z "${seenRunIds[${slaveData[runid]}]}" ]]; then seenRunIds["${slaveData[runid]}"]=1 printf '%s\t%s\t%s\t%s\t%s\n' "${slaveData[name]}" "${slaveData[ip]}" "${slaveData[port]}" "${slaveData[master-host]}" "${slaveData[runid]}" fi fi slaveData=() fi slaveData["$key"]="$value" done <<< "$raw" if [[ ${#slaveData[@]} -gt 0 ]]; then flags="${slaveData[flags]}" if [[ -n "${slaveData[runid]}" && "$flags" != *disconnected* && "$flags" != *s_down* ]]; then if [[ -z "${seenRunIds[${slaveData[runid]}]}" ]]; then printf '%s\t%s\t%s\t%s\t%s\n' "${slaveData[name]}" "${slaveData[ip]}" "${slaveData[port]}" "${slaveData[master-host]}" "${slaveData[runid]}" fi fi fi }