Files
SODEW/sodew-bash-main/libs/modules/mariadb.sh
T
2026-08-13 09:10:21 +02:00

361 lines
14 KiB
Bash

# Executes a command inside the MariaDB master pod.
# $@ (...): command and arguments to execute.
function mariadbMasterExec() {
k3sRun exec pod/"$mariadbMasterKube"-0 -c "$mariadbMasterKube" -- "$@"
}
# Executes a command inside the MariaDB master pod with stdin attached (-i).
# $@ (...): command and arguments to execute.
function mariadbMasterExecI() {
k3sRun exec -i pod/"$mariadbMasterKube"-0 -c "$mariadbMasterKube" -- "$@"
}
# Executes a command inside the MariaDB slave pod.
# $@ (...): command and arguments to execute.
function mariadbSlaveExec() {
k3sRun exec pod/"$mariadbSlaveKube"-0 -c "$mariadbSlaveKube" -- "$@"
}
# Runs a SQL query as root against the specified MariaDB pod.
# $1 (target): master|slave
# $2 (query): SQL query.
# [$3] (showColumn): pass "showColumn" to keep column headers.
function mariadbRootQuery() {
local target="$1" query="$2"
[[ -n "$query" ]] || { appError "Query not specified"; return 1; }
local execFn
case "$target" in
master) execFn=mariadbMasterExec ;;
slave) execFn=mariadbSlaveExec ;;
*) appError "Unknown target: $target"; return 1 ;;
esac
if [[ "${3-}" == "showColumn" ]]; then
"$execFn" mariadb -uroot -p"$mariadbRootPass" -e "$query"
else
"$execFn" mariadb -uroot -p"$mariadbRootPass" -N -e "$query"
fi
}
# Runs a SQL query as root against the MariaDB master pod.
# $1 (query): SQL query.
# [$2] (showColumn): pass "showColumn" to keep column headers.
function mariadbMasterRootQuery() { mariadbRootQuery master "$@"; }
# Runs a SQL query as root against the MariaDB slave pod.
# $1 (query): SQL query.
# [$2] (showColumn): pass "showColumn" to keep column headers.
function mariadbSlaveRootQuery() { mariadbRootQuery slave "$@"; }
# Converts a domain name into a MariaDB-safe identifier (max 60 chars).
# $1 (domain): domain name.
function mariadbDomain2id() {
domainToRandom "$1" 60
}
# Reads the MariaDB root password from the Kubernetes secret.
function mariadbRootPassSecretGet() {
k3sRun get secret "$mariadbKube-secret" -o jsonpath="{.data.root-password}" --ignore-not-found | base64 -d
}
# Saves the MariaDB root password from the Kubernetes secret to a local file.
function mariadbRootPassSecretSave() {
local password
password="$(mariadbRootPassSecretGet)"
[[ -n "$password" ]] && printf '%s\n' "$password" > "$appDataPath/$hostName.$mariadbKube"
}
# Lists user-created MariaDB databases (system schemas excluded).
function mariadbDatabaseListGet() {
local output error
run output error mariadbMasterRootQuery "SHOW DATABASES;" || { appError "$error"; return 1; }
printf '%s\n' "$output" | awk '!/^(information_schema|performance_schema|mysql|sys)$/'
}
# Lists MariaDB users with Host=% (root and replication_user excluded).
function mariadbUserListGet() {
local output error
run output error mariadbMasterRootQuery "SELECT user FROM mysql.user WHERE Host = '%';" || { appError "$error"; return 1; }
printf '%s\n' "$output" | awk '!/^(root|replication_user)$/'
}
# Creates or updates a MariaDB database and user with full privileges.
# Revokes privileges from any other users previously assigned to the same database.
# $1 (database): database name.
# $2 (username): database username.
# $3 (password): database user password.
function mariadbDatabaseUserSet() {
local database="$1"
[[ -n "$database" ]] || { appError "Database not specified"; return 1; }
local username="$2"
[[ -n "$username" ]] || { appError "Username not specified"; return 1; }
local password="$3"
[[ -n "$password" ]] || { appError "Password not specified"; return 1; }
mariadbMasterRootQuery "CREATE DATABASE IF NOT EXISTS \`$database\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER IF NOT EXISTS '$username'@'%' IDENTIFIED BY '$password'; ALTER USER '$username'@'%' IDENTIFIED BY '$password'; GRANT ALL PRIVILEGES ON \`$database\`.* TO '$username'@'%' WITH MAX_USER_CONNECTIONS 15;" || {
appError "Create/update database user failed: database=$database username=$username"
return 1
}
local others
others="$(mariadbMasterRootQuery "SELECT DISTINCT User, Host FROM mysql.db WHERE Db='$database' AND NOT (User='$username' AND Host='%');")"
while IFS=$'\t' read -r u h; do
[[ -z "$u" || -z "$h" ]] && continue
mariadbMasterRootQuery "REVOKE ALL PRIVILEGES ON \`$database\`.* FROM '$u'@'$h';" || \
appError "Revoke privileges failed: database=$database user=$u host=$h"
done <<< "$others"
}
# Checks whether a MariaDB database exists.
# $1 (database): database name.
function mariadbDatabaseExists() {
local database="$1"
[[ -n "$database" ]] || { appError "Database not specified"; return 1; }
local out
out="$(mariadbMasterRootQuery "SHOW DATABASES LIKE '$database';" 2>/dev/null | tail -n 1 | tr -d '\r')"
[[ "$out" == "$database" ]]
}
# Removes a MariaDB database if it exists.
# $1 (database): database name.
function mariadbDatabaseRemove() {
local database="$1"
[[ -n "$database" ]] || { appError "Database not specified"; return 1; }
mariadbMasterRootQuery "DROP DATABASE IF EXISTS \`$database\`;"
}
# Removes a MariaDB user from all hosts.
# $1 (username): MariaDB username.
function mariadbUserRemove() {
local username="$1"
[[ -n "$username" ]] || { appError "Username not specified"; return 1; }
local hosts
hosts="$(mariadbMasterRootQuery "SELECT Host FROM mysql.user WHERE User='$username';")"
while read -r host; do
mariadbMasterRootQuery "DROP USER IF EXISTS '$username'@'$host';"
done <<< "$hosts"
}
# Removes all tables from a MariaDB database.
# $1 (database): database name.
# $2 (username): database username.
# $3 (password): database user password.
function mariadbDatabaseClean() {
local database="$1"
[[ -n "$database" ]] || { appError "Database not specified"; return 1; }
local username="$2"
[[ -n "$username" ]] || { appError "Username not specified"; return 1; }
local password="$3"
[[ -n "$password" ]] || { appError "Password not specified"; return 1; }
local output error
run output error mariadbMasterExec mariadb -u "$username" -p"$password" -N -e "SELECT CONCAT('DROP TABLE \`', table_name, '\`;') FROM information_schema.tables WHERE table_schema = '$database';" || { appError "$error"; return 1; }
run output error mariadbMasterExec mariadb -u "$username" -p"$password" -e "USE \`$database\`; SET FOREIGN_KEY_CHECKS = 0; $output SET FOREIGN_KEY_CHECKS = 1;" || { appError "$error"; return 1; }
}
# Base: runs mariadb-dump via the given exec function.
# $1 (execFn): mariadbMasterExec|mariadbSlaveExec.
# $2 (username), $3 (password), $4 (database|all).
# [$5] (file): auto-generated if omitted.
# [$6+] (...): optional mariadb-dump parameters.
function mariadbExport() {
local execFn="$1"
[[ -n "$execFn" ]] || { appError "Exec function not specified"; return 1; }
shift || true
local username="$1"
[[ -n "$username" ]] || { appError "Username not specified"; return 1; }
shift || true
local password="$1"
[[ -n "$password" ]] || { appError "Password not specified"; return 1; }
shift || true
local database="$1"
[[ -n "$database" ]] || { appError "Database not specified"; return 1; }
shift || true
local file="$1"
if [[ -z "$file" ]]; then
[[ "$database" == "all" ]] \
&& file="$appDataPath/$mariadbMasterKube/${appDate}_${appTime}_full.sql.tar.gz" \
|| file="$appDataPath/$mariadbMasterKube/${appDate}_${appTime}_$database.sql.tar.gz"
fi
shift || true
local -a params
if [[ $# -gt 0 ]]; then
params=("$@")
local haveAll=false haveDatabases=false havePositional=false
for a in "${params[@]}"; do
case "$a" in
--all-databases|-A) haveAll=true ;;
--databases|-B) haveDatabases=true ;;
-*) ;;
*) havePositional=true ;;
esac
done
if ! $haveAll && ! $haveDatabases && ! $havePositional; then
[[ "$database" == "all" ]] && params+=(--all-databases) || params+=("$database")
fi
else
if [[ "$database" == "all" ]]; then
params=(--all-databases --single-transaction --quick --master-data=2 --routines --events)
else
params=("$database" --single-transaction --quick --routines --events)
fi
fi
local filePath fileBase fileSql
filePath=$(dirname -- "$file")
fileBase=$(basename -- "$file")
case "$fileBase" in
*.zip) fileSql="${fileBase%.zip}" ;;
*.tar.gz) fileSql="${fileBase%.tar.gz}" ;;
*) fileSql="$fileBase" ;;
esac
mkdir -p -- "$filePath" || { appError "Failed to create directory: $filePath"; return 1; }
rm -f -- "$filePath/$fileSql" "$filePath/$fileBase" &>/dev/null
local output error
runShell output error "$execFn" mariadb-dump -u "$username" -p"$password" "${params[@]}" ">" "$filePath/$fileSql" || {
rm -f -- "$filePath/$fileSql" &>/dev/null
appError "Error creating dump: ${error:-$output}"
return 1
}
case "$fileBase" in
*.zip|*.tar.gz|*.tgz)
runError error archiveCreate "$filePath/$fileSql" "$file" || {
rm -f -- "$filePath/$fileSql" "$filePath/$fileBase" &>/dev/null
appError "Error creating archive dump: $error"
return 1
}
rm -f -- "$filePath/$fileSql" &>/dev/null
;;
esac
printf '%s' "$file"
}
# Exports from the master pod.
# $1 (username), $2 (password), $3 (database|all), [$4] (file), [$5+] (params)
function mariadbMasterExport() {
mariadbExport mariadbMasterExec "$@"
}
# Exports using root credentials.
# $1 (execFn), $2 (database|all), [$3] (file), [$4+] (params)
function mariadbRootExport() {
mariadbExport "$1" root "$mariadbRootPass" "${@:2}"
}
# Exports from master using root credentials.
# [$1] (database|all), [$2] (file), [$3+] (params)
function mariadbMasterRootExport() {
local target="${1:-all}"
local file="${2:-$appDataPath/$mariadbMasterKube/${appDate}_${appTime}_full.sql.tar.gz}"
mariadbRootExport mariadbMasterExec "$target" "$file" ${@:3}
}
# Exports from slave using root credentials.
# [$1] (database|all), [$2] (file)
function mariadbSlaveRootExport() {
local target="${1:-all}"
local file="${2:-$appDataPath/$mariadbSlaveKube/${appDate}_${appTime}_full.sql.tar.gz}"
mariadbRootExport mariadbSlaveExec "$target" "$file" --all-databases --single-transaction --quick --routines --events
}
# Imports a MariaDB dump into a database.
# Supports plain SQL, .zip and .tar.gz archives (must contain exactly one file).
# $1 (database|all): target database, or "all" to import without selecting a database.
# $2 (username): username used for import.
# $3 (password): password used for import.
# $4 (file): source dump file.
function mariadbMasterImport() {
local database="$1"
[[ -n "$database" ]] || { appError "Database not specified"; return 1; }
local username="$2"
[[ -n "$username" ]] || { appError "Username not specified"; return 1; }
local password="$3"
[[ -n "$password" ]] || { appError "Password not specified"; return 1; }
local file="$4"
[[ -n "$file" ]] || { appError "File not specified"; return 1; }
[[ -f "$file" ]] || { appError "File not found: $file"; return 1; }
local tmpFile
if [[ "$file" == *.zip ]]; then
local entriesRaw
entriesRaw="$(unzip -Z1 "$file" 2>/dev/null)" || { appError "Not a valid zip archive: $file"; return 1; }
local -a entries=()
mapfile -t entries < <(printf '%s\n' "$entriesRaw" | sed '/\/$/d')
[[ ${#entries[@]} -eq 1 ]] || { appError "Archive must contain exactly one file: $file"; return 1; }
tmpFile="$(mktemp)" || { appError "Failed to create temporary file"; return 1; }
unzip -p "$file" "${entries[0]}" > "$tmpFile" 2>/dev/null || {
rm -f -- "$tmpFile"
appError "Failed to extract ZIP archive"
return 1
}
elif [[ "$file" == *.tar.gz ]]; then
local entriesRaw
entriesRaw="$(tar -tzf "$file" 2>/dev/null)" || { appError "Not a valid tar.gz archive: $file"; return 1; }
local -a entries=()
mapfile -t entries < <(printf '%s\n' "$entriesRaw" | sed '/\/$/d')
[[ ${#entries[@]} -eq 1 ]] || { appError "Archive must contain exactly one file: $file"; return 1; }
tmpFile="$(mktemp)" || { appError "Failed to create temporary file"; return 1; }
tar -xOzf "$file" "${entries[0]}" > "$tmpFile" 2>/dev/null || {
rm -f -- "$tmpFile"
appError "Failed to extract tar.gz archive"
return 1
}
else
tmpFile="$file"
fi
if [[ ! -s "$tmpFile" ]]; then
[[ "$tmpFile" == "$file" ]] || rm -f -- "$tmpFile"
appError "The SQL dump is empty"
return 1
fi
local -a mariadbCmd=(mariadbMasterExecI mariadb -u "$username" -p"$password")
[[ "$database" != "all" ]] && mariadbCmd+=("$database")
local output error
runShell output error "${mariadbCmd[@]}" "<" "$tmpFile" ">" /dev/null || {
[[ "$tmpFile" == "$file" ]] || rm -f -- "$tmpFile"
appError "Import failed: ${error:-$output}"
return 1
}
[[ "$tmpFile" == "$file" ]] || rm -f -- "$tmpFile"
}
# Imports a MariaDB dump using root credentials.
# $1 (database|all): target database, or "all".
# $2 (file): source dump file.
function mariadbMasterRootImport() {
mariadbMasterImport "$1" root "$mariadbRootPass" "${@:2}"
}
# Creates or updates the MariaDB database and user for a site.
# $1 (domain): site domain name.
function mariadbConfigRebuild() {
local domain
domain=$(domainPrepare "$1")
domainCheck "$domain" || return 1
local databaseName databaseUser databasePass
databaseName=$(siteConfigGetOrSet "$domain" "databaseName" "$(mariadbDomain2id "$domain")")
databaseUser=$(siteConfigGetOrSet "$domain" "databaseUser" "$(mariadbDomain2id "$domain")")
databasePass=$(siteConfigGetOrCreate "$domain" "databasePass")
mariadbDatabaseUserSet "$databaseName" "$databaseUser" "$databasePass" || return 1
}