From 230d3d420401ed0c54142aea03814185e272657b Mon Sep 17 00:00:00 2001 From: "Qichao.Sun" Date: Thu, 19 Mar 2026 23:49:16 +0800 Subject: [PATCH] add xml parse --- Cargo.lock | 851 +++++++++++++++++++++++++++ Cargo.toml | 14 +- config/groups/common.toml | 0 config/groups/compute.toml | 11 + config/groups/gpu.toml | 5 + config/groups/storage.toml | 0 config/main.toml | 38 ++ output/compute-0-0.ks | 0 output/compute-0-1.ks | 0 output/frontend-0-0.ks | 0 scripts/common/base_config.sh | 0 scripts/common/check_hardware.sh | 0 scripts/common/register_dns.sh | 0 scripts/compute/install_docker.sh | 0 scripts/compute/join_slurm.sh | 0 scripts/gpu/install_cuda.sh | 0 scripts/gpu/install_nvidia_driver.sh | 0 scripts/storage/install_ceph.sh | 0 scripts/storage/prepare_osd_disks.sh | 0 src/commands/config/check.rs | 133 +++++ src/commands/config/mod.rs | 25 + src/commands/config/sync.rs | 13 + src/commands/db/init.rs | 49 ++ src/commands/db/mod.rs | 7 +- src/commands/db/status.rs | 27 + src/commands/mod.rs | 12 +- src/commands/report/mod.rs | 21 + src/commands/report/nextip.rs | 71 +++ src/internal/config/ksgen.rs | 595 +++++++++++++++++++ src/internal/config/loader.rs | 322 ++++++++++ src/internal/config/mod.rs | 5 + src/internal/config/xml_parser.rs | 226 +++++++ src/internal/database/database.rs | 52 ++ src/internal/database/mod.rs | 8 + src/internal/mod.rs | 5 + src/internal/network/ip.rs | 238 ++++++++ src/internal/network/mod.rs | 3 + src/main.rs | 14 +- 38 files changed, 2736 insertions(+), 9 deletions(-) create mode 100644 config/groups/common.toml create mode 100644 config/groups/compute.toml create mode 100644 config/groups/gpu.toml create mode 100644 config/groups/storage.toml create mode 100644 config/main.toml create mode 100644 output/compute-0-0.ks create mode 100644 output/compute-0-1.ks create mode 100644 output/frontend-0-0.ks create mode 100644 scripts/common/base_config.sh create mode 100644 scripts/common/check_hardware.sh create mode 100644 scripts/common/register_dns.sh create mode 100644 scripts/compute/install_docker.sh create mode 100644 scripts/compute/join_slurm.sh create mode 100644 scripts/gpu/install_cuda.sh create mode 100644 scripts/gpu/install_nvidia_driver.sh create mode 100644 scripts/storage/install_ceph.sh create mode 100644 scripts/storage/prepare_osd_disks.sh create mode 100644 src/commands/config/check.rs create mode 100644 src/commands/config/mod.rs create mode 100644 src/commands/config/sync.rs create mode 100644 src/commands/db/init.rs create mode 100644 src/commands/db/status.rs create mode 100644 src/commands/report/mod.rs create mode 100644 src/commands/report/nextip.rs create mode 100644 src/internal/config/ksgen.rs create mode 100644 src/internal/config/loader.rs create mode 100644 src/internal/config/mod.rs create mode 100644 src/internal/config/xml_parser.rs create mode 100644 src/internal/database/database.rs create mode 100644 src/internal/database/mod.rs create mode 100644 src/internal/mod.rs create mode 100644 src/internal/network/ip.rs create mode 100644 src/internal/network/mod.rs diff --git a/Cargo.lock b/Cargo.lock index e2fde74..2a375af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,24 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.21" @@ -58,6 +76,64 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + [[package]] name = "clap" version = "4.5.60" @@ -104,24 +180,301 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + [[package]] name = "once_cell_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "path-slash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro2" version = "1.0.106" @@ -140,6 +493,243 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "roxmltree" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +dependencies = [ + "memchr", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_with" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.13.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "strsim" version = "0.11.1" @@ -152,6 +742,18 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", + "lazy_static", + "path-slash", + "regex", + "roxmltree", + "serde", + "serde_json", + "serde_with", + "serde_yaml", + "thiserror", + "tokio", + "toml", + "yaml-rust", ] [[package]] @@ -165,24 +767,252 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "1.0.7+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd28d57d8a6f6e458bc0b8784f8fdcc4b99a437936056fa122cb234f18656a96" +dependencies = [ + "indexmap 2.13.0", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.0.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.10+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.7+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -191,3 +1021,24 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] + +[[package]] +name = "winnow" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index c871478..7d91642 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,16 @@ edition = "2024" [dependencies] anyhow = "1.0.102" -clap = { version = "4.5.60", features = ["derive"] } +clap = { version = "4.5.60", features = ["derive", "env"] } +lazy_static = "1.5.0" +path-slash = "0.2.1" +regex = "1.12.3" +roxmltree = "0.21.1" +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" +serde_yaml = "0.9.34" +serde_with = "*" +thiserror = "2.0.18" +tokio = { version = "1.50.0", features = ["full"] } +toml = "1.0.7" +yaml-rust = "0.4.5" \ No newline at end of file diff --git a/config/groups/common.toml b/config/groups/common.toml new file mode 100644 index 0000000..e69de29 diff --git a/config/groups/compute.toml b/config/groups/compute.toml new file mode 100644 index 0000000..1654eb6 --- /dev/null +++ b/config/groups/compute.toml @@ -0,0 +1,11 @@ +# 计算节点专属配置 +linux_distro = "centos" +linux_version = "8" + +[nodes.compute] +variables = {"cpu_cores" = "48","memory" = "192G","swap_size" = "32G"} + +# 追加XML路径 +xml_paths = [ + "xml/compute" +] \ No newline at end of file diff --git a/config/groups/gpu.toml b/config/groups/gpu.toml new file mode 100644 index 0000000..502cf61 --- /dev/null +++ b/config/groups/gpu.toml @@ -0,0 +1,5 @@ +extends = "compute" + +[vars] +os = "rocky9" +has_gpu = true \ No newline at end of file diff --git a/config/groups/storage.toml b/config/groups/storage.toml new file mode 100644 index 0000000..e69de29 diff --git a/config/main.toml b/config/main.toml new file mode 100644 index 0000000..5ebeea7 --- /dev/null +++ b/config/main.toml @@ -0,0 +1,38 @@ +# 全局配置 +linux_distro = "centos" +linux_version = "8" + +# XML配置路径(相对路径,基于main.toml所在目录) +xml_paths = [ + "xml/kickstart", + "xml/packages" +] + +# 全局变量 +[variables] +domain = "sunhpc.local" +ntp_server = "ntp.sunhpc.local" +dns_server = "192.168.1.1" +root_password = "111111" + +# 节点配置 +[nodes] +[nodes.common] +arch = "x86_64" +linux_distro = "centos" +linux_version = "8" +[nodes.common.variables] +network_interface = "eth0" +ip_prefix = "192.168.1." + +[nodes.compute] +inherit = "common" +[nodes.compute.variables] +ip_suffix = "100" +hostname = "compute-01" + +[nodes.storage] +inherit = "common" +[nodes.storage.variables] +ip_suffix = "200" +hostname = "storage-01" \ No newline at end of file diff --git a/output/compute-0-0.ks b/output/compute-0-0.ks new file mode 100644 index 0000000..e69de29 diff --git a/output/compute-0-1.ks b/output/compute-0-1.ks new file mode 100644 index 0000000..e69de29 diff --git a/output/frontend-0-0.ks b/output/frontend-0-0.ks new file mode 100644 index 0000000..e69de29 diff --git a/scripts/common/base_config.sh b/scripts/common/base_config.sh new file mode 100644 index 0000000..e69de29 diff --git a/scripts/common/check_hardware.sh b/scripts/common/check_hardware.sh new file mode 100644 index 0000000..e69de29 diff --git a/scripts/common/register_dns.sh b/scripts/common/register_dns.sh new file mode 100644 index 0000000..e69de29 diff --git a/scripts/compute/install_docker.sh b/scripts/compute/install_docker.sh new file mode 100644 index 0000000..e69de29 diff --git a/scripts/compute/join_slurm.sh b/scripts/compute/join_slurm.sh new file mode 100644 index 0000000..e69de29 diff --git a/scripts/gpu/install_cuda.sh b/scripts/gpu/install_cuda.sh new file mode 100644 index 0000000..e69de29 diff --git a/scripts/gpu/install_nvidia_driver.sh b/scripts/gpu/install_nvidia_driver.sh new file mode 100644 index 0000000..e69de29 diff --git a/scripts/storage/install_ceph.sh b/scripts/storage/install_ceph.sh new file mode 100644 index 0000000..e69de29 diff --git a/scripts/storage/prepare_osd_disks.sh b/scripts/storage/prepare_osd_disks.sh new file mode 100644 index 0000000..e69de29 diff --git a/src/commands/config/check.rs b/src/commands/config/check.rs new file mode 100644 index 0000000..b3c00c7 --- /dev/null +++ b/src/commands/config/check.rs @@ -0,0 +1,133 @@ +//! sunhpc config check 命令实现 +//! 功能:加载配置文件 → 解析XML → 生成Linux Kickstart脚本 + +use anyhow::{Context, Result}; +use clap::Args; +use std::env; +use std::path::PathBuf; + +// 导入内部模块 +use crate::internal::config::ksgen::KickstartGenerator; +use crate::internal::config::loader::ConfigLoader; + +/// 配置检查命令参数 +#[derive(Args, Debug)] +#[command(about = "检查配置文件并生成Kickstart脚本", long_about = None)] +pub struct CheckArgs { + /// 配置文件路径(默认:config/main.toml) + #[arg( + short = 'c', + long = "config", + default_value = "config/main.toml", + help = "指定主配置文件路径(TOML/YAML格式)" + )] + pub config: PathBuf, + + /// 输出格式(text/json,默认:text) + #[arg( + short = 'f', + long = "format", + default_value = "text", + help = "Kickstart输出格式:text(纯文本)/json(调试用)" + )] + pub format: String, + + /// 详细输出模式 + #[arg( + long = "verbose", + help = "显示更多调试信息(配置加载过程、节点变量等)" + )] + pub verbose: bool, + + /// 目标节点名称(默认:compute) + #[arg( + short = 'n', + long = "node", + default_value = "compute", + help = "指定要生成Kickstart的节点名称(对应配置中的nodes字段)" + )] + pub node: String, + + /// Linux发行版过滤(可选,覆盖配置中的值) + #[arg( + long = "distro", + help = "指定Linux发行版(如centos/ubuntu/rhel),优先级高于配置文件" + )] + pub distro: Option, + + /// Linux版本过滤(可选,覆盖配置中的值) + #[arg( + long = "version", + help = "指定Linux版本(如8/9/22.04),优先级高于配置文件" + )] + pub version: Option, +} + +/// 执行check命令的核心逻辑 +pub fn run(args: CheckArgs) -> Result<()> { + + let cwd = get_current_working_dir()?; + // 1. 打印基础信息 + println!("========================================"); + println!("🔍 SUNHPC 配置检查工具(Linux专属)"); + println!("========================================"); + println!("配置文件路径: {}", args.config.display()); + println!("当前路径: {}", cwd.display()); + println!("目标节点: {}", args.node); + println!("输出格式: {}", args.format); + println!("详细模式: {}", if args.verbose { "开启" } else { "关闭" }); + if let Some(distro) = &args.distro { + println!("指定发行版: {}", distro); + } + if let Some(version) = &args.version { + println!("指定版本: {}", version); + } + println!("----------------------------------------"); + + // 2. 加载配置文件(TOML/YAML) + let mut config_loader = ConfigLoader::new(&args.config) + .with_context(|| format!("加载配置文件失败: {}", args.config.display()))?; + + // 覆盖发行版/版本配置(如果命令行指定) + if let Some(distro) = &args.distro { + config_loader.global.linux_distro = distro.clone(); + } + if let Some(version) = &args.version { + config_loader.global.linux_version = version.clone(); + } + + // 详细模式:打印加载的配置信息 + if args.verbose { + println!("📄 配置加载成功!"); + println!(" - 全局变量数: {}", config_loader.global.variables.len()); + println!(" - 节点数: {}", config_loader.global.nodes.len()); + println!(" - XML路径数: {}", config_loader.global.xml_paths.len()); + println!(" - Linux发行版: {}", config_loader.global.linux_distro); + println!(" - Linux版本: {}", config_loader.global.linux_version); + println!("----------------------------------------"); + } + + // 3. 初始化Kickstart生成器 + let mut generator = KickstartGenerator::new(config_loader, &args.node) + .with_context(|| format!("初始化Kickstart生成器失败(节点: {})", args.node))?; + + // 4. 解析XML配置并构建Kickstart上下文 + generator.parse() + .with_context(|| "解析XML配置文件失败")?; + + // 5. 生成Kickstart脚本内容 + let kickstart_content = generator.generate(&args.format) + .with_context(|| format!("生成Kickstart失败(格式: {})", args.format))?; + + // 6. 输出结果 + println!("✅ Kickstart脚本生成成功!"); + println!("----------------------------------------"); + println!("{}", kickstart_content); + + Ok(()) +} + +/// 获取当前路径 +fn get_current_working_dir() -> Result { + env::current_dir() +} \ No newline at end of file diff --git a/src/commands/config/mod.rs b/src/commands/config/mod.rs new file mode 100644 index 0000000..e3a5977 --- /dev/null +++ b/src/commands/config/mod.rs @@ -0,0 +1,25 @@ +use clap::Subcommand; +use anyhow::Result; + +// 引入具体的命令逻辑文件 +mod check; +mod sync; + +/// Server 子命令集的具体命令 +#[derive(Subcommand)] +pub enum ConfigCommands { + /// 检查配置文件语法正确性 + Check(check::CheckArgs), + + /// 分发配置文件到所有节点 + Sync(sync::StopArgs), + +} + +/// 执行 server 命令集的入口函数 +pub fn execute(cmd: ConfigCommands) -> Result<(), anyhow::Error> { + match cmd { + ConfigCommands::Check(args) => check::run(args), + ConfigCommands::Sync(args) => sync::run(args), + } +} \ No newline at end of file diff --git a/src/commands/config/sync.rs b/src/commands/config/sync.rs new file mode 100644 index 0000000..83ba047 --- /dev/null +++ b/src/commands/config/sync.rs @@ -0,0 +1,13 @@ +use anyhow::Result; + +#[derive(clap::Args)] +pub struct StopArgs { + /// 强制停止 + #[arg(short, long)] + pub force: bool, +} + +pub fn run(args: StopArgs) -> Result<()> { + println!("🛑 正在停止服务器... (force: {})", args.force); + Ok(()) +} \ No newline at end of file diff --git a/src/commands/db/init.rs b/src/commands/db/init.rs new file mode 100644 index 0000000..0a44e54 --- /dev/null +++ b/src/commands/db/init.rs @@ -0,0 +1,49 @@ +use clap::Args; +use anyhow::Result; +// 引入核心逻辑 +use crate::internal::database::DbConfig; +use crate::internal::database::init_db; + +#[derive(Args)] +pub struct InitArgs { + /// 数据库连接 URL + #[arg(short, long, env = "DATABASE_URL", default_value = "postgres://localhost/mydb")] + pub url: String, + + /// 最大连接池大小 + #[arg(long, default_value_t = 10)] + pub max_connections: u32, + + /// 是否跳过确认提示 + #[arg(long, short)] + pub yes: bool, +} + +/// CLI 层的执行函数 +pub async fn run(args: InitArgs) -> Result<()> { + if !args.yes { + print!("⚠️ 即将初始化数据库 '{}',数据可能会被清空。继续吗?(y/N): ", args.url); + use std::io::{self, Write}; + io::stdout().flush()?; + + let mut input = String::new(); + io::stdin().read_line(&mut input)?; + + if !input.trim().eq_ignore_ascii_case("y") { + println!("操作已取消。"); + return Ok(()); + } + } + + // 1. 将 CLI 参数转换为 核心逻辑需要的结构体 + let config = DbConfig { + url: args.url, + max_connections: args.max_connections, + }; + + // 2. 调用 internal 层的纯逻辑 + // 注意:这里调用的是 async 函数 + init_db(&config).await?; + + Ok(()) +} \ No newline at end of file diff --git a/src/commands/db/mod.rs b/src/commands/db/mod.rs index 3ddfe74..1956682 100644 --- a/src/commands/db/mod.rs +++ b/src/commands/db/mod.rs @@ -2,15 +2,20 @@ use clap::Subcommand; use anyhow::Result; mod migrate; +mod init; #[derive(Subcommand)] pub enum DbCommands { /// 运行数据库迁移 Migrate(migrate::MigrateArgs), + + /// 数据库初始化 + Init(init::InitArgs), } -pub fn execute(cmd: DbCommands) -> Result<()> { +pub async fn execute(cmd: DbCommands) -> Result<()> { match cmd { DbCommands::Migrate(args) => migrate::run(args), + DbCommands::Init(args) => init::run(args).await, } } \ No newline at end of file diff --git a/src/commands/db/status.rs b/src/commands/db/status.rs new file mode 100644 index 0000000..bdfa068 --- /dev/null +++ b/src/commands/db/status.rs @@ -0,0 +1,27 @@ +// src/commands/db/status.rs +use clap::Args; +use anyhow::Result; +use crate::internal::database::check_connection; + +#[derive(Args)] +pub struct StatusArgs { + /// 数据库 URL (默认从环境变量读取) + #[arg(short, long, env = "DATABASE_URL", default_value = "postgres://localhost/mydb")] + pub url: String, +} + +pub async fn run(args: StatusArgs) -> Result<()> { + println!("🏓 正在检查数据库状态..."); + + match check_connection(&args.url).await? { + true => { + println!("✅ 数据库连接正常: {}", args.url); + Ok(()) + } + false => { + eprintln!("❌ 无法连接到数据库: {}", args.url); + // 返回错误码 + std::process::exit(1); + } + } +} \ No newline at end of file diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 65b8194..afc1dbc 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,6 +1,8 @@ use clap::Subcommand; // 声明子模块,对应 src/commands/ 下的目录 +pub mod config; +pub mod report; pub mod server; pub mod db; // 未来扩展:pub mod new_feature; @@ -13,7 +15,15 @@ pub enum CliCommands { #[command(subcommand)] Server(server::ServerCommands), - /// 数据库管理相关命令 (db migrate, db seed) + /// 服务器配置文件解析相关命令 + #[command(subcommand)] + Config(config::ConfigCommands), + + /// 打印集群配置信息 + #[command(subcommand)] + Report(report::ReportCommands), + + /// 数据库管理相关命令 (db init, db migrate) #[command(subcommand)] Db(db::DbCommands), diff --git a/src/commands/report/mod.rs b/src/commands/report/mod.rs new file mode 100644 index 0000000..9b34ac7 --- /dev/null +++ b/src/commands/report/mod.rs @@ -0,0 +1,21 @@ +use clap::Subcommand; +use anyhow::Result; + +// 引入具体的命令逻辑文件 +mod nextip; + +/// Server 子命令集的具体命令 +#[derive(Subcommand)] +pub enum ReportCommands { + /// 生成下一个可用 IP 地址 + #[command(name = "nextip")] // 强制子命令名为 nextip + NextIP(nextip::NextIPArgs), // 默认字母有大小写,自动转换成驼峰命令法next-ip. + +} + +/// 执行 report nextip 命令集的入口函数 +pub fn execute(cmd: ReportCommands) -> Result<(), anyhow::Error> { + match cmd { + ReportCommands::NextIP(args) => nextip::run(args), + } +} \ No newline at end of file diff --git a/src/commands/report/nextip.rs b/src/commands/report/nextip.rs new file mode 100644 index 0000000..e4b9a57 --- /dev/null +++ b/src/commands/report/nextip.rs @@ -0,0 +1,71 @@ + +#[warn(unused_imports)] +use crate::internal::network::IPGenerator; +use anyhow::Result; + +#[derive(clap::Args, Debug)] +pub struct NextIPArgs { + /// 网络地址(如 10.1.1.0) + #[arg(short = 'n', long = "network", default_value = "10.1.1.0")] + network: String, + + /// 子网掩码(如 255.255.255.128,不指定则自动推断) + #[arg(short = 'm', long = "netmask", default_value = "255.255.255.0")] + netmask: Option, + + /// IP 偏移步长(仅允许正数,从第一个可以用 IP 向后偏移) + #[arg(short = 's', long = "step", default_value = "1")] + step: u32, +} + +pub fn run(args: NextIPArgs) -> Result<()> { + + // 调用 IP 生成逻辑 + generate_next_ip(args)?; + + Ok(()) +} + + +/// 核心逻辑:生成指定偏移的 IP 地址 +fn generate_next_ip(opts: NextIPArgs) -> Result<()> { + // 创建 IP 生成器实例(用 anyhow 包装错误) + let mut generator = IPGenerator::new(&opts.network, opts.netmask.as_deref()) + .map_err(|e| anyhow::anyhow!("Failed to create IP generator: {}", e))?; + + // 获取网段最大可用步长,提前校验 + let max_step = generator.get_max_available_step(); + if opts.step > max_step { + return Err(anyhow::anyhow!( + "Step {} exceeds maximum available step {} for this network (mask: {})", + opts.step, + max_step, + generator.get_netmask_str() + )); + } + + // 获取初始 IP、新 IP、网络地址等信息 + let initial_ip = generator.curr() + .map_err(|e| anyhow::anyhow!("Failed to get initial IP: {}", e))?; + let new_ip = generator.next(opts.step as i32) + .map_err(|e| anyhow::anyhow!("Failed to move IP address: {}", e))?; + let network_addr = generator.get_network(); + let netmask_str = generator.get_netmask_str(); + + // ========== 核心:格式化对齐输出 ========== + // 定义左侧字段的宽度(根据最长字段调整,这里选 25 足够覆盖所有字段名) + const FIELD_WIDTH: usize = 30; + println!("{:>FIELD_WIDTH$}: {}", "Initial IP (first available)", initial_ip); + println!("{:>FIELD_WIDTH$}: {}", "IP after step", format!("{} (step: {})", new_ip, opts.step)); + println!("{:>FIELD_WIDTH$}: {}", "Network address", network_addr); + println!("{:>FIELD_WIDTH$}: {}", "Subnet mask", netmask_str); + println!("{:>FIELD_WIDTH$}: {}", "Max available step", max_step); + + println!("\nIP generation completed successfully"); + + + // 扩展点:写入配置/同步到集群的业务逻辑 + // 示例:write_ip_to_config(&new_ip)?; + + Ok(()) +} \ No newline at end of file diff --git a/src/internal/config/ksgen.rs b/src/internal/config/ksgen.rs new file mode 100644 index 0000000..20d3d9a --- /dev/null +++ b/src/internal/config/ksgen.rs @@ -0,0 +1,595 @@ +//! Kickstart生成器模块 +//! 功能: +//! 1. 基于配置和XML解析结果生成Linux Kickstart脚本 +//! 2. 支持不同Linux发行版/版本的适配 +//! 3. 输出纯文本或JSON格式的Kickstart内容 + +use anyhow::{Context, Result}; +use serde::Serialize; +use std::collections::{HashMap, HashSet}; +use std::collections::hash_map::RandomState; +use std::path::PathBuf; +use roxmltree::Node; + +// 导入内部模块 +use super::loader::ConfigLoader; +use super::xml_parser::XmlLoader; + +/// Kickstart上下文(存储生成脚本所需的所有数据) +/// 对应gen.py的Generator_linux的ks字段 +#[derive(Debug, Default, Serialize)] +pub struct KickstartContext { + /// 节点遍历顺序(file, roll) + pub order: Vec<(String, String)>, + /// debug信息列表 + pub debug: Vec, + /// main部分内容(Kickstart核心配置) + pub main: Vec, + /// 启用的RPM包列表 + pub rpms_on: Vec, + /// 禁用的RPM包列表 + pub rpms_off: Vec, + /// pre脚本([参数, 内容]) + pub pre: Vec>, + /// post脚本([参数, 解释器, 内容]) + pub post: Vec>, + /// boot pre脚本 + pub boot_pre: Vec, + /// boot post脚本 + pub boot_post: Vec, + /// RCS文件配置(路径: (所有者, 权限)) + pub rcs_files: HashMap, +} + +/// 节点数据结构(用于避免借用冲突) +#[derive(Debug)] +struct NodeData { + /// 节点名称 + pub node_name: String, + /// 文件属性 + pub file: String, + /// roll属性 + pub roll: String, + /// 节点文本内容 + pub text: String, + /// 是否禁用(package节点) + pub disable: bool, + /// 是否元包(package节点) + pub meta_type: bool, + /// 解释器(pre/post节点) + pub interpreter: String, + /// 参数(pre/post节点) + pub arg: String, + /// 顺序(boot节点) + pub order: String, + /// 文件名(file节点) + pub file_name: String, + /// 所有者(file节点) + pub owner: String, + /// 权限(file节点) + pub perms: String, + /// main节点的子节点数据 + pub children_data: Vec, +} + +/// main节点的子节点数据 +#[derive(Debug)] +struct MainChildData { + /// 子节点名称 + pub name: String, + /// 子节点文本 + pub text: String, + /// partition属性(clearpart节点) + pub partition: String, +} + +/// Linux Kickstart生成器 +pub struct KickstartGenerator { + /// 配置加载器 + config_loader: ConfigLoader, + /// XML加载器 + xml_loader: XmlLoader, + /// Kickstart上下文 + context: KickstartContext, + /// 目标节点名称 + node_name: String, +} + +impl KickstartGenerator { + /// 创建Kickstart生成器 + /// 参数: + /// - config_loader: 配置加载器实例 + /// - node_name: 目标节点名称 + pub fn new(config_loader: ConfigLoader, node_name: &str) -> Result { + // 1. 获取节点变量(用于XML过滤) + let node_vars = config_loader.get_node_vars(node_name) + .with_context(|| format!("获取节点{}的变量失败", node_name))?; + + // 2. 创建XML加载器(使用节点变量作为过滤条件) + let xml_loader = XmlLoader::new(node_vars, &config_loader.global.xml_paths) + .with_context(|| "初始化XML加载器失败")?; + + // 3. 初始化上下文 + let context = KickstartContext::default(); + + Ok(Self { + config_loader, + xml_loader, + context, + node_name: node_name.to_string(), + }) + } + + /// 解析XML并构建Kickstart上下文 + pub fn parse(&mut self) -> Result<()> { + println!("📝 开始解析XML配置生成Kickstart..."); + + // 提取节点数据(避免借用冲突) + let node_data: Vec = self.xml_loader.iter_linux_nodes() + .map(|node| { + let node_name = node.tag_name().name().to_string(); + let file = node.attribute("file").unwrap_or_default().to_string(); + let roll = node.attribute("roll").unwrap_or_default().to_string(); + let text = self.xml_loader.get_node_text(&node); + + // 提取常用属性 + let disable = node.attribute("disable").is_some(); + let meta_type = node.attribute("type").map(|s| s == "meta").unwrap_or(false); + let interpreter = node.attribute("interpreter").unwrap_or("/bin/bash").to_string(); + let arg = node.attribute("arg").unwrap_or_default().to_string(); + let order = node.attribute("order").unwrap_or("pre").to_string(); + let file_name = node.attribute("name").unwrap_or_default().to_string(); + let owner = node.attribute("owner").unwrap_or("root").to_string(); + let perms = node.attribute("perms").unwrap_or("0644").to_string(); + + // 对于main节点,提取子节点数据 + let children_data = if node_name == "main" { + Self::extract_main_children(&node, &self.xml_loader) + } else { + Vec::new() + }; + + NodeData { + node_name, + file, + roll, + text, + disable, + meta_type, + interpreter, + arg, + order, + file_name, + owner, + perms, + children_data, + } + }) + .collect(); + + // 遍历节点数据(此时不再持有xml_loader的借用) + for data in node_data { + // 记录遍历顺序 + self.context.order.push((data.file.clone(), data.roll.clone())); + + // 根据节点类型处理 + match data.node_name.as_str() { + "main" => self.handle_main_node_data(&data)?, + "package" => self.handle_package_node_data(&data)?, + "pre" => self.handle_pre_node_data(&data)?, + "post" => self.handle_post_node_data(&data)?, + "configure" => self.handle_configure_node_data(&data)?, + "boot" => self.handle_boot_node_data(&data)?, + "debug" => self.context.debug.push(data.text), + "file" => self.handle_file_node_data(&data)?, + _ => { + // 其他节点:记录到debug + self.context.debug.push(format!( + "未处理的节点类型: {} (文本: {})", + data.node_name, data.text + )); + } + } + } + + println!("✅ XML解析完成,共处理{}个节点", self.context.order.len()); + Ok(()) + } + + /// 提取main节点的子节点数据 + fn extract_main_children<'a, 'b>(node: &Node<'a, 'b>, xml_loader: &XmlLoader) -> Vec { + let mut children_data = Vec::new(); + + for child in node.children() { + if child.node_type() != roxmltree::NodeType::Element { + continue; + } + + let child_name = child.tag_name().name().to_string(); + let child_text = xml_loader.get_node_text(&child); + let partition = child.attribute("partition").unwrap_or_default().to_string(); + + children_data.push(MainChildData { + name: child_name, + text: child_text, + partition, + }); + } + + children_data + } + + /// 生成Kickstart脚本内容 + /// 参数:format - 输出格式(text/json) + pub fn generate(&self, format: &str) -> Result { + match format.to_lowercase().as_str() { + "text" => self.generate_text(), + "json" => self.generate_json(), + _ => Err(anyhow::anyhow!("不支持的输出格式: {},仅支持text/json", format)), + } + } + + /// 生成纯文本格式的Kickstart脚本 + fn generate_text(&self) -> Result { + let mut content = String::new(); + + // 1. 生成头部注释 + content.push_str(&format!( + "# SUNHPC Kickstart脚本(自动生成) +# 节点: {} +# Linux发行版: {} +# Linux版本: {} +# 生成时间: autoGen +\n", + self.node_name, + self.config_loader.global.linux_distro, + self.config_loader.global.linux_version + )); + + // 2. 生成main部分(Kickstart核心配置) + content.push_str("# ========== 核心配置 ==========\n"); + for line in &self.context.main { + content.push_str(line); + content.push('\n'); + } + content.push('\n'); + + // 3. 生成包管理部分 + content.push_str("# ========== 包配置 ==========\n"); + if !self.context.rpms_on.is_empty() || !self.context.rpms_off.is_empty() { + content.push_str("%packages --ignoremissing\n"); + // 启用的包 + for rpm in &self.context.rpms_on { + content.push_str(rpm); + content.push('\n'); + } + // 禁用的包(前缀-) + for rpm in &self.context.rpms_off { + content.push_str(&format!("-{}\n", rpm)); + } + content.push_str("%end\n\n"); + } + + // 4. 生成pre脚本部分 + content.push_str("# ========== Pre安装脚本 ==========\n"); + for pre in &self.context.pre { + content.push_str(&format!("%pre --log=/tmp/ks-pre.log {}\n", pre[0])); + content.push_str(&pre[1]); + content.push_str("\n%end\n\n"); + } + + // 5. 生成post脚本部分 + content.push_str("# ========== Post安装脚本 ==========\n"); + let log_path = "/mnt/sysimage/var/log/sunhpc-install.log"; + for post in &self.context.post { + content.push_str(&format!("%post --log={} {}\n", log_path, post[0])); + content.push_str(&post[1]); // 解释器(#!/bin/bash) + content.push_str("\n"); + content.push_str(&post[2]); // 脚本内容 + content.push_str("\n%end\n\n"); + } + + // 6. 生成boot脚本部分 + content.push_str("# ========== Boot配置脚本 ==========\n"); + content.push_str(&format!("%post --log={}\n", log_path)); + content.push_str("cat >> /etc/sysconfig/sunhpc-pre << EOF\n"); + for line in &self.context.boot_pre { + content.push_str(line); + content.push('\n'); + } + content.push_str("EOF\n\n"); + + content.push_str("cat >> /etc/sysconfig/sunhpc-post << EOF\n"); + for line in &self.context.boot_post { + content.push_str(line); + content.push('\n'); + } + content.push_str("EOF\n%end\n"); + + // 7. 生成debug信息(如果有) + if !self.context.debug.is_empty() { + content.push_str("\n# ========== Debug信息 ==========\n"); + content.push_str("# 以下为调试信息,实际部署时可删除\n"); + for debug in &self.context.debug { + content.push_str(&format!("# {}\n", debug)); + } + } + + Ok(content) + } + + /// 生成JSON格式的Kickstart上下文(调试用) + fn generate_json(&self) -> Result { + let json = serde_json::to_string_pretty(&self.context) + .with_context(|| "将Kickstart上下文序列化为JSON失败")?; + Ok(json) + } + + // ------------------------------ + // 以下为节点处理方法(按XML节点类型) + // ------------------------------ + + /// 处理
节点(Kickstart核心配置) + fn handle_main_node_data(&mut self, data: &NodeData) -> Result<()> { + for child in &data.children_data { + // 根据不同子节点类型生成配置 + match child.name.as_str() { + "clearpart" => { + // clearpart配置(示例:clearpart --all --initlabel) + let mut clearpart_line = "clearpart".to_string(); + if !child.partition.is_empty() { + clearpart_line.push_str(&format!(" --partition={}", child.partition)); + } + if !child.text.is_empty() { + clearpart_line.push_str(&format!(" {}", child.text)); + } + self.context.main.push(clearpart_line); + } + + "bootloader" | "lilo" => { + // 引导加载器配置 + self.context.main.push(format!("bootloader {}", child.text)); + } + + "lang" => { + // 语言配置 + self.context.main.push(format!("lang {}", child.text)); + } + + "keyboard" => { + // 键盘配置 + self.context.main.push(format!("keyboard {}", child.text)); + } + + "timezone" => { + // 时区配置 + self.context.main.push(format!("timezone {}", child.text)); + } + + "rootpw" => { + // root密码配置 + self.context.main.push(format!("rootpw {}", child.text)); + } + + "network" => { + // 网络配置 + self.context.main.push(format!("network {}", child.text)); + } + + "part" | "volgroup" | "logvol" | "raid" => { + // 分区配置 + self.context.main.push(format!("{} {}", child.name, child.text)); + } + + "reboot" | "poweroff" => { + // 安装后动作 + self.context.main.push(child.name.clone()); + } + + _ => { + // 其他主节点:直接拼接 + self.context.main.push(format!("{} {}", child.name, child.text)); + } + } + } + + Ok(()) + } + + /// 处理节点(RPM包配置) + fn handle_package_node_data(&mut self, data: &NodeData) -> Result<()> { + let package_name = data.text.trim(); + if package_name.is_empty() { + return Ok(()); + } + + // 构建包名(元包前缀@) + let rpm_name = if data.meta_type { + format!("@{}", package_name) + } else { + package_name.to_string() + }; + + // 去重集合(避免重复添加) + let mut rpms_on_set: HashSet = self.context.rpms_on.clone() + .into_iter() + .collect(); + let mut rpms_off_set: HashSet = self.context.rpms_off.clone() + .into_iter() + .collect(); + + if data.disable { + // 禁用的包:不在启用列表才添加到禁用列表 + if !rpms_on_set.contains(&rpm_name) { + rpms_off_set.insert(rpm_name.clone()); + } + rpms_on_set.remove(&rpm_name); + } else { + // 启用的包:添加到启用列表,移除禁用列表 + rpms_on_set.insert(rpm_name.clone()); + rpms_off_set.remove(&rpm_name); + } + + // 将临时HashSet转回Vec,覆盖原数据(可变借用) + self.context.rpms_on = rpms_on_set.into_iter().collect(); + self.context.rpms_off = rpms_off_set.into_iter().collect(); + + Ok(()) + } + + /// 处理
节点(安装前脚本)
+    fn handle_pre_node_data(&mut self, data: &NodeData) -> Result<()> {
+        // 构建pre脚本参数
+        let pre_args = format!(
+            "--interpreter {} {}",
+            data.interpreter,
+            data.arg
+        ).trim().to_string();
+
+        // 添加到pre列表
+        self.context.pre.push(vec![pre_args, data.text.clone()]);
+
+        Ok(())
+    }
+
+    /// 处理节点(安装后脚本)
+    fn handle_post_node_data(&mut self, data: &NodeData) -> Result<()> {
+        // 构建post脚本项(参数、解释器、内容)
+        let post_item = vec![
+            data.arg.clone(),
+            format!("#!{}", data.interpreter),
+            data.text.clone(),
+        ];
+
+        // 添加到post列表
+        self.context.post.push(post_item);
+
+        Ok(())
+    }
+
+    /// 处理节点(配置脚本,转为post脚本)
+    fn handle_configure_node_data(&mut self, data: &NodeData) -> Result<()> {
+        // configure节点等价于post节点
+        self.handle_post_node_data(data)
+    }
+
+    /// 处理节点(配置脚本,转为post脚本)- 保留以兼容
+    #[allow(dead_code)]
+    fn handle_configure_node<'a, 'b>(&mut self, node: &Node<'a, 'b>) -> Result<()> {
+        // configure节点等价于post节点(保留这个方法以兼容)
+        let interpreter = node.attribute("interpreter").unwrap_or("/bin/bash");
+        let arg = node.attribute("arg").unwrap_or_default();
+        let content = self.xml_loader.get_node_text(node);
+
+        // 构建post脚本项(参数、解释器、内容)
+        let post_item = vec![
+            arg.to_string(),
+            format!("#!{}", interpreter),
+            content,
+        ];
+
+        // 添加到post列表
+        self.context.post.push(post_item);
+
+        Ok(())
+    }
+
+    /// 处理节点(启动脚本)
+    fn handle_boot_node_data(&mut self, data: &NodeData) -> Result<()> {
+        match data.order.as_str() {
+            "pre" => self.context.boot_pre.push(data.text.clone()),
+            "post" => self.context.boot_post.push(data.text.clone()),
+            _ => {
+                // 未知顺序:同时添加到pre和post
+                self.context.boot_pre.push(data.text.clone());
+                self.context.boot_post.push(data.text.clone());
+            }
+        }
+
+        Ok(())
+    }
+
+    /// 处理节点(调试信息)
+    #[allow(dead_code)]
+    fn handle_debug_node<'a, 'b>(&mut self, node: &Node<'a, 'b>) -> Result<()> {
+        // 保留这个方法以兼容
+        let content = self.xml_loader.get_node_text(node);
+        self.context.debug.push(content);
+        Ok(())
+    }
+
+    /// 处理节点(文件配置)
+    fn handle_file_node_data(&mut self, data: &NodeData) -> Result<()> {
+        if data.file_name.is_empty() {
+            return Ok(());
+        }
+
+        // 记录RCS文件配置
+        let file_path = PathBuf::from(&data.file_name);
+        self.context.rcs_files.insert(file_path, (data.owner.clone(), data.perms.clone()));
+
+        // 生成文件创建脚本(添加到post)
+        let post_content = format!(
+            r#"
+# 创建文件: {}
+mkdir -p $(dirname {})
+cat > {} << EOF
+->{}
+EOF
+chown {} {}
+chmod {} {}
+"#,
+            data.file_name, data.file_name, data.file_name, data.text, data.owner, data.file_name, data.perms, data.file_name
+        );
+
+        // 添加到post脚本
+        self.context.post.push(vec![
+            "".to_string(),
+            "#!/bin/bash".to_string(),
+            post_content,
+        ]);
+
+        Ok(())
+    }
+
+    /// 处理节点(文件配置)
+    #[allow(dead_code)]
+    fn handle_file_node<'a, 'b>(&mut self, node: &Node<'a, 'b>) -> Result<()> {
+        // 提取文件属性
+        let file_name = node.attribute("name").unwrap_or_default();
+        let owner = node.attribute("owner").unwrap_or("root");
+        let perms = node.attribute("perms").unwrap_or("0644");
+        let content = self.xml_loader.get_node_text(node);
+
+        if file_name.is_empty() {
+            return Ok(());
+        }
+
+        // 记录RCS文件配置
+        let file_path = PathBuf::from(file_name);
+        self.context.rcs_files.insert(file_path, (owner.to_string(), perms.to_string()));
+
+        // 生成文件创建脚本(添加到post)
+        let post_content = format!(
+            r#"
+# 创建文件: {}
+mkdir -p $(dirname {})
+cat > {} << EOF
+{}
+EOF
+chown {} {}
+chmod {} {}
+"#,
+            file_name, file_name, file_name, content, owner, file_name, perms, file_name
+        );
+
+        // 添加到post脚本
+        self.context.post.push(vec![
+            "".to_string(),
+            "#!/bin/bash".to_string(),
+            post_content,
+        ]);
+
+        Ok(())
+    }
+}
\ No newline at end of file
diff --git a/src/internal/config/loader.rs b/src/internal/config/loader.rs
new file mode 100644
index 0000000..f6436e4
--- /dev/null
+++ b/src/internal/config/loader.rs
@@ -0,0 +1,322 @@
+//! 配置加载器模块
+//! 功能:
+//! 1. 解析TOML/YAML格式的主配置+分组配置
+//! 2. 处理节点继承关系(子节点合并父节点变量)
+//! 3. 管理Linux发行版/版本配置
+//! 4. 收集XML文件路径(转为绝对路径)
+
+use anyhow::{Context, Result};
+use serde::{Deserialize};
+use std::collections::HashMap;
+use std::fs;
+use std::path::{Path, PathBuf};
+use yaml_rust::{Yaml, YamlLoader};
+
+/// 全局配置结构体(对应main.toml的根节点)
+#[derive(Debug, Deserialize, Clone)]
+pub struct GlobalConfig {
+    /// 全局通用变量(所有节点共享)
+    #[serde(default)]
+    pub variables: HashMap,
+
+    /// 节点配置(key=节点名,value=节点具体配置)
+    #[serde(default)]
+    pub nodes: HashMap,
+
+    /// XML配置文件/目录路径列表
+    #[serde(default)]
+    pub xml_paths: Vec,
+
+    /// Linux发行版(如centos/rhel/ubuntu)
+    #[serde(default = "default_linux_distro")]
+    pub linux_distro: String,
+
+    /// Linux版本(如8/9/22.04)
+    #[serde(default = "default_linux_version")]
+    pub linux_version: String,
+}
+
+/// 节点配置结构体(支持继承)
+#[derive(Debug, Deserialize, Clone)]
+pub struct NodeConfig {
+    /// 继承的父节点名称(可选)
+    #[serde(default)]
+    pub inherit: Option,
+
+    /// 节点架构(x86_64/aarch64等)
+    #[serde(default = "default_arch")]
+    pub arch: String,
+
+    /// Linux发行版(覆盖全局配置)
+    #[serde(default)]
+    pub linux_distro: String,
+
+    /// Linux版本(覆盖全局配置)
+    #[serde(default)]
+    pub linux_version: String,
+
+    /// 节点私有变量(覆盖全局变量)
+    #[serde(default)]
+    pub variables: HashMap,
+}
+
+/// 配置加载器核心结构体
+pub struct ConfigLoader {
+    /// 加载完成的全局配置
+    pub global: GlobalConfig,
+
+    /// 原始配置文件路径
+    #[allow(dead_code)]
+    config_path: PathBuf,
+}
+
+// 默认值函数
+fn default_arch() -> String {
+    "x86_64".to_string()
+}
+
+fn default_linux_distro() -> String {
+    "centos".to_string()
+}
+
+fn default_linux_version() -> String {
+    "8".to_string()
+}
+
+impl ConfigLoader {
+    /// 创建配置加载器并加载配置文件
+    /// 参数:config_path - 主配置文件路径(TOML/YAML)
+    pub fn new(config_path: &Path) -> Result {
+        // 1. 读取配置文件内容
+        let config_str = fs::read_to_string(config_path)
+            .with_context(|| format!("无法读取配置文件: {}", config_path.display()))?;
+
+        // 2. 解析配置(优先TOML,兼容YAML)
+        let mut global: GlobalConfig = match toml::from_str(&config_str) {
+            Ok(cfg) => cfg,
+            Err(toml_err) => {
+                eprintln!("TOML解析失败: {}", toml_err);
+                // TOML解析失败,尝试YAML
+                let yaml_docs = YamlLoader::load_from_str(&config_str)
+                    .with_context(|| "配置文件不是有效的TOML或YAML格式")?;
+
+                if yaml_docs.is_empty() {
+                    return Err(anyhow::anyhow!("YAML配置文件为空"))
+                        .context("解析Yaml后未获取到任何信息");
+                }
+                Self::yaml_to_global(&yaml_docs[0])?
+            }
+        };
+
+        // 3. 合并分组配置(groups目录下的compute/storage等)
+        Self::merge_group_configs(&mut global, config_path.parent().unwrap())?;
+
+        // 4. 处理节点继承关系(子节点合并父节点变量)
+        Self::process_node_inheritance(&mut global)?;
+
+        // 5. 标准化XML路径(转为绝对路径)
+        global.xml_paths = global.xml_paths
+            .iter()
+            .map(|p| {
+                if p.is_absolute() {
+                    p.clone()
+                } else {
+                    config_path.parent().unwrap().join(p)
+                }
+            })
+            .collect();
+
+        Ok(Self {
+            global,
+            config_path: config_path.to_path_buf(),
+        })
+    }
+
+    /// 将YAML对象转换为GlobalConfig(兼容逻辑)
+    fn yaml_to_global(yaml: &Yaml) -> Result {
+        let mut global = GlobalConfig {
+            variables: HashMap::new(),
+            nodes: HashMap::new(),
+            xml_paths: Vec::new(),
+            linux_distro: default_linux_distro(),
+            linux_version: default_linux_version(),
+        };
+
+        // 解析全局变量
+        if let Yaml::Hash(vars_hash) = &yaml["variables"] {
+            for (k, v) in vars_hash {
+                if let (Yaml::String(k_str), Yaml::String(v_str)) = (k, v) {
+                    global.variables.insert(k_str.clone(), v_str.clone());
+                }
+            }
+        }
+
+        // 解析节点配置
+        if let Yaml::Hash(nodes_hash) = &yaml["nodes"] {
+            for (node_name, node_yaml) in nodes_hash {
+                let node_name_str = node_name
+                    .as_str()
+                    .with_context(|| "节点名称必须是字符串")?
+                    .to_string();
+
+                // 解析单个节点配置
+                let inherit = node_yaml["inherit"].as_str().map(|s| s.to_string());
+                let arch = node_yaml["arch"].as_str().unwrap_or(&default_arch()).to_string();
+                let linux_distro = node_yaml["linux_distro"]
+                    .as_str()
+                    .unwrap_or(&default_linux_distro())
+                    .to_string();
+                let linux_version = node_yaml["linux_version"]
+                    .as_str()
+                    .unwrap_or(&default_linux_version())
+                    .to_string();
+
+                // 解析节点私有变量
+                let mut node_vars = HashMap::new();
+                if let Yaml::Hash(vars_hash) = &node_yaml["variables"] {
+                    for (k, v) in vars_hash {
+                        if let (Yaml::String(k_str), Yaml::String(v_str)) = (k, v) {
+                            node_vars.insert(k_str.clone(), v_str.clone());
+                        }
+                    }
+                }
+
+                // 添加到节点列表
+                global.nodes.insert(
+                    node_name_str,
+                    NodeConfig {
+                        inherit,
+                        arch,
+                        linux_distro,
+                        linux_version,
+                        variables: node_vars,
+                    },
+                );
+            }
+        }
+
+        // 解析XML路径
+        if let Yaml::Array(xml_paths_arr) = &yaml["xml_paths"] {
+            for p in xml_paths_arr {
+                if let Yaml::String(p_str) = p {
+                    global.xml_paths.push(PathBuf::from(p_str));
+                }
+            }
+        }
+
+        // 解析Linux发行版/版本
+        if let Yaml::String(distro) = &yaml["linux_distro"] {
+            global.linux_distro = distro.clone();
+        }
+        if let Yaml::String(version) = &yaml["linux_version"] {
+            global.linux_version = version.clone();
+        }
+
+        Ok(global)
+    }
+
+    /// 合并groups目录下的分组配置(compute/storage/gpu/common)
+    fn merge_group_configs(global: &mut GlobalConfig, parent_dir: &Path) -> Result<()> {
+        let groups_dir = parent_dir.join("groups");
+        if !groups_dir.exists() || !groups_dir.is_dir() {
+            // 没有groups目录,直接返回
+            return Ok(());
+        }
+
+        // 遍历groups目录下的所有TOML/YAML文件
+        for entry in fs::read_dir(groups_dir)? {
+            let entry = entry?;
+            let path = entry.path();
+
+            // 只处理文件,且后缀为toml/yaml/yml
+            if !path.is_file() {
+                continue;
+            }
+            let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
+            if !["toml", "yaml", "yml"].contains(&ext) {
+                continue;
+            }
+
+            // 读取并解析分组配置
+            let group_str = fs::read_to_string(&path)?;
+            let group_config: GlobalConfig = if ext == "toml" {
+                toml::from_str(&group_str)?
+            } else {
+                let yaml_docs = YamlLoader::load_from_str(&group_str)?;
+                Self::yaml_to_global(&yaml_docs[0])?
+            };
+
+            // 合并配置(分组配置覆盖全局配置)
+            global.variables.extend(group_config.variables);
+            global.nodes.extend(group_config.nodes);
+            global.xml_paths.extend(group_config.xml_paths);
+
+            // 合并Linux发行版/版本(如果分组配置有值)
+            if !group_config.linux_distro.is_empty() {
+                global.linux_distro = group_config.linux_distro;
+            }
+            if !group_config.linux_version.is_empty() {
+                global.linux_version = group_config.linux_version;
+            }
+        }
+
+        Ok(())
+    }
+
+    /// 处理节点继承关系(子节点合并父节点的变量/配置)
+    fn process_node_inheritance(global: &mut GlobalConfig) -> Result<()> {
+        let nodes_clone = global.nodes.clone(); // 克隆一份用于读取父节点
+
+        for (node_name, node) in global.nodes.iter_mut() {
+            // 如果节点有继承的父节点
+            if let Some(parent_name) = &node.inherit {
+                let parent_node = nodes_clone
+                    .get(parent_name)
+                    .with_context(|| format!("节点{}继承的父节点{}不存在", node_name, parent_name))?;
+
+                // 1. 合并变量:父节点变量 → 子节点变量(子节点覆盖父节点)
+                let mut parent_vars = parent_node.variables.clone();
+                parent_vars.extend(node.variables.clone());
+                node.variables = parent_vars;
+
+                // 2. 继承架构(子节点未设置则使用父节点)
+                if node.arch == default_arch() && parent_node.arch != default_arch() {
+                    node.arch = parent_node.arch.clone();
+                }
+
+                // 3. 继承Linux发行版(子节点未设置则使用父节点)
+                if node.linux_distro == default_linux_distro() && parent_node.linux_distro != default_linux_distro() {
+                    node.linux_distro = parent_node.linux_distro.clone();
+                }
+
+                // 4. 继承Linux版本(子节点未设置则使用父节点)
+                if node.linux_version == default_linux_version() && parent_node.linux_version != default_linux_version() {
+                    node.linux_version = parent_node.linux_version.clone();
+                }
+            }
+        }
+
+        Ok(())
+    }
+
+    /// 获取指定节点的完整变量(全局变量 + 节点私有变量)
+    /// 参数:node_name - 节点名称
+    pub fn get_node_vars(&self, node_name: &str) -> Result> {
+        let node = self.global.nodes.get(node_name)
+            .with_context(|| format!("节点{}不存在于配置文件中", node_name))?;
+
+        // 1. 复制全局变量
+        let mut vars = self.global.variables.clone();
+
+        // 2. 合并节点私有变量(覆盖全局变量)
+        vars.extend(node.variables.clone());
+
+        // 3. 注入节点内置变量
+        vars.insert("arch".to_string(), node.arch.clone());
+        vars.insert("linux_distro".to_string(), node.linux_distro.clone());
+        vars.insert("linux_version".to_string(), node.linux_version.clone());
+        vars.insert("node_name".to_string(), node_name.to_string());
+
+        Ok(vars)
+    }
+}
\ No newline at end of file
diff --git a/src/internal/config/mod.rs b/src/internal/config/mod.rs
new file mode 100644
index 0000000..c26b3b3
--- /dev/null
+++ b/src/internal/config/mod.rs
@@ -0,0 +1,5 @@
+
+// 配置相关模块:加载、解析、合并 TOML 配置文件
+pub mod loader;
+pub mod xml_parser;
+pub mod ksgen;
\ No newline at end of file
diff --git a/src/internal/config/xml_parser.rs b/src/internal/config/xml_parser.rs
new file mode 100644
index 0000000..3ea679c
--- /dev/null
+++ b/src/internal/config/xml_parser.rs
@@ -0,0 +1,226 @@
+//! XML解析器模块
+//! 功能:
+//! 1. 加载指定目录/文件下的所有XML文件
+//! 2. 实现NodeFilter逻辑(过滤符合Linux发行版/版本/架构的节点)
+//! 3. 提供XML节点遍历和文本提取功能
+
+use anyhow::{Context, Result};
+use roxmltree::{Document, Node, NodeType};
+use std::collections::{HashMap, HashSet};
+use std::fs;
+use std::path::{Path, PathBuf};
+
+/// XML节点过滤器(复刻gen.py的NodeFilter)
+/// 用于过滤符合条件的XML节点(架构/发行版/版本/阶段)
+#[derive(Debug, Clone)]
+pub struct XmlNodeFilter {
+    /// 过滤条件(arch/linux_distro/linux_version等)
+    filter_attrs: HashMap,
+    /// 阶段过滤(pre/post,默认全部)
+    phases: HashSet,
+}
+
+impl XmlNodeFilter {
+    /// 创建过滤器
+    /// 参数:filter_attrs - 过滤属性(arch/linux_distro/linux_version)
+    pub fn new(filter_attrs: HashMap) -> Self {
+        // 默认包含pre和post阶段
+        let mut phases = HashSet::new();
+        phases.insert("pre".to_string());
+        phases.insert("post".to_string());
+
+        Self {
+            filter_attrs,
+            phases,
+        }
+    }
+
+    /// 设置阶段过滤(仅保留指定阶段)
+    /// 参数:phases - 阶段列表(如["pre"]或["post"])
+    #[allow(dead_code)]
+    pub fn set_phases(&mut self, phases: &[&str]) {
+        self.phases = phases.iter().map(|s| s.to_string()).collect();
+    }
+
+    /// 检查节点是否符合过滤条件(核心逻辑)
+    /// 对应gen.py的isCorrectCond
+    pub fn is_node_match(&self, node: &Node) -> bool {
+        // 非元素节点直接过滤
+        if node.node_type() != NodeType::Element {
+            return false;
+        }
+
+        // 1. 提取节点属性
+        let node_arch = node.attribute("arch").unwrap_or_default();
+        let node_distro = node.attribute("linux_distro").unwrap_or_default();
+        let node_version = node.attribute("linux_version").unwrap_or_default();
+        let node_phase = node.attribute("phase").unwrap_or_default();
+
+        // 2. 阶段过滤(支持逗号分隔的多个阶段)
+        let node_phases: HashSet = node_phase
+            .split(',')
+            .map(|s| s.trim().to_lowercase())
+            .filter(|s| !s.is_empty())
+            .collect();
+        // 如果节点指定了阶段,但与过滤器阶段无交集 → 过滤
+        if !node_phases.is_empty() && node_phases.intersection(&self.phases).next().is_none() {
+            return false;
+        }
+
+        // 3. 架构过滤(节点指定了arch但不匹配 → 过滤)
+        if !node_arch.is_empty() && node_arch != self.filter_attrs.get("arch").map(|s| s.as_str()).unwrap_or("") {
+            return false;
+        }
+
+        // 4. 发行版过滤(节点指定了linux_distro但不匹配 → 过滤)
+        if !node_distro.is_empty() && node_distro != self.filter_attrs.get("linux_distro").map(|s|s.as_str()).unwrap_or("") {
+            return false;
+        }
+
+        // 5. 版本过滤(节点指定了linux_version但不匹配 → 过滤)
+        if !node_version.is_empty() && node_version != self.filter_attrs.get("linux_version").map(|s|s.as_str()).unwrap_or("") {
+            return false;
+        }
+
+        // 所有条件都满足
+        true
+    }
+
+    /// 检查节点是否是Linux Kickstart允许的主节点
+    /// 对应gen.py的MainNodeFilter_linux
+    pub fn is_linux_main_node(&self, node: &Node) -> bool {
+        if !self.is_node_match(node) {
+            return false;
+        }
+
+        // 允许的主节点列表(Kickstart核心节点)
+        let allowed_main_nodes = [
+            "kickstart", "include", "main", "auth", "clearpart", "device", "driverdisk",
+            "install", "nfs", "cdrom", "interactive", "harddrive", "url", "keyboard",
+            "lang", "langsupport", "lilo", "bootloader", "mouse", "network", "part",
+            "volgroup", "logvol", "raid", "reboot", "rootpw", "skipx", "text",
+            "timezone", "upgrade", "xconfig", "zerombr"
+        ];
+
+        allowed_main_nodes.contains(&node.tag_name().name())
+    }
+
+    /// 检查节点是否是Linux Kickstart允许的其他节点
+    /// 对应gen.py的OtherNodeFilter_linux
+    pub fn is_linux_other_node(&self, node: &Node) -> bool {
+        if !self.is_node_match(node) {
+            return false;
+        }
+
+        // 允许的其他节点列表(包/脚本/配置相关)
+        let allowed_other_nodes = [
+            "attributes", "debug", "description", "package", "pre", "post",
+            "boot", "configure", "file"
+        ];
+
+        allowed_other_nodes.contains(&node.tag_name().name())
+    }
+}
+
+/// XML加载器(加载所有XML文件并提供遍历接口)
+#[derive(Debug)]
+pub struct XmlLoader{
+    /// 加载的所有XML文档
+    pub docs: Vec>,
+    /// 节点过滤器
+    pub filter: XmlNodeFilter,
+}
+
+impl XmlLoader {
+    /// 创建XML加载器
+    /// 参数:
+    /// - filter_attrs: 过滤属性(arch/linux_distro/linux_version)
+    /// - xml_paths: XML文件/目录路径列表
+    pub fn new(filter_attrs: HashMap, xml_paths: &[PathBuf]) -> Result {
+        let mut docs = Vec::new();
+
+        // 遍历所有XML路径(文件/目录)
+        for path in xml_paths {
+            if path.is_dir() {
+                // 目录:遍历所有.xml文件
+                docs.extend(Self::load_xml_dir(path)?);
+            } else if path.is_file() && path.extension().and_then(|e| e.to_str()) == Some("xml") {
+                // 文件:直接加载
+                let doc = Self::load_xml_file(path)?;
+                docs.push(doc);
+            }
+        }
+
+        // 创建过滤器
+        let filter = XmlNodeFilter::new(filter_attrs);
+
+        Ok(Self { docs, filter})
+    }
+
+    /// 加载目录下的所有XML文件
+    fn load_xml_dir(dir: &Path) -> Result>> {
+        let mut docs = Vec::new();
+
+        for entry in fs::read_dir(dir)? {
+            let entry = entry?;
+            let path = entry.path();
+
+            // 只处理.xml文件
+            if path.is_file() && path.extension().and_then(|e| e.to_str()) == Some("xml") {
+                let doc = Self::load_xml_file(&path)?;
+                docs.push(doc);
+            }
+        }
+
+        Ok(docs)
+    }
+
+    /// 加载单个XML文件
+    fn load_xml_file(path: &Path) -> Result> {
+        // 读取文件内容
+        let xml_str = fs::read_to_string(path)
+            .with_context(|| format!("读取XML文件失败: {}", path.display()))?;
+
+        // 解析XML为DOM文档
+        let xml_static: &'static str = Box::leak(xml_str.into_boxed_str());
+        let doc = Document::parse(xml_static)
+            .with_context(|| format!("解析XML文件失败: {}", path.display()))?;
+
+        Ok(doc)
+    }
+
+    /// 遍历所有符合条件的Linux节点
+    pub fn iter_linux_nodes(&self) -> impl Iterator> {
+        self.docs.iter()
+            .flat_map(|doc| doc.descendants()) // 遍历所有后代节点
+            .filter(|node| {
+                // 过滤出主节点或其他节点
+                self.filter.is_linux_main_node(node) || self.filter.is_linux_other_node(node)
+            })
+    }
+
+    /// 获取节点的所有文本内容(包括子节点)
+    pub fn get_node_text<'a, 'b>(&self, node: &Node<'a, 'b>) -> String {
+        let mut text = String::new();
+
+        for child in node.children() {
+            match child.node_type() {
+                // 文本节点:直接追加
+                NodeType::Text => {
+                    if let Some(t) = child.text() {
+                        text.push_str(t);
+                    }
+                }
+                // 元素节点:递归获取文本
+                NodeType::Element => {
+                    text.push_str(&self.get_node_text(&child));
+                }
+                // 其他节点:忽略
+                _ => {}
+            }
+        }
+
+        // 去除首尾空白,替换多空格为单空格
+        text.trim().replace(&['\n', '\r', '\t'][..], " ").replace("  ", " ")
+    }
+}
\ No newline at end of file
diff --git a/src/internal/database/database.rs b/src/internal/database/database.rs
new file mode 100644
index 0000000..340d738
--- /dev/null
+++ b/src/internal/database/database.rs
@@ -0,0 +1,52 @@
+use anyhow::{Result};
+use std::time::Duration;
+
+/// 模拟数据库配置结构
+#[derive(Debug, Clone)]
+pub struct DbConfig {
+    pub url: String,
+    pub max_connections: u32,
+}
+
+/// 核心逻辑:初始化数据库
+/// 这个函数不关心参数从哪里来(CLI、配置文件、环境变量)
+pub async fn init_db(config: &DbConfig) -> Result<()> {
+    println!("🔌 正在连接数据库: {}", config.url);
+    
+    // 模拟网络延迟
+    tokio::time::sleep(Duration::from_millis(500)).await;
+
+    // 模拟连接逻辑
+    if config.url.contains("error") {
+        anyhow::bail!("无法连接到数据库,URL 无效");
+    }
+
+    println!("✅ 连接成功!最大连接数设置为: {}", config.max_connections);
+    
+    // 模拟执行初始化脚本
+    run_migrations().await?;
+    
+    println!("🎉 数据库初始化完成!");
+    Ok(())
+}
+
+/// 核心逻辑:检查连接状态
+#[allow(dead_code)]
+pub async fn check_connection(url: &str) -> Result {
+    println!("🏓 正在 Ping 数据库: {}", url);
+    tokio::time::sleep(Duration::from_millis(200)).await;
+    
+    if url.contains("error") {
+        return Ok(false);
+    }
+    Ok(true)
+}
+
+/// 内部辅助函数:运行迁移
+async fn run_migrations() -> Result<()> {
+    println!("🚀 正在运行数据迁移脚本...");
+    tokio::time::sleep(Duration::from_millis(300)).await;
+    println!("   - 创建表 users");
+    println!("   - 创建表 logs");
+    Ok(())
+}
\ No newline at end of file
diff --git a/src/internal/database/mod.rs b/src/internal/database/mod.rs
new file mode 100644
index 0000000..8a0cf83
--- /dev/null
+++ b/src/internal/database/mod.rs
@@ -0,0 +1,8 @@
+// 公开具体的实现文件
+pub mod database;
+
+// 重新导出常用函数,方便 commands 层调用
+// 例如:use crate::internal::database::init_db;
+pub use database::init_db;
+//pub use database::check_connection;
+pub use database::DbConfig;
\ No newline at end of file
diff --git a/src/internal/mod.rs b/src/internal/mod.rs
new file mode 100644
index 0000000..c87e24e
--- /dev/null
+++ b/src/internal/mod.rs
@@ -0,0 +1,5 @@
+// 公开 database 模块,供外部(commands)使用
+
+pub mod database;
+pub mod config;
+pub mod network;
\ No newline at end of file
diff --git a/src/internal/network/ip.rs b/src/internal/network/ip.rs
new file mode 100644
index 0000000..84d9009
--- /dev/null
+++ b/src/internal/network/ip.rs
@@ -0,0 +1,238 @@
+use std::fmt;
+use std::error::Error;
+
+// 自定义错误类型(实现 Error trait 方便上层处理)
+#[derive(Debug)]
+pub enum IPError {
+    InvalidAddressFormat,
+    OutOfRange(String),
+    NonUnicastAddress,
+    ParseError(String),
+}
+
+impl fmt::Display for IPError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            IPError::InvalidAddressFormat => write!(f, "Invalid IP address format (must be x.x.x.x)"),
+            IPError::OutOfRange(msg) => write!(f, "IP address out of range: {}", msg),
+            IPError::NonUnicastAddress => write!(f, "Not a unicast IP address (invalid network type)"),
+            IPError::ParseError(msg) => write!(f, "IP parse error: {}", msg),
+        }
+    }
+}
+
+impl Error for IPError {}
+
+// IP 地址核心结构体(IPv4)
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct IPAddr(u32);
+
+impl IPAddr {
+    /// 从字符串创建 IP 地址(如 "192.168.1.1")
+    pub fn from_str(s: &str) -> Result {
+        let parts: Vec<&str> = s.split('.').collect();
+        if parts.len() != 4 {
+            return Err(IPError::InvalidAddressFormat);
+        }
+
+        let mut octets = [0u8; 4];
+        for (i, part) in parts.iter().enumerate() {
+            octets[i] = part.parse().map_err(|e| {
+                IPError::ParseError(format!("Failed to parse octet {}: {}", part, e))
+            })?;
+        }
+
+        // 转换为 u32(大端序:第一个 octet 是最高位)
+        let addr = ((octets[0] as u32) << 24)
+            | ((octets[1] as u32) << 16)
+            | ((octets[2] as u32) << 8)
+            | (octets[3] as u32);
+
+        Ok(Self(addr))
+    }
+
+    /// 从 u32 数值创建 IP 地址
+    #[allow(dead_code)]
+    pub fn from_u32(addr: u32) -> Self {
+        Self(addr)
+    }
+
+    /// 获取 u32 格式的地址
+    pub fn to_u32(&self) -> u32 {
+        self.0
+    }
+
+    /// 位运算:与
+    pub fn bitwise_and(&self, other: &Self) -> Self {
+        Self(self.0 & other.0)
+    }
+
+    /// 位运算:或
+    #[allow(dead_code)]
+    pub fn bitwise_or(&self, other: &Self) -> Self {
+        Self(self.0 | other.0)
+    }
+
+    /// 位运算:非
+    pub fn bitwise_not(&self) -> Self {
+        Self(!self.0)
+    }
+
+    /// 地址加法(支持负数)
+    pub fn add(&self, n: i32) -> Self {
+        if n >= 0 {
+            Self(self.0 + n as u32)
+        } else {
+            Self(self.0 - n.abs() as u32)
+        }
+    }
+}
+
+// 实现 Display 以便格式化输出
+impl fmt::Display for IPAddr {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "{}.{}.{}.{}",
+            (self.0 >> 24) & 0xFF,
+            (self.0 >> 16) & 0xFF,
+            (self.0 >> 8) & 0xFF,
+            self.0 & 0xFF
+        )
+    }
+}
+
+// IP 生成器核心结构体
+#[derive(Debug)]
+pub struct IPGenerator {
+    network: IPAddr,
+    netmask: IPAddr,
+    addr: IPAddr,
+}
+
+impl IPGenerator {
+    /// 创建 IP 生成器实例
+    /// - network: 网络地址(如 "10.1.1.0")
+    /// - netmask: 子网掩码(None 则自动推断 A/B/C 类)
+    pub fn new(network: &str, netmask: Option<&str>) -> Result {
+        let network_addr = IPAddr::from_str(network)?;
+        let netmask_addr = match netmask {
+            Some(addr_str) => IPAddr::from_str(addr_str)?,
+            None => Self::infer_netmask(&network_addr)?,
+        };
+
+        // 初始IP = 网络地址 + 1(第一个可用地址)
+        let initial_addr = network_addr.add(1);
+
+        // 效验初始 IP 是否在合法范围
+        Self::validate_ip_in_range(&initial_addr, &network_addr, &netmask_addr)?;
+
+        Ok(Self {
+            network: network_addr,
+            netmask: netmask_addr,
+            addr: initial_addr,
+        })
+    }
+
+    /// 自动推断子网掩码(A/B/C 类)
+    fn infer_netmask(addr: &IPAddr) -> Result {
+        let first_octet = (addr.0 >> 24) & 0xFF;
+        match first_octet {
+            // A 类: 0-127
+            0..=127 => Ok(IPAddr::from_str("255.0.0.0")?),
+            // B 类: 128-191
+            128..=191 => Ok(IPAddr::from_str("255.255.0.0")?),
+            // C 类: 192-223
+            192..=223 => Ok(IPAddr::from_str("255.255.255.0")?),
+            // 非单播地址
+            _ => Err(IPError::NonUnicastAddress),
+        }
+    }
+
+    // 校验 IP 是否在网段合法范围(非网络/广播地址)
+    fn validate_ip_in_range(ip: &IPAddr, network: &IPAddr, netmask: &IPAddr) -> Result<(), IPError> {
+        let inverted_netmask = netmask.bitwise_not();
+        let net_addr = network.bitwise_and(netmask).to_u32();
+        let ip_addr = ip.to_u32();
+        let broadcast_addr = net_addr | inverted_netmask.to_u32();
+
+        // 检查是否是网络地址
+        if ip_addr == net_addr {
+            return Err(IPError::OutOfRange("IP is network address".to_string()));
+        }
+        // 检查是否是广播地址
+        if ip_addr == broadcast_addr {
+            return Err(IPError::OutOfRange("IP is broadcast address".to_string()));
+        }
+        // 检查是否在网段内
+        if (ip_addr & netmask.to_u32()) != net_addr {
+            return Err(IPError::OutOfRange("IP is not in network range".to_string()));
+        }
+
+        Ok(())
+    }
+
+    // 获取当前 IP(带校验)
+    pub fn curr(&self) -> Result {
+        Self::validate_ip_in_range(&self.addr, &self.network, &self.netmask)?;
+        Ok(self.addr)
+    }
+
+    // 动态计算最大可用步长(根据子网掩码)
+    pub fn get_max_available_step(&self) -> u32 {
+        let inverted_netmask = self.netmask.bitwise_not().to_u32();
+        // 可用 IP 数量 = 主机位总数 - 2(排除网络/广播地址)
+        let total_available = inverted_netmask - 1; // 减 1 是因为初始 IP 已经是 +1 了
+        total_available as u32
+    }
+
+    // 获取子网掩码的字符串形式
+    pub fn get_netmask_str(&self) -> String {
+        format!("{}", self.netmask)
+    }
+
+    /// 获取网络地址(如 10.1.1.0/25 对应 10.1.1.0)
+    pub fn get_network(&self) -> String {
+        format!("{}", self.addr.bitwise_and(&self.netmask))
+    }
+
+    // 偏移 IP(仅支持正数步长)
+    pub fn next(&mut self, n: i32) -> Result {
+        // 强制转为正数(禁用负数偏移)
+        let step = if n < 0 { 0 } else { n as u32 };
+        let new_addr = self.addr.add(step as i32);
+
+        // 校验新 IP 是否合法
+        Self::validate_ip_in_range(&new_addr, &self.network, &self.netmask)?;
+
+        self.addr = new_addr;
+        Ok(self.addr)
+    }
+
+    /// 地址递减(等价于 next(-1))
+    #[allow(dead_code)]
+    pub fn dec(&mut self) -> Result {
+        self.next(-1)
+    }
+
+}
+
+// 测试用例(可选)
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_ip_from_str() {
+        let ip = IPAddr::from_str("10.1.1.0").unwrap();
+        assert_eq!(ip.to_u32(), 0x0a010100);
+    }
+
+    #[test]
+    fn test_ip_generator() {
+        let mut generator = IPGenerator::new("10.1.1.0", Some("255.255.255.128")).unwrap();
+        assert_eq!(generator.curr().unwrap().to_string(), "10.1.1.127");
+        generator.next(-126).unwrap();
+        assert_eq!(generator.curr().unwrap().to_string(), "10.1.1.1");
+    }
+}
\ No newline at end of file
diff --git a/src/internal/network/mod.rs b/src/internal/network/mod.rs
new file mode 100644
index 0000000..c883556
--- /dev/null
+++ b/src/internal/network/mod.rs
@@ -0,0 +1,3 @@
+pub mod ip;
+
+pub use ip::IPGenerator;
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index ef1b6de..306401d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,13 +1,12 @@
 use clap::Parser;
 use anyhow::Result;
 
