Initial commit: 50 shell-script generators with web UI
- 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)
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
"""
|
||||
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())
|
||||
Reference in New Issue
Block a user