361 lines
14 KiB
Bash
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
|
|
}
|