mirror of
https://github.com/stefanpejcic/openpanel
synced 2025-06-26 18:28:26 +00:00
1828 lines
65 KiB
Bash
1828 lines
65 KiB
Bash
#!/bin/bash
|
||
################################################################################
|
||
#
|
||
# OpenPanel Installer ✌️
|
||
# https://openpanel.com/install
|
||
#
|
||
# Supported OS: Ubuntu, Debian, AlmaLinux, RockyLinux, CentOS
|
||
# Supported Architecture: x86_64(AMD64), AArch64(ARM64)
|
||
#
|
||
# Usage: bash <(curl -sSL https://openpanel.org)
|
||
# Author: Stefan Pejcic <stefan@pejcic.rs>
|
||
# Created: 11.07.2023
|
||
# Last Modified: 23.06.2025
|
||
#
|
||
################################################################################
|
||
|
||
|
||
|
||
# ======================================================================
|
||
# Constants
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[0;33m'
|
||
RED='\033[0;31m'
|
||
RESET='\033[0m'
|
||
export TERM=xterm-256color # bug fix: tput: No value for $TERM and no -T specified
|
||
export DEBIAN_FRONTEND=noninteractive
|
||
# ======================================================================
|
||
# Defaults for environment variables
|
||
CUSTOM_VERSION=false # default version is latest
|
||
DEBUG=false # verbose output for debugging failed install
|
||
SKIP_APT_UPDATE=false # they are auto-pulled on account creation
|
||
SKIP_DNS_SERVER=false
|
||
REPAIR=false
|
||
LOCALES=true # only en
|
||
NO_SSH=false # deny port 22
|
||
SET_HOSTNAME_NOW=false # must be a FQDN
|
||
SETUP_SWAP_ANYWAY=false
|
||
CORAZA=true # install CorazaWAF, unless user provices --no-waf flag
|
||
SWAP_FILE="1" # calculated based on ram
|
||
SEND_EMAIL_AFTER_INSTALL=false # send admin logins to specified email
|
||
SET_PREMIUM=false # added in 0.2.1
|
||
CSF_SETUP=true # default since >0.2.2
|
||
SET_ADMIN_USERNAME=false # random
|
||
SET_ADMIN_PASSWORD=false # random
|
||
SCREENSHOTS_API_URL="http://screenshots-api.openpanel.com/screenshot" # default since 0.2.1
|
||
DEV_MODE=false
|
||
post_install_path="" # not to run
|
||
# ======================================================================
|
||
# PATHs used throughout the script
|
||
ETC_DIR="/etc/openpanel/" # https://github.com/stefanpejcic/openpanel-configuration
|
||
LOG_FILE="openpanel_install.log" # install log # install running
|
||
SERVICES_DIR="/etc/systemd/system/" # used for admin and sentinel services
|
||
CONFIG_FILE="${ETC_DIR}openpanel/conf/openpanel.config" # main config file for openpanel
|
||
|
||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||
|
||
# ======================================================================
|
||
# Helper functions that are not mandatory and should not be modified
|
||
|
||
# logo
|
||
print_header() {
|
||
printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' -
|
||
echo -e " ____ _____ _ "
|
||
echo -e " / __ \ | __ \ | | "
|
||
echo -e " | | | | _ __ ___ _ __ | |__) | __ _ _ __ ___ | | "
|
||
echo -e " | | | || '_ \ / _ \| '_ \ | ___/ / _\" || '_ \ / _ \| | "
|
||
echo -e " | |__| || |_) || __/| | | | | | | (_| || | | || __/| | "
|
||
echo -e " \____/ | .__/ \___||_| |_| |_| \__,_||_| |_| \___||_| "
|
||
echo -e " | | "
|
||
echo -e " |_| version: ${GREEN}$PANEL_VERSION${RESET} "
|
||
printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' -
|
||
}
|
||
|
||
|
||
|
||
install_started_message(){
|
||
echo -e "\nStarting the installation of OpenPanel. This process will take approximately 3-5 minutes."
|
||
echo -e "During this time, we will:"
|
||
if [ "$CSF_SETUP" = true ]; then
|
||
echo -e "- Install necessary services and tools: CSF, Docker, MySQL, SQLite, Python3, PIP.. "
|
||
else
|
||
echo -e "- Install necessary services and tools: Docker, MySQL, SQLite, Python3, PIP.. "
|
||
fi
|
||
if [ "$SET_ADMIN_USERNAME" = true ]; then
|
||
if [ "$SET_ADMIN_PASSWORD" = true ]; then
|
||
echo -e "- Create an admin account $custom_username with password $custom_password for you."
|
||
else
|
||
echo -e "- Create an admin account $custom_username with a strong random password for you."
|
||
fi
|
||
else
|
||
echo -e "- Create an admin account with random username and strong password for you."
|
||
fi
|
||
if [ "$CSF_SETUP" = true ]; then
|
||
echo -e "- Set up ConfigServer Firewall for enhanced security."
|
||
fi
|
||
|
||
echo -e "- Set up 2 hosting plans so you can start right away."
|
||
|
||
echo -e "\nThank you for your patience. We're setting everything up for your seamless OpenPanel experience!\n"
|
||
printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' -
|
||
echo -e ""
|
||
}
|
||
|
||
|
||
|
||
# Display error and exit
|
||
radovan() {
|
||
echo -e "${RED}INSTALLATION FAILED${RESET}"
|
||
echo ""
|
||
echo -e "Error: $2" >&2
|
||
exit 1
|
||
}
|
||
|
||
|
||
debug_log() {
|
||
local timestamp
|
||
timestamp=$(date +'%Y-%m-%d %H:%M:%S')
|
||
|
||
if [ "$DEBUG" = true ]; then
|
||
# Show both on terminal and log file
|
||
echo "[$timestamp] $message" | tee -a "$LOG_FILE"
|
||
"$@" 2>&1 | tee -a "$LOG_FILE"
|
||
else
|
||
# ❯❯❯
|
||
# No terminal output, only log file
|
||
echo "[$timestamp] COMMAND: $@" >> "$LOG_FILE"
|
||
"$@" > /dev/null 2>&1
|
||
fi
|
||
}
|
||
|
||
# Check if a package is already installed
|
||
is_package_installed() {
|
||
if [ "$DEBUG" = false ]; then
|
||
$PACKAGE_MANAGER -qq list "$1" 2>/dev/null | grep -qE "^ii"
|
||
else
|
||
$PACKAGE_MANAGER -qq list "$1" | grep -qE "^ii"
|
||
echo "Updating $PACKAGE_MANAGER package manager.."
|
||
fi
|
||
}
|
||
|
||
get_server_ipv4(){
|
||
# Get server ipv4
|
||
|
||
# list of ip servers for checks
|
||
IP_SERVER_1="https://ip.openpanel.com"
|
||
IP_SERVER_2="https://ipv4.openpanel.com"
|
||
IP_SERVER_3="https://ifconfig.me"
|
||
|
||
current_ip=$(curl --silent --max-time 2 -4 $IP_SERVER_1 || \
|
||
wget --inet4-only --timeout=2 -qO- $IP_SERVER_2 || \
|
||
curl --silent --max-time 2 -4 $IP_SERVER_3)
|
||
|
||
# If no site is available, get the ipv4 from the hostname -I
|
||
if [ -z "$current_ip" ]; then
|
||
# ip addr command is more reliable then hostname - to avoid getting private ip
|
||
current_ip=$(ip addr|grep 'inet '|grep global|head -n1|awk '{print $2}'|cut -f1 -d/)
|
||
fi
|
||
|
||
is_valid_ipv4() {
|
||
local ip=$1
|
||
# is it ip
|
||
[[ $ip =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] && \
|
||
# is it private
|
||
! [[ $ip =~ ^10\. ]] && \
|
||
! [[ $ip =~ ^172\.(1[6-9]|2[0-9]|3[0-1])\. ]] && \
|
||
! [[ $ip =~ ^192\.168\. ]]
|
||
}
|
||
|
||
if ! is_valid_ipv4 "$current_ip"; then
|
||
echo "Invalid or private IPv4 address: $current_ip. OpenPanel requires a public IPv4 address to bind Nginx configuration files."
|
||
fi
|
||
|
||
}
|
||
|
||
set_version_to_install(){
|
||
|
||
if [ "$CUSTOM_VERSION" = false ]; then
|
||
response=$(curl -4 -s "https://hub.docker.com/v2/repositories/openpanel/openpanel-ui/tags")
|
||
if command -v jq &> /dev/null; then
|
||
PANEL_VERSION=$(echo $response | jq -r '.results[0].name')
|
||
else
|
||
PANEL_VERSION=$(echo "$response" | grep -o '"name":"[^"]*"' | head -n 1 | sed 's/"name":"\([^"]*\)"/\1/')
|
||
fi
|
||
if [[ ! "$PANEL_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||
PANEL_VERSION="1.4.4" # fallback if hub.docker.com unreachable
|
||
fi
|
||
fi
|
||
}
|
||
|
||
|
||
# prints fullwidth line
|
||
print_space_and_line() {
|
||
echo " "
|
||
printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' -
|
||
echo " "
|
||
}
|
||
|
||
|
||
setup_progress_bar_script(){
|
||
# Progress bar script
|
||
PROGRESS_BAR_URL="https://raw.githubusercontent.com/pollev/bash_progress_bar/master/progress_bar.sh"
|
||
PROGRESS_BAR_FILE="progress_bar.sh"
|
||
|
||
# Check if wget is available
|
||
if command -v wget &> /dev/null; then
|
||
wget --timeout=5 --inet4-only "$PROGRESS_BAR_URL" -O "$PROGRESS_BAR_FILE" > /dev/null 2>&1
|
||
if [ $? -ne 0 ]; then
|
||
echo "ERROR: wget failed or timed out after 5 seconds while downloading from github"
|
||
echo "repeat with --debug flag to see where errored."
|
||
exit 1
|
||
fi
|
||
# If wget is not available, check if curl is available *(fallback for fedora)
|
||
elif command -v curl -4 &> /dev/null; then
|
||
curl -4 --max-time 5 -s "$PROGRESS_BAR_URL" -o "$PROGRESS_BAR_FILE" > /dev/null 2>&1
|
||
if [ $? -ne 0 ]; then
|
||
echo "ERROR: curl failed or timed out after 5 seconds while downloading progress_bar.sh"
|
||
exit 1
|
||
fi
|
||
else
|
||
echo "Neither wget nor curl is available. Please install one of them to proceed."
|
||
exit 1
|
||
fi
|
||
|
||
if [ ! -f "$PROGRESS_BAR_FILE" ]; then
|
||
echo "ERROR: Failed to download progress_bar.sh - Github may be unreachable from your server: $PROGRESS_BAR_URL"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
|
||
|
||
|
||
display_what_will_be_installed(){
|
||
echo -e "[ OK ] DETECTED OPERATING SYSTEM: ${GREEN} ${NAME^^} $VERSION_ID ${RESET}"
|
||
if [ -z "$SKIP_REQUIREMENTS" ]; then
|
||
if [ "$architecture" == "x86_64" ] || [ "$architecture" == "aarch64" ]; then
|
||
echo -e "[ OK ] CPU ARCHITECTURE: ${GREEN} ${architecture^^} ${RESET}"
|
||
else
|
||
echo -e "[PASS] CPU ARCHITECTURE: ${YELLOW} ${architecture^^} ${RESET}"
|
||
fi
|
||
fi
|
||
echo -e "[ OK ] PACKAGE MANAGEMENT SYSTEM: ${GREEN} ${PACKAGE_MANAGER^^} ${RESET}"
|
||
echo -e "[ OK ] PUBLIC IPV4 ADDRESS: ${GREEN} ${current_ip} ${RESET}"
|
||
echo ""
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
# ======================================================================
|
||
# Core program logic
|
||
setup_progress_bar_script
|
||
source "$PROGRESS_BAR_FILE" # Source the progress bar script
|
||
|
||
FUNCTIONS=(
|
||
detect_os_cpu_and_package_manager # detect os and package manager
|
||
display_what_will_be_installed # display os, version, ip
|
||
install_python312
|
||
update_package_manager # update dnf/yum/apt-get
|
||
install_packages # install docker, csf, sqlite, etc.
|
||
download_skeleton_directory_from_github # download configuration to /etc/openpanel/
|
||
edit_fstab # enable quotas
|
||
setup_bind # must run after -configuration
|
||
install_openadmin # set admin interface
|
||
opencli_setup # set terminal commands
|
||
extra_step_on_hetzner # run it here, then csf install does docker restart later
|
||
setup_redis_service # for redis container
|
||
create_rdnc # generate rdnc key for managing domains
|
||
panel_customize # customizations
|
||
docker_compose_up # must be after configure_nginx
|
||
docker_cpu_limiting # https://docs.docker.com/engine/security/rootless/#limiting-resources
|
||
set_premium_features # must be after docker_compose_up
|
||
configure_coraza # download corazawaf coreruleset or change docker image
|
||
extra_step_for_caddy # so that webmail domain works without any setups!
|
||
enable_dev_mode # https://dev.openpanel.com/cli/config.html#dev-mode
|
||
set_custom_hostname # set hostname if provided
|
||
generate_and_set_ssl_for_panels # if FQDN then lets setup https
|
||
setup_firewall_service # setup firewall
|
||
set_system_cronjob # setup crons, must be after csf
|
||
set_logrotate # setup logrotate, ignored on fedora
|
||
tweak_ssh # basic ssh
|
||
log_dirs # for almalinux
|
||
download_ui_image # pull openpanel-ui image
|
||
setup_swap # swap space
|
||
clean_apt_and_dnf_cache # clear
|
||
verify_license # ping our server
|
||
)
|
||
|
||
|
||
TOTAL_STEPS=${#FUNCTIONS[@]}
|
||
CURRENT_STEP=0
|
||
|
||
update_progress() {
|
||
CURRENT_STEP=$((CURRENT_STEP + 1))
|
||
PERCENTAGE=$(($CURRENT_STEP * 100 / $TOTAL_STEPS))
|
||
draw_progress_bar $PERCENTAGE
|
||
}
|
||
|
||
main() {
|
||
enable_trapping # clean on CTRL+C
|
||
setup_scroll_area # load progress bar
|
||
for func in "${FUNCTIONS[@]}"
|
||
do
|
||
$func # Execute each function
|
||
update_progress # update progress after each
|
||
done
|
||
destroy_scroll_area
|
||
}
|
||
|
||
|
||
|
||
|
||
check_requirements() {
|
||
if [ -z "$SKIP_REQUIREMENTS" ]; then
|
||
|
||
# check if the current user is root
|
||
if [ "$(id -u)" != "0" ]; then
|
||
echo -e "${RED}Error: you must be root to execute this script.${RESET}" >&2
|
||
exit 1
|
||
# check if OS is MacOS
|
||
elif [ "$(uname)" = "Darwin" ]; then
|
||
echo -e "${RED}Error: MacOS is not currently supported.${RESET}" >&2
|
||
exit 1
|
||
# check if running inside a container
|
||
elif [[ -f /.dockerenv || $(grep -sq 'docker\|lxc' /proc/1/cgroup) ]]; then
|
||
echo -e "${RED}Error: running openpanel inside a container is not supported.${RESET}" >&2
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
}
|
||
|
||
|
||
parse_args() {
|
||
|
||
# ======================================================================
|
||
# Usage info
|
||
show_help() {
|
||
echo "Available options:"
|
||
echo " --key=<key_here> Set the license key for OpenPanel Enterprise edition."
|
||
echo " --domain=<domain> Set the server hostname and domain for accessing panel."
|
||
echo " --username=<username> Set Admin username - random generated if not provided."
|
||
echo " --password=<password> Set Admin Password - random generated if not provided."
|
||
echo " --version=<version> Set a custom OpenPanel version to be installed."
|
||
echo " --email=<stefan@example.net> Set email address to receive email with admin credentials and future notifications."
|
||
echo " --skip-requirements Skip the requirements check."
|
||
echo " --skip-panel-check Skip checking if existing panels are installed."
|
||
echo " --skip-apt-update Skip the APT update."
|
||
echo " --skip-firewall Skip installing CSF - Only do this if you will set another external firewall!"
|
||
echo " --csf Install and setup ConfigServer Firewall (default from >0.2.3)"
|
||
echo " --no-waf Do not configure CorazaWAF with OWASP Coreruleset."
|
||
echo " --no-ssh Disable port 22 and whitelist the IP address of user installing the panel."
|
||
echo " --skip-dns-server Skip setup for DNS (Bind9) server."
|
||
echo " --post_install=<path> Specify the post install script path."
|
||
echo " --screenshots=<url> Set the screenshots API URL."
|
||
echo " --swap=<2> Set space in GB to be allocated for SWAP."
|
||
echo " --debug Display debug information during installation."
|
||
echo " --enable-dev-mode Enable dev_mode after installation."
|
||
echo " --repair OR --retry Retry and overwrite everything."
|
||
echo " -h, --help Show this help message and exit."
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
# ======================================================================
|
||
# Change defaults
|
||
while [[ $# -gt 0 ]]; do
|
||
case $1 in
|
||
--key=*)
|
||
SET_PREMIUM=true
|
||
license_key="${1#*=}"
|
||
;;
|
||
--domain=*)
|
||
SET_HOSTNAME_NOW=true
|
||
new_hostname="${1#*=}"
|
||
;;
|
||
--username=*)
|
||
SET_ADMIN_USERNAME=true
|
||
custom_username="${1#*=}"
|
||
;;
|
||
--password=*)
|
||
SET_ADMIN_PASSWORD=true
|
||
custom_password="${1#*=}"
|
||
;;
|
||
--skip-requirements)
|
||
SKIP_REQUIREMENTS=true
|
||
;;
|
||
--skip-panel-check)
|
||
SKIP_PANEL_CHECK=true
|
||
;;
|
||
--skip-apt-update)
|
||
SKIP_APT_UPDATE=true
|
||
;;
|
||
--skip-dns-server)
|
||
SKIP_DNS_SERVER=true
|
||
;;
|
||
--skip-firewall)
|
||
SKIP_FIREWALL=true
|
||
;;
|
||
--csf)
|
||
CSF_SETUP=true
|
||
;;
|
||
--no-waf)
|
||
CORAZA=false
|
||
;;
|
||
--debug)
|
||
DEBUG=true
|
||
;;
|
||
--no-ssh)
|
||
NO_SSH=true
|
||
;;
|
||
--post_install=*)
|
||
post_install_path="${1#*=}"
|
||
;;
|
||
--screenshots=*)
|
||
SCREENSHOTS_API_URL="${1#*=}"
|
||
;;
|
||
--version=*)
|
||
CUSTOM_VERSION=true
|
||
PANEL_VERSION="${1#*=}"
|
||
;;
|
||
--swap=*)
|
||
SETUP_SWAP_ANYWAY=true
|
||
SWAP="${1#*=}"
|
||
;;
|
||
--email=*)
|
||
SEND_EMAIL_AFTER_INSTALL=true
|
||
EMAIL="${1#*=}"
|
||
;;
|
||
--repair)
|
||
REPAIR=true
|
||
SKIP_PANEL_CHECK=true
|
||
SKIP_APT_UPDATE=true
|
||
#SKIP_REQUIREMENTS=true
|
||
;;
|
||
--retry)
|
||
REPAIR=true
|
||
SKIP_PANEL_CHECK=true
|
||
SKIP_APT_UPDATE=true
|
||
#SKIP_REQUIREMENTS=true
|
||
;;
|
||
|
||
--enable-dev-mode)
|
||
DEV_MODE=true
|
||
;;
|
||
|
||
|
||
-h|--help)
|
||
show_help
|
||
exit 0
|
||
;;
|
||
*)
|
||
echo "Unknown option: $1"
|
||
show_help
|
||
exit 1
|
||
;;
|
||
esac
|
||
shift
|
||
done
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
detect_installed_panels() {
|
||
if [ -z "$SKIP_PANEL_CHECK" ]; then
|
||
# Define an associative array with key as the directory path and value as the error message
|
||
declare -A paths=(
|
||
["/usr/local/admin/"]="You already have OpenPanel installed. ${RESET}\nInstead, did you want to update? Run ${GREEN}'opencli update --force' to update OpenPanel."
|
||
["/usr/local/cpanel/whostmgr"]="cPanel WHM is installed. OpenPanel only supports servers without any hosting control panel installed."
|
||
["/opt/psa/version"]="Plesk is installed. OpenPanel only supports servers without any hosting control panel installed."
|
||
["/usr/local/psa/version"]="Plesk is installed. OpenPanel only supports servers without any hosting control panel installed."
|
||
["/usr/local/CyberPanel"]="CyberPanel is installed. OpenPanel only supports servers without any hosting control panel installed."
|
||
["/usr/local/directadmin"]="DirectAdmin is installed. OpenPanel only supports servers without any hosting control panel installed."
|
||
["/usr/local/cwpsrv"]="CentOS Web Panel (CWP) is installed. OpenPanel only supports servers without any hosting control panel installed."
|
||
["/usr/local/httpd"]="Apache WebServer is already installed. OpenPanel only supports servers without any webservers installed."
|
||
["/usr/local/apache2"]="Apache WebServer is already installed. OpenPanel only supports servers without any webservers installed."
|
||
["/usr/sbin/httpd"]="Apache WebServer is already installed. OpenPanel only supports servers without any webservers installed."
|
||
["/usr/lib/nginx"]="Nginx WebServer is already installed. OpenPanel only supports servers without any webservers installed."
|
||
)
|
||
|
||
for path in "${!paths[@]}"; do
|
||
if [ -d "$path" ] || [ -e "$path" ]; then
|
||
radovan 1 "${paths[$path]}"
|
||
fi
|
||
done
|
||
|
||
echo -e "${GREEN}No currently installed hosting control panels or webservers found. Starting the installation process.${RESET}"
|
||
fi
|
||
}
|
||
|
||
|
||
|
||
detect_os_cpu_and_package_manager() {
|
||
if [ -f "/etc/os-release" ]; then
|
||
. /etc/os-release
|
||
|
||
case $ID in
|
||
ubuntu)
|
||
PACKAGE_MANAGER="apt-get"
|
||
;;
|
||
debian)
|
||
PACKAGE_MANAGER="apt-get"
|
||
;;
|
||
fedora)
|
||
PACKAGE_MANAGER="dnf"
|
||
;;
|
||
rocky)
|
||
PACKAGE_MANAGER="dnf"
|
||
;;
|
||
centos)
|
||
PACKAGE_MANAGER="yum"
|
||
;;
|
||
almalinux|alma)
|
||
PACKAGE_MANAGER="dnf"
|
||
;;
|
||
*)
|
||
echo -e "${RED}Unsupported Operating System: $ID. Exiting.${RESET}"
|
||
echo -e "${RED}INSTALL FAILED${RESET}"
|
||
exit 1
|
||
;;
|
||
esac
|
||
|
||
|
||
architecture=$(lscpu | grep Architecture | awk '{print $2}')
|
||
|
||
|
||
|
||
else
|
||
echo -e "${RED}Could not detect Linux distribution from /etc/os-release${RESET}"
|
||
echo -e "${RED}INSTALL FAILED${RESET}"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
|
||
docker_compose_up(){
|
||
echo "Setting docker-compose.."
|
||
# install docker compose on dnf
|
||
DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
|
||
mkdir -p $DOCKER_CONFIG/cli-plugins
|
||
|
||
if [ "$architecture" == "aarch64" ]; then
|
||
link="https://github.com/docker/compose/releases/download/v2.36.0/docker-compose-linux-aarch64"
|
||
else
|
||
link="https://github.com/docker/compose/releases/download/v2.36.0/docker-compose-linux-x86_64"
|
||
fi
|
||
|
||
curl -4 -SL $link -o $DOCKER_CONFIG/cli-plugins/docker-compose > /dev/null 2>&1
|
||
debug_log chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
|
||
|
||
# need to download compose and add it as alias
|
||
debug_log curl -4 -L "https://github.com/docker/compose/releases/download/v2.30.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||
debug_log mv /usr/local/bin/docker-compose /usr/bin/docker-compose
|
||
ln -s /usr/bin/docker-compose /usr/local/bin/docker-compose
|
||
debug_log chmod +x /usr/bin/docker-compose
|
||
|
||
function_to_insert='docker() {
|
||
if [[ $1 == "compose" ]]; then
|
||
/usr/local/bin/docker-compose "${@:2}"
|
||
else
|
||
command docker "$@"
|
||
fi
|
||
}'
|
||
|
||
# Check which shell configuration file to edit
|
||
if [ -f "$HOME/.bashrc" ]; then
|
||
config_file="$HOME/.bashrc"
|
||
elif [ -f "$HOME/.zshrc" ]; then
|
||
config_file="$HOME/.zshrc"
|
||
else
|
||
radovan 1 "ERROR: Neither .bashrc nor .zshrc file found. Exiting."
|
||
fi
|
||
|
||
# for armcpu, or if user did sudo su to switch..
|
||
source ~/.bashrc
|
||
|
||
# Check if the function already exists in the config file
|
||
if grep -q "docker() {" "$config_file"; then
|
||
:
|
||
else
|
||
# Add the function to the configuration file
|
||
echo "$function_to_insert" >> "$config_file"
|
||
debug_log "Function 'docker' has been added to $config_file."
|
||
source "$config_file"
|
||
fi
|
||
|
||
# https://community.openpanel.org/d/157-issue-with-installation-script-error-mysql-container-not-found
|
||
testing_docker=$(timeout 10 docker run --rm alpine echo "Hello from Alpine!")
|
||
if [ "$testing_docker" != "Hello from Alpine!" ]; then
|
||
radovan 1: "ERROR: Unable to run the Alpine Docker image! This suggests an issue with connecting to Docker Hub or with the Docker installation itself. To troubleshoot, try running the following command manually: 'docker run --rm alpine'."
|
||
fi
|
||
|
||
|
||
|
||
cp /etc/openpanel/mysql/initialize/1.1/plans.sql /root/initialize.sql > /dev/null 2>&1
|
||
|
||
# added in 1.2.5 for dumping dbs
|
||
chmod +x /etc/openpanel/mysql/scripts/dump.sh
|
||
|
||
# compose doesnt alllow /
|
||
cd /root || radovan 1 "ERROR: Failed to change directory to /root. OpenPanel needs to be installed by the root user and have write access to the /root directory."
|
||
|
||
rm -rf /etc/my.cnf .env > /dev/null 2>&1 # on centos we get default my.cnf, and on repair we already have symlink and .env
|
||
cp /etc/openpanel/docker/compose/docker-compose.yml /root/docker-compose.yml > /dev/null 2>&1
|
||
cp /etc/openpanel/docker/compose/.env /root/.env > /dev/null 2>&1
|
||
|
||
sed -i "s/^VERSION=.*$/VERSION=\"$PANEL_VERSION\"/" /root/.env
|
||
|
||
# generate random password for mysql
|
||
MYSQL_ROOT_PASSWORD=$(openssl rand -base64 -hex 9)
|
||
sed -i 's/MYSQL_ROOT_PASSWORD=.*/MYSQL_ROOT_PASSWORD='"${MYSQL_ROOT_PASSWORD}"'/g' /root/.env > /dev/null 2>&1
|
||
echo "MYSQL_ROOT_PASSWORD = $MYSQL_ROOT_PASSWORD"
|
||
|
||
# save it to /etc/my.cnf
|
||
ln -s /etc/openpanel/mysql/host_my.cnf /etc/my.cnf > /dev/null 2>&1
|
||
sed -i 's/password = .*/password = '"${MYSQL_ROOT_PASSWORD}"'/g' ${ETC_DIR}mysql/host_my.cnf > /dev/null 2>&1
|
||
sed -i 's/password = .*/password = '"${MYSQL_ROOT_PASSWORD}"'/g' ${ETC_DIR}mysql/container_my.cnf > /dev/null 2>&1
|
||
|
||
# added in 0.2.9
|
||
# fix for bug with mysql-server image on Almalinux 9.2
|
||
os_name=$(grep ^ID= /etc/os-release | cut -d'=' -f2 | tr -d '"')
|
||
if [ "$os_name" == "almalinux" ]; then
|
||
sed -i 's/mysql\/mysql-server/mysql/g' /root/docker-compose.yml
|
||
echo "mysql/mysql-server docker image has known issues on AlmaLinux - editing docker compose to use the mysql:latest instead"
|
||
elif [ "$os_name" == "debian" ]; then
|
||
echo "Setting AppArmor profiles for Debian"
|
||
apt install apparmor -y > /dev/null 2>&1
|
||
fi
|
||
|
||
|
||
if [ "$REPAIR" = true ]; then
|
||
echo "Deleting all existing MySQL data in volume root_openadmin_mysql due to the '--repair' flag."
|
||
cd /root && docker compose down > /dev/null 2>&1 # in case mysql was running
|
||
docker --context default volume rm root_openadmin_mysql > /dev/null 2>&1 # delete database
|
||
fi
|
||
|
||
if [ "$architecture" == "aarch64" ]; then
|
||
sed -i 's/mysql\/mysql-server/mariadb:10-focal/' docker-compose.yml
|
||
# todo: replace in docker-compose.yml if needed!
|
||
fi
|
||
|
||
# from 0.2.5 we only start mysql by default
|
||
cd /root && docker compose up -d openpanel_mysql > /dev/null 2>&1
|
||
|
||
# check if compose started the mysql container, and if is currently running
|
||
mysql_container=$(docker compose ps -q openpanel_mysql)
|
||
|
||
# Check if the container ID is found
|
||
if [ -z "$mysql_container" ]; then
|
||
radovan 1 "ERROR: MySQL container not found. Please ensure Docker Compose is set up correctly."
|
||
exit 1
|
||
fi
|
||
|
||
# Check if the container is running
|
||
if ! docker --context default ps -q --no-trunc | grep -q "$mysql_container"; then
|
||
radovan 1 "ERROR: MySQL container is not running. Please retry installation with '--repair' flag."
|
||
else
|
||
echo -e "[${GREEN}OK${RESET}] MySQL service started successfully."
|
||
fi
|
||
|
||
# needed from 1.0.0 for docker contexts to work both inside openpanel ui container and host os
|
||
ln -s / /hostfs > /dev/null 2>&1
|
||
}
|
||
|
||
|
||
|
||
|
||
clean_apt_and_dnf_cache(){
|
||
|
||
if [ "$PACKAGE_MANAGER" == "dnf" ]; then
|
||
dnf clean > /dev/null > /dev/null 2>&1
|
||
elif [ "$PACKAGE_MANAGER" == "apt-get" ]; then
|
||
# clear /var/cache/apt/archives/ # TODO: cover https://github.com/debuerreotype/debuerreotype/issues/95
|
||
apt-get clean > /dev/null > /dev/null 2>&1
|
||
fi
|
||
}
|
||
|
||
tweak_ssh(){
|
||
echo "Tweaking SSH service.."
|
||
|
||
sed -i "s/[#]LoginGraceTime [[:digit:]]m/LoginGraceTime 1m/g" /etc/ssh/sshd_config
|
||
|
||
if [ "$PACKAGE_MANAGER" == "apt-get" ]; then
|
||
if [ -z "$(grep "^DebianBanner no" /etc/ssh/sshd_config)" ]; then
|
||
sed -i '/^[#]Banner .*/a DebianBanner no' /etc/ssh/sshd_config
|
||
if [ -z "$(grep "^DebianBanner no" /etc/ssh/sshd_config)" ]; then
|
||
echo '' >> /etc/ssh/sshd_config # fallback
|
||
echo 'DebianBanner no' >> /etc/ssh/sshd_config
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
|
||
|
||
#echo "Generating MOTD for users.. *(auto-refreshed every 10hrs)"
|
||
#opencli server-motd > /dev/null 2>&1 #user has no access to check version: {"error": "Docker image or .env files are missing."}
|
||
|
||
# ssh on debian, sshd on rhel
|
||
if [ "$PACKAGE_MANAGER" == "dnf" ] || [ "$PACKAGE_MANAGER" == "yum" ]; then
|
||
systemctl restart sshd > /dev/null 2>&1
|
||
else
|
||
systemctl restart ssh > /dev/null 2>&1
|
||
fi
|
||
|
||
echo -e "[${GREEN} OK ${RESET}] SSH service is configured."
|
||
|
||
}
|
||
|
||
|
||
setup_firewall_service() {
|
||
if [ -z "$SKIP_FIREWALL" ]; then
|
||
echo "Setting up the firewall.."
|
||
|
||
if [ "$CSF_SETUP" = true ]; then
|
||
echo "Installing ConfigServer Firewall.."
|
||
|
||
install_csf() {
|
||
wget --inet4-only https://download.configserver.com/csf.tgz > /dev/null 2>&1
|
||
debug_log tar -xzf csf.tgz
|
||
rm csf.tgz
|
||
cd csf
|
||
sh install.sh > /dev/null 2>&1
|
||
cd ..
|
||
rm -rf csf
|
||
#perl /usr/local/csf/bin/csftest.pl
|
||
echo "Setting CSF auto-login from OpenAdmin interface.."
|
||
if [ "$PACKAGE_MANAGER" == "dnf" ]; then
|
||
debug_log dnf install -y wget curl yum-utils policycoreutils-python-utils libwww-perl
|
||
# fixes bug when starting csf: Can't locate locale.pm in @INC (you may need to install the locale module)
|
||
if [ -f /etc/fedora-release ]; then
|
||
debug_log yum --allowerasing install perl -y
|
||
fi
|
||
|
||
|
||
|
||
elif [ "$PACKAGE_MANAGER" == "apt-get" ]; then
|
||
debug_log apt-get install -y perl libwww-perl libgd-dev libgd-perl libgd-graph-perl
|
||
fi
|
||
# autologin from openpanel
|
||
ln -s /etc/csf/ui/images/ /usr/local/admin/static/configservercsf
|
||
chmod +x /usr/local/admin/modules/security/csf.pl
|
||
|
||
|
||
# play nice with docker
|
||
git clone https://github.com/stefanpejcic/csfpost-docker.sh > /dev/null 2>&1
|
||
mv csfpost-docker.sh/csfpost.sh /usr/local/csf/bin/csfpost.sh
|
||
chmod +x /usr/local/csf/bin/csfpost.sh
|
||
rm -rf csfpost-docker.sh
|
||
}
|
||
|
||
|
||
function open_port_csf() {
|
||
local port=$1
|
||
local csf_conf="/etc/csf/csf.conf"
|
||
|
||
# IPv4
|
||
port_opened_v4=$(grep "TCP_IN = .*${port}" "$csf_conf")
|
||
if [ -z "$port_opened_v4" ]; then
|
||
sed -i "s/TCP_IN = \"\(.*\)\"/TCP_IN = \"\1,${port}\"/" "$csf_conf"
|
||
echo -e "Port ${GREEN} ${port} ${RESET} is now open in IPv4."
|
||
ports_opened=1
|
||
else
|
||
echo -e "Port ${GREEN} ${port} ${RESET} is already open for IPv4."
|
||
fi
|
||
# IPv6
|
||
port_opened_v6=$(grep "TCP_IN = .*${port}" "$csf_conf")
|
||
if [ -z "$port_opened_v6" ]; then
|
||
sed -i "s/TCP6_IN = \"\(.*\)\"/TCP6_IN = \"\1,${port}\"/" "$csf_conf"
|
||
echo -e "Port ${GREEN} ${port} ${RESET} is now open in IPv6."
|
||
ports_opened=1
|
||
else
|
||
echo -e "Port ${GREEN} ${port} ${RESET} is already open in IPv6."
|
||
fi
|
||
|
||
}
|
||
|
||
|
||
function open_tcpout_csf() {
|
||
local port=$1
|
||
local csf_conf="/etc/csf/csf.conf"
|
||
|
||
# IPv4
|
||
port_opened_v4=$(grep "TCP_OUT = .*${port}" "$csf_conf")
|
||
if [ -z "$port_opened_v4" ]; then
|
||
sed -i "s/TCP_OUT = \"\(.*\)\"/TCP_OUT = \"\1,${port}\"/" "$csf_conf"
|
||
echo -e "Outgoing Port ${GREEN} ${port} ${RESET} is now open in IPv4."
|
||
ports_opened=1
|
||
else
|
||
echo -e "Port ${GREEN} ${port} ${RESET} is already open in IPv4."
|
||
fi
|
||
|
||
# IPv6
|
||
port_opened_v6=$(grep "TCP6_OUT = .*${port}" "$csf_conf")
|
||
if [ -z "$port_opened_v6" ]; then
|
||
sed -i "s/TCP6_OUT = \"\(.*\)\"/TCP6_OUT = \"\1,${port}\"/" "$csf_conf"
|
||
echo -e "Outgoing Port ${GREEN} ${port} ${RESET} is now open in IPv6."
|
||
ports_opened=1
|
||
else
|
||
echo -e "Port ${GREEN} ${port} ${RESET} is already open in IPv6."
|
||
fi
|
||
|
||
|
||
}
|
||
|
||
edit_csf_conf() {
|
||
echo "Tweaking /etc/csf/csf.conf"
|
||
sed -i 's/TESTING = "1"/TESTING = "0"/' /etc/csf/csf.conf
|
||
sed -i 's/RESTRICT_SYSLOG = "0"/RESTRICT_SYSLOG = "3"/' /etc/csf/csf.conf
|
||
sed -i 's/ETH_DEVICE_SKIP = ""/ETH_DEVICE_SKIP = "docker0"/' /etc/csf/csf.conf
|
||
sed -i 's/DOCKER = "0"/DOCKER = "1"/' /etc/csf/csf.conf
|
||
|
||
echo "Blocking known TOR and PROXY blacklists"
|
||
blocklist_exists() {
|
||
local section_name=$1
|
||
grep -qF "Name: $section_name" /etc/csf/csf.blocklists
|
||
}
|
||
|
||
# Check if the sections exist, add them if missing
|
||
if ! blocklist_exists "PROXYSPY"; then
|
||
echo -e "# Name: PROXYSPY\n# Information: Open proxies (updated hourly)\nPROXYSPY|86400|0|http://txt.proxyspy.net/proxy.txt\n" >> /etc/csf/csf.blocklists
|
||
fi
|
||
|
||
if ! blocklist_exists "PROXYLISTS"; then
|
||
echo -e "# Name: PROXYLISTS\n# Information: Open proxies (this list is composed using an RSS feed)\nPROXYLISTS|86400|0|http://www.proxylists.net/proxylists.xml\n" >> /etc/csf/csf.blocklists
|
||
fi
|
||
|
||
if ! blocklist_exists "TOR Exit nodes"; then
|
||
echo -e "# Name: TOR Exit nodes\n# Information: Blocks known TOR exit notes\nTOR|86400|0|https://www.dan.me.uk/torlist/\n" >> /etc/csf/csf.blocklists
|
||
fi
|
||
|
||
}
|
||
|
||
set_csf_email_address() {
|
||
email_address=$(grep -E "^e-mail=" $CONFIG_FILE | cut -d "=" -f2)
|
||
|
||
if [[ -n "$email_address" ]]; then
|
||
sed -i "s/LF_ALERT_TO = \"\"/LF_ALERT_TO = \"$email_address\"/" /etc/csf/csf.conf
|
||
fi
|
||
}
|
||
|
||
function extract_port_from_file() {
|
||
local file_path=$1
|
||
local pattern=$2
|
||
local port=$(grep -Po "(?<=${pattern}[ =])\d+" "$file_path")
|
||
echo "$port"
|
||
}
|
||
|
||
|
||
install_csf
|
||
edit_csf_conf
|
||
open_tcpout_csf 3306 # mysql tcp_out only
|
||
open_tcpout_csf 465 # for emails
|
||
open_tcpout_csf 2087 # for openadmin api
|
||
open_port_csf 22 # ssh
|
||
open_port_csf 53 # dns
|
||
open_port_csf 80 # http
|
||
open_port_csf 443 # https
|
||
open_port_csf 2083 # user
|
||
open_port_csf 2087 # admin
|
||
open_port_csf $(extract_port_from_file "/etc/ssh/sshd_config" "Port") # ssh
|
||
open_port_csf 32768:60999 # docker
|
||
open_port_csf 21 # ftp
|
||
open_port_csf 21000:21010 # passive ftp
|
||
set_csf_email_address
|
||
csf -r > /dev/null 2>&1
|
||
echo "Restarting CSF service"
|
||
systemctl restart docker # not sure why
|
||
systemctl enable csf
|
||
systemctl restart csf # also restarts docker at csfpost.sh
|
||
|
||
# https://github.com/stefanpejcic/OpenPanel/issues/338
|
||
touch /usr/sbin/sendmail
|
||
chmod +x /usr/sbin/sendmail
|
||
|
||
if command -v csf > /dev/null 2>&1; then
|
||
echo -e "[${GREEN} OK ${RESET}] ConfigServer Firewall is installed and configured."
|
||
else
|
||
echo -e "[${RED} X ${RESET}] ConfigServer Firewall is not installed properly."
|
||
fi
|
||
fi
|
||
fi
|
||
}
|
||
|
||
update_package_manager() {
|
||
if [ "$SKIP_APT_UPDATE" = false ]; then
|
||
echo "Updating $PACKAGE_MANAGER package manager.."
|
||
debug_log $PACKAGE_MANAGER update -y
|
||
fi
|
||
}
|
||
|
||
|
||
|
||
create_rdnc() {
|
||
if [ "$SKIP_DNS_SERVER" = false ]; then
|
||
echo "Setting remote name daemon control (rndc) for DNS.."
|
||
mkdir -p /etc/bind/
|
||
cp -r /etc/openpanel/bind9/* /etc/bind/
|
||
|
||
# Only on Ubuntu and Debian 12, systemd-resolved is installed
|
||
if [ -f /etc/os-release ] && grep -qE "Ubuntu|Debian" /etc/os-release; then
|
||
echo "DNSStubListener=no" >> /etc/systemd/resolved.conf
|
||
systemctl restart systemd-resolved
|
||
fi
|
||
|
||
RNDC_KEY_PATH="/etc/bind/rndc.key"
|
||
|
||
if [ -f "$RNDC_KEY_PATH" ]; then
|
||
echo "rndc.key already exists."
|
||
return 0
|
||
fi
|
||
|
||
echo "Generating rndc.key for DNS zone management."
|
||
|
||
debug_log timeout 90 docker --context default run --rm \
|
||
-v /etc/bind/:/etc/bind/ \
|
||
--entrypoint=/bin/sh \
|
||
ubuntu/bind9:latest \
|
||
-c 'rndc-confgen -a -A hmac-sha256 -b 256 -c /etc/bind/rndc.key'
|
||
|
||
# Check if rndc.key was successfully generated
|
||
if [ -f "$RNDC_KEY_PATH" ]; then
|
||
echo -e "[${GREEN} OK ${RESET}] rndc.key successfully generated."
|
||
else
|
||
echo -e "[${YELLOW} ! ${RESET}] Warning: Unable to generate rndc.key."
|
||
echo "RNDC is required for managing the named service. Without it, you won’t be able to reload DNS zones."
|
||
echo "That is OK if you don’t plan on using custom nameservers or DNS Clustering on this server."
|
||
fi
|
||
|
||
find /etc/bind/ -type d -print0 | xargs -0 chmod 755
|
||
find /etc/bind/ -type f -print0 | xargs -0 chmod 644
|
||
else
|
||
echo "Skipping rndc.key generation due to the '--skip-dns-server' flag."
|
||
fi
|
||
}
|
||
|
||
|
||
extra_step_on_hetzner() {
|
||
if [ -f /etc/hetzner-build ]; then
|
||
echo "Hetzner provider detected, adding Google DNS resolvers..."
|
||
echo "info: https://github.com/stefanpejcic/OpenPanel/issues/471"
|
||
|
||
mv /etc/resolv.conf /etc/resolv.conf.bak
|
||
echo "nameserver 8.8.8.8" >> /etc/resolv.conf
|
||
echo "nameserver 8.8.4.4" >> /etc/resolv.conf
|
||
fi
|
||
}
|
||
|
||
|
||
set_logrotate(){
|
||
|
||
echo "Setting Logrotate for Nginx.."
|
||
|
||
opencli server-logrotate
|
||
|
||
echo "Setting Logrotate for OpenPanel logs.."
|
||
|
||
cat <<EOF > "/etc/logrotate.d/openpanel"
|
||
/var/log/openpanel/**/*.log {
|
||
su root adm
|
||
size 50M
|
||
rotate 5
|
||
missingok
|
||
notifempty
|
||
compress
|
||
delaycompress
|
||
copytruncate
|
||
create 640 root adm
|
||
postrotate
|
||
endscript
|
||
}
|
||
EOF
|
||
|
||
logrotate -f /etc/logrotate.d/openpanel
|
||
|
||
|
||
|
||
echo "Setting Logrotate for Syslogs.."
|
||
|
||
cat <<EOF > "/etc/logrotate.d/syslog"
|
||
/var/log/syslog {
|
||
su root adm
|
||
weekly
|
||
rotate 4
|
||
missingok
|
||
notifempty
|
||
compress
|
||
delaycompress
|
||
postrotate
|
||
/usr/bin/systemctl reload rsyslog > /dev/null 2>&1 || true
|
||
endscript
|
||
}
|
||
EOF
|
||
|
||
logrotate -f /etc/logrotate.d/syslog
|
||
|
||
}
|
||
|
||
|
||
install_packages() {
|
||
|
||
echo "Installing required services.."
|
||
|
||
|
||
if [ "$PACKAGE_MANAGER" == "apt-get" ]; then
|
||
|
||
if [ -f /etc/os-release ] && grep -q "Ubuntu" /etc/os-release; then
|
||
|
||
packages=("curl" "git" "gnupg" "dbus-user-session" "systemd" "dbus" "systemd-container" "quota" "quotatool" "uidmap" "docker.io" "linux-generic" "default-mysql-client" "jc" "sqlite3" "geoip-bin")
|
||
else
|
||
# debian has linux-image-amd64 instead of generic
|
||
packages=("curl" "git" "gnupg" "dbus-user-session" "systemd" "dbus" "systemd-container" "quota" "quotatool" "uidmap" "docker.io" "linux-image-amd64" "default-mysql-client" "jc" "sqlite3" "geoip-bin")
|
||
|
||
fi
|
||
|
||
# https://www.faqforge.com/linux/fixed-ubuntu-apt-get-upgrade-auto-restart-services/
|
||
debug_log sed -i 's/#$nrconf{restart} = '"'"'i'"'"';/$nrconf{restart} = '"'"'a'"'"';/g' /etc/needrestart/needrestart.conf
|
||
|
||
debug_log $PACKAGE_MANAGER -qq install apt-transport-https ca-certificates -y
|
||
|
||
# configure apt to retry downloading on error
|
||
if [ ! -f /etc/apt/apt.conf.d/80-retries ]; then
|
||
echo "APT::Acquire::Retries \"3\";" > /etc/apt/apt.conf.d/80-retries
|
||
fi
|
||
|
||
echo "Updating certificates.."
|
||
debug_log update-ca-certificates
|
||
|
||
|
||
echo -e "Installing services.."
|
||
for package in "${packages[@]}"; do
|
||
echo -e "Installing ${GREEN}$package${RESET}"
|
||
debug_log $PACKAGE_MANAGER -qq install "$package" -y
|
||
done
|
||
|
||
for package in "${packages[@]}"; do
|
||
if is_package_installed "$package"; then
|
||
echo -e "${GREEN}$package is already installed. Skipping.${RESET}"
|
||
else
|
||
debug_log $PACKAGE_MANAGER -qq install "$package" -y
|
||
if [ $? -ne 0 ]; then
|
||
echo "Error: Installation of $package failed. Retrying.."
|
||
$PACKAGE_MANAGER -qq install "$package" -y
|
||
if [ $? -ne 0 ]; then
|
||
radovan 1 "ERROR: Installation failed. Please retry installation with '--repair' flag."
|
||
exit 1
|
||
fi
|
||
fi
|
||
fi
|
||
done
|
||
|
||
|
||
elif [ "$PACKAGE_MANAGER" == "yum" ]; then
|
||
|
||
# otherwise we get podman..
|
||
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
|
||
|
||
packages=("wget" "git" "gnupg" "dbus-user-session" "systemd" "dbus" "systemd-container" "quota" "quotatool" "uidmap" "docker-ce" "mysql" "pip" "jc" "sqlite" "geoip" "perl-Math-BigInt") #sqlite for almalinux and perl-Math-BigInt is needed for csf
|
||
|
||
for package in "${packages[@]}"; do
|
||
echo -e "Installing ${GREEN}$package${RESET}"
|
||
debug_log $PACKAGE_MANAGER install "$package" -y
|
||
done
|
||
|
||
elif [ "$PACKAGE_MANAGER" == "dnf" ]; then
|
||
# otherwise we get podman..
|
||
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
|
||
|
||
# special case for fedora,
|
||
if [ -f /etc/fedora-release ]; then
|
||
packages=("git" "wget" "gnupg" "dbus-user-session" "systemd" "dbus" "systemd-container" "quota" "quotatool" "uidmap" "docker" "docker-compose" "mysql" "docker-compose-plugin" "sqlite" "sqlite-devel" "perl-Math-BigInt")
|
||
else
|
||
packages=("git" "wget" "gnupg" "dbus-user-session" "systemd" "dbus" "systemd-container" "quota" "quotatool" "uidmap" "docker-ce" "docker-compose" "docker-ce-cli" "mysql" "containerd.io" "docker-compose-plugin" "sqlite" "sqlite-devel" "geoip" "perl-Math-BigInt")
|
||
fi
|
||
|
||
debug_log dnf install yum-utils -y
|
||
debug_log yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo -y # need confirm on alma, rocky and centos
|
||
|
||
# needed for csf
|
||
debug_log dnf --allowerasing install perl -y
|
||
|
||
# needed for gunicorn
|
||
debug_log dnf install epel-release -y
|
||
|
||
# needed for admin panel
|
||
debug_log dnf install python3-pip python3-devel gcc -y
|
||
|
||
for package in "${packages[@]}"; do
|
||
echo -e "Installing ${GREEN}$package${RESET}"
|
||
debug_log $PACKAGE_MANAGER install "$package" -y
|
||
debug_log $PACKAGE_MANAGER -y install "$package"
|
||
done
|
||
|
||
fi
|
||
}
|
||
|
||
|
||
docker_cpu_limiting() {
|
||
# https://docs.docker.com/engine/security/rootless/#limiting-resources
|
||
|
||
mkdir -p /etc/systemd/system/user@.service.d
|
||
|
||
cat > /etc/systemd/system/user@.service.d/delegate.conf << EOF
|
||
[Service]
|
||
Delegate=cpu cpuset io memory pids
|
||
EOF
|
||
|
||
debug_log systemctl daemon-reload
|
||
|
||
}
|
||
|
||
|
||
edit_fstab() {
|
||
|
||
echo "Setting quotas for disk limits of user files"
|
||
fstab_file="/etc/fstab"
|
||
root_entry=$(grep -E '^\S+\s+/.*' "$fstab_file")
|
||
|
||
if [[ "$root_entry" =~ "usrquota" && "$root_entry" =~ "grpquota" ]]; then
|
||
echo "Success, usrquota and grpquota are already set for /"
|
||
else
|
||
# Add usrquota and grpquota to the fstab entry (only for the root entry)
|
||
sed -i -E '/\s+\/\s+/s/(\S+)(\s+\/\s+\S+\s+\S+)(\s+[0-9]+\s+[0-9]+)$/\1\2,usrquota,grpquota\3/' "$fstab_file"
|
||
fi
|
||
systemctl daemon-reload >/dev/null 2>&1
|
||
quotaoff -a >/dev/null 2>&1
|
||
mount -o remount,usrquota,grpquota / >/dev/null 2>&1
|
||
mount /dev/vda1 /mnt >/dev/null 2>&1
|
||
cd /mnt >/dev/null 2>&1
|
||
chmod 600 aquota.user aquota.group >/dev/null 2>&1
|
||
quotacheck -cum / -f >/dev/null 2>&1
|
||
quotaon -a >/dev/null 2>&1
|
||
repquota / >/dev/null 2>&1
|
||
quota -v >/dev/null 2>&1
|
||
|
||
debug_log "Testing quotas.."
|
||
repquota -u / > /etc/openpanel/openpanel/core/users/repquota 2>/dev/null
|
||
if [ $? -eq 0 ]; then
|
||
echo -e "[${GREEN} OK ${RESET}] Quotas are now enabled for users."
|
||
else
|
||
echo -e "[${RED} FAIL ${RESET}] Quota check failed."
|
||
fi
|
||
|
||
|
||
}
|
||
|
||
set_system_cronjob(){
|
||
echo "Setting cronjobs.."
|
||
mv ${ETC_DIR}cron /etc/cron.d/openpanel
|
||
chown root:root /etc/cron.d/openpanel
|
||
chmod 0600 /etc/cron.d/openpanel
|
||
|
||
if [ "$PACKAGE_MANAGER" == "dnf" ] || [ "$PACKAGE_MANAGER" == "yum" ]; then
|
||
# extra steps for SELinux
|
||
restorecon -R /etc/cron.d/ > /dev/null 2>&1
|
||
restorecon -R /etc/cron.d/openpanel > /dev/null 2>&1
|
||
systemctl restart crond.service > /dev/null 2>&1
|
||
fi
|
||
|
||
if [ -f "/etc/cron.d/openpanel" ]; then
|
||
echo -e "[${GREEN} OK ${RESET}] Cronjobs configured."
|
||
fi
|
||
|
||
|
||
}
|
||
|
||
|
||
set_custom_hostname(){
|
||
if [ "$SET_HOSTNAME_NOW" = true ]; then
|
||
# Check if the provided hostname is a valid domain
|
||
if [[ $new_hostname =~ ^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
|
||
sed -i "s/example\.net/$new_hostname/g" /etc/openpanel/caddy/Caddyfile
|
||
else
|
||
echo "Hostname provided: $new_hostname is not a valid FQDN, OpenPanel will use IP address $current_ip for access."
|
||
fi
|
||
fi
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
opencli_setup(){
|
||
echo "Downloading OpenCLI and adding to path.."
|
||
cd /usr/local
|
||
git clone https://github.com/stefanpejcic/opencli.git
|
||
chmod +x -R /usr/local/opencli
|
||
ln -s /usr/local/opencli/opencli /usr/local/bin/opencli
|
||
echo "# opencli aliases
|
||
ALIASES_FILE=\"/usr/local/opencli/aliases.txt\"
|
||
generate_autocomplete() {
|
||
awk '{print \$NF}' \"\$ALIASES_FILE\"
|
||
}
|
||
complete -W \"\$(generate_autocomplete)\" opencli" >> ~/.bashrc
|
||
|
||
# Fix for: The command could not be located because '/usr/local/bin' is not included in the PATH environment variable.
|
||
export PATH="/usr/bin:$PATH"
|
||
|
||
source ~/.bashrc
|
||
|
||
echo "Testing 'opencli' commands:"
|
||
if [ -x "/usr/local/bin/opencli" ]; then
|
||
echo -e "[${GREEN} OK ${RESET}] opencli commands are available."
|
||
else
|
||
radovan 1 "'opencli --version' command failed."
|
||
fi
|
||
|
||
}
|
||
|
||
|
||
|
||
enable_dev_mode() {
|
||
if [ "$DEV_MODE" = true ]; then
|
||
echo "Enabling dev_mode"
|
||
opencli config update dev_mode "on" > /dev/null 2>&1
|
||
fi
|
||
}
|
||
|
||
set_premium_features(){
|
||
if [ "$SET_PREMIUM" = true ]; then
|
||
LICENSE="Enterprise"
|
||
echo "Setting OpenPanel enterprise version license key $license_key"
|
||
opencli config update key "$license_key"
|
||
systemctl restart admin > /dev/null 2>&1
|
||
|
||
#added in 0.2.5 https://community.openpanel.org/d/91-email-support-for-openpanel-enterprise-edition
|
||
echo "Setting mailserver.."
|
||
timeout 60 opencli email-server install
|
||
echo "Enabling Roundcube webmail.."
|
||
timeout 60 opencli email-webmail roundcube
|
||
|
||
else
|
||
LICENSE="Community"
|
||
fi
|
||
}
|
||
|
||
|
||
log_dirs() {
|
||
local error_dir="/var/log/openpanel/" # https://dev.openpanel.com/logs.html
|
||
mkdir -p ${error_dir} ${error_dir}user ${error_dir}admin
|
||
chmod -R 755 $error_dir
|
||
}
|
||
|
||
|
||
|
||
set_email_address_and_email_admin_logins(){
|
||
if [ "$SEND_EMAIL_AFTER_INSTALL" = true ]; then
|
||
# Check if the provided email is valid
|
||
if [[ $EMAIL =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
|
||
echo "Setting email address $EMAIL for notifications"
|
||
opencli config update email "$EMAIL"
|
||
# Send an email alert
|
||
|
||
generate_random_token_one_time_only() {
|
||
local config_file="${CONFIG_FILE}"
|
||
TOKEN_ONE_TIME="$(tr -dc 'a-zA-Z0-9' < /dev/urandom | head -c 64)"
|
||
local new_value="mail_security_token=$TOKEN_ONE_TIME"
|
||
sed -i "s|^mail_security_token=.*$|$new_value|" "${CONFIG_FILE}"
|
||
}
|
||
|
||
|
||
email_notification() {
|
||
local title="$1"
|
||
local message="$2"
|
||
generate_random_token_one_time_only
|
||
TRANSIENT=$(awk -F'=' '/^mail_security_token/ {print $2}' "${CONFIG_FILE}")
|
||
|
||
PROTOCOL="http"
|
||
admin_domain="127.0.0.1"
|
||
|
||
if [ "$SET_HOSTNAME_NOW" = true ]; then
|
||
if [[ $new_hostname =~ ^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
|
||
if [ -f "/etc/openpanel/caddy/ssl/acme-v02.api.letsencrypt.org-directory/$new_hostname/$new_hostname.key" ]; then
|
||
PROTOCOL="https"
|
||
admin_domain="$new_hostname"
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# Send email using appropriate protocol
|
||
curl -4 -k -X POST "$PROTOCOL://$admin_domain:2087/send_email" -F "transient=$TRANSIENT" -F "recipient=$EMAIL" -F "subject=$title" -F "body=$message"
|
||
|
||
}
|
||
|
||
server_hostname=$(hostname)
|
||
email_notification "OpenPanel successfully installed" "OpenAdmin URL: http://$server_hostname:2087/ | username: $new_username | password: $new_password"
|
||
else
|
||
echo "Address provided: $EMAIL is not a valid email address. Admin login credentials and future notifications will not be sent."
|
||
fi
|
||
fi
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
generate_and_set_ssl_for_panels() {
|
||
if [ "$SET_HOSTNAME_NOW" = true ]; then
|
||
echo "Checking if SSL can be generated for the server hostname.."
|
||
CADDYFILE="/etc/openpanel/caddy/Caddyfile"
|
||
HOSTNAME=$(awk '/# START HOSTNAME DOMAIN #/{flag=1; next} /# END HOSTNAME DOMAIN #/{flag=0} flag' "$CADDYFILE" | awk 'NF {print $1; exit}')
|
||
|
||
if [[ -n "$HOSTNAME" && "$HOSTNAME" != "example.net" ]]; then
|
||
cd /root && docker --context default compose up -d caddy # start and generate ssl
|
||
|
||
MAX_RETRIES=5
|
||
SLEEP_SECONDS=5
|
||
SUCCESS=0
|
||
for ((i=1; i<=MAX_RETRIES; i++)); do
|
||
debug_log echo "Attempt $i to generate SSL for $HOSTNAME..."
|
||
if curl -4 -sf -o /dev/null "https://$HOSTNAME"; then
|
||
debug_log echo "SSL certificate is ready! OpenAdmin is now using HTTPS protocol."
|
||
SUCCESS=1
|
||
debug_log systemctl restart admin
|
||
break
|
||
else
|
||
debug_log echo "SSL not ready yet, retrying in $SLEEP_SECONDS seconds..."
|
||
debug_log docker restart caddy
|
||
sleep $SLEEP_SECONDS
|
||
fi
|
||
done
|
||
if [ $SUCCESS -ne 1 ]; then
|
||
echo "Failed to generate SSL certificate after $MAX_RETRIES attempts. OpenAdmin fallback to using HTTP protocol."
|
||
exit 1
|
||
fi
|
||
fi
|
||
fi
|
||
}
|
||
|
||
|
||
|
||
download_ui_image() {
|
||
# added in 0.2.2 to pre-pull image so its ready on acocunt create
|
||
debug_log "Pulling OpenPanel image in background (not starting the service).."
|
||
nohup sh -c "cd /root && docker --context default compose pull openpanel" </dev/null >nohup.out 2>nohup.err &
|
||
}
|
||
|
||
|
||
|
||
setup_redis_service() {
|
||
mkdir -p /tmp/redis
|
||
chmod 777 /tmp/redis
|
||
}
|
||
|
||
run_custom_postinstall_script() {
|
||
if [ -n "$post_install_path" ]; then
|
||
# run the custom script
|
||
echo " "
|
||
echo "Running post install script.."
|
||
debug_log "https://dev.openpanel.com/customize.html#After-installation"
|
||
debug_log bash $post_install_path
|
||
fi
|
||
}
|
||
|
||
|
||
verify_license() {
|
||
debug_log "echo Current time: $(date +%T)"
|
||
server_hostname=$(hostname)
|
||
license_data='{"hostname": "'"$server_hostname"'", "public_ip": "'"$current_ip"'"}'
|
||
response=$(curl -4 -s -X POST -H "Content-Type: application/json" -d "$license_data" https://api.openpanel.com/license/index.php)
|
||
debug_log "echo Checking OpenPanel license for IP address: $current_ip"
|
||
debug_log "echo Response: $response"
|
||
}
|
||
|
||
|
||
download_skeleton_directory_from_github(){
|
||
echo "Downloading configuration files to ${ETC_DIR}"
|
||
|
||
# Retry variables
|
||
MAX_RETRIES=5
|
||
RETRY_DELAY=5
|
||
ATTEMPT=1
|
||
|
||
while [ $ATTEMPT -le $MAX_RETRIES ]; do
|
||
git clone https://github.com/stefanpejcic/openpanel-configuration ${ETC_DIR} > /dev/null 2>&1
|
||
|
||
if [ -f "${CONFIG_FILE}" ]; then
|
||
echo -e "[${GREEN} OK ${RESET}] Configuration created successfully."
|
||
break
|
||
else
|
||
echo "Attempt $ATTEMPT of $MAX_RETRIES failed. Retrying in $RETRY_DELAY seconds..."
|
||
((ATTEMPT++))
|
||
sleep $RETRY_DELAY
|
||
fi
|
||
done
|
||
|
||
if [ ! -f "${CONFIG_FILE}" ]; then
|
||
radovan 1 "Downloading configuration files from GitHub failed after $MAX_RETRIES attempts, main conf file ${CONFIG_FILE} is missing."
|
||
fi
|
||
|
||
chmod a+x ${ETC_DIR}wordpress/wp-cli.phar
|
||
chmod +x /etc/openpanel/ftp/start_vsftpd.sh
|
||
systemctl daemon-reload > /dev/null 2>&1
|
||
|
||
}
|
||
|
||
|
||
setup_bind(){
|
||
if [ "$SKIP_DNS_SERVER" = false ]; then
|
||
echo "Setting DNS service.."
|
||
mkdir -p /etc/bind/
|
||
chmod 777 /etc/bind/
|
||
cp -r /etc/openpanel/bind9/* /etc/bind/
|
||
|
||
# only on ubuntu systemd-resolved is installed
|
||
if [ -f /etc/os-release ] && grep -q "Ubuntu" /etc/os-release; then
|
||
echo " DNSStubListener=no" >> /etc/systemd/resolved.conf && systemctl restart systemd-resolved
|
||
# debian12 also!
|
||
elif [ -f /etc/os-release ] && grep -q "Debian" /etc/os-release; then
|
||
echo " DNSStubListener=no" >> /etc/systemd/resolved.conf && systemctl restart systemd-resolved
|
||
fi
|
||
|
||
chmod 0777 -R /etc/bind
|
||
else
|
||
echo "Skipping BIND setup due to the '--skip-dns-server' flag."
|
||
fi
|
||
}
|
||
|
||
send_install_log(){
|
||
# Restore normal output to the terminal, so we dont save generated admin password in log file!
|
||
exec > /dev/tty
|
||
exec 2>&1
|
||
opencli report --public >> "$LOG_FILE"
|
||
curl -4 --max-time 15 -F "file=@/root/$LOG_FILE" https://support.openpanel.org/install_logs.php
|
||
# Redirect again stdout and stderr to the log file
|
||
exec > >(tee -a "$LOG_FILE")
|
||
exec 2>&1
|
||
}
|
||
|
||
|
||
rm_helpers(){
|
||
rm -rf $PROGRESS_BAR_FILE
|
||
}
|
||
|
||
|
||
|
||
setup_swap(){
|
||
# Function to create swap file
|
||
create_swap() {
|
||
fallocate -l ${SWAP_FILE}G /swapfile > /dev/null 2>&1
|
||
chmod 600 /swapfile
|
||
mkswap /swapfile
|
||
swapon /swapfile
|
||
echo "/swapfile none swap sw 0 0" >> /etc/fstab
|
||
|
||
echo -e "[${GREEN} OK ${RESET}] Created SWAP file of ${SWAP_FILE}G."
|
||
}
|
||
|
||
# Check if swap space already exists
|
||
if [ -n "$(swapon -s)" ]; then
|
||
echo "ERROR: Skipping creating swap space as there already exists a swap partition."
|
||
return
|
||
fi
|
||
|
||
|
||
|
||
# Check if we should set up swap anyway
|
||
if [ "$SETUP_SWAP_ANYWAY" = true ]; then
|
||
create_swap
|
||
else
|
||
# Only create swap if RAM is less than 8GB
|
||
memory_kb=$(grep 'MemTotal' /proc/meminfo | awk '{print $2}')
|
||
memory_gb=$(awk "BEGIN {print $memory_kb/1024/1024}")
|
||
|
||
if [ $(awk "BEGIN {print ($memory_gb < 8)}") -eq 1 ]; then
|
||
create_swap
|
||
else
|
||
echo "Total available memory is ${memory_gb}GB, skipping creating swap file."
|
||
fi
|
||
fi
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
support_message() {
|
||
|
||
DISCORD_INVITE_URL="https://discord.openpanel.com/"
|
||
FORUMS_LINK="https://community.openpanel.org/"
|
||
DOCS_LINK="https://openpanel.com/docs/admin/intro/"
|
||
DOCS_GET_STARTED_LINK="https://openpanel.com/docs/admin/intro/#post-install-steps"
|
||
GITHUB_LINK="https://github.com/stefanpejcic/OpenPanel/"
|
||
TICKETS_URL="https://my.openpanel.com/submitticket.php?step=2&deptid=2"
|
||
|
||
support_message_for_enterprise() {
|
||
echo ""
|
||
echo "🎉 Welcome aboard and thank you for choosing OpenPanel Enterprise edition! 🎉"
|
||
echo ""
|
||
echo "Need assistance or looking to learn more? We've got you covered:"
|
||
echo " - Check the Admin Docs: $DOCS_LINK"
|
||
echo " - Open Support Ticket: $TICKETS_URL"
|
||
echo " - Chat with us on Discord: $DISCORD_INVITE_URL"
|
||
echo ""
|
||
}
|
||
|
||
support_message_for_community() {
|
||
echo ""
|
||
echo "🎉 Welcome aboard and thank you for choosing OpenPanel! 🎉"
|
||
echo ""
|
||
echo "To get started, check out our Post Install Steps:"
|
||
echo "👉 $DOCS_GET_STARTED_LINK"
|
||
echo ""
|
||
echo "Join our community and connect with us on:"
|
||
echo " - Github: $GITHUB_LINK"
|
||
echo " - Discord: $DISCORD_INVITE_URL"
|
||
echo " - Our community forums: $FORUMS_LINK"
|
||
echo ""
|
||
}
|
||
|
||
if [[ "$LICENSE" == "Enterprise" ]]; then
|
||
support_message_for_enterprise
|
||
else
|
||
support_message_for_community
|
||
fi
|
||
|
||
|
||
}
|
||
|
||
panel_customize(){
|
||
if [ "$SCREENSHOTS_API_URL" == "local" ]; then
|
||
echo "Setting the local API service for website screenshots.. (additional 1GB of disk space will be used for the self-hosted Playwright service)"
|
||
debug_log playwright install
|
||
debug_log playwright install-deps
|
||
sed -i 's#screenshots=.*#screenshots=''#' "${CONFIG_FILE}" # must use '#' as delimiter
|
||
else
|
||
echo "Setting the remote API service '$SCREENSHOTS_API_URL' for website screenshots.."
|
||
sed -i 's#screenshots=.*#screenshots='"$SCREENSHOTS_API_URL"'#' "${CONFIG_FILE}" # must use '#' as delimiter
|
||
fi
|
||
}
|
||
|
||
|
||
check_permissions_in_root_dir() {
|
||
local root_dir="/root"
|
||
# Check if user has read and write permissions
|
||
if ! [ -r "$root_dir" ] || ! [ -w "$root_dir" ]; then
|
||
radovan 1 "User $(whoami) does NOT have read and write permissions on $root_dir"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
install_python312() {
|
||
OS=$(grep '^ID=' /etc/os-release | cut -d= -f2 | tr -d '"')
|
||
|
||
if command -v python3.12 &> /dev/null; then
|
||
echo "Python 3.12 is already installed, installing python3.12-venv.."
|
||
|
||
# install venv only!
|
||
debug_log $PACKAGE_MANAGER install -y software-properties-common
|
||
|
||
if [ "$OS" == "ubuntu" ]; then
|
||
debug_log add-apt-repository -y ppa:deadsnakes/ppa
|
||
elif [ "$OS" == "debian" ]; then
|
||
echo "Debian detected, adding backports repository."
|
||
wget --inet4-only -qO- https://pascalroeleven.nl/deb-pascalroeleven.gpg | tee /etc/apt/keyrings/deb-pascalroeleven.gpg &> /dev/null
|
||
cat <<EOF | tee /etc/apt/sources.list.d/pascalroeleven.sources
|
||
Types: deb
|
||
URIs: http://deb.pascalroeleven.nl/python3.12
|
||
Suites: bookworm-backports
|
||
Components: main
|
||
Signed-By: /etc/apt/keyrings/deb-pascalroeleven.gpg
|
||
EOF
|
||
fi
|
||
|
||
debug_log $PACKAGE_MANAGER update -y
|
||
debug_log $PACKAGE_MANAGER install -y python3.12-venv
|
||
|
||
|
||
|
||
|
||
|
||
else
|
||
echo "Installing Python 3.12"
|
||
debug_log $PACKAGE_MANAGER install -y software-properties-common
|
||
|
||
if [ "$OS" == "ubuntu" ]; then
|
||
debug_log add-apt-repository -y ppa:deadsnakes/ppa
|
||
|
||
debug_log $PACKAGE_MANAGER update -y
|
||
# https://almalinux.pkgs.org/8/almalinux-appstream-x86_64/python3.12-3.12.1-4.el8.x86_64.rpm.html
|
||
debug_log $PACKAGE_MANAGER install -y python3.12 python3.12-venv
|
||
|
||
elif [ "$OS" == "alma" ] || [ "$OS" == "rocky" ] || [ "$OS" == "centos" ]; then
|
||
debug_log $PACKAGE_MANAGER update -y
|
||
debug_log $PACKAGE_MANAGER install -y python3.12
|
||
|
||
|
||
elif [ "$OS" == "debian" ]; then
|
||
debug_log "adding backports repository."
|
||
debug_log wget --inet4-only -qO- https://pascalroeleven.nl/deb-pascalroeleven.gpg | tee /etc/apt/keyrings/deb-pascalroeleven.gpg &> /dev/null
|
||
debug_log cat <<EOF | tee /etc/apt/sources.list.d/pascalroeleven.sources
|
||
Types: deb
|
||
URIs: http://deb.pascalroeleven.nl/python3.12
|
||
Suites: bookworm-backports
|
||
Components: main
|
||
Signed-By: /etc/apt/keyrings/deb-pascalroeleven.gpg
|
||
EOF
|
||
|
||
|
||
debug_log $PACKAGE_MANAGER update -y
|
||
debug_log $PACKAGE_MANAGER install -y python3.12 python3.12-venv
|
||
|
||
elif [[ "$OS" == "almalinux" || "$OS" == "rocky" || "$OS" == "centos" ]]; then
|
||
|
||
debug_log dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm &> /dev/null
|
||
|
||
debug_log $PACKAGE_MANAGER update -y
|
||
debug_log $PACKAGE_MANAGER install -y python3.12 # venv is included!
|
||
|
||
fi
|
||
|
||
|
||
|
||
if python3.12 --version &> /dev/null; then
|
||
:
|
||
else
|
||
radovan 1 "Python 3.12 installation failed."
|
||
fi
|
||
|
||
fi
|
||
|
||
}
|
||
|
||
|
||
|
||
extra_step_for_caddy() {
|
||
sed -i "s/example\.net/$current_ip/g" /etc/openpanel/caddy/redirects.conf > /dev/null 2>&1
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
configure_coraza() {
|
||
|
||
if [ "$CORAZA" = true ]; then
|
||
echo "Installing CorazaWAF and setting OWASP core ruleset.."
|
||
debug_log mkdir -p /etc/openpanel/caddy/
|
||
debug_log wget --inet4-only https://raw.githubusercontent.com/corazawaf/coraza/v3/dev/coraza.conf-recommended -O /etc/openpanel/caddy/coraza_rules.conf
|
||
debug_log git clone https://github.com/coreruleset/coreruleset /etc/openpanel/caddy/coreruleset/
|
||
else
|
||
echo "Disabling CorazaWAF: setting caddy:latest docker image instead of openpanel/caddy-coraza"
|
||
sed -i 's|image: .*caddy.*|image: caddy:latest|' /root/docker-compose.yml
|
||
fi
|
||
|
||
}
|
||
|
||
|
||
install_openadmin(){
|
||
|
||
# OpenAdmin
|
||
#
|
||
# https://openpanel.com/docs/admin/intro/
|
||
#
|
||
echo "Setting up OpenAdmin panel.."
|
||
|
||
local openadmin_dir="/usr/local/admin/"
|
||
|
||
if [ "$REPAIR" = true ]; then
|
||
rm -rf $openadmin_dir
|
||
fi
|
||
|
||
mkdir -p $openadmin_dir
|
||
|
||
debug_log echo "Downloading OpenAdmin files"
|
||
|
||
|
||
if [ "$architecture" == "aarch64" ]; then
|
||
branch="armcpu"
|
||
else
|
||
branch="110"
|
||
fi
|
||
|
||
git clone -b $branch --single-branch https://github.com/stefanpejcic/openadmin $openadmin_dir
|
||
|
||
cd $openadmin_dir
|
||
python3.12 -m venv ${openadmin_dir}venv
|
||
|
||
source ${openadmin_dir}venv/bin/activate
|
||
pip install --default-timeout=300 --force-reinstall --ignore-installed -r requirements.txt > /dev/null 2>&1 || pip install --default-timeout=300 --force-reinstall --ignore-installed -r requirements.txt --break-system-packages > /dev/null 2>&1
|
||
|
||
# on debian12 yaml is also needed to read conf files!
|
||
if [ -f /etc/os-release ] && grep -q "Debian" /etc/os-release; then
|
||
apt install python3-yaml -y > /dev/null 2>&1
|
||
fi
|
||
|
||
|
||
cp -fr /etc/openpanel/openadmin/service/openadmin.service ${SERVICES_DIR}admin.service > /dev/null 2>&1
|
||
cp -fr /usr/local/admin/service/watcher.service ${SERVICES_DIR}watcher.service > /dev/null 2>&1
|
||
|
||
systemctl daemon-reload > /dev/null 2>&1
|
||
|
||
systemctl start admin > /dev/null 2>&1
|
||
systemctl enable admin > /dev/null 2>&1
|
||
|
||
if [ "$SKIP_DNS_SERVER" = false ]; then
|
||
chmod +x /usr/local/admin/service/watcher.sh
|
||
systemctl start watcher > /dev/null 2>&1
|
||
systemctl enable watcher > /dev/null 2>&1
|
||
else
|
||
echo "Skipping Watcher service setup due to the '--skip-dns-server' flag."
|
||
fi
|
||
|
||
echo "Testing if OpenAdmin service is available on default port '2087':"
|
||
if ss -tuln | grep ':2087' >/dev/null; then
|
||
echo -e "[${GREEN} OK ${RESET}] OpenAdmin service is running."
|
||
else
|
||
radovan 1 "OpenAdmin service is NOT listening on port 2087."
|
||
fi
|
||
|
||
}
|
||
|
||
|
||
create_admin_and_show_logins_success_message() {
|
||
|
||
#motd
|
||
ln -s ${ETC_DIR}ssh/admin_welcome.sh /etc/profile.d/welcome.sh
|
||
chmod +x /etc/profile.d/welcome.sh
|
||
|
||
echo -e "${GREEN}OpenPanel ${LICENSE} $PANEL_VERSION installation complete.${RESET}"
|
||
echo ""
|
||
|
||
# added in 0.2.3
|
||
# option to specify logins
|
||
if [ "$SET_ADMIN_USERNAME" = true ]; then
|
||
new_username="${custom_username}"
|
||
else
|
||
wget --inet4-only -O /tmp/generate.sh https://gist.githubusercontent.com/stefanpejcic/905b7880d342438e9a2d2ffed799c8c6/raw/a1cdd0d2f7b28f4e9c3198e14539c4ebb9249910/random_username_generator_docker.sh > /dev/null 2>&1
|
||
|
||
if [ -f "/tmp/generate.sh" ]; then
|
||
source /tmp/generate.sh
|
||
new_username=($random_name)
|
||
else
|
||
new_username="admin"
|
||
fi
|
||
|
||
fi
|
||
|
||
if [ "$SET_ADMIN_PASSWORD" = true ]; then
|
||
if [[ "$custom_password" =~ ^[A-Za-z0-9]{6,16}$ ]]; then
|
||
new_password="${custom_password}"
|
||
else
|
||
echo "Warning: custom_password is invalid (must be alphanumeric and 6–16 characters). Generating a secure password."
|
||
new_password=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16)
|
||
fi
|
||
else
|
||
new_password=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16)
|
||
fi
|
||
|
||
|
||
display_admin_status_and_logins() {
|
||
# Restore normal output to the terminal, so we dont save generated admin password in log file!
|
||
exec > /dev/tty
|
||
exec 2>&1
|
||
|
||
opencli admin
|
||
echo -e "- Username: ${GREEN} ${new_username} ${RESET}"
|
||
echo -e "- Password: ${GREEN} ${new_password} ${RESET}"
|
||
echo " "
|
||
print_space_and_line
|
||
set_email_address_and_email_admin_logins
|
||
|
||
# Redirect again stdout and stderr to the log file
|
||
exec > >(tee -a "$LOG_FILE")
|
||
exec 2>&1
|
||
|
||
}
|
||
|
||
|
||
|
||
sqlite3 /etc/openpanel/openadmin/users.db "CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'user', is_active BOOLEAN DEFAULT 1 NOT NULL);" > /dev/null 2>&1 &&
|
||
|
||
opencli admin new "$new_username" "$new_password" --super > /dev/null 2>&1 &&
|
||
|
||
# Check if the user exists in the SQLite database
|
||
user_exists=$(sqlite3 /etc/openpanel/openadmin/users.db "SELECT COUNT(*) FROM user WHERE username = '$new_username';")
|
||
|
||
if [ "$user_exists" -gt 0 ]; then
|
||
echo "User $new_username has been successfully added."
|
||
display_admin_status_and_logins
|
||
else
|
||
echo "WARNING: Admin user $new_username was not created using opencli. Trying to insert user manually to SQLite database.."
|
||
password_hash=$(/usr/local/admin/venv/bin/python3 /usr/local/admin/core/users/hash $new_password)
|
||
create_table_sql="CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'user', is_active BOOLEAN DEFAULT 1 NOT NULL);"
|
||
insert_user_sql="INSERT INTO user (username, password_hash, role) VALUES ('$new_username', '$password_hash', 'admin');"
|
||
output=$(sqlite3 "$db_file_path" "$create_table_sql" "$insert_user_sql" 2>&1)
|
||
if [ $? -ne 0 ]; then
|
||
echo "WARNING: Admin user was not created: $output"
|
||
else
|
||
display_admin_status_and_logins
|
||
fi
|
||
|
||
fi
|
||
|
||
}
|
||
|
||
|
||
# ======================================================================
|
||
# Main program
|
||
|
||
# touch /root/openpanel_install.lock
|
||
|
||
(
|
||
flock -n 200 || { echo "Error: Another instance of the install script is already running. Exiting."; exit 1; }
|
||
# shellcheck disable=SC2068
|
||
parse_args "$@"
|
||
check_permissions_in_root_dir
|
||
get_server_ipv4
|
||
set_version_to_install
|
||
print_header
|
||
check_requirements
|
||
detect_installed_panels
|
||
install_started_message
|
||
main
|
||
rm_helpers
|
||
print_space_and_line
|
||
support_message
|
||
print_space_and_line
|
||
send_install_log
|
||
create_admin_and_show_logins_success_message
|
||
run_custom_postinstall_script
|
||
)200>/root/openpanel_install.lock
|