├── LICENSE ├── bin ├── db-change ├── db-export ├── db-import ├── db-init ├── db-load ├── db-log ├── db-nuke ├── db-remove ├── db-save ├── db-server ├── db-show ├── db-structure ├── db-test ├── drivers │ └── mysql │ │ ├── connectors │ │ ├── direct │ │ │ ├── load │ │ │ ├── run │ │ │ ├── save │ │ │ └── test │ │ └── ssh │ │ │ ├── load │ │ │ ├── run │ │ │ ├── save │ │ │ └── test │ │ └── vendor │ │ └── extract_sql │ │ └── extract_sql.pl └── include │ ├── all │ ├── colors │ ├── functions │ └── vars ├── db └── readme.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Edward Akerboom 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bin/db-change: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | . "${bin}/include/all" 4 | 5 | if [ "$#" -gt 1 ]; then shift 2; fi 6 | subcommand="${1}" 7 | server="${2}" 8 | 9 | if [ "$#" -gt 2 ]; then shift 2; fi 10 | 11 | 12 | # include functions to run queries 13 | . "${bin}/drivers/${databasetype}/connectors/${connectiontype}/run" 14 | 15 | 16 | change_charset() 17 | { 18 | _charset=$1 19 | _collation=$2 20 | 21 | if [ -z "${_charset}" ] || [ -z "${_collation}" ]; then 22 | echo "Please provide both the character set and the collation" 23 | exit 1 24 | fi 25 | 26 | database=$(cat "${database_file}") 27 | 28 | sql="SELECT CONCAT('ALTER TABLE \`',TABLE_SCHEMA,'\`.\`',TABLE_NAME,'\` CONVERT TO CHARACTER SET ${_charset} COLLATE ${_collation};') from information_schema.TABLES WHERE TABLE_SCHEMA = '${database}';" 29 | run_sql "${sql}" 30 | 31 | if [ "${code}" -eq 0 ]; then 32 | # Initial SQL ran successfully 33 | run_sql "SET FOREIGN_KEY_CHECKS=0; ${output}; SET FOREIGN_KEY_CHECKS=1;" 34 | 35 | if [ "${code}" -eq 0 ]; then 36 | echo "${colored_alias}${COLOR_GREEN}Successfully changed character set to '${_charset}' and collation to '${_collation}'.${COLOR_NC}" 37 | fi 38 | fi 39 | 40 | if [ "${code}" -eq 1 ]; then 41 | echo "${COLOR_RED}Error while converting character set and collation.${COLOR_NC}" 42 | fi 43 | } 44 | 45 | change_wordpress() 46 | { 47 | _new_url=$1 48 | 49 | if [ -z "${_new_url}" ]; then 50 | echo "Please provide a new URL to migrate this WordPress database to" 51 | exit 1 52 | fi 53 | 54 | database=$(cat "${database_file}") 55 | 56 | # get current ('old') site url 57 | sql="SELECT \`option_value\` FROM \`${database}\`.\`wp_options\` WHERE \`option_name\`=\"siteurl\";" 58 | run_sql "${sql}" 59 | _prev_url=$output 60 | 61 | if [ "${code}" -eq 0 ]; then 62 | # set new site url 63 | sql="USE \`${database}\`; UPDATE \`wp_options\` SET \`option_value\`=\"${_new_url}\" WHERE \`option_name\` IN (\"siteurl\", \"home\");" 64 | run_sql "${sql}" 65 | 66 | if [ "${code}" -eq 0 ]; then 67 | # replace old site url with new site url in content 68 | sql="USE \`${database}\`; UPDATE \`wp_posts\` SET \`post_content\` = REPLACE(\`post_content\`, '${_prev_url}', '${_new_url}')" 69 | run_sql "${sql}" 70 | 71 | if [ "${code}" -eq 0 ]; then 72 | echo "${colored_alias}${COLOR_GREEN}Successfully changed WordPress installation to be hosted from ${_new_url} instead of from ${_prev_url}.${COLOR_NC}" 73 | fi 74 | fi 75 | fi 76 | 77 | if [ "${code}" -eq 1 ]; then 78 | echo "${COLOR_RED}Error while changing WordPress installation URL.${COLOR_NC}" 79 | fi 80 | } 81 | 82 | if [ "${subcommand}" != "charset" ] && [ "${subcommand}" != "wordpress" ]; then 83 | echo "USAGE:" 84 | echo "" 85 | echo " ${COLOR_LIGHT_BLUE}db change charset [server alias] [new characterset] [new collation] ${COLOR_NC}" 86 | echo "" 87 | echo " Change the character set and collation" 88 | echo "" 89 | echo " ${COLOR_LIGHT_BLUE}db change wordpress [server alias] [new url] ${COLOR_NC}" 90 | echo "" 91 | echo " Assume the database is a WordPress database and make changes so that it can run on the new url" 92 | echo "" 93 | echo "${COLOR_RED}WARNING!! These commands do NOT operate on a snapshot but on the real database instead!${COLOR_NC}" 94 | fi 95 | 96 | case ${subcommand} in 97 | charset) 98 | change_charset "$@" 99 | exit 0 100 | ;; 101 | wordpress) 102 | change_wordpress "$@" 103 | exit 0 104 | ;; 105 | esac 106 | -------------------------------------------------------------------------------- /bin/db-export: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | . "${bin}/include/all" 4 | snapshot="${3}" 5 | export_filename="${4}" 6 | 7 | find_snapshot_from_any_server "${snapshot}" 8 | 9 | if [ -z "${export_filename}" ]; then 10 | echo "USAGE:" 11 | echo " ${COLOR_LIGHT_BLUE}db export [server alias] [snapshot] ${COLOR_NC}" 12 | echo "" 13 | echo "Exports a database snapshot from the repository to a local file" 14 | exit 1 15 | fi 16 | 17 | if [ -f "${file}" ]; then 18 | gunzip < "${file}" > "${export_filename}" 19 | 20 | echo "${colored_alias}Exported ${COLOR_LIGHT_BLUE}${snapshot}${COLOR_NC} to ${COLOR_LIGHT_BLUE}${export_filename}${COLOR_NC}" 21 | 22 | exit 0 23 | else 24 | echo "${colored_alias}Cannot export ${COLOR_LIGHT_BLUE}${snapshot}${COLOR_NC} - snapshot does not exist" 25 | exit 1 26 | fi -------------------------------------------------------------------------------- /bin/db-import: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | file="${3}" 4 | . "${bin}/include/all" 5 | 6 | if [ ! -e "${file}" ]; then 7 | snapshot="${3}" 8 | find_snapshot_from_any_server "${snapshot}" 9 | fi 10 | 11 | 12 | if [ -z "${file}" ] || [ ! -f "${file}" ]; then 13 | echo "USAGE:" 14 | echo " ${COLOR_LIGHT_BLUE}db import [server alias] [ | ]${COLOR_NC}" 15 | echo "" 16 | echo "Imports a database dump from a local file, or from an existing snapshot" 17 | exit 1 18 | fi 19 | 20 | connection_details="--defaults-extra-file=${connection_config}" 21 | database=$(cat "${database_file}") 22 | 23 | # include functions to restore full snapshot and to restore individual tables 24 | . "${bin}/drivers/${databasetype}/connectors/${connectiontype}/load" 25 | 26 | 27 | if [ -f "${file}" ]; then 28 | echo "${colored_alias}Starting import" 29 | 30 | restore_snapshot 31 | 32 | echo "${colored_alias}Imported ${COLOR_LIGHT_BLUE}${file}${COLOR_NC}" 33 | exit 0 34 | else 35 | echo "${colored_alias}Cannot import ${COLOR_LIGHT_BLUE}${file}${COLOR_NC} - file does not exist" 36 | exit 1 37 | fi 38 | -------------------------------------------------------------------------------- /bin/db-init: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | trap ctrl_c INT 3 | 4 | repository="." # make sure that it's defined 5 | ctrl_c() 6 | { 7 | # Try to remove repository if this script is interrupted 8 | # Will silently fail if repository is not empty 9 | rmdir "${repository}" 2>/dev/null 10 | exit 1 11 | } 12 | 13 | 14 | . "${bin}/include/all" 15 | 16 | if [ ! -d "${repository}" ]; then 17 | mkdir -p "${repository}" 18 | fi 19 | 20 | type="${3}" 21 | if [ -z "${type}" ]; then 22 | type="mysql" 23 | else 24 | shift 25 | fi 26 | supported=$(find "${bin}/drivers" -mindepth 1 -maxdepth 1 -type d | rev | cut -d '/' -f 1 | rev) 27 | 28 | while true; do 29 | if [ "$#" -lt 3 ]; then 30 | 31 | echo "" 32 | echo "${COLOR_LIGHT_BLUE}Please provide the database type for this repository${COLOR_NC}" 33 | echo "Supported types: ${supported}" 34 | echo "" 35 | user_input "Database type" "" "${type}" 0 36 | type=${answer} 37 | 38 | fi 39 | 40 | if [ -d "${bin}/drivers/${type}" ]; then 41 | echo "${type}" > "${repository}/type" 42 | "${bin}/../db" server add 43 | exit $? 44 | fi 45 | 46 | done 47 | -------------------------------------------------------------------------------- /bin/db-load: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | . "${bin}/include/all" 4 | 5 | snapshot=${3} 6 | find_snapshot_from_any_server "${snapshot}" 7 | connection_details="--defaults-extra-file=${connection_config}" 8 | database=$(cat "${database_file}") 9 | 10 | if [ "$#" -lt 3 ]; then 11 | echo "USAGE:" 12 | echo " ${COLOR_LIGHT_BLUE}db load [server alias] [snapshot] [--match MATCH] <...> ${COLOR_NC}" 13 | echo "" 14 | echo "Loads a database snapshot, or part of a snapshot, to a given server" 15 | echo "" 16 | echo "OPTIONS" 17 | echo " --match MATCH Only loads tables whose name matches the regexp 'MATCH'" 18 | echo "" 19 | echo "${COLOR_RED}WARNING!! Will overwrite (parts of) the database on that server!${COLOR_NC}" # that's the point 20 | exit 1 21 | fi 22 | 23 | 24 | if [ ! -f "${file}" ]; then 25 | echo "${colored_alias}Could not load snapshot ${COLOR_LIGHT_BLUE}${snapshot}${COLOR_NC} - snapshot does not exist" 26 | exit 1 27 | fi 28 | 29 | # include functions to load full snapshot and to load individual tables 30 | . "${bin}/drivers/${databasetype}/connectors/${connectiontype}/load" 31 | 32 | 33 | if [ "$#" -gt 3 ]; then 34 | # restore individual tables 35 | shift 3 36 | 37 | tables=$* 38 | if [ "$1" = '--match' ]; then 39 | shift 1 40 | echo "${colored_alias}Start restoring tables matching $1${COLOR_NC}" 41 | extract="${bin}/drivers/mysql/vendor/extract_sql/extract_sql.pl" 42 | tables=$(gunzip -c "${file}" | ${extract} --listTables | grep -e "$1") 43 | fi 44 | 45 | for table in $tables; do 46 | echo "Restoring table ${table}" 47 | code="" 48 | 49 | restore_table "${table}" 50 | 51 | if [ "${code}" -eq 0 ]; then 52 | echo "${colored_alias}Loaded table ${COLOR_LIGHT_BLUE}${table}${COLOR_NC} from snapshot ${COLOR_LIGHT_BLUE}${snapshot}${COLOR_NC}" 53 | else 54 | echo "${colored_alias}Could not load table ${COLOR_LIGHT_BLUE}${table}${COLOR_NC} from snapshot ${COLOR_LIGHT_BLUE}${snapshot}${COLOR_NC}" 55 | fi 56 | 57 | done 58 | exit 1 59 | else 60 | # load full snapshot 61 | code="" 62 | 63 | restore_snapshot 64 | 65 | if [ "${code}" -eq 0 ]; then 66 | echo "${colored_alias}Loaded snapshot ${COLOR_LIGHT_BLUE}${snapshot}${COLOR_NC}" 67 | exit 0 68 | else 69 | echo "${colored_alias}Could not load snapshot ${COLOR_LIGHT_BLUE}${snapshot}${COLOR_NC}" 70 | exit ${code} 71 | fi 72 | fi 73 | 74 | -------------------------------------------------------------------------------- /bin/db-log: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | . "${bin}/include/all" 4 | 5 | if [ "${3}" = "--help" ]; then 6 | echo "USAGE:" 7 | echo " ${COLOR_LIGHT_BLUE}db log [server alias | all]${COLOR_NC}" 8 | echo "" 9 | echo "Prints a list of recorded database dumps" 10 | exit 1 11 | fi 12 | 13 | alias="${2}" 14 | all=0 15 | if [ "${3}" = "all" ]; then 16 | all=1 17 | fi 18 | 19 | for s in ${servers} 20 | do 21 | if [ "${all}" = "0" ]; then 22 | if [ "${alias}" != "${s}" ]; then 23 | continue; 24 | fi 25 | fi 26 | echo "[${COLOR_YELLOW}${s}${COLOR_NC}]" 27 | 28 | dumps="${repository}/${s}/dumps" 29 | path_length=$(echo "${dumps}/" | wc -c) 30 | meta="${repository}/${s}/meta" 31 | 32 | find "${dumps}" -type f -print0 | xargs -0 ls -1rt | sed -e 's/\.gz$//' | cut -c ${path_length}- | while read -r hash 33 | do 34 | echo "${COLOR_NC}${hash}${COLOR_NC}" 35 | 36 | if [ -f "${meta}/${hash}" ]; then 37 | # unix_ts=$(head -n 1 "${meta}/${hash}") 38 | human_ts=$(head -n 2 "${meta}/${hash}" | tail -n 1) 39 | msg=$(tail -n 1 "${meta}/${hash}") 40 | echo " ${COLOR_LIGHT_BLUE}${human_ts}${COLOR_NC} ${COLOR_LIGHT_GRAY}${msg}${COLOR_NC} " 41 | echo "" 42 | fi 43 | done 44 | 45 | echo "" 46 | done 47 | -------------------------------------------------------------------------------- /bin/db-nuke: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | . "${bin}/include/all" 4 | 5 | if [ "$#" -lt 2 ]; then 6 | echo "USAGE:" 7 | echo " ${COLOR_LIGHT_BLUE}db nuke [server alias]${COLOR_NC}" 8 | echo "" 9 | echo "Deletes all tables in a database" 10 | exit 1 11 | fi 12 | 13 | database=$(cat "${database_file}") 14 | 15 | options="--no-data --add-drop-database" 16 | connection_details="--defaults-extra-file=${connection_config}" 17 | echo "SET GLOBAL FOREIGN_KEY_CHECKS=0;" | "${mysql}" ${connection_details} "${database}" 18 | "${mysqldump}" ${connection_details} ${options} "${database}" | grep 'DROP TABLE' | "${mysql}" ${connection_details} "${database}" 19 | echo "SET GLOBAL FOREIGN_KEY_CHECKS=1;" | "${mysql}" ${connection_details} "${database}" 20 | 21 | echo "${colored_alias}Nuked ${database}" 22 | -------------------------------------------------------------------------------- /bin/db-remove: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | . "${bin}/include/all" 4 | snapshot="${3}" 5 | 6 | find_snapshot_from_any_server "${snapshot}" 7 | 8 | if [ -z "${3}" ]; then 9 | echo "USAGE:" 10 | echo " ${COLOR_LIGHT_BLUE}db remove [snapshot]${COLOR_NC}" 11 | echo "" 12 | echo "Removes a given snapshot from the repository" 13 | exit 1 14 | fi 15 | 16 | 17 | if [ -f "${file}" ]; then 18 | rm "${file}" 19 | echo "${colored_alias}Removed snapshot ${COLOR_LIGHT_BLUE}${snapshot}${COLOR_NC}" 20 | exit 0 21 | else 22 | echo "${colored_alias}Cannot remove ${COLOR_LIGHT_BLUE}${snapshot}${COLOR_NC} - snapshot does not exist" 23 | exit 1 24 | fi 25 | -------------------------------------------------------------------------------- /bin/db-save: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | . "${bin}/include/all" 4 | 5 | if [ -z "${3}" ]; then 6 | echo "USAGE:" 7 | echo " ${COLOR_LIGHT_BLUE}db save [server alias] [commit message]${COLOR_NC}" 8 | echo "" 9 | echo "Saves a snapshot of the database to the repository" 10 | exit 1 11 | fi 12 | 13 | msg=${3} 14 | 15 | shift 2 16 | 17 | # create temporary filename 18 | ts=$(date +%Y%m%d-%H%M%S) 19 | filename=${ts} 20 | file="${dumps}/${filename}.gz" 21 | 22 | # create database dump to temporary file 23 | echo "${colored_alias}" 24 | "${bin}/drivers/${databasetype}/connectors/${connectiontype}/save" "${project_root}" "${identifier}" "${file}" "$@" 25 | code=$? 26 | 27 | # calculate hash of this dump 28 | hash=$(gunzip -c "${file}" | grep -v "^--" | grep -v "^/\*" | grep -v "^$" | shasum -a 512224 | cut -d " " -f 1) 29 | 30 | # check if we already have a dump with that hash 31 | count=$(find "${dumps}" -type f 2>/dev/null | grep -c "${hash}" | sed -e 's/^[[:space:]]*//') 32 | 33 | if [ "${count}" -eq "0" ]; then 34 | # no, we don't have a dump with that hash 35 | if [ "${code}" -eq 1 ]; then 36 | # but there was an error making the database dump 37 | echo "${COLOR_RED}Could not make snapshot of database.${COLOR_NC}" 38 | else 39 | # no errors, so give temporary dump its final name, based on the hash 40 | mv "${file}" "${dumps}/${hash}.gz" 41 | 42 | # save message and timestamp to metadata 43 | since_epoch=$(date +%s) 44 | timestamp=$(date -r $(date +%s)) 45 | echo "${since_epoch}" > "${meta}/${hash}" 46 | echo "${timestamp}" >> "${meta}/${hash}" 47 | echo "${msg}" >> "${meta}/${hash}" 48 | echo "${COLOR_GREEN}Successfully made snapshot of database${COLOR_NC} - hash ${COLOR_BLUE}${hash}${COLOR_NC}" 49 | fi 50 | else 51 | rm "${file}" 52 | echo "${COLOR_RED}Database not changed since last dump${COLOR_NC}, not making a new snapshot." 53 | fi 54 | 55 | 56 | -------------------------------------------------------------------------------- /bin/db-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | . "${bin}/include/all" 4 | 5 | if [ "$#" -gt 1 ]; then shift 2; fi 6 | subcommand="${1}" 7 | server="${2}" 8 | connectiontype="${3}" 9 | ssh="${4}" 10 | host="${5}" 11 | port="${6}" 12 | username="${7}" 13 | password="${8}" 14 | database="${9}" 15 | 16 | write_config() 17 | { 18 | _server=$1 19 | _config="${repository}/${_server}/config" 20 | _dumps="${repository}/${_server}/dumps" 21 | 22 | _credentials="${_config}/credentials.cnf" 23 | _db_file="${_config}/database" 24 | _connectiontype_file="${_config}/connectiontype" 25 | _ssh_config="${_config}/ssh.cnf" 26 | 27 | if [ ! -d "${_config}" ]; then 28 | mkdir -p "${_config}" 29 | fi 30 | 31 | if [ ! -d "${_dumps}" ]; then 32 | mkdir -p "${_dumps}" 33 | fi 34 | 35 | echo "[client]" > "${_credentials}" 36 | echo "user = ${username}" >> "${_credentials}" 37 | echo "password = ${password}" >> "${_credentials}" 38 | echo "host = ${host}" >> "${_credentials}" 39 | echo "port = ${port}" >> "${_credentials}" 40 | 41 | chmod 600 "${_credentials}" 42 | 43 | echo "${database}" > "${_db_file}" 44 | chmod 600 "${_db_file}" 45 | 46 | echo "${connectiontype}" > "${_connectiontype_file}" 47 | 48 | if [ -f "${_ssh_config}" ]; then 49 | rm "${_ssh_config}" 50 | fi 51 | 52 | case $connectiontype in 53 | direct) 54 | ;; 55 | 56 | ssh) 57 | echo "${ssh}" > "${_ssh_config}" 58 | chmod 600 "${_ssh_config}" 59 | ;; 60 | esac 61 | } 62 | 63 | test_config() 64 | { 65 | _server=$1 66 | 67 | "${bin}/../db" test "$_server" >/dev/null 2>&1 68 | } 69 | 70 | delete_config() 71 | { 72 | _server=$(basename "$1") 73 | if [ -d "${repository}/${_server:?}" ]; then 74 | rm -rf "${repository}/${_server:?}" 75 | fi 76 | } 77 | 78 | add_server() 79 | { 80 | while true; do 81 | if [ "$#" -ne 8 ]; then 82 | 83 | echo "" 84 | echo "${COLOR_LIGHT_BLUE}Please provide the connection details for the database${COLOR_NC}" 85 | user_input "Server alias" "Please provide a meaningful server name to refer to this database, such as 'localhost', 'development', 'staging', ..." "${server}" 0 86 | server=${answer} 87 | 88 | user_input "Connection type, e.g. direct, or ssh" "Please specify how you connect to this database" "${connectiontype}" 0 89 | connectiontype=${answer} 90 | 91 | case ${connectiontype} in 92 | direct) 93 | ;; 94 | 95 | ssh) 96 | user_input "SSH login, e.g. user@hostname.com" "Please provide the username and host of the remote server" "${ssh}" 0 97 | ssh=${answer} 98 | ;; 99 | esac 100 | 101 | echo "Please provide the database host. For connection types SSH and direct, this is probably 127.0.0.1". 102 | user_input "Database host" "Please provide a database hostname" "127.0.0.1" 0 103 | host=${answer} 104 | 105 | echo "Please provide the database port. For connection types SSH and direct, this is probably 3306". 106 | user_input "Database port" "Please provide a database port" "3306" 0 107 | port=${answer} 108 | 109 | user_input "Username" "Please provide a username" "root" 0 110 | username=${answer} 111 | 112 | user_input "Password" "Please provide the password for user '${username}'" "" 1 113 | password=${answer} 114 | 115 | user_input "Database" "Please provide the name of a database" "" 0 116 | database=${answer} 117 | else 118 | # remove host, user, password etc from command line arguments 119 | shift 6 120 | fi 121 | 122 | ts=$(date +%Y%m%d-%H%M%S) 123 | test="test-${ts}" 124 | write_config "${test}" 125 | test_config "${test}" 126 | code="${?}" 127 | delete_config "${test}" 128 | 129 | if [ "${code}" -eq 127 ]; then 130 | echo "${COLOR_RED}Could not find mysql binary.${COLOR_NC}" 131 | else 132 | if [ "${code}" -eq 1 ]; then 133 | echo "${COLOR_RED}Could not connect to database. Please try again.${COLOR_NC}" 134 | continue 135 | else 136 | echo "${COLOR_GREEN}Successfully connected to database. Writing config.${COLOR_NC}" 137 | fi 138 | fi 139 | 140 | write_config "${server}" 141 | exit 0 142 | done 143 | } 144 | 145 | rm_server() 146 | { 147 | _server=$1 148 | if [ -d "${repository}/${_server}/config" ]; then 149 | delete_config "$_server" 150 | echo "${COLOR_GREEN}Succesfully removed server configuration \"${_server}\".${COLOR_NC}" 151 | else 152 | echo "${COLOR_RED}Server configuration \"${_server}\" does not exist.${COLOR_NC}" 153 | fi 154 | } 155 | 156 | list_servers() 157 | { 158 | find "${repository}" -mindepth 1 -maxdepth 1 -type d | rev | cut -d '/' -f 1 | rev 159 | } 160 | 161 | show_server() 162 | { 163 | _server=$1 164 | 165 | _type=$(cat "${config}/connectiontype") 166 | 167 | echo "${colored_alias}" 168 | echo "${COLOR_LIGHT_BLUE}Connection${COLOR_NC} ${_type}" 169 | 170 | if [ -f "${ssh_config}" ]; then 171 | _ssh=$(cat "${ssh_config}") 172 | echo "${COLOR_LIGHT_BLUE}SSH command${COLOR_NC} ssh ${_ssh}" 173 | fi 174 | 175 | _user=$(cat "${connection_config}" | grep "^user" | cut -d '=' -f 2 | xargs) 176 | _pass=$(cat "${connection_config}" | grep "^password" | cut -d '=' -f 2 | xargs) 177 | _host=$(cat "${connection_config}" | grep "^host" | cut -d '=' -f 2 | xargs) 178 | _port=$(cat "${connection_config}" | grep "^port" | cut -d '=' -f 2 | xargs) 179 | echo "${COLOR_LIGHT_BLUE}Username${COLOR_NC} ${_user}" 180 | echo "${COLOR_LIGHT_BLUE}Password${COLOR_NC} ${_pass}" 181 | echo "${COLOR_LIGHT_BLUE}Hostname${COLOR_NC} ${_host}" 182 | echo "${COLOR_LIGHT_BLUE}Port${COLOR_NC} ${_port}" 183 | 184 | _database=$(cat "${config}/database") 185 | echo "${COLOR_LIGHT_BLUE}Database${COLOR_NC} ${_database}" 186 | } 187 | 188 | if [ "${subcommand}" != "add" ] && [ "${subcommand}" != "rm" ] && [ "${subcommand}" != "list" ] && [ "${subcommand}" != "show" ]; then 189 | echo "USAGE:" 190 | echo "" 191 | echo " ${COLOR_LIGHT_BLUE}db server add (server alias) (ssh) (hostname) (port) (username) (password) (database)${COLOR_NC}" 192 | echo "" 193 | echo " Adds a server to the repository. All parameters are optional." 194 | echo "" 195 | echo " ${COLOR_LIGHT_BLUE}db server rm [server alias]${COLOR_NC}" 196 | echo "" 197 | echo " Removes a server and all its snapshots from the repository." 198 | echo "" 199 | echo " ${COLOR_LIGHT_BLUE}db server show [server alias]${COLOR_NC}" 200 | echo "" 201 | echo " Displays a server's configuration." 202 | echo "" 203 | echo " ${COLOR_LIGHT_BLUE}db server list${COLOR_NC}" 204 | echo "" 205 | echo " Shows a list of available servers." 206 | echo "" 207 | fi 208 | 209 | case ${subcommand} in 210 | add) 211 | add_server 212 | exit 0 213 | ;; 214 | 215 | rm) 216 | rm_server "${server}" 217 | exit 0 218 | ;; 219 | 220 | show) 221 | show_server "${server}" 222 | exit 0 223 | ;; 224 | 225 | list) 226 | list_servers 227 | exit 0 228 | ;; 229 | esac 230 | -------------------------------------------------------------------------------- /bin/db-show: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | . "${bin}/include/all" 4 | snapshot="${3}" 5 | 6 | find_snapshot_from_any_server "${snapshot}" 7 | 8 | if [ -z "${3}" ]; then 9 | echo "USAGE:" 10 | echo " ${COLOR_LIGHT_BLUE}db show [snapshot]${COLOR_NC}" 11 | echo "" 12 | echo "Displays the SQL contents of a snapshot" 13 | exit 1 14 | fi 15 | 16 | if [ -f "${file}" ]; then 17 | gunzip < "${file}" 18 | exit 0 19 | else 20 | echo "${colored_alias}Cannot display ${COLOR_LIGHT_BLUE}${snapshot}${COLOR_NC} - snapshot does not exist" 21 | exit 1 22 | fi 23 | -------------------------------------------------------------------------------- /bin/db-structure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | 4 | . "${bin}/include/all" 5 | snapshot="${3}" 6 | 7 | if [ -z "${3}" ]; then 8 | echo "USAGE:" 9 | echo " ${COLOR_LIGHT_BLUE}db structure [snapshot]${COLOR_NC}" 10 | echo "" 11 | echo "Displays the table structure of a snapshot" 12 | exit 1 13 | fi 14 | 15 | find_snapshot_from_any_server "${snapshot}" 16 | 17 | 18 | if [ -f "${file}" ]; then 19 | extract="${bin}/drivers/mysql/vendor/extract_sql/extract_sql.pl" 20 | tables=$(gunzip -c "${file}" | ${extract} --listTables) 21 | 22 | for table in $tables 23 | do 24 | gunzip -c "${file}" | ${extract} -t "${table}" | grep -v "^--" | grep -v "^/\*" | grep -v "^$" | grep -v "INSERT INTO" | grep -v "^LOCK TABLES" | grep -v "^UNLOCK TABLES" | grep -v "^DROP" 25 | done 26 | exit 0 27 | else 28 | echo "${colored_alias}Cannot display structure of ${COLOR_LIGHT_BLUE}${snapshot}${COLOR_NC} - snapshot does not exist" 29 | exit 1 30 | fi 31 | -------------------------------------------------------------------------------- /bin/db-test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | . "${bin}/include/all" 4 | 5 | if [ -z "${2}" ]; then 6 | echo "USAGE:" 7 | echo " ${COLOR_LIGHT_BLUE}db test [server alias]${COLOR_NC}" 8 | echo "" 9 | echo "Tries to connect to the provided database server" 10 | exit 1 11 | fi 12 | 13 | echo "${colored_alias}" 14 | "${bin}/drivers/${databasetype}/connectors/${connectiontype}/test" "$@" 15 | exit $? -------------------------------------------------------------------------------- /bin/drivers/mysql/connectors/direct/load: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | restore_table() { 4 | table=$1 5 | 6 | extract="${bin}/drivers/mysql/vendor/extract_sql/extract_sql.pl" 7 | 8 | file "${file}" | grep gzip > /dev/null 9 | is_gzip="${?}" 10 | 11 | if [ "${is_gzip}" -eq 0 ]; then 12 | # this is a gzipped file 13 | gunzip < "${file}" | "${extract}" -t "${table}" | "${mysql}" ${connection_details} "${database}" 14 | else 15 | # this is a regular .sql file 16 | cat "${file}" | "${extract}" -t "${table}" | "${mysql}" ${connection_details} "${database}" 17 | fi 18 | 19 | code="${?}" 20 | } 21 | 22 | restore_snapshot() { 23 | file "${file}" | grep gzip > /dev/null 24 | is_gzip="${?}" 25 | 26 | if [ "${is_gzip}" -eq 0 ]; then 27 | # this is a gzipped file 28 | gunzip < "${file}" | grep -v "DROP DATABASE"| grep -v "^CREATE DATABASE" | grep -v "^USE \`" | "${mysql}" ${connection_details} "${database}" 29 | else 30 | # this is a regular .sql file 31 | cat "${file}" | grep -v "DROP DATABASE"| grep -v "^CREATE DATABASE" | grep -v "^USE \`" | "${mysql}" ${connection_details} "${database}" 32 | fi 33 | 34 | code="${?}" 35 | } 36 | -------------------------------------------------------------------------------- /bin/drivers/mysql/connectors/direct/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | run_sql() { 4 | sql=$1 5 | connection_details="--defaults-extra-file=${connection_config}" 6 | 7 | output=$(echo "${sql}" | "${mysql}" ${connection_details} --skip-column-names) 8 | code=${?} 9 | } -------------------------------------------------------------------------------- /bin/drivers/mysql/connectors/direct/save: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | . "${bin}/include/all" 4 | 5 | file=$3 6 | 7 | options="--opt -B --add-drop-database" 8 | connection_details="--defaults-extra-file=${connection_config}" 9 | 10 | "${mysqldump}" ${connection_details} ${options} "${database}" | gzip > "${file}" 11 | exit $? -------------------------------------------------------------------------------- /bin/drivers/mysql/connectors/direct/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | . "${bin}/include/all" 4 | 5 | connection_details="--defaults-extra-file=${connection_config}" 6 | 7 | "${mysql}" ${connection_details} -e"quit" >/dev/null 8 | code=${?} 9 | 10 | if [ "${code}" -eq 127 ]; then 11 | echo "${COLOR_RED}Could not find mysql binary.${COLOR_NC}" 12 | else 13 | if [ "${code}" -eq 1 ]; then 14 | echo "${COLOR_RED}Could not connect to database.${COLOR_NC}" 15 | else 16 | echo "${COLOR_GREEN}Successfully connected to database.${COLOR_NC}" 17 | fi 18 | fi 19 | exit ${code} -------------------------------------------------------------------------------- /bin/drivers/mysql/connectors/ssh/load: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | restore_table() { 4 | table=$1 5 | 6 | _config="${repository}/${identifier}/config/" 7 | _credentials="${_config}credentials.cnf" 8 | _ssh_config="${_config}/ssh.cnf" 9 | _db_file="${_config}/database" 10 | 11 | where=$(cat "${_ssh_config}") 12 | _user=$(printf %q $(cat "${_credentials}" | grep "^user" | cut -d '=' -f 2 | xargs)) 13 | _pass=$(printf %q $(cat "${_credentials}" | grep "^password" | cut -d '=' -f 2 | xargs)) 14 | _host=$(printf %q $(cat "${_credentials}" | grep "^host" | cut -d '=' -f 2 | xargs)) 15 | _port=$(printf %q $(cat "${_credentials}" | grep "^port" | cut -d '=' -f 2 | xargs)) 16 | _database=$(cat "${_db_file}") 17 | 18 | extract="${bin}/drivers/mysql/vendor/extract_sql/extract_sql.pl" 19 | 20 | file "${file}" | grep gzip > /dev/null 21 | is_gzip="${?}" 22 | 23 | cmd="mysql --user='${_user}' --host='${_host}' --port='${_port}' --password='${_pass}' ${_database}" 24 | 25 | if [ "${is_gzip}" -eq 0 ]; then 26 | # this is a gzipped file 27 | gunzip < "${file}" | "${extract}" -t "${table}" | ssh ${where} "nice -n 19 sh -c '${cmd}'" 28 | else 29 | # this is a regular .sql file 30 | cat "${file}" | "${extract}" -t "${table}" | ssh ${where} "nice -n 19 sh -c '${cmd}'" 31 | fi 32 | 33 | code="${?}" 34 | } 35 | 36 | restore_snapshot() { 37 | _config="${repository}/${identifier}/config/" 38 | _credentials="${_config}credentials.cnf" 39 | _ssh_config="${_config}/ssh.cnf" 40 | _db_file="${_config}/database" 41 | 42 | where=$(cat "${_ssh_config}") 43 | _user=$(printf %q $(cat "${_credentials}" | grep "^user" | cut -d '=' -f 2 | xargs)) 44 | _pass=$(printf %q $(cat "${_credentials}" | grep "^password" | cut -d '=' -f 2 | xargs)) 45 | _host=$(printf %q $(cat "${_credentials}" | grep "^host" | cut -d '=' -f 2 | xargs)) 46 | _port=$(printf %q $(cat "${_credentials}" | grep "^port" | cut -d '=' -f 2 | xargs)) 47 | _database=$(cat "${_db_file}") 48 | 49 | file "${file}" | grep gzip > /dev/null 50 | is_gzip="${?}" 51 | 52 | cmd="mysql --user='${_user}' --host='${_host}' --port='${_port}' --password='${_pass}' ${_database}" 53 | 54 | if [ "${is_gzip}" -eq 0 ]; then 55 | # this is a gzipped file 56 | gunzip < "${file}" | grep -v "DROP DATABASE"| grep -v "^CREATE DATABASE" | grep -v "^USE \`" | ssh ${where} "nice -n 19 sh -c '${cmd}'" 57 | else 58 | # this is a regular .sql file 59 | cat "${file}" | grep -v "DROP DATABASE"| grep -v "^CREATE DATABASE" | grep -v "^USE \`" | ssh ${where} "nice -n 19 sh -c '${cmd}'" 60 | fi 61 | 62 | code="${?}" 63 | } -------------------------------------------------------------------------------- /bin/drivers/mysql/connectors/ssh/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | run_sql() { 4 | sql=$1 5 | 6 | connection_details="--defaults-extra-file=${connection_config}" 7 | 8 | where=$(cat "${ssh_config}") 9 | _user=$(cat "${connection_config}" | grep "^user" | cut -d '=' -f 2 | xargs) 10 | _pass=$(cat "${connection_config}" | grep "^password" | cut -d '=' -f 2 | xargs) 11 | _host=$(cat "${connection_config}" | grep "^host" | cut -d '=' -f 2 | xargs) 12 | _port=$(cat "${connection_config}" | grep "^port" | cut -d '=' -f 2 | xargs) 13 | 14 | output=$(ssh ${where} "echo \"$sql\" | mysql --user=\"$_user\" --host=\"$_host\" --port=\"${_port}\" --password=\"$_pass\" $database --skip-column-names ") 15 | code=${?} 16 | } 17 | -------------------------------------------------------------------------------- /bin/drivers/mysql/connectors/ssh/save: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | . "${bin}/include/all" 4 | 5 | file=$3 6 | 7 | connection_details="--defaults-extra-file=${connection_config}" 8 | 9 | where=$(cat "${ssh_config}") 10 | # We do NOT escape usernames etc here, because in the final command we will wrap them in single quotes (') which don't need escapes 11 | _user=$(cat "${connection_config}" | grep "^user" | cut -d '=' -f 2 | xargs | sed "s/'/''/g") # last part escapes ' to '' 12 | _pass=$(cat "${connection_config}" | grep "^password" | cut -d '=' -f 2 | xargs | sed "s/'/''/g") 13 | _host=$(cat "${connection_config}" | grep "^host" | cut -d '=' -f 2 | xargs | sed "s/'/''/g") 14 | _port=$(cat "${connection_config}" | grep "^port" | cut -d '=' -f 2 | xargs | sed "s/'/''/g") 15 | 16 | # define mysqldump command 17 | dump='mysqldump --opt -B --add-drop-database --skip-extended-insert --user=''\$1'' --host=''\$2'' --port=''\$3'' --password=''\$4'' ''\$5''' 18 | 19 | # check if we have a 'normal' password or one that needs to be escaped 20 | _user_escaped=$(printf %q $_user) 21 | _pass_escaped=$(printf %q $_pass) 22 | 23 | # I am too stupid to write one command line that handles 'simple' and 'complicated' usernames and passwords at the 24 | # same time. Whichever version I write, something will break. Luckily I can figure out *when* it goes wrong, so I 25 | # will just work around it. Sorry. 26 | if [ "$_user_escaped$_pass_escaped" = "$_user$_pass" ]; then 27 | # "Normal" passwords 28 | cmd="nice -n 19 sh -c \"${dump}\" some_name '${_user}' '${_host}' '${_port}' '${_pass}' '${database}'" 29 | else 30 | # "Complicated" passwords (that contain characters that are relevant for the shell) 31 | cmd="nice -n 19 sh -c \"'${dump}' some_name '${_user}' '${_host}' '${_port}' '${_pass}' '${database}'\"" 32 | fi 33 | 34 | # somehow, somewhere, I messed up gzipping on the server. If I add it to the ${dump} command, 35 | # we end up with a broken .gz file. I don't know why. 36 | ssh ${where} ${cmd} | gzip > "${file}" 37 | 38 | exit $? -------------------------------------------------------------------------------- /bin/drivers/mysql/connectors/ssh/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | . "${bin}/include/all" 4 | 5 | connection_details="--defaults-extra-file=${connection_config}" 6 | 7 | where=$(cat "${ssh_config}") 8 | _user=$(printf %q $(cat "${connection_config}" | grep "^user" | cut -d '=' -f 2 | xargs)) 9 | _pass=$(printf %q $(cat "${connection_config}" | grep "^password" | cut -d '=' -f 2 | xargs)) 10 | _host=$(printf %q $(cat "${connection_config}" | grep "^host" | cut -d '=' -f 2 | xargs)) 11 | _port=$(printf %q $(cat "${connection_config}" | grep "^port" | cut -d '=' -f 2 | xargs)) 12 | 13 | ssh ${where} "mysql --user=\"$_user\" --host=\"$_host\" --port=\"${_port}\" --password=\"$_pass\" -e\"quit\" $database" 14 | code=${?} 15 | 16 | if [ "${code}" -eq 127 ]; then 17 | echo "${COLOR_RED}Could not find mysql binary.${COLOR_NC}" 18 | else 19 | if [ "${code}" -eq 1 ]; then 20 | echo "${COLOR_RED}Could not connect to database.${COLOR_NC}" 21 | else 22 | echo "${COLOR_GREEN}Successfully connected to database.${COLOR_NC}" 23 | fi 24 | fi 25 | exit ${code} -------------------------------------------------------------------------------- /bin/drivers/mysql/vendor/extract_sql/extract_sql.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | ############################################################################## 3 | ## 4 | ## Written by: Jared Cheney 5 | ## 6 | ## Original Template written by: 7 | ## Brandon Zehm and Jared Cheney 8 | ## 9 | ## License: 10 | ## 11 | ## This (hereafter referred to as "program") is free software; 12 | ## you can redistribute it and/or modify it under the terms of the GNU General 13 | ## Public License as published by the Free Software Foundation; either version 14 | ## 2 of the License, or (at your option) any later version. 15 | ## Note that when redistributing modified versions of this source code, you 16 | ## must ensure that this disclaimer and the above coder's names are included 17 | ## VERBATIM in the modified code. 18 | ## 19 | ## Disclaimer: 20 | ## This program is provided with no warranty of any kind, either expressed or 21 | ## implied. It is the responsibility of the user (you) to fully research and 22 | ## comprehend the usage of this program. As with any tool, it can be misused, 23 | ## either intentionally (you're a vandal) or unintentionally (you're a moron). 24 | ## THE AUTHOR(S) IS(ARE) NOT RESPONSIBLE FOR ANYTHING YOU DO WITH THIS PROGRAM 25 | ## or anything that happens because of your use (or misuse) of this program, 26 | ## including but not limited to anything you, your lawyers, or anyone else 27 | ## can dream up. And now, a relevant quote directly from the GPL: 28 | ## 29 | ## NO WARRANTY 30 | ## 31 | ## 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 32 | ## FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 33 | ## OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 34 | ## PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 35 | ## OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 36 | ## MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 37 | ## TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 38 | ## PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 39 | ## REPAIR OR CORRECTION. 40 | ## 41 | ############################################################################## 42 | # Written by: Jared Cheney 43 | # 44 | # Purpose: This program will extract the necessary portions from a full 45 | # database mysqldump file required to restore a single table. 46 | # 47 | # Creation Date: 2008-05-23 48 | # 49 | # Changelog: 50 | # 2008-05-23 v1.0 Jared Cheney 51 | # - initial release 52 | # 53 | ############################################################################# 54 | 55 | ## FIXME's: 56 | 57 | use strict; 58 | 59 | 60 | ## Global Variable(s) 61 | my %conf = ( 62 | "programName" => $0, ## The name of this program 63 | "version" => '1.0', ## The version of this program 64 | "authorName" => 'Jared Cheney', ## Author's Name 65 | "authorEmail" => 'jared.cheney@gmail.com', ## Author's Email Address 66 | "debug" => 0, ## Default debug level 67 | "mode" => '', 68 | 69 | 70 | ## PROGRAM VARIABLES 71 | "logFile" => '', ## default log file, if none specified on command line 72 | "prepend" => '', ## Something that gets added to every msg that the script outputs 73 | "alertCommand" => '', ## cmd to run if printmsg() contains the string 'ERR' or 'CRIT' or 'WARN' 74 | "noExtras" => 0, ## if 1, then we'll skip extra cmds for disabling foreign key checks, etc. at top of file 75 | "listTables" => 0, ## if 1, then return a list of tables contained in the restore file 76 | ); 77 | 78 | $conf{'programName'} =~ s/(.)*[\/,\\]//; ## Remove path from filename 79 | $0 = "[$conf{'programName'}]"; 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | ############################# 90 | ## 91 | ## MAIN PROGRAM 92 | ## 93 | ############################# 94 | 95 | ## Initialize 96 | initialize(); 97 | 98 | ## Process Command Line 99 | processCommandLine(); 100 | 101 | 102 | ## get current timestamp for use later 103 | my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(); 104 | $mon +=1; 105 | if ($mon < 10) {$mon = "0" . $mon} 106 | if ($mday < 10) {$mday = "0" . $mday} 107 | $year +=1900; 108 | printmsg ("current date is $mon/$mday/$year",3); 109 | 110 | 111 | if ($conf{'mode'} eq "running") { 112 | printmsg ("INFO => PROGRAM STARTED",1); 113 | 114 | ############################# 115 | ######## MAIN CODE #### 116 | ############################# 117 | 118 | if ($conf{'restoreFile'}) { 119 | ## open the mysqldump file 120 | open(STDIN, "<$conf{'restoreFile'}") || quit("ERROR => Couldn't open file $conf{'restoreFile'}: $!", 3); 121 | } 122 | 123 | my $flag = 0; 124 | 125 | ## go through the file one line at a time 126 | while (my $line = ) { 127 | 128 | if ($conf{'listTables'}) { 129 | if ($line =~ /^-- Table structure for table `(.*)`/) { 130 | print $1 . "\n"; 131 | } 132 | } 133 | else { 134 | 135 | ## if we're not ignoring extra lines, and we haven't set the flag, and if it's not a 40000 code, then print 136 | if (!$conf{'noExtras'} && !$flag) { 137 | if ($line =~ /^\/\*!(.....).*\*\//) { print $line unless ($1 == 40000); } 138 | } 139 | 140 | ## set a flag when we encounter the table we want 141 | if ($line =~ /^-- Table structure for table `$conf{'tableName'}`/) { 142 | $flag = 1; 143 | printmsg("Turning flag on", 1); 144 | } 145 | ## turn flag off as soon as we encounter next table definition 146 | elsif ($line =~ /^-- Table structure for table/) { 147 | $flag = 0; 148 | printmsg("Turning flag off", 1); 149 | } 150 | 151 | ## if flag is set, then print to STDOUT, otherwise just move on 152 | if ($flag) { 153 | print $line; 154 | } 155 | } 156 | } 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | ############################# 165 | ######## END MAIN CODE #### 166 | ############################# 167 | 168 | } 169 | 170 | ## Quit 171 | quit("",0); 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | ###################################################################### 191 | ## Function: help () 192 | ## 193 | ## Description: For all those newbies ;) 194 | ## Prints a help message and exits the program. 195 | ## 196 | ###################################################################### 197 | sub help { 198 | print < 201 | 202 | This program will parse a full mysqldump file and 203 | extract the necessary portions required to restore 204 | a single table. The output is printed to STDOUT, so you'll 205 | want to redirect to a file from the command line, like so: 206 | $conf{'programName'} > somefile.sql 207 | 208 | Brought to you by the fine tech folk at www.tsheets.com - Time Is Money, Track It! 209 | 210 | Usage: $conf{'programName'} -t -r [options] 211 | 212 | Required: 213 | -t
table name to extract from the file 214 | 215 | 216 | Optional: 217 | -r mysqldump file that you want to parse. If not specified, 218 | then it reads from STDIN 219 | --listTables If set, then a list of tables existing in your restore file is returned, 220 | and no other actions are taken 221 | --noExtras If set, then extra cmds at top of mysqldump file 222 | will not be included (such as disabling foreign key checks). 223 | Usually you will want these things changed before restoring a 224 | table, so the default is for these to be included. 225 | -v verbosity - use multiple times for greater effect 226 | -h Display this help message 227 | 228 | 229 | 230 | EOM 231 | exit(1); 232 | } 233 | 234 | 235 | 236 | 237 | 238 | ###################################################################### 239 | ## Function: initialize () 240 | ## 241 | ## Does all the script startup jibberish. 242 | ## 243 | ###################################################################### 244 | sub initialize { 245 | 246 | ## Set STDOUT to flush immediatly after each print 247 | $| = 1; 248 | 249 | ## Intercept signals 250 | $SIG{'QUIT'} = sub { quit("$$ - $conf{'programName'} - EXITING: Received SIG$_[0]", 1); }; 251 | $SIG{'INT'} = sub { quit("$$ - $conf{'programName'} - EXITING: Received SIG$_[0]", 1); }; 252 | $SIG{'KILL'} = sub { quit("$$ - $conf{'programName'} - EXITING: Received SIG$_[0]", 1); }; 253 | $SIG{'TERM'} = sub { quit("$$ - $conf{'programName'} - EXITING: Received SIG$_[0]", 1); }; 254 | 255 | ## ALARM and HUP signals are not supported in Win32 256 | unless ($^O =~ /win/i) { 257 | $SIG{'HUP'} = sub { quit("$$ - $conf{'programName'} - EXITING: Received SIG$_[0]", 1); }; 258 | $SIG{'ALRM'} = sub { quit("$$ - $conf{'programName'} - EXITING: Received SIG$_[0]", 1); }; 259 | } 260 | 261 | return(1); 262 | } 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | ###################################################################### 272 | ## Function: processCommandLine () 273 | ## 274 | ## Processes command line storing important data in global var %conf 275 | ## 276 | ###################################################################### 277 | sub processCommandLine { 278 | 279 | 280 | ############################ 281 | ## Process command line ## 282 | ############################ 283 | 284 | my $x; 285 | my @ARGS = @ARGV; 286 | my $numargv = scalar(@ARGS); 287 | help() unless ($numargv); 288 | for (my $i = 0; $i < $numargv; $i++) { 289 | $x = $ARGS[$i]; 290 | if ($x =~ /^-h$|^--help$/) { help(); } 291 | elsif ($x =~ /^-v+/i) { my $tmp = (length($&) - 1); $conf{'debug'} += $tmp; } 292 | elsif ($x =~ /^-l$/) { $i++; $conf{'logFile'} = $ARGS[$i];} 293 | elsif ($x =~ /^-p$/) { $i++; $conf{'policyName'} = $ARGS[$i];} 294 | elsif ($x =~ /^-t$/) { $i++; $conf{'tableName'} = $ARGS[$i];} 295 | elsif ($x =~ /^-r$/) { $i++; $conf{'restoreFile'}= $ARGS[$i];} 296 | elsif ($x =~ /^--noExtras$/i) { $conf{'noExtras'} = 1; } 297 | elsif ($x =~ /^--listTables$/i) { $conf{'listTables'} = 1; } 298 | else { 299 | printmsg("Error: \"$x\" is not a recognised option!", 0); 300 | help(); 301 | } 302 | } 303 | 304 | my @required = ( 305 | 'tableName', 306 | ); 307 | 308 | if ($conf{'listTables'}) { 309 | $conf{'mode'} = 'running'; 310 | return(1); 311 | } 312 | 313 | foreach (@required) { 314 | if (!$conf{$_}) { 315 | quit("ERROR: Value [$_] was not set after parsing command line arguments!", 1); 316 | } 317 | } 318 | $conf{'mode'} = 'running'; 319 | return(1); 320 | } 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | ############################################################################################### 338 | ## Function: printmsg (string $message, int $level) 339 | ## 340 | ## Description: Handles all messages - logging them to a log file, 341 | ## printing them to the screen or both depending on 342 | ## the $level passed in, $conf{'debug'} and wether 343 | ## $conf{'mode'}. 344 | ## 345 | ## Input: $message A message to be printed, logged, etc. 346 | ## $level The debug level of the message. If 347 | ## not defined 0 will be assumed. 0 is 348 | ## considered a normal message, 1 and 349 | ## higher is considered a debug message. 350 | ## $leaveCarriageReturn Whether or not to strip carriage returns (always will strip, unless other than 0) 351 | ## 352 | ## Output: Prints to STDOUT, to LOGFILE, both, or none depending 353 | ## on the state of the program. 354 | ## 355 | ## Example: printmsg ("WARNING: We believe in generic error messages... NOT!", 1); 356 | ############################################################################################### 357 | sub printmsg { 358 | my %incoming = (); 359 | ( 360 | $incoming{'message'}, 361 | $incoming{'level'}, 362 | $incoming{'leaveCarriageReturn'}, 363 | ) = @_; 364 | $incoming{'level'} = 0 if (!defined($incoming{'level'})); 365 | $incoming{'leaveCarriageReturn'} = 0 if (!defined($incoming{'leaveCarriageReturn'})); 366 | $incoming{'message'} =~ s/\r|\n/ /sg unless ($incoming{'leaveCarriageReturn'} >= 1); 367 | 368 | ## Add program name and PID 369 | ## $incoming{'message'} = "- $conf{'programName'} [$$]: " . $incoming{'message'}; 370 | ## add prepend info 371 | ## $incoming{'message'} = "$conf{'prepend'} : $incoming{'message'}"; 372 | 373 | ## Continue on if the debug level is >= the incoming message level 374 | if ($conf{'debug'} >= $incoming{'level'}) { 375 | ## Print to the log file 376 | if ($conf{'logFile'}) { 377 | open (LOGFILE, ">>$conf{'logFile'}"); 378 | print LOGFILE "$conf{'programName'}:[". localtime() . "] $incoming{'message'}\n"; 379 | close (LOGFILE); 380 | } 381 | if ($conf{'alertCommand'} && ($conf{'debug'} == 0) && ($incoming{'message'} =~ /ERR|CRIT|WARN/) ) { 382 | my $tmpAlert = $conf{'alertCommand'}; 383 | $tmpAlert =~ s/MESSAGE/$incoming{'message'}/g; 384 | system ($tmpAlert); 385 | } 386 | ## Print to STDOUT 387 | if ($conf{'debug'} >= 1) { 388 | print STDOUT "$conf{'programName'}:[" . localtime() . "]($incoming{'level'}): $incoming{'message'}\n"; 389 | } 390 | else { 391 | print STDOUT "$conf{'programName'}:[" . localtime() . "] $incoming{'message'}\n"; 392 | } 393 | } 394 | 395 | ## Return 396 | return(0); 397 | } 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | ###################################################################### 408 | ## Function: quit (string $message, int $errorLevel) 409 | ## 410 | ## Description: Exits the program, optionally printing $message. It 411 | ## returns an exit error level of $errorLevel to the 412 | ## system (0 means no errors, and is assumed if empty.) 413 | ## 414 | ## Example: quit("Exiting program normally", 0); 415 | ###################################################################### 416 | sub quit { 417 | my %incoming = (); 418 | ( 419 | $incoming{'message'}, 420 | $incoming{'errorLevel'} 421 | ) = @_; 422 | 423 | 424 | $incoming{'errorLevel'} = 0 if (!defined($incoming{'errorLevel'})); 425 | 426 | ## Print exit message 427 | if ($incoming{'message'}) { 428 | printmsg($incoming{'message'}, 0); 429 | } 430 | 431 | ## Exit 432 | exit($incoming{'errorLevel'}); 433 | } 434 | 435 | 436 | 437 | -------------------------------------------------------------------------------- /bin/include/all: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Get the directory where the main script is installed 4 | includes="${bin}/include" 5 | 6 | . "${includes}/colors" 7 | . "${includes}/vars" 8 | . "${includes}/functions" 9 | 10 | boot -------------------------------------------------------------------------------- /bin/include/colors: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | COLOR_NC='\033[0m' # No Color 4 | COLOR_WHITE='\033[1;37m' 5 | COLOR_BLACK='\033[0;30m' 6 | COLOR_BLUE='\033[0;34m' 7 | COLOR_LIGHT_BLUE='\033[1;34m' 8 | COLOR_GREEN='\033[0;32m' 9 | COLOR_LIGHT_GREEN='\033[1;32m' 10 | COLOR_CYAN='\033[0;36m' 11 | COLOR_LIGHT_CYAN='\033[1;36m' 12 | COLOR_RED='\033[0;31m' 13 | COLOR_LIGHT_RED='\033[1;31m' 14 | COLOR_PURPLE='\033[0;35m' 15 | COLOR_LIGHT_PURPLE='\033[1;35m' 16 | COLOR_BROWN='\033[0;33m' 17 | COLOR_YELLOW='\033[1;33m' 18 | COLOR_GRAY='\033[0;30m' 19 | COLOR_LIGHT_GRAY='\033[0;37m' 20 | -------------------------------------------------------------------------------- /bin/include/functions: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | user_input() 4 | { 5 | prompt=$1 6 | error_msg=$2 7 | default=$3 8 | allow_empty=$4 9 | 10 | while true; do 11 | read -p "${prompt} [${default}]: " answer 12 | 13 | if [ -z "${answer}" ]; then 14 | # if answer is empty... 15 | 16 | if [ ! -z "${default}" ]; then 17 | # if a default value has been provided 18 | answer=${default} 19 | break 20 | fi 21 | 22 | if [ "${allow_empty}" = 1 ]; then 23 | # if we allow empty answers 24 | break 25 | fi 26 | 27 | echo "${error_msg}" 28 | else 29 | # answer is not empty 30 | break 31 | fi 32 | 33 | done 34 | } 35 | 36 | find_snapshot() 37 | { 38 | snapshot=$1 39 | file="${dumps}/${snapshot}.gz" 40 | 41 | if [ ! -f "${file}" ]; then 42 | if [ $(find "${dumps}" | grep "${snapshot}" | wc -l | sed -e 's/^[[:space:]]*//') -eq 1 ]; then 43 | file=$(find "${dumps}" | grep "${snapshot}") 44 | else 45 | echo "${COLOR_LIGHT_BLUE}Provided snapshot name is not unique or does not exist. Aborting.${COLOR_NC}" 46 | exit 1 47 | fi 48 | fi 49 | } 50 | 51 | find_snapshot_from_any_server() 52 | { 53 | snapshot=$1 54 | file="${dumps}/${snapshot}.gz" 55 | 56 | if [ ! -z "${snapshot}" ]; then 57 | if [ ! -f "${file}" ]; then 58 | if [ $(find "${repository}/" | grep -c "dumps/${snapshot}") -eq 1 ]; then 59 | file=$(find "${repository}" | grep "dumps/${snapshot}") 60 | else 61 | echo "${COLOR_LIGHT_BLUE}Provided snapshot, \"${snapshot}\", is not unique or does not exist. Aborting.${COLOR_NC}" 62 | exit 1 63 | fi 64 | fi 65 | fi 66 | } 67 | 68 | escape_commandline() 69 | { 70 | cmd=$1 71 | escaped=$(printf %q "${cmd}" | sed "s/\\\'/\'\\\'\'/g") # replaces \' with '\'' - see https://stackoverflow.com/a/33949338 72 | } 73 | 74 | boot() 75 | { 76 | if [ ! -d "${meta}" ]; then 77 | mkdir -p "${meta}" 78 | fi 79 | } -------------------------------------------------------------------------------- /bin/include/vars: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Get the project directory, i.e. the directory where the repository lives 4 | project_root="${1}" 5 | identifier="${2}" 6 | 7 | repository="${project_root}/.db" 8 | 9 | databasetype="mysql" 10 | if [ -f "${repository}/databasetype" ]; then 11 | databasetype=$(cat "${repository}/databasetype") 12 | fi 13 | 14 | config="${repository}/${identifier}/config" 15 | database_file="${config}/database" 16 | 17 | database="" 18 | if [ -f "${database_file}" ]; then 19 | database=$(cat "${database_file}") 20 | fi 21 | connection_config="${config}/credentials.cnf" 22 | connectiontype="direct" 23 | if [ -f "${config}/connectiontype" ]; then 24 | connectiontype=$(cat "${config}/connectiontype") 25 | fi 26 | 27 | ssh_config="${config}/ssh.cnf" 28 | 29 | dumps="${repository}/${identifier}/dumps" 30 | meta="${repository}/${identifier}/meta" 31 | 32 | colored_alias="[${COLOR_YELLOW}${identifier}${COLOR_NC}] " 33 | 34 | mysqldump=`which mysqldump` 35 | mysql=`which mysql` 36 | 37 | servers="localhost" 38 | if [ -d "${repository}" ]; then 39 | servers=$(find "${repository}" -mindepth 1 -maxdepth 1 -type d | xargs -n1 basename) 40 | fi -------------------------------------------------------------------------------- /db: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | command="${1}" 4 | if [ "${command}" = "rm" ]; then 5 | command="remove" 6 | fi 7 | if [ "$#" -gt 0 ]; then shift; fi 8 | subcommand="${1}" 9 | 10 | # Try to figure out what the project root directory is 11 | project_root=$(pwd) 12 | while [ ! -d "${project_root}/.db" ]; do 13 | 14 | if [ "${project_root}" = "/" ]; then 15 | if [ "${command}" = "init" ]; then 16 | project_root=$(pwd) 17 | break 18 | else 19 | echo "fatal: Not a db repository (or any of the parent directories). Please run 'db init'."; 20 | exit 2 21 | fi 22 | fi 23 | 24 | project_root=$(cd "${project_root}/.." && pwd) 25 | done 26 | 27 | 28 | rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`. 29 | 30 | target=$1 fname= targetDir= CDPATH= 31 | 32 | # Try to make the execution environment as predictable as possible: 33 | # All commands below are invoked via `command`, so we must make sure that `command` 34 | # itself is not redefined as an alias or shell function. 35 | # (Note that command is too inconsistent across shells, so we don't use it.) 36 | # `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not even have 37 | # an external utility version of it (e.g, Ubuntu). 38 | # `command` bypasses aliases and shell functions and also finds builtins 39 | # in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for that 40 | # to happen. 41 | { \unalias command; \unset -f command; } >/dev/null 2>&1 42 | [ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too. 43 | 44 | while :; do # Resolve potential symlinks until the ultimate target is found. 45 | [ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; } 46 | command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path. 47 | fname=$(command basename -- "$target") # Extract filename. 48 | [ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/' 49 | if [ -L "$fname" ]; then 50 | # Extract [next] target path, which may be defined 51 | # *relative* to the symlink's own directory. 52 | # Note: We parse `ls -l` output to find the symlink target 53 | # which is the only POSIX-compliant, albeit somewhat fragile, way. 54 | target=$(command ls -l "$fname") 55 | target=${target#* -> } 56 | continue # Resolve [next] symlink target. 57 | fi 58 | break # Ultimate target reached. 59 | done 60 | targetDir=$(command pwd -P) # Get canonical dir. path 61 | # Output the ultimate target's canonical path. 62 | # Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path. 63 | if [ "$fname" = '.' ]; then 64 | command printf '%s\n' "${targetDir%/}" 65 | elif [ "$fname" = '..' ]; then 66 | # Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied 67 | # AFTER canonicalization. 68 | command printf '%s\n' "$(command dirname -- "${targetDir}")" 69 | else 70 | command printf '%s\n' "${targetDir%/}/$fname" 71 | fi 72 | ) 73 | 74 | # Get the directory where the main script (this one) is installed 75 | export installation_root=$(dirname -- "$(rreadlink "$0")") 76 | 77 | # Load the colors 78 | export bin="${installation_root}/bin" 79 | . "${bin}/include/colors" 80 | 81 | # Default server alias is the server that was added first. If no servers exist yet, use "localhost" 82 | alias="localhost" 83 | if [ -d "${project_root}/.db" ]; then 84 | configs=$(find "${project_root}/.db" -mindepth 1 -maxdepth 1 -type d | wc -l) 85 | if [ ${configs} -gt 0 ]; then 86 | alias=$(find "${project_root}/.db" -mindepth 1 -maxdepth 1 -type d -print0 | xargs -0 ls -1drt | head -n 1 | rev | cut -f 1 -d '/' | rev) 87 | else 88 | if [ "${command}" != "server" ] && [ "${subcommand}" != "add" ]; then 89 | echo "${COLOR_RED}No database servers defined!${COLOR_NC}" 90 | echo "Please add a server configuration by typing ${COLOR_LIGHT_BLUE}db server add${COLOR_NC}" 91 | exit 1 92 | fi 93 | fi 94 | fi 95 | 96 | # Figure out if an alias has been provided on the command line 97 | if [ "$#" -ge 1 ]; then 98 | # yes, perhaps 99 | if [ "${command}" = "server" ]; then 100 | # with the 'server' command, the alias is the third argument (server add XXX) 101 | if [ "$#" -ge 2 ]; then 102 | if [ -d "${project_root}/.db/${2}/config" ] || [ "${subcommand}" = "add" ]; then 103 | alias="${2}" 104 | fi 105 | fi 106 | else 107 | # with other commands, the alias is the second argument (snapshot XXX) 108 | if [ -d "${project_root}/.db/${1}/config" ]; then 109 | alias="${1}" 110 | shift 111 | fi 112 | fi 113 | fi 114 | 115 | # for synching database CONTENTS http://www.cri.ensmp.fr/~coelho/pg_comparator/pg_comparator.html 116 | # for synching database STRUCTURE http://mmatuson.github.io/SchemaSync/ or http://search.cpan.org/dist/MySQL-Diff/bin/mysqldiff 117 | 118 | cmd="${installation_root}/bin/db-${command}" 119 | if [ -f "${cmd}" ]; then 120 | ${cmd} "${project_root}" "${alias}" "$@" 121 | else 122 | commands=$(ls -1 "${installation_root}/bin" | grep "db-" | cut -d "-" -f 2 | paste -s -d "|" -) 123 | echo "USAGE:" 124 | echo " ${COLOR_BLUE}db (${commands})${COLOR_NC}" 125 | exit 1 126 | fi -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | With DB you can very easily save, restore, and archive snapshots of your database from the command line. It 4 | supports connecting to different database servers (for example a local development server and a staging or 5 | production server) and allows you to load a database dump from one environment into another environment. 6 | 7 | > For now, this is for MySQL only, but it could be extended to be used with other database systems as well. 8 | 9 | ## Table of Contents 10 | - [Introduction](#introduction) 11 | - [Examples](#examples) 12 | - [Installation](#installation) 13 | * [Additional configuration / different port or socket](#additional-configuration--different-port-or-socket) 14 | - [Available commands](#available-commands) 15 | * [db init](#db-init) 16 | * [db save](#db-save) 17 | * [db load](#db-load) 18 | * [db remove](#db-remove) 19 | * [db server](#db-server) 20 | + [db server add](#db-server-add) 21 | + [db server remove](#db-server-remove) 22 | + [db server show](#db-server-show) 23 | + [db server list](#db-server-list) 24 | * [db export](#db-export) 25 | * [db import](#db-import) 26 | * [db nuke](#db-nuke) 27 | * [db log](#db-log) 28 | * [db show](#db-show) 29 | * [db structure](#db-structure) 30 | * [db change](#db-change) 31 | + [db change charset](#db-change-charset) 32 | + [db change wordpress](#db-change-wordpress) 33 | * [db test](#db-test) 34 | 35 | ## Examples 36 | 37 | ```shell 38 | $ db save localhost "snapshot before running migrations" 39 | 40 | > [localhost] 41 | > Successfully made snapshot of database - hash f4f0c1d3fac74166c12a3708cdaa5d804dcd4b970c6e2789ccb23303 42 | ``` 43 | 44 | This will save a copy of your database to the repository. 45 | 46 | > By default, this repository lives in the ```.db``` folder in your project root. In this repository are gzipped database 47 | > dumps. Whether or not you want to commit the contents of that folder to ```git``` or another VCS is up to you. If you 48 | > do, make sure to exclude your database credentials by adding something like ```.db/*/config/credentials.cnf``` 49 | > (untested!) to your ```.gitignore```. 50 | 51 | Imagine the following scenario: after creating the local snapshot, you run a database migration. However, the migration 52 | breaks your database and deletes some stuff that you didn't want to delete. Usually, if you run a migration, there is 53 | also a way to undo that migration - but alas, that won't return your deleted data. No problem. To restore your database 54 | to the state it was in before you started the migration, simply run: 55 | 56 | ```shell 57 | $ db load localhost f4f0c1d3fac74166c12a3708cdaa5d804dcd4b970c6e2789ccb23303 58 | 59 | > [localhost] Loaded snapshot f4f0c1d3fac74166c12a3708cdaa5d804dcd4b970c6e2789ccb23303 60 | ``` 61 | 62 | This will load the snapshot you made before you ran the migration. It's like the migration never happened. 63 | 64 | There are many other things ```db``` can do, for example pulling down a database from the staging environment to 65 | your localhost: 66 | 67 | ```shell 68 | $ db save staging "Snapshot intended for development" 69 | 70 | > [staging] 71 | > Successfully made snapshot of database - hash 1577b2173c672eb824d5b43e989f15448f2bfca43b5b1144e6977479 72 | 73 | $ db load localhost 1577b2173c672eb824d5b43e989f15448f2bfca43b5b1144e6977479 74 | 75 | > [localhost] Loaded snapshot 1577b2173c672eb824d5b43e989f15448f2bfca43b5b1144e6977479 76 | 77 | ``` 78 | 79 | Here you first make a snapshot of the database on the **staging** server, which you then load into your **localhost** 80 | database. This works most reliably if your staging and your development server are on the same database version. 81 | 82 | See the full [list of available commands](#available-commands) to get a complete overview of ```db```'s capabilities. 83 | 84 | ## Installation 85 | 86 | ### MacOS 87 | On MacOS, you can install ```db``` with the [HomeBrew package manager](https://brew.sh/): 88 | 89 | ```shell 90 | $ brew install db-vcs 91 | ``` 92 | 93 | ### Linux / others 94 | On other operating systems, you can install ```db``` by cloning the repository: 95 | 96 | ```shell 97 | $ git clone https://www.github.com/infostreams/db 98 | ``` 99 | 100 | and then creating a symlink to the main ```db``` script from a directory that is in your path, e.g.: 101 | 102 | ```shell 103 | $ cd db/ # change to the directory you cloned the github repository in 104 | $ ln -s db /usr/local/bin/ # create the symlink 105 | ``` 106 | 107 | You can see if it works by running 108 | 109 | ```shell 110 | $ db 111 | ``` 112 | 113 | If all is well you should see the following friendly error message: 114 | 115 | ```shell 116 | fatal: Not a db repository (or any of the parent directories). Please run 'db init'. 117 | ``` 118 | 119 | So, to really get started, go to a directory where you have a project that uses a database, and type 120 | [```db init```](#db-init). This will start the process of setting up your database connection details, after which 121 | the following commands will be available to you. 122 | 123 | ### Additional configuration / different port or socket 124 | 125 | You can provide additional configuration for the MySQL connection by providing them in 126 | [the options file](https://dev.mysql.com/doc/refman/5.7/en/option-files.html) that can be found at 127 | ```.db//config/credentials.cnf```. Here you can provide a different port number or you can specify a socket 128 | to connect through, for example 129 | 130 | ```ini 131 | [client] 132 | user = root 133 | password = 134 | host = 127.0.0.1 135 | port = 3307 136 | ``` 137 | 138 | to connect to a MySQL database on a port 3307 instead of the standard 3306, or 139 | 140 | ```ini 141 | [client] 142 | user = root 143 | password = 144 | socket = /var/run/mysqld/mysql.sock 145 | ``` 146 | 147 | to connect to MySQL through a socket file located at ```/var/run/mysqld/mysql.sock```. 148 | 149 | ## Available commands 150 | 151 | ```db``` has commands to import external SQL files into your database ([```db import```](#db-import)), to export a 152 | dump from the repository to a file ([```db export```](#db-export)), to make minor changes to your database (such as 153 | changing the encoding, with [```db change```](#db-change)), to clean out your database entirely ([```db nuke```](#db-nuke)), 154 | or to show the table structure of a particular dump ([```db structure```](#db-structure)). Very high on the wish 155 | list is a way to display a "diff" between two dumps, but that's not so easy and hasn't been implemented yet. 156 | 157 | ### Full list 158 | 159 | The full list of available commands is as follows 160 | 161 | * [init](#db-init) 162 | * [save](#db-save) 163 | * [load](#db-load) 164 | * [remove](#db-remove) 165 | * [server](#db-server) 166 | * [export](#db-export) 167 | * [import](#db-import) 168 | * [log](#db-log) 169 | * [nuke](#db-nuke) 170 | * [show](#db-show) 171 | * [structure](#db-structure) 172 | * [change](#db-change) 173 | * [test](#db-test) 174 | 175 | ### db init 176 | 177 | Creates a new DB repository in the current directory. Starts an interactive session that allows you to specify the 178 | connection details. 179 | 180 | #### Syntax 181 | ```shell 182 | $ db init 183 | ``` 184 | 185 | If no database-type is provided, you will be asked to specify it in the interactive session. At the moment, only mysql 186 | is supported. The extension points for other database systems are already in place and could (theoretically) be added 187 | relatively easily. 188 | 189 | Below you see an example of the questions you might be asked. 190 | 191 | ```shell 192 | $ db init 193 | 194 | 195 | > Please provide the database type for this repository 196 | > Supported types: mysql 197 | > 198 | > Database type [mysql]: 199 | > 200 | > Please provide the connection details for the database 201 | > Server alias []: 202 | > Connection type, e.g. direct, or ssh []: 203 | > SSH login, e.g. user@hostname.com []: 204 | > Please provide the database host. For connection types SSH and direct, this is probably 127.0.0.1. 205 | > Database host [127.0.0.1]: 206 | > Username [root]: 207 | > Password []: 208 | > Database []: 209 | > 210 | > Successfully connected to database. Writing config. 211 | ``` 212 | 213 | The password for your connection will be stored in plain text, in a file that only the current user has read-access to 214 | (file mode 0600). Make sure to not commit this file (```.db//config/credentials.cnf```) to source control! 215 | 216 | If you want to add a remote server, you need to have ssh access to it. It is best if you have setup passwordless access, 217 | otherwise it will ask you for your password every time you interact with the remote server. 218 | 219 | ### db save 220 | 221 | Saves a snapshot of the database to the repository. 222 | 223 | #### Syntax 224 | 225 | ```shell 226 | $ db save [server alias] [commit message] 227 | ``` 228 | 229 | If you omit the **server alias** it will default to using the oldest server alias, in my case almost always **localhost**. 230 | 231 | Example 232 | 233 | ```shell 234 | $ db save localhost "Hello database" 235 | 236 | > [localhost] 237 | > Successfully made snapshot of database - hash e82a736789b421e7efd0ee2071bff33945a5fab6be08be6821a3f576 238 | ``` 239 | 240 | If you try to make a snapshot of a database that didn't change, it will not save a new snapshot. 241 | 242 | 243 | ### db load 244 | 245 | Loads a snapshot from the repository into the database. 246 | 247 | CAVEAT: Any tables that are **not** in the snapshot you are restoring are left untouched. *It only replaces the tables 248 | that are in the snapshot.* If you want to completely empty your database first, have a look at [db nuke](#db-nuke). 249 | 250 | #### Syntax 251 | 252 | ```shell 253 | $ db load [server alias] [snapshot] [--match MATCH] <...> 254 | ``` 255 | 256 | If you omit the **server alias** it will default to using the oldest server alias, in my case almost always **localhost**. 257 | 258 | For ```[snapshot]``` you can either provide the full hash (e.g. ```e82a736789b421e7efd0ee2071bff33945a5fab6be08be6821a3f576```) 259 | or you can provide just enough characters to uniquely identify a given dump (e.g. ```e82a736789b4```) 260 | 261 | You can choose to only load one or more specific tables from this snapshot. For example, the following command will only 262 | restore the ```wp_users``` table to localhost: 263 | 264 | ```shell 265 | $ db load localhost e82a736789b421e7efd0ee2071bff33945a5fab6be08be6821a3f576 wp_users 266 | ``` 267 | 268 | You can provide more than one table to restore. If you don't provide any tables, it will load all the tables that 269 | are defined in the snapshot. 270 | 271 | You can also provide a regular expression to match the table name to restore. For example, to only restore tables 272 | whose name matches the regular expression ```wp_13_.*```, you can run the following command: 273 | 274 | ```shell 275 | $ db load localhost e82a736789b421e7efd0ee2071bff33945a5fab6be08be6821a3f576 --match "wp_13_.*" 276 | ``` 277 | 278 | ### db remove 279 | 280 | Removes a snapshot from the repository 281 | 282 | #### Syntax 283 | 284 | ```shell 285 | $ db remove [server alias] [snapshot] 286 | ``` 287 | 288 | #### Alternative syntax 289 | 290 | Instead of typing out ```db remove```, you can also use an abbreviated version, ```db rm```: 291 | 292 | ```shell 293 | $ db rm [server alias] [snapshot] 294 | ``` 295 | 296 | ### db server 297 | 298 | 299 | #### db server add 300 | 301 | Adds a new database server 302 | 303 | ##### Syntax 304 | 305 | 306 | ```shell 307 | $ db server add 308 | ``` 309 | 310 | Example 311 | 312 | ```shell 313 | $ db server add 314 | 315 | 316 | > Please provide the connection details for the database 317 | > 318 | > Server alias []: production 319 | > Connection type, e.g. direct, or ssh []: ssh 320 | > SSH login, e.g. user@hostname.com []: account@server.com 321 | > Please provide the database host. For connection types SSH and direct, this is probably 127.0.0.1. 322 | > Database host [127.0.0.1]: 127.0.0.1 323 | > Username [root]: user 324 | > Password []: password 325 | > Database []: database 326 | > 327 | > Successfully connected to database. Writing config. 328 | ``` 329 | 330 | This would have added a new server with the alias **production**, which you can then use to save and load snapshots. 331 | 332 | If you want to add a remote server, you need to have ssh access to it. It is best if you have setup passwordless access, 333 | otherwise it will ask you for your password every time you interact with the remote server. 334 | 335 | 336 | #### db server remove 337 | 338 | Removes a database server alias and all the snapshots. Does not do affect the actual database server itself. 339 | 340 | ##### Syntax 341 | 342 | ``` 343 | $ db server remove [server alias] 344 | ``` 345 | 346 | #### db server show 347 | 348 | Shows the connection details for a database server alias. 349 | 350 | ##### Syntax 351 | 352 | ``` 353 | $ db server show [server alias] 354 | ``` 355 | 356 | #### db server list 357 | 358 | Shows a list of which servers are available 359 | 360 | ##### Syntax 361 | 362 | ```shell 363 | $ db server list 364 | 365 | > localhost 366 | > staging 367 | ``` 368 | 369 | ### db export 370 | 371 | Copies a database snapshot from the repository to a local file 372 | 373 | #### Syntax 374 | 375 | ```shell 376 | $ db export [server alias] [snapshot] 377 | ``` 378 | 379 | Example 380 | 381 | ```shell 382 | $ db export project a604f1d9063e9100fef408119287d8b604542434ea79214713088bc3 ~/Desktop/latest-dump.sql 383 | ``` 384 | 385 | ### db import 386 | 387 | Tries to load an external database dump into the database. Will not empty out the database first (!) - if you want that, 388 | make sure to run [```db nuke```](#db-nuke) first. 389 | 390 | You can import gzip compressed database dumps, as long as the file extension is **.gz**. 391 | 392 | #### Syntax 393 | 394 | ```shell 395 | $ db import [server alias] [ | ] 396 | ``` 397 | 398 | Example 399 | 400 | ```shell 401 | $ db import localhost ~/Desktop/some-dump-from-a-colleague-or-customer.sql 402 | ``` 403 | 404 | ### db nuke 405 | 406 | *Deletes all tables in your database* (!!) 407 | 408 | #### Syntax 409 | 410 | ```shell 411 | $ db nuke [server alias] 412 | ``` 413 | 414 | This will delete all the tables in your database. If you omit the **server alias** it will default to using the oldest 415 | server alias, in my case almost always **localhost**. 416 | 417 | Use with care (obviously). 418 | 419 | ### db log 420 | 421 | Show a list of snapshots in the repository. By default, it will list all snapshots for all servers. 422 | 423 | 424 | #### Syntax 425 | 426 | ```shell 427 | $ db log [server alias | all] 428 | ``` 429 | 430 | Example 431 | 432 | ```shell 433 | $ db log 434 | ``` 435 | 436 | This will list all snapshots in the repository. 437 | 438 | ```shell 439 | $ db log localhost 440 | ``` 441 | 442 | This will only list the snapshots for the _localhost_ database. 443 | 444 | ### db show 445 | 446 | Displays the contents of a snapshot. Basically echos the contents of the database dump to STDOUT. 447 | 448 | #### Syntax 449 | 450 | ```shell 451 | $ db show [snapshot] 452 | ``` 453 | 454 | You don't need to provide the server alias here, since snapshots have unique names. 455 | 456 | Example 457 | 458 | ```shell 459 | $ db show e9d5240e077b0f180d594d59aac468b4fc844d984eaf6e76363e1c14 | less 460 | ``` 461 | 462 | Displays the contents of the given database dump, and pipes it through ```less``` so you can inspect it. 463 | 464 | 465 | ### db structure 466 | 467 | Displays the table structure of a given snapshot: shows all the **CREATE TABLE** statements in the snapshot. 468 | Useful for debugging or inspecting the database. 469 | 470 | #### Syntax 471 | 472 | ```shell 473 | $ db structure [snapshot] 474 | ``` 475 | 476 | You don't need to provide the server alias here, since snapshots have unique names. 477 | 478 | Example 479 | 480 | ```shell 481 | $ db structure c6d7451de16a8ddd0ec240a6d8f7cc376544583433f74bcf9960c6ab | less 482 | ``` 483 | 484 | Displays the table definitions in the provided snapshot, and pipes it through ```less``` for inspection. 485 | 486 | ### db change 487 | 488 | Allows you to make changes to a database. Does not operate on a snapshot but on the actual database itself. 489 | 490 | #### db change charset 491 | 492 | Changes the character set and collation of the database and its tables. 493 | 494 | ##### Syntax 495 | 496 | ```shell 497 | $ db change charset [server alias] [new characterset] [new collation] 498 | ``` 499 | 500 | Example 501 | 502 | ```shell 503 | $ db change charset localhost utf8mb4 utf8mb4_unicode_ci 504 | ``` 505 | 506 | #### db change wordpress 507 | 508 | Runs a naive search and replace on the database to make changes to allow WordPress to run on a different domain name. 509 | Only use this if you cannot use [WP-CLI](https://wp-cli.org/) for some reason. 510 | 511 | ##### Syntax 512 | 513 | ```shell 514 | $ db change wordpress [server alias] [new url] 515 | ``` 516 | 517 | Example 518 | 519 | ```shell 520 | $ db change wordpress localhost "http://www.my-new-domain.com/" 521 | ``` 522 | 523 | ### db test 524 | 525 | Tries to connect to a given database server. Known to sometimes give false positives. 526 | 527 | ### Syntax 528 | 529 | ```shell 530 | $ db test [server alias] 531 | ``` 532 | 533 | Example 534 | 535 | ```shell 536 | $ db test staging 537 | ``` 538 | 539 | Tries to connect to the **staging** server. 540 | --------------------------------------------------------------------------------