-
-
Save qrwteyrutiyoup/6876e6687ac1ac8eb36c to your computer and use it in GitHub Desktop.
Mini VPS Control Panel, v0.1, Mar 25th 2014
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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