diff --git a/cmd/init/database.go b/cmd/init/database.go index b646755..a114371 100644 --- a/cmd/init/database.go +++ b/cmd/init/database.go @@ -29,9 +29,6 @@ func NewDatabaseCmd() *cobra.Command { } log.Info("初始化数据库...") - if force { - log.Warn("⚠️ 警告:强制重新初始化将清空数据库中的所有数据!") - } dbInst := db.MustGetDB() // panic if fail (ok for CLI tool) if err := dbInst.InitSchema(force); err != nil { diff --git a/cmd/root.go b/cmd/root.go index f40dbe5..1c31c40 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,13 +1,11 @@ package cmd import ( - "os" - "strings" initcmd "sunhpc/cmd/init" "sunhpc/cmd/soft" "sunhpc/cmd/tmpl" "sunhpc/internal/auth" - "sunhpc/internal/config" + "sunhpc/internal/db" "sunhpc/internal/log" "github.com/spf13/cobra" @@ -19,43 +17,6 @@ var ( noColor bool ) -func checkDB() error { - cfg, err := config.LoadConfig() - if err != nil { - log.Fatal("加载配置失败: ", err) - } - - // 统一转为小写,避免用户输入错误 - dbType := strings.ToLower(cfg.DB.Type) - - // 打印配置(调试用) - log.Debugf("数据库类型: %s", dbType) - log.Debugf("数据库名称: %s", cfg.DB.Name) - log.Debugf("数据库路径: %s", cfg.DB.Path) - log.Debugf("数据库用户: %s", cfg.DB.User) - log.Debugf("数据库主机: %s", cfg.DB.Host) - log.Debugf("数据库套接字: %s", cfg.DB.Socket) - log.Debugf("数据库详细日志: %v", cfg.DB.Verbose) - - // 支持 sqlite,mysql的常见别名 - isSQLite := dbType == "sqlite" || dbType == "sqlite3" - isMySQL := dbType == "mysql" - - // 检查数据库类型,只允许 sqlite 和 mysql - if !isSQLite && !isMySQL { - log.Fatalf("不支持的数据库类型: %s(仅支持 sqlite、sqlite3、mysql)", dbType) - } - - // 检查数据库路径是否存在 - if isSQLite { - if _, err := os.Stat(cfg.DB.Path); os.IsNotExist(err) { - log.Warnf("SQLite 数据库路径 %s 不存在", cfg.DB.Path) - log.Fatalf("必须先执行 'sunhpc init database' 初始化数据库") - } - } - return nil -} - var rootCmd = &cobra.Command{ Use: "sunhpc", Short: "SunHPC - HPC集群一体化运维工具", @@ -68,15 +29,9 @@ var rootCmd = &cobra.Command{ log.Debugf("当前命令 Annotations: %+v", cmd.Annotations) - // 检查当前命令是否标记为跳过 DB 检查 - if cmd.Annotations["skip-db-check"] == "true" { - log.Debugf("当前命令 %s 标记为跳过 DB 检查", cmd.Name()) - return - } else { - // 检查数据库 - if err := checkDB(); err != nil { - log.Fatalf("数据库检查失败: %v", err) - } + _, err := db.CheckDB() + if err != nil { + log.Warnf("数据库检查失败: %v", err) } // 需要 root 权限 diff --git a/internal/db/db.go b/internal/db/db.go index d365523..bf81ce8 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -1,10 +1,12 @@ package db import ( + "bufio" "database/sql" "fmt" "os" "path/filepath" + "strings" "sync" _ "github.com/go-sql-driver/mysql" @@ -17,31 +19,127 @@ import ( // DB wraps the sql.DB connection pool. type DB struct { engine *sql.DB + config *config.DBConfig // 保存配置 } +/* // Engine returns the underlying *sql.DB. func (d *DB) Engine() *sql.DB { return d.engine } +*/ + +func ConfirmWithRetry(prompt string, maxAttempts int) bool { + reader := bufio.NewReader(os.Stdin) + + for attempt := 1; attempt <= maxAttempts; attempt++ { + log.Infof("%s [y/n]", prompt) + + response, err := reader.ReadString('\n') + if err != nil { + continue + } + + response = strings.ToLower(strings.TrimSpace(response)) + + switch response { + case "y", "yes": + return true + case "n", "no", "": + return false + default: + if attempt < maxAttempts { + log.Warnf( + "⚠️ 无效输入、请输入 'y' 或 'n'(剩余尝试次数: %d)", + maxAttempts-attempt) + } + } + } + + log.Warn("⚠️ 警告:尝试次数过多、操作已取消") + return false +} // InitSchema initializes the database schema. // If force is true, drops existing tables before recreating them. func (d *DB) InitSchema(force bool) error { - db := d.engine + fullPath := filepath.Join(d.config.Path, d.config.Name) - if force { - if err := dropTables(db); err != nil { - return fmt.Errorf("failed to drop tables: %w", err) + // 检查文件是否存在 + _, err := os.Stat(fullPath) + fileExists := err == nil + + // 处理不同的场景 + switch { + case !fileExists: + // 场景1:文件不存在,连接并创建(allowCreate = true). + log.Infof("数据库文件不存在,将创建: %s", fullPath) + if err := d.Connect(true); err != nil { + return err } + return createTables(d.engine) + + case fileExists && !force: + // 场景2:文件存在、无 force 参数、提示友好退出. + log.Warnf("数据库文件已存在: %s", fullPath) + log.Warn("如果需要强制重新初始化,请添加 --force 参数") + log.Warn("数据库已存在、退出初始化操作.") + os.Exit(1) + + case fileExists && force: + // 场景3:文件存在、force 参数 -> 需要用户确认并重建. + log.Warn("警告:强制重新初始化将清空数据库中的所有数据!") + if !ConfirmWithRetry("是否继续?", 3) { + return fmt.Errorf("用户取消操作") + } + + // 连接现有数据库(allowCreate = true, 因为文件已经存在) + if err := d.Connect(true); err != nil { + return err + } + + // 清空现有数据. + if err := dropTables(d.engine); err != nil { + return err + } + + // 清空表 + if err := dropTriggers(d.engine); err != nil { + return err + } + + log.Info("已清空现有数据库触发器") + return createTables(d.engine) } + log.Info("数据库创建成功") + return nil +} + +// 辅助函数:检查文件是否存在 +func fileExists(path string) bool { + _, err := os.Stat(path) + if err == nil { + return true + } + if os.IsNotExist(err) { + return false + } + // 其他问题(如权限问题)也视为文件不存在,但应该记录日志 + log.Debugf("检查数据库文件状态失败: %v", err) + return false +} + +// 辅助函数: 创建数据库表 +func createTables(db *sql.DB) error { // ✅ 调用 schema.go 中的函数 for _, ddl := range CreateTableStatements() { + log.Debugf("执行: %s", ddl) if _, err := db.Exec(ddl); err != nil { - return fmt.Errorf("failed to create table: %w", err) + return fmt.Errorf("数据表创建失败: %w", err) } } - + log.Info("数据库表创建成功") return nil } @@ -55,6 +153,16 @@ func dropTables(db *sql.DB) error { 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 + } + } + return nil +} + // --- Singleton DB Instance --- var ( globalDB *DB @@ -70,52 +178,77 @@ func GetDB() (*DB, error) { return } - if _, err := os.Stat(cfg.DB.Path); err != nil { - // 创建数据库目录 - if err := os.MkdirAll(cfg.DB.Path, 0755); err != nil { - log.Fatalf("创建数据库目录失败: %v", err) - } - log.Infof("数据库目录创建成功: %s", cfg.DB.Path) + globalDB = &DB{ + config: &cfg.DB, + } + }) + return globalDB, initErr +} + +func (d *DB) Connect(allowCreate bool) error { + // 如果已经连接,直接返回 + if d.engine != nil { + return nil + } + + switch d.config.Type { + case "sqlite": + fullPath := filepath.Join(d.config.Path, d.config.Name) + + // 检查文件是否存在 + _, err := os.Stat(fullPath) + fileExists := err == nil + + // 如果文件不存在且不允许创建,返回错误 + if !fileExists && !allowCreate { + return fmt.Errorf("数据库文件不存在: %s, 请先初始化.", fullPath) } - var dsn string - var driver string - - switch cfg.DB.Type { - case "sqlite": - driver = "sqlite3" - fullPath := filepath.Join(cfg.DB.Path, cfg.DB.Name) - dsn = fmt.Sprintf("%s?_foreign_keys=on&_journal_mode=WAL&_timeout=5000", fullPath) - case "mysql": - driver = "mysql" - if cfg.DB.Socket != "" { - dsn = fmt.Sprintf("%s:%s@unix(%s)/%s?parseTime=true&loc=Local", - cfg.DB.User, cfg.DB.Password, cfg.DB.Socket, cfg.DB.Name) - } else { - dsn = fmt.Sprintf("%s:%s@tcp(%s)/%s?parseTime=true&loc=Local", - cfg.DB.User, cfg.DB.Password, cfg.DB.Host, cfg.DB.Name) - } - default: - initErr = fmt.Errorf("unsupported database type: %s", cfg.DB.Type) - return + // 确保目录存在 + if err := os.MkdirAll(d.config.Path, 0755); err != nil { + return fmt.Errorf("创建数据库目录失败: %w", err) } - engine, err := sql.Open(driver, dsn) + // 连接参数 + dsn := fmt.Sprintf("%s?_foreign_keys=on&_journal_mode=WAL&_timeout=5000", + fullPath) + + engine, err := sql.Open("sqlite3", dsn) if err != nil { - initErr = fmt.Errorf("failed to open database: %w", err) - return + return fmt.Errorf("数据库打开失败: %w", err) } if err := engine.Ping(); err != nil { engine.Close() - initErr = fmt.Errorf("failed to ping database: %w", err) - return + return fmt.Errorf("数据库连接失败: %w", err) } - globalDB = &DB{engine: engine} - }) + d.engine = engine + case "mysql": + // TODO: 实现 MySQL 连接逻辑 + return fmt.Errorf("mysql 数据库连接未实现") + } - return globalDB, initErr + return nil +} + +// Close 关闭数据库连接 +func (d *DB) Close() error { + if d.engine != nil { + return d.engine.Close() + } + return nil +} + +// GetEngine 获取数据库引擎(自动连接) +func (d *DB) GetEngine() (*sql.DB, error) { + // 如果还没有连接,自动连接(但不创建新文件) + if d.engine == nil { + if err := d.Connect(false); err != nil { + return nil, err + } + } + return d.engine, nil } // MustGetDB is a helper that panics on error (use in main/init only). @@ -126,3 +259,48 @@ func MustGetDB() *DB { } return db } + +func GetDBConfig() (*config.DBConfig, error) { + cfg, err := config.LoadConfig() + if err != nil { + return nil, fmt.Errorf("数据库配置文件加载失败: %w", err) + } + return &cfg.DB, nil +} + +func CheckDB() (*config.Config, error) { + cfg, err := config.LoadConfig() + if err != nil { + log.Warnf("加载配置失败: %v", err) + } + + // 统一转为小写,避免用户输入错误 + dbType := strings.ToLower(cfg.DB.Type) + + // 打印配置(调试用) + log.Debugf("数据库类型: %s", dbType) + log.Debugf("数据库名称: %s", cfg.DB.Name) + log.Debugf("数据库路径: %s", cfg.DB.Path) + log.Debugf("数据库用户: %s", cfg.DB.User) + log.Debugf("数据库主机: %s", cfg.DB.Host) + log.Debugf("数据库套接字: %s", cfg.DB.Socket) + log.Debugf("数据库详细日志: %v", cfg.DB.Verbose) + + // 支持 sqlite,mysql的常见别名 + isSQLite := dbType == "sqlite" || dbType == "sqlite3" + isMySQL := dbType == "mysql" + + // 检查数据库类型,只允许 sqlite 和 mysql + if !isSQLite && !isMySQL { + log.Fatalf("不支持的数据库类型: %s(仅支持 sqlite、sqlite3、mysql)", dbType) + } + + // 检查数据库路径是否存在 + if isSQLite { + if _, err := os.Stat(cfg.DB.Path); os.IsNotExist(err) { + log.Warnf("SQLite 数据库路径 %s 不存在", cfg.DB.Path) + log.Warn("必须先执行 'sunhpc init database' 初始化数据库") + } + } + return cfg, nil +} diff --git a/internal/db/schema.go b/internal/db/schema.go index 6d79321..768dc1a 100644 --- a/internal/db/schema.go +++ b/internal/db/schema.go @@ -9,31 +9,56 @@ func CurrentSchemaVersion() int { // CreateTableStatements returns a list of CREATE TABLE statements. func CreateTableStatements() []string { return []string{ - createNodesTable(), + createAliasesTable(), createAttributesTable(), + createBootactionTable(), + createDistributionsTable(), + createFirewallsTable(), createNetworksTable(), - createSubnetsTable(), + createPartitionsTable(), + createPublicKeysTable(), createSoftwareTable(), + createNodesTable(), + createSubnetsTable(), + createTrg_nodes_before_delete(), } } // DropTableOrder returns table names in reverse dependency order for safe DROP. func DropTableOrder() []string { - return []string{"software", "attributes", "nodes", "subnets", "networks"} + return []string{ + "aliases", + "attributes", + "bootactions", + "distributions", + "firewalls", + "networks", + "partitions", + "publickeys", + "software", + "nodes", + "subnets", + } +} + +func DropTriggerStatements() []string { + return []string{ + "trg_nodes_before_delete", + } } // --- Private DDL Functions --- - -func createNodesTable() string { +func createAliasesTable() string { return ` -CREATE TABLE IF NOT EXISTS nodes ( +CREATE TABLE IF NOT EXISTS aliases ( id INTEGER PRIMARY KEY AUTOINCREMENT, - hostname TEXT NOT NULL UNIQUE, - ip TEXT, - status TEXT DEFAULT 'active', - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP -);` + 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 { @@ -41,33 +66,149 @@ func createAttributesTable() string { CREATE TABLE IF NOT EXISTS attributes ( id INTEGER PRIMARY KEY AUTOINCREMENT, node_id INTEGER NOT NULL, - key TEXT NOT NULL, - value TEXT NOT NULL, - FOREIGN KEY(node_id) REFERENCES nodes(id) ON DELETE CASCADE, - UNIQUE(node_id, key) -);` + 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, - name TEXT NOT NULL UNIQUE, + 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, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP -);` + 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, - network_id INTEGER NOT NULL, - cidr TEXT NOT NULL, - gateway TEXT, - vlan INTEGER, - FOREIGN KEY(network_id) REFERENCES networks(id) ON DELETE CASCADE, - UNIQUE(network_id, cidr) + 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) );` } @@ -76,9 +217,78 @@ func createSoftwareTable() string { CREATE TABLE IF NOT EXISTS software ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, - version TEXT NOT NULL, - installed_on INTEGER, - FOREIGN KEY(installed_on) REFERENCES nodes(id) ON DELETE SET NULL, - UNIQUE(name, version, installed_on) -);` + 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; +` } diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..8de9a1e --- /dev/null +++ b/readme.md @@ -0,0 +1,146 @@ +## 其他包使用数据库模块 +```go +package repository + +import ( + "your-project/database" + "your-project/log" +) + +type NodeRepository struct { + db *database.DB +} + +// NewNodeRepository 创建仓库(延迟连接) +func NewNodeRepository() (*NodeRepository, error) { + db, err := database.GetDB() + if err != nil { + return nil, err + } + + return &NodeRepository{db: db}, nil +} + +// GetNode 获取节点(自动连接) +func (r *NodeRepository) GetNode(id int) (*Node, error) { + // 获取数据库引擎(会自动连接) + engine, err := r.db.GetEngine() + if err != nil { + return nil, fmt.Errorf("数据库连接失败: %w", err) + } + + var node Node + err = engine.QueryRow("SELECT id, name FROM nodes WHERE id = ?", id). + Scan(&node.ID, &node.Name) + if err != nil { + return nil, err + } + + return &node, nil +} + +// CreateNode 创建节点 +func (r *NodeRepository) CreateNode(name string) error { + engine, err := r.db.GetEngine() + if err != nil { + return err + } + + _, err = engine.Exec("INSERT INTO nodes (name) VALUES (?)", name) + return err +} +``` + +## 服务层使用 +```go +package service + +import ( + "your-project/repository" + "your-project/log" +) + +type NodeService struct { + repo *repository.NodeRepository +} + +func NewNodeService() (*NodeService, error) { + repo, err := repository.NewNodeRepository() + if err != nil { + return nil, err + } + + return &NodeService{repo: repo}, nil +} + +func (s *NodeService) ListNode() error { + // 自动连接数据库 + nodes, err := s.repo.GetAllNodes() + if err != nil { + return err + } + + for _, node := range nodes { + log.Infof("Node: %v", node) + } + return nil +} +``` +## 命令处理 +```go +package main + +import ( + "your-project/cmd" + "your-project/database" + "your-project/log" + "github.com/spf13/cobra" +) + +func main() { + var rootCmd = &cobra.Command{Use: "sunhpc"} + + // init database 命令 + var initCmd = &cobra.Command{ + Use: "init database", + Short: "初始化数据库", + Run: func(cmd *cobra.Command, args []string) { + force, _ := cmd.Flags().GetBool("force") + + // 获取DB实例(只加载配置) + db, err := database.GetDB() + if err != nil { + log.Fatal(err) + } + + // 初始化schema(会根据force参数决定行为) + if err := db.InitSchema(force); err != nil { + log.Fatal(err) + } + + log.Info("数据库初始化成功") + }, + } + initCmd.Flags().BoolP("force", "f", false, "强制重新初始化") + rootCmd.AddCommand(initCmd) + + // node list 命令 - 自动连接已存在的数据库 + var nodeCmd = &cobra.Command{ + Use: "node list", + Short: "列出所有节点", + Run: func(cmd *cobra.Command, args []string) { + service, err := service.NewNodeService() + if err != nil { + log.Fatal(err) // 如果数据库不存在,这里会报错 + } + + if err := service.ListNode(); err != nil { + log.Fatal(err) + } + }, + } + rootCmd.AddCommand(nodeCmd) + + rootCmd.Execute() +} +``` \ No newline at end of file diff --git a/sunhpc b/sunhpc index 1dbbb28..22f2835 100755 Binary files a/sunhpc and b/sunhpc differ diff --git a/sunhpc.yaml b/sunhpc.yaml deleted file mode 100644 index 755c3ca..0000000 --- a/sunhpc.yaml +++ /dev/null @@ -1,4 +0,0 @@ -db: - type: sqlite - name: sunhpc.db - path: /tmp/sunhpc