319f683a16
- 8 categories / 50 generators covering middleware, databases, runtimes, systemd services, system tools, network, monitoring, security - VNC supports XFCE / GNOME / KDE Plasma / MATE / LXQt desktops - Node.js versions 18-26 (incl. current LTS 24 Krypton and current 26) - Live preview, copy / download / multi-script bundle in web UI - Distro-aware (Ubuntu/Debian/CentOS/RHEL/Rocky/Alma/Fedora) - All 50 generators pass bash -n syntax check - Zero pip dependencies (Flask stdlib only)
227 lines
11 KiB
Python
227 lines
11 KiB
Python
"""
|
|
Monitoring & alerting.
|
|
- Prometheus Node Exporter
|
|
- Prometheus server
|
|
- Grafana
|
|
- node_exporter + alertmanager bundle
|
|
- logrotate helper
|
|
"""
|
|
from . import register, Generator, Field
|
|
from . import bash_header, quote, bool_str, yes
|
|
|
|
|
|
# ========================== Node Exporter ==========================
|
|
class NodeExporter(Generator):
|
|
id = "node_exporter"
|
|
title = "Prometheus Node Exporter"
|
|
category = "monitoring"
|
|
icon = "📊"
|
|
tags = ["prometheus", "node_exporter", "monitoring"]
|
|
description = "安装 Prometheus node_exporter,默认监听 9100。"
|
|
fields = [
|
|
Field("version", "版本", "text", default="1.8.2"),
|
|
Field("port", "监听端口", "number", default="9100", min_=1, max_=65535),
|
|
Field("extra_args", "额外参数", "text",
|
|
default="--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)"),
|
|
]
|
|
|
|
def render(self, p):
|
|
ver = p.get("version", "1.8.2")
|
|
port = str(p.get("port", "9100"))
|
|
extra = p.get("extra_args", "")
|
|
out = [bash_header(self.title)]
|
|
out.append('log "Installing node_exporter ' + ver + '..."')
|
|
out.append('useradd -r -s /bin/false node_exporter 2>/dev/null || true')
|
|
out.append('cd /tmp')
|
|
out.append('curl -fsSL -O https://github.com/prometheus/node_exporter/releases/download/v' + ver + '/node_exporter-' + ver + '.linux-amd64.tar.gz')
|
|
out.append('tar -xzf node_exporter-' + ver + '.linux-amd64.tar.gz')
|
|
out.append('cp node_exporter-' + ver + '.linux-amd64/node_exporter /usr/local/bin/')
|
|
out.append('rm -rf node_exporter-' + ver + '.linux-amd64*')
|
|
out.append('cat > /etc/systemd/system/node_exporter.service <<UNIT_EOF\n'
|
|
'[Unit]\nDescription=Prometheus Node Exporter\n'
|
|
'After=network.target\n\n'
|
|
'[Service]\n'
|
|
'User=node_exporter\n'
|
|
'ExecStart=/usr/local/bin/node_exporter --web.listen-address=:' + port + ' ' + extra + '\n'
|
|
'Restart=on-failure\n\n'
|
|
'[Install]\nWantedBy=multi-user.target\nUNIT_EOF')
|
|
out.append('systemctl daemon-reload')
|
|
out.append('systemctl enable --now node_exporter')
|
|
out.append('sleep 1')
|
|
out.append('curl -s http://127.0.0.1:' + port + '/metrics | head -5')
|
|
return "\n".join(out) + "\n"
|
|
|
|
|
|
# ========================== Prometheus Server ==========================
|
|
class Prometheus(Generator):
|
|
id = "prometheus"
|
|
title = "Prometheus 服务端"
|
|
category = "monitoring"
|
|
icon = "🔥"
|
|
tags = ["prometheus", "monitoring", "metrics"]
|
|
description = "安装 Prometheus,带可配置 scrape jobs。"
|
|
fields = [
|
|
Field("version", "版本", "text", default="2.53.0"),
|
|
Field("port", "监听端口", "number", default="9090", min_=1, max_=65535),
|
|
Field("retention", "保留时间", "text", default="30d"),
|
|
Field("scrape_jobs", "Scrape 目标", "textarea",
|
|
default="node-exporter: ['localhost:9100']\nmyapp: ['localhost:8000']",
|
|
help="每行格式: <job_name>: ['host:port', ...]"),
|
|
]
|
|
|
|
def render(self, p):
|
|
ver = p.get("version", "2.53.0")
|
|
port = str(p.get("port", "9090"))
|
|
ret = p.get("retention", "30d")
|
|
lines = p.get("scrape_jobs", "").splitlines()
|
|
out = [bash_header(self.title)]
|
|
out.append('log "Installing Prometheus ' + ver + '..."')
|
|
out.append('useradd -r -s /bin/false prometheus 2>/dev/null || true')
|
|
out.append('mkdir -p /etc/prometheus /var/lib/prometheus')
|
|
out.append('cd /tmp')
|
|
out.append('curl -fsSL -O https://github.com/prometheus/prometheus/releases/download/v' + ver + '/prometheus-' + ver + '.linux-amd64.tar.gz')
|
|
out.append('tar -xzf prometheus-' + ver + '.linux-amd64.tar.gz')
|
|
out.append('cp prometheus-' + ver + '.linux-amd64/prometheus /usr/local/bin/')
|
|
out.append('cp prometheus-' + ver + '.linux-amd64/promtool /usr/local/bin/')
|
|
out.append('cp -r prometheus-' + ver + '.linux-amd64/console_libraries /etc/prometheus/')
|
|
out.append('cp -r prometheus-' + ver + '.linux-amd64/consoles /etc/prometheus/')
|
|
out.append('rm -rf prometheus-' + ver + '.linux-amd64*')
|
|
out.append('chown -R prometheus:prometheus /etc/prometheus /var/lib/prometheus')
|
|
out.append('')
|
|
cfg = ['global:', ' scrape_interval: 15s', ' evaluation_interval: 15s',
|
|
'', 'rule_files:', ' - "rules/*.yml"', '', 'scrape_configs:',
|
|
' - job_name: prometheus', ' static_configs:',
|
|
' - targets: [\'localhost:' + port + '\']']
|
|
for line in lines:
|
|
if ':' not in line:
|
|
continue
|
|
name, targets = line.split(':', 1)
|
|
name = name.strip()
|
|
tg = [t.strip().strip("'").strip('"') for t in
|
|
targets.strip().lstrip('[').rstrip(']').split(',') if t.strip()]
|
|
cfg.append(f' - job_name: {name}')
|
|
cfg.append(' static_configs:')
|
|
cfg.append(' - targets: ' + str(tg).replace("'", '"'))
|
|
out.append('cat > /etc/prometheus/prometheus.yml <<CFG_EOF\n'
|
|
+ '\n'.join(cfg) + '\nCFG_EOF')
|
|
out.append('cat > /etc/systemd/system/prometheus.service <<UNIT_EOF\n'
|
|
'[Unit]\nDescription=Prometheus\nAfter=network.target\n\n'
|
|
'[Service]\nUser=prometheus\nGroup=prometheus\n'
|
|
'Type=simple\n'
|
|
'ExecStart=/usr/local/bin/prometheus \\\n'
|
|
' --config.file=/etc/prometheus/prometheus.yml \\\n'
|
|
' --storage.tsdb.path=/var/lib/prometheus/ \\\n'
|
|
' --web.console.libraries=/etc/prometheus/console_libraries \\\n'
|
|
' --web.console.templates=/etc/prometheus/consoles \\\n'
|
|
' --web.listen-address=:' + port + ' \\\n'
|
|
' --storage.tsdb.retention.time=' + ret + '\n'
|
|
'Restart=on-failure\n\n'
|
|
'[Install]\nWantedBy=multi-user.target\nUNIT_EOF')
|
|
out.append('systemctl daemon-reload')
|
|
out.append('systemctl enable --now prometheus')
|
|
out.append('sleep 2')
|
|
out.append('curl -s http://127.0.0.1:' + port + '/-/ready')
|
|
return "\n".join(out) + "\n"
|
|
|
|
|
|
# ========================== Grafana ==========================
|
|
class Grafana(Generator):
|
|
id = "grafana"
|
|
title = "Grafana"
|
|
category = "monitoring"
|
|
icon = "📈"
|
|
tags = ["grafana", "dashboard", "monitoring"]
|
|
description = "安装 Grafana OSS,配置 admin 密码。"
|
|
fields = [
|
|
Field("admin_user", "Admin 用户", "text", default="admin"),
|
|
Field("admin_password", "Admin 密码", "password", default="admin"),
|
|
Field("port", "端口", "number", default="3000", min_=1, max_=65535),
|
|
Field("prom_url", "Prometheus 数据源 URL", "text", default="http://localhost:9090"),
|
|
Field("root_url", "Grafana 公开 URL", "text", default="http://localhost:3000"),
|
|
]
|
|
|
|
def render(self, p):
|
|
user = p.get("admin_user", "admin")
|
|
pwd = p.get("admin_password", "admin")
|
|
port = str(p.get("port", "3000"))
|
|
prom = p.get("prom_url", "http://localhost:9090")
|
|
root = p.get("root_url", "http://localhost:3000")
|
|
out = [bash_header(self.title)]
|
|
out.append('log "Installing Grafana..."')
|
|
out.append('case "$PKG" in\n'
|
|
' apt-get)\n'
|
|
' $PKG_INSTALL -y software-properties-common\n'
|
|
' mkdir -p /etc/apt/keyrings\n'
|
|
' wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | tee /etc/apt/keyrings/grafana.gpg > /dev/null\n'
|
|
' echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" > /etc/apt/sources.list.d/grafana.list\n'
|
|
' apt-get update\n'
|
|
' $PKG_INSTALL grafana\n'
|
|
' ;;\n'
|
|
' yum|dnf)\n'
|
|
' cat > /etc/yum.repos.d/grafana.repo <<REPO_EOF\n'
|
|
'[grafana]\nname=grafana\nbaseurl=https://rpm.grafana.com\nrepo=gpgkey\nenabled=1\ngpgcheck=1\ngpgkey=https://rpm.grafana.com/gpg.key\nREPO_EOF\n'
|
|
' $PKG_INSTALL grafana\n'
|
|
' ;;\n'
|
|
'esac')
|
|
out.append('systemctl enable --now grafana-server')
|
|
out.append('sleep 3')
|
|
out.append('log "Setting admin password..."')
|
|
out.append('grafana-cli admin reset-admin-password ' + pwd + ' || warn "reset failed, use UI"')
|
|
out.append('log "Adding Prometheus datasource..."')
|
|
out.append('cat > /etc/grafana/provisioning/datasources/prometheus.yaml <<YAML_EOF\n'
|
|
'apiVersion: 1\ndatasources:\n - name: Prometheus\n'
|
|
' type: prometheus\n access: proxy\n url: ' + prom + '\n'
|
|
' isDefault: true\nYAML_EOF')
|
|
out.append('systemctl restart grafana-server')
|
|
out.append('log "Open: ' + root + ' user: ' + user + ' pass: ' + pwd + '"')
|
|
return "\n".join(out) + "\n"
|
|
|
|
|
|
# ========================== Logrotate (generic) ==========================
|
|
class Logrotate(Generator):
|
|
id = "logrotate"
|
|
title = "Logrotate 日志轮转"
|
|
category = "monitoring"
|
|
icon = "🔄"
|
|
tags = ["logrotate", "log"]
|
|
description = "为指定日志文件生成 logrotate 配置。"
|
|
fields = [
|
|
Field("paths", "日志路径 (空格分隔)", "textarea", default="/var/log/myapp/*.log"),
|
|
Field("rotate_count", "保留份数", "number", default="14", min_=1, max_=365),
|
|
Field("size", "超过该大小就轮转", "text", default="100M",
|
|
help="如 100M / 1G,留空 = 仅按时间。"),
|
|
Field("compress", "启用压缩", "checkbox", default="yes"),
|
|
Field("user", "轮转用户", "text", default="root"),
|
|
Field("group", "轮转组", "text", default="root"),
|
|
Field("postrotate", "Postrotate 脚本", "text", default=""),
|
|
]
|
|
|
|
def render(self, p):
|
|
paths = p.get("paths", "/var/log/myapp/*.log").replace("\n", " ")
|
|
n = str(p.get("rotate_count", "14"))
|
|
sz = p.get("size", "100M")
|
|
comp = bool_str(p.get("compress", True))
|
|
u = p.get("user", "root")
|
|
g = p.get("group", "root")
|
|
pr = p.get("postrotate", "").strip()
|
|
out = [bash_header(self.title)]
|
|
logrot = [paths + ' {', ' daily', ' missingok', ' rotate ' + n]
|
|
if sz:
|
|
logrot.append(' size ' + sz)
|
|
logrot.append(' compress' if comp else ' nocompress')
|
|
if comp:
|
|
logrot.append(' delaycompress')
|
|
logrot.append(' notifempty')
|
|
logrot.append(' create 0640 ' + u + ' ' + g)
|
|
logrot.append(' sharedscripts')
|
|
if pr:
|
|
logrot.append(' postrotate\n ' + pr + '\n endscript')
|
|
logrot.append('}')
|
|
out.append('cat > /etc/logrotate.d/99-shellgen <<CFG_EOF\n' + '\n'.join(logrot) + '\nCFG_EOF')
|
|
out.append('logrotate -d /etc/logrotate.d/99-shellgen')
|
|
return "\n".join(out) + "\n"
|
|
|
|
|
|
for _g in [NodeExporter, Prometheus, Grafana, Logrotate]:
|
|
register(_g())
|