diff --git a/uisp/Chart.yaml b/uisp/Chart.yaml new file mode 100644 index 0000000..3dffffd --- /dev/null +++ b/uisp/Chart.yaml @@ -0,0 +1,9 @@ +apiVersion: v2 +name: uisp +description: A Helm chart for deploying UISP +type: application +version: 0.1.0 +appVersion: "1.0" +maintainers: + - name: Your Name + email: your.email@example.com \ No newline at end of file diff --git a/uisp/docker-compose.yml b/uisp/docker-compose.yml new file mode 100644 index 0000000..e3bf325 --- /dev/null +++ b/uisp/docker-compose.yml @@ -0,0 +1,249 @@ +networks: + public: + ipam: + config: + - subnet: "172.18.251.0/25" + + internal: + internal: true + ipam: + config: + - subnet: "172.18.251.128/25" + + +services: + fluentd: + container_name: unms-fluentd + image: ubnt/unms-fluentd:2.4.188 + restart: always + networks: + - public + ports: + - 127.0.0.1:24224:24224 + volumes: + - /home/unms/data/logs:/fluentd/log + environment: + - FLUENTD_UID=1001 + + siridb: + container_name: unms-siridb + image: ubnt/unms-siridb:2.4.188 + restart: always + depends_on: + - fluentd + networks: + - internal + volumes: + - /home/unms/data/siridb:/var/lib/siridb + - /home/unms/data/siridb-cores:/cores + logging: + driver: fluentd + options: + tag: siridb + fluentd-async-connect: "true" + # Allow debugger + cap_add: + - SYS_PTRACE + environment: + - SIRIDB_UID=1001 + + postgres: + container_name: unms-postgres + image: ubnt/unms-postgres:2.4.188 + command: postgres -c deadlock_timeout=5000 -c max_connections=570 + restart: always + depends_on: + - fluentd + networks: + - internal + volumes: + - /home/unms/data/postgres:/var/lib/postgresql/data/pgdata + logging: + driver: fluentd + options: + tag: postgres + fluentd-async-connect: "true" + environment: + - POSTGRES_UID=1001 + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=1B0Ux1wJIDbo7NTQy1Arxp4sx8BCx9AQq7U2RGTygw7QjqZL + - UNMS_POSTGRES_DB=unms + - UNMS_POSTGRES_SCHEMA=unms + - UNMS_POSTGRES_USER=unms + - UNMS_POSTGRES_PASSWORD=JcJo4IBhHIcsN0rwF4aNHnqQL0caXukEmSgAz1JtMJzZC943 + - UCRM_POSTGRES_DB=unms + - UCRM_POSTGRES_SCHEMA=ucrm + - UCRM_POSTGRES_USER=ucrm + - UCRM_POSTGRES_PASSWORD=d9NZxDpRHnUTmcwjdF3f2UYth9BeYXIkfkVzFxnO6kepuCfU + - PGDATA=/var/lib/postgresql/data/pgdata + + rabbitmq: + container_name: unms-rabbitmq + image: rabbitmq:3.7.28-alpine + user: "1001" + restart: always + depends_on: + - fluentd + networks: + - internal + hostname: rabbitmq + volumes: + - /home/unms/data/rabbitmq:/var/lib/rabbitmq + logging: + driver: fluentd + options: + tag: rabbitmq + fluentd-async-connect: "true" + environment: + - RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS=-rabbit channel_max 4096 + + unms: + container_name: unms + image: ubnt/unms:2.4.188 + restart: always + depends_on: + - fluentd + - siridb + - postgres + - rabbitmq + - nginx + - ucrm + networks: + - public + - internal + volumes: + - /home/unms/data:/home/app/unms/data + logging: + driver: fluentd + options: + tag: unms + fluentd-async-connect: "true" + environment: + - UNMS_USER_ID=1001 + - DEMO=false + - NODE_ENV=production + - HTTP_PORT=8081 + - WS_PORT=8082 + - WS_SHELL_PORT=8083 + - UNMS_WS_API_PORT=8084 + - UNMS_NETFLOW_PORT=2055 + - PUBLIC_HTTPS_PORT=443 + - NGINX_HTTPS_PORT=443 + - SUSPEND_PORT=81 + - BRANCH=master + - SECURE_LINK_SECRET=IdNMEEUnBadoA0dRr6e7t76798JLBZLVQ0FPGAtqiz31lYMxeuLYefkfPkZepsGM2WoomGFPolv5u5Ld74e2XVRt0rTlsZo9j0eh + - CLUSTER_SIZE=auto + - UNMS_PG_PASSWORD=JcJo4IBhHIcsN0rwF4aNHnqQL0caXukEmSgAz1JtMJzZC943 + - UNMS_PG_USER=unms + - UNMS_PG_DB=unms + - UNMS_PG_SCHEMA=unms + - UNMS_TOKEN=NltSFUMqKWHd4Tf5PdqZbs9XsFenA7UiR0DcGQDD8yN5yClK + - UNMS_CLI_TOKEN=HDDwClgKV9HqPx9Ck47Kg8EE0neW0L4mzuG60XgI9VXZERT5 + - USE_LOCAL_DISCOVERY=true + - USE_ALTERNATIVE_CERT_DIR=false + + cap_add: + - NET_ADMIN + + ucrm: + container_name: ucrm + image: ubnt/unms-crm:4.4.30 + restart: always + volumes: + - /home/unms/data/ucrm:/data + command: server_with_migrate + depends_on: + - fluentd + - postgres + - rabbitmq + - nginx + networks: + - public + - internal + logging: + driver: fluentd + options: + tag: ucrm + fluentd-async-connect: "true" + environment: + - POSTGRES_HOST=unms-postgres + - POSTGRES_PASSWORD=d9NZxDpRHnUTmcwjdF3f2UYth9BeYXIkfkVzFxnO6kepuCfU + - POSTGRES_SCHEMA=ucrm + - POSTGRES_USER=ucrm + - POSTGRES_DB=unms + - MAILER_ADDRESS=127.1.0.1 + - MAILER_ADDRESS_USERNAME=username + - MAILER_ADDRESS_PASSWORD=password + - SECRET=q9i5rV7NTNQf8qSPQIONCFWGjDNbm4K83APtscfnuvJc7DAN + - SUSPEND_PORT=81 + - PUBLIC_HTTPS_PORT=443 + - UCRM_USER=unms + - UNMS_VERSION=2.4.188 + - UNMS_HOST=unms + - UNMS_PORT=8081 + - UNMS_TOKEN=NltSFUMqKWHd4Tf5PdqZbs9XsFenA7UiR0DcGQDD8yN5yClK + - UNMS_BASE_URL=/v2.1 + - UNMS_POSTGRES_SCHEMA=unms + + + nginx: + image: ubnt/unms-nginx:2.4.188 + container_name: unms-nginx + restart: always + ports: + - 80:80 + - 443:443 + - 81:81 + - 8089:8089 + + networks: + - public + - internal + volumes: + - /home/unms/data/cert:/cert + + - /home/unms/data/firmwares:/www/firmwares + depends_on: + - fluentd + logging: + driver: fluentd + options: + tag: nginx + fluentd-async-connect: "true" + environment: + - NGINX_UID=1001 + - HTTP_PORT=80 + - HTTPS_PORT=443 + - SUSPEND_PORT=81 + - UNMS_HTTP_PORT=8081 + - UNMS_WS_PORT=8082 + - UNMS_WS_SHELL_PORT=8083 + - UNMS_WS_API_PORT=8084 + - PUBLIC_HTTPS_PORT=443 + - SECURE_LINK_SECRET=IdNMEEUnBadoA0dRr6e7t76798JLBZLVQ0FPGAtqiz31lYMxeuLYefkfPkZepsGM2WoomGFPolv5u5Ld74e2XVRt0rTlsZo9j0eh + + netflow: + image: ubnt/unms-netflow:2.4.188 + container_name: unms-netflow + user: "1001" + restart: always + ports: + - 2055:2055/udp + networks: + - internal + - public + depends_on: + - fluentd + - postgres + - rabbitmq + logging: + driver: fluentd + options: + tag: netflow + fluentd-async-connect: "true" + environment: + - UNMS_NETFLOW_PORT=2055 + - UNMS_PG_PASSWORD=JcJo4IBhHIcsN0rwF4aNHnqQL0caXukEmSgAz1JtMJzZC943 + - UNMS_PG_USER=unms + - UNMS_PG_DB=unms + - UNMS_PG_SCHEMA=unms \ No newline at end of file diff --git a/uisp/install-full.sh b/uisp/install-full.sh new file mode 100755 index 0000000..30de1e8 --- /dev/null +++ b/uisp/install-full.sh @@ -0,0 +1,1495 @@ +#!/usr/bin/env bash +set -o nounset +set -o errexit +set -o pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PATH="${PATH}:/usr/local/bin" +TMP_INSTALL_DIR="${SCRIPT_DIR}" + +# prerequisites "command|package" +PREREQUISITES=( + "curl|curl" + "sed|sed" + "envsubst|gettext-base" + "useradd|passwd" +) + +COMPOSE_PROJECT_NAME="unms" +USERNAME="${UNMS_USER:-unms}" +if getent passwd "${USERNAME}" >/dev/null; then + HOME_DIR="$(getent passwd "${USERNAME}" | cut -d: -f6)" +else + HOME_DIR="${UNMS_HOME_DIR:-"/home/${USERNAME}"}" +fi + +# files and directoris +export APP_DIR="${HOME_DIR}/app" +export DATA_DIR="${HOME_DIR}/data" +export RESTORE_DIR="${DATA_DIR}/unms-backups/restore" +export CONFIG_DIR="${APP_DIR}/conf" +export CONFIG_FILE="${APP_DIR}/unms.conf" +export METADATA_FILE="${APP_DIR}/metadata" +export DOCKER_COMPOSE_INSTALL_PATH="/usr/local/bin" +export DOCKER_COMPOSE_FILENAME="docker-compose.yml" +export DOCKER_COMPOSE_PATH="${APP_DIR}/${DOCKER_COMPOSE_FILENAME}" +export DOCKER_COMPOSE_TEMPLATE_FILENAME="docker-compose.yml.template" +export DOCKER_COMPOSE_TEMPLATE_PATH="${APP_DIR}/${DOCKER_COMPOSE_TEMPLATE_FILENAME}" + +if [ "${SCRIPT_DIR}" = "${APP_DIR}" ]; then + echo >&2 "Please don't run the installation script in the application directory ${APP_DIR}" + exit 1 +fi + +# NMS variables +export UNMS_HTTP_PORT="8081" +export UNMS_WS_PORT="8082" +export UNMS_WS_SHELL_PORT="8083" +export UNMS_WS_API_PORT="8084" +export UNMS_POSTGRES_USER="unms" +export UNMS_POSTGRES_DB="unms" +export UNMS_POSTGRES_SCHEMA="unms" +export UNMS_POSTGRES_PASSWORD="$(LC_CTYPE=C tr -dc "a-zA-Z0-9" < /dev/urandom | fold -w 48 | head -n 1 || true)" +export UNMS_TOKEN="$(LC_CTYPE=C tr -dc "a-zA-Z0-9" < /dev/urandom | fold -w 48 | head -n 1 || true)" +export UNMS_CLI_TOKEN="$(LC_CTYPE=C tr -dc "a-zA-Z0-9" < /dev/urandom | fold -w 48 | head -n 1 || true)" +export UNMS_DEPLOYMENT="" +export UNMS_VERSION="$(grep "^version=" "${SCRIPT_DIR}/metadata" | sed 's/version=//')" +export UNMS_IP_WHITELIST="" +export UNMS_FEATURES="" +export ALTERNATIVE_HTTP_PORT="8080" +export ALTERNATIVE_HTTPS_PORT="8443" +export ALTERNATIVE_SUSPEND_PORT="8081" +export ALTERNATIVE_NETFLOW_PORT="2056" +export ALTERNATIVE_REMOTE_UI_WS_PORT_PUBLIC="8189"; +export SECURE_LINK_SECRET="$(LC_CTYPE=C tr -dc "a-zA-Z0-9" < /dev/urandom | fold -w 100 | head -n 1 || true)" +export CLOUD_API_URL="" +export CLOUD_PORTAL_URL="" +export REMOTE_UI_WS_PORT_PUBLIC="8089"; + +# CRM variables +export UCRM_DOCKER_IMAGE="ubnt/unms-crm" +export UCRM_VERSION="4.4.30" +export UCRM_POSTGRES_PASSWORD="$(LC_CTYPE=C tr -dc "a-zA-Z0-9" < /dev/urandom | fold -w 48 | head -n 1 || true)" +export UCRM_SECRET="$(LC_CTYPE=C tr -dc "a-zA-Z0-9" < /dev/urandom | fold -w 48 | head -n 1 || true)" +export UCRM_USER="${USERNAME}" +export UCRM_POSTGRES_USER="ucrm" +export UCRM_POSTGRES_DB="unms" +export UCRM_POSTGRES_SCHEMA="ucrm" +export UCRM_MAILER_ADDRESS="127.1.0.1" +export UCRM_MAILER_USERNAME="username" +export UCRM_MAILER_PASSWORD="password" +export NODE_ENV="production" + +# other variables +export HTTP_PORT="80" +export HTTPS_PORT="443" +export SUSPEND_PORT="81" +export WS_PORT="" +export PROXY_HTTPS_PORT="" +export PROXY_WS_PORT="" +export NETFLOW_PORT="2055" +export VERSION="latest" +export DEMO="false" +export USE_LOCAL_IMAGES="false" +export DOCKER_IMAGE="ubnt/unms" +export DOCKER_REGISTRY="docker.io" +export DOCKER_USERNAME="" +export DOCKER_PASSWORD="" +export DOCKER_VERSION="" +export DOCKER_MIN_VERSION="20.10.5" +export DOCKER_STRICT_VERSION="27.3.1" +export DOCKER_COMPOSE_COMMAND="docker compose" +export DOCKER_COMPOSE_PROJECT_NAME="unms" +export DOCKER_COMPOSE_VERSION="" +export SSL_CERT_DIR="" +export SSL_CERT="" +export SSL_CERT_KEY="" +export SSL_CERT_CA="" +export HOST_TAG="" +export UNATTENDED="false" +export UPDATING="false" +export NO_AUTO_UPDATE="false" +export START_CONTAINERS="true" +export USE_LOCAL_DISCOVERY="true" +export USE_ALTERNATIVE_CERT_DIR="false" +export BRANCH="master" +export SUBNET="172.18.251.0/24" +export BRIDGE_NAME_PREFIX="" +export CLUSTER_SIZE="auto" +export IPAM_PUBLIC="" +export IPAM_PRIVATE="" +export NET_DRIVER_OPTS_PUBLIC="" +export NET_DRIVER_OPTS_PRIVATE="" +export CERT_DIR_MAPPING_NGINX="" +export USERCERT_DIR_MAPPING_NGINX="" +export RUN_VACUUM="true" +export POSTGRES_USER="postgres" +export POSTGRES_PASSWORD="$(LC_CTYPE=C tr -dc "a-zA-Z0-9" < /dev/urandom | fold -w 48 | head -n 1 || true)" +export CURRENT_VERSION="$(cat "${METADATA_FILE}" 2>/dev/null | grep '^version=' | sed 's/version=//'|| true)" +export MIN_CURRENT_VERSION="1.3.0" +export DELETE_CRM_DATA="false" +if [ -f /proc/ubnthal/system.info ]; then + export SUBSYSTEM_ID="$(grep systemid /proc/ubnthal/system.info | sed 's/systemid=//')" + export SERVER_MAC="$(grep eth0.macaddr /proc/ubnthal/system.info | sed 's/eth0.macaddr=//')" +else + export SUBSYSTEM_ID= + export SERVER_MAC= +fi +export ENV_DIR= +export ENV_FILES_UNMS= +export ENV_FILES_UCRM= +export ENV_FILES_NETFLOW= + +cleanup() { + # Cleanup temp install dir. + if [ "${TMP_INSTALL_DIR}" != "${SCRIPT_DIR}" ] ; then + rm -rf "${TMP_INSTALL_DIR}" || true; + fi +} + +fail() { + echo -e "ERROR: $1" >&2 + cleanup || true; + exit 1 +} + +read_previous_config() { + # read WS port settings from existing running container + # they were not saved to config file in versions <=0.7.18 + if ! oldEnv="$(docker inspect --format '{{ .Config.Env }}' unms)"; then + echo "Couldn't read WS port config from existing UISP container" + else + WS_PORT="$(docker ps --filter "name=unms$" --filter "status=running" --format "{{.Ports}}" | sed -E "s/.*0.0.0.0:([0-9]+)->8444.*|.*/\1/")" + echo "Setting WS_PORT=${WS_PORT}" + PROXY_WS_PORT="$(echo "${oldEnv}" | sed -E "s/.*[ []PUBLIC_WS_PORT=([0-9]*).*|.*/\1/")" + echo "Setting PROXY_WS_PORT=${PROXY_WS_PORT}" + fi + + # read config file + if [ -f "${CONFIG_FILE}" ]; then + echo "Reading configuration file ${CONFIG_FILE}" + sed -i -E 's/^SERVER_MAC="([^"]*)$/SERVER_MAC="\1"/' "${CONFIG_FILE}" 2> /dev/null # fix for corrupted config in 1.4.0-beta.8 + if ! source "${CONFIG_FILE}"; then + echo >&2 "Failed to read configuration from ${CONFIG_FILE}" + exit 1 + fi + else + echo "Configuration file not found." + fi + UCRM_POSTGRES_DB="${UNMS_POSTGRES_DB}" +} + +# parse arguments +while [[ "$#" -gt 0 ]]; do + case "$1" in + --demo) + echo "Setting DEMO=true" + DEMO="true" + ;; + --update) + echo "Restoring previous configuration" + read_previous_config + UPDATING="true" + ;; + --unattended) + echo "Setting UNATTENDED=true" + UNATTENDED="true" + ;; + --no-auto-update) + echo "Setting NO_AUTO_UPDATE=true" + NO_AUTO_UPDATE="true" + ;; + --no-local-discovery) + echo "Setting USE_LOCAL_DISCOVERY=false" + USE_LOCAL_DISCOVERY="false" + ;; + --no-start-containers) + echo "Setting START_CONTAINERS=false" + START_CONTAINERS="false" + ;; + --no-vacuum) + echo "Setting RUN_VACUUM=false" + RUN_VACUUM="false" + ;; + --ucrm-version) + echo "Setting UCRM_VERSION=$2" + UCRM_VERSION="$2" + ;; + --ucrm-docker-image) + echo "Setting UCRM_DOCKER_IMAGE=$2" + UCRM_DOCKER_IMAGE="$2" + shift # past argument value + ;; + --use-local-images) + echo "Setting USE_LOCAL_IMAGES=true" + USE_LOCAL_IMAGES="true" + ;; + --use-alt-cert-dir) + echo "Setting USE_ALTERNATIVE_CERT_DIR=true" + USE_ALTERNATIVE_CERT_DIR="true" + ;; + -v|--version) + echo "Setting VERSION=$2" + VERSION="$2" + shift # past argument value + ;; + --docker-image) + echo "Setting DOCKER_IMAGE=$2" + DOCKER_IMAGE="$2" + shift # past argument value + ;; + --docker-registry) + echo "Setting DOCKER_REGISTRY=$2" + DOCKER_REGISTRY="$2" + shift # past argument value + ;; + --docker-username) + echo "Setting DOCKER_USERNAME=$2" + DOCKER_USERNAME="$2" + shift # past argument value + ;; + --docker-password) + echo "Setting DOCKER_PASSWORD=*****" + DOCKER_PASSWORD="$2" + shift # past argument value + ;; + --data-dir) + echo "Setting DATA_DIR=$2" + DATA_DIR="$2" + shift # past argument value + ;; + --http-port) + echo "Setting HTTP_PORT=$2" + HTTP_PORT="$2" + shift # past argument value + ;; + --https-port) + echo "Setting HTTPS_PORT=$2" + HTTPS_PORT="$2" + shift # past argument value + ;; + --suspend-port) + echo "Setting SUSPEND_PORT=$2" + SUSPEND_PORT="$2" + shift # past argument value + ;; + --ws-port) + echo "Setting WS_PORT=$2" + WS_PORT="$2" + shift # past argument value + ;; + --public-https-port) + echo "Setting PROXY_HTTPS_PORT=$2" + PROXY_HTTPS_PORT="$2" + shift # past argument value + ;; + --public-ws-port) + echo "Setting PROXY_WS_PORT=$2" + PROXY_WS_PORT="$2" + shift # past argument value + ;; + --netflow-port) + echo "Setting NETFLOW_PORT=$2" + NETFLOW_PORT="$2" + shift # past argument value + ;; + --public-remote-ui-ws-port) + echo "Setting REMOTE_UI_WS_PORT_PUBLIC=$2" + REMOTE_UI_WS_PORT_PUBLIC="$2" + shift # past argument value + ;; + --ssl-cert-dir) + echo "Setting SSL_CERT_DIR=$2" + SSL_CERT_DIR="$2" + shift # past argument value + ;; + --ssl-cert) + echo "Setting SSL_CERT=$2" + SSL_CERT="$2" + shift # past argument value + ;; + --ssl-cert-key) + echo "Setting SSL_CERT_KEY=$2" + SSL_CERT_KEY="$2" + shift # past argument value + ;; + --ssl-cert-ca) + echo "Setting SSL_CERT_CA=$2" + SSL_CERT_CA="$2" + shift # past argument value + ;; + --host-tag) + echo "Setting HOST_TAG=$2" + HOST_TAG="$2" + shift # past argument value + ;; + --branch) + echo "Setting BRANCH=$2" + BRANCH="$2" + shift # past argument value + ;; + --subnet) + echo "Setting SUBNET=$2" + SUBNET="$2" + shift # past argument value + ;; + --bridge-name-prefix) + echo "Setting BRIDGE_NAME_PREFIX=$2" + BRIDGE_NAME_PREFIX="$2" + shift # past argument value + ;; + --node-env) + echo "Setting NODE_ENV=$2" + NODE_ENV="$2" + shift # past argument value + ;; + --workers) + echo "Setting CLUSTER_SIZE=$2" + [[ "${2}" =~ ^[1-9]$|^[1-4][0-9]$|^50$ ]] || [[ "${2}" = "auto" ]] || fail "--workers argument must be a number in range 1-50 or 'auto'." + CLUSTER_SIZE="$2" + shift # past argument value + ;; + --deployment) + echo "Setting UNMS_DEPLOYMENT=$2" + UNMS_DEPLOYMENT=$2 + shift # past argument value + ;; + --ip-whitelist) + echo "Setting UNMS_IP_WHITELIST=$2" + UNMS_IP_WHITELIST=$2 + shift # past argument value + ;; + --env-dir) + echo "Setting ENV_DIR=$2" + ENV_DIR=$2 + shift # past argument value + ;; + --features) + echo "Setting UNMS_FEATURES=$2" + UNMS_FEATURES=$2 + shift # past argument value + ;; + --cloud-api-url) + echo "Setting CLOUD_API_URL=$2" + CLOUD_API_URL=$2 + shift # past argument value + ;; + --cloud-portal-url) + echo "Setting CLOUD_PORTAL_URL=$2" + CLOUD_PORTAL_URL=$2 + shift # past argument value + ;; + *) + # unknown option + ;; + esac + shift # past argument key +done + +# '+' symbol is not allowed in docker tags, replace with '-' +export DOCKER_TAG=${VERSION//+/-} +export UCRM_DOCKER_TAG=${UCRM_VERSION//+/-} + +# check that none or all three SSL variables are set +if [ ! -z "${SSL_CERT_DIR}" ] || [ ! -z "${SSL_CERT}" ] || [ ! -z "${SSL_CERT_KEY}" ]; then + if [ -z "${SSL_CERT_DIR}" ]; then echo >&2 "Please set --ssl-cert-dir"; exit 1; fi + if [ -z "${SSL_CERT}" ]; then echo >&2 "Please set --ssl-cert"; exit 1; fi + if [ -z "${SSL_CERT_KEY}" ]; then echo >&2 "Please set --ssl-cert-key"; exit 1; fi +fi + +# check subnet and prepare the networks section of docker compose file +if [ ! -z "${SUBNET}" ]; then + cidrRegex="^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))?$" + + if [[ ! "${SUBNET}" =~ ${cidrRegex} ]]; then + echo >&2 "Value of --subnet is invalid. Please use CIDR notation (ex. 172.45.0.1/24)" + exit 1 + fi + + IFS=/ read -r subnetIp subnetPrefix <<< "${SUBNET}" + if [ "${subnetPrefix}" -gt 27 ]; then + echo >&2 Please specify a subnet with 32 or more addresses + exit 1 + fi + + dec2ip () { + local ip dec=$@ delim="" + for e in {3..0}; do + ((octet = dec / (256 ** e) )) + ((dec -= octet * 256 ** e)) + ip+=$delim$octet + delim=. + done + printf '%s\n' "$ip" + } + + ip2dec () { + local a b c d ip=$@ + IFS=. read -r a b c d <<< "$ip" + printf '%d\n' "$((a * 256 ** 3 + b * 256 ** 2 + c * 256 + d))" + } + + # split subnet into privateSubnet and publicSubnet + subnetIpDec=$( ip2dec "${subnetIp}" ) + subnetMask=$(( 0xffffffff - 2 ** ( 32 - ${subnetPrefix} ) + 1 )) + subnetIpMaskedDec=$(( ${subnetIpDec} & ${subnetMask} )) + + newSubnetPrefix=$(( ${subnetPrefix} + 1 )) + publicSubnetIpDec=$(( ${subnetIpMaskedDec} )) + privateSubnetIpDec=$(( ${subnetIpMaskedDec} + 2 ** (32 - ${newSubnetPrefix}) )) + + publicSubnetIp=$(dec2ip "${publicSubnetIpDec}") + privateSubnetIp=$(dec2ip "${privateSubnetIpDec}") + + # prepare subnet section of the docker compose file + IPAM_PUBLIC=$(printf "ipam:\n config:\n - subnet: \"${publicSubnetIp}/${newSubnetPrefix}\"") + IPAM_PRIVATE=$(printf "ipam:\n config:\n - subnet: \"${privateSubnetIp}/${newSubnetPrefix}\"") +fi + +if [ -n "${BRIDGE_NAME_PREFIX}" ]; then + NET_DRIVER_OPTS_PUBLIC=$(printf "driver_opts:\n com.docker.network.bridge.name: ${BRIDGE_NAME_PREFIX}-pub") + NET_DRIVER_OPTS_PRIVATE=$(printf "driver_opts:\n com.docker.network.bridge.name: ${BRIDGE_NAME_PREFIX}-priv") +fi + +if [ ! -z "${ENV_DIR}" ]; then + # prepare env_file sections of the docker compose file + [ -f ${ENV_DIR}/unms.env ] && ENV_FILES_UNMS=$(printf "env_file:\n - ${ENV_DIR}/unms.env") + [ -f ${ENV_DIR}/ucrm.env ] && ENV_FILES_UCRM=$(printf "env_file:\n - ${ENV_DIR}/ucrm.env") + [ -f ${ENV_DIR}/netflow.env ] && ENV_FILES_NETFLOW=$(printf "env_file:\n - ${ENV_DIR}/netflow.env") +fi + +# prepare --silent option for curl +curlSilent="" +if [ "${UNATTENDED}" = "true" ]; then + curlSilent="--silent" +fi + +is_decimal_number() { + [[ ${1} =~ ^[0-9]+$ ]] +} + +version_equal_or_newer() { + if [[ "$1" == "$2" ]]; then return 0; fi + local IFS=. + local i ver1=($1) ver2=($2) + for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)); do ver1[i]=0; done + for ((i=0; i<${#ver1[@]}; i++)); do + if [[ -z ${ver2[i]} ]]; then ver2[i]=0; fi + if ! is_decimal_number "${ver1[i]}" ; then return 1; fi + if ! is_decimal_number "${ver2[i]}" ; then return 1; fi + if ((10#${ver1[i]} > 10#${ver2[i]})); then return 0; fi + if ((10#${ver1[i]} < 10#${ver2[i]})); then return 1; fi + done + return 0; +} + +semver_equal_or_newer() { + if [[ "$1" == "$2" ]]; then return 0; fi + # remove build number + local v1="$(echo "$1" | sed 's/\+.*//')" + local v2="$(echo "$2" | sed 's/\+.*//')" + # split to core and pre-release parts + local IFS=- + local i j parts1=($v1) parts2=($v2) + for ((i=0; i<${#parts1[@]} || i < ${#parts2[@]}; i++)); do + # split parts to items + if [ "${i}" -ge "${#parts1[@]}" ]; then return 0; fi + if [ "${i}" -ge "${#parts2[@]}" ]; then return 1; fi + local part1="${parts1[i]}" part2="${parts2[i]}" + local IFS=. + local j ver1=($part1) ver2=($part2) + for ((j=0; j < ${#ver1[@]} || j < ${#ver2[@]}; j++)); do + # compare items + if [ "${j}" -ge "${#ver1[@]}" ]; then return 1; fi + if [ "${j}" -ge "${#ver2[@]}" ]; then return 0; fi + local item1="${ver1[j]}" item2="${ver2[j]}" + if is_decimal_number "${item1}" && is_decimal_number "${item2}"; then + # compare numerically + if [ "${item1}" -gt "${item2}" ]; then return 0; fi + if [ "${item1}" -lt "${item2}" ]; then return 1; fi + else + # compare alphabetically + if [[ "${item1}" > "${item2}" ]]; then return 0; fi + if [[ "${item1}" < "${item2}" ]]; then return 1; fi + fi + done + done + return 0; +} + +version_older() { + ! version_equal_or_newer "$1" "$2" +} + +# usage: confirm +# Prints given question and asks user to type Y or N. +# Returns 0 if user typed Y, 1 if user typed N. +# Exits if user failed to type Y or N too many times. +# examples: +# confirm "Do you want to continue?" || exit 1 +confirm() { + local question="$1" + for i in {0..10}; do + read -p "${question} [Y/N]" -n 1 -r + echo + if [[ ${REPLY} =~ ^[Yy]$ ]]; then + echo "Yes" + return 0 + fi + if [[ ${REPLY} =~ ^[Nn]$ ]]; then + echo "No" + return 1 + fi + echo "Please type Y or N." + done + echo "Too many failed attempts." + exit 1 +} + +check_system() { + local architecture + architecture=$(uname -m) + case "${architecture}" in + amd64|x86_64|aarch64) + ;; + *) + echo >&2 "Unsupported platform '${architecture}'." + echo >&2 "UISP supports: x86_64/amd64/aarch64." + exit 1 + ;; + esac + + local lsb_dist + local dist_version + + if [ -z "${lsb_dist:-}" ] && [ -r /etc/lsb-release ]; then + lsb_dist="$(. /etc/lsb-release && echo "${DISTRIB_ID:-}")" + fi + + if [ -z "${lsb_dist:-}" ] && [ -r /etc/debian_version ]; then + lsb_dist="debian" + fi + + if [ -z "${lsb_dist:-}" ] && [ -r /etc/fedora-release ]; then + lsb_dist="fedora" + fi + + if [ -z "${lsb_dist:-}" ] && [ -r /etc/oracle-release ]; then + lsb_dist="oracleserver" + fi + + if [ -z "${lsb_dist:-}" ]; then + if [ -r /etc/centos-release ] || [ -r /etc/redhat-release ]; then + lsb_dist="centos" + fi + fi + + if [ -z "${lsb_dist:-}" ] && [ -r /etc/os-release ]; then + lsb_dist="$(. /etc/os-release && echo "${ID:-}")" + fi + + lsb_dist="$(echo "${lsb_dist:-}" | tr '[:upper:]' '[:lower:]')" + DISTRO="$lsb_dist" + SUPPORTED_DISTRO=false + local SKIP_PREREQ_CHECK=false + case "$DISTRO" in + ubuntu) + if [ -z "${dist_version:-}" ] && [ -r /etc/lsb-release ]; then + dist_version="$(. /etc/lsb-release && echo "${DISTRIB_RELEASE:-}")" + fi + if version_equal_or_newer "${dist_version}" "16.04"; then + SUPPORTED_DISTRO=true + fi + ;; + + debian) + dist_version="$(sed 's/\/.*//' /etc/debian_version | sed 's/\..*//')" + case "${dist_version}" in + jessie) dist_version=8;; + stretch) dist_version=9;; + buster) dist_version=10;; + esac + if version_equal_or_newer "${dist_version}" "8"; then + SUPPORTED_DISTRO=true + fi + ;; + + *coreos) + if [ -z "${dist_version:-}" ] && [ -r /etc/lsb-release ]; then + dist_version="$(. /etc/lsb-release && echo "${DISTRIB_RELEASE:-}")" + fi + if version_equal_or_newer "${dist_version}" "1465.6.0"; then + if ! echo $PATH | grep -q "/opt/bin" ; then + export PATH="/opt/bin:$PATH" + fi + + if [ ! -d /opt/bin ]; then + mkdir -p /opt/bin + fi + + DOCKER_COMPOSE_INSTALL_PATH="/opt/bin" + + SUPPORTED_DISTRO=true + fi + ;; + + ubios) + if [ -z "${dist_version:-}" ] && [ -r /etc/os-release ]; then + dist_version="$(. /etc/os-release && echo "${VERSION_ID:-}")" + fi + + SUPPORTED_DISTRO=true + SKIP_PREREQ_CHECK=true + ;; + + *) + if [ -z "${dist_version:-}" ] && [ -r /etc/os-release ]; then + dist_version="$(. /etc/os-release && echo "${VERSION_ID:-}")" + fi + ;; + + esac + + if [ "${SKIP_PREREQ_CHECK}" = "false" ]; then + for prerequisite in "${PREREQUISITES[@]}"; do + IFS=\| read -r preCommand prePackage <<< "${prerequisite}" + command -v "${preCommand}" >/dev/null 2>&1 || { + if [ "$UNATTENDED" = true ]; then + echo "Warning: this script requires '${preCommand}' from '${prePackage}' but it can't be found. The installation may fail." + else + echo >&2 "This script requires '${preCommand}'. Please install '${prePackage}' and try again. Aborting." + exit 1 + fi + } + done + fi + + DIST_VERSION="${dist_version:-}" + if [ "${UNATTENDED}" = "false" ] && [ "${SUPPORTED_DISTRO}" = "false" ]; then + echo "Your distribution '${lsb_dist} ${dist_version:-}' is not supported." + echo "We recommend that you install UISP on Ubuntu 18.04, Debian 9 or newer." + confirm "Would you like to continue with the installation anyway?" || exit 1 + else + echo "Distribution: '${lsb_dist} ${dist_version:-}'" + fi + + if [ "${UNATTENDED}" = "false" ] && [[ -e /proc/meminfo ]]; then + local memory + local memoryUnit + memory="$(awk '/MemTotal/{print $2}' /proc/meminfo)" + if (which bc > /dev/null 2>&1); then + memoryUnit=$(echo "scale=2; ${memory}/1024^2" | bc) + memoryUnit="${memoryUnit} GB" + else + memoryUnit="${memory} KB" + fi + + if [[ "${memory}" -lt 1000000 ]]; then + echo >&2 "ERROR: Your system has only ${memoryUnit} of RAM." + echo >&2 "UISP requires at least 1 GB of RAM to run and 2 GB is recommended. Installation aborted." + exit 1 + fi + + if [[ "${memory}" -lt 2000000 ]]; then + echo >&2 "WARNING: Your system has only ${memoryUnit} RAM." + echo >&2 "We recommend at least 2 GB RAM to run UISP without problems." + fi + fi + + if [ "${UNATTENDED}" = "false" ] && docker --version >/dev/null 2>&1; then + # it's possible to have different client and server version + local dockerClientVersion=$(docker version -f '{{.Client.Version}}') + local dockerServerVersion=$(docker version -f '{{.Server.Version}}') + + if [ $dockerClientVersion != $dockerServerVersion ]; then + echo "Your docker client version $dockerClientVersion and server version $dockerServerVersion are different. This is highly unrecommended." + confirm "Would you like to continue with the installation anyway?" || exit 1 + fi + fi +} + +check_update_allowed() { + if [ -z "${CURRENT_VERSION}" ]; then + return 0 + fi + + if [[ "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then + if ! semver_equal_or_newer "${VERSION}" "${CURRENT_VERSION}"; then + fail "Cannot downgrade from version '${CURRENT_VERSION}' to '${VERSION}'." + fi + else + echo "'${VERSION}' is not semver, ignoring downgrade check." + fi + + if ! semver_equal_or_newer "${CURRENT_VERSION}" "${MIN_CURRENT_VERSION}"; then + fail "Cannot update to version ${VERSION}. Current version ${CURRENT_VERSION} is too old. Please update to ${MIN_CURRENT_VERSION} with + 'sudo ${APP_DIR}/unms-cli update --version ${MIN_CURRENT_VERSION}'" + fi +} + +check_forgotten_update_flag() { + if [ "${UPDATING}" = "true" ] || [ "${UNATTENDED}" = "true" ]; then + return 0 + fi + + if [ -f "${CONFIG_FILE}" ]; then + echo "Found configuration file for a previous UISP installation but --update was not specified." + echo "Proceeding with the installation would overwrite the previous configuration and could make UISP unavailable." + confirm "Do you want to continue without loading the previous configuration?" || exit 1 + fi +} + +check_custom_cert_path() { + if [ -z "${SSL_CERT_DIR}" ]; then + # Not using custom cert. + return 0 + fi + if [ "${EUID}" -ne 0 ]; then + # Ignore cert check during update. + return 0 + fi + + local cert_path="${SSL_CERT_DIR}/${SSL_CERT}" + local key_path="${SSL_CERT_DIR}/${SSL_CERT_KEY}" + local norm_cert_dir + local norm_cert_path + local norm_key_path + + # Check that cert and key files exist. + test -f "${cert_path}" || fail "Cert file '${cert_path}' does not exist. Check the --ssl-cert-dir and --ssl-cert arguments." + test -f "${key_path}" || fail "Key file '${key_path}' does not exist. Check the --ssl-cert-dir and --ssl-cert-key arguments." + + # Check that the cert dir is parent of cert and key files. + # Nginx container mounts this directory and if the cert or key file actually placed within another directory it will + # be inaccessible from within the container. + norm_cert_dir=$(readlink -f "${SSL_CERT_DIR}") || fail "Failed to determine real path of cert directory '${SSL_CERT_DIR}'. Check the --ssl-cert-dir argument." + norm_cert_path=$(readlink -f "${cert_path}") || fail "Failed to determine real path of cert file '${cert_path}'. Check the --ssl-cert argument." + norm_key_path=$(readlink -f "${key_path}") || fail "Failed to determine real path of key file '${key_path}'. Check the --ssl-cert-key argument." + [[ "${norm_cert_path}" = "${norm_cert_dir}"* ]] || fail "Cert file: \n${norm_cert_path}\n is not placed in the cert directory:\n${norm_cert_dir}\nCheck --ssl-cert-dir and --ssl-cert arguments for symbolic links. The actual ssl cert file (not just symbolic link) must be within the ssl cert directory or its subdirectories." + [[ "${norm_key_path}" = "${norm_cert_dir}"* ]] || fail "Key file:\n${norm_key_path}\n is not placed in the cert directory:\n${norm_cert_dir}\nCheck --ssl-cert-dir and --ssl-cert-key arguments for symbolic links. The actual ssl key file (not just symbolic link) must be within the ssl cert directory or its subdirectories." +} + +check_free_space() { + dockerRootDir="$(docker info --format='{{ print .DockerRootDir }}')" + freeSpace="$(df -m "${dockerRootDir}" | tail -1 | awk '{print $4}')" + local minRequiredSpace=3000 # MB + if [ "${USE_LOCAL_IMAGES}" = "true" ]; then + minRequiredSpace=500 # MB + fi + if [[ "${freeSpace}" -lt "${minRequiredSpace}" ]]; then + echo >&2 "There is not enough disk space available to safely install UISP. At least ${minRequiredSpace} MB is required." + echo >&2 "You have ${freeSpace} MB of available disk space in ${dockerRootDir}" + echo >&2 -e "\n----------------\n" + echo >&2 "We recommend running \"docker system prune -a\" once in a while to clean unused containers, images, etc." + echo >&2 "You can determine how much space can be cleaned up by running \"docker system df\"" + + exit 1 + fi +} + +dockerCompose() { + ${DOCKER_COMPOSE_COMMAND} -p "${DOCKER_COMPOSE_PROJECT_NAME}" -f "${DOCKER_COMPOSE_PATH}" "$@" +} + +install_docker() { + if [ "${DISTRO}" = "ubios" ]; then + return 0 + fi + + if ! command -v docker > /dev/null 2>&1; then + echo "Download and install Docker" + curl -fsSL https://get.docker.com | sh -s -- --version "${DOCKER_STRICT_VERSION}" > /dev/null + + systemctl enable docker + systemctl start docker + fi + + if ! command -v docker > /dev/null 2>&1; then + fail "Docker not installed. Please check previous logs. Aborting." + fi + + DOCKER_VERSION=$(docker -v | sed 's/.*version v\{0,1\}\([0-9.]*\).*/\1/'); + if ! version_equal_or_newer "${DOCKER_VERSION}" "${DOCKER_STRICT_VERSION}" ; then + if [ "${UNATTENDED}" = "false" ] && [ "${SUPPORTED_DISTRO}" = "true" ] && [ "${EUID}" -eq 0 ]; then + if confirm "Docker version ${DOCKER_VERSION} is below recommended version ${DOCKER_STRICT_VERSION}. Would you like to update docker automatically? This action will affect all docker services on this computer, not just UISP."; then + case "${DISTRO}" in + ubuntu|debian) + if realpath "$(command -v docker)" | grep snap > /dev/null 2>&1; then + # current latest stable for snap 24.0.5, to keep the version fixed, once released find revision with docker 25.0.3 + snap refresh docker --revision 2915 || fail "Failed to update docker from snap for '${DISTRO}'." + else + curl -fsSL https://get.docker.com | sh -s -- --version "${DOCKER_STRICT_VERSION}" || fail "Failed to update docker for '${DISTRO}'." + fi + ;; + *) + fail "Cannot update docker for '${DISTRO}' automatically. Please update docker to ${DOCKER_STRICT_VERSION} or newer and run this installation script again." + ;; + esac + fi + fi + + # Check that the version was updated. + DOCKER_VERSION=$(docker -v | sed 's/.*version \([0-9.]*\).*/\1/'); + if ! version_equal_or_newer "${DOCKER_VERSION}" "${DOCKER_MIN_VERSION}" ; then + fail "Docker version ${DOCKER_VERSION} is not supported. Please upgrade to version ${DOCKER_STRICT_VERSION} or newer." + fi + fi + + echo "Docker version client: $(docker version -f '{{.Client.Version}}'), server: $(docker version -f '{{.Server.Version}}')" +} + +check_docker_compose() { + if ! ($DOCKER_COMPOSE_COMMAND > /dev/null 2>&1); then + echo "Warning: Docker Compose plugin is not installed. We recommend installing it for compatibility." >&2 + DOCKER_COMPOSE_COMMAND="docker-compose" + DOCKER_COMPOSE_VERSION="$(docker-compose -v | sed 's/.*version \([0-9]*\.[0-9]*\.[0-9]*\).*/\1/')"; + echo "Docker compose version: ${DOCKER_COMPOSE_VERSION}" + fi +} + +configure_containerd() { + if [ -f /lib/systemd/system/containerd.service ] && [ ! -f /lib/systemd/system/containerd.service.d/unms-killmode.conf ]; then + if [ ! -w /lib/systemd/system ]; then + echo "Containerd service found but got no permissions for creating config override, skipping." + return + fi + + echo "Containerd service found, updating configuration." + + mkdir -p /lib/systemd/system/containerd.service.d + cat > /lib/systemd/system/containerd.service.d/unms-killmode.conf <&2 "WARNING: User '${USERNAME}' already exists. We are going to ensure that the" + echo >&2 "user is in the 'docker' group and that its home '${HOME_DIR}' dir exists and" + echo >&2 "is owned by the user." + if ! [ "$UNATTENDED" = true ]; then + confirm "Would you like to continue with the installation?" || exit 1 + fi + fi + + if ! getent group docker | grep -q "\b${USERNAME}\b"; then + echo "Adding user '${USERNAME}' to docker group." + if ! usermod -aG docker "${USERNAME}"; then + echo >&2 "Failed to add user '${USERNAME}' to docker group." + exit 1 + fi + fi + + if ! [ -d "${HOME_DIR}" ]; then + echo "Creating home directory '${HOME_DIR}'." + if ! mkdir -p "${HOME_DIR}"; then + echo >&2 "Failed to create home directory '${HOME_DIR}'." + exit 1 + fi + fi + + if [ "$(stat -c '%u' "${HOME_DIR}")" != "$(id -u "${USERNAME}")" ]; then + chown "${USERNAME}" "${HOME_DIR}" + fi + + export USER_ID=$(id -u "${USERNAME}") +} + +cleanup_docker() { + # Networks created by the compose v1 are not deleted upon uisp shutdown and + # cause compose v2 "up" to fail. Let's get rid of them. + docker network rm unms_internal unms_public >/dev/null 2>&1 || true +} + +start_postgres() { + echo "Starting postgres DB." + dockerCompose up -d postgres >/dev/null || fail "Failed to start Postgres DB." + for delay in 1 2 2 5 10 10 0; do + test "${delay}" != "0" || fail "Postgres DB failed to start in time." + if dockerCompose exec -T postgres pg_isready 2>&1 >/dev/null; then + break + fi + sleep "${delay}" + done +} + +stop_postgres() { + if version_equal_or_newer "${DOCKER_VERSION}" "23.0.0" || version_equal_or_newer "${DOCKER_COMPOSE_VERSION}" "1.18.0" ; then + dockerCompose down --timeout 60 || fail "Failed to stop Postgres DB." + else + dockerCompose down || fail "Failed to stop Postgres DB." + fi +} + +exec_psql() { + dockerCompose exec -T postgres psql --username postgres --no-password --no-align --tuples-only --quiet --dbname "${UNMS_POSTGRES_DB}" "$@" +} + +exec_psql_command() { + exec_psql --command "${1}" +} + +fix_postgres() { + test -d "${DATA_DIR}/postgres" || return 0 # do nothing during first installation + test "${RUN_VACUUM}" = "true" || return 0 + + CURRENT_DB_VERSION="$(head -n 1 "${DATA_DIR}/postgres/PG_VERSION" 2>/dev/null || true)" + if [ "${CURRENT_DB_VERSION}" == "13" ]; then + echo "Reducing Postgres DB size." + start_postgres + exec_psql_command "VACUUM FULL" || fail "Failed to reduce Postgres DB size." + stop_postgres + fi +} + +remove_old_restore_files() { + # Make sure that restore dir does not exist. We are now applying any backup in this directory during start + # of UISP container. Under normal circumstances this directory should be empty or not exist at all. + test -d "${RESTORE_DIR}" || return 0 # nothing to delete + rm "${RESTORE_DIR}" -rf || echo "WARNING: Failed to clear restore directory '${RESTORE_DIR}'." +} + +migrate_app_files() { + oldConfigFile="${HOME_DIR}/unms.conf" + oldDockerComposeFile="${HOME_DIR}/docker-compose.yml" + oldDockerComposeTemplate="${HOME_DIR}/docker-compose.yml.template" + oldConfigDir="${HOME_DIR}/conf" + + mkdir -p -m 700 "${APP_DIR}" + + if [ -f "${oldConfigFile}" ]; then mv -u "${oldConfigFile}" "${CONFIG_FILE}"; fi + if [ -f "${oldDockerComposeFile}" ]; then mv -u "${oldDockerComposeFile}" "${DOCKER_COMPOSE_PATH}"; fi + if [ -f "${oldDockerComposeTemplate}" ]; then mv -u "${oldDockerComposeTemplate}" "${DOCKER_COMPOSE_TEMPLATE_PATH}"; fi + if [ -d "${oldConfigDir}" ]; then rm -rf "${oldConfigDir}"; fi + + chown -R "${USERNAME}" "${APP_DIR}" || true +} + +determine_public_ports() { + # The docker-compose.yml will be broken if we use same port for http and https. Make sure that they are different. + if [ "${HTTP_PORT}" = "${HTTPS_PORT}" ]; then + echo >&2 "ERROR: Port '${HTTP_PORT}' cannot be configured for both http and https. Please choose different ports using --http-port and --https-port arguments" + exit 1 + fi + if [ "${WS_PORT}" = "${HTTP_PORT}" ]; then + echo >&2 "ERROR: Port '${HTTP_PORT}' cannot be configured for both http and ws. Please choose different ports using --http-port and --ws-port arguments" + exit 1 + fi + + # default for PUBLIC_HTTPS_PORT is HTTPS_PORT + PUBLIC_HTTPS_PORT="${HTTPS_PORT}" + + # PROXY_HTTPS_PORT overrides PUBLIC_HTTPS_PORT + if [ ! -z "${PROXY_HTTPS_PORT:-}" ]; then + PUBLIC_HTTPS_PORT="${PROXY_HTTPS_PORT}" + fi + + # default for PROXY_WS_PORT is PROXY_HTTPS_PORT + if [ -z "${PROXY_WS_PORT:-}" ]; then + PROXY_WS_PORT="${PROXY_HTTPS_PORT}" + fi + + # default for PUBLIC_WS_PORT is WS_PORT + PUBLIC_WS_PORT="${WS_PORT}" + + # PROXY_WS_PORT overrides PUBLIC_WS_PORT + if [ ! -z "${PROXY_WS_PORT:-}" ]; then + PUBLIC_WS_PORT="${PROXY_WS_PORT}" + fi + + # if WS port is different from HTTP port, add port mapping + WS_PORT_MAPPING= + if [ ! -z "${WS_PORT}" ] && [ ! "${WS_PORT}" = "${HTTPS_PORT}" ]; then + WS_PORT_MAPPING="- \"${WS_PORT}:${WS_PORT}\"" + fi + + export PUBLIC_HTTPS_PORT + export PUBLIC_WS_PORT + export WS_PORT_MAPPING +} + +create_docker_compose_file() { + echo "Creating docker-compose.yml" + + # Workaround for docker-compose from snap package. + if realpath "$(command -v ${DOCKER_COMPOSE_COMMAND})" | grep snap > /dev/null 2>&1 && [ "${TMP_INSTALL_DIR}" = "${SCRIPT_DIR}" ] ; then + echo "It appears that docker compose was installed using snap package manager. This version of docker compose cannot access files in '/tmp' directory." + TMP_INSTALL_DIR="$(mktemp -d -p "${HOME_DIR}" -t install-XXXXXX)" || fail "Failed to create temporary installation dir." + echo "Moving installation files from '${SCRIPT_DIR}' to '${TMP_INSTALL_DIR}'." + cp -a "${SCRIPT_DIR}/"* "${TMP_INSTALL_DIR}" || fail "Failed to copy installation files to install dir '${TMP_INSTALL_DIR}'." + if [ "${EUID}" -eq 0 ]; then + chown -R "${USERNAME}" "${TMP_INSTALL_DIR}/" || fail "Failed to change install dir '${TMP_INSTALL_DIR}' owner." + fi + fi + + envsubst < "${TMP_INSTALL_DIR}/${DOCKER_COMPOSE_TEMPLATE_FILENAME}" > "${TMP_INSTALL_DIR}/${DOCKER_COMPOSE_FILENAME}" || fail "Failed to create docker-compose.yml" + sed -i '/.*=$/d' "${TMP_INSTALL_DIR}/${DOCKER_COMPOSE_FILENAME}" || fail "Failed to remove empty env variables." +} + +login_to_dockerhub() { + if [[ ${DOCKER_USERNAME} ]]; then + echo "Logging in to Docker Hub as ${DOCKER_USERNAME}" + docker login -u="${DOCKER_USERNAME}" -p="${DOCKER_PASSWORD}" "${DOCKER_REGISTRY}" + fi +} + +pull_docker_images() { + if [ "${USE_LOCAL_IMAGES}" = "true" ]; then + echo "Will try to use local Docker images." + return 0 + fi + + echo "Pulling docker images." + local newDockerComposeFile="${TMP_INSTALL_DIR}/${DOCKER_COMPOSE_FILENAME}" + if [ -f "${newDockerComposeFile}" ]; then + ${DOCKER_COMPOSE_COMMAND} -p unms -f "${newDockerComposeFile}" pull || fail "Failed to pull docker images" + fi + + docker pull ubnt/ucrm-conntrack || fail "Failed to pull ubnt/ucrm-conntrack image" +} + +stop_docker_containers() { + if [ -f "${DOCKER_COMPOSE_PATH}" ]; then + runningContainers="$(dockerCompose ps -q)" || fail "Failed to get running containers." + if [ -n "${runningContainers}" ]; then + echo "Stopping docker containers." + if version_equal_or_newer "${DOCKER_VERSION}" "23.0.0" || version_equal_or_newer "${DOCKER_COMPOSE_VERSION}" "1.18.0" ; then + dockerCompose down --timeout 60 && RC=$? || RC=$? + else + dockerCompose down && RC=$? || RC=$? + fi + + if [ "${RC}" -gt 0 ]; then + # Failed to stop UISP. Try to restart it before exiting. + dockerCompose up -d unms netflow || true + fail "Failed to stop docker containers. This usually happens due to problem in docker service. Try restarting docker +service with 'sudo systemctl restart docker' and then retry the installation." + fi + fi + fi +} + +check_raw_sockets() { + local error + # Try to set raw sockets capabilities for node. If it fails the docker is running without 'setcap cap_net_raw' support. + error="$(docker run --rm --entrypoint /usr/sbin/setcap "${DOCKER_IMAGE}:${DOCKER_TAG}" \ + cap_net_raw=pe /usr/local/bin/node 2>&1)" && RC=$? || RC=$? + if [ "${RC}" -gt 0 ]; then + if echo "${error}" | grep -q "Failed to set capabilities on file"; then + fail "This Docker installation does not support setting file capabilities using 'setcap' command. +This happens usually due to lack of support from Docker's storage driver. +Please check the storage driver used by Docker by running 'docker info | grep \"Storage Driver\"'. It should be +set to overlay2 or similar filesystem with support for extended file attributes. +To change Docker's storage driver to overlay2 first check that the kernel supports it by running \"modprobe -a overlay\". +If that command does not print any error then it means that the overlay2 is supported. +If overlay2 is supported edit /etc/docker/daemon.json and add { \"storage-driver\": \"overlay2\" }. +Restart Docker service with 'sudo systemctl restart docker' and run this installation script again." + else + fail "Failed to check raw sockets: ${error}" + fi + fi + echo "File capabilities are supported." +} + +try_to_bind_port() { + local port="${1}" + local protocol="${2}" # tcp or udp + local error + # Try to create docker container that binds given port. + error="$(docker run --rm -p "${port}:${port}/${protocol}" --entrypoint /bin/true "${DOCKER_IMAGE}:${DOCKER_TAG}" 2>&1)" && RC=$? || RC=$? + if [ "${RC}" -gt 0 ]; then + if echo "${error}" | grep -q "already allocated" || echo "${error}" | grep -q "already in use"; then + return 1 + else + fail "Failed to check free ports: ${error}" + fi + fi + echo "Port ${port}/${protocol} is free." + return 0 +} + +check_free_ports() { + echo "Checking available ports" + while ! try_to_bind_port "${HTTP_PORT}" "tcp"; do + test "${UNATTENDED}" = "false" || fail "Port ${HTTP_PORT} is in use." + read -r -p "Port ${HTTP_PORT} is already in use, please choose a different HTTP port for UISP. [${ALTERNATIVE_HTTP_PORT}]: " HTTP_PORT + HTTP_PORT=${HTTP_PORT:-$ALTERNATIVE_HTTP_PORT} + done + + while ! try_to_bind_port "${HTTPS_PORT}" "tcp"; do + test "${UNATTENDED}" = "false" || fail "Port ${HTTPS_PORT} is in use." + read -r -p "Port ${HTTPS_PORT} is already in use, please choose a different HTTPS port for UISP. [${ALTERNATIVE_HTTPS_PORT}]: " HTTPS_PORT + HTTPS_PORT=${HTTPS_PORT:-$ALTERNATIVE_HTTPS_PORT} + done + + while ! try_to_bind_port "${SUSPEND_PORT}" "tcp"; do + if [ "${UNATTENDED}" = true ]; then + echo >&2 "WARNING: Port ${SUSPEND_PORT} is in use. Selecting ${ALTERNATIVE_SUSPEND_PORT} port for suspension." + SUSPEND_PORT="${ALTERNATIVE_SUSPEND_PORT}" + break + else + read -r -p "Port ${SUSPEND_PORT} is already in use, please choose a different Suspension port for UISP. [${ALTERNATIVE_SUSPEND_PORT}]: " SUSPEND_PORT + SUSPEND_PORT=${SUSPEND_PORT:-$ALTERNATIVE_SUSPEND_PORT} + fi + done + + while ! try_to_bind_port "${REMOTE_UI_WS_PORT_PUBLIC}" "tcp"; do + if [ "${UNATTENDED}" = true ]; then + echo >&2 "WARNING: Port ${REMOTE_UI_WS_PORT_PUBLIC} is in use. Selecting ${ALTERNATIVE_REMOTE_UI_WS_PORT_PUBLIC} port for remote device web UI." + REMOTE_UI_WS_PORT_PUBLIC="${ALTERNATIVE_REMOTE_UI_WS_PORT_PUBLIC}" + break + else + read -r -p "Port ${REMOTE_UI_WS_PORT_PUBLIC} is already in use, please choose a different remote device web UI port for UISP. [${ALTERNATIVE_REMOTE_UI_WS_PORT_PUBLIC}]: " REMOTE_UI_WS_PORT_PUBLIC + REMOTE_UI_WS_PORT_PUBLIC=${REMOTE_UI_WS_PORT_PUBLIC:-$ALTERNATIVE_REMOTE_UI_WS_PORT_PUBLIC} + fi + done + + while ! try_to_bind_port "${NETFLOW_PORT}" "udp"; do + if [ "${UNATTENDED}" = true ]; then + echo >&2 "WARNING: Port ${NETFLOW_PORT} is in use. Selecting ${ALTERNATIVE_NETFLOW_PORT} port for NetFlow." + NETFLOW_PORT="${ALTERNATIVE_NETFLOW_PORT}" + break + else + read -r -p "Port ${NETFLOW_PORT} is already in use, please choose a different NetFlow port for UISP. [${ALTERNATIVE_NETFLOW_PORT}]: " NETFLOW_PORT + NETFLOW_PORT=${NETFLOW_PORT:-$ALTERNATIVE_NETFLOW_PORT} + fi + done +} + +create_data_volumes() { + # some old versions linked data/cert to external cert dir + if [ -L "${DATA_DIR}/cert" ]; then + echo "Deleting old symlink ${DATA_DIR}/cert" + rm -f "${DATA_DIR}/cert"; + fi + + echo "Creating data volumes in '${DATA_DIR}'." + + local defaultNginxCertDir="${DATA_DIR}/cert" + local alternativeNginxCertDir="${HOME_DIR}/cert" + local nginxCertDir + if [ "${USE_ALTERNATIVE_CERT_DIR}" = "true" ]; then + nginxCertDir="${alternativeNginxCertDir}" + if [ -e "${defaultNginxCertDir}" ]; then + test ! -e "${alternativeNginxCertDir}" || fail "Both '${defaultNginxCertDir}' and '${alternativeNginxCertDir}' exist." + mv "${defaultNginxCertDir}" "${alternativeNginxCertDir}" || fail "Failed to move cert dir '${defaultNginxCertDir}' to '${alternativeNginxCertDir}'." + fi + else + nginxCertDir="${defaultNginxCertDir}" + if [ -e "${alternativeNginxCertDir}" ]; then + test ! -e "${defaultNginxCertDir}" || fail "Both '${alternativeNginxCertDir}' and '${defaultNginxCertDir}' exist." + mv "${alternativeNginxCertDir}" "${defaultNginxCertDir}" || fail "Failed to move cert dir '${alternativeNginxCertDir}' to '${defaultNginxCertDir}'." + fi + fi + + volumes=( + "${DATA_DIR}" + "${nginxCertDir}" + "${DATA_DIR}/siridb" + "${DATA_DIR}/siridb-cores" + "${DATA_DIR}/rabbitmq" + ) + + for volume in "${volumes[@]}"; do + mkdir -p -m u+rwX,g-rwx,o-rwx "${volume}" || fail "Failed to create volume '${volume}'." + if [ "${EUID}" -eq 0 ]; then + chown "${USERNAME}" "${volume}" || fail "Failed to change ownership of '${volume}'." + fi + done + + # always mount ~unms/data/cert as /cert + # mount either an external cert dir or ~unms/data/cert as /usercert + CERT_DIR_MAPPING_NGINX="- ${nginxCertDir}:/cert" + if [ -z "${SSL_CERT_DIR}" ]; then + USERCERT_DIR_MAPPING_NGINX="" + else + echo "Will mount ${SSL_CERT_DIR} as /usercert" + USERCERT_DIR_MAPPING_NGINX="- ${SSL_CERT_DIR}:/usercert:ro" + fi +} + +deploy_templates() { + echo "Deploying templates" + mkdir -p "${APP_DIR}" + cp -r "${TMP_INSTALL_DIR}"/* "${APP_DIR}/" || fail "Failed to deploy templates form '${TMP_INSTALL_DIR}' to '${APP_DIR}'." +} + +save_config() { + echo "Writing config file" + if ! cat >"${CONFIG_FILE}" <&2 "Failed to setup auto-update script" + exit 1 + fi + + if [ -d /etc/cron.d ] && command -v crontab > /dev/null 2>&1; then + echo "* * * * * ${USERNAME} ${updateScript} --cron > /dev/null 2>&1 || true" > /etc/cron.d/unms-update + + if (crontab -l -u "${USERNAME}" | grep "update.sh --cron"); then + # The per-user crontab was used in previous versions of UISP. Now we are using the global crontab. + # Remove the update script from per-user crontab. There should be no other records by default but + # user may have set up some custom job so remove just the update script. + crontab -l -u "${USERNAME}" | grep -v "update.sh --cron" || true | crontab -u "${USERNAME}" - + fi + else + if [ -d /etc/systemd/system ] && command -v systemctl > /dev/null 2>&1; then + +cat > /etc/systemd/system/unms-update.service < /etc/systemd/system/unms-update.timer <&2 "Failed to enable systemd auto update timer and service" + exit 1 + fi + + else + echo >&2 "Failed to enable auto update. UISP requires either Crontab or systemd timers." + exit 1 + fi + fi + + fi + +} + +delete_old_firmwares() { + echo "Deleting old firmwares from ${DATA_DIR}/firmwares/unms/*" + if ! rm -rf "${DATA_DIR}/firmwares/unms"/*; then + echo >&2 "WARNING: Failed to delete old firmwares" + fi +} + +change_owner() { + # only necessary when installing for the first time, as root + if [ "${EUID}" -eq 0 ]; then + cd "${HOME_DIR}" + + if ! chown -R "${USERNAME}" ./*; then + echo >&2 "Failed to change config files owner" + exit 1 + fi + + oldUninstallScript="${APP_DIR}/uninstall.sh" + if [ -f "${oldUninstallScript}" ]; then + if rm "${oldUninstallScript}"; then + echo "Removed ${oldUninstallScript}" + else + echo >&2 "Failed to remove ${oldUninstallScript}" + fi + fi + else + echo "Not running as root - will not change config files owner" + fi + + if ! chmod +x "${APP_DIR}/unms-cli"; then + echo >&2 "Failed to change permissions on ${APP_DIR}/unms-cli" + exit 1 + fi +} + +start_docker_containers() { + if [ "${START_CONTAINERS}" = "false" ]; then + return 0 + fi + + echo "Starting docker containers." + dockerCompose up -d unms netflow || fail "Failed to start docker containers" +} + +remove_old_image() { + local containerName="$1" + local imageName="$2" + local currentImage="$(docker ps --format "{{.Image}}" --filter name="^${containerName}$" || true)" + if [ -z "${currentImage}" ]; then + return 0; + fi + + local allImages="$(docker images "${imageName}:"* --format "{{.Repository}}:{{.Tag}}" || true)" + if [ -z "${allImages}" ]; then + return 0; + fi + + for value in ${allImages}; do + if [ "${value}" != "${currentImage}" ]; then + echo "Removing old image '${value}'" + if ! docker rmi "${value}"; then + echo "Failed to remove old image '${value}'" + fi + fi + done +} + +remove_old_images() { + echo "Removing old images" + danglingImages="$(docker images -qf "dangling=true")" + if [ ! -z "${danglingImages}" ]; then + echo "Removing dangling images" + docker rmi ${danglingImages} || true; + fi + + remove_old_image "unms" "${DOCKER_IMAGE}" + remove_old_image "unms-netflow" "${DOCKER_IMAGE}-netflow" + remove_old_image "unms-nginx" "${DOCKER_IMAGE}-nginx" + remove_old_image "unms-fluentd" "${DOCKER_IMAGE}-fluentd" + remove_old_image "unms-siridb" "${DOCKER_IMAGE}-siridb" + remove_old_image "ucrm" "${UCRM_DOCKER_IMAGE}" +} + +confirm_success() { + if [ "${START_CONTAINERS}" = "false" ]; then + echo "Skipping start of containers, use unms-cli to start UISP manually." + return 0 + fi + + echo "Waiting for UISP to start" + n=0 + until [ ${n} -ge 10 ] + do + sleep 3s + unmsRunning=true + # env -i is to ensure that http[s]_proxy variables are not set + # Otherwise the check would go through proxy. + env -i curl -skL "https://127.0.0.1:${HTTPS_PORT}" > /dev/null && break + echo "." + unmsRunning=false + n=$((n+1)) + done + + if [ "${unmsRunning}" = "false" ]; then + # Sometimes UISP is not available from localhost. Try to access it through docker interface. + dockerInterfaceIp="$(ip a show docker0 | grep inet | head -n 1 | sed 's/.* inet \([^\/]*\)\/.*/\1/' 2> /dev/null || echo "")" + if [ -n "${dockerInterfaceIp}" ]; then + if env -i curl -skL "https://${dockerInterfaceIp}:${HTTPS_PORT}" > /dev/null; then + unmsRunning=true + fi + fi + fi + docker ps + + if [ "${unmsRunning}" = true ]; then + echo "UISP is running" + else + fail "UISP is NOT running" + fi +} + +check_system +check_update_allowed +check_forgotten_update_flag +check_custom_cert_path +install_docker +check_docker_compose +configure_containerd +check_free_space +create_user +remove_old_restore_files +migrate_app_files +determine_public_ports # need to set all docker compose variables +create_data_volumes +create_docker_compose_file # compose file for docker-compose down +login_to_dockerhub +pull_docker_images +stop_docker_containers +check_raw_sockets +check_free_ports +determine_public_ports # again - now we have all info +create_docker_compose_file # again - compose file for docker-compose up +deploy_templates +cleanup_docker +fix_postgres +save_config +setup_auto_update +delete_old_firmwares +change_owner +start_docker_containers +remove_old_images +confirm_success +cleanup + +exit 0 diff --git a/uisp/templates/configMap.yaml b/uisp/templates/configMap.yaml new file mode 100644 index 0000000..b96bcfa --- /dev/null +++ b/uisp/templates/configMap.yaml @@ -0,0 +1,140 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: fluentd-config +data: + FLUENTD_UID: "1001" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: siridb-config +data: + SIRIDB_UID: "1001" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: postgres-config +data: + POSTGRES_UID: "1001" + POSTGRES_USER: "postgres" + UNMS_POSTGRES_DB: "unms" + UNMS_POSTGRES_SCHEMA: "unms" + UNMS_POSTGRES_USER: "unms" + UCRM_POSTGRES_DB: "unms" + UCRM_POSTGRES_SCHEMA: "ucrm" + UCRM_POSTGRES_USER: "ucrm" + PGDATA: "/var/lib/postgresql/data/pgdata" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: rabbitmq-config +data: + RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS: "-rabbit channel_max 4096" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: unms-config +data: + UNMS_USER_ID: "1001" + DEMO: "false" + NODE_ENV: "production" + HTTP_PORT: "8081" + WS_PORT: "8082" + WS_SHELL_PORT: "8083" + UNMS_WS_API_PORT: "8084" + UNMS_NETFLOW_PORT: "2055" + PUBLIC_HTTPS_PORT: "443" + NGINX_HTTPS_PORT: "443" + SUSPEND_PORT: "81" + BRANCH: "master" + SECURE_LINK_SECRET: "IdNMEEUnBadoA0dRr6e7t76798JLBZLVQ0FPGAtqiz31lYMxeuLYefkfPkZepsGM2WoomGFPolv5u5Ld74e2XVRt0rTlsZo9j0eh" + CLUSTER_SIZE: "auto" + UNMS_PG_USER: "unms" + UNMS_PG_DB: "unms" + UNMS_PG_SCHEMA: "unms" + UNMS_TOKEN: "NltSFUMqKWHd4Tf5PdqZbs9XsFenA7UiR0DcGQDD8yN5yClK" + UNMS_CLI_TOKEN: "HDDwClgKV9HqPx9Ck47Kg8EE0neW0L4mzuG60XgI9VXZERT5" + USE_LOCAL_DISCOVERY: "true" + USE_ALTERNATIVE_CERT_DIR: "false" + UNMS_RABBITMQ_HOST: "unms-rabbitmq" + UNMS_RABBITMQ_PORT: "5672" + UNMS_SIRIDB_HOST: "unms-siridb" + UNMS_SIRIDB_PORT: "9000" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: ucrm-config +data: + POSTGRES_HOST: "unms-postgres" + POSTGRES_SCHEMA: "ucrm" + POSTGRES_USER: "ucrm" + POSTGRES_DB: "unms" + MAILER_ADDRESS: "127.1.0.1" + MAILER_ADDRESS_USERNAME: "username" + MAILER_ADDRESS_PASSWORD: "password" + SECRET: "q9i5rV7NTNQf8qSPQIONCFWGjDNbm4K83APtscfnuvJc7DAN" + SUSPEND_PORT: "81" + PUBLIC_HTTPS_PORT: "443" + UCRM_USER: "unms" + UNMS_VERSION: "2.4.188" + UNMS_HOST: "unms" + UNMS_PORT: "8081" + UNMS_TOKEN: "NltSFUMqKWHd4Tf5PdqZbs9XsFenA7UiR0DcGQDD8yN5yClK" + UNMS_BASE_URL: "/v2.1" + UNMS_POSTGRES_SCHEMA: "unms" + RABBITMQ_HOST: "unms-rabbitmq" + RABBITMQ_PORT: "5672" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: netflow-config +data: + netflow.conf: | + # Configuration for netflow + [netflow] + port = 2055 + buffer_size = 4096 + workers = 4 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-config +data: + nginx.conf: | + # Configuration for nginx + user nginx; + worker_processes 1; + + error_log /var/log/nginx/error.log warn; + pid /var/run/nginx.pid; + + events { + worker_connections 1024; + } + + http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + include /etc/nginx/conf.d/*.conf; + } \ No newline at end of file diff --git a/uisp/templates/deployment.yaml b/uisp/templates/deployment.yaml new file mode 100644 index 0000000..97a7976 --- /dev/null +++ b/uisp/templates/deployment.yaml @@ -0,0 +1,304 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: fluentd +spec: + replicas: 1 + selector: + matchLabels: + app: fluentd + template: + metadata: + labels: + app: fluentd + spec: + containers: + - name: fluentd + image: ubnt/unms-fluentd:2.4.188 + ports: + - containerPort: 24224 + volumeMounts: + - name: logs + mountPath: /fluentd/log + envFrom: + - configMapRef: + name: fluentd-config + volumes: + - name: logs + persistentVolumeClaim: + claimName: fluentd-logs-pvc +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: siridb +spec: + replicas: 1 + selector: + matchLabels: + app: siridb + template: + metadata: + labels: + app: siridb + spec: + containers: + - name: siridb + image: ubnt/unms-siridb:2.4.188 + volumeMounts: + - name: siridb-data + mountPath: /var/lib/siridb + - name: siridb-cores + mountPath: /cores + envFrom: + - configMapRef: + name: siridb-config + securityContext: + capabilities: + add: ["SYS_PTRACE"] + volumes: + - name: siridb-data + persistentVolumeClaim: + claimName: siridb-data-pvc + - name: siridb-cores + persistentVolumeClaim: + claimName: siridb-cores-pvc +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + initContainers: + - name: init-postgres + image: busybox + command: ["sh", "-c"] + args: + - | + mkdir -p /var/lib/postgresql/data/pgdata + chown -R 999:999 /var/lib/postgresql/data/pgdata + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + containers: + - name: postgres + image: ubnt/unms-postgres:2.4.188 + args: ["postgres", "-c", "deadlock_timeout=5000", "-c", "max_connections=570"] + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + envFrom: + - configMapRef: + name: postgres-config + env: + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: db-secrets + key: POSTGRES_PASSWORD + - name: UNMS_POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: db-secrets + key: UNMS_POSTGRES_PASSWORD + - name: UCRM_POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: db-secrets + key: UCRM_POSTGRES_PASSWORD + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: postgres-data-pvc +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rabbitmq +spec: + replicas: 1 + selector: + matchLabels: + app: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + containers: + - name: rabbitmq + image: rabbitmq:3.7.28-alpine + ports: + - containerPort: 5672 + volumeMounts: + - name: rabbitmq-data + mountPath: /var/lib/rabbitmq + envFrom: + - configMapRef: + name: rabbitmq-config + volumes: + - name: rabbitmq-data + persistentVolumeClaim: + claimName: rabbitmq-data-pvc +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: unms +spec: + replicas: 1 + selector: + matchLabels: + app: unms + template: + metadata: + labels: + app: unms + spec: + containers: + - name: unms + image: ubnt/unms:2.4.188 + volumeMounts: + - name: unms-data + mountPath: /home/app/unms/data + envFrom: + - configMapRef: + name: unms-config + env: + - name: UNMS_PG_PASSWORD + valueFrom: + secretKeyRef: + name: db-secrets + key: UNMS_POSTGRES_PASSWORD + securityContext: + capabilities: + add: ["NET_ADMIN"] + volumes: + - name: unms-data + persistentVolumeClaim: + claimName: unms-data-pvc +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ucrm +spec: + replicas: 1 + selector: + matchLabels: + app: ucrm + template: + metadata: + labels: + app: ucrm + spec: + containers: + - name: ucrm + image: ubnt/unms-crm:4.4.30 + args: ["server_with_migrate"] + volumeMounts: + - name: ucrm-data + mountPath: /data + envFrom: + - configMapRef: + name: ucrm-config + env: + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: db-secrets + key: UCRM_POSTGRES_PASSWORD + volumes: + - name: ucrm-data + persistentVolumeClaim: + claimName: ucrm-data-pvc +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + initContainers: + - name: init-cert-generator + image: alpine:3.12 + command: ["/bin/sh", "-c"] + args: + - | + # Install OpenSSL + apk add --no-cache openssl + # Ensure the /cert directory exists and has the correct permissions + mkdir -p /cert + chown 1000:1000 /cert + chmod 700 /cert + # Generate SSL certificate keys + openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /cert/nginx.key -out /cert/nginx.crt -subj "/CN=localhost" + volumeMounts: + - name: unms-data + mountPath: /cert + subPath: data/cert + containers: + - name: nginx + image: ubnt/unms-nginx:2.4.188 + ports: + - containerPort: 80 + - containerPort: 443 + - containerPort: 81 + - containerPort: 8089 + volumeMounts: + - name: unms-data + mountPath: /cert + subPath: data/cert + - name: firmwares + mountPath: /www/firmwares + envFrom: + - configMapRef: + name: nginx-config + volumes: + - name: unms-data + persistentVolumeClaim: + claimName: unms-data-pvc + - name: firmwares + persistentVolumeClaim: + claimName: nginx-firmwares-pvc +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: netflow +spec: + replicas: 1 + selector: + matchLabels: + app: netflow + template: + metadata: + labels: + app: netflow + spec: + containers: + - name: netflow + image: ubnt/unms-netflow:2.4.188 + ports: + - containerPort: 2055 + protocol: UDP + envFrom: + - configMapRef: + name: netflow-config \ No newline at end of file diff --git a/uisp/templates/persistentVolumeClaim.yaml b/uisp/templates/persistentVolumeClaim.yaml new file mode 100644 index 0000000..79aec3c --- /dev/null +++ b/uisp/templates/persistentVolumeClaim.yaml @@ -0,0 +1,98 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: fluentd-logs-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: siridb-data-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: siridb-cores-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgres-data-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: rabbitmq-data-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: unms-data-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 25Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: ucrm-data-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: nginx-cert-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: nginx-firmwares-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi \ No newline at end of file diff --git a/uisp/templates/secret.yaml b/uisp/templates/secret.yaml new file mode 100644 index 0000000..cf9c069 --- /dev/null +++ b/uisp/templates/secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: db-secrets +type: Opaque +data: + POSTGRES_PASSWORD: "MWIwVXgxdkpJRGJvN05UUXkxQXJ4cDRzeDhCQ3g5QVFxN1UyUkdUeWd3N0FqcVpM" # base64 encoded "1B0Ux1wJIDbo7NTQy1Arxp4sx8BCx9AQq7U2RGTygw7QjqZL" + UNMS_POSTGRES_PASSWORD: "SmNKbzRJQmhISWNzTjByd0Y0YU5IbnFRTDBjYVh1a0VtU2dBejFKdE1KelpDOTQz" # base64 encoded "JcJo4IBhHIcsN0rwF4aNHnqQL0caXukEmSgAz1JtMJzZC943" + UCRM_POSTGRES_PASSWORD: "ZDlOWnhEcFJIbkVUbWN3amRGM2YyVVl0aDlCZVlYSWtmZ1Z6RnhuTzZrZXB1Q2ZV" # base64 encoded "d9NZxDpRHnUTmcwjdF3f2UYth9BeYXIkfkVzFxnO6kepuCfU" \ No newline at end of file diff --git a/uisp/templates/service.yaml b/uisp/templates/service.yaml new file mode 100644 index 0000000..660d618 --- /dev/null +++ b/uisp/templates/service.yaml @@ -0,0 +1,152 @@ +apiVersion: v1 +kind: Service +metadata: + name: unms-fluentd +spec: + selector: + app: fluentd + ports: + - name: fluentd-port + protocol: TCP + port: 24224 + targetPort: 24224 +--- +apiVersion: v1 +kind: Service +metadata: + name: unms-siridb +spec: + selector: + app: siridb + ports: + - name: siridb-port + protocol: TCP + port: 9000 + targetPort: 9000 +--- +apiVersion: v1 +kind: Service +metadata: + name: unms-postgres +spec: + selector: + app: postgres + ports: + - name: postgres-port + protocol: TCP + port: 5432 + targetPort: 5432 +--- +apiVersion: v1 +kind: Service +metadata: + name: rabbitmq +spec: + selector: + app: rabbitmq + ports: + - name: rabbitmq-port + protocol: TCP + port: 5672 + targetPort: 5672 +--- +apiVersion: v1 +kind: Service +metadata: + name: unms-rabbitmq +spec: + selector: + app: rabbitmq + ports: + - name: rabbitmq-port + protocol: TCP + port: 5672 + targetPort: 5672 +--- +apiVersion: v1 +kind: Service +metadata: + name: unms +spec: + selector: + app: unms + ports: + - name: http + protocol: TCP + port: 8081 + targetPort: 8081 + - name: ws + protocol: TCP + port: 8082 + targetPort: 8082 + - name: ws-shell + protocol: TCP + port: 8083 + targetPort: 8083 + - name: ws-api + protocol: TCP + port: 8084 + targetPort: 8084 + - name: https + protocol: TCP + port: 443 + targetPort: 443 + - name: suspend + protocol: TCP + port: 81 + targetPort: 81 +--- +apiVersion: v1 +kind: Service +metadata: + name: ucrm +spec: + selector: + app: ucrm + ports: + - name: https + protocol: TCP + port: 443 + targetPort: 443 + - name: suspend + protocol: TCP + port: 81 + targetPort: 81 +--- +apiVersion: v1 +kind: Service +metadata: + name: unms-nginx +spec: + selector: + app: nginx + ports: + - name: http + protocol: TCP + port: 80 + targetPort: 80 + - name: https + protocol: TCP + port: 443 + targetPort: 443 + - name: suspend + protocol: TCP + port: 81 + targetPort: 81 + - name: custom + protocol: TCP + port: 8089 + targetPort: 8089 +--- +apiVersion: v1 +kind: Service +metadata: + name: unms-netflow +spec: + selector: + app: netflow + ports: + - name: netflow-port + protocol: UDP + port: 2055 + targetPort: 2055 \ No newline at end of file diff --git a/uisp/values.yaml b/uisp/values.yaml new file mode 100644 index 0000000..e69de29