diff --git a/.gitignore b/.gitignore index b8026d0..f756ed7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +main sunhpc testgui diff --git a/build-sunhpc.sh b/build-sunhpc.sh new file mode 100755 index 0000000..b39e119 --- /dev/null +++ b/build-sunhpc.sh @@ -0,0 +1,31 @@ +#!/bin/bash +set -e # 出错立即退出,便于定位问题 + +# ========== 核心配置(根据你的项目修改) ========== +# 1. 必须和go.mod中的module名称一致(关键!) +MODULE_NAME="sunhpc" +# 2. 编译的入口文件路径(你的main.go在cmd/sunhpc下) +ENTRY_FILE="./cmd/sunhpc/main.go" +# 3. 输出的可执行文件名称 +APP_NAME="sunhpc" +# 4. 自定义版本号 +APP_VERSION="v1.0.0" + +# ========== 获取Git和编译信息(兼容无Git环境) ========== +GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") +GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown") +BUILD_TIME=$(date +"%Y-%m-%d-%H:%M:%S") + +# ========== 编译(核心修复:ldflags格式) ========== +# 关键:去掉反斜杠,用单层双引号包裹,内部换行分隔参数 +go build -ldflags " +-X ${MODULE_NAME}/pkg/info.Version=${APP_VERSION} +-X ${MODULE_NAME}/pkg/info.BuildTime=${BUILD_TIME} +-X ${MODULE_NAME}/pkg/info.GitCommit=${GIT_COMMIT} +-X ${MODULE_NAME}/pkg/info.GitBranch=${GIT_BRANCH} +" -o ${APP_NAME} ${ENTRY_FILE} + +# ========== 验证提示 ========== +echo "- 执行文件:./${APP_NAME}" +echo "- 版本信息:${APP_VERSION} (Git: ${GIT_COMMIT} @ ${GIT_BRANCH})" +echo "- 编译时间:${BUILD_TIME}" diff --git a/cmd/test/main.go b/cmd/test/main.go index 214d6fe..86e6adc 100644 --- a/cmd/test/main.go +++ b/cmd/test/main.go @@ -1,66 +1,9 @@ package main import ( - "fmt" - - tea "github.com/charmbracelet/bubbletea" + "sunhpc/pkg/info" ) -// model 定义应用的状态 -type model struct { - items []string // 列表数据 - selectedIdx int // 当前选中的索引 -} - -// Init 初始化模型,返回初始命令(这里不需要,返回 nil) -func (m model) Init() tea.Cmd { - return nil -} - -// Update 处理用户输入和状态更新 -func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - // 处理键盘输入 - case tea.KeyMsg: - switch msg.String() { - // Tab 键:切换选中项(循环切换) - case "tab": - m.selectedIdx = (m.selectedIdx + 1) % len(m.items) - // Ctrl+C 或 q 键:退出程序 - case "ctrl+c", "q": - return m, tea.Quit - } - } - return m, nil -} - -// View 渲染界面 -func (m model) View() string { - s := "网络接口列表(按 Tab 切换选择,按 q 退出)\n\n" - - // 遍历列表项,渲染每一项 - for i, item := range m.items { - // 标记当前选中的项 - if i == m.selectedIdx { - s += fmt.Sprintf("→ %s (选中)\n", item) - } else { - s += fmt.Sprintf(" %s\n", item) - } - } - - return s -} - func main() { - // 初始化模型,设置列表数据 - initialModel := model{ - items: []string{"eth0", "eth1", "eth2", "eth3"}, - selectedIdx: 0, // 默认选中第一个项 - } - - // 启动 Bubble Tea 程序 - p := tea.NewProgram(initialModel) - if _, err := p.Run(); err != nil { - fmt.Printf("程序运行出错: %v\n", err) - } + info.PrintAllInfo() } diff --git a/internal/cli/init/db.go b/internal/cli/init/db.go index d0a4652..ebe48b1 100644 --- a/internal/cli/init/db.go +++ b/internal/cli/init/db.go @@ -35,7 +35,8 @@ func NewInitDBCmd() *cobra.Command { defer db.Close() if err := database.InitTables(db, force); err != nil { - return fmt.Errorf("数据库初始化失败: %w", err) + logger.Debug(err) + return err } // 测试数据库连接 diff --git a/pkg/config/config.go b/pkg/config/config.go index 5f9b856..6042a7a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -73,10 +73,28 @@ func InitConfigs() error { return fmt.Errorf("创建默认配置文件失败: %w", err) } } + + // 读取配置文件,根据配置文件内容初始化相关目录. + cfg, err := LoadConfig() + if err != nil { + return fmt.Errorf("读取配置文件失败: %w", err) + } + + // 初始化日志 + logger.Init(cfg.Log) + + // 确保数据库目录存在 + if err := os.MkdirAll(cfg.Database.Path, 0755); err != nil { + logger.Debugf("创建数据库目录 %s 失败: %v", cfg.Database.Path, err) + return err + } + logger.Debugf("创建数据库目录 %s 成功", cfg.Database.Path) + return nil } func createDefaultConfig(configPath string) error { + fmt.Printf("设置默认配置文件: %s\n", configPath) defaultConfig := &Config{ Database: DatabaseConfig{ Path: utils.DefaultDBPath, @@ -93,11 +111,6 @@ func createDefaultConfig(configPath string) error { }, } - // 确保数据库目录存在 - if err := os.MkdirAll(defaultConfig.Database.Path, 0755); err != nil { - return fmt.Errorf("创建数据库目录失败: %w", err) - } - // 序列号并写入 data, err := yaml.Marshal(defaultConfig) if err != nil { @@ -108,6 +121,7 @@ func createDefaultConfig(configPath string) error { // ----------------------------------- 配置加载(只加载一次) ----------------------------------- func LoadConfig() (*Config, error) { + configMutex.RLock() if GlobalConfig != nil { // 如果已经加载过,直接返回 diff --git a/pkg/database/database.go b/pkg/database/database.go index d6a5beb..ccfd4bf 100644 --- a/pkg/database/database.go +++ b/pkg/database/database.go @@ -1,11 +1,8 @@ package database import ( - "bufio" "database/sql" "fmt" - "os" - "strings" "sync" "sunhpc/pkg/config" @@ -25,10 +22,114 @@ var ( dbErr error ) +// ========================================================= +// 封装数据库函数使用Go实现 +// ========================================================= +// MapCategory - 根据类别名称查ID +// 查询方式: globalID, err := db.MapCategory(conn, "global") +func MapCategory(conn *sql.DB, catname string) (int, error) { + var id int + query := "select id from categories where name = ?" + logger.Debugf("查询SQL: %s", query) + logger.Debugf("查询类别ID: %s", catname) + err := conn.QueryRow(query, catname).Scan(&id) + if err == sql.ErrNoRows { + logger.Debugf("未找到类别 %s, 返回ID=0", catname) + return 0, nil // 无匹配返回0 + } + logger.Debugf("查询到类别 %s, ID=%d", catname, id) + return id, nil +} + +// MapCategoryIndex - 根据类别名称 + 索引名称查ID +// 调用方式: linuxOSID, err := db.MapCategoryIndex(conn, "os", "linux") +func MapCategoryIndex(conn *sql.DB, catindexName, categoryIndex string) (int, error) { + var id int + query := ` + select index_id from vmapCategoryIndex + where categoryName = ? and categoryIndex = ? + ` + logger.Debugf("查询SQL: %s", query) + logger.Debugf("查询索引ID: %s, 类别: %s", catindexName, categoryIndex) + err := conn.QueryRow(query, catindexName, categoryIndex).Scan(&id) + if err == sql.ErrNoRows { + logger.Debugf("未找到索引 %s, 返回ID=0", catindexName) + return 0, nil // 无匹配返回0 + } + logger.Debugf("查询到索引 %s, ID=%d", catindexName, id) + return id, nil +} + +// ResolveFirewalls - 解析指定主机的防火墙规则 +// 返回解析后的防火墙规则(fwresolved表数据),临时表使用后自动清理 +// 调用方式: rows, err := db.ResolveFirewalls(conn, "compute-0-1", "default") +func ResolveFirewalls(conn *sql.DB, hostname, chainname string) (*sql.Rows, error) { + // 步骤1: 创建临时表 fresolved1 + _, err := conn.Exec(` + DROP TABLE IF EXISTS fresolved1; + CREATE TEMPORARY TABLE fresolved1 AS + SELECT + ? AS hostname, + ? AS Resolver, + f.*, + r.precedence + FROM + resolvechain r + inner join hostselections hs on r.category = hs.category and r.name = ? + inner join firewalls f on hs.category = f.category and hs.selection = f.catindex + where hs.host = ?; + `, hostname, chainname, chainname, hostname) + if err != nil { + return nil, fmt.Errorf("Create temporary table fresolved1 failed: %w", err) + } + + // 步骤2:创建临时表 fresolved2 + _, err = conn.Exec(` + DROP TABLE IF EXISTS fresolved2; + CREATE TEMPORARY TABLE fresolved2 AS + SELECT + * + FROM + fresolved1; + `) + if err != nil { + return nil, fmt.Errorf("Create temporary table fresolved2 failed: %w", err) + } + + // 步骤3:创建最终结果表 fwresolved + _, err = conn.Exec(` + DROP TABLE IF EXISTS fwresolved; + CREATE TEMPORARY TABLE fwresolved AS + SELECT + r1.*, + cat.name AS categoryName + FROM + fresolved1 r1 + inner join ( + select Rulename, MAX(precedence) as precedence + from fresolved2 + group by Rulename + ) AS r2 on r1.Rulename = r2.Rulename and r1.precedence = r2.precedence + inner join categories cat on r1.category = cat.id; + `) + if err != nil { + return nil, fmt.Errorf("Create temporary table fwresolved failed: %w", err) + } + + // 步骤4:查询结果并返回 + rows, err := conn.Query("SELECT * FROM fwresolved") + if err != nil { + return nil, fmt.Errorf("Query fwresolved failed: %w", err) + } + return rows, nil +} + // ========================================================= // GetDB - 获取数据库连接(单例模式) // ========================================================= func GetDB() (*sql.DB, error) { + logger.Debug("获取数据库连接...") + dbOnce.Do(func() { if dbInstance != nil { return @@ -75,45 +176,11 @@ func GetDB() (*sql.DB, error) { return dbInstance, nil } -func confirmAction(prompt string) bool { - reader := bufio.NewReader(os.Stdin) - - logger.Warnf("%s [Y/Yes]: ", prompt) - response, err := reader.ReadString('\n') - if err != nil { - return false - } - - response = strings.ToLower(strings.TrimSpace(response)) - return response == "y" || response == "yes" -} - func InitTables(db *sql.DB, force bool) error { - if force { - // 确认是否强制删除 - if !confirmAction("确认强制删除所有表和触发器?") { - logger.Info("操作已取消") - db.Close() - os.Exit(0) - return nil - } - - // 强制删除所有表和触发器 - logger.Debug("强制删除所有表和触发器...") - if err := dropTables(db); err != nil { - return fmt.Errorf("删除表失败: %w", err) - } - logger.Debug("删除所有表和触发器成功") - - if err := dropTriggers(db); err != nil { - return fmt.Errorf("删除触发器失败: %w", err) - } - logger.Debug("删除所有触发器成功") - } - // ✅ 调用 schema.go 中的函数 - for _, ddl := range CreateTableStatements() { + //for _, ddl := range CreateTableStatements() { + for _, ddl := range BaseTables() { logger.Debugf("执行: %s", ddl) if _, err := db.Exec(ddl); err != nil { return fmt.Errorf("数据表创建失败: %w", err) @@ -128,26 +195,12 @@ func InitTables(db *sql.DB, force bool) error { select * from sqlite_master where type='table'; # 查看表定义 PRAGMA integrity_check; # 检查数据库完整性 */ - return nil -} -func dropTables(db *sql.DB) error { - // ✅ 调用 schema.go 中的函数 - for _, table := range DropTableOrder() { - if _, err := db.Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table)); err != nil { - return err - } - } - return nil -} - -func dropTriggers(db *sql.DB) error { - // ✅ 调用 schema.go 中的函数 - for _, trigger := range DropTriggerStatements() { - if _, err := db.Exec(fmt.Sprintf("DROP TRIGGER IF EXISTS `%s`", trigger)); err != nil { - return err - } + // 添加基础数据 + if err := InitBaseData(db); err != nil { + return fmt.Errorf("初始化基础数据失败: %w", err) } + logger.Info("基础数据初始化成功") return nil } @@ -212,3 +265,64 @@ func TestNodeInsert(db *sql.DB) error { return nil }) } + +// ========================================================= +// 带事务执行 SQL 语句,自动提交/回滚 +// ========================================================= + +// 执行单条SQL语句,带事务管理 +func ExecSingleWithTransaction(sqlStr string) error { + // 复用批量函数,将单条SQL语句包装为数组执行 + return ExecWithTransaction([]string{sqlStr}) +} + +// 批量执行 DDL 语句,带事务管理 +func ExecWithTransaction(ddl []string) error { + conn, err := GetDB() + if err != nil { + logger.Errorf("获取数据库连接失败: %v", err) + return err + } + + // 开始事务 + tx, err := conn.Begin() + if err != nil { + logger.Errorf("开始事务失败: %v", err) + return err + } + + // 延迟处理:如果函数异常,回滚事务 + defer func() { + if r := recover(); r != nil { + // 捕获 panic 并回滚事务 + tx.Rollback() + logger.Errorf("事务执行中发生 panic: %v", r) + } + }() + + // 遍历执行 DDL 语句 + for idx, sql := range ddl { + logger.Debugf("执行 DDL 语句 %d: %s", idx+1, sql) + _, err = tx.Exec(sql) + if err != nil { + // 执行失败时,回滚事务 + rollbackErr := tx.Rollback() + if rollbackErr != nil { + logger.Errorf("执行失败: 回滚失败: %v (原错误: %v, SQL: %s)", rollbackErr, err, sql) + } else { + logger.Errorf("执行失败: 回滚事务: %v, SQL: %s", err, sql) + } + logger.Errorf("执行 %d 条, 失败: %w (SQL: %s)", idx+1, err, sql) + } + } + + // 所有SQL语句执行成功,提交事务 + logger.Info("所有SQL语句执行成功,提交事务") + if err := tx.Commit(); err != nil { + logger.Errorf("提交事务失败: %w", err) + return err + } + + logger.Debugf("成功执行 %d 条 SQL 语句, 事务已提交.", len(ddl)) + return nil +} diff --git a/pkg/database/schema.go b/pkg/database/schema.go index 10befb9..aeea9e6 100644 --- a/pkg/database/schema.go +++ b/pkg/database/schema.go @@ -1,294 +1,571 @@ // Package db defines the database schema. package database -// CurrentSchemaVersion returns the current schema version (for migrations) -func CurrentSchemaVersion() int { - return 1 +import ( + "database/sql" + "fmt" +) + +func BaseTables() []string { + + datalist := []string{} + + Appliances := ` + CREATE TABLE IF NOT EXISTS appliances ( + ID integer primary key autoincrement, + Name varchar(32) not null default '', + Graph varchar(64) not null default 'default', + Node varchar(64) not null default '', + OS varchar(64) not null default 'linux' + ); + ` + datalist = append(datalist, Appliances) + + Memberships := ` + CREATE TABLE IF NOT EXISTS memberships ( + ID integer primary key autoincrement, + Name varchar(64) not null default '', + Appliance integer(11) default '0', + Distribution integer(11) default '1', + Public varchar(64) not null default 'no' + ); + ` + datalist = append(datalist, Memberships) + + Categories := ` + CREATE TABLE IF NOT EXISTS categories ( + ID integer primary key autoincrement, + Name varchar(64) not null unique default '0', + Description varchar(255) default null, + UNIQUE(Name) + ); + ` + datalist = append(datalist, Categories) + + Catindex := ` + CREATE TABLE IF NOT EXISTS catindex ( + ID integer primary key autoincrement, + Name varchar(64) not null unique default '0', + Category integer not null, + Foreign key(Category) references categories(ID) on delete cascade + ); + ` + datalist = append(datalist, Catindex) + + Resolvechain := ` + CREATE TABLE IF NOT EXISTS resolvechain ( + ID integer primary key autoincrement, + Name varchar(64) not null default '0', + Category integer(11) not null, + Precedence integer(11) not null default '10', + UNIQUE(Name, Category) + Foreign key(Category) references categories(ID) on delete cascade + ); + ` + datalist = append(datalist, Resolvechain) + + Nodes := ` + CREATE TABLE IF NOT EXISTS nodes ( + ID integer primary key autoincrement, + Name varchar(128) default null, + Membership integer(11) default '2', + CPUs integer(11) not null default '1', + Rack varchar(11) default null, + Rank integer(11) default null, + Arch varchar(32) default null, + OS varchar(64) not null default 'linux', + RunAction varchar(64) default 'os', + InstallAction varchar(64) default 'install' + ); + create index if not exists idx_nodes_name on nodes(Name); + ` + datalist = append(datalist, Nodes) + + Aliases := ` + CREATE TABLE IF NOT EXISTS aliases ( + ID integer primary key autoincrement, + Node integer(15) not null default '0', + Name varchar(32) default null, + Foreign key(Node) references nodes(ID) on delete cascade + ); + ` + datalist = append(datalist, Aliases) + + Networks := ` + CREATE TABLE IF NOT EXISTS networks ( + ID integer primary key autoincrement, + Node integer(11) default null, + MAC varchar(64) default null, + IP varchar(64) default null, + Name varchar(128) default null, + Device varchar(32) default null, + Subnet integer(11) default null, + Module varchar(128) default null, + VlanID integer(11) default null, + Options varchar(128) default null, + Channel varchar(128) default null, + Foreign key(Node) references nodes(ID) on delete cascade, + Foreign key(Subnet) references subnets(ID) on delete cascade + ); + ` + datalist = append(datalist, Networks) + + GlobalRoutes := ` + CREATE TABLE IF NOT EXISTS globalroutes ( + Network varchar(32) not null default '', + Netmask varchar(32) not null default '', + Gateway varchar(32) not null default '', + Subnet integer(11) default null, + Primary key(Network, Netmask) + Foreign key(Subnet) references subnets(ID) on delete cascade + ); + ` + datalist = append(datalist, GlobalRoutes) + + OSRoutes := ` + CREATE TABLE IF NOT EXISTS osroutes ( + OS varchar(64) not null default 'linux', + Network varchar(32) not null default '', + Netmask varchar(32) not null default '', + Gateway varchar(32) not null default '', + Subnet integer(11) default null, + Primary key(OS, Network, Netmask) + Foreign key(Subnet) references subnets(ID) on delete cascade + ); + ` + datalist = append(datalist, OSRoutes) + + ApplianceRoutes := ` + CREATE TABLE IF NOT EXISTS applianceroutes ( + Appliance varchar(11) not null default '0', + Network varchar(32) not null default '', + Netmask varchar(32) not null default '', + Gateway varchar(32) not null default '', + Subnet integer(11) default null, + Primary key(Appliance, Network, Netmask) + Foreign key(Subnet) references subnets(ID) on delete cascade + ); + ` + datalist = append(datalist, ApplianceRoutes) + + NodeRoutes := ` + CREATE TABLE IF NOT EXISTS noderoutes ( + Node varchar(11) not null default '0', + Network varchar(32) not null default '', + Netmask varchar(32) not null default '', + Gateway varchar(32) not null default '', + Subnet integer(11) default null, + Primary key(Node, Network, Netmask) + Foreign key(Subnet) references subnets(ID) on delete cascade + ); + ` + datalist = append(datalist, NodeRoutes) + + Subnets := ` + CREATE TABLE IF NOT EXISTS subnets ( + ID integer primary key autoincrement, + name varchar(32) unique not null, + dnszone varchar(64) unique not null, + subnet varchar(32) default null, + netmask varchar(32) default null, + mtu integer(11) default '1500', + servedns boolean default false + ); + ` + datalist = append(datalist, Subnets) + + PublicKeys := ` + CREATE TABLE IF NOT EXISTS publickeys ( + ID integer primary key autoincrement, + Node integer(11) not null default '0', + Public_Key varchar(8192) default null, + Description varchar(8192) default null, + Foreign key(Node) references nodes(ID) on delete cascade + ); + ` + datalist = append(datalist, PublicKeys) + + SecGlobal := ` + CREATE TABLE IF NOT EXISTS secglobal ( + Attr varchar(128) default null, + Value text, + Enc varchar(128) default null, + Primary key(Attr) + ); + ` + datalist = append(datalist, SecGlobal) + + SecNodes := ` + CREATE TABLE IF NOT EXISTS secnodes ( + Attr varchar(128) default null, + Enc varchar(128) default null, + Value text, + Node integer(15) not null default '0', + Primary key(Attr, Node) + ); + ` + datalist = append(datalist, SecNodes) + + Attributes := ` + CREATE TABLE IF NOT EXISTS attributes ( + ID integer primary key autoincrement, + Attr varchar(128) not null, + Value text, + Shadow text, + Category integer(11) not null, + Catindex integer(11) not null, + UNIQUE(Attr, Category, Catindex), + Foreign key(Catindex) references catindex(ID) on delete cascade + ); + ` + datalist = append(datalist, Attributes) + + Partitions := ` + CREATE TABLE IF NOT EXISTS partitions ( + ID integer primary key autoincrement, + Node integer(15) not null default '0', + Device varchar(128) not null default '', + MountPoint varchar(128) not null default '', + SectorStart varchar(128) not null default '', + PartitionSize varchar(128) not null default '', + FsType varchar(128) not null default '', + PartitionFlags varchar(128) not null default '', + FormatFlags varchar(128) not null default '' + ); + ` + datalist = append(datalist, Partitions) + + Firewalls := ` + CREATE TABLE IF NOT EXISTS firewalls ( + ID integer primary key autoincrement, + Rulename varchar(128) not null, + Rulesrc varchar(256) not null default 'custom', + InSubnet int(11), + OutSubnet int(11), + Service varchar(256), + Protocol varchar(256), + Action varchar(256), + Chain varchar(256), + Flags varchar(256), + Comment varchar(256), + Category integer(11) not null, + Catindex integer(11) not null, + Check(rulesrc IN ('system', 'custom')) + UNIQUE(Rulename, Category, Catindex), + Foreign key(Catindex) references catindex(ID) on delete cascade + ); + ` + datalist = append(datalist, Firewalls) + + Rolls := ` + CREATE TABLE IF NOT EXISTS rolls ( + ID integer primary key autoincrement, + Name varchar(128) not null default '', + Version varchar(32) not null default '', + Arch varchar(32) not null default '', + OS varchar(64) not null default 'linux', + Enabled varchar(3) not null default 'yes', + Check(Enabled IN ('yes', 'no')) + Check(OS IN ('linux', 'other')) + ); + ` + datalist = append(datalist, Rolls) + + NodeRolls := ` + CREATE TABLE IF NOT EXISTS noderolls ( + Node varchar(11) not null default '0', + RollID varchar(11) not null, + Primary key(Node, RollID) + ); + ` + datalist = append(datalist, NodeRolls) + + Bootactions := ` + CREATE TABLE IF NOT EXISTS bootactions ( + ID integer primary key autoincrement, + Action varchar(256) default null, + Kernel varchar(256) default null, + Ramdisk varchar(256) default null, + Args varchar(1024) default null + ); + ` + datalist = append(datalist, Bootactions) + + BootFlags := ` + CREATE TABLE IF NOT EXISTS bootflags ( + ID integer primary key autoincrement, + Node integer(11) not null default '0', + Flags varchar(256) default null + ); + ` + datalist = append(datalist, BootFlags) + + Distributions := ` + CREATE TABLE IF NOT EXISTS distributions ( + ID integer primary key autoincrement, + Name varchar(32) not null default '', + OS varchar(32) default '', + Release varchar(32) default '' + ); + ` + datalist = append(datalist, Distributions) + + View_vnet := ` + DROP VIEW IF EXISTS vnet; + CREATE VIEW vnet AS + SELECT + n.name AS nodename, /* 查询nodes表中name字段,将字段改名为nodename */ + m.name AS membership, + a.name AS appliance, + n.rack, n.rank, /* 查询nodes表中rack和rank字段,使用原始字段名 */ + s.name AS subnet, + nt.ip, nt.device, nt.module, + nt.name AS hostname, + s.dnszone AS domainname, + s.netmask, s.mtu + FROM + nodes n /* 主表: 先查询nodes表,别名n */ + inner join memberships m on n.membership=m.id /* 连接memberships表,on只保留满足条件的行 */ + inner join appliances a on m.appliance=a.id /* 连接appliances表,on只保留满足条件的行 */ + inner join networks nt on n.id=nt.node /* 连接networks表,on只保留满足条件的行 */ + inner join subnets s on nt.subnet=s.id /* 连接subnets表,on只保留满足条件的行 */ + ; + ` + datalist = append(datalist, View_vnet) + + View_hostselections := ` + DROP VIEW IF EXISTS hostselections; + CREATE VIEW hostselections AS + SELECT + n.name AS host, + c.id as category, + ci.id as selection + FROM + nodes n + inner join memberships m on n.membership=m.id -- 节点表关联所属分组 + inner join appliances a on m.appliance=a.id -- 分组关联所属应用角色 + inner join categories c on + -- 匹配4类分层配置的category(全局/OS/应用/主机) + c.name in ('global', 'os', 'appliance', 'host') + inner join catindex ci on + -- 核心匹配逻辑: category和catindex的name字段一一对应 + (c.name = 'global' and ci.name = 'global') or + (c.name = 'os' and ci.name = n.os) or + (c.name = 'appliance' and ci.name = a.name) or + (c.name = 'host' and ci.name = n.name) + ; + ` + datalist = append(datalist, View_hostselections) + + View_vcatindex := ` + -- 视图vcatindex: 类别索引可读试图 + DROP VIEW IF EXISTS vcatindex; + CREATE VIEW vcatindex AS + SELECT + c.id AS ID, + cat.Name AS Category, + ci.Name AS catindex + FROM + categories cat + inner join catindex ci on ci.category=cat.id + ; + ` + datalist = append(datalist, View_vcatindex) + + View_vresolvechain := ` + -- 视图vresolvechain: 解析链可读试图 + DROP VIEW IF EXISTS vresolvechain; + CREATE VIEW vresolvechain AS + SELECT + r.name AS chain, + cat.name AS category, + precedence + FROM + resolvechain r + inner join categories cat on r.category=cat.id + order by chain, precedence + ; + ` + datalist = append(datalist, View_vresolvechain) + + View_vattributes := ` + -- 视图vattributes: 属性可读试图 + DROP VIEW IF EXISTS vattributes; + CREATE VIEW vattributes AS + SELECT + a.id, + attr, + value, + shadow, + cat.name AS category, + ci.name AS catindex + FROM + attributes a + inner join catindex ci on a.catindex=ci.id + inner join categories cat on a.category=cat.id + order by attr, catindex, category + ; + ` + datalist = append(datalist, View_vattributes) + + View_vfirewalls := ` + -- 视图vfirewalls: 防火墙规则可读试图 + DROP VIEW IF EXISTS vfirewalls; + CREATE VIEW vfirewalls AS + SELECT + f.id, + f.Rulename, + f.Rulesrc, + f.InSubnet, + f.OutSubnet, + f.Service, + f.Protocol, + f.Action, + f.Chain, + f.Flags, + f.Comment, + cat.name AS category, + ci.name AS catindex + FROM + firewalls f + inner join catindex ci on f.catindex=ci.id + inner join categories cat on f.category=cat.id + order by f.Rulename, catindex, category + ; + ` + datalist = append(datalist, View_vfirewalls) + + View_vhostselections := ` + -- 视图vhostselections: 主机选择可读试图 + DROP VIEW IF EXISTS vhostselections; + CREATE VIEW vhostselections AS + SELECT + hs.host AS host, + cat.name AS category, + ci.name AS selection + FROM + hostselections hs + inner join categories cat on hs.category=cat.id + inner join catindex ci on hs.selection=ci.id + order by host, category, selection + ; + ` + datalist = append(datalist, View_vhostselections) + + View_vmapCategoryIndex := ` + -- 视图vmapcategoryindex: 类别索引映射可读试图 + DROP VIEW IF EXISTS vmapcategoryindex; + CREATE VIEW vmapCategoryIndex AS + SELECT + cat.name AS categoryName, + ci.name AS categoryIndex, + ci.ID AS index_ID + FROM + cateindex ci + inner join categories cat on ci.category=cat.id + ; + ` + datalist = append(datalist, View_vmapCategoryIndex) + + return datalist } -// CreateTableStatements returns a list of CREATE TABLE statements. -func CreateTableStatements() []string { - return []string{ - createAliasesTable(), - createAttributesTable(), - createBootactionTable(), - createDistributionsTable(), - createFirewallsTable(), - createNetworksTable(), - createPartitionsTable(), - createPublicKeysTable(), - createSoftwareTable(), - createNodesTable(), - createSubnetsTable(), - createTrg_nodes_before_delete(), +func InitBaseData(conn *sql.DB) error { + // ========== 第一步:插入 categories 数据 ========== + categoryData := []struct { + Name string + Description string + }{ + {"global", "Global Defaults"}, + {"os", "OS Choice(Linux,Sunos)"}, + {"appliance", "Logical Appliances"}, + {"rack", "Machine Room Racks"}, + {"host", "Hosts - Physical AND Virtual"}, } -} -// DropTableOrder returns table names in reverse dependency order for safe DROP. -func DropTableOrder() []string { - return []string{ - "aliases", - "attributes", - "bootactions", - "distributions", - "firewalls", - "networks", - "partitions", - "publickeys", - "software", - "nodes", - "subnets", + // 批量插入 categories (忽略重复) + for _, cd := range categoryData { + query := ` + insert or ignore into categories (Name, Description) + values (?, ?) + ` + _, err := conn.Exec(query, cd.Name, cd.Description) + if err != nil { + return fmt.Errorf("error inserting category %s: %w", cd.Name, err) + } } -} -func DropTriggerStatements() []string { - return []string{ - "trg_nodes_before_delete", + // ========== 第二步:插入 catindex 数据 ========== + catindexData := []struct { + Name string + Category string + }{ + {"global", "global"}, + {"linux", "os"}, + {"sunos", "os"}, + {"frontend", "appliance"}, + {"compute", "appliance"}, + {"nas", "appliance"}, + {"network", "appliance"}, + {"power", "appliance"}, + {"devel-server", "appliance"}, + {"login", "appliance"}, } -} - -// --- Private DDL Functions --- -func createAliasesTable() string { - return ` -CREATE TABLE IF NOT EXISTS aliases ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - node_id INTEGER NOT NULL, - alias TEXT NOT NULL, - CONSTRAINT fk_aliases_node FOREIGN KEY(node_id) REFERENCES nodes(id), - UNIQUE(node_id, alias) -); -create index if not exists idx_aliases_node on aliases (node_id); -` -} - -func createAttributesTable() string { - return ` -CREATE TABLE IF NOT EXISTS attributes ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - node_id INTEGER NOT NULL, - attr TEXT NOT NULL, - value TEXT, - shadow TEXT, - CONSTRAINT fk_attributes_node FOREIGN KEY(node_id) REFERENCES nodes(id) -); -create index if not exists idx_attributes_node on attributes (node_id); -` -} - -func createBootactionTable() string { - return ` -CREATE TABLE IF NOT EXISTS bootactions ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - node_id INTEGER NOT NULL, - action TEXT, - kernel TEXT, - initrd TEXT, - cmdline TEXT, - CONSTRAINT fk_bootactions_node FOREIGN KEY(node_id) REFERENCES nodes(id), - UNIQUE(node_id) -); -create index if not exists idx_bootactions_node on bootactions (node_id); -` -} - -func createDistributionsTable() string { - return ` -CREATE TABLE IF NOT EXISTS distributions ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - node_id INTEGER, - name TEXT NOT NULL, - version TEXT, - lang TEXT, - os_release TEXT, - constraint distributions_nodes_fk FOREIGN KEY(node_id) REFERENCES nodes(id) -); -create index if not exists idx_distributions_node on distributions (node_id); -` -} - -func createFirewallsTable() string { - return ` -CREATE TABLE IF NOT EXISTS firewalls ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - node_id INTEGER, - rulename TEXT NOT NULL, - rulesrc TEXT NOT NULL, - insubnet INTEGER, - outsubnet INTEGER, - service TEXT, - protocol TEXT, - action TEXT, - chain TEXT, - flags TEXT, - comment TEXT, - constraint firewalls_nodes_fk FOREIGN KEY(node_id) REFERENCES nodes(id) -); -create index if not exists idx_firewalls_node on firewalls (node_id); -` -} - -func createNetworksTable() string { - return ` -CREATE TABLE IF NOT EXISTS networks ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - node_id INTEGER, - subnet_id INTEGER, - mac TEXT, - ip TEXT, - name TEXT, - device TEXT, - module TEXT, - vlanid INTEGER, - options TEXT, - channel TEXT, - disable_kvm INTEGER NOT NULL DEFAULT 0 CHECK (disable_kvm IN (0, 1)), - constraint networks_nodes_fk FOREIGN KEY(node_id) REFERENCES nodes(id), - constraint networks_subnets_fk FOREIGN KEY(subnet_id) REFERENCES subnets(id) -); -create index if not exists idx_networks_node on networks (node_id); -` -} - -func createNodesTable() string { - return ` -CREATE TABLE IF NOT EXISTS nodes ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - cpus INTEGER NOT NULL, - rack INTEGER NOT NULL, - rank INTEGER NOT NULL, - arch TEXT, - os TEXT, - runaction TEXT, - installaction TEXT -); -create index if not exists idx_nodes_name on nodes (name); -` -} - -func createPartitionsTable() string { - return ` -CREATE TABLE IF NOT EXISTS partitions ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - node_id INTEGER, - device TEXT NOT NULL, - formatflags TEXT NOT NULL, - fstype TEXT NOT NULL, - mountpoint TEXT NOT NULL, - partitionflags TEXT NOT NULL, - partitionid TEXT NOT NULL, - partitionsize TEXT NOT NULL, - sectorstart TEXT NOT NULL, - constraint partitions_nodes_fk FOREIGN KEY(node_id) REFERENCES nodes(id) -); -create index if not exists idx_partitions_node on partitions (node_id); -` -} - -func createPublicKeysTable() string { - return ` -CREATE TABLE IF NOT EXISTS publickeys ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - node_id INTEGER, - publickey TEXT NOT NULL, - description TEXT, - constraint publickeys_nodes_fk FOREIGN KEY(node_id) REFERENCES nodes(id) -); -create index if not exists idx_publickeys_node on publickeys (node_id); -` -} - -func createSubnetsTable() string { - return ` -CREATE TABLE IF NOT EXISTS subnets ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - dnszone TEXT NOT NULL, - subnet TEXT NOT NULL, - netmask TEXT NOT NULL, - mtu INTEGER NOT NULL DEFAULT 1500, - servedns INTEGER NOT NULL DEFAULT 0 CHECK (servedns IN (0, 1)), - UNIQUE(name, dnszone) -);` -} - -func createSoftwareTable() string { - return ` -CREATE TABLE IF NOT EXISTS software ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - pathversion TEXT, - fullversion TEXT, - description TEXT, - website TEXT, - license TEXT, - install_method TEXT NOT NULL CHECK (install_method IN ( - 'source', - 'binary', - 'rpm', - 'docker', - 'apptainer', - 'conda', - 'mamba', - 'spack', - 'tarball', - 'zipball', - 'pip', - 'npm', - 'custom' - )), - -- 源码编译相关参数 - source_url TEXT, -- 源码下载地址 - source_checksum TEXT, -- 源码校验和 - source_checksum_type TEXT NOT NULL CHECK (source_checksum_type IN ( - 'md5', - 'sha1', - 'sha256', - 'sha512' - )), - build_dependencies TEXT, -- 编译依赖(JSON格式) - configure_params TEXT, -- 配置参数(JSON格式) - make_params TEXT, -- make参数(JSON格式) - make_install_params TEXT, -- make install参数(JSON格式) - - -- 安装路径参数 - install_path TEXT NOT NULL, -- 安装路径 - env_vars TEXT, -- 环境变量(JSON格式) - - -- 状态信息 - is_installed INTEGER NOT NULL DEFAULT 0 CHECK (is_installed IN (0, 1)), -- 是否安装 - install_date TEXT, -- 安装日期 - updated_date TEXT, -- 更新日期 - install_user TEXT, -- 安装用户 - notes TEXT, -- 安装备注 - - -- 元数据 - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间 - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, -- 更新时间 - - UNIQUE(name) -); -create index if not exists idx_software_name on software (name); -create index if not exists idx_software_install_method on software (install_method); -create index if not exists idx_software_is_installed on software (is_installed); -` -} - -func createTrg_nodes_before_delete() string { - return ` -CREATE TRIGGER IF NOT EXISTS trg_nodes_before_delete - BEFORE DELETE ON nodes - FOR EACH ROW - BEGIN - -- 先删除子表的关联记录 - DELETE FROM aliases WHERE node_id = OLD.id; - DELETE FROM attributes WHERE node_id = OLD.id; - DELETE FROM bootactions WHERE node_id = OLD.id; - DELETE FROM distributions WHERE node_id = OLD.id; - DELETE FROM firewalls WHERE node_id = OLD.id; - DELETE FROM networks WHERE node_id = OLD.id; - DELETE FROM partitions WHERE node_id = OLD.id; - DELETE FROM publickeys WHERE node_id = OLD.id; - END; -` + // 批量插入 catindex (忽略重复) + for _, ci := range catindexData { + // 动态获取类别ID (复用MapCategory函数) + catID, err := MapCategory(conn, ci.Category) + if err != nil { + return fmt.Errorf("error mapping category %s: %w", ci.Category, err) + } + if catID == 0 { + return fmt.Errorf("category %s not found", ci.Category) + } + + // 插入 catindex (忽略重复) + query := ` + insert or ignore into catindex (Name, Category) + values (?, ?) + ` + _, err = conn.Exec(query, ci.Name, catID) + if err != nil { + return fmt.Errorf("error inserting catindex %s: %w", ci.Name, err) + } + } + + // ========== 第三步:插入 resolvechain 数据 ========== + resolveChainData := []struct { + Name string // 解析链名称,global/linux/sunos + Category string // 类别名称,linux/sunos + Precedence int // 优先级,数值越大优先级越高 + }{ + {"default", "global", 10}, + {"default", "os", 20}, + {"default", "appliance", 30}, + {"default", "rack", 40}, + {"default", "host", 50}, + } + // 批量插入 resolvechain (忽略重复) + for _, rcd := range resolveChainData { + // 动态获取类别ID (复用MapCategory函数) + catID, err := MapCategory(conn, rcd.Category) + if err != nil { + return fmt.Errorf("error mapping category %s: %w", rcd.Category, err) + } + if catID == 0 { + return fmt.Errorf("category %s not found", rcd.Category) + } + + // 插入 resolvechain (忽略重复) + query := ` + insert or ignore into resolvechain (Name, Category, Precedence) + values (?, ?, ?) + ` + _, err = conn.Exec(query, rcd.Name, catID, rcd.Precedence) + if err != nil { + return fmt.Errorf("error inserting resolvechain %s: %w", rcd.Name, err) + } + } + + return nil } diff --git a/pkg/info/info.go b/pkg/info/info.go new file mode 100644 index 0000000..9f5574e --- /dev/null +++ b/pkg/info/info.go @@ -0,0 +1,162 @@ +package info + +import ( + "bufio" + "fmt" + "os" + "runtime" + "strings" +) + +// -------------------------- 编译注入的静态信息 -------------------------- +var ( + Version = "dev" // 应用版本号 + BuildTime = "unknown" // 编译时间 + GitCommit = "unknown" // Git提交ID + GitBranch = "unknown" // Git分支 +) + +// -------------------------- 固定常量 -------------------------- +const ( + AppName = "sunhpc" + linuxProcVersion = "/proc/version" + linuxProcCpuinfo = "/proc/cpuinfo" +) + +// -------------------------- 系统信息结构体 -------------------------- +// SystemInfo 封装所有系统相关信息(Linux专属) +type SystemInfo struct { + OS string // 操作系统 + Arch string // 系统架构 + KernelVersion string // 内核版本 + CPUModel string // CPU型号 + NumCPU int // CPU核心数 + MemTotal string // 总内存 + Hostname string // 主机名 + GoVersion string // Go运行时版本 +} + +// -------------------------- 通用工具函数(无错误返回) -------------------------- +// readFileFirstLine 读取文件第一行,失败返回空字符串 +func readFileFirstLine(path string) string { + file, err := os.Open(path) + if err != nil { + return "" + } + defer file.Close() + + scanner := bufio.NewScanner(file) + if scanner.Scan() { + return strings.TrimSpace(scanner.Text()) + } + return "" +} + +// -------------------------- 核心函数(无错误返回) -------------------------- +// readCPUModel 读取CPU型号,失败返回 "unknown" +func readCPUModel() string { + file, err := os.Open(linuxProcCpuinfo) + if err != nil { + return "unknown" + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if strings.HasPrefix(line, "model name") { + parts := strings.SplitN(line, ":", 2) + if len(parts) == 2 { + return strings.TrimSpace(parts[1]) + } + } + } + return "unknown" +} + +// readMemTotal 读取总内存,失败返回 "unknown" +func readMemTotal() string { + file, err := os.Open("/proc/meminfo") + if err != nil { + return "unknown" + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if strings.HasPrefix(line, "MemTotal") { + parts := strings.Fields(line) + if len(parts) >= 2 { + memKB := strings.TrimSuffix(parts[1], "kB") + var memTotalKB int + fmt.Sscanf(memKB, "%d", &memTotalKB) + memTotalMB := memTotalKB / 1024 + return fmt.Sprintf("%d MB", memTotalMB) + } + } + } + return "unknown" +} + +// GetSystemInfo 获取Linux系统信息,无错误返回,异常字段用默认值填充 +func GetSystemInfo() SystemInfo { + // 初始化结构体,先填充基础默认值 + sysInfo := SystemInfo{ + OS: runtime.GOOS, + Arch: runtime.GOARCH, + NumCPU: runtime.NumCPU(), + GoVersion: runtime.Version(), + // 以下字段先设为默认值,后续尝试覆盖 + KernelVersion: "unknown", + CPUModel: "unknown", + MemTotal: "unknown", + Hostname: "unknown", + } + + // 1. 获取主机名(失败保留默认值) + if hostname, err := os.Hostname(); err == nil { + sysInfo.Hostname = hostname + } + + // 2. 获取内核版本(失败保留默认值) + procVersion := readFileFirstLine(linuxProcVersion) + if procVersion != "" { + versionParts := strings.Split(procVersion, " ") + if len(versionParts) >= 3 { + sysInfo.KernelVersion = versionParts[2] + } else { + sysInfo.KernelVersion = procVersion + } + } + + // 3. 获取CPU型号(失败已返回 "unknown") + sysInfo.CPUModel = readCPUModel() + + // 4. 获取总内存(失败已返回 "unknown") + sysInfo.MemTotal = readMemTotal() + + return sysInfo +} + +// -------------------------- 辅助函数:打印所有信息 -------------------------- +// PrintAllInfo 打印所有公共信息(调试用) +func PrintAllInfo() { + fmt.Println("=== 应用公共信息 ===") + fmt.Printf("应用名称 : %s\n", AppName) + fmt.Printf("版本号 : %s\n", Version) + fmt.Printf("编译时间 : %s\n", BuildTime) + fmt.Printf("Git提交ID : %s\n", GitCommit) + fmt.Printf("Git分支 : %s\n", GitBranch) + + fmt.Println("\n=== 系统信息 ===") + sysInfo := GetSystemInfo() + fmt.Printf("操作系统 : %s\n", sysInfo.OS) + fmt.Printf("系统架构 : %s\n", sysInfo.Arch) + fmt.Printf("内核版本 : %s\n", sysInfo.KernelVersion) + fmt.Printf("CPU型号 : %s\n", sysInfo.CPUModel) + fmt.Printf("CPU核心数 : %d\n", sysInfo.NumCPU) + fmt.Printf("总内存 : %s\n", sysInfo.MemTotal) + fmt.Printf("主机名 : %s\n", sysInfo.Hostname) + fmt.Printf("Go版本 : %s\n", sysInfo.GoVersion) +} diff --git a/pkg/wizard/config.go b/pkg/wizard/config.go index 8f49ce1..094153b 100644 --- a/pkg/wizard/config.go +++ b/pkg/wizard/config.go @@ -2,9 +2,11 @@ package wizard import ( "errors" + "fmt" "net" "os" "sunhpc/pkg/database" + "sunhpc/pkg/info" "sunhpc/pkg/logger" "sunhpc/pkg/utils" @@ -68,6 +70,23 @@ type ConfigMapping struct { } `toml:"private"` } +type IPMaskInfo struct { + NetworkAddress string // 网络地址 192.168.1.0 + CIDR string // CIDR 格式 192.168.1.0/24 + IPAddress string // IP 地址 192.168.1.100 + MacAddress string // MAC 地址 00:11:22:33:44:55 + Netmask string // 子网掩码 255.255.255.0 + PrefixLength int // 前缀长度 24 +} + +type AttrItem struct { + Key string + Value string + Shadow string + Category int + Catindex int +} + func NewConfigWithDefault() *ConfigMapping { return &ConfigMapping{ Title: "Cluster Configuration", @@ -197,32 +216,111 @@ func loadConfig() (*ConfigMapping, error) { // saveConfig 入口函数:保存所有配置到数据库 func (m *model) saveConfig() error { - conn, err := database.GetDB() // 假设database包已实现getDB()获取连接 - if err != nil { - logger.Errorf("Database connection failed: %v", err) - return err - } - defer conn.Close() - m.force = false // 初始化全量覆盖标识 - config, err := loadConfig() + c, err := loadConfig() if err != nil { logger.Debugf("[DEBUG] Load config file failed: %v", err) return err } - t_value := m.config.PrivateIPAddress - c_value := config.Private.PrivateAddress + // 合并配置项 + result := make(map[string]string) - logger.Debugf("t_value: %s\n", t_value) - logger.Debugf("c_value: %s\n", c_value) + // base 配置 + result["country"] = mergeValue(m.config.Country, c.Base.Country) + result["state"] = mergeValue(m.config.State, c.Base.State) + result["city"] = mergeValue(m.config.City, c.Base.City) + result["contact"] = mergeValue(m.config.Contact, c.Base.Contact) + result["homepage"] = mergeValue(m.config.HomePage, c.Base.HomePage) + result["cluster_name"] = mergeValue(m.config.ClusterName, c.Base.ClusterName) + result["license"] = c.Base.License + result["distribution"] = c.Base.Distribution + result["timezone"] = mergeValue(m.config.Timezone, c.Base.Timezone) + result["base_dir"] = c.Base.BaseDir + result["work_dir"] = c.Base.WorkDir + result["distro_dir"] = mergeValue(m.config.DistroDir, c.Base.DistroDir) + result["partition"] = c.Base.Partition - logger.Debugf("config.Base.ClusterName: %s\n", config.Base.ClusterName) + // safe 配置 + result["safe_port"] = c.Base.SafePort + result["safe_dirs"] = c.Base.SafeDirs + result["safe_security"] = c.Base.SafeSecurity + // plugin 配置 + result["plugin_dirs"] = c.Base.PluginDirs + result["plugin_port"] = c.Base.PluginPort + + // monitor 配置 + result["ganglia_addr"] = c.Base.GangliaAddr + + // public 配置 + result["public_hostname"] = mergeValue(m.config.PublicHostname, c.Public.PublicHostname) + result["public_interface"] = mergeValue(m.config.PublicInterface, c.Public.PublicInterface) + result["public_address"] = mergeValue(m.config.PublicIPAddress, c.Public.PublicAddress) + result["public_netmask"] = mergeValue(m.config.PublicNetmask, c.Public.PublicNetmask) + result["public_gateway"] = mergeValue(m.config.PublicGateway, c.Public.PublicGateway) + + // 获取公网网络信息 + publicIface := mergeValue(m.config.PublicInterface, c.Public.PublicInterface) + publicInfo, err := GetNetworkInfo( + publicIface, c.Public.PublicAddress, c.Public.PublicNetmask) + if err != nil { + logger.Debugf("[DEBUG] Get public interface %s IP mask info failed: %v", + publicIface, err) + } + + result["public_network"] = publicInfo.NetworkAddress + result["public_domain"] = mergeValue(m.config.PublicDomain, c.Public.PublicDomain) + + result["public_cidr"] = mergeValue(c.Public.PublicCIDR, publicInfo.CIDR) + result["public_dns"] = c.Public.PublicDNS + result["public_mac"] = publicInfo.MacAddress + result["public_mtu"] = mergeValue(m.config.PublicMTU, c.Public.PublicMTU) + result["public_ntp"] = c.Public.PublicNTP + + // private 配置 + // 获取内网网络信息 + privateIface := mergeValue(m.config.PrivateInterface, c.Private.PrivateInterface) + privateInfo, err := GetNetworkInfo( + privateIface, c.Private.PrivateAddress, c.Private.PrivateNetmask) + if err != nil { + logger.Debugf("[DEBUG] Get private interface %s IP mask info failed: %v", + privateIface, err) + } + result["private_hostname"] = mergeValue(m.config.PrivateHostname, c.Private.PrivateHostname) + result["private_interface"] = mergeValue(m.config.PrivateInterface, c.Private.PrivateInterface) + result["private_address"] = mergeValue(m.config.PrivateIPAddress, c.Private.PrivateAddress) + result["private_netmask"] = mergeValue(m.config.PrivateNetmask, c.Private.PrivateNetmask) + result["private_network"] = privateInfo.NetworkAddress + result["private_domain"] = mergeValue(m.config.PrivateDomain, c.Private.PrivateDomain) + result["private_cidr"] = mergeValue(c.Private.PrivateCIDR, privateInfo.CIDR) + result["private_mac"] = privateInfo.MacAddress + result["private_mtu"] = mergeValue(m.config.PrivateMTU, c.Private.PrivateMTU) + + // pxe 配置 + result["next_server"] = mergeValue(privateInfo.IPAddress, c.Pxelinux.NextServer) + result["pxe_filename"] = c.Pxelinux.PxeFilename + result["pxelinux_dir"] = c.Pxelinux.PxeLinuxDir + result["boot_args"] = c.Pxelinux.BootArgs + + // 插入数据到数据库 + if err := insertDataToDB(result); err != nil { + logger.Debugf("[DEBUG] Insert config data to database failed: %v", err) + return err + } + + logger.Debugf("Result: %v", result) return nil } +func mergeValue(tui_value, cfg_value string) string { + if tui_value == "" { + return cfg_value + } + return tui_value +} + // 获取系统网络接口 func getNetworkInterfaces() []string { // 实现获取系统网络接口的逻辑 @@ -247,3 +345,226 @@ func getNetworkInterfaces() []string { } return result } + +func GetNetworkInfo(iface, ip, mask string) (*IPMaskInfo, error) { + + logger.Debugf("Get Network %s, IP %s, mask %s", iface, ip, mask) + + // 解析IP + ipAddr := net.ParseIP(ip) + if ipAddr == nil { + logger.Debugf("Invalid IP address: %s", ip) + return nil, fmt.Errorf("invalid IP address: %s", ip) + } + + // 解析子网掩码 + maskAddr := net.ParseIP(mask) + if maskAddr == nil { + logger.Debugf("Invalid subnet mask: %s", mask) + return nil, fmt.Errorf("invalid subnet mask: %s", mask) + } + + // 确保是IPv4地址 + ipv4 := ipAddr.To4() + maskv4 := maskAddr.To4() + if ipv4 == nil || maskv4 == nil { + logger.Debugf("Only support IPv4 address") + return nil, fmt.Errorf("only support IPv4 address") + } + + // 计算网络地址 (IP & 子网掩码) + network := make([]byte, 4) + for i := 0; i < 4; i++ { + network[i] = ipv4[i] & maskv4[i] + } + networkAddr := fmt.Sprintf( + "%d.%d.%d.%d", network[0], network[1], network[2], network[3]) + + // 计算前缀长度 + prefixLen := 0 + for i := 0; i < 4; i++ { + for j := 7; j >= 0; j-- { + if maskv4[i]&(1<