685 lines
25 KiB
Bash
685 lines
25 KiB
Bash
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
CONFIG_FILE="${1:-${SCRIPT_DIR}/vfire-stack.env}"
|
|
DRY_RUN=0
|
|
|
|
APP_USER="vfire-monitor"
|
|
APP_GROUP="vfire-monitor"
|
|
APP_HOME="/opt/vfire-monitor"
|
|
APP_VENV="${APP_HOME}/.venv"
|
|
APP_STATE_DIR="/var/lib/vfire-monitor"
|
|
APP_LOG_DIR="/var/log/vfire-monitor"
|
|
APP_SERVICE="vfire-monitor.service"
|
|
APP_ENV_FILE="/etc/default/vfire-monitor"
|
|
APP_SYSTEMD_FILE="/etc/systemd/system/${APP_SERVICE}"
|
|
|
|
ZABBIX_APT_KEYRING="/usr/share/keyrings/zabbix.gpg"
|
|
ZABBIX_APT_LIST="/etc/apt/sources.list.d/zabbix.list"
|
|
GRAFANA_APT_KEYRING="/etc/apt/keyrings/grafana.asc"
|
|
GRAFANA_APT_LIST="/etc/apt/sources.list.d/grafana.list"
|
|
GRAFANA_DASHBOARD_DIR="/var/lib/grafana/dashboards/vfire"
|
|
SUMMARY_FILE="/root/vfire-stack-summary.txt"
|
|
|
|
log() {
|
|
printf '[vfire-installer] %s\n' "$*"
|
|
}
|
|
|
|
die() {
|
|
printf '[vfire-installer] ERROR: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
run_cmd() {
|
|
if [[ "${DRY_RUN}" == "1" ]]; then
|
|
printf '[dry-run] %s\n' "$*"
|
|
return 0
|
|
fi
|
|
"$@"
|
|
}
|
|
|
|
parse_args() {
|
|
local positional=()
|
|
while (($#)); do
|
|
case "$1" in
|
|
--dry-run)
|
|
DRY_RUN=1
|
|
shift
|
|
;;
|
|
*)
|
|
positional+=("$1")
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if ((${#positional[@]} > 0)); then
|
|
CONFIG_FILE="${positional[0]}"
|
|
fi
|
|
}
|
|
|
|
require_root() {
|
|
[[ "${EUID}" -eq 0 ]] || die "Execute como root."
|
|
}
|
|
|
|
require_supported_os() {
|
|
source /etc/os-release
|
|
[[ "${ID}" == "debian" ]] || die "Este instalador suporta apenas Debian."
|
|
[[ "${VERSION_CODENAME:-}" == "trixie" ]] || die "Este instalador foi preparado para Debian 13 (trixie)."
|
|
}
|
|
|
|
random_secret() {
|
|
tr -dc 'A-Za-z0-9' </dev/urandom | head -c 32
|
|
}
|
|
|
|
load_config() {
|
|
[[ -f "${CONFIG_FILE}" ]] || die "Arquivo de configuracao nao encontrado: ${CONFIG_FILE}. Copie installer/vfire-stack.env.example para installer/vfire-stack.env e ajuste os valores."
|
|
# shellcheck disable=SC1090
|
|
source "${CONFIG_FILE}"
|
|
|
|
PUBLIC_HOSTNAME="${PUBLIC_HOSTNAME:-$(hostname -f 2>/dev/null || hostname)}"
|
|
VFM_APP_PORT="${VFM_APP_PORT:-8088}"
|
|
ZABBIX_WEB_PORT="${ZABBIX_WEB_PORT:-8080}"
|
|
GRAFANA_PORT="${GRAFANA_PORT:-3000}"
|
|
VFM_DEFAULT_PASSWORD="${VFM_DEFAULT_PASSWORD:-admin}"
|
|
VFM_LOG_LEVEL="${VFM_LOG_LEVEL:-INFO}"
|
|
ZABBIX_VERSION="${ZABBIX_VERSION:-7.4}"
|
|
ZABBIX_DB_NAME="${ZABBIX_DB_NAME:-zabbix}"
|
|
ZABBIX_DB_USER="${ZABBIX_DB_USER:-zabbix}"
|
|
ZABBIX_ADMIN_USERNAME="${ZABBIX_ADMIN_USERNAME:-Admin}"
|
|
ZABBIX_ADMIN_PASSWORD="${ZABBIX_ADMIN_PASSWORD:-zabbix}"
|
|
ZABBIX_TEMPLATE_FILE="${ZABBIX_TEMPLATE_FILE:-${APP_HOME}/zbx_export_templates.yaml}"
|
|
ZABBIX_TEMPLATE_NAME="${ZABBIX_TEMPLATE_NAME:-Notifier NFS320}"
|
|
ZABBIX_HOST_GROUP="${ZABBIX_HOST_GROUP:-Notifier}"
|
|
ZABBIX_MONITORED_HOST="${ZABBIX_MONITORED_HOST:-NFS-320}"
|
|
GRAFANA_ADMIN_USER="${GRAFANA_ADMIN_USER:-admin}"
|
|
GRAFANA_INSTALL_ZABBIX_PLUGIN="${GRAFANA_INSTALL_ZABBIX_PLUGIN:-1}"
|
|
ALLOW_LEGACY_LICENSE_KEY="${ALLOW_LEGACY_LICENSE_KEY:-0}"
|
|
|
|
ZABBIX_DB_PASSWORD="${ZABBIX_DB_PASSWORD:-$(random_secret)}"
|
|
GRAFANA_ADMIN_PASSWORD="${GRAFANA_ADMIN_PASSWORD:-$(random_secret)}"
|
|
|
|
[[ "${ZABBIX_ADMIN_USERNAME}" == "Admin" ]] || die "O instalador atualmente suporta apenas o usuario administrativo padrao do Zabbix: Admin."
|
|
|
|
if [[ -z "${VFM_LICENSE_MASTER_KEY:-}" && "${ALLOW_LEGACY_LICENSE_KEY}" != "1" ]]; then
|
|
die "Defina VFM_LICENSE_MASTER_KEY no arquivo de configuracao ou ajuste ALLOW_LEGACY_LICENSE_KEY=1."
|
|
fi
|
|
}
|
|
|
|
assert_command_dependencies() {
|
|
local cmd
|
|
for cmd in apt-get python3 runuser systemctl; do
|
|
command -v "${cmd}" >/dev/null 2>&1 || die "Comando obrigatorio ausente: ${cmd}"
|
|
done
|
|
}
|
|
|
|
assert_ports_available() {
|
|
local port
|
|
[[ "${DRY_RUN}" == "1" ]] && return 0
|
|
command -v ss >/dev/null 2>&1 || return 0
|
|
if [[ "${VFM_APP_PORT}" == "${ZABBIX_WEB_PORT}" || "${VFM_APP_PORT}" == "${GRAFANA_PORT}" || "${ZABBIX_WEB_PORT}" == "${GRAFANA_PORT}" ]]; then
|
|
die "As portas configuradas para V-Fire Monitor, Zabbix e Grafana precisam ser distintas."
|
|
fi
|
|
for port in "${VFM_APP_PORT}" "${ZABBIX_WEB_PORT}" "${GRAFANA_PORT}"; do
|
|
if ss -ltn "( sport = :${port} )" | tail -n +2 | grep -q .; then
|
|
log "Porta ${port} ja esta em uso. O instalador vai prosseguir para permitir reexecucoes e atualizacoes."
|
|
fi
|
|
done
|
|
}
|
|
|
|
apt_install() {
|
|
if [[ "${DRY_RUN}" == "1" ]]; then
|
|
printf '[dry-run] DEBIAN_FRONTEND=noninteractive apt-get install -y %s\n' "$*"
|
|
return 0
|
|
fi
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y "$@"
|
|
}
|
|
|
|
write_zabbix_repo() {
|
|
run_cmd install -d -m 0755 /usr/share/keyrings
|
|
if [[ "${DRY_RUN}" == "1" ]]; then
|
|
printf '[dry-run] curl -fsSL https://repo.zabbix.com/zabbix-official-repo.key | gpg --dearmor --yes -o %s\n' "${ZABBIX_APT_KEYRING}"
|
|
else
|
|
curl -fsSL "https://repo.zabbix.com/zabbix-official-repo.key" | gpg --dearmor --yes -o "${ZABBIX_APT_KEYRING}"
|
|
fi
|
|
run_cmd chmod 0644 "${ZABBIX_APT_KEYRING}"
|
|
cat >"${ZABBIX_APT_LIST}" <<EOF
|
|
deb [signed-by=${ZABBIX_APT_KEYRING}] https://repo.zabbix.com/zabbix/${ZABBIX_VERSION}/stable/debian trixie main
|
|
EOF
|
|
}
|
|
|
|
write_grafana_repo() {
|
|
run_cmd install -d -m 0755 /etc/apt/keyrings
|
|
if [[ "${DRY_RUN}" == "1" ]]; then
|
|
printf '[dry-run] wget -q -O %s https://apt.grafana.com/gpg-full.key\n' "${GRAFANA_APT_KEYRING}"
|
|
else
|
|
wget -q -O "${GRAFANA_APT_KEYRING}" "https://apt.grafana.com/gpg-full.key"
|
|
fi
|
|
run_cmd chmod 0644 "${GRAFANA_APT_KEYRING}"
|
|
cat >"${GRAFANA_APT_LIST}" <<EOF
|
|
deb [signed-by=${GRAFANA_APT_KEYRING}] https://apt.grafana.com stable main
|
|
EOF
|
|
}
|
|
|
|
install_system_packages() {
|
|
log "Instalando pacotes base"
|
|
if [[ "${DRY_RUN}" == "1" ]]; then
|
|
printf '[dry-run] apt-get update\n'
|
|
else
|
|
apt-get update
|
|
fi
|
|
apt_install ca-certificates curl wget gnupg apt-transport-https rsync jq
|
|
apt_install python3 python3-venv python3-pip
|
|
apt_install postgresql nginx php-fpm php-pgsql php-mbstring php-gd php-xml php-bcmath php-ldap
|
|
|
|
write_zabbix_repo
|
|
write_grafana_repo
|
|
|
|
if [[ "${DRY_RUN}" == "1" ]]; then
|
|
printf '[dry-run] apt-get update\n'
|
|
else
|
|
apt-get update
|
|
fi
|
|
apt_install zabbix-server-pgsql zabbix-frontend-php zabbix-nginx-conf zabbix-sql-scripts zabbix-agent2
|
|
apt_install grafana
|
|
}
|
|
|
|
deploy_application() {
|
|
log "Publicando codigo da aplicacao em ${APP_HOME}"
|
|
getent group "${APP_GROUP}" >/dev/null || run_cmd groupadd --system "${APP_GROUP}"
|
|
id -u "${APP_USER}" >/dev/null 2>&1 || run_cmd useradd --system --gid "${APP_GROUP}" --home "${APP_HOME}" --shell /usr/sbin/nologin "${APP_USER}"
|
|
|
|
run_cmd install -d -o "${APP_USER}" -g "${APP_GROUP}" "${APP_HOME}" "${APP_STATE_DIR}" "${APP_LOG_DIR}"
|
|
run_cmd rsync -a \
|
|
--delete \
|
|
--exclude '.git' \
|
|
--exclude '.venv' \
|
|
--exclude '.pytest_cache' \
|
|
--exclude '__pycache__' \
|
|
--exclude '*.pyc' \
|
|
--exclude 'config_nodes.json' \
|
|
--exclude 'mapa_dispositivos.json' \
|
|
--exclude 'license.key' \
|
|
--exclude 'app_secret.key' \
|
|
"${REPO_ROOT}/" "${APP_HOME}/"
|
|
|
|
run_cmd python3 -m venv "${APP_VENV}"
|
|
run_cmd "${APP_VENV}/bin/python" -m pip install --upgrade pip
|
|
run_cmd "${APP_VENV}/bin/python" -m pip install -r "${APP_HOME}/requirements.txt"
|
|
|
|
for file_name in config_nodes.json mapa_dispositivos.json license.key app_secret.key; do
|
|
if [[ -f "${REPO_ROOT}/${file_name}" && ! -f "${APP_STATE_DIR}/${file_name}" ]]; then
|
|
run_cmd install -m 0600 -o "${APP_USER}" -g "${APP_GROUP}" "${REPO_ROOT}/${file_name}" "${APP_STATE_DIR}/${file_name}"
|
|
fi
|
|
done
|
|
|
|
run_cmd chown -R "${APP_USER}:${APP_GROUP}" "${APP_HOME}" "${APP_STATE_DIR}" "${APP_LOG_DIR}"
|
|
}
|
|
|
|
configure_vfire_service() {
|
|
log "Configurando servico systemd do V-Fire Monitor"
|
|
cat >"${APP_ENV_FILE}" <<EOF
|
|
VFM_BASE_DIR=${APP_STATE_DIR}
|
|
VFM_DEFAULT_PASSWORD=${VFM_DEFAULT_PASSWORD}
|
|
VFM_LOG_LEVEL=${VFM_LOG_LEVEL}
|
|
VFM_ENV=production
|
|
HOST=127.0.0.1
|
|
PORT=${VFM_APP_PORT}
|
|
VFM_TRUST_PROXY=0
|
|
VFM_SESSION_COOKIE_SECURE=0
|
|
VFM_WAITRESS_THREADS=8
|
|
VFM_ALLOW_LEGACY_LICENSE_KEY=${ALLOW_LEGACY_LICENSE_KEY}
|
|
${VFM_APP_SECRET:+VFM_APP_SECRET=${VFM_APP_SECRET}}
|
|
${VFM_LICENSE_MASTER_KEY:+VFM_LICENSE_MASTER_KEY=${VFM_LICENSE_MASTER_KEY}}
|
|
EOF
|
|
run_cmd chmod 0600 "${APP_ENV_FILE}"
|
|
|
|
cat >"${APP_SYSTEMD_FILE}" <<EOF
|
|
[Unit]
|
|
Description=V-Fire Monitor
|
|
After=network.target postgresql.service
|
|
Wants=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=${APP_USER}
|
|
Group=${APP_GROUP}
|
|
WorkingDirectory=${APP_HOME}
|
|
EnvironmentFile=${APP_ENV_FILE}
|
|
ExecStart=${APP_VENV}/bin/python ${APP_HOME}/serve.py
|
|
Restart=always
|
|
RestartSec=5
|
|
StandardOutput=append:${APP_LOG_DIR}/app.log
|
|
StandardError=append:${APP_LOG_DIR}/app.log
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
}
|
|
|
|
ensure_postgres_role() {
|
|
local exists
|
|
exists="$(runuser -u postgres -- psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${ZABBIX_DB_USER}'")"
|
|
if [[ "${exists}" != "1" ]]; then
|
|
runuser -u postgres -- psql -v ON_ERROR_STOP=1 -c "CREATE USER ${ZABBIX_DB_USER} WITH PASSWORD '${ZABBIX_DB_PASSWORD}';"
|
|
else
|
|
runuser -u postgres -- psql -v ON_ERROR_STOP=1 -c "ALTER USER ${ZABBIX_DB_USER} WITH PASSWORD '${ZABBIX_DB_PASSWORD}';"
|
|
fi
|
|
}
|
|
|
|
ensure_postgres_database() {
|
|
local exists
|
|
exists="$(runuser -u postgres -- psql -tAc "SELECT 1 FROM pg_database WHERE datname='${ZABBIX_DB_NAME}'")"
|
|
if [[ "${exists}" != "1" ]]; then
|
|
runuser -u postgres -- psql -v ON_ERROR_STOP=1 -c "CREATE DATABASE ${ZABBIX_DB_NAME} OWNER ${ZABBIX_DB_USER} ENCODING 'UTF8';"
|
|
fi
|
|
}
|
|
|
|
import_zabbix_schema_if_needed() {
|
|
local initialized
|
|
initialized="$(runuser -u postgres -- psql -d "${ZABBIX_DB_NAME}" -tAc "SELECT to_regclass('public.users') IS NOT NULL")"
|
|
if [[ "${initialized}" != "t" ]]; then
|
|
zcat /usr/share/zabbix/sql-scripts/postgresql/server.sql.gz | PGPASSWORD="${ZABBIX_DB_PASSWORD}" psql -h localhost -U "${ZABBIX_DB_USER}" "${ZABBIX_DB_NAME}"
|
|
fi
|
|
}
|
|
|
|
set_ini_value() {
|
|
local file="$1"
|
|
local key="$2"
|
|
local value="$3"
|
|
if grep -Eq "^[# ]*${key}=" "${file}"; then
|
|
sed -i -E "s|^[# ]*${key}=.*|${key}=${value}|" "${file}"
|
|
else
|
|
printf '%s=%s\n' "${key}" "${value}" >>"${file}"
|
|
fi
|
|
}
|
|
|
|
configure_zabbix() {
|
|
log "Configurando PostgreSQL e Zabbix"
|
|
run_cmd systemctl enable --now postgresql
|
|
|
|
ensure_postgres_role
|
|
ensure_postgres_database
|
|
import_zabbix_schema_if_needed
|
|
|
|
set_ini_value "/etc/zabbix/zabbix_server.conf" "DBHost" "localhost"
|
|
set_ini_value "/etc/zabbix/zabbix_server.conf" "DBName" "${ZABBIX_DB_NAME}"
|
|
set_ini_value "/etc/zabbix/zabbix_server.conf" "DBUser" "${ZABBIX_DB_USER}"
|
|
set_ini_value "/etc/zabbix/zabbix_server.conf" "DBPassword" "${ZABBIX_DB_PASSWORD}"
|
|
|
|
run_cmd install -d -m 0750 /etc/zabbix/web
|
|
cat > /etc/zabbix/web/zabbix.conf.php <<EOF
|
|
<?php
|
|
// Zabbix GUI configuration file generated by V-Fire installer.
|
|
|
|
\$DB['TYPE'] = 'POSTGRESQL';
|
|
\$DB['SERVER'] = 'localhost';
|
|
\$DB['PORT'] = '0';
|
|
\$DB['DATABASE'] = '${ZABBIX_DB_NAME}';
|
|
\$DB['USER'] = '${ZABBIX_DB_USER}';
|
|
\$DB['PASSWORD'] = '${ZABBIX_DB_PASSWORD}';
|
|
\$DB['SCHEMA'] = '';
|
|
|
|
\$ZBX_SERVER = 'localhost';
|
|
\$ZBX_SERVER_PORT = '10051';
|
|
\$ZBX_SERVER_NAME = '${ZABBIX_MONITORED_HOST}';
|
|
|
|
\$IMAGE_FORMAT_DEFAULT = IMAGE_FORMAT_PNG;
|
|
EOF
|
|
run_cmd chown root:www-data /etc/zabbix/web/zabbix.conf.php
|
|
run_cmd chmod 0640 /etc/zabbix/web/zabbix.conf.php
|
|
|
|
if [[ -f /etc/zabbix/nginx.conf ]]; then
|
|
sed -i -E "s|^[[:space:]]*#[[:space:]]*listen[[:space:]]+8080;| listen ${ZABBIX_WEB_PORT};|" /etc/zabbix/nginx.conf
|
|
sed -i -E "s|^[[:space:]]*#[[:space:]]*server_name[[:space:]]+example.com;| server_name _;|" /etc/zabbix/nginx.conf
|
|
fi
|
|
|
|
run_cmd ln -sf /etc/zabbix/nginx.conf /etc/nginx/conf.d/zabbix.conf
|
|
}
|
|
|
|
detect_php_fpm_service() {
|
|
systemctl list-unit-files 'php*-fpm.service' --no-legend | awk '{print $1}' | head -n 1
|
|
}
|
|
|
|
configure_grafana() {
|
|
log "Configurando Grafana"
|
|
run_cmd install -d -m 0755 /etc/grafana/provisioning/datasources
|
|
run_cmd install -d -m 0755 /etc/grafana/provisioning/dashboards
|
|
run_cmd install -d -m 0755 "${GRAFANA_DASHBOARD_DIR}"
|
|
run_cmd install -d -o grafana -g grafana -m 0755 /var/lib/grafana/plugins
|
|
|
|
if [[ "${GRAFANA_INSTALL_ZABBIX_PLUGIN}" == "1" ]]; then
|
|
if [[ "${DRY_RUN}" == "1" ]]; then
|
|
printf '[dry-run] grafana cli --homepath /usr/share/grafana --pluginsDir /var/lib/grafana/plugins plugins install alexanderzobnin-zabbix-app\n'
|
|
else
|
|
grafana cli --homepath /usr/share/grafana --pluginsDir /var/lib/grafana/plugins plugins install alexanderzobnin-zabbix-app || true
|
|
fi
|
|
fi
|
|
|
|
cat >/etc/grafana/provisioning/datasources/zabbix.yaml <<EOF
|
|
apiVersion: 1
|
|
datasources:
|
|
- name: Zabbix
|
|
type: alexanderzobnin-zabbix-datasource
|
|
access: proxy
|
|
url: http://127.0.0.1:${ZABBIX_WEB_PORT}/api_jsonrpc.php
|
|
isDefault: true
|
|
jsonData:
|
|
username: ${ZABBIX_ADMIN_USERNAME}
|
|
trends: true
|
|
timeout: 30
|
|
secureJsonData:
|
|
password: ${ZABBIX_ADMIN_PASSWORD}
|
|
EOF
|
|
|
|
run_cmd install -d -m 0755 /etc/systemd/system/grafana-server.service.d
|
|
cat >/etc/systemd/system/grafana-server.service.d/override.conf <<EOF
|
|
[Service]
|
|
Environment=GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USER}
|
|
Environment=GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}
|
|
Environment=GF_SERVER_HTTP_PORT=${GRAFANA_PORT}
|
|
EOF
|
|
|
|
cat >/etc/grafana/provisioning/dashboards/vfire.yaml <<EOF
|
|
apiVersion: 1
|
|
providers:
|
|
- name: V-Fire
|
|
orgId: 1
|
|
folder: V-Fire
|
|
type: file
|
|
disableDeletion: false
|
|
updateIntervalSeconds: 30
|
|
allowUiUpdates: true
|
|
options:
|
|
path: ${GRAFANA_DASHBOARD_DIR}
|
|
EOF
|
|
|
|
run_cmd cp "${APP_HOME}/dashboards/grafana/vfire-floorplan-starter.json" "${GRAFANA_DASHBOARD_DIR}/vfire-floorplan-starter.json"
|
|
run_cmd chown -R grafana:grafana "${GRAFANA_DASHBOARD_DIR}"
|
|
run_cmd chown -R grafana:grafana /var/lib/grafana/plugins
|
|
}
|
|
|
|
restart_core_services() {
|
|
local php_fpm_service
|
|
php_fpm_service="$(detect_php_fpm_service)"
|
|
[[ -n "${php_fpm_service}" ]] || die "Servico php-fpm nao encontrado."
|
|
|
|
run_cmd systemctl daemon-reload
|
|
run_cmd systemctl enable --now "${php_fpm_service}"
|
|
run_cmd systemctl enable --now nginx
|
|
run_cmd systemctl restart nginx
|
|
|
|
run_cmd systemctl enable --now zabbix-server zabbix-agent2
|
|
run_cmd systemctl restart zabbix-server zabbix-agent2
|
|
}
|
|
|
|
wait_for_http() {
|
|
local url="$1"
|
|
local attempts="${2:-60}"
|
|
local delay="${3:-2}"
|
|
local attempt
|
|
[[ "${DRY_RUN}" == "1" ]] && return 0
|
|
for attempt in $(seq 1 "${attempts}"); do
|
|
if curl -fsS "${url}" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
sleep "${delay}"
|
|
done
|
|
die "Servico indisponivel em ${url}"
|
|
}
|
|
|
|
zabbix_api_request() {
|
|
local payload="$1"
|
|
local auth_token="${2:-}"
|
|
local curl_args=(-fsS -H 'Content-Type: application/json-rpc')
|
|
[[ "${DRY_RUN}" == "1" ]] && return 0
|
|
if [[ -n "${auth_token}" ]]; then
|
|
curl_args+=(-H "Authorization: Bearer ${auth_token}")
|
|
fi
|
|
curl "${curl_args[@]}" -d "${payload}" "http://127.0.0.1:${ZABBIX_WEB_PORT}/api_jsonrpc.php"
|
|
}
|
|
|
|
zabbix_error_message() {
|
|
local response="$1"
|
|
printf '%s' "${response}" | jq -r '.error.data // .error.message // empty'
|
|
}
|
|
|
|
zabbix_require_success() {
|
|
local response="$1"
|
|
local context="$2"
|
|
local error_message
|
|
error_message="$(zabbix_error_message "${response}")"
|
|
if [[ -n "${error_message}" ]]; then
|
|
die "${context}: ${error_message}"
|
|
fi
|
|
}
|
|
|
|
configure_zabbix_admin_password() {
|
|
local auth_payload auth_response auth_token user_id
|
|
|
|
wait_for_http "http://127.0.0.1:${ZABBIX_WEB_PORT}/"
|
|
|
|
auth_payload='{"jsonrpc":"2.0","method":"user.login","params":{"username":"Admin","password":"zabbix","userData":true},"id":1}'
|
|
auth_response="$(zabbix_api_request "${auth_payload}" || true)"
|
|
auth_token="$(printf '%s' "${auth_response}" | jq -r '.result.sessionid // empty')"
|
|
user_id="$(printf '%s' "${auth_response}" | jq -r '.result.userid // empty')"
|
|
|
|
if [[ -n "${auth_token}" && -n "${user_id}" && "${ZABBIX_ADMIN_PASSWORD}" != "zabbix" ]]; then
|
|
auth_response="$(zabbix_api_request "$(jq -cn \
|
|
--arg userid "${user_id}" \
|
|
--arg current "zabbix" \
|
|
--arg passwd "${ZABBIX_ADMIN_PASSWORD}" \
|
|
'{jsonrpc:"2.0",method:"user.update",params:{userid:$userid,current_passwd:$current,passwd:$passwd},id:1}')" "${auth_token}" || true)"
|
|
zabbix_require_success "${auth_response}" "Falha ao atualizar a senha administrativa do Zabbix"
|
|
return 0
|
|
fi
|
|
|
|
auth_payload="$(jq -cn --arg username "${ZABBIX_ADMIN_USERNAME}" --arg password "${ZABBIX_ADMIN_PASSWORD}" '{jsonrpc:"2.0",method:"user.login",params:{username:$username,password:$password},id:1}')"
|
|
auth_response="$(zabbix_api_request "${auth_payload}" || true)"
|
|
zabbix_require_success "${auth_response}" "Falha ao autenticar na API do Zabbix com as credenciais administrativas configuradas"
|
|
auth_token="$(printf '%s' "${auth_response}" | jq -r '.result // empty')"
|
|
[[ -n "${auth_token}" ]] || die "Nao foi possivel autenticar na API do Zabbix com as credenciais administrativas configuradas."
|
|
}
|
|
|
|
zabbix_login() {
|
|
local response
|
|
response="$(zabbix_api_request "$(jq -cn --arg username "${ZABBIX_ADMIN_USERNAME}" --arg password "${ZABBIX_ADMIN_PASSWORD}" '{jsonrpc:"2.0",method:"user.login",params:{username:$username,password:$password},id:1}')")"
|
|
zabbix_require_success "${response}" "Falha ao obter token da API do Zabbix"
|
|
printf '%s' "${response}" | jq -r '.result // empty'
|
|
}
|
|
|
|
zabbix_get_single_id() {
|
|
local method="$1"
|
|
local filter_key="$2"
|
|
local filter_value="$3"
|
|
local id_key="$4"
|
|
local auth="$5"
|
|
zabbix_api_request "$(jq -cn \
|
|
--arg method "${method}" \
|
|
--arg key "${filter_key}" \
|
|
--arg value "${filter_value}" \
|
|
'{jsonrpc:"2.0",method:$method,params:{output:["'"${id_key}"'"],filter:{($key):[$value]}},id:1}')" "${auth}" \
|
|
| jq -r ".result[0].${id_key} // empty"
|
|
}
|
|
|
|
import_zabbix_template() {
|
|
local auth="$1"
|
|
local source
|
|
[[ -f "${ZABBIX_TEMPLATE_FILE}" ]] || die "Template do Zabbix nao encontrado: ${ZABBIX_TEMPLATE_FILE}"
|
|
source="$(cat "${ZABBIX_TEMPLATE_FILE}")"
|
|
zabbix_api_request "$(jq -cn \
|
|
--arg source "${source}" \
|
|
'{jsonrpc:"2.0",method:"configuration.import",params:{format:"yaml",source:$source,rules:{template_groups:{createMissing:true,updateExisting:true},templates:{createMissing:true,updateExisting:true},discoveryRules:{createMissing:true,updateExisting:true,deleteMissing:false},items:{createMissing:true,updateExisting:true,deleteMissing:false},triggers:{createMissing:true,updateExisting:true,deleteMissing:false},valueMaps:{createMissing:true,updateExisting:true,deleteMissing:false},templateDashboards:{createMissing:true,updateExisting:true,deleteMissing:false}}},id:1}')" "${auth}" >/dev/null
|
|
}
|
|
|
|
ensure_zabbix_host_group() {
|
|
local auth="$1"
|
|
local group_id
|
|
group_id="$(zabbix_get_single_id "hostgroup.get" "name" "${ZABBIX_HOST_GROUP}" "groupid" "${auth}")"
|
|
if [[ -n "${group_id}" ]]; then
|
|
printf '%s' "${group_id}"
|
|
return 0
|
|
fi
|
|
zabbix_api_request "$(jq -cn --arg name "${ZABBIX_HOST_GROUP}" '{jsonrpc:"2.0",method:"hostgroup.create",params:{name:$name},id:1}')" "${auth}" | jq -r '.result.groupids[0]'
|
|
}
|
|
|
|
ensure_zabbix_monitored_host() {
|
|
local auth="$1"
|
|
local group_id="$2"
|
|
local template_id host_id
|
|
|
|
template_id="$(zabbix_get_single_id "template.get" "host" "${ZABBIX_TEMPLATE_NAME}" "templateid" "${auth}")"
|
|
[[ -n "${template_id}" ]] || die "Template do Zabbix nao encontrado apos importacao: ${ZABBIX_TEMPLATE_NAME}"
|
|
|
|
host_id="$(zabbix_get_single_id "host.get" "host" "${ZABBIX_MONITORED_HOST}" "hostid" "${auth}")"
|
|
if [[ -n "${host_id}" ]]; then
|
|
zabbix_api_request "$(jq -cn \
|
|
--arg hostid "${host_id}" \
|
|
--arg host "${ZABBIX_MONITORED_HOST}" \
|
|
--arg group_id "${group_id}" \
|
|
--arg template_id "${template_id}" \
|
|
'{jsonrpc:"2.0",method:"host.update",params:{hostid:$hostid,host:$host,name:$host,status:0,groups:[{groupid:$group_id}],templates:[{templateid:$template_id}]},id:1}')" "${auth}" >/dev/null
|
|
printf '%s' "${host_id}"
|
|
return 0
|
|
fi
|
|
|
|
zabbix_api_request "$(jq -cn \
|
|
--arg host "${ZABBIX_MONITORED_HOST}" \
|
|
--arg group_id "${group_id}" \
|
|
--arg template_id "${template_id}" \
|
|
'{jsonrpc:"2.0",method:"host.create",params:{host:$host,name:$host,status:0,groups:[{groupid:$group_id}],templates:[{templateid:$template_id}],tags:[{tag:"application",value:"vfire-monitor"}]},id:1}')" "${auth}" | jq -r '.result.hostids[0]'
|
|
}
|
|
|
|
seed_vfire_runtime_config() {
|
|
if [[ "${DRY_RUN}" == "1" ]]; then
|
|
printf '[dry-run] seed runtime config at %s/config_nodes.json\n' "${APP_STATE_DIR}"
|
|
return 0
|
|
fi
|
|
|
|
"${APP_VENV}/bin/python" - <<PY
|
|
from pathlib import Path
|
|
import json
|
|
from werkzeug.security import generate_password_hash
|
|
|
|
config_path = Path("${APP_STATE_DIR}") / "config_nodes.json"
|
|
default_password = ${VFM_DEFAULT_PASSWORD@Q}
|
|
hostname_zabbix = ${ZABBIX_MONITORED_HOST@Q}
|
|
zabbix_server = "127.0.0.1"
|
|
|
|
if config_path.exists():
|
|
config = json.loads(config_path.read_text(encoding="utf-8"))
|
|
else:
|
|
config = {
|
|
"web_user": "admin",
|
|
"web_password_hash": generate_password_hash(default_password),
|
|
"nodes": [],
|
|
}
|
|
|
|
config.setdefault("web_user", "admin")
|
|
config.setdefault("web_password_hash", generate_password_hash(default_password))
|
|
config.setdefault("nodes", [])
|
|
config["hostname_zabbix"] = hostname_zabbix
|
|
config["zabbix_server"] = zabbix_server
|
|
|
|
config_path.write_text(json.dumps(config, indent=4, ensure_ascii=False), encoding="utf-8")
|
|
PY
|
|
chown "${APP_USER}:${APP_GROUP}" "${APP_STATE_DIR}/config_nodes.json"
|
|
chmod 0600 "${APP_STATE_DIR}/config_nodes.json"
|
|
}
|
|
|
|
provision_zabbix_objects() {
|
|
local auth group_id host_id
|
|
[[ "${DRY_RUN}" == "1" ]] && return 0
|
|
auth="$(zabbix_login)"
|
|
[[ -n "${auth}" ]] || die "Falha ao autenticar na API do Zabbix."
|
|
import_zabbix_template "${auth}"
|
|
group_id="$(ensure_zabbix_host_group "${auth}")"
|
|
[[ -n "${group_id}" ]] || die "Falha ao criar ou localizar host group do Zabbix."
|
|
host_id="$(ensure_zabbix_monitored_host "${auth}" "${group_id}")"
|
|
[[ -n "${host_id}" ]] || die "Falha ao criar ou localizar host monitorado do Zabbix."
|
|
seed_vfire_runtime_config
|
|
}
|
|
|
|
restart_remaining_services() {
|
|
run_cmd systemctl daemon-reload
|
|
run_cmd systemctl enable --now grafana-server
|
|
run_cmd systemctl restart grafana-server
|
|
|
|
run_cmd systemctl enable --now "${APP_SERVICE}"
|
|
run_cmd systemctl restart "${APP_SERVICE}"
|
|
}
|
|
|
|
write_summary() {
|
|
cat >"${SUMMARY_FILE}" <<EOF
|
|
V-Fire Stack instalado com sucesso.
|
|
|
|
Host: ${PUBLIC_HOSTNAME}
|
|
|
|
V-Fire Monitor
|
|
- URL: http://${PUBLIC_HOSTNAME}:${VFM_APP_PORT}
|
|
- Usuario inicial: admin
|
|
- Senha inicial: ${VFM_DEFAULT_PASSWORD}
|
|
- Servico: ${APP_SERVICE}
|
|
|
|
Zabbix
|
|
- URL: http://${PUBLIC_HOSTNAME}:${ZABBIX_WEB_PORT}
|
|
- Usuario: ${ZABBIX_ADMIN_USERNAME}
|
|
- Senha: ${ZABBIX_ADMIN_PASSWORD}
|
|
- Banco: ${ZABBIX_DB_NAME}
|
|
- Usuario DB: ${ZABBIX_DB_USER}
|
|
- Senha DB: ${ZABBIX_DB_PASSWORD}
|
|
|
|
Grafana
|
|
- URL: http://${PUBLIC_HOSTNAME}:${GRAFANA_PORT}
|
|
- Usuario: ${GRAFANA_ADMIN_USER}
|
|
- Senha: ${GRAFANA_ADMIN_PASSWORD}
|
|
- Datasource provisionado: Zabbix
|
|
- Dashboard provisionado: V-Fire Floorplan Starter
|
|
|
|
Arquivos importantes
|
|
- Codigo app: ${APP_HOME}
|
|
- Estado app: ${APP_STATE_DIR}
|
|
- Variaveis app: ${APP_ENV_FILE}
|
|
- Log app: ${APP_LOG_DIR}/app.log
|
|
- Template Zabbix: ${ZABBIX_TEMPLATE_NAME}
|
|
- Host Zabbix: ${ZABBIX_MONITORED_HOST}
|
|
EOF
|
|
run_cmd chmod 0600 "${SUMMARY_FILE}"
|
|
log "Resumo salvo em ${SUMMARY_FILE}"
|
|
}
|
|
|
|
verify_services_health() {
|
|
[[ "${DRY_RUN}" == "1" ]] && return 0
|
|
wait_for_http "http://127.0.0.1:${VFM_APP_PORT}/healthz"
|
|
wait_for_http "http://127.0.0.1:${ZABBIX_WEB_PORT}/"
|
|
wait_for_http "http://127.0.0.1:${GRAFANA_PORT}/login"
|
|
systemctl is-active --quiet "${APP_SERVICE}" || die "Servico do V-Fire Monitor inativo."
|
|
systemctl is-active --quiet zabbix-server || die "Servico zabbix-server inativo."
|
|
systemctl is-active --quiet grafana-server || die "Servico grafana-server inativo."
|
|
}
|
|
|
|
main() {
|
|
parse_args "$@"
|
|
require_root
|
|
require_supported_os
|
|
load_config
|
|
assert_command_dependencies
|
|
assert_ports_available
|
|
install_system_packages
|
|
deploy_application
|
|
configure_vfire_service
|
|
configure_zabbix
|
|
restart_core_services
|
|
configure_zabbix_admin_password
|
|
provision_zabbix_objects
|
|
configure_grafana
|
|
restart_remaining_services
|
|
verify_services_health
|
|
write_summary
|
|
log "Instalacao concluida"
|
|
}
|
|
|
|
main "$@"
|