init
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/target
|
||||
Generated
+2473
File diff suppressed because it is too large
Load Diff
+20
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "rust-learning"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
# 显式重命名二进制产物
|
||||
[[bin]]
|
||||
name = "toolbox" # 输出的二进制文件名
|
||||
path = "src/main.rs" # 入口文件路径
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.6.6", features = ["derive"] }
|
||||
flate2 = "1.1.10"
|
||||
reqwest = { version = "0.13.4", features = ["json"] }
|
||||
serde = { version = "1.0.229", features = ["derive"] }
|
||||
serde_json = "1.0.151"
|
||||
sysinfo = "0.39.6"
|
||||
tar = "0.4.46"
|
||||
tokio = { version = "1.53.1", features = ["full"] }
|
||||
zip = "8.6.0"
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
use clap::{Args, Subcommand};
|
||||
use flate2::read::GzDecoder;
|
||||
use flate2::write::GzEncoder;
|
||||
use flate2::Compression;
|
||||
use std::fs::File;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tar::Archive as TarArchive;
|
||||
use tar::Builder as TarBuilder;
|
||||
use zip::ZipArchive;
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct ArchiveArgs {
|
||||
#[command(subcommand)]
|
||||
pub command: ArchiveCommands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum ArchiveCommands {
|
||||
/// 自动识别格式并解压 (替代 extract)
|
||||
Extract {
|
||||
/// 压缩包路径
|
||||
file: PathBuf,
|
||||
/// 解压目标目录 (默认为当前目录)
|
||||
#[arg(short, long)]
|
||||
target: Option<PathBuf>,
|
||||
},
|
||||
/// 打包为 .tar.gz (替代 targz)
|
||||
Compress {
|
||||
/// 输出的 .tar.gz 文件名
|
||||
output: PathBuf,
|
||||
/// 要压缩的文件或目录列表
|
||||
files: Vec<PathBuf>,
|
||||
},
|
||||
/// 查看压缩包内的内容 (替代 tarls)
|
||||
List {
|
||||
/// 压缩包路径
|
||||
file: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
pub async fn run(args: ArchiveArgs) -> Result<(), Box<dyn std::error::Error>> {
|
||||
match args.command {
|
||||
ArchiveCommands::Extract { file, target } => {
|
||||
let out_dir = target.unwrap_or_else(|| PathBuf::from("."));
|
||||
extract_archive(&file, &out_dir)?;
|
||||
}
|
||||
ArchiveCommands::Compress { output, files } => {
|
||||
compress_targz(&output, &files)?;
|
||||
}
|
||||
ArchiveCommands::List { file } => {
|
||||
list_archive(&file)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 智能解压实现
|
||||
fn extract_archive(archive_path: &Path, target_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if !archive_path.exists() {
|
||||
return Err(format!("文件不存在: {:?}", archive_path).into());
|
||||
}
|
||||
|
||||
let file_name = archive_path.to_string_lossy().to_lowercase();
|
||||
|
||||
println!("📦 正在解压 {:?} 到 {:?} ...", archive_path, target_dir);
|
||||
|
||||
if file_name.ends_with(".tar.gz") || file_name.ends_with(".tgz") {
|
||||
let file = File::open(archive_path)?;
|
||||
let gz = GzDecoder::new(file);
|
||||
let mut archive = TarArchive::new(gz);
|
||||
archive.unpack(target_dir)?;
|
||||
} else if file_name.ends_with(".zip") {
|
||||
let file = File::open(archive_path)?;
|
||||
let mut archive = ZipArchive::new(file)?;
|
||||
archive.extract(target_dir)?;
|
||||
} else {
|
||||
return Err("不支持的压缩包格式!仅支持 .tar.gz, .tgz, .zip".into());
|
||||
}
|
||||
|
||||
println!("✅ 解压完成!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// .tar.gz 压缩实现
|
||||
fn compress_targz(output_path: &Path, files: &[PathBuf]) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let tar_gz_file = File::create(output_path)?;
|
||||
let enc = GzEncoder::new(tar_gz_file, Compression::default());
|
||||
let mut tar = TarBuilder::new(enc);
|
||||
|
||||
for path in files {
|
||||
if !path.exists() {
|
||||
eprintln!("警告: 跳过不存在的文件/目录 {:?}", path);
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = path.file_name().ok_or("无效的文件名")?;
|
||||
if path.is_dir() {
|
||||
tar.append_dir_all(name, path)?;
|
||||
} else {
|
||||
tar.append_path_with_name(path, name)?;
|
||||
}
|
||||
}
|
||||
|
||||
tar.finish()?;
|
||||
println!("打包成功生成: {:?}", output_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 查看压缩包列表实现
|
||||
fn list_archive(archive_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let file_name = archive_path.to_string_lossy().to_lowercase();
|
||||
|
||||
if file_name.ends_with(".tar.gz") || file_name.ends_with(".tgz") {
|
||||
let file = File::open(archive_path)?;
|
||||
let gz = GzDecoder::new(file);
|
||||
let mut archive = TarArchive::new(gz);
|
||||
|
||||
println!("{:<10} {:<10} 名称", "权限", "大小");
|
||||
println!("{}", "-".repeat(40));
|
||||
for entry in archive.entries()? {
|
||||
let entry = entry?;
|
||||
let header = entry.header();
|
||||
println!("{:<10} {:<10} {:?}", header.mode()?, header.size()?, entry.path()?);
|
||||
}
|
||||
} else if file_name.ends_with(".zip") {
|
||||
let file = File::open(archive_path)?;
|
||||
let mut archive = ZipArchive::new(file)?;
|
||||
|
||||
println!("{:<10} 名称", "大小");
|
||||
println!("{}", "-".repeat(30));
|
||||
for i in 0..archive.len() {
|
||||
let file = archive.by_index(i)?;
|
||||
println!("{:<10} {}", file.size(), file.name());
|
||||
}
|
||||
} else {
|
||||
return Err("不支持的压缩包格式".into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
use clap::Args;
|
||||
use serde::Serialize;
|
||||
|
||||
/// Bark 推送命令的具体参数
|
||||
#[derive(Args, Debug)]
|
||||
pub struct BarkArgs {
|
||||
/// 接口请求地址 URL
|
||||
#[arg(short, long, default_value = "https://bark.maimaicuizhiji.top/push")]
|
||||
pub url: String,
|
||||
|
||||
/// 设备 Key (device_key)
|
||||
#[arg(short, long)]
|
||||
pub device_key: String,
|
||||
|
||||
/// 通知标题
|
||||
#[arg(short, long)]
|
||||
pub title: String,
|
||||
|
||||
/// 通知内容
|
||||
#[arg(short, long)]
|
||||
pub body: String,
|
||||
|
||||
/// 分组名称
|
||||
#[arg(short, long, default_value = "backup")]
|
||||
pub group: String,
|
||||
|
||||
/// 是否归档 (1 或 0)
|
||||
#[arg(long, default_value_t = 1)]
|
||||
pub is_archive: u8,
|
||||
|
||||
/// 存活时间 TTL(秒)
|
||||
#[arg(long, default_value_t = 3600)]
|
||||
pub ttl: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PushPayload<'a> {
|
||||
device_key: &'a str,
|
||||
title: &'a str,
|
||||
body: &'a str,
|
||||
group: &'a str,
|
||||
#[serde(rename = "isArchive")]
|
||||
is_archive: u8,
|
||||
ttl: u64,
|
||||
}
|
||||
|
||||
/// 执行 Bark 推送逻辑的入口函数
|
||||
pub async fn run(args: BarkArgs) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let payload = PushPayload {
|
||||
device_key: &args.device_key,
|
||||
title: &args.title,
|
||||
body: &args.body,
|
||||
group: &args.group,
|
||||
is_archive: args.is_archive,
|
||||
ttl: args.ttl,
|
||||
};
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let response = client.post(&args.url).json(&payload).send().await?;
|
||||
|
||||
if response.status().is_success() {
|
||||
let resp_text = response.text().await?;
|
||||
println!("Bark 推送成功: {}", resp_text);
|
||||
} else {
|
||||
eprintln!("Bark 推送失败,状态码: {}", response.status());
|
||||
let err_text = response.text().await?;
|
||||
eprintln!("错误信息: {}", err_text);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
mod bark;
|
||||
mod nettool;
|
||||
mod archive;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "toolbox", version, about = "多功能集成运维 CLI 工具")]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Commands {
|
||||
/// 发送 Bark 推送通知
|
||||
Bark(bark::BarkArgs),
|
||||
/// 网络运维诊断工具
|
||||
Nettool(nettool::NetToolArgs),
|
||||
/// 文件归档与解压工具
|
||||
Archive(archive::ArchiveArgs),
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
match cli.command {
|
||||
Commands::Bark(args) => {
|
||||
bark::run(args).await?;
|
||||
}
|
||||
Commands::Nettool(args) => {
|
||||
nettool::run(args).await?;
|
||||
}
|
||||
Commands::Archive(args) => {
|
||||
archive::run(args).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
use clap::{Args, Subcommand};
|
||||
use std::net::{TcpStream, ToSocketAddrs};
|
||||
use std::time::Duration;
|
||||
use sysinfo::{ProcessesToUpdate, System};
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct NetToolArgs {
|
||||
#[command(subcommand)]
|
||||
pub command: NetCommands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum NetCommands {
|
||||
/// 查看端口占用情况
|
||||
Port {
|
||||
/// 要查询的端口号
|
||||
port: u16,
|
||||
},
|
||||
/// 测试目标地址端口连通性
|
||||
Probe {
|
||||
/// 目标主机 (IP 或域名)
|
||||
host: String,
|
||||
/// 目标端口
|
||||
port: u16,
|
||||
/// 超时时间(毫秒)
|
||||
#[arg(short, long, default_value_t = 2000)]
|
||||
timeout: u64,
|
||||
},
|
||||
/// 获取本机公网 IP
|
||||
Myip,
|
||||
}
|
||||
|
||||
pub async fn run(args: NetToolArgs) -> Result<(), Box<dyn std::error::Error>> {
|
||||
match args.command {
|
||||
NetCommands::Port { port } => {
|
||||
check_port(port);
|
||||
}
|
||||
NetCommands::Probe { host, port, timeout } => {
|
||||
test_probe(&host, port, timeout);
|
||||
}
|
||||
NetCommands::Myip => {
|
||||
fetch_my_ip().await;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_port(port: u16) {
|
||||
let mut sys = System::new_all();
|
||||
sys.refresh_processes(ProcessesToUpdate::All, true);
|
||||
|
||||
println!("正在查询端口 {} 的占用情况...", port);
|
||||
let mut found = false;
|
||||
for (pid, process) in sys.processes() {
|
||||
if process.name().to_string_lossy().contains("listen") {
|
||||
println!("PID: {:<8} 进程名: {}", pid, process.name().to_string_lossy());
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
println!("未发现端口 {} 的相关进程,或需要更高权限。", port);
|
||||
}
|
||||
}
|
||||
|
||||
fn test_probe(host: &str, port: u16, timeout_ms: u64) {
|
||||
let addr_str = format!("{}:{}", host, port);
|
||||
println!("正在探测 {} ...", addr_str);
|
||||
|
||||
match addr_str.to_socket_addrs() {
|
||||
Ok(mut addrs) => {
|
||||
if let Some(addr) = addrs.next() {
|
||||
let timeout = Duration::from_millis(timeout_ms);
|
||||
match TcpStream::connect_timeout(&addr, timeout) {
|
||||
Ok(_) => println!("连接成功![{}]", addr),
|
||||
Err(e) => eprintln!("连接失败: {}", e),
|
||||
}
|
||||
} else {
|
||||
eprintln!("域名无法解析");
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("地址解析错误: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_my_ip() {
|
||||
let providers = [
|
||||
"https://api.ipify.org",
|
||||
"https://ifconfig.me/ip",
|
||||
"https://icanhazip.com",
|
||||
];
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(3))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
for url in providers {
|
||||
if let Ok(resp) = client.get(url).send().await {
|
||||
if let Ok(ip) = resp.text().await {
|
||||
println!("Public IP: {}", ip.trim());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
eprintln!("获取公网 IP 失败,请检查网络设置。");
|
||||
}
|
||||
Reference in New Issue
Block a user