diff --git a/README.md b/README.md index e320ce9..392b357 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ --- -## 🧩 支持组件(44 个) +## 🧩 支持组件(59 个) | 分类 | 组件 | 输出格式 | 核心配置项 | |------|------|----------|------------| @@ -85,6 +85,21 @@ | 日志监控 | **Syslog** | `.conf` | UDP/TCP/RELP 协议、远程日志、按主机分目录、速率限制 | | 自动化运维 | **Ansible** | `.cfg` | SSH 连接、Pipelining、Become 提权、Forks、事实缓存 | | 自动化运维 | **Ansible Playbook** | `.yaml` | 变量、包管理、服务管理、文件复制、模板渲染、Handlers | +| 系统服务 | **NTP** | `.conf` | NTP 服务器、认证、轮询间隔、drift 文件 | +| 系统服务 | **DHCP** | `.conf` | 子网、地址范围、租约时间、网关、静态绑定 | +| 系统服务 | **BIND (DNS)** | `.conf` | 监听地址、转发器、递归、DNSSEC、Zone 配置 | +| 系统服务 | **NFS** | `.exports` | 共享路径、客户端地址、权限、root_squash、NFSv4 | +| 系统服务 | **SAMBA** | `.conf` | 工作组、共享目录、权限、用户认证、日志 | +| 系统服务 | **RSYNC** | `.conf` | 模块、路径、认证、主机白名单 | +| 系统服务 | **Sersync** | `.xml` | 监控目录、远程同步目标、过滤规则、定时触发 | +| 系统服务 | **vsftpd** | `.conf` | 被动模式、TLS、用户限制、chroot、日志 | +| 系统服务 | **Subversion** | `.conf` | 匿名访问、认证、SASL、Hook 脚本 | +| 数据库中间件 | **Mycat** | `.xml` | 端口、字符集、连接数、事务隔离、认证 | +| CI/CD | **GitLab** | `.rb` | 外部 URL、SSL、数据库、Redis、Registry、SMTP | +| CI/CD | **Jenkins** | `.yaml` | 端口、执行器、LDAP、Casc、Git 配置 | +| 高可用 | **Keepalived** | `.conf` | VRRP 实例、VIP、优先级、健康检查、通知脚本 | +| 高可用 | **HAProxy** | `.cfg` | 全局配置、前后端、负载均衡算法、SSL、统计页面 | +| 高可用 | **LVS** | `.conf` | 调度算法、VIP、Real Server、DR/NAT/TUN 模式 | --- diff --git a/confTemplate.tar.gz b/confTemplate.tar.gz new file mode 100644 index 0000000..e37e975 Binary files /dev/null and b/confTemplate.tar.gz differ diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue index c71adca..56c10c1 100644 --- a/src/components/Sidebar.vue +++ b/src/components/Sidebar.vue @@ -60,7 +60,7 @@ diff --git a/src/schemas/bind.js b/src/schemas/bind.js new file mode 100644 index 0000000..1dac6ce --- /dev/null +++ b/src/schemas/bind.js @@ -0,0 +1,252 @@ +export const bindSchema = { + id: 'bind', + name: 'BIND (DNS)', + icon: 'Connection', + category: '系统服务', + description: 'BIND DNS 服务器配置', + format: 'conf', + fileName: 'named.conf', + groups: [ + { + title: '监听与查询', + fields: [ + { + key: 'listenOn', + label: 'IPv4 监听地址', + type: 'text', + default: '127.0.0.1; any', + tip: 'IPv4 监听地址列表', + }, + { + key: 'listenOnV6', + label: 'IPv6 监听地址', + type: 'text', + default: '::1; any', + tip: 'IPv6 监听地址列表', + }, + { + key: 'allowQuery', + label: '允许查询', + type: 'text', + default: 'localhost; any', + tip: '允许哪些客户端查询', + }, + { + key: 'allowRecursion', + label: '允许递归', + type: 'text', + default: 'localhost; localnets', + tip: '允许哪些客户端进行递归查询', + }, + { + key: 'recursion', + label: '启用递归', + type: 'switch', + default: true, + }, + ], + }, + { + title: '转发配置', + fields: [ + { + key: 'forwarders', + label: '转发服务器', + type: 'text', + default: '8.8.8.8; 8.8.4.4', + tip: '上游 DNS 服务器地址', + }, + { + key: 'forwardType', + label: '转发策略', + type: 'select', + options: [ + { label: 'first - 先转发再自行解析', value: 'first' }, + { label: 'only - 仅转发不自行解析', value: 'only' }, + ], + default: 'first', + }, + ], + }, + { + title: '日志配置', + fields: [ + { + key: 'enableLogging', + label: '启用日志', + type: 'switch', + default: true, + }, + { + key: 'logCategory', + label: '日志类别', + type: 'text', + default: 'queries', + dependsOn: { key: 'enableLogging', value: true }, + tip: 'BIND 日志类别名称', + }, + { + key: 'logFile', + label: '日志文件路径', + type: 'text', + default: '/var/log/named/queries.log', + dependsOn: { key: 'enableLogging', value: true }, + }, + ], + }, + { + title: 'DNSSEC', + fields: [ + { + key: 'enableDnssec', + label: '启用 DNSSEC', + type: 'switch', + default: true, + tip: '启用 DNS 安全扩展', + }, + { + key: 'dnssecValidation', + label: 'DNSSEC 验证', + type: 'switch', + default: true, + tip: '对查询结果进行 DNSSEC 验证', + }, + ], + }, + { + title: '区域配置', + fields: [ + { + key: 'enableZone', + label: '启用自定义区域', + type: 'switch', + default: false, + tip: '配置本地 DNS 区域', + }, + { + key: 'zoneName', + label: '区域名称', + type: 'text', + default: 'example.com', + dependsOn: { key: 'enableZone', value: true }, + }, + { + key: 'zoneType', + label: '区域类型', + type: 'select', + options: [ + { label: 'master - 主区域', value: 'master' }, + { label: 'slave - 从区域', value: 'slave' }, + { label: 'hint - 根提示', value: 'hint' }, + ], + default: 'master', + dependsOn: { key: 'enableZone', value: true }, + }, + { + key: 'zoneFile', + label: '区域文件路径', + type: 'text', + default: '/var/named/example.com.zone', + dependsOn: { key: 'enableZone', value: true }, + }, + ], + }, + ], +} + +export function generateBindConf(config) { + const lines = [] + lines.push(`// BIND DNS 配置文件 - 由 ConfTemplate 生成`) + lines.push(`// 生成时间: ${new Date().toLocaleString('zh-CN')}`) + lines.push(``) + + // Options block + lines.push(`// ======================== 全局选项 ========================`) + lines.push(`options {`) + lines.push(` listen-on port 53 { ${config.listenOn} };`) + lines.push(` listen-on-v6 port 53 { ${config.listenOnV6} };`) + lines.push(` directory "/var/named";`) + lines.push(` dump-file "/var/named/data/cache_dump.db";`) + lines.push(` statistics-file "/var/named/data/named_stats.txt";`) + lines.push(` memstatistics-file "/var/named/data/named_mem_stats.txt";`) + lines.push(` allow-query { ${config.allowQuery} };`) + lines.push(` allow-recursion { ${config.allowRecursion} };`) + lines.push(` recursion ${config.recursion ? 'yes' : 'no'};`) + lines.push(``) + lines.push(` forwarders {`) + const forwarders = config.forwarders.split(';').map((s) => s.trim()).filter(Boolean) + forwarders.forEach((f) => { + lines.push(` ${f};`) + }) + lines.push(` };`) + lines.push(` forward ${config.forwardType};`) + lines.push(``) + + if (config.enableDnssec) { + lines.push(` dnssec-enable yes;`) + } + if (config.dnssecValidation) { + lines.push(` dnssec-validation yes;`) + } + lines.push(``) + lines.push(` pid-file "/run/named/named.pid";`) + lines.push(` session-keyfile "/run/named/session.key";`) + lines.push(`};`) + lines.push(``) + + // Logging + if (config.enableLogging) { + lines.push(`// ======================== 日志配置 ========================`) + lines.push(`logging {`) + lines.push(` channel default_debug {`) + lines.push(` file "data/named.run";`) + lines.push(` severity dynamic;`) + lines.push(` };`) + lines.push(` channel query_log {`) + lines.push(` file "${config.logFile}" versions 3 size 5m;`) + lines.push(` severity dynamic;`) + lines.push(` print-time yes;`) + lines.push(` print-category yes;`) + lines.push(` };`) + lines.push(` category ${config.logCategory} { query_log; };`) + lines.push(`};`) + lines.push(``) + } + + // Zone configuration + lines.push(`// ======================== 标准区域 ========================`) + lines.push(`zone "." IN {`) + lines.push(` type hint;`) + lines.push(` file "named.ca";`) + lines.push(`};`) + lines.push(``) + lines.push(`zone "localhost" IN {`) + lines.push(` type master;`) + lines.push(` file "named.localhost";`) + lines.push(` allow-update { none; };`) + lines.push(`};`) + lines.push(``) + lines.push(`zone "0.0.127.in-addr.arpa" IN {`) + lines.push(` type master;`) + lines.push(` file "named.loopback";`) + lines.push(` allow-update { none; };`) + lines.push(`};`) + lines.push(``) + + if (config.enableZone) { + lines.push(`// ======================== 自定义区域 ========================`) + lines.push(`zone "${config.zoneName}" IN {`) + lines.push(` type ${config.zoneType};`) + lines.push(` file "${config.zoneFile}";`) + if (config.zoneType === 'master') { + lines.push(` allow-update { none; };`) + } + lines.push(`};`) + lines.push(``) + } + + lines.push(`include "/etc/named.rfc1912.zones";`) + lines.push(`include "/etc/named.root.key";`) + + return lines.join('\n') +} diff --git a/src/schemas/dhcp.js b/src/schemas/dhcp.js new file mode 100644 index 0000000..2785d59 --- /dev/null +++ b/src/schemas/dhcp.js @@ -0,0 +1,169 @@ +export const dhcpSchema = { + id: 'dhcp', + name: 'DHCP', + icon: 'Connection', + category: '系统服务', + description: 'ISC DHCP 服务器配置', + format: 'conf', + fileName: 'dhcpd.conf', + groups: [ + { + title: '全局参数', + fields: [ + { + key: 'domainName', + label: '域名', + type: 'text', + default: 'example.com', + tip: '分配给客户端的域名', + }, + { + key: 'domainNameServers', + label: 'DNS 服务器', + type: 'text', + default: '8.8.8.8, 8.8.4.4', + tip: '多个用逗号分隔', + }, + { + key: 'defaultLeaseTime', + label: '默认租约时间 (秒)', + type: 'number', + min: 60, + max: 86400, + default: 600, + }, + { + key: 'maxLeaseTime', + label: '最大租约时间 (秒)', + type: 'number', + min: 60, + max: 604800, + default: 7200, + }, + ], + }, + { + title: '子网配置', + fields: [ + { + key: 'subnet', + label: '子网地址', + type: 'text', + default: '192.168.1.0', + tip: 'DHCP 分配的子网', + }, + { + key: 'netmask', + label: '子网掩码', + type: 'text', + default: '255.255.255.0', + }, + { + key: 'rangeStart', + label: '地址池起始', + type: 'text', + default: '192.168.1.100', + tip: '动态分配范围起始 IP', + }, + { + key: 'rangeEnd', + label: '地址池结束', + type: 'text', + default: '192.168.1.200', + tip: '动态分配范围结束 IP', + }, + { + key: 'gateway', + label: '默认网关', + type: 'text', + default: '192.168.1.1', + }, + { + key: 'broadcastAddress', + label: '广播地址', + type: 'text', + default: '192.168.1.255', + }, + ], + }, + { + title: '静态主机绑定', + fields: [ + { + key: 'enableStaticHost', + label: '启用静态主机', + type: 'switch', + default: false, + tip: '为特定 MAC 地址分配固定 IP', + }, + { + key: 'staticHostName', + label: '主机名', + type: 'text', + default: 'myhost', + dependsOn: { key: 'enableStaticHost', value: true }, + }, + { + key: 'staticHostMac', + label: 'MAC 地址', + type: 'text', + default: '00:11:22:33:44:55', + dependsOn: { key: 'enableStaticHost', value: true }, + tip: '客户端 MAC 地址', + }, + { + key: 'staticHostIp', + label: '固定 IP 地址', + type: 'text', + default: '192.168.1.50', + dependsOn: { key: 'enableStaticHost', value: true }, + }, + ], + }, + ], +} + +export function generateDhcpConf(config) { + const lines = [] + lines.push(`# DHCP 服务器配置文件 - 由 ConfTemplate 生成`) + lines.push(`# 生成时间: ${new Date().toLocaleString('zh-CN')}`) + lines.push(``) + + // Global parameters + lines.push(`# ======================== 全局参数 ========================`) + lines.push(`option domain-name "${config.domainName}";`) + const dnsServers = config.domainNameServers.split(',').map((s) => s.trim()).join(', ') + lines.push(`option domain-name-servers ${dnsServers};`) + lines.push(``) + lines.push(`default-lease-time ${config.defaultLeaseTime};`) + lines.push(`max-lease-time ${config.maxLeaseTime};`) + lines.push(``) + lines.push(`# 如果本机不是 DHCP 服务器的 authoritative,取消注释下行`) + lines.push(`# authoritative;`) + lines.push(``) + + // Subnet configuration + lines.push(`# ======================== 子网配置 ========================`) + lines.push(`subnet ${config.subnet} netmask ${config.netmask} {`) + lines.push(` range ${config.rangeStart} ${config.rangeEnd};`) + lines.push(` option routers ${config.gateway};`) + lines.push(` option broadcast-address ${config.broadcastAddress};`) + lines.push(` option domain-name-servers ${dnsServers};`) + lines.push(` option domain-name "${config.domainName}";`) + lines.push(` default-lease-time ${config.defaultLeaseTime};`) + lines.push(` max-lease-time ${config.maxLeaseTime};`) + lines.push(`}`) + lines.push(``) + + // Static host + if (config.enableStaticHost) { + lines.push(`# ======================== 静态主机绑定 ========================`) + lines.push(`host ${config.staticHostName} {`) + lines.push(` hardware ethernet ${config.staticHostMac};`) + lines.push(` fixed-address ${config.staticHostIp};`) + lines.push(`}`) + lines.push(``) + } + + return lines.join('\n') +} diff --git a/src/schemas/gitlab.js b/src/schemas/gitlab.js new file mode 100644 index 0000000..ebfdd1f --- /dev/null +++ b/src/schemas/gitlab.js @@ -0,0 +1,325 @@ +export const gitlabSchema = { + id: 'gitlab', + name: 'GitLab', + icon: 'Files', + category: 'CI/CD', + description: 'DevOps 生命周期平台,提供代码托管、CI/CD、制品管理等', + format: 'rb', + fileName: 'gitlab.rb', + groups: [ + { + title: '基础配置', + fields: [ + { + key: 'externalUrl', + label: '外部访问 URL', + type: 'text', + placeholder: 'http://gitlab.example.com', + default: 'http://gitlab.example.com', + required: true, + tip: 'GitLab 实例的外部访问地址', + }, + ], + }, + { + title: 'Nginx 配置', + fields: [ + { + key: 'nginxListenPort', + label: 'Nginx 监听端口', + type: 'number', + min: 1, + max: 65535, + default: 80, + }, + { + key: 'nginxEnableSsl', + label: '启用 SSL', + type: 'switch', + default: false, + tip: '为 Nginx 启用 HTTPS', + }, + { + key: 'sslCertificate', + label: 'SSL 证书路径', + type: 'text', + default: '/etc/gitlab/ssl/gitlab.crt', + dependsOn: { key: 'nginxEnableSsl', value: true }, + required: true, + }, + { + key: 'sslCertificateKey', + label: 'SSL 私钥路径', + type: 'text', + default: '/etc/gitlab/ssl/gitlab.key', + dependsOn: { key: 'nginxEnableSsl', value: true }, + required: true, + }, + ], + }, + { + title: '数据库配置', + fields: [ + { + key: 'gitlabRbDbAdapter', + label: '数据库类型', + type: 'select', + options: [ + { label: 'PostgreSQL', value: 'postgresql' }, + { label: 'MySQL', value: 'mysql' }, + ], + default: 'postgresql', + }, + { + key: 'gitlabRbDbHost', + label: '数据库主机', + type: 'text', + default: '127.0.0.1', + }, + { + key: 'gitlabRbDbPort', + label: '数据库端口', + type: 'number', + min: 1, + max: 65535, + default: 5432, + }, + { + key: 'gitlabRbDbName', + label: '数据库名称', + type: 'text', + default: 'gitlabhq_production', + }, + { + key: 'gitlabRbDbUser', + label: '数据库用户', + type: 'text', + default: 'gitlab', + }, + { + key: 'gitlabRbDbPassword', + label: '数据库密码', + type: 'text', + default: 'changeme', + tip: '生产环境请使用强密码', + }, + ], + }, + { + title: 'Redis 配置', + fields: [ + { + key: 'redisHost', + label: 'Redis 主机', + type: 'text', + default: '127.0.0.1', + }, + { + key: 'redisPort', + label: 'Redis 端口', + type: 'number', + min: 1, + max: 65535, + default: 6379, + }, + ], + }, + { + title: 'Container Registry', + fields: [ + { + key: 'registryEnable', + label: '启用 Container Registry', + type: 'switch', + default: false, + tip: 'Docker 镜像仓库', + }, + { + key: 'registryExternalUrl', + label: 'Registry 外部 URL', + type: 'text', + placeholder: 'http://registry.example.com', + default: 'http://registry.example.com', + dependsOn: { key: 'registryEnable', value: true }, + }, + ], + }, + { + title: 'GitLab Pages', + fields: [ + { + key: 'pagesEnable', + label: '启用 GitLab Pages', + type: 'switch', + default: false, + tip: '静态网站托管服务', + }, + { + key: 'pagesExternalUrl', + label: 'Pages 外部 URL', + type: 'text', + placeholder: 'http://pages.example.com', + default: 'http://pages.example.com', + dependsOn: { key: 'pagesEnable', value: true }, + }, + ], + }, + { + title: 'SMTP 邮件配置', + fields: [ + { + key: 'smtpEnable', + label: '启用 SMTP', + type: 'switch', + default: false, + tip: '用于发送邮件通知', + }, + { + key: 'smtpAddress', + label: 'SMTP 服务器', + type: 'text', + default: 'smtp.example.com', + dependsOn: { key: 'smtpEnable', value: true }, + }, + { + key: 'smtpPort', + label: 'SMTP 端口', + type: 'number', + min: 1, + max: 65535, + default: 587, + dependsOn: { key: 'smtpEnable', value: true }, + }, + { + key: 'smtpUser', + label: 'SMTP 用户名', + type: 'text', + default: 'gitlab@example.com', + dependsOn: { key: 'smtpEnable', value: true }, + }, + { + key: 'smtpPassword', + label: 'SMTP 密码', + type: 'text', + default: 'changeme', + dependsOn: { key: 'smtpEnable', value: true }, + }, + { + key: 'smtpTls', + label: '启用 TLS', + type: 'switch', + default: true, + dependsOn: { key: 'smtpEnable', value: true }, + }, + ], + }, + { + title: '备份与监控', + fields: [ + { + key: 'backupKeepTime', + label: '备份保留时间 (秒)', + type: 'number', + min: 0, + default: 604800, + tip: '604800 秒 = 7 天', + }, + { + key: 'prometheusEnable', + label: '启用 Prometheus 监控', + type: 'switch', + default: true, + }, + ], + }, + ], +} + +export function generateGitlabRb(config) { + const lines = [] + lines.push(`# GitLab 配置文件 - 由 ConfTemplate 生成`) + lines.push(`# 生成时间: ${new Date().toLocaleString('zh-CN')}`) + lines.push(``) + lines.push(`## 外部访问 URL`) + lines.push(`external_url '${config.externalUrl}'`) + lines.push(``) + + // Nginx + lines.push(`## Nginx 配置`) + lines.push(`nginx['listen_port'] = ${config.nginxListenPort}`) + if (config.nginxEnableSsl) { + lines.push(`nginx['ssl_certificate'] = '${config.sslCertificate}'`) + lines.push(`nginx['ssl_certificate_key'] = '${config.sslCertificateKey}'`) + } else { + lines.push(`nginx['enable_ssl'] = false`) + } + lines.push(``) + + // Database + lines.push(`## 数据库配置`) + if (config.gitlabRbDbAdapter === 'postgresql') { + lines.push(`postgresql['enable'] = true`) + lines.push(`gitlab_rails['db_adapter'] = 'postgresql'`) + lines.push(`gitlab_rails['db_host'] = '${config.gitlabRbDbHost}'`) + lines.push(`gitlab_rails['db_port'] = ${config.gitlabRbDbPort}`) + } else { + lines.push(`postgresql['enable'] = false`) + lines.push(`gitlab_rails['db_adapter'] = 'mysql2'`) + lines.push(`gitlab_rails['db_host'] = '${config.gitlabRbDbHost}'`) + lines.push(`gitlab_rails['db_port'] = ${config.gitlabRbDbPort}`) + } + lines.push(`gitlab_rails['db_database'] = '${config.gitlabRbDbName}'`) + lines.push(`gitlab_rails['db_username'] = '${config.gitlabRbDbUser}'`) + lines.push(`gitlab_rails['db_password'] = '${config.gitlabRbDbPassword}'`) + lines.push(``) + + // Redis + lines.push(`## Redis 配置`) + lines.push(`redis['enable'] = true`) + lines.push(`gitlab_rails['redis_host'] = '${config.redisHost}'`) + lines.push(`gitlab_rails['redis_port'] = ${config.redisPort}`) + lines.push(``) + + // Registry + if (config.registryEnable) { + lines.push(`## Container Registry`) + lines.push(`registry['enable'] = true`) + lines.push(`registry_external_url '${config.registryExternalUrl}'`) + lines.push(``) + } + + // Pages + if (config.pagesEnable) { + lines.push(`## GitLab Pages`) + lines.push(`pages_external_url '${config.pagesExternalUrl}'`) + lines.push(`gitlab_pages['enable'] = true`) + lines.push(``) + } + + // SMTP + if (config.smtpEnable) { + lines.push(`## SMTP 邮件配置`) + lines.push(`gitlab_rails['smtp_enable'] = true`) + lines.push(`gitlab_rails['smtp_address'] = '${config.smtpAddress}'`) + lines.push(`gitlab_rails['smtp_port'] = ${config.smtpPort}`) + lines.push(`gitlab_rails['smtp_user_name'] = '${config.smtpUser}'`) + lines.push(`gitlab_rails['smtp_password'] = '${config.smtpPassword}'`) + lines.push(`gitlab_rails['smtp_tls'] = ${config.smtpTls}`) + lines.push(``) + } + + // Backup + lines.push(`## 备份配置`) + lines.push(`gitlab_rails['backup_keep_time'] = ${config.backupKeepTime}`) + lines.push(``) + + // Prometheus + if (config.prometheusEnable) { + lines.push(`## Prometheus 监控`) + lines.push(`prometheus['enable'] = true`) + } else { + lines.push(`prometheus['enable'] = false`) + } + + return lines.join('\n') +} diff --git a/src/schemas/haproxy.js b/src/schemas/haproxy.js new file mode 100644 index 0000000..aa9bd81 --- /dev/null +++ b/src/schemas/haproxy.js @@ -0,0 +1,318 @@ +export const haproxySchema = { + id: 'haproxy', + name: 'HAProxy', + icon: 'Connection', + category: '高可用', + description: '高性能 TCP/HTTP 负载均衡器和代理服务器', + format: 'cfg', + fileName: 'haproxy.cfg', + groups: [ + { + title: 'Global 全局配置', + fields: [ + { + key: 'globalMaxconn', + label: '最大连接数', + type: 'number', + min: 100, + max: 1000000, + default: 1000, + }, + { + key: 'globalLog', + label: '日志输出', + type: 'text', + default: '127.0.0.1 local0', + }, + { + key: 'globalChroot', + label: '启用 chroot', + type: 'switch', + default: false, + tip: '安全加固,限制进程访问范围', + }, + { + key: 'chrootDir', + label: 'chroot 目录', + type: 'text', + default: '/var/lib/haproxy', + dependsOn: { key: 'globalChroot', value: true }, + }, + { + key: 'globalUser', + label: '运行用户', + type: 'text', + default: 'haproxy', + }, + { + key: 'globalGroup', + label: '运行用户组', + type: 'text', + default: 'haproxy', + }, + { + key: 'globalStatsEnable', + label: '启用 Stats Socket', + type: 'switch', + default: true, + }, + { + key: 'statsSocket', + label: 'Stats Socket 路径', + type: 'text', + default: '/var/run/haproxy.sock', + dependsOn: { key: 'globalStatsEnable', value: true }, + }, + ], + }, + { + title: 'Defaults 默认配置', + fields: [ + { + key: 'defaultsMode', + label: '代理模式', + type: 'select', + options: [ + { label: 'HTTP 模式', value: 'http' }, + { label: 'TCP 模式', value: 'tcp' }, + ], + default: 'http', + }, + { + key: 'defaultsLog', + label: '日志模式', + type: 'text', + default: 'global', + }, + { + key: 'defaultsOptionHttplog', + label: '启用 HTTP 日志', + type: 'switch', + default: true, + }, + { + key: 'defaultsOptionDontlognull', + label: '忽略空连接日志', + type: 'switch', + default: true, + }, + { + key: 'defaultsTimeoutConnect', + label: '连接超时 (ms)', + type: 'number', + min: 100, + max: 60000, + default: 5000, + }, + { + key: 'defaultsTimeoutClient', + label: '客户端超时 (ms)', + type: 'number', + min: 1000, + max: 300000, + default: 50000, + }, + { + key: 'defaultsTimeoutServer', + label: '服务端超时 (ms)', + type: 'number', + min: 1000, + max: 300000, + default: 50000, + }, + { + key: 'defaultsRetries', + label: '重试次数', + type: 'number', + min: 0, + max: 10, + default: 3, + }, + ], + }, + { + title: 'Frontend 前端配置', + fields: [ + { + key: 'frontendName', + label: '前端名称', + type: 'text', + default: 'myapp_front', + }, + { + key: 'frontendBind', + label: '绑定地址', + type: 'text', + default: '*:80', + tip: '格式: 地址:端口', + }, + { + key: 'frontendBackend', + label: '关联后端', + type: 'text', + default: 'myapp_back', + }, + { + key: 'enableSsl', + label: '启用 SSL', + type: 'switch', + default: false, + }, + { + key: 'sslBind', + label: 'SSL 绑定配置', + type: 'text', + default: '*:443 ssl crt /etc/haproxy/cert.pem', + dependsOn: { key: 'enableSsl', value: true }, + }, + ], + }, + { + title: 'Backend 后端配置', + fields: [ + { + key: 'backendName', + label: '后端名称', + type: 'text', + default: 'myapp_back', + }, + { + key: 'balanceType', + label: '负载均衡算法', + type: 'select', + options: [ + { label: 'roundrobin - 轮询 (推荐)', value: 'roundrobin' }, + { label: 'leastconn - 最少连接', value: 'leastconn' }, + { label: 'source - 源地址哈希', value: 'source' }, + { label: 'uri - URI 哈希', value: 'uri' }, + ], + default: 'roundrobin', + }, + { + key: 'backendServer1', + label: '后端服务器 1', + type: 'text', + default: 'web1 192.168.1.10:8080 check', + tip: '格式: 名称 IP:端口 check', + }, + { + key: 'backendServer2', + label: '后端服务器 2', + type: 'text', + default: 'web2 192.168.1.11:8080 check', + }, + ], + }, + { + title: 'Stats 统计页面', + fields: [ + { + key: 'enableStats', + label: '启用统计页面', + type: 'switch', + default: true, + }, + { + key: 'statsUri', + label: '统计页面 URI', + type: 'text', + default: '/haproxy?stats', + dependsOn: { key: 'enableStats', value: true }, + }, + { + key: 'statsAuthEnable', + label: '统计页面认证', + type: 'switch', + default: true, + dependsOn: { key: 'enableStats', value: true }, + }, + { + key: 'statsUser', + label: '统计页面用户名', + type: 'text', + default: 'admin', + dependsOn: { key: 'statsAuthEnable', value: true }, + }, + { + key: 'statsPassword', + label: '统计页面密码', + type: 'text', + default: 'admin', + dependsOn: { key: 'statsAuthEnable', value: true }, + }, + ], + }, + ], +} + +export function generateHaproxyCfg(config) { + const lines = [] + lines.push(`# HAProxy 配置文件 - 由 ConfTemplate 生成`) + lines.push(`# 生成时间: ${new Date().toLocaleString('zh-CN')}`) + lines.push(``) + + // Global + lines.push(`global`) + lines.push(` log ${config.globalLog} local0`) + lines.push(` maxconn ${config.globalMaxconn}`) + lines.push(` user ${config.globalUser}`) + lines.push(` group ${config.globalGroup}`) + lines.push(` daemon`) + if (config.globalChroot) { + lines.push(` chroot ${config.chrootDir}`) + } + if (config.globalStatsEnable) { + lines.push(` stats socket ${config.statsSocket} mode 660 level admin`) + } + lines.push(``) + + // Defaults + lines.push(`defaults`) + lines.push(` mode ${config.defaultsMode}`) + lines.push(` log ${config.defaultsLog}`) + if (config.defaultsOptionHttplog) { + lines.push(` option httplog`) + } + if (config.defaultsOptionDontlognull) { + lines.push(` option dontlognull`) + } + lines.push(` timeout connect ${config.defaultsTimeoutConnect}`) + lines.push(` timeout client ${config.defaultsTimeoutClient}`) + lines.push(` timeout server ${config.defaultsTimeoutServer}`) + lines.push(` retries ${config.defaultsRetries}`) + lines.push(``) + + // Frontend + lines.push(`frontend ${config.frontendName}`) + lines.push(` bind ${config.frontendBind}`) + if (config.enableSsl) { + lines.push(` bind ${config.sslBind}`) + } + lines.push(` default_backend ${config.frontendBackend}`) + lines.push(``) + + // Backend + lines.push(`backend ${config.backendName}`) + lines.push(` balance ${config.balanceType}`) + if (config.backendServer1) { + lines.push(` server ${config.backendServer1}`) + } + if (config.backendServer2) { + lines.push(` server ${config.backendServer2}`) + } + lines.push(``) + + // Stats + if (config.enableStats) { + lines.push(`listen stats`) + lines.push(` bind *:8404`) + lines.push(` stats enable`) + lines.push(` stats uri ${config.statsUri}`) + if (config.statsAuthEnable) { + lines.push(` stats auth ${config.statsUser}:${config.statsPassword}`) + } + lines.push(` stats refresh 10s`) + } + + return lines.join('\n') +} diff --git a/src/schemas/index.js b/src/schemas/index.js index 5e819e2..1c283d9 100644 --- a/src/schemas/index.js +++ b/src/schemas/index.js @@ -74,6 +74,29 @@ import { syslogSchema, generateSyslogConf } from './syslog' import { ansibleSchema, generateAnsibleCfg } from './ansible' import { ansiblePlaybookSchema, generateAnsiblePlaybookYaml } from './ansible-playbook' +// ============ CI/CD ============ +import { gitlabSchema, generateGitlabRb } from './gitlab' +import { jenkinsSchema, generateJenkinsYaml } from './jenkins' + +// ============ 高可用 ============ +import { keepalivedSchema, generateKeepalivedConf } from './keepalived' +import { haproxySchema, generateHaproxyCfg } from './haproxy' +import { lvsSchema, generateLvsConf } from './lvs' + +// ============ 系统服务 ============ +import { ntpSchema, generateNtpConf } from './ntp' +import { dhcpSchema, generateDhcpConf } from './dhcp' +import { bindSchema, generateBindConf } from './bind' +import { nfsSchema, generateNfsExports } from './nfs' +import { sambaSchema, generateSambaConf } from './samba' +import { rsyncSchema, generateRsyncConf } from './rsync' +import { sersyncSchema, generateSersyncXml } from './sersync' +import { vsftpdSchema, generateVsftpdConf } from './vsftpd' +import { subversionSchema, generateSubversionConf } from './subversion' + +// ============ 数据库中间件 ============ +import { mycatSchema, generateMycatServerXml } from './mycat' + // ============================================================ // Schema Registry // ============================================================ @@ -139,6 +162,25 @@ export const schemas = { // 自动化运维 ansible: ansibleSchema, 'ansible-playbook': ansiblePlaybookSchema, + // CI/CD + gitlab: gitlabSchema, + jenkins: jenkinsSchema, + // 高可用 + keepalived: keepalivedSchema, + haproxy: haproxySchema, + lvs: lvsSchema, + // 系统服务 + ntp: ntpSchema, + dhcp: dhcpSchema, + bind: bindSchema, + nfs: nfsSchema, + samba: sambaSchema, + rsync: rsyncSchema, + sersync: sersyncSchema, + vsftpd: vsftpdSchema, + subversion: subversionSchema, + // 数据库中间件 + mycat: mycatSchema, } // ============================================================ @@ -190,6 +232,25 @@ export const generators = { syslog: generateSyslogConf, ansible: generateAnsibleCfg, 'ansible-playbook': generateAnsiblePlaybookYaml, + // CI/CD + gitlab: generateGitlabRb, + jenkins: generateJenkinsYaml, + // 高可用 + keepalived: generateKeepalivedConf, + haproxy: generateHaproxyCfg, + lvs: generateLvsConf, + // 系统服务 + ntp: generateNtpConf, + dhcp: generateDhcpConf, + bind: generateBindConf, + nfs: generateNfsExports, + samba: generateSambaConf, + rsync: generateRsyncConf, + sersync: generateSersyncXml, + vsftpd: generateVsftpdConf, + subversion: generateSubversionConf, + // 数据库中间件 + mycat: generateMycatServerXml, } // ============================================================ @@ -277,6 +338,26 @@ export const categories = [ icon: 'Files', items: ['ansible', 'ansible-playbook'], }, + { + name: 'CI/CD', + icon: 'Files', + items: ['gitlab', 'jenkins'], + }, + { + name: '高可用', + icon: 'Connection', + items: ['keepalived', 'haproxy', 'lvs'], + }, + { + name: '系统服务', + icon: 'Setting', + items: ['ntp', 'dhcp', 'bind', 'nfs', 'samba', 'rsync', 'sersync', 'vsftpd', 'subversion'], + }, + { + name: '数据库中间件', + icon: 'Coin', + items: ['mycat'], + }, ] // ============================================================ @@ -299,6 +380,7 @@ export function getLanguage(format) { cnf: 'ini', cfg: 'ini', ora: 'ini', + rb: 'ruby', yml: 'yaml', yaml: 'yaml', json: 'json', diff --git a/src/schemas/jenkins.js b/src/schemas/jenkins.js new file mode 100644 index 0000000..20c5ffe --- /dev/null +++ b/src/schemas/jenkins.js @@ -0,0 +1,267 @@ +export const jenkinsSchema = { + id: 'jenkins', + name: 'Jenkins', + icon: 'Setting', + category: 'CI/CD', + description: '开源自动化服务器,支持持续集成与持续交付', + format: 'yml', + fileName: 'jenkins.yaml', + groups: [ + { + title: '基础配置', + fields: [ + { + key: 'httpPort', + label: 'HTTP 端口', + type: 'number', + min: 1, + max: 65535, + default: 8080, + }, + { + key: 'ajpPort', + label: 'AJP 端口', + type: 'number', + default: -1, + tip: '-1 表示禁用 AJP', + }, + { + key: 'jenkinsHome', + label: 'JENKINS_HOME 目录', + type: 'text', + default: '/var/lib/jenkins', + }, + { + key: 'numExecutors', + label: '执行器数量', + type: 'number', + min: 0, + max: 100, + default: 2, + tip: '同时执行的任务数', + }, + { + key: 'mode', + label: '节点模式', + type: 'select', + options: [ + { label: 'normal - 正常模式', value: 'normal' }, + { label: 'exclusive - 仅构建指定任务', value: 'exclusive' }, + ], + default: 'normal', + }, + ], + }, + { + title: '安全配置', + fields: [ + { + key: 'securityRealm', + label: '安全域', + type: 'text', + default: 'default', + tip: '用户认证方式', + }, + { + key: 'enableLdap', + label: '启用 LDAP 认证', + type: 'switch', + default: false, + }, + { + key: 'ldapServer', + label: 'LDAP 服务器', + type: 'text', + default: 'ldap://ldap.example.com', + dependsOn: { key: 'enableLdap', value: true }, + }, + { + key: 'ldapRootDn', + label: 'LDAP Root DN', + type: 'text', + default: 'dc=example,dc=com', + dependsOn: { key: 'enableLdap', value: true }, + }, + { + key: 'authorizationStrategy', + label: '授权策略', + type: 'select', + options: [ + { label: '登录用户可以做任何事', value: 'loggedInUsersCanDoAnything' }, + { label: '项目矩阵授权', value: 'projectMatrix' }, + ], + default: 'loggedInUsersCanDoAnything', + }, + { + key: 'adminUser', + label: '管理员用户名', + type: 'text', + default: 'admin', + }, + { + key: 'adminPassword', + label: '管理员密码', + type: 'text', + default: 'admin', + tip: '生产环境请修改默认密码', + }, + ], + }, + { + title: 'JCasC (Configuration as Code)', + fields: [ + { + key: 'enableCasc', + label: '启用 JCasC', + type: 'switch', + default: true, + tip: 'Configuration as Code 插件', + }, + { + key: 'jenkinsUrl', + label: 'Jenkins URL', + type: 'text', + default: 'http://localhost:8080/', + tip: 'Jenkins 系统 URL', + }, + ], + }, + { + title: 'Agent 配置', + fields: [ + { + key: 'enableAgent', + label: '启用 Agent 节点', + type: 'switch', + default: false, + tip: '允许远程 Agent 连接', + }, + { + key: 'agentPort', + label: 'Agent 端口', + type: 'number', + min: 1, + max: 65535, + default: 50000, + dependsOn: { key: 'enableAgent', value: true }, + }, + ], + }, + { + title: 'Git 与 Gitea 集成', + fields: [ + { + key: 'enableGitea', + label: '启用 Gitea 集成', + type: 'switch', + default: false, + }, + { + key: 'giteaUrl', + label: 'Gitea 服务器 URL', + type: 'text', + default: 'http://gitea.example.com', + dependsOn: { key: 'enableGitea', value: true }, + }, + { + key: 'enableGit', + label: '启用 Git', + type: 'switch', + default: true, + }, + { + key: 'gitPath', + label: 'Git 可执行文件路径', + type: 'text', + default: '/usr/bin/git', + dependsOn: { key: 'enableGit', value: true }, + }, + ], + }, + ], +} + +export function generateJenkinsYaml(config) { + const lines = [] + lines.push(`# Jenkins Configuration as Code - 由 ConfTemplate 生成`) + lines.push(`# 生成时间: ${new Date().toLocaleString('zh-CN')}`) + lines.push(``) + lines.push(`jenkins:`) + + // System + lines.push(` systemMessage: "Jenkins configured via ConfTemplate"`) + lines.push(` numExecutors: ${config.numExecutors}`) + lines.push(` mode: ${config.mode}`) + lines.push(` jenkinsHome: "${config.jenkinsHome}"`) + lines.push(``) + + // Security Realm + if (config.enableLdap) { + lines.push(` securityRealm:`) + lines.push(` ldap:`) + lines.push(` configurations:`) + lines.push(` - server: "${config.ldapServer}"`) + lines.push(` rootDN: "${config.ldapRootDn}"`) + } else { + lines.push(` securityRealm:`) + lines.push(` local:`) + lines.push(` allowsSignup: false`) + lines.push(` users:`) + lines.push(` - id: "${config.adminUser}"`) + lines.push(` password: "${config.adminPassword}"`) + } + lines.push(``) + + // Authorization + if (config.authorizationStrategy === 'loggedInUsersCanDoAnything') { + lines.push(` authorizationStrategy:`) + lines.push(` loggedInUsersCanDoAnything:`) + lines.push(` allowAnonymousRead: false`) + } else { + lines.push(` authorizationStrategy:`) + lines.push(` projectMatrix:`) + lines.push(` entries:`) + lines.push(` - user:"${config.adminUser}":`) + lines.push(` permissions:`) + lines.push(` - "Overall/Administer"`) + } + lines.push(``) + + // Agent + if (config.enableAgent) { + lines.push(` agent:`) + lines.push(` port: ${config.agentPort}`) + } + + // JCasC + if (config.enableCasc) { + lines.push(` configuration-as-code:`) + lines.push(` enabled: true`) + } + lines.push(``) + + // URL + lines.push(`unclassified:`) + lines.push(` jenkinsLocationConfiguration:`) + lines.push(` jenkinsUrl: "${config.jenkinsUrl}"`) + lines.push(``) + + // Git + if (config.enableGit) { + lines.push(` git:`) + lines.push(` installations:`) + lines.push(` - name: "Default"`) + lines.push(` home: "${config.gitPath}"`) + lines.push(``) + } + + // Gitea + if (config.enableGitea) { + lines.push(` giteaServers:`) + lines.push(` servers:`) + lines.push(` - displayName: "Gitea"`) + lines.push(` serverUrl: "${config.giteaUrl}"`) + } + + return lines.join('\n') +} diff --git a/src/schemas/keepalived.js b/src/schemas/keepalived.js new file mode 100644 index 0000000..88e06a8 --- /dev/null +++ b/src/schemas/keepalived.js @@ -0,0 +1,296 @@ +export const keepalivedSchema = { + id: 'keepalived', + name: 'Keepalived', + icon: 'Connection', + category: '高可用', + description: '基于 VRRP 协议的高可用解决方案,用于 IP 漂移和故障转移', + format: 'conf', + fileName: 'keepalived.conf', + groups: [ + { + title: 'VRRP 实例配置', + fields: [ + { + key: 'vrrpInstanceName', + label: 'VRRP 实例名称', + type: 'text', + default: 'VI_1', + }, + { + key: 'interface', + label: '网络接口', + type: 'text', + default: 'eth0', + tip: '绑定的网络接口名称', + }, + { + key: 'state', + label: '初始状态', + type: 'select', + options: [ + { label: 'MASTER - 主节点', value: 'master' }, + { label: 'BACKUP - 备节点', value: 'backup' }, + ], + default: 'master', + }, + { + key: 'virtualRouterId', + label: '虚拟路由 ID', + type: 'number', + min: 1, + max: 255, + default: 51, + tip: '同一组的节点必须相同', + }, + { + key: 'priority', + label: '优先级', + type: 'number', + min: 1, + max: 255, + default: 100, + tip: '数值越大优先级越高', + }, + { + key: 'advertInt', + label: '通告间隔 (秒)', + type: 'number', + min: 1, + max: 255, + default: 1, + }, + ], + }, + { + title: '认证配置', + fields: [ + { + key: 'authType', + label: '认证类型', + type: 'text', + default: 'PASS', + tip: 'PASS 或 AH', + }, + { + key: 'authPass', + label: '认证密码', + type: 'text', + default: '1234', + tip: '同一组的节点必须相同', + }, + ], + }, + { + title: '虚拟 IP 配置', + fields: [ + { + key: 'virtualIpAddress', + label: '虚拟 IP 地址', + type: 'text', + default: '192.168.1.100/24 dev eth0', + required: true, + tip: '格式: IP/MASK dev 接口', + }, + ], + }, + { + title: '通知脚本', + fields: [ + { + key: 'enableNotify', + label: '启用通知脚本', + type: 'switch', + default: false, + tip: '状态切换时执行的脚本', + }, + { + key: 'notifyMaster', + label: '切换为 Master 时执行', + type: 'text', + default: "'/etc/keepalived/notify.sh master'", + dependsOn: { key: 'enableNotify', value: true }, + }, + { + key: 'notifyBackup', + label: '切换为 Backup 时执行', + type: 'text', + default: "'/etc/keepalived/notify.sh backup'", + dependsOn: { key: 'enableNotify', value: true }, + }, + { + key: 'notifyFault', + label: '进入 Fault 状态时执行', + type: 'text', + default: "'/etc/keepalived/notify.sh fault'", + dependsOn: { key: 'enableNotify', value: true }, + }, + ], + }, + { + title: '单播配置', + fields: [ + { + key: 'enableVipUnicast', + label: '启用单播', + type: 'switch', + default: false, + tip: '使用单播代替组播通信', + }, + { + key: 'unicastPeerIp', + label: '单播对端 IP', + type: 'text', + default: '192.168.1.2', + dependsOn: { key: 'enableVipUnicast', value: true }, + }, + ], + }, + { + title: '健康检查', + fields: [ + { + key: 'enableHealthCheck', + label: '启用健康检查', + type: 'switch', + default: true, + }, + { + key: 'healthCheckType', + label: '检查类型', + type: 'select', + options: [ + { label: 'TCP 端口检查', value: 'tcp' }, + { label: 'HTTP GET 检查', value: 'http_get' }, + { label: 'SSL GET 检查', value: 'ssl_get' }, + { label: 'SMTP 检查', value: 'smtp' }, + { label: 'MISC 自定义检查', value: 'misc_check' }, + ], + default: 'tcp', + dependsOn: { key: 'enableHealthCheck', value: true }, + }, + { + key: 'healthCheckUrl', + label: '检查 URL 路径', + type: 'text', + default: '/health', + dependsOn: { key: 'healthCheckType', valueIn: ['http_get', 'ssl_get'] }, + }, + { + key: 'healthCheckPort', + label: '检查端口', + type: 'number', + min: 1, + max: 65535, + default: 80, + dependsOn: { key: 'enableHealthCheck', value: true }, + }, + { + key: 'healthCheckTimeout', + label: '超时时间 (秒)', + type: 'number', + min: 1, + max: 30, + default: 3, + dependsOn: { key: 'enableHealthCheck', value: true }, + }, + { + key: 'healthCheckRetry', + label: '重试次数', + type: 'number', + min: 1, + max: 10, + default: 3, + dependsOn: { key: 'enableHealthCheck', value: true }, + }, + { + key: 'healthCheckDelayBeforeRetry', + label: '重试间隔 (秒)', + type: 'number', + min: 1, + max: 30, + default: 3, + dependsOn: { key: 'enableHealthCheck', value: true }, + }, + ], + }, + ], +} + +export function generateKeepalivedConf(config) { + const lines = [] + lines.push(`# Keepalived 配置文件 - 由 ConfTemplate 生成`) + lines.push(`# 生成时间: ${new Date().toLocaleString('zh-CN')}`) + lines.push(``) + + // VRRP Script (health check) + if (config.enableHealthCheck) { + lines.push(`vrrp_script chk_service {`) + if (config.healthCheckType === 'tcp') { + lines.push(` script " /etc/lvs/check_real_server.sh << 'HEALTHCHECK_EOF'`) + lines.push(`#!/bin/bash`) + lines.push(`# LVS 健康检查脚本`) + lines.push(`VIP="${config.vip}"`) + lines.push(`VPORT="${config.vipPort}"`) + lines.push(``) + + const servers = [ + { ip: config.realServer1, port: config.realServer1Port, weight: config.realServer1Weight }, + { ip: config.realServer2, port: config.realServer2Port, weight: config.realServer2Weight }, + ] + if (config.enableRealServer3) { + servers.push({ ip: config.realServer3, port: config.realServer3Port, weight: config.realServer3Weight }) + } + + for (let i = 0; i < servers.length; i++) { + const rs = servers[i] + lines.push(`# Check Real Server ${i + 1}: ${rs.ip}`) + if (config.healthCheckType === 'tcp') { + lines.push(`if nc -z -w 3 ${rs.ip} ${rs.port} > /dev/null 2>&1; then`) + } else { + lines.push(`if curl -s -o /dev/null -w "%{http_code}" http://${rs.ip}:${config.healthCheckPort}${config.healthCheckUrl} | grep -q "200"; then`) + } + lines.push(` # Server ${rs.ip} is UP`) + lines.push(` ipvsadm -a -t $VIP:$VPORT -r ${rs.ip}:${rs.port} -w ${rs.weight} ${modeFlag} 2>/dev/null`) + lines.push(`else`) + lines.push(` # Server ${rs.ip} is DOWN, removing from pool`) + lines.push(` ipvsadm -d -t $VIP:$VPORT -r ${rs.ip}:${rs.port} 2>/dev/null`) + lines.push(`fi`) + lines.push(``) + } + + lines.push(`HEALTHCHECK_EOF`) + lines.push(`chmod +x /etc/lvs/check_real_server.sh`) + lines.push(``) + lines.push(`# 添加 crontab 定时检查 (每 10 秒)`) + lines.push(`# */1 * * * * /etc/lvs/check_real_server.sh`) + lines.push(`# */1 * * * * sleep 10 && /etc/lvs/check_real_server.sh`) + lines.push(`# */1 * * * * sleep 20 && /etc/lvs/check_real_server.sh`) + lines.push(`# */1 * * * * sleep 30 && /etc/lvs/check_real_server.sh`) + lines.push(`# */1 * * * * sleep 40 && /etc/lvs/check_real_server.sh`) + lines.push(`# */1 * * * * sleep 50 && /etc/lvs/check_real_server.sh`) + } + + return lines.join('\n') +} diff --git a/src/schemas/mycat.js b/src/schemas/mycat.js new file mode 100644 index 0000000..790ec23 --- /dev/null +++ b/src/schemas/mycat.js @@ -0,0 +1,226 @@ +export const mycatSchema = { + id: 'mycat', + name: 'Mycat', + icon: 'Coin', + category: '数据库中间件', + description: '开源数据库中间件,支持 MySQL 协议', + format: 'xml', + fileName: 'server.xml', + groups: [ + { + title: '网络配置', + fields: [ + { + key: 'port', + label: '服务端口', + type: 'number', + min: 1, + max: 65535, + default: 8066, + tip: 'MyCat 数据服务端口', + }, + { + key: 'managerPort', + label: '管理端口', + type: 'number', + min: 1, + max: 65535, + default: 9066, + tip: 'MyCat 管理服务端口', + }, + { + key: 'processors', + label: '处理线程数', + type: 'number', + min: 1, + max: 64, + default: 8, + tip: '建议设为 CPU 核心数', + }, + { + key: 'maxConnections', + label: '最大连接数', + type: 'number', + min: 1, + max: 10000, + default: 1000, + }, + { + key: 'idleTimeout', + label: '空闲超时 (毫秒)', + type: 'number', + min: 0, + max: 3600000, + step: 1000, + default: 1800000, + tip: '连接空闲超时时间,默认 30 分钟', + }, + ], + }, + { + title: '数据库配置', + fields: [ + { + key: 'defaultSchema', + label: '默认 Schema', + type: 'text', + default: 'mydb', + tip: '客户端连接后默认使用的逻辑库', + }, + { + key: 'charset', + label: '字符集', + type: 'select', + options: [ + { label: 'utf8mb4 (推荐)', value: 'utf8mb4' }, + { label: 'utf8', value: 'utf8' }, + ], + default: 'utf8mb4', + }, + { + key: 'txIsolation', + label: '事务隔离级别', + type: 'select', + options: [ + { label: '1 - READ_UNCOMMITTED', value: '1' }, + { label: '2 - READ_COMMITTED (推荐)', value: '2' }, + { label: '3 - REPEATABLE_READ', value: '3' }, + { label: '4 - SERIALIZABLE', value: '4' }, + ], + default: '2', + tip: '1=READ_UNCOMMITTED 2=READ_COMMITTED 3=REPEATABLE_READ 4=SERIALIZABLE', + }, + { + key: 'sqlExecuteLimit', + label: 'SQL 执行限制', + type: 'number', + min: 0, + max: 10000, + default: 100, + tip: '单次查询最大执行行数,0 表示不限制', + }, + ], + }, + { + title: '性能与优化', + fields: [ + { + key: 'enableGather', + label: '统计 SQL 执行', + type: 'switch', + default: true, + tip: '统计 SQL 执行次数和时间', + }, + { + key: 'useCompression', + label: '启用压缩', + type: 'switch', + default: false, + tip: 'MySQL 协议压缩,减少网络传输', + }, + ], + }, + { + title: '用户认证', + fields: [ + { + key: 'authUser', + label: '用户名', + type: 'text', + default: 'root', + tip: '连接 MyCat 的用户名', + }, + { + key: 'authPassword', + label: '密码', + type: 'text', + default: '123456', + tip: '连接 MyCat 的密码', + }, + { + key: 'readOnly', + label: '只读用户', + type: 'switch', + default: false, + tip: '该用户是否只有读权限', + }, + ], + }, + ], +} + +export function generateMycatServerXml(config) { + const lines = [] + lines.push(``) + lines.push(``) + lines.push(``) + lines.push(``) + lines.push(``) + lines.push(``) + + // 系统配置 + lines.push(` `) + lines.push(` ${config.port}`) + lines.push(` ${config.managerPort}`) + lines.push(` ${config.processors}`) + lines.push(` ${config.maxConnections}`) + lines.push(` ${config.idleTimeout}`) + lines.push(` ${config.defaultSchema}`) + lines.push(` ${config.charset}`) + lines.push(` ${config.txIsolation}`) + lines.push(` ${config.sqlExecuteLimit}`) + + if (config.enableGather) { + lines.push(` 1`) + } + + if (config.useCompression) { + lines.push(` 0`) + lines.push(` 0`) + lines.push(` `) + lines.push(` 4`) + lines.push(` 16777216`) + } + + lines.push(` `) + lines.push(``) + + // 用户配置 + lines.push(` `) + lines.push(` `) + lines.push(` ${config.authPassword}`) + lines.push(` ${config.defaultSchema}`) + + if (config.readOnly) { + lines.push(` true`) + } + + lines.push(` `) + lines.push(``) + + // 集群配置注释 + lines.push(` `) + + lines.push(``) + + // 连接池注释 + lines.push(` `) + lines.push(` `) + + lines.push(``) + + return lines.join('\n') +} diff --git a/src/schemas/nfs.js b/src/schemas/nfs.js new file mode 100644 index 0000000..0f6933a --- /dev/null +++ b/src/schemas/nfs.js @@ -0,0 +1,185 @@ +export const nfsSchema = { + id: 'nfs', + name: 'NFS', + icon: 'Files', + category: '系统服务', + description: 'NFS 网络文件系统 exports 配置', + format: 'conf', + fileName: 'exports', + groups: [ + { + title: '主共享配置', + fields: [ + { + key: 'sharePath', + label: '共享路径', + type: 'text', + default: '/data/share', + tip: '要共享的本地目录路径', + }, + { + key: 'clientAddress', + label: '客户端地址', + type: 'text', + default: '192.168.1.0/24', + tip: '允许访问的客户端网段或主机', + }, + { + key: 'permissions', + label: '权限', + type: 'select', + options: [ + { label: 'rw - 读写', value: 'rw' }, + { label: 'ro - 只读', value: 'ro' }, + ], + default: 'rw', + }, + { + key: 'rootSquash', + label: 'Root Squash', + type: 'switch', + default: true, + tip: '将 root 用户映射为匿名用户', + }, + { + key: 'allSquash', + label: 'All Squash', + type: 'switch', + default: false, + tip: '将所有用户映射为匿名用户', + }, + { + key: 'sync', + label: '同步写入', + type: 'switch', + default: true, + tip: '同步写入磁盘,更安全但稍慢', + }, + { + key: 'anonuid', + label: '匿名用户 UID', + type: 'number', + min: 0, + max: 65535, + default: 65534, + tip: '匿名用户的 UID', + }, + { + key: 'anongid', + label: '匿名用户 GID', + type: 'number', + min: 0, + max: 65535, + default: 65534, + tip: '匿名用户的 GID', + }, + ], + }, + { + title: '附加共享', + fields: [ + { + key: 'additionalShares', + label: '启用附加共享', + type: 'switch', + default: false, + tip: '配置第二个 NFS 共享目录', + }, + { + key: 'sharePath2', + label: '共享路径 2', + type: 'text', + default: '/data/backup', + dependsOn: { key: 'additionalShares', value: true }, + }, + { + key: 'clientAddress2', + label: '客户端地址 2', + type: 'text', + default: '10.0.0.0/24', + dependsOn: { key: 'additionalShares', value: true }, + }, + { + key: 'permissions2', + label: '权限 2', + type: 'select', + options: [ + { label: 'rw - 读写', value: 'rw' }, + { label: 'ro - 只读', value: 'ro' }, + ], + default: 'ro', + dependsOn: { key: 'additionalShares', value: true }, + }, + ], + }, + { + title: 'NFS 版本', + fields: [ + { + key: 'enableNfsV4', + label: '启用 NFSv4', + type: 'switch', + default: true, + tip: '使用 NFS 第四版协议', + }, + ], + }, + ], +} + +export function generateNfsExports(config) { + const lines = [] + lines.push(`# NFS exports 配置文件 - 由 ConfTemplate 生成`) + lines.push(`# 生成时间: ${new Date().toLocaleString('zh-CN')}`) + lines.push(`# 格式: <目录> <客户端>(<选项>)`) + lines.push(``) + + // Build options string for primary share + const buildOptions = (cfg, prefix = '') => { + const perms = cfg[`permissions${prefix}`] || 'rw' + const rootSquash = cfg.rootSquash !== undefined ? cfg.rootSquash : true + const allSquash = cfg.allSquash !== undefined ? cfg.allSquash : false + const sync = cfg.sync !== undefined ? cfg.sync : true + + const opts = [] + opts.push(perms) + opts.push(sync ? 'sync' : 'async') + opts.push(rootSquash ? 'root_squash' : 'no_root_squash') + if (allSquash) { + opts.push('all_squash') + } + opts.push(`anonuid=${cfg.anonuid || 65534}`) + opts.push(`anongid=${cfg.anongid || 65534}`) + + return opts.join(',') + } + + // Primary share + lines.push(`# ======================== 主共享 ========================`) + lines.push(`${config.sharePath} ${config.clientAddress}(${buildOptions(config)})`) + lines.push(``) + + // Additional share + if (config.additionalShares) { + lines.push(`# ======================== 附加共享 ========================`) + const opts2 = [] + opts2.push(config.permissions2 || 'ro') + opts2.push('sync') + opts2.push('root_squash') + opts2.push(`anonuid=${config.anonuid || 65534}`) + opts2.push(`anongid=${config.anongid || 65534}`) + lines.push(`${config.sharePath2} ${config.clientAddress2}(${opts2.join(',')})`) + lines.push(``) + } + + // NFSv4 note + if (config.enableNfsV4) { + lines.push(`# ======================== NFSv4 配置提示 ========================`) + lines.push(`# NFSv4 需要在 /etc/exports 中使用 fsid=0 或 fsid=N 来定义导出根`) + lines.push(`# 确保 rpcbind 和 nfs-server 服务已启动:`) + lines.push(`# systemctl enable --now rpcbind nfs-server`) + lines.push(`# 修改后执行: exportfs -ra`) + } + + return lines.join('\n') +} diff --git a/src/schemas/ntp.js b/src/schemas/ntp.js new file mode 100644 index 0000000..91d7a97 --- /dev/null +++ b/src/schemas/ntp.js @@ -0,0 +1,181 @@ +export const ntpSchema = { + id: 'ntp', + name: 'NTP', + icon: 'Timer', + category: '系统服务', + description: 'NTP 网络时间协议服务配置', + format: 'conf', + fileName: 'ntp.conf', + groups: [ + { + title: '时间服务器', + fields: [ + { + key: 'server', + label: 'NTP 服务器', + type: 'text', + default: 'pool.ntp.org', + tip: '主 NTP 服务器地址', + }, + { + key: 'server2', + label: '备用 NTP 服务器', + type: 'text', + default: 'cn.ntp.org.cn', + tip: '备用 NTP 服务器地址', + }, + ], + }, + { + title: '访问控制', + fields: [ + { + key: 'restrict', + label: 'IPv4 访问限制', + type: 'text', + default: '-4 default kod notrap nomodify nopeer noquery limited', + tip: 'IPv4 默认访问控制规则', + }, + { + key: 'restrict6', + label: 'IPv6 访问限制', + type: 'text', + default: '-6 default kod notrap nomodify nopeer noquery limited', + tip: 'IPv6 默认访问控制规则', + }, + ], + }, + { + title: '文件路径', + fields: [ + { + key: 'driftfile', + label: '漂移文件路径', + type: 'text', + default: '/var/lib/ntp/drift', + tip: '频率补偿文件路径', + }, + { + key: 'logfile', + label: '日志文件路径', + type: 'text', + default: '/var/log/ntp.log', + tip: 'NTP 日志文件路径', + }, + ], + }, + { + title: '认证配置', + fields: [ + { + key: 'enableAuth', + label: '启用认证', + type: 'switch', + default: false, + tip: '启用 NTP 认证机制', + }, + { + key: 'keysFile', + label: '密钥文件路径', + type: 'text', + default: '/etc/ntp/keys', + dependsOn: { key: 'enableAuth', value: true }, + tip: 'NTP 认证密钥文件', + }, + ], + }, + { + title: '性能调优', + fields: [ + { + key: 'tinkerPanic', + label: 'Tinker Panic 阈值', + type: 'number', + min: 0, + max: 1000, + default: 0, + tip: '0=永不 panic,超过此秒数偏移将 panic', + }, + { + key: 'burst', + label: '启用 Burst 模式', + type: 'switch', + default: false, + tip: '在网络正常时发送一批请求以提高精度', + }, + { + key: 'minpoll', + label: '最小轮询间隔 (2^n 秒)', + type: 'number', + min: 3, + max: 17, + default: 6, + tip: '最小轮询间隔指数,3=8秒', + }, + { + key: 'maxpoll', + label: '最大轮询间隔 (2^n 秒)', + type: 'number', + min: 3, + max: 17, + default: 10, + tip: '最大轮询间隔指数,10=1024秒', + }, + ], + }, + ], +} + +export function generateNtpConf(config) { + const lines = [] + lines.push(`# NTP 配置文件 - 由 ConfTemplate 生成`) + lines.push(`# 生成时间: ${new Date().toLocaleString('zh-CN')}`) + lines.push(``) + + // Time servers + lines.push(`# ======================== 时间服务器 ========================`) + lines.push(`server ${config.server} iburst minpoll ${config.minpoll} maxpoll ${config.maxpoll}`) + lines.push(`server ${config.server2} iburst minpoll ${config.minpoll} maxpoll ${config.maxpoll}`) + lines.push(``) + + // Access control + lines.push(`# ======================== 访问控制 ========================`) + lines.push(`restrict ${config.restrict}`) + lines.push(`restrict ${config.restrict6}`) + lines.push(`restrict 127.0.0.1`) + lines.push(`restrict ::1`) + lines.push(``) + + // File paths + lines.push(`# ======================== 文件路径 ========================`) + lines.push(`driftfile ${config.driftfile}`) + lines.push(`logfile ${config.logfile}`) + lines.push(``) + + // Authentication + if (config.enableAuth) { + lines.push(`# ======================== 认证配置 ========================`) + lines.push(`enable auth`) + lines.push(`keys ${config.keysFile}`) + lines.push(``) + } + + // Performance + lines.push(`# ======================== 性能调优 ========================`) + if (config.tinkerPanic > 0) { + lines.push(`tinker panic ${config.tinkerPanic}`) + } else { + lines.push(`tinker panic 0`) + } + if (config.burst) { + lines.push(`burst`) + } + lines.push(``) + + // Leap second + lines.push(`# ======================== 其他 ========================`) + lines.push(`# 闰秒文件(如需要)`) + lines.push(`# leapfile /usr/share/zoneinfo/leap-seconds.list`) + + return lines.join('\n') +} diff --git a/src/schemas/rsync.js b/src/schemas/rsync.js new file mode 100644 index 0000000..2c3cd00 --- /dev/null +++ b/src/schemas/rsync.js @@ -0,0 +1,196 @@ +export const rsyncSchema = { + id: 'rsync', + name: 'RSYNC', + icon: 'Files', + category: '系统服务', + description: '快速增量文件传输工具', + format: 'conf', + fileName: 'rsyncd.conf', + groups: [ + { + title: '全局配置', + fields: [ + { + key: 'uid', + label: '用户 (uid)', + type: 'text', + default: 'root', + tip: '传输文件时使用的用户', + }, + { + key: 'gid', + label: '用户组 (gid)', + type: 'text', + default: 'root', + tip: '传输文件时使用的用户组', + }, + { + key: 'useChroot', + label: '使用 chroot', + type: 'switch', + default: false, + tip: '是否限制在根目录下,提高安全性', + }, + { + key: 'port', + label: '监听端口', + type: 'number', + min: 1, + max: 65535, + default: 873, + }, + { + key: 'maxConnections', + label: '最大连接数', + type: 'number', + min: 1, + max: 1000, + default: 10, + }, + { + key: 'timeout', + label: '超时时间 (秒)', + type: 'number', + min: 0, + max: 3600, + default: 300, + tip: 'I/O 超时时间', + }, + { + key: 'logFile', + label: '日志文件路径', + type: 'text', + default: '/var/log/rsyncd.log', + }, + { + key: 'pidFile', + label: 'PID 文件路径', + type: 'text', + default: '/var/run/rsyncd.pid', + }, + ], + }, + { + title: '模块配置', + fields: [ + { + key: 'enableModule', + label: '启用共享模块', + type: 'switch', + default: true, + tip: '是否配置共享模块', + }, + { + key: 'moduleName', + label: '模块名称', + type: 'text', + default: 'backup', + dependsOn: { key: 'enableModule', value: true }, + tip: '客户端访问时使用的模块名', + }, + { + key: 'modulePath', + label: '模块路径', + type: 'text', + default: '/data/backup', + dependsOn: { key: 'enableModule', value: true }, + tip: '共享目录的绝对路径', + }, + { + key: 'moduleComment', + label: '模块注释', + type: 'text', + default: 'Backup Area', + dependsOn: { key: 'enableModule', value: true }, + }, + { + key: 'readOnly', + label: '只读模式', + type: 'switch', + default: false, + dependsOn: { key: 'enableModule', value: true }, + }, + { + key: 'list', + label: '允许列出模块', + type: 'switch', + default: true, + dependsOn: { key: 'enableModule', value: true }, + tip: '客户端是否可以列出可用模块', + }, + ], + }, + { + title: '认证与安全', + fields: [ + { + key: 'authUsers', + label: '授权用户', + type: 'text', + default: 'backup_user', + tip: '多个用户用逗号分隔', + }, + { + key: 'secretsFile', + label: '密码文件路径', + type: 'text', + default: '/etc/rsyncd.secrets', + tip: '格式: 用户名:密码,每行一个', + }, + { + key: 'hostsAllow', + label: '允许的主机', + type: 'text', + default: '192.168.1.0/24', + tip: 'IP 或网段,多个用逗号或空格分隔', + }, + { + key: 'hostsDeny', + label: '拒绝的主机', + type: 'text', + default: '*', + tip: '* 表示拒绝所有未明确允许的主机', + }, + ], + }, + ], +} + +export function generateRsyncConf(config) { + const lines = [] + lines.push(`# rsyncd.conf 配置文件 - 由 ConfTemplate 生成`) + lines.push(`# 生成时间: ${new Date().toLocaleString('zh-CN')}`) + lines.push(``) + + // 全局配置 + lines.push(`# ======================== 全局配置 ========================`) + lines.push(`uid = ${config.uid}`) + lines.push(`gid = ${config.gid}`) + lines.push(`use chroot = ${config.useChroot ? 'true' : 'false'}`) + lines.push(`port = ${config.port}`) + lines.push(`max connections = ${config.maxConnections}`) + lines.push(`timeout = ${config.timeout}`) + lines.push(`log file = ${config.logFile}`) + lines.push(`pid file = ${config.pidFile}`) + lines.push(``) + + // 模块配置 + if (config.enableModule) { + lines.push(`# ======================== 模块配置 ========================`) + lines.push(`[${config.moduleName}]`) + lines.push(` comment = ${config.moduleComment}`) + lines.push(` path = ${config.modulePath}`) + lines.push(` read only = ${config.readOnly ? 'true' : 'false'}`) + lines.push(` list = ${config.list ? 'true' : 'false'}`) + lines.push(``) + } + + // 认证与安全 + lines.push(`# ======================== 认证与安全 ========================`) + lines.push(`auth users = ${config.authUsers}`) + lines.push(`secrets file = ${config.secretsFile}`) + lines.push(`hosts allow = ${config.hostsAllow}`) + lines.push(`hosts deny = ${config.hostsDeny}`) + + return lines.join('\n') +} diff --git a/src/schemas/samba.js b/src/schemas/samba.js new file mode 100644 index 0000000..dcebf56 --- /dev/null +++ b/src/schemas/samba.js @@ -0,0 +1,233 @@ +export const sambaSchema = { + id: 'samba', + name: 'SAMBA', + icon: 'Files', + category: '系统服务', + description: 'Samba 文件共享服务配置', + format: 'conf', + fileName: 'smb.conf', + groups: [ + { + title: '全局设置', + fields: [ + { + key: 'workgroup', + label: '工作组', + type: 'text', + default: 'WORKGROUP', + tip: 'Windows 工作组名称', + }, + { + key: 'serverString', + label: '服务器描述', + type: 'text', + default: 'Samba Server', + tip: '服务器的描述字符串', + }, + { + key: 'netbiosName', + label: 'NetBIOS 名称', + type: 'text', + default: 'smbserver', + tip: '网络中显示的计算机名', + }, + { + key: 'securityType', + label: '安全模式', + type: 'select', + options: [ + { label: 'user - 用户级认证 (推荐)', value: 'user' }, + { label: 'share - 共享级认证', value: 'share' }, + { label: 'domain - 域认证', value: 'domain' }, + ], + default: 'user', + }, + { + key: 'mapToGuest', + label: '访客映射', + type: 'text', + default: 'Bad User', + tip: '认证失败时映射为 guest 用户', + }, + ], + }, + { + title: '共享目录', + fields: [ + { + key: 'enableShare', + label: '启用共享', + type: 'switch', + default: true, + tip: '配置一个共享目录', + }, + { + key: 'shareName', + label: '共享名称', + type: 'text', + default: 'share', + dependsOn: { key: 'enableShare', value: true }, + tip: '网络访问路径中的共享名', + }, + { + key: 'sharePath', + label: '共享路径', + type: 'text', + default: '/data/samba', + dependsOn: { key: 'enableShare', value: true }, + }, + { + key: 'shareComment', + label: '共享注释', + type: 'text', + default: 'Shared Folder', + dependsOn: { key: 'enableShare', value: true }, + }, + { + key: 'browseable', + label: '可浏览', + type: 'switch', + default: true, + dependsOn: { key: 'enableShare', value: true }, + tip: '是否在网络中可见', + }, + { + key: 'writable', + label: '可写', + type: 'switch', + default: true, + dependsOn: { key: 'enableShare', value: true }, + }, + { + key: 'guestOk', + label: '允许匿名访问', + type: 'switch', + default: false, + dependsOn: { key: 'enableShare', value: true }, + }, + { + key: 'validUsers', + label: '有效用户', + type: 'text', + default: 'user1 user2', + dependsOn: { key: 'enableShare', value: true }, + tip: '允许访问的用户列表,空格分隔', + }, + ], + }, + { + title: '权限掩码', + fields: [ + { + key: 'createMask', + label: '文件创建掩码', + type: 'text', + default: '0664', + tip: '新建文件的权限掩码', + }, + { + key: 'directoryMask', + label: '目录创建掩码', + type: 'text', + default: '0775', + tip: '新建目录的权限掩码', + }, + ], + }, + { + title: '日志配置', + fields: [ + { + key: 'enableLogging', + label: '启用日志', + type: 'switch', + default: true, + }, + { + key: 'logFile', + label: '日志文件', + type: 'text', + default: '/var/log/samba/log.%m', + dependsOn: { key: 'enableLogging', value: true }, + tip: '%m 会被替换为客户端机器名', + }, + { + key: 'maxLogSize', + label: '最大日志大小 (KB)', + type: 'number', + min: 0, + max: 100000, + default: 5000, + dependsOn: { key: 'enableLogging', value: true }, + tip: '0 表示不限制', + }, + ], + }, + ], +} + +export function generateSambaConf(config) { + const lines = [] + lines.push(`# Samba 配置文件 - 由 ConfTemplate 生成`) + lines.push(`# 生成时间: ${new Date().toLocaleString('zh-CN')}`) + lines.push(``) + + // Global section + lines.push(`# ======================== 全局设置 ========================`) + lines.push(`[global]`) + lines.push(` workgroup = ${config.workgroup}`) + lines.push(` server string = ${config.serverString}`) + lines.push(` netbios name = ${config.netbiosName}`) + lines.push(` security = ${config.securityType}`) + lines.push(` map to guest = ${config.mapToGuest}`) + lines.push(``) + + // Logging + if (config.enableLogging) { + lines.push(` # 日志配置`) + lines.push(` log file = ${config.logFile}`) + if (config.maxLogSize > 0) { + lines.push(` max log size = ${config.maxLogSize}`) + } + lines.push(``) + } + + // Performance tuning + lines.push(` # 性能优化`) + lines.push(` socket options = TCP_NODELAY IPTOS_LOWDELAY`) + lines.push(` load printers = no`) + lines.push(` printing = bsd`) + lines.push(` printcap name = /dev/null`) + lines.push(` disable spoolss = yes`) + lines.push(``) + + // Share section + if (config.enableShare) { + lines.push(`# ======================== 共享目录 ========================`) + lines.push(`[${config.shareName}]`) + lines.push(` comment = ${config.shareComment}`) + lines.push(` path = ${config.sharePath}`) + lines.push(` browseable = ${config.browseable ? 'yes' : 'no'}`) + lines.push(` writable = ${config.writable ? 'yes' : 'no'}`) + lines.push(` guest ok = ${config.guestOk ? 'yes' : 'no'}`) + + if (!config.guestOk && config.validUsers) { + const users = config.validUsers.split(/\s+/).filter(Boolean).join(', ') + lines.push(` valid users = ${users}`) + } + + lines.push(` create mask = ${config.createMask}`) + lines.push(` directory mask = ${config.directoryMask}`) + lines.push(``) + } + + // Tips + lines.push(`# ======================== 使用提示 ========================`) + lines.push(`# 1. 添加 Samba 用户: smbpasswd -a `) + lines.push(`# 2. 确保目录存在: mkdir -p ${config.enableShare ? config.sharePath : '/data/samba'}`) + lines.push(`# 3. 设置权限: chmod 775 ${config.enableShare ? config.sharePath : '/data/samba'}`) + lines.push(`# 4. 重启服务: systemctl restart smb nmb`) + lines.push(`# 5. 测试配置: testparm`) + + return lines.join('\n') +} diff --git a/src/schemas/sersync.js b/src/schemas/sersync.js new file mode 100644 index 0000000..8bc0062 --- /dev/null +++ b/src/schemas/sersync.js @@ -0,0 +1,256 @@ +export const sersyncSchema = { + id: 'sersync', + name: 'Sersync', + icon: 'Refresh', + category: '系统服务', + description: '基于 inotify + rsync 的实时文件同步工具', + format: 'xml', + fileName: 'confxml.xml', + groups: [ + { + title: '基础配置', + fields: [ + { + key: 'watchPath', + label: '监控目录', + type: 'text', + default: '/data/web', + tip: '需要实时同步的本地目录', + }, + { + key: 'localDebug', + label: '本地调试模式', + type: 'switch', + default: false, + tip: '开启后仅记录日志不执行同步', + }, + ], + }, + { + title: 'Rsync 配置', + fields: [ + { + key: 'rsyncCommand', + label: 'Rsync 命令路径', + type: 'text', + default: '/usr/bin/rsync', + }, + { + key: 'rsyncArgs', + label: 'Rsync 参数', + type: 'text', + default: '-avz --delete', + tip: '常用的 rsync 同步参数', + }, + { + key: 'rsyncUser', + label: 'Rsync 用户', + type: 'text', + default: 'root', + }, + { + key: 'remoteHosts', + label: '远程主机列表', + type: 'text', + default: '192.168.1.101::backup 192.168.1.102::backup', + tip: '格式: IP::模块名,多个用空格分隔', + }, + { + key: 'remotePassword', + label: '密码文件路径', + type: 'text', + default: '/etc/rsyncd.secrets', + }, + ], + }, + { + title: 'SSH 配置', + fields: [ + { + key: 'enableSsh', + label: '启用 SSH 传输', + type: 'switch', + default: false, + tip: '使用 SSH 代替 rsync daemon 传输', + }, + { + key: 'sshPort', + label: 'SSH 端口', + type: 'number', + min: 1, + max: 65535, + default: 22, + dependsOn: { key: 'enableSsh', value: true }, + }, + ], + }, + { + title: '监控配置', + fields: [ + { + key: 'enableMonitor', + label: '开启 HTTP 监控', + type: 'switch', + default: true, + tip: '提供 HTTP 接口查看同步状态', + }, + { + key: 'monitorPort', + label: '监控端口', + type: 'number', + min: 1, + max: 65535, + default: 8008, + dependsOn: { key: 'enableMonitor', value: true }, + }, + { + key: 'failLog', + label: '失败日志脚本', + type: 'text', + default: '/tmp/rsync_fail_log.sh', + tip: '同步失败时执行的脚本', + }, + ], + }, + { + title: '定时任务与过滤', + fields: [ + { + key: 'enableCron', + label: '启用定时同步', + type: 'switch', + default: true, + tip: '定时执行全量同步作为兜底', + }, + { + key: 'cronInterval', + label: '定时同步间隔 (秒)', + type: 'number', + min: 60, + max: 86400, + default: 600, + dependsOn: { key: 'enableCron', value: true }, + tip: '每隔多少秒执行一次全量同步', + }, + { + key: 'filterRule', + label: '启用过滤规则', + type: 'switch', + default: false, + tip: '过滤不需要同步的文件', + }, + { + key: 'filterExpression', + label: '过滤表达式', + type: 'text', + default: '*.tmp *.temp', + dependsOn: { key: 'filterRule', value: true }, + tip: '匹配的文件不同步,空格分隔', + }, + ], + }, + ], +} + +export function generateSersyncXml(config) { + const lines = [] + lines.push(``) + lines.push(``) + lines.push(``) + lines.push(``) + lines.push(` `) + lines.push(` `) + + if (config.filterRule && config.filterExpression) { + const expressions = config.filterExpression.split(/\s+/).filter(Boolean) + for (const expr of expressions) { + lines.push(` `) + } + } + + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + + // 解析远程主机列表 + const hosts = config.remoteHosts.split(/\s+/).filter(Boolean) + for (const host of hosts) { + if (config.enableSsh) { + const parts = host.split('::') + const ip = parts[0] + const remotePath = parts[1] || '/data/backup' + lines.push(` `) + } else { + const parts = host.split('::') + const ip = parts[0] + const module = parts[1] || 'backup' + lines.push(` `) + } + } + + lines.push(` `) + lines.push(` `) + lines.push(` `) + + if (config.enableSsh) { + lines.push(` `) + } + + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + + if (config.enableMonitor) { + lines.push(` `) + } else { + lines.push(` `) + } + + if (config.enableCron) { + lines.push(` `) + lines.push(` `) + if (config.filterRule && config.filterExpression) { + const expressions = config.filterExpression.split(/\s+/).filter(Boolean) + for (const expr of expressions) { + lines.push(` `) + } + } + lines.push(` `) + lines.push(` `) + } else { + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + } + + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(` `) + lines.push(``) + + return lines.join('\n') +} diff --git a/src/schemas/subversion.js b/src/schemas/subversion.js new file mode 100644 index 0000000..5df0085 --- /dev/null +++ b/src/schemas/subversion.js @@ -0,0 +1,224 @@ +export const subversionSchema = { + id: 'subversion', + name: 'Subversion (SVN)', + icon: 'Files', + category: '系统服务', + description: '集中式版本控制系统', + format: 'conf', + fileName: 'svnserve.conf', + groups: [ + { + title: '访问控制', + fields: [ + { + key: 'anonAccess', + label: '匿名访问权限', + type: 'select', + options: [ + { label: 'none - 禁止匿名访问 (推荐)', value: 'none' }, + { label: 'read - 匿名用户只读', value: 'read' }, + { label: 'write - 匿名用户可写', value: 'write' }, + ], + default: 'none', + }, + { + key: 'authAccess', + label: '认证用户权限', + type: 'select', + options: [ + { label: 'write - 认证用户可写 (推荐)', value: 'write' }, + { label: 'read - 认证用户只读', value: 'read' }, + ], + default: 'write', + }, + { + key: 'passwordDb', + label: '密码数据库文件', + type: 'text', + default: 'passwd', + tip: '相对于 conf 目录的路径', + }, + { + key: 'authzDb', + label: '权限数据库文件', + type: 'text', + default: 'authz', + tip: '相对于 conf 目录的路径', + }, + { + key: 'realm', + label: '认证域', + type: 'text', + default: 'My Repository', + tip: '认证域名称,用于区分不同的仓库', + }, + { + key: 'listenPort', + label: '监听端口', + type: 'number', + min: 1, + max: 65535, + default: 3690, + }, + ], + }, + { + title: 'SASL 认证', + fields: [ + { + key: 'enableSasl', + label: '启用 SASL 认证', + type: 'switch', + default: false, + tip: '使用 SASL 框架进行认证,支持更高级的加密', + }, + { + key: 'saslRealm', + label: 'SASL 域', + type: 'text', + default: '', + dependsOn: { key: 'enableSasl', value: true }, + tip: 'SASL 认证域,留空使用默认', + }, + { + key: 'minEncryption', + label: '最小加密强度', + type: 'number', + min: 0, + max: 256, + default: 0, + dependsOn: { key: 'enableSasl', value: true }, + tip: '0=不加密, 1=仅完整性, 128=128位, 256=256位', + }, + { + key: 'maxEncryption', + label: '最大加密强度', + type: 'number', + min: 0, + max: 256, + default: 256, + dependsOn: { key: 'enableSasl', value: true }, + tip: '0=不加密, 1=仅完整性, 128=128位, 256=256位', + }, + ], + }, + { + title: '钩子脚本', + fields: [ + { + key: 'enableHooks', + label: '启用钩子脚本', + type: 'switch', + default: false, + tip: '在特定 SVN 事件时执行自定义脚本', + }, + { + key: 'hookType', + label: '钩子类型', + type: 'select', + options: [ + { label: 'post-commit - 提交后执行 (推荐)', value: 'post-commit' }, + { label: 'pre-commit - 提交前执行', value: 'pre-commit' }, + ], + default: 'post-commit', + dependsOn: { key: 'enableHooks', value: true }, + }, + { + key: 'hookScript', + label: '钩子脚本内容', + type: 'text', + default: '#!/bin/sh\n/usr/bin/svn update /var/www/html', + dependsOn: { key: 'enableHooks', value: true }, + tip: '钩子脚本的完整内容', + }, + ], + }, + ], +} + +export function generateSubversionConf(config) { + const lines = [] + lines.push(`# svnserve.conf 配置文件 - 由 ConfTemplate 生成`) + lines.push(`# 生成时间: ${new Date().toLocaleString('zh-CN')}`) + lines.push(`#`) + lines.push(`# 将此文件放置在仓库的 conf/ 目录下`) + lines.push(`# 启动命令: svnserve -d -r /path/to/repos --listen-port ${config.listenPort}`) + lines.push(``) + + // 通用配置 + lines.push(`[general]`) + + // 匿名访问 + lines.push(`# 匿名访问权限`) + lines.push(`anon-access = ${config.anonAccess}`) + lines.push(``) + + // 认证用户访问 + lines.push(`# 认证用户访问权限`) + lines.push(`auth-access = ${config.authAccess}`) + lines.push(``) + + // 密码数据库 + lines.push(`# 密码数据库`) + lines.push(`password-db = ${config.passwordDb}`) + lines.push(``) + + // 权限数据库 + lines.push(`# 权限数据库`) + lines.push(`authz-db = ${config.authzDb}`) + lines.push(``) + + // 认证域 + lines.push(`# 认证域`) + lines.push(`realm = ${config.realm}`) + lines.push(``) + + // SASL 认证 + if (config.enableSasl) { + lines.push(`[sasl]`) + lines.push(`# 启用 SASL 认证`) + lines.push(`use-sasl = true`) + + if (config.saslRealm) { + lines.push(`sasl-realm = ${config.saslRealm}`) + } + + lines.push(`min-encryption = ${config.minEncryption}`) + lines.push(`max-encryption = ${config.maxEncryption}`) + lines.push(``) + } + + // 钩子脚本说明 + if (config.enableHooks) { + lines.push(`# ======================== 钩子脚本 ========================`) + lines.push(`# 以下为 ${config.hookType} 钩子脚本`) + lines.push(`# 请将此内容保存为: <仓库路径>/hooks/${config.hookType}`) + lines.push(`# 并确保有执行权限: chmod +x <仓库路径>/hooks/${config.hookType}`) + lines.push(`#`) + lines.push(`# ---- 脚本内容开始 ----`) + lines.push(config.hookScript) + lines.push(`# ---- 脚本内容结束 ----`) + lines.push(`#`) + } + + // passwd 文件示例 + lines.push(`# ======================== 附录 ========================`) + lines.push(`# passwd 文件示例 (${config.passwordDb}):`) + lines.push(`# [users]`) + lines.push(`# admin = admin123`) + lines.push(`# developer = dev123`) + lines.push(`#`) + lines.push(`# authz 文件示例 (${config.authzDb}):`) + lines.push(`# [groups]`) + lines.push(`# admin = admin`) + lines.push(`# developers = developer`) + lines.push(`#`) + lines.push(`# [/]`) + lines.push(`# @admin = rw`) + lines.push(`# * = ${config.anonAccess === 'none' ? '' : config.anonAccess}`) + lines.push(`#`) + lines.push(`# [/trunk]`) + lines.push(`# @developers = rw`) + + return lines.join('\n') +} diff --git a/src/schemas/vsftpd.js b/src/schemas/vsftpd.js new file mode 100644 index 0000000..9ca0358 --- /dev/null +++ b/src/schemas/vsftpd.js @@ -0,0 +1,296 @@ +export const vsftpdSchema = { + id: 'vsftpd', + name: 'vsftpd', + icon: 'Upload', + category: '系统服务', + description: '非常安全的 FTP 守护进程', + format: 'conf', + fileName: 'vsftpd.conf', + groups: [ + { + title: '基础配置', + fields: [ + { + key: 'listenPort', + label: '监听端口', + type: 'number', + min: 1, + max: 65535, + default: 21, + }, + { + key: 'anonymousEnable', + label: '允许匿名访问', + type: 'switch', + default: false, + tip: '生产环境建议关闭', + }, + { + key: 'localEnable', + label: '允许本地用户登录', + type: 'switch', + default: true, + }, + { + key: 'writeEnable', + label: '允许写操作', + type: 'switch', + default: true, + tip: 'FTP 写命令权限 (STOR, DELE 等)', + }, + { + key: 'localUmask', + label: '本地用户 umask', + type: 'text', + default: '022', + tip: '新建文件/目录的权限掩码', + }, + ], + }, + { + title: '用户限制', + fields: [ + { + key: 'chrootLocalUser', + label: '限制用户在家目录', + type: 'switch', + default: true, + tip: 'chroot 将用户限制在其主目录', + }, + { + key: 'allowWriteableChroot', + label: '允许可写 chroot', + type: 'switch', + default: true, + dependsOn: { key: 'chrootLocalUser', value: true }, + tip: 'chroot 目录可写时需要开启', + }, + { + key: 'maxClients', + label: '最大客户端数', + type: 'number', + min: 1, + max: 10000, + default: 100, + }, + { + key: 'maxPerIp', + label: '单 IP 最大连接数', + type: 'number', + min: 1, + max: 100, + default: 5, + }, + { + key: 'idleSessionTimeout', + label: '空闲会话超时 (秒)', + type: 'number', + min: 30, + max: 3600, + default: 300, + }, + { + key: 'dataConnectionTimeout', + label: '数据连接超时 (秒)', + type: 'number', + min: 30, + max: 3600, + default: 120, + }, + ], + }, + { + title: '被动模式', + fields: [ + { + key: 'pasvEnable', + label: '启用被动模式', + type: 'switch', + default: true, + tip: '防火墙/NAT 后的客户端需要被动模式', + }, + { + key: 'pasvMinPort', + label: '被动模式最小端口', + type: 'number', + min: 1024, + max: 65535, + default: 30000, + dependsOn: { key: 'pasvEnable', value: true }, + }, + { + key: 'pasvMaxPort', + label: '被动模式最大端口', + type: 'number', + min: 1024, + max: 65535, + default: 30100, + dependsOn: { key: 'pasvEnable', value: true }, + }, + ], + }, + { + title: 'TLS/SSL 安全', + fields: [ + { + key: 'enableTls', + label: '启用 TLS/SSL', + type: 'switch', + default: false, + tip: '通过 FTPS 加密传输', + }, + { + key: 'rsaCertFile', + label: 'SSL 证书路径', + type: 'text', + default: '/etc/ssl/certs/vsftpd.pem', + dependsOn: { key: 'enableTls', value: true }, + }, + { + key: 'rsaPrivateKeyFile', + label: 'SSL 私钥路径', + type: 'text', + default: '/etc/ssl/private/vsftpd.key', + dependsOn: { key: 'enableTls', value: true }, + }, + { + key: 'sslTlsv1', + label: '启用 TLSv1', + type: 'switch', + default: true, + dependsOn: { key: 'enableTls', value: true }, + }, + ], + }, + { + title: '用户列表与日志', + fields: [ + { + key: 'userlistEnable', + label: '启用用户列表', + type: 'switch', + default: true, + }, + { + key: 'userlistDeny', + label: '用户列表为黑名单', + type: 'switch', + default: false, + dependsOn: { key: 'userlistEnable', value: true }, + tip: 'false=白名单 (允许列表中用户), true=黑名单 (拒绝列表中用户)', + }, + { + key: 'logEnable', + label: '启用日志', + type: 'switch', + default: true, + }, + { + key: 'xferlogFile', + label: '传输日志路径', + type: 'text', + default: '/var/log/vsftpd.log', + dependsOn: { key: 'logEnable', value: true }, + }, + { + key: 'asciiUploadEnable', + label: 'ASCII 上传模式', + type: 'switch', + default: false, + tip: '允许 ASCII 模式上传', + }, + { + key: 'asciiDownloadEnable', + label: 'ASCII 下载模式', + type: 'switch', + default: false, + tip: '允许 ASCII 模式下载', + }, + { + key: 'ftpdBanner', + label: '欢迎横幅', + type: 'text', + default: 'Welcome to FTP service', + tip: '用户登录时显示的欢迎信息', + }, + ], + }, + ], +} + +export function generateVsftpdConf(config) { + const lines = [] + lines.push(`# vsftpd.conf 配置文件 - 由 ConfTemplate 生成`) + lines.push(`# 生成时间: ${new Date().toLocaleString('zh-CN')}`) + lines.push(``) + + // 基础配置 + lines.push(`# ======================== 基础配置 ========================`) + lines.push(`listen=YES`) + lines.push(`listen_port=${config.listenPort}`) + lines.push(`anonymous_enable=${config.anonymousEnable ? 'YES' : 'NO'}`) + lines.push(`local_enable=${config.localEnable ? 'YES' : 'NO'}`) + lines.push(`write_enable=${config.writeEnable ? 'YES' : 'NO'}`) + lines.push(`local_umask=${config.localUmask}`) + lines.push(`dirmessage_enable=YES`) + lines.push(`use_localtime=YES`) + lines.push(`xferlog_enable=YES`) + lines.push(`connect_from_port_20=YES`) + lines.push(``) + + // 用户限制 + lines.push(`# ======================== 用户限制 ========================`) + lines.push(`chroot_local_user=${config.chrootLocalUser ? 'YES' : 'NO'}`) + if (config.chrootLocalUser) { + lines.push(`allow_writeable_chroot=${config.allowWriteableChroot ? 'YES' : 'NO'}`) + } + lines.push(`max_clients=${config.maxClients}`) + lines.push(`max_per_ip=${config.maxPerIp}`) + lines.push(`idle_session_timeout=${config.idleSessionTimeout}`) + lines.push(`data_connection_timeout=${config.dataConnectionTimeout}`) + lines.push(``) + + // 被动模式 + lines.push(`# ======================== 被动模式 ========================`) + lines.push(`pasv_enable=${config.pasvEnable ? 'YES' : 'NO'}`) + if (config.pasvEnable) { + lines.push(`pasv_min_port=${config.pasvMinPort}`) + lines.push(`pasv_max_port=${config.pasvMaxPort}`) + } + lines.push(``) + + // TLS/SSL 安全 + lines.push(`# ======================== TLS/SSL 安全 ========================`) + if (config.enableTls) { + lines.push(`ssl_enable=YES`) + lines.push(`rsa_cert_file=${config.rsaCertFile}`) + lines.push(`rsa_private_key_file=${config.rsaPrivateKeyFile}`) + lines.push(`ssl_tlsv1=${config.sslTlsv1 ? 'YES' : 'NO'}`) + lines.push(`ssl_sslv2=NO`) + lines.push(`ssl_sslv3=NO`) + lines.push(`require_ssl_reuse=NO`) + lines.push(`ssl_ciphers=HIGH`) + } else { + lines.push(`ssl_enable=NO`) + } + lines.push(``) + + // 用户列表与日志 + lines.push(`# ======================== 用户列表与日志 ========================`) + lines.push(`userlist_enable=${config.userlistEnable ? 'YES' : 'NO'}`) + if (config.userlistEnable) { + lines.push(`userlist_deny=${config.userlistDeny ? 'YES' : 'NO'}`) + } + lines.push(`log_enable=${config.logEnable ? 'YES' : 'NO'}`) + if (config.logEnable) { + lines.push(`xferlog_file=${config.xferlogFile}`) + lines.push(`xferlog_std_format=YES`) + } + lines.push(`ascii_upload_enable=${config.asciiUploadEnable ? 'YES' : 'NO'}`) + lines.push(`ascii_download_enable=${config.asciiDownloadEnable ? 'YES' : 'NO'}`) + lines.push(`ftpd_banner=${config.ftpdBanner}`) + lines.push(`pam_service_name=vsftpd`) + lines.push(`tcp_wrappers=YES`) + + return lines.join('\n') +}