Add production installer and observability tooling
This commit is contained in:
@@ -0,0 +1,640 @@
|
||||
#!/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 curl jq gpg wget rsync python3 psql runuser systemctl ss; do
|
||||
command -v "${cmd}" >/dev/null 2>&1 || die "Comando obrigatorio ausente: ${cmd}"
|
||||
done
|
||||
}
|
||||
|
||||
assert_ports_available() {
|
||||
local port
|
||||
[[ "${DRY_RUN}" == "1" ]] && return 0
|
||||
for port in "${VFM_APP_PORT}" "${ZABBIX_WEB_PORT}" "${GRAFANA_PORT}"; do
|
||||
if ss -ltn "( sport = :${port} )" | tail -n +2 | grep -q .; then
|
||||
die "Porta ${port} ja esta em uso."
|
||||
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 -o %s\n' "${ZABBIX_APT_KEYRING}"
|
||||
else
|
||||
curl -fsSL "https://repo.zabbix.com/zabbix-official-repo.key" | gpg --dearmor -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_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}"
|
||||
|
||||
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}"
|
||||
|
||||
if [[ "${GRAFANA_INSTALL_ZABBIX_PLUGIN}" == "1" ]]; then
|
||||
if [[ "${DRY_RUN}" == "1" ]]; then
|
||||
printf '[dry-run] grafana-cli plugins install alexanderzobnin-zabbix-app\n'
|
||||
else
|
||||
grafana-cli 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}"
|
||||
}
|
||||
|
||||
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"
|
||||
[[ "${DRY_RUN}" == "1" ]] && return 0
|
||||
curl -fsS -H 'Content-Type: application/json-rpc' -d "${payload}" "http://127.0.0.1:${ZABBIX_WEB_PORT}/api_jsonrpc.php"
|
||||
}
|
||||
|
||||
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
|
||||
zabbix_api_request "$(jq -cn \
|
||||
--arg auth "${auth_token}" \
|
||||
--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},auth:$auth,id:1}')" >/dev/null
|
||||
zabbix_api_request "$(jq -cn --arg auth "${auth_token}" '{jsonrpc:"2.0",method:"user.logout",params:[],auth:$auth,id:1}')" >/dev/null || true
|
||||
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)"
|
||||
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_api_request "$(jq -cn --arg auth "${auth_token}" '{jsonrpc:"2.0",method:"user.logout",params:[],auth:$auth,id:1}')" >/dev/null || true
|
||||
}
|
||||
|
||||
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}')")"
|
||||
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}" \
|
||||
--arg auth "${auth}" \
|
||||
'{jsonrpc:"2.0",method:$method,params:{output:["'"${id_key}"'"],filter:{($key):[$value]}},auth:$auth,id:1}')" \
|
||||
| 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 auth "${auth}" \
|
||||
--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}}},auth:$auth,id:1}')" >/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 auth "${auth}" --arg name "${ZABBIX_HOST_GROUP}" '{jsonrpc:"2.0",method:"hostgroup.create",params:{name:$name},auth:$auth,id:1}')" | 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 auth "${auth}" \
|
||||
--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}]},auth:$auth,id:1}')" >/dev/null
|
||||
printf '%s' "${host_id}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
zabbix_api_request "$(jq -cn \
|
||||
--arg auth "${auth}" \
|
||||
--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"}]},auth:$auth,id:1}')" | 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."
|
||||
zabbix_api_request "$(jq -cn --arg auth "${auth}" '{jsonrpc:"2.0",method:"user.logout",params:[],auth:$auth,id:1}')" >/dev/null || true
|
||||
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 "$@"
|
||||
Reference in New Issue
Block a user