#!/usr/bin/env bash # # ############################### # Script to run PMM 3. # If docker is not installed, this script will try to install it as root user. # if PMM 2 is running, it will be stopped and data will be migrated to PMM 3. # # Usage example: # curl -fsSL https://raw.githubusercontent.com/percona/pmm/v3/get-pmm.sh -o get-pmm.sh; chmod +x get-pmm.sh; ./get-pmm.sh -b # ################################# set -Eeuo pipefail trap cleanup SIGINT SIGTERM ERR EXIT # Set defaults. network_name=${NETWORK_NAME:-pmm-net} tag=${PMM_TAG:-3} repo=${PMM_REPO:-percona/pmm-server} port=${PMM_PORT:-443} container_name=${CONTAINER_NAME:-pmm-server} docker_socket_path=${DOCKER_SOCKET_PATH:-/var/run/docker.sock} watchtower_token=${WATCHTOWER_TOKEN:-} volume_name=${VOLUME_NAME:-pmm-data} backup_data=0 interactive=0 root_is_needed=no ####################################### # Show script usage info. ####################################### usage() { cat <&2 -e "${1-}" } ####################################### # Prints message and exit with code. # Arguments: # message string; # exit code. # Outputs: # writes message to stderr. ####################################### die() { local message=$1 local code=${2-1} # default exit status 1 msg "$message" exit "$code" } ####################################### # Accept and parse script's params. ####################################### parse_params() { while :; do case "${1-}" in -h | --help) usage ;; -v | --verbose) set -x ;; -nc | --no-color) NO_COLOR=1 ;; -i | --interactive) interactive=1 ;; -t | --tag) tag="${2-}" shift ;; -r | --repo) repo="${2-}" shift ;; -p | --port) port="${2-}" shift ;; -n | --name) container_name="${2-}" shift ;; -b | --backup) backup_data=1 ;; -wt | --watchtower-token) watchtower_token="${2-}" shift ;; -dsp | --docker-socket-path) docker_socket_path="${2-}" shift ;; -net | --network-name) network_name="${2-}" shift ;; -vn | --volume-name) volume_name="${2-}" shift ;; -?*) die "Unknown option: $1" ;; *) break ;; esac shift done args=("$@") return 0 } ####################################### # Gathers PMM setup param in interactive mode. ####################################### gather_info() { msg "${GREEN}PMM Server Wizard Install${NOFORMAT}" default_port=$port default_container_name=$container_name default_tag=$tag read -p " Port Number to start PMM Server on (default: $default_port): " port : ${port:=$default_port} read -p " PMM Server Container Name (default: $default_container_name): " container_name : ${container_name:="$default_container_name"} read -p " Override specific version (container tag) (default: $default_tag in 3.x series) format: 3.x.y: " tag : ${tag:=$default_tag} } check_command() { command -v "$@" 1>/dev/null } ####################################### # Runs command as root. ####################################### run_root() { sh='sh -c' if [ "$(id -un)" != 'root' ]; then if check_command sudo; then sh='sudo -E sh -c' elif check_command su; then sh='su -c' else die "${RED}ERROR: root rights needed to run "$*" command${NOFORMAT}" fi fi ${sh} "$@" } ####################################### # Check if MacOS ####################################### is_darwin() { case "$(uname -s)" in *darwin* | *Darwin* ) true ;; * ) false;; esac } ####################################### # Installs docker if needed. ####################################### install_docker() { printf "Checking docker installation" if ! check_command docker; then if is_darwin; then echo echo "ERROR: Cannot auto-install components on MacOS" echo "Please get Docker Desktop from https://www.docker.com/products/docker-desktop and rerun installer after starting" echo exit 1 fi printf " - not installed. Installing...\n\n" curl -fsSL get.docker.com -o /tmp/get-docker.sh || wget -qO /tmp/get-docker.sh get.docker.com sh /tmp/get-docker.sh || die "${RED}ERROR: cannot install Docker, please install Docker manually and try to run this script again${NOFORMAT}" run_root 'service docker start' || : else printf " - installed.\n\n" fi if ! docker ps 1>/dev/null; then root_is_needed='yes' if ! run_root 'docker ps > /dev/null'; then if is_darwin; then run_root 'open --background -a Docker' echo "Giving Docker Desktop time to start" sleep 30 else die "${RED}ERROR: cannot run "docker ps" command${NOFORMAT}" fi fi fi } ####################################### # Runs docker command as root if required. ####################################### run_docker() { if [ "${root_is_needed}" = 'yes' ]; then run_root "docker $*" else sh -c "docker $*" fi } ####################################### # Generates Watchtower token if needed, or reuses existing one. ####################################### generate_watchtower_token() { if [ ! "$watchtower_token" == "" ]; then msg "Using provided Watchtower token" return 0 fi if run_docker "inspect watchtower 1> /dev/null 2> /dev/null"; then watchtower_token=$(run_docker "inspect --format='{{range .Config.Env}}{{println .}}{{end}}' watchtower | grep WATCHTOWER_HTTP_API_TOKEN | cut -d'=' -f2") msg "Found Watchtower Token: $watchtower_token" return 0 fi if run_docker "inspect pmm-server 1> /dev/null 2> /dev/null"; then watchtower_token=$(run_docker "inspect --format='{{range .Config.Env}}{{println .}}{{end}}' pmm-server | grep PMM_WATCHTOWER_TOKEN | cut -d'=' -f2") msg "Found Watchtower Token: $watchtower_token" # we don't return here, as we want to generate a new token if it's not found fi if [ "$watchtower_token" == "" ]; then watchtower_token=random-$(date "+%F-%H%M%S") msg "Generated Watchtower Token: $watchtower_token" fi } ####################################### # Creates PMM Network if needed. ####################################### create_pmm_network() { if ! run_docker "network inspect $network_name 1> /dev/null 2> /dev/null"; then run_docker "network create $network_name 1> /dev/null" msg "Created PMM Network: $network_name" fi } ####################################### # Starts Watchtower container if needed. ####################################### start_watchtower() { if ! run_docker "inspect watchtower 1> /dev/null 2> /dev/null"; then run_docker "run -d --name watchtower --restart always --network $network_name -e WATCHTOWER_HTTP_API_TOKEN=$watchtower_token -e WATCHTOWER_HTTP_LISTEN_PORT=8080 -e WATCHTOWER_HTTP_API_UPDATE=1 -v $docker_socket_path:/var/run/docker.sock percona/watchtower --cleanup" msg "Created Watchtower container" fi } ####################################### # Migrates PMM Server data from 2.x to 3.x ####################################### migrate_pmm_data() { msg "Migrating PMM Server data from 2.x to 3.x" run_docker "start $container_name" sleep 5 run_docker "exec -t $container_name supervisorctl stop all" sleep 5 run_docker "exec -t $container_name chown -R pmm:pmm /srv" run_docker "stop $container_name" } # Full Mapping of PMM v2 to v3 Environment Variables # Simulated mapping using an indexed array ENV_MAPPING=( "DATA_RETENTION=PMM_DATA_RETENTION" "DISABLE_ALERTING=PMM_ENABLE_ALERTING" "DISABLE_UPDATES=PMM_ENABLE_UPDATES" "DISABLE_TELEMETRY=PMM_ENABLE_TELEMETRY" "PERCONA_PLATFORM_API_TIMEOUT=PMM_DEV_PERCONA_PLATFORM_API_TIMEOUT" "DISABLE_BACKUP_MANAGEMENT=PMM_ENABLE_BACKUP_MANAGEMENT" "ENABLE_AZUREDISCOVER=PMM_ENABLE_AZURE_DISCOVER" "ENABLE_RBAC=PMM_ENABLE_ACCESS_CONTROL" "METRICS_RESOLUTION=PMM_METRICS_RESOLUTION" "METRICS_RESOLUTION_HR=PMM_METRICS_RESOLUTION_HR" "METRICS_RESOLUTION_LR=PMM_METRICS_RESOLUTION_LR" "METRICS_RESOLUTION_MR=PMM_METRICS_RESOLUTION_MR" "OAUTH_PMM_CLIENT_ID=PMM_DEV_OAUTH_CLIENT_ID" "OAUTH_PMM_CLIENT_SECRET=PMM_DEV_OAUTH_CLIENT_SECRET" "PERCONA_TEST_AUTH_HOST=PMM_DEV_PERCONA_PLATFORM_ADDRESS" "PERCONA_TEST_CHECKS_FILE=PMM_DEV_ADVISOR_CHECKS_FILE" "PERCONA_TEST_CHECKS_HOST=PMM_DEV_PERCONA_PLATFORM_ADDRESS" "PERCONA_TEST_CHECKS_PUBLIC_KEY=PMM_DEV_PERCONA_PLATFORM_PUBLIC_KEY" "PERCONA_TEST_PLATFORM_ADDRESS=PMM_DEV_PERCONA_PLATFORM_ADDRESS" "PERCONA_TEST_PLATFORM_INSECURE=PMM_DEV_PERCONA_PLATFORM_INSECURE" "PERCONA_TEST_PLATFORM_PUBLIC_KEY=PMM_DEV_PERCONA_PLATFORM_PUBLIC_KEY" "PERCONA_TEST_SAAS_HOST=PMM_DEV_PERCONA_PLATFORM_ADDRESS" "PERCONA_TEST_POSTGRES_ADDR=PMM_POSTGRES_ADDR" "PERCONA_TEST_POSTGRES_DBNAME=PMM_POSTGRES_DBNAME" "PERCONA_TEST_POSTGRES_SSL_CA_PATH=PMM_POSTGRES_SSL_CA_PATH" "PERCONA_TEST_POSTGRES_SSL_CERT_PATH=PMM_POSTGRES_SSL_CERT_PATH" "PERCONA_TEST_POSTGRES_SSL_KEY_PATH=PMM_POSTGRES_SSL_KEY_PATH" "PERCONA_TEST_POSTGRES_SSL_MODE=PMM_POSTGRES_SSL_MODE" "PERCONA_TEST_POSTGRES_USERNAME=PMM_POSTGRES_USERNAME" "PERCONA_TEST_POSTGRES_DBPASSWORD=PMM_POSTGRES_DBPASSWORD" "PMM_TEST_TELEMETRY_DISABLE_SEND=PMM_DEV_TELEMETRY_DISABLE_SEND" "PERCONA_TEST_TELEMETRY_DISABLE_START_DELAY=PMM_DEV_TELEMETRY_DISABLE_START_DELAY" "PERCONA_TEST_PMM_CLICKHOUSE_ADDR=PMM_CLICKHOUSE_ADDR" "PERCONA_TEST_PMM_CLICKHOUSE_DATABASE=PMM_CLICKHOUSE_DATABASE" "PERCONA_TEST_PMM_CLICKHOUSE_DATASOURCE=PMM_CLICKHOUSE_DATASOURCE" "PERCONA_TEST_PMM_CLICKHOUSE_HOST=PMM_CLICKHOUSE_HOST" "PERCONA_TEST_PMM_CLICKHOUSE_PORT=PMM_CLICKHOUSE_PORT" "PERCONA_TEST_PMM_DISABLE_BUILTIN_CLICKHOUSE=PMM_DISABLE_BUILTIN_CLICKHOUSE" "PERCONA_TEST_PMM_DISABLE_BUILTIN_POSTGRES=PMM_DISABLE_BUILTIN_POSTGRES" "PERCONA_TEST_INTERFACE_TO_BIND=PMM_INTERFACE_TO_BIND" "PERCONA_TEST_VERSION_SERVICE_URL=PMM_DEV_PERCONA_PLATFORM_ADDRESS" "PMM_TEST_TELEMETRY_FILE=PMM_DEV_TELEMETRY_FILE" "PERCONA_TEST_TELEMETRY_HOST=PMM_DEV_TELEMETRY_HOST" "PERCONA_TEST_TELEMETRY_INTERVAL=PMM_DEV_TELEMETRY_INTERVAL" "PERCONA_TEST_TELEMETRY_RETRY_BACKOFF=PMM_DEV_TELEMETRY_RETRY_BACKOFF" "PERCONA_TEST_STARLARK_ALLOW_RECURSION=PMM_DEV_ADVISOR_STARLARK_ALLOW_RECURSION" ) ENV_TO_DROP=( "PATH" "LANG" "LC_ALL" "GF_PLUGIN_DIR" "PS1" "LESS_LOG_NOISE" "PERCONA_TEST_CHECKS_INTERVAL" "PERCONA_TEST_NICER_API" "PERCONA_TEST_PMM_CLICKHOUSE_BLOCK_SIZE" "PERCONA_TEST_PMM_CLICKHOUSE_POOL_SIZE" ) # Function to get the new key from the mapping get_mapped_key() { local key="$1" for mapping in "${ENV_MAPPING[@]}"; do local old_key="${mapping%%=*}" local new_key="${mapping#*=}" if [[ "$old_key" == "$key" ]]; then echo "$new_key" return fi done echo "" } needs_to_drop() { local key="$1" for drop_key in "${ENV_TO_DROP[@]}"; do if [[ "$drop_key" == "$key" ]]; then return 0 fi done return 1 } # Function to migrate environment variables migrate_env_vars() { local current_env IFS=$'\n' current_env=$(run_docker "inspect --format '{{range .Config.Env}}{{println .}}{{end}}' $container_name") local docker_env_flags=$1 for env in $current_env; do local key="${env%%=*}" local value="${env#*=}" if [[ -z "$value" ]]; then continue fi local new_key new_key=$(get_mapped_key "$key") local needs_drop needs_drop=$(needs_to_drop "$key") if [[ -n "$new_key" ]]; then msg "Migrating env variable $key to $new_key" # Handle DISABLE_* to ENABLE_* boolean reversal if [[ "$key" =~ ^DISABLE_ ]]; then value=$((1 - value)) # Reverse the boolean fi docker_env_flags+="--env $new_key=\"$value\" " elif [[ "$needs_drop" -eq 0 ]]; then docker_env_flags+="--env $key=\"$value\" " else msg "Dropping env variable $key" fi done echo "$docker_env_flags" } ####################################### # Backs up existing PMM Data Volume. ####################################### backup_pmm_data() { pmm_volume_archive="$volume_name-$(date "+%F-%H%M%S")" msg "Backing up existing PMM Data Volume to $pmm_volume_archive" run_docker "volume create $pmm_volume_archive 1> /dev/null" run_docker "run --rm -v $volume_name:/from -v $pmm_volume_archive:/to alpine ash -c 'cd /from ; cp -av . /to'" msg "Successfully backed up existing PMM Data Volume to $pmm_volume_archive" } ####################################### # Starts PMM Server container with given repo, tag, name and port. # If a PMM Server instance is running - stop and back it up. ####################################### start_pmm() { msg "Pulling $repo:$tag" run_docker "pull $repo:$tag 1> /dev/null" docker_env_flags="-e PMM_WATCHTOWER_HOST=http://watchtower:8080 -e PMM_WATCHTOWER_TOKEN=$watchtower_token " if run_docker "inspect $container_name 1> /dev/null 2> /dev/null"; then pmm_archive="$container_name-$(date "+%F-%H%M%S")" msg "\tExisting PMM Server found, renaming to $pmm_archive\n" run_docker "stop $container_name" || : volume_name=$(run_docker "inspect -f '{{ range .Mounts }}{{ if and (eq .Type \"volume\") (eq .Destination \"/srv\" )}}{{ .Name }}{{ \"\n\" }}{{ end }}{{ end }}' $container_name") if [[ "$backup_data" == 1 ]]; then backup_pmm_data fi # get container tag from inspect old_version=$(run_docker "inspect --format='{{.Config.Image}}' $container_name | cut -d':' -f2") # if tag starts with 2.x, we need to migrate data if [[ "$old_version" == "2" || "$old_version" == 2.* || "$old_version" == "dev-latest" ]]; then docker_env_flags=$(migrate_env_vars "$docker_env_flags") migrate_pmm_data fi run_docker "rename $container_name $pmm_archive\n" fi if ! run_docker "volume inspect $volume_name 1> /dev/null 2> /dev/null"; then if ! run_docker "volume create $volume_name 1> /dev/null"; then die "${RED}ERROR: cannot create PMM Data Volume${NOFORMAT}" fi msg "Created PMM Data Volume: $volume_name" fi msg "Starting PMM Server..." run_pmm="run -d -p $port:8443 --volume $volume_name:/srv --name $container_name --network $network_name $docker_env_flags --restart always $repo:$tag" run_docker "$run_pmm 1> /dev/null" msg "Created PMM Server: $container_name" msg "\nUse the following command if you ever need to update your container manually:" msg "\tdocker pull $repo:$tag \n" msg "\tdocker $run_pmm \n" } ####################################### # Shows final message. # Shows a list of addresses on which PMM Server is available. ####################################### show_message() { msg "PMM Server has been successfully setup on this system!\n" if check_command ifconfig; then ips=$(ifconfig | awk '/inet / {print $2}' | sed 's/addr://') elif check_command ip; then ips=$(ip -f inet a | awk -F"[/ ]+" '/inet / {print $3}') else die "${RED}ERROR: cannot detect PMM Server address${NOFORMAT}" fi msg "You can access your new server using one of the following web addresses:" for ip in $ips; do msg "\t${GREEN}https://$ip:$port/${NOFORMAT}" done msg "\nThe default username is '${PURPLE}admin${NOFORMAT}' and the password is '${PURPLE}admin${NOFORMAT}' :)" msg "Note: Some browsers may not trust the default SSL certificate when you first open one of the urls above." msg "If this is the case, Chrome users may want to type '${PURPLE}thisisunsafe${NOFORMAT}' to bypass the warning.\n" } main() { setup_colors if [[ "$interactive" == 1 ]]; then gather_info fi msg "Gathering/downloading required components, this may take a moment\n" install_docker create_pmm_network generate_watchtower_token start_pmm start_watchtower show_message } parse_params "$@" main die "Enjoy Percona Monitoring and Management!" 0