Add production installer and observability tooling

This commit is contained in:
2026-05-07 12:11:15 -03:00
parent a19ac9f00c
commit 5d92a6c99a
30 changed files with 3493 additions and 1015 deletions
+640
View File
@@ -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 "$@"