-// 引入 commands 模块
-mod commands;
-mod utils; // 假设有通用工具
+mod commands; // 引入 commands 模块
+mod internal; // 引入 internal 模块
+mod utils;    // 引入 utils 通用工具模块
 
 use commands::CliCommands;
 
-/// 我的超级 CLI 工具
 #[derive(Parser)]
 #[command(name = "sunhpc")]
 #[command(author = "Qichao.Sun")]
@@ -23,7 +22,8 @@ struct Cli {
     command: CliCommands,
 }
 
-fn main() -> Result<()> {
+#[tokio::main]
+async fn main() -> Result<()> {
     let cli = Cli::parse();
 
     if cli.debug {
@@ -33,7 +33,9 @@ fn main() -> Result<()> {
     // 根据子命令分发逻辑
     match cli.command {
         CliCommands::Server(args) => commands::server::execute(args)?,
-        CliCommands::Db(args) => commands::db::execute(args)?,
+        CliCommands::Db(args) => commands::db::execute(args).await?,
+        CliCommands::Config(args) => commands::config::execute(args)?,
+        CliCommands::Report(args) => commands::report::execute(args)?,
         // 未来扩展新命令时,只需在这里添加新的匹配臂
         // CliCommands::NewFeature(args) => commands::new_feature::execute(args)?,
     }