diff --git a/mariadb/README.md b/mariadb/README.md new file mode 100644 index 0000000..00c2bd3 --- /dev/null +++ b/mariadb/README.md @@ -0,0 +1,220 @@ +--- +title: MariaDB (MySQL) +homepage: https://mariadb.com/ +tagline: | + MariaDB: The original MySQL, renamed due to Oracle acquiring the trademark +--- + +To update or switch versions, run `webi mariadb@stable` (or `@v11`, `@lts`, +etc). + +### Files + +These are the files / directories that are created and/or modified with this +install: + +```text +~/.config/envman/PATH.env +~/.local/opt/mariadb/ +~/.local/share/mariadb/ +~/.my.cnf +~/.config/mariadb/my.cnf +~/.local/share/mariadb/my.cnf +``` + +## Cheat Sheet + +> MariaDB is the original authors' successor to MySQL, after Oracle's +> acquisition of the MySQL trademark. Although [Postgres](../postgres/) is +> generally recommended for new projects, projects that previously used MySQL or +> MariaDB can continue to gain benefit from the continued development of +> MariaDB. + +Connect as the default admin, the root admin, or a remote (`%`) user: + +```sh +mysql 'dbname' +sudo mysql -u root 'dbname' +mysql -u 'dbuser' -p -h '127.0.0.1' -P 3306 'dbname' +``` + +Manage MariaDB as a system service with [serviceman](../serviceman/): + +```sh +curl https://webi.sh/serviceman | sh + +# Linux and macOS +serviceman add --name 'mysqld' --workdir ~/.local/opt/mariadb/ -- \ + mariadbd --defaults-file="$HOME/.local/share/mariadb/my.cnf" + +# On Linux, with systemd +sudo systemctl restart systemd-journald +sudo systemctl restart 'mysqld' +sudo journalctl -xef --unit 'mysqld' +``` + +## Table of Contents + +- Use UTF-8 (not Swedish) +- Vertical Rows +- Create an App User and DB +- Backup and Restore +- Connect via SSH Proxy +- Remove default users + +### Switch from Swedish to UTF-8 + +This is done automatically if installed by Webi, and in MariaDB 11.6+. + +Edit your `my.cnf` files as follows: + +```sh +[server] + character-set-server = utf8mb4 + collation-server = utf8mb4_unicode_ci + init-connect = 'SET NAMES utf8mb4' +``` + +```sh +[client] + default-character-set = utf8mb4 +``` + +You can then update old tables: + +```sql +ALTER DATABASE your_database_name CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; +``` + +See . + +In some cases it may be simpler better to **backup and restore** (see table of +contents) the databases due to foreign key constraints. + +### How to View Rows Vertically + +Use `\G` instead of `;` for a single query + +```sh +SELECT * FROM `mysql`.`global_priv` \G +``` + +### How to Create an App User + DB + +You create a database, a user (typically of the same name), a password +(typically random via `xxd` or ), and grant the app admin +privileges on its database. + +```sql +USE `mysql`; +CREATE DATABASE `appdb`; +CREATE USER 'appuser'@'%' IDENTIFIED BY 'super-secret'; +GRANT ALL PRIVILEGES ON `appdb`.* TO 'appuser'@'%'; +FLUSH PRIVILEGES; +``` + +Here's a script for doing the same: + +```sh +mariadb-create-app 'foobar' +``` + +```sh +#!/bin/sh +set -e +set -u + +# USAGE +# mariadb-create-app [app-name] +# +# EXAMPLE +# mariadb-create-app 'foobar' + +main() {( + b_appname="${1:-$(hostname)}" + + b_dbname="${b_appname}" + b_user="${b_dbname}" + b_password="$(xxd -l8 -p /dev/urandom | sed 's/..../&-/g; s/-$//')" + + mariadb -e " + USE \`mysql\`; + CREATE DATABASE IF NOT EXISTS \`${b_dbname}\`; + CREATE USER '${b_user}'@'%' IDENTIFIED BY '${b_password}'; + GRANT ALL PRIVILEGES ON \`${b_dbname}\`.* TO '${b_user}'@'%'; + FLUSH PRIVILEGES; + " + + echo "${b_password}" > ./"${b_appname}-password.txt" + + echo "" + echo "Password in ./${b_appname}-password.txt" + echo "" + echo "mysql://${b_user}:********@localhost:3306/${b_dbname}" + echo "mariadb -u ${b_user} -p ${b_dbname}" + echo "" +)} +``` + +### How to Backup and Restore + +Backup a single database: + +```sh +my_ts="$(date "+%F_%H.%M.%S")" +mysqldump -u username -p --force --hex-blob --databases 'dbname' \ + --triggers --routines --events \ + --add-drop-table --add-drop-triggers \ + --skip-set-charset --single-transaction > ./"backup.${ts}.sql" +``` + +Notes: + +- `--force` **is always necessary** and should be the default - it will continue + the backup in the case that inconsistencies are found - such as a _View_ + referencing a column that no longer exists (which is not checked during + `ALTER TABLE`) +- `--skip-set-charset` to accept the default `utf8` rather than accidentally + recreating tables as legacy `latin1-swedish` +- `--add-drop-table` and `--add-drop-triggers` do not need to be used if your + user has the ability to drop and recreate the database +- `--databases 'dbname'` omits ``USE `dbname`;``, which allows you to easily + restore to a different database name + +Destructive Restore (drop that database and then restore it): + +```sh +mysql -u username -p -e 'DROP DATABASE `dbname`'; +mysql -u username -p 'dbname' < ./backup.sql +``` + +### Connect via SSH Proxy + +1. Create a proxy (ignore warnings) + ```sh + #ssh user@server -fnNT -L :: + ssh ${USER}@${b_hostname} -fnNT -L 13306:localhost:3306 + ``` +2. Connect via `mysql`, `mariadb`, Sequel Ace, etc: + ```sh + mysql -u remote-user -h 127.0.0.1 -P 13306 + ``` + +**Notes** + +- connect with a user that has the host `%` and DOES NOT have a `localhost` or + `127.0.0.1` entry - otherwise the client may "upgrade" to a socket connection + and fail. +- you may need to remove the wildcard `localhost` users (see below) + +### Remove Default Access `localhost` Users + +You may not be able to connect via an SSH proxy if the default users exist. \ +(it may match `` `%`@`localhost` `` instead of `` `app`@`%` `` and deny the +password) + +```sql +USE `mysql`; +DELETE FROM `global_priv` WHERE `User` = ''; +FLUSH PRIVILEGES; +``` diff --git a/mariadb/install.sh b/mariadb/install.sh new file mode 100755 index 0000000..05907ab --- /dev/null +++ b/mariadb/install.sh @@ -0,0 +1,165 @@ +#!/bin/sh +# shellcheck disable=SC2034 + +__init_mariadb() { + set -e + set -u + + ################### + # Install mariadb # + ################### + + # Every package should define these 6 variables + pkg_cmd_name="mariadb" + + pkg_dst_cmd="${HOME}/.local/opt/mariadb/bin/mariadb" + pkg_dst_bin="${HOME}/.local/opt/mariadb/bin" + pkg_dst_dir="${HOME}/.local/opt/mariadb" + pkg_dst="${pkg_dst_dir}" + + pkg_src_cmd="${HOME}/.local/opt/mariadb-v${WEBI_VERSION}/bin/mariadb" + pkg_src_dir="${HOME}/.local/opt/mariadb-v${WEBI_VERSION}" + pkg_src="${pkg_src_dir}" + + pkg_get_current_version() { + # 'mariadb --version' has output in this format: + # mariadb (mariadbQL) 17.0 + # This trims it down to just the version number: + # 17.0 + mariadb --version 2> /dev/null | head -n 1 | cut -d' ' -f3 + } + + pkg_install() { + # mkdir -p $HOME/.local/opt + mkdir -p "$(dirname "$pkg_src")" + + # mv ./mariadb-11.4.4-linux-systemd-x86_64 "$HOME/.local/opt/mariadb-v11.4.4" + mv ./"mariadb-"* "$pkg_src" + } + + pkg_link() { + # ln -s "$HOME/.local/opt/mariadb-v17.0" "$HOME/.local/opt/mariadb" + # for the old mariadb version + ln -s "$pkg_src" "$pkg_dst" + if ! test -d ~/.local/opt/mysql; then + rm -f ~/.local/opt/mysql + ln -s "mariadb-v${WEBI_VERSION}" ~/.local/opt/mysql + fi + } + + #shellcheck disable=SC2059 + pkg_post_install() { + b_hostname="$(hostname)" + + if ! test -e ~/.config/mariadb; then + mkdir -p ~/.config/mariadb + chmod 0700 ~/.config/mariadb/ + fi + + if ! test -e ~/.local/share/mariadb; then + mkdir -p ~/.local/share/mariadb/ + chmod 0700 ~/.local/share/mariadb/ + mkdir -p ~/.local/share/mariadb/run/ + chmod 0750 ~/.local/share/mariadb/run/ + mkdir -p ~/.local/share/mariadb/var/ + chmod 0750 ~/.local/share/mariadb/var/ + else + mkdir -p ~/.local/share/mariadb/run/ + mkdir -p ~/.local/share/mariadb/data/ + mkdir -p ~/.local/share/mariadb/var/ + fi + + printf " $(t_path '~''/.my.cnf') $(t_dim '(sources client & server config)') " + if test -e ~/.my.cnf; then + printf -- "$(t_dim 'Found')\n" + else + { + echo '[client]' + echo '!include /home/app/.config/mariadb/my.cnf' + echo '' + echo '[server]' + echo '!include /home/app/.local/share/mariadb/my.cnf' + } > ~/.my.cnf + printf -- "Created\n" + fi + + printf " $(t_path '~''/.config/mariadb/my.cnf') $(t_dim '(client config)') " + if test -e ~/.config/mariadb/my.cnf; then + printf -- "$(t_dim 'Found')\n" + else + { + echo '[client]' + echo ' default-character-set = utf8mb4' + echo '' + echo " socket = $HOME/.local/share/mariadb/run/mariadbd.sock" + echo " #host = ${b_hostname}" + echo ' #port = 3306' + echo " #user = ${USER}" + echo " #password = (none)" + echo " #database = (none)" + } > ~/.config/mariadb/my.cnf + printf -- "Created\n" + fi + + printf " $(t_path '~''/.local/share/mariadb/my.cnf') $(t_dim '(server config)') " + if test -e ~/.local/share/mariadb/my.cnf; then + printf -- "$(t_dim 'Found')\n" + else + { + echo '[server]' + echo " character-set-server = utf8mb4" + echo " collation-server = utf8mb4_unicode_ci" + echo " init-connect = 'SET NAMES utf8mb4'" + echo '' + echo " bind-address = 127.0.0.1" + echo " port = 3306" + echo " socket = $HOME/.local/share/mariadb/run/mariadbd.sock" + echo '' + echo " basedir = $HOME/.local/opt/mariadb/" + echo " datadir = $HOME/.local/share/mariadb/data/" + echo " pid-file = $HOME/.local/share/mariadb/run/mariadbd.pid" + echo " #log-error = $HOME/.local/share/mariadb/var/error.log" + } > ~/.local/share/mariadb/my.cnf + printf -- "Created\n" + fi + + printf " $(t_path '~''/.local/share/mariadb/data/') $(t_dim '(database)') " + if test -e ~/.local/share/mariadb/data; then + printf -- "$(t_dim 'Found')\n" + else + printf -- "Initializing ... " + mkdir -p ~/.local/share/mariadb/data/ + chmod 0700 ~/.local/share/mariadb/data/ + # echo "" + # echo " Consider joining MariaDB's strong and vibrant community:" + # echo " https://mariadb.org/get-involved/" + # echo "" + ~/.local/opt/mariadb/scripts/mariadb-install-db --defaults-file="$HOME/.my.cnf" > /dev/null + printf -- "OK\n" + fi + webi_path_add "$(dirname "$pkg_dst_cmd")" + } + + pkg_done_message() { + b_hostname="$(hostname)" + + echo "" + echo " Installed $(t_pkg "mariadb") $(t_dim "(mysql)") as $(t_link '~''/.local/opt/mariadb/bin/mariadb')" + + echo "" + echo "To run manually" + echo " $(t_cmd 'mariadbd-safe') --defaults-file=\"$(t_path "$HOME/.my.cnf")\"" + #mariadbd-safe --defaults-file=$HOME/.my.cnf --print-defaults + echo "" + echo "To install as a system service" + # export MARIADB_HOME=$HOME/.local/opt/mariadb/ + echo " $(t_cmd 'serviceman') add --name mariadb --workdir $(t_path '~''/.local/opt/mariadb/') -- \\" + echo " $(t_cmd 'mariadbd') --defaults-file=\"$(t_path "$HOME/.my.cnf")\"" + echo "" + echo "To connect with a client:" + echo " $(t_cmd 'mariadb') -u 'root' -h 'localhost' $(t_dim '# all-privileges admin')" + echo " $(t_cmd 'mariadb') -u '${USER}' -h 'localhost' $(t_dim '# all-privileges admin')" + } +} + +__init_mariadb diff --git a/mariadb/releases.js b/mariadb/releases.js new file mode 100644 index 0000000..19d96be --- /dev/null +++ b/mariadb/releases.js @@ -0,0 +1,181 @@ +'use strict'; + +let Fetcher = require('../_common/fetcher.js'); + +let Releases = module.exports; + +let PRODUCT = `mariadb`; +// `https://downloads.mariadb.org/rest-api/${PRODUCT}/${minor}/` +// `https://downloads.mariadb.org/rest-api/mariadb/10.5/` +// https://github.com/MariaDB/mariadb-documentation/issues/41 + +Releases.latest = async function () { + let packages = []; + let versionData = await getVersionIds(); + for (let verData of versionData.major_releases) { + let isVersion = /^\d+[.]\d+$/.test(verData.release_id); + if (!isVersion) { + continue; + } + + let releaseData = await getReleases(verData.release_id); + let versions = Object.keys(releaseData.releases); + for (let ver of versions) { + let relData = releaseData.releases[ver]; + for (let fileData of relData.files) { + let packageData = pluckData(verData, relData, fileData); + if (!packageData) { + continue; + } + packages.push(packageData); + } + } + } + + let all = { releases: packages }; + return all; +}; + +/** @type {Object.} */ +let channelsMap = { + // 'Long Term Support': 'stable', + // 'Short Term Support': 'stable', + // 'Rolling': null, + Stable: 'stable', + RC: 'rc', + Alpha: 'preview', + null: 'preview', +}; + +/** @type {Object.} */ +let cpusMap = { + x86_64: 'amd64', +}; + +/** + * @param {MajorRelease} verData + * @param {Release} relData + * @param {File} fileData + */ +function pluckData(verData, relData, fileData) { + let lts = + verData.release_status === 'Stable' && + verData.release_support_type === 'Long Term Support'; + let cpu = fileData.cpu || ''; + cpu = cpu.trim(); + + let isNotBinary = !fileData.os || !cpu; // "Source" or some such + if (isNotBinary) { + return null; + } + + let isDebug = /debug/.test(fileData.file_name); + if (isDebug) { + return null; + } + + let pkgData = { + name: fileData.file_name, + version: relData.release_id, + lts: lts, + channel: channelsMap[verData.release_status], + date: relData.date_of_release, + os: fileData.os?.toLowerCase(), + arch: cpusMap[cpu] || cpu, + hash: fileData.checksum.sha256sum, + download: fileData.file_download_url, + }; + + return pkgData; +} + +/** + * @typedef {String} ISODate - YYYY-MM-DD (ISO 8601 format) + */ + +/** + * @typedef MajorRelease + * @prop {String} release_id - version-like for stable versions, otherwise a title + * @prop {String} release_name - same as id for MariaDB + * @prop {String} release_status - Stable|RC|Alpha + * @prop {String?} release_support_type - Long Term Support|Short Term Support|Rolling|null + * @prop {ISODate?} release_eol_date + */ + +/** + * @typedef MajorReleasesWrapper + * @prop {Array} major_releases + */ + +/** + * @typedef Release + * @prop {String} release_id - "11.4.4" or "11.6.0 Vector" + * @prop {String} release_name - "MariaDB Server 11.8.0 Preview" + * @prop {ISODate} date_of_release + * @prop {String} release_notes_url + * @prop {String} change_log + * @prop {Array} files - release assets (packages, docs, etc) + */ + +/** + * @typedef ReleasesWrapper + * @prop {Object.} releases + */ + +/** + * @typedef File + * @prop {Number} file_id + * @prop {String} file_name + * @prop {String?} package_type - "gzipped tar file" or "ZIP file" + * @prop {String?} os - "Linux" or "Windows" + * @prop {String?} cpu - "x86_64" (or null) + * @prop {Checksum} checksum + * @prop {String} file_download_url + * @prop {String?} signature + * @prop {String} checksum_url + * @prop {String} signature_url + */ + +/** + * @typedef Checksum + * @prop {String?} md5sum + * @prop {String?} sha1sum + * @prop {String?} sha256sum + * @prop {String?} sha512sum + */ + +/** + * @returns {Promise} + */ +async function getVersionIds() { + let url = `https://downloads.mariadb.org/rest-api/${PRODUCT}/`; + let resp = await Fetcher.fetch(url, { + headers: { Accept: 'application/json' }, + }); + + let result = JSON.parse(resp.body); + return result; +} + +/** + * @param {String} verId + * @returns {Promise} + */ +async function getReleases(verId) { + let url = `https://downloads.mariadb.org/rest-api/${PRODUCT}/${verId}`; + let resp = await Fetcher.fetch(url, { + headers: { Accept: 'application/json' }, + }); + + let result = JSON.parse(resp.body); + return result; +} + +if (module === require.main) { + Releases.latest().then(function (all) { + let normalize = require('../_webi/normalize.js'); + all = normalize(all); + let json = JSON.stringify(all, null, 2); + console.info(json); + }); +}