Skip to content

Instantly share code, notes, and snippets.

@qrwteyrutiyoup
Created March 25, 2014 18:30
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save qrwteyrutiyoup/6876e6687ac1ac8eb36c to your computer and use it in GitHub Desktop.
Save qrwteyrutiyoup/6876e6687ac1ac8eb36c to your computer and use it in GitHub Desktop.
Mini VPS Control Panel, v0.1, Mar 25th 2014
#!/usr/bin/env bash
# Mini VPS Control Panel, a silly control panel supporting the SolusVM API.
# v0.1, Mar 25th 2014.
#
# Copyright (C) 2014 Sergio Correia <sergio@correia.cc>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# Command-line params.
MINICP_ARGS="${@}"
MINICP_DIR=~/.minicp
MINICP_SERVERS="${MINICP_DIR}"/servers
MINICP_CONFIG="${MINICP_DIR}"/password
MINICP_PW=''
mkdir -p "${MINICP_DIR}"
REQUIRED_PACKAGES="curl openssl dialog"
function die() {
clear
echo "ERROR: "${@}
exit 1
}
## Package management stuff
function cmd_exists() {
hash ${1} 2>/dev/null
return $?
}
function pkg_exists() {
${PKG_CHECK} ${1} 2>/dev/null >/dev/null
return $?
}
function setup_package_manager() {
if cmd_exists 'apt-get'; then
DISTRO='ubuntu/debian'
PKG_CHECK='dpkg -s'
PKG_UPDATE='apt-get update'
PKG_INSTALL='apt-get install --assume-yes'
elif cmd_exists 'yum'; then
DISTRO='centos/fedora'
PKG_CHECK='rpm -q'
PKG_UPDATE='yum clean all'
PKG_INSTALL='yum install -y'
elif cmd_exists 'zypper'; then
DISTRO='opensuse'
PKG_CHECK='rpm -q'
PKG_UPDATE='zypper refresh'
PKG_INSTALL='zypper --non-interactive install --no-recommends'
elif cmd_exists 'pacman'; then
DISTRO='archlinux'
PKG_CHECK='pacman -Q'
PKG_UPDATE='pacman -Syy'
PKG_INSTALL='pacman -S --needed --noconfirm'
else
die "OS/Package manager not supported; exiting."
fi
}
function check_required_packages() {
setup_package_manager
missing_packages=""
for p in ${REQUIRED_PACKAGES[@]}; do
if ! pkg_exists ${p}; then
missing_packages=${missing_packages}" "${p}
fi
done
if [ -n "${missing_packages}" ]; then
if [ $(id -u) -eq 0 ]; then
echo "The following packages are missing, but we are going to try to install them:"${missing_packages}
sleep 1
${PKG_UPDATE} || die "Problem trying to update packages database."
${PKG_INSTALL} ${missing_packages} || die "Problem during the packages install; please install the missing packages with '"${PKG_INSTALL}${missing_packages}"'."
else
die "The following packages are missing:"${missing_packages}". You can try to install them with '"${PKG_INSTALL}${missing_packages}"'."
fi
fi
}
## Encoding
function encode() {
local data="${@}"
echo "${data}" | openssl aes-256-cbc -a -salt -k "${MINICP_PW}"
}
function decode() {
local data="${@}"
echo "${data}" | openssl aes-256-cbc -a -d -salt -k "${MINICP_PW}"
}
function hash_password() {
local pw="${1}"
echo "${pw}" | sha256sum | awk '{print $1}'
}
##
function read_input() {
local title="${1}"
local dimensions="${2}"
if [[ -z "${dimensions}" ]]; then
dimensions='0 0'
fi
local input
while [[ -z ${input} ]]; do
input=$(dialog --stdout --no-cancel --inputbox "${title}" ${dimensions})
done
echo "${input}" | sed -e 's/[ \t\r\n]*$//' -e 's/^[ \t\r\n]*//' | tr ';' '_' # ';' is used as delimiter, so it cannot exist here.
}
function readable_from_bytes() {
echo "${1}" | awk '{ sum=$1 ; hum[1024**4]="TB";hum[1024**3]="GB";hum[1024**2]="MB"; hum[1024]="KB"; if (sum < 1024) { printf "%d B\n", sum} else {for (x=1024**4; x>=1024; x/=1024){ if (sum>=x) { printf "%.2f %s\n",sum/x,hum[x];break }} }}'
}
function get_decoded_server_info() {
local server_name="${1}"
local server_info=''
if [[ -r "${MINICP_SERVERS}" ]] && grep -q -i "${server_name}": "${MINICP_SERVERS}"; then
local encoded_info=$(grep -i "${server_name}": "${MINICP_SERVERS}" | cut -d ':' -f 2 | tr ';' '\n')
server_info=$(decode "${encoded_info}")
fi
echo "${server_info}"
}
function get_servers_list() {
local servers_list=''
if [[ -r "${MINICP_SERVERS}" ]]; then
# Now let's try to read the servers.
local server_name
local server_info
local server_type
while read s; do
server_name=$(echo ${s} | cut -d ':' -f 1)
server_info=$(get_decoded_server_info "${server_name}")
server_type=$(echo ${server_info} | cut -d ';' -f 1)
servers_list=${servers_list}" '"${server_name}"' '("${server_type}")'"
done < "${MINICP_SERVERS}"
fi
echo "${servers_list}"
}
function msgbox() {
local title="${1}"
local msg="${2}"
dialog --title "${title}" --msgbox "${msg}" 6 75
}
function infobox() {
local title="${1}"
local msg="${2}"
dialog --title "${title}" --infobox "${msg}" 3 75
}
function confirmbox() {
local title="${1}"
local msg="${2}"
dialog --title "${title}" --yesno "${msg}" 6 75
}
## Solus
function solus_resource_usage() {
local resource="${1}"
local used=$(readable_from_bytes $(echo "${resource}" | cut -d ',' -f 2))
local total=$(readable_from_bytes $(echo "${resource}" | cut -d ',' -f 1))
local used_percent=$(echo "${resource}" | cut -d ',' -f 4)
echo ${total}' total, '${used}' ('${used_percent}'%) used'
}
function show_solus_server_info() {
local server_name="${1}"
local server_info="${2}"
local action
local width=80
local height=18 # infobox
local xstart=$(($(tput cols) - ${width}))
xstart=$((${xstart} / 2))
local ystart=0
if [[ $(tput lines) -gt 25 ]]; then
ystart=$(($(tput lines) - 25)) # height + 7
ystart=$((${ystart} / 2))
fi
local response
while /bin/true; do
local status=">>> Status: "$(parse_solus_response_tag "${server_info}" 'vmstat')"\n"
status=${status}"\n>>> Hostname: "$(parse_solus_response_tag "${server_info}" 'hostname')
status=${status}"\n>>> IP's: "$(parse_solus_response_tag "${server_info}" 'ipaddr' | sed -e 's/,/ /g')
status=${status}"\n>>> Disk space: "$(solus_resource_usage $(parse_solus_response_tag "${server_info}" 'hdd'))
status=${status}"\n>>> RAM: "$(solus_resource_usage $(parse_solus_response_tag "${server_info}" 'mem'))
status=${status}"\n>>> Bandwidth: "$(solus_resource_usage $(parse_solus_response_tag "${server_info}" 'bw'))
action=$(dialog --stdout --title "${server_name}" --no-shadow \
--begin ${ystart} ${xstart} --infobox "${status}" ${height} ${width} --and-widget \
--begin ${height} ${xstart} --cancel-label 'Return' --menu 'Available actions' 7 ${width} 0\
'update' 'Update the server info' \
'shutdown' 'Shutdown the server' \
'boot' 'Boot the server' \
'reboot' 'Reboot the server')
case ${action} in
update)
infobox 'Server info' "Retrieving info for '${server_name}'..."
response=$(solus_do_action "${server_name}" 'info')
if [[ -z "${response}" ]]; then
msgbox 'Ouch' 'It was not possible to retrieve the data from the selected server'
break
fi
server_info="${response}"
;;
shutdown)
confirmbox 'Server shutdown' "Are you sure you want to shutdown '${server_name}'?"
if [[ ${?} -eq 0 ]]; then
infobox 'Server shutdown' "Shutting '${server_name}' down..."
response=$(solus_do_action "${server_name}" 'shutdown')
if [[ -z "${response}" ]]; then
msgbox 'Ouch' 'It was not possible to retrieve the data from the selected server'
break
else
msgbox 'Shutdown' 'The server was shutdown successfully'
response=$(solus_do_action "${server_name}" 'info')
if [[ -z "${response}" ]]; then
msgbox 'Ouch' 'It was not possible to retrieve the data from the selected server'
break
fi
server_info="${response}"
fi
fi
;;
boot)
confirmbox 'Server boot' "Are you sure you want to boot '${server_name}'?"
if [[ ${?} -eq 0 ]]; then
infobox 'Server boot' "Booting '${server_name}'..."
response=$(solus_do_action "${server_name}" 'boot')
if [[ -z "${response}" ]]; then
msgbox 'Ouch' 'It was not possible to retrieve the data from the selected server'
break
else
msgbox 'Boot' 'The server booted successfully'
response=$(solus_do_action "${server_name}" 'info')
if [[ -z "${response}" ]]; then
msgbox 'Ouch' 'It was not possible to retrieve the data from the selected server'
break
fi
server_info="${response}"
fi
fi
;;
reboot)
confirmbox 'Server reboot' "Are you sure you want to reboot '${server_name}'?"
if [[ ${?} -eq 0 ]]; then
infobox 'Server reboot' "Rebooting '${server_name}'..."
response=$(solus_do_action "${server_name}" 'boot')
if [[ -z "${response}" ]]; then
msgbox 'Ouch' 'It was not possible to retrieve the data from the selected server'
break
else
msgbox 'Reboot' 'The server rebooted successfully'
response=$(solus_do_action "${server_name}" 'info')
if [[ -z "${response}" ]]; then
msgbox 'Ouch' 'It was not possible to retrieve the data from the selected server'
break
fi
server_info="${response}"
fi
fi
;;
*)
break
;;
esac
done
}
function parse_solus_response_tag() {
local response="${1}"
local tag="${2}"
local sed_pattern='s/^\(.*<'${tag}'>\)\(.*\)\(<\/'${tag}'>.*$\)/\2/'
echo "${response}" | sed -e "${sed_pattern}"
}
function add_solus_server() {
local server_type='solus'
local server_name=$(read_input 'New SolusVM server name:' '0 75')
local server_panel=$(read_input 'SolusVM server control panel URL:' '0 75')
local server_endpoint=$(echo "${server_panel}" | sed -e 's/\(.*\/\)\(.*\.php.*\)/\1/')'api/client/command.php'
local server_key=$(read_input 'Solus API key:' '0 75')
local server_hash=$(read_input 'Solus API hash:' '0 75')
server_data="${server_type}"\;"${server_endpoint}"\;"${server_key}"\;"${server_hash}"
local encoded=$(encode "${server_data}" | tr '\n' ';')
local config="${server_name}":"${encoded}"
if grep -q -i "${server_name}:" "${MINICP_SERVERS}"; then
confirmbox 'Server already exists' "'${server_name}' already exists. Would you like to update it?"
if [[ ${?} -eq 0 ]]; then
local sed_pattern='s/^'${server_name}':.*$/'${config}'/i'
local sed_pattern='/^'${server_name}':.*$/Id'
sed -i "${sed_pattern}" "${MINICP_SERVERS}" || die "It was not possible to update the selected server '"${server_name}"'"
echo "${config}" >> "${MINICP_SERVERS}" || die "Problem updating "${server_name}" settings."
fi
else
echo "${config}" >> "${MINICP_SERVERS}" || die "Problem saving "${server_name}" settings."
fi
}
function solus_do_action() {
local server_name="${1}"
local action="${2}"
local decoded_info=$(get_decoded_server_info "${server_name}")
if [[ -z "${decoded_info}" ]]; then
echo ''
fi
local endpoint=$(echo "${decoded_info}" | cut -d ';' -f 2)
local api_key=$(echo "${decoded_info}" | cut -d ';' -f 3)
local api_hash=$(echo "${decoded_info}" | cut -d ';' -f 4)
response=$(curl -k -s "${endpoint}?key=${api_key}&hash=${api_hash}&action=${action}&hdd=true&ipaddr=true&mem=true&bw=true&status=true")
local status=$(parse_solus_response_tag "${response}" status)
if [[ "${status}" = "success" ]]; then
echo "${response}"
else
echo ''
fi
}
## MiniCP generic
function minicp_remove_servers() {
if [[ ! -r "${MINICP_SERVERS}" ]]; then
msgbox 'No servers found' 'There are no servers.'
return
fi
local server_name
local servers
while /bin/true; do
servers=$(get_servers_list)
if [[ -z "${servers}" ]]; then
msgbox 'No servers found' 'There are no servers.'
break
fi
server_name=$(eval "dialog --stdout --ok-label 'Remove' --menu 'List of servers' 0 0 0 ${servers}")
if [[ ${?} -eq 0 ]]; then
confirmbox 'Remove server' "Are you sure you want to remove '${server_name}'?"
if [[ ${?} -eq 0 ]]; then
local sed_pattern='/^'${server_name}':.*$/Id'
sed -i "${sed_pattern}" "${MINICP_SERVERS}" || die "It was not possible to remove the selected server '"${server_name}"'"
fi
else
break
fi
done
}
function minicp_manage_servers() {
if [[ ! -r "${MINICP_SERVERS}" ]]; then
msgbox 'No servers found' 'There are no servers.'
return
fi
local server_name
local servers
while /bin/true; do
servers=$(get_servers_list)
if [[ -z "${servers}" ]]; then
msgbox 'No servers found' 'There are no servers.'
break
fi
server_name=$(eval "dialog --stdout --ok-label 'Manage' --menu 'List of servers' 0 0 0 ${servers}")
if [[ ${?} -eq 0 ]]; then
infobox 'Server info' "Retrieving info for '${server_name}'..."
local response=$(solus_do_action "${server_name}" 'info')
if [[ -z "${response}" ]]; then
msgbox 'Ouch' 'It was not possible to retrieve the data from the selected server'
else
show_solus_server_info "${server_name}" "${response}"
break
fi
else
break
fi
done
}
function minicp_menu() {
local action
local server_type
while /bin/true; do
action=$(dialog --stdout --cancel-label 'Exit' --menu 'Mini VPS Control Panel' 0 0 0 'manage' 'Manage your servers' 'add' 'Add new server' 'remove' 'Remove a server')
case ${action} in
'manage')
minicp_manage_servers
;;
'add')
server_type=$(dialog --stdout --title 'Select the management API' --radiolist '' 0 0 0 \
solus 'SolusVM' on)
case ${server_type} in
'solus')
add_solus_server
;;
esac
;;
'remove')
minicp_remove_servers
;;
*)
clear
exit 0
;;
esac
done
clear
}
function minicp_new_password() {
for i in $(seq 1 5); do
local pw1=$(dialog --stdout --title 'MiniCP Password' --passwordbox 'Please create a password to use with MiniCP:' 0 0)
local pw2=$(dialog --stdout --title 'MiniCP Confirm Password' --passwordbox 'Please confirm your password:' 0 0)
if [[ "${pw1}" = "${pw2}" ]]; then
MINICP_PW="${pw1}"
local hashed=$(hash_password "${pw1}")
echo "${hashed}" > "${MINICP_CONFIG}"
infobox 'Success' 'Your password was saved successfully'
sleep 1
minicp_menu
else
msgbox 'Mismatched passwords' 'The passwords do not match.'
fi
done
msgbox 'Error' 'Too many failed attempts. Exiting...'
clear
exit 1
}
function minicp_auth() {
for i in $(seq 1 5); do
local pw=$(dialog --stdout --title 'MiniCP Password' --passwordbox 'Please enter your password:' 0 0)
hashed=$(hash_password "${pw}")
actual_pw=$(head -n1 "${MINICP_CONFIG}")
if [[ "${hashed}" = "${actual_pw}" ]]; then
MINICP_PW="${pw}"
minicp_menu
else
msgbox 'Incorrect passwords' 'The password does not match.'
fi
done
msgbox 'Error' 'Too many failed attempts. Exiting...'
clear
exit 1
}
function minicp_control_c() {
clear
exit 1
}
function minicp_main() {
trap minicp_control_c SIGINT
check_required_packages
for a in ${MINICP_ARGS}; do
if [[ "${a}" = '--new-password' ]]; then
minicp_new_password
fi
done
if [[ -r "${MINICP_CONFIG}" ]] && [[ $(wc -l "${MINICP_CONFIG}" | awk '{print $1}') -eq 1 ]]; then
minicp_auth
else
minicp_new_password
fi
}
minicp_main
# vim:set ts=2 sw=2 et:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment