Skip to content

Instantly share code, notes, and snippets.

@jacoblindqvist
Created February 13, 2025 13:23
Show Gist options
  • Select an option

  • Save jacoblindqvist/e229dc9688e8a20aad1d3ce1d0e421e7 to your computer and use it in GitHub Desktop.

Select an option

Save jacoblindqvist/e229dc9688e8a20aad1d3ce1d0e421e7 to your computer and use it in GitHub Desktop.
install.sh 8.0.0 for Enhance
#!/bin/bash
# The purpose of this script is to bootstrap the Enhance installation by downloading
# and installing as systemd daemon the speficied version of controld.
set -e
export DEBIAN_FRONTEND="noninteractive"
print_help() {
echo "Usage:
Interactive Mode:
$0
Non Interactive Mode:
Control panel installation:
$0 --control-panel \\
App server installation:
$0 --control-panel-api-url <control panel API URL> \\
--reg-key <registration key> \\
"
}
# default values
restart_stopped_containers=true
non_interactive=false
for key in "$@"; do
case "${key}" in
--control-panel)
control_panel=true
;;
--control-panel-api-url)
control_panel_api_url="$2"
;;
--reg-key)
reg_key="$2"
;;
--registry)
registry_hostname="$2"
;;
--registry-user)
registry_user="$2"
;;
--registry-password)
registry_password="$2"
;;
--apt-repository)
apt_repository="$2"
;;
-h | --help)
print_help
exit 0
;;
esac
shift
done
# Error codes
invalid_arg=2
unsupported_distro=3
control_panel_unreachable=4
# Paths
data_dir="/var/local/enhance"
www_dir="/var/www"
# Control Panel Paths
# Note changing this paths will break other Enhance components (such as apachecd)
# that may rely on these invariants.
controld_daemon="enhcontrold"
controld_work_dir="${data_dir}/controld"
database_path="${controld_work_dir}/db.sqlite"
config_path="${controld_work_dir}/conf.json"
install_path="${controld_work_dir}/${controld_daemon}"
control_panel_dir="${www_dir}/control-panel"
orchd_static_dir="${control_panel_dir}/assets"
screenshots_dir="${control_panel_dir}/screenshots"
# TLS
cert_dir="/etc/ssl/certs/enhance"
key_dir="/etc/ssl/private/enhance"
ca_trust_dir="/usr/share/ca-certificates"
# Distros
distro="unknown"
distro_version="unknown"
supported_distros=("Ubuntu" "Pop!_OS")
minimum_os_version="20.04"
# Docker tags
orchd_tag="${orchd_tag:-latest-release}"
# prompt config
re_configure=false
allow_unreachable_domain=false
# system /etc/hosts file path
etc_hosts="/etc/hosts"
# domain and orchd api url validation regex
domain_regex_base='(([a-zA-Z0-9]{1,63}(-*[a-zA-Z0-9]{1,63})*)\.)+[a-zA-Z]{2,63}'
api_regex="^https?:\/\/${domain_regex_base}\/api$"
domain_regex="^${domain_regex_base}$"
# sets the control_panel variable according to the user provided input
# i.e if control_panel api url is provided control_panel is set to false
determine_control_panel() {
if [[ "${control_panel}" = true ]] && [[ -n "${control_panel_api_url}" ]]; then
echo "[$(date)] Error: Installation must be either for a control panel or an app server, not both"
exit "${invalid_arg}"
fi
if [[ -n "${control_panel_api_url}" ]]; then
control_panel=false
elif [[ -n "${domain}" ]]; then
control_panel=true
fi
}
# helper function that displays the current value
# and reminds user that pressing enter will keep the current
display_current_value() {
value="${1}"
if [[ -n "${value}" ]]; then
echo "Current Value: '${value}'"
echo "Press Enter to use current"
fi
}
# Retries the given command a pre-determined amount of times until it succeeds
# or the number of attempts reaches the limit.
# $1 - The command to run.
# $2 - The maximum number of attempts (default: 10).
# $3 - The interval of time in seconds between consecutive retries (default: 2).
retry_cmd() {
cmd="${1}"
max_attempts="${2-10}"
interval_s="${3-2}"
n=0
until [[ "$n" -ge "${max_attempts}" ]]; do
echo "[$(date)] Attempt ${n} to run: ${cmd}"
${cmd} && break
n=$((n + 1))
sleep "${interval_s}"
done
[[ "$n" -lt "${max_attempts}" ]]
}
# Installs all the required dependencies of Enhance in Ubuntu.
install_ubuntu_deps() {
# pause between apt-get commands to avoid `Could not get lock /var/lib/apt/lists/lock` error
readonly APT_CMD_PAUSE=3
sudo apt-get update -y
sleep $APT_CMD_PAUSE
sudo apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
iproute2 \
jq \
libsqlite3-dev \
libssl-dev \
policycoreutils \
software-properties-common \
unzip \
uuid \
rsync \
gnupg \
dnsutils \
btrfs-progs
# check if docker-ce is not installed
if [[ "$(dpkg-query -W -f='${Status}' docker-ce 2>/dev/null | grep -c "ok installed")" -eq 0 ]]; then
# Docker
# add docker gpg keys
curl -fsSL https://download.docker.com/linux/ubuntu/gpg |
sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo \
"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" |
sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt-get update
sleep $APT_CMD_PAUSE
sudo apt-get install -y docker-ce
fi
sleep $APT_CMD_PAUSE
sudo apt-get clean
}
# Installs all the dependencies required by Enhance to run, and created the required
# filesystem structure.
install_deps() {
distro_name="${distro}-${distro_version}"
echo -e "\n[$(date)] Installing dependencies for ${distro_name}"
install_ubuntu_deps
# create required dirs (owned by root) if they don't exist
for dir in "${cert_dir}" "${key_dir}" "${ca_trust_dir}" "${controld_work_dir}" "${www_dir}"; do
sudo mkdir -p "${dir}"
done
sudo chmod 600 "${controld_work_dir}"
if [[ "${control_panel}" = true ]]; then
sudo mkdir -p "${control_panel_dir}"
fi
# add root to docker group so controld can connect to the docker daemon
# (root is not in the docker group by default)
sudo usermod -a -G docker root
sudo docker login "${registry_hostname}" --username "${registry_user}" --password-stdin <<<"${registry_password}"
# start the docker daemon with every system boot
sudo systemctl enable docker
sudo systemctl start docker
}
# Extracts a file from a Docker image.
# $1 - The Docker image.
# $2 - The path of the file within the image.
# $3 - The output path where to extract the file.
extract_docker_file() {
img="${1}"
file_path="${2}"
output_path="${3}"
echo "[$(date)] Extracting ${file_path} from Docker image ${img}"
retry_cmd "sudo docker pull ${img}"
# create the container without starting it so that we can extract files from
# it and save the container id
id=$(sudo docker create "${img}")
# extract the file
sudo docker cp "${id}:${file_path}" "${output_path}"
# remove the container after extraction is complete
sudo docker rm "${id}"
}
# Downloads and installs the controld binary to bootstrap the installation. Other
# services are installed by controld itself.
# $1 - The controld tag.
install_controld() {
controld_tag="8.0.0"
echo -e "\n[$(date)] Installing controld version: ${controld_tag}"
# pull the release image
controld_img="${registry_hostname}/controld:${controld_tag}"
controld_bin="/usr/bin/enhance/controld"
extract_docker_file "${controld_img}" "${controld_bin}" "${install_path}"
# set controld binary owner and permissions
sudo chmod +x "${install_path}"
sudo chown root "${install_path}"
sudo chgrp root "${install_path}"
}
# Stores the (JSON) configurations of controld which includes sensitive data, and
# can be updated over time by controld itself.
store_configuration() {
configuration="{
\"registry\": {
\"url\": \"${registry_hostname}\",
\"user\": \"${registry_user}\",
\"secret\": \"${registry_password}\"
},
\"reg_key\": \"${reg_key}\"
}"
echo "${configuration}" | sudo tee "${config_path}" 1>/dev/null
sudo chmod 600 "${config_path}"
}
# Here we register controld's systemd daemon and start it, the rest is taken care
# of by controld.
create_controld_systemd_unit() {
echo -e "\n[$(date)] Creating ${controld_daemon} systemd service unit"
docker_path="$(which docker)"
openssl_path="$(which openssl)"
ip_path="$(sudo which ip)"
sh_path="$(which sh)"
# set contrld's execution mode
if [[ "${control_panel}" = true ]]; then
mode=master
key_pass="pass:${registry_password}"
control_panel_domain="${domain}"
else
mode=slave
key_pass=""
control_panel_domain="${control_panel_api_url}"
fi
# NOTE: service file is defined inline so that it doesn't have to be
# downloaded separately (plus it's easier to subsitute env var values
# in-script). In the future we may bundle such dependencies in one place in
# a zip file or similar.
unit_conf="
[Unit]
Description=Enhance control daemon service
Wants=network-online.target
After=network.target network-online.target
Requires=network.target
[Service]
StartLimitInterval=5
User=root
Group=root
Type=simple
Restart=always
RestartSec=1
Environment=CA_TRUST_DIR=${ca_trust_dir}
Environment=CERT_DIR=${cert_dir}
Environment=CONTROL_PANEL_DIR=${control_panel_dir}
Environment=CONTROL_PANEL_DOMAIN=${control_panel_domain}
Environment=DATABASE_URL=${database_path}
Environment=DATA_DIR=${data_dir}
Environment=DOCKER_PATH=${docker_path}
Environment=INSTALL_PATH=${install_path}
Environment=IP_PATH=${ip_path}
Environment=JWT_SECRET_DIR=${key_dir}
Environment=KEY_DIR=${key_dir}
Environment=KEY_PASS=${key_pass}
Environment=MODE=${mode}
Environment=OPENSSL_PATH=${openssl_path}
Environment=ORCHD_TAG=${orchd_tag}
Environment=RESTART_STOPPED_CONTAINERS=${restart_stopped_containers}
Environment=RUST_LOG=${rust_log}
Environment=SCREENSHOTS_DIR=${screenshots_dir}
Environment=STATIC_DIR=${orchd_static_dir}
Environment=WWW_DIR=${www_dir}
WorkingDirectory=${controld_work_dir}
ExecStart=${sh_path} -c \"${install_path}\"
[Install]
WantedBy=multi-user.target"
unit_filename="${controld_daemon}.service"
unit_conf_path="/etc/systemd/system/${unit_filename}"
echo "Creating conf in ${unit_conf_path}"
echo "${unit_conf}" | sudo tee "${unit_conf_path}" 1>/dev/null
# enable (auto-start) controld service and start it
echo "Enabling and starting ${unit_filename}"
sudo systemctl enable "${unit_filename}"
sudo systemctl start "${unit_filename}"
}
install_apt_repo() {
sudo apt install -y gnupg coreutils
if [ -z "${apt_repository}" ]; then
apt_repository="https://apt.enhance.com"
fi
mkdir -p /usr/share/keyrings
curl -o /tmp/enhance-pub-key.gpg $apt_repository/pgp-key.public
cat /tmp/enhance-pub-key.gpg | gpg --dearmor >/tmp/enhance-pub-key-dearmored.gpg
sudo cp /tmp/enhance-pub-key-dearmored.gpg /usr/share/keyrings/enhance.gpg
echo "deb [arch=amd64, signed-by=/usr/share/keyrings/enhance.gpg] ${apt_repository} $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/enhance.list >/dev/null
sudo apt update
}
install_appcd() {
echo "Installing appcd and building chroot, this may take a long time..."
if [ -z "${appcd_version}" ]; then
sudo apt -y install appcd
else
sudo apt -y install "appcd=${appcd_version}"
fi
}
install_control_panel_secondary() {
echo "Fetching version from master server at ${control_panel_api_url}/version"
# Fetch version from the API
version=$(curl -k -s $control_panel_api_url/version)
echo "Version of master server is ${version}"
# Check if the version is in valid SemVer format
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Invalid control panel version format: $version"
exit 1
fi
# Compare the version with 12.0.0
version_comparison=$(echo -e "$version\n11.99.99" | sort -V | head -n1)
if [[ "$version_comparison" == "$version" ]]; then
echo "Version is < 12.0.0"
install_control_panel_secondary_legacy
else
echo "Version is >= 12.0.0"
install_control_panel_secondary_current
fi
}
install_control_panel_secondary_legacy() {
echo "Installing legacy secondary server"
install_deps
install_appcd
install_controld
store_configuration
create_controld_systemd_unit
echo "Done, go to your control panel to manage the new server"
}
install_control_panel_secondary_current() {
echo "Installing secondary server for >= 12.0.0 with reg key $reg_key"
apt-get install -y ecp-core ecp-filerd
control_panel_api_url="${control_panel_api_url#https://}" # Removes https:// from the start
control_panel_api_url="${control_panel_api_url%/api}" # Removes /api from the end
ecp join $control_panel_api_url $reg_key
echo "Done, go to your control panel to manage the new server"
}
## Execution starts here
if [[ "${EUID}" != 0 ]]; then
if sudo true; then
echo ""
else
echo "[$(date)] Error: Please run this script with root privileges!"
exit 1
fi
fi
if [[ -d "${data_dir}" ]]; then
echo "[$(date)] Error: Cannot install enhance '${data_dir}' already exists!"
exit 1
fi
echo -e "[$(date)] Welcome to Enhance Installation \n\n"
echo "Installing lsb-release if not present"
apt-get install -y lsb-release
determine_control_panel
echo "Installing apt repo"
install_apt_repo
if [[ -z "$control_panel_api_url" ]]; then
clear
echo "Installing fresh control panel"
apt-get -o DPkg::Lock::Timeout=120 -y install ecp-core
apt-get -o DPkg::Lock::Timeout=120-y install ecp-filerd
sleep 5
apt-get -o DPkg::Lock::Timeout=120 -y install orchd
ecp init
else
echo "Installing secondary server"
install_control_panel_secondary
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment