Files
craftlabs-authorization-sdk/native/craftlabs-auth-cli/src/main.rs
T

250 lines
9.3 KiB
Rust

use clap::{Parser, Subcommand};
use craftlabs_auth_core::{
craft_activate, craft_check_license, craft_destroy, craft_free_license_info,
craft_get_license_info, craft_heartbeat, craft_initialize, craft_release,
};
use craftlabs_auth_core::device;
use std::ffi::CString;
use std::path::PathBuf;
use std::fs;
mod config;
mod platform_api;
use config::Config;
use platform_api::PlatformClient;
#[derive(Parser)]
#[command(name = "craft", version, about = "CraftLabs 授权客户端")]
struct Cli {
#[command(subcommand)]
command: Commands,
/// 平台 API 地址 (默认: http://localhost:8080)
#[arg(long, global = true)]
api: Option<String>,
/// JSON 格式输出
#[arg(long, global = true)]
json: bool,
}
#[derive(Subcommand)]
enum Commands {
/// 查看本地授权状态
Status,
/// 使用 SN 激活本机
Activate { sn: String },
/// 检查授权是否有效
Check,
/// 显示授权详情
Info,
/// 撤销本机授权
Release,
/// 迁移授权到本机 (释放旧授权 + 激活新 SN)
Migrate { sn: String },
/// 显示本机设备指纹
DeviceId,
/// 手动触发心跳
Heartbeat,
/// 查看/修改配置
Config {
/// 查看或设置: status, set-api <url>, set-sn <sn>
action: Vec<String>,
},
}
fn get_config_path() -> PathBuf {
let mut path = dirs::config_dir().unwrap_or_else(|| PathBuf::from("."));
path.push("craftlabs");
fs::create_dir_all(&path).ok();
path.join("config.json")
}
fn main() {
let cli = Cli::parse();
let config_path = get_config_path();
let mut config = Config::load(&config_path);
if let Some(api_url) = &cli.api {
config.api_base_url = api_url.clone();
}
let rt = tokio::runtime::Runtime::new().unwrap();
match &cli.command {
Commands::Status => {
print_status(&config, cli.json);
}
Commands::Activate { sn } => {
let handle = init_engine();
if handle.is_null() {
eprintln!("错误: 初始化授权引擎失败");
std::process::exit(1);
}
let c_sn = CString::new(sn.as_str()).unwrap();
let result = craft_activate(handle, c_sn.as_ptr(), std::ptr::null());
if result.success != 0 {
println!("OK: 激活成功");
config.sn = Some(sn.clone());
config.save(&config_path);
rt.block_on(sync_activation(&config, sn));
} else {
let msg = if result.message.is_null() { "未知错误" } else {
unsafe { std::ffi::CStr::from_ptr(result.message) }.to_str().unwrap_or("未知错误")
};
eprintln!("错误: 激活失败 - {}", msg);
}
craft_destroy(handle);
}
Commands::Check => {
let handle = init_engine();
if handle.is_null() { eprintln!("错误: 初始化失败"); return; }
let result = craft_check_license(handle);
if cli.json {
let msg = if !result.message.is_null() {
unsafe { std::ffi::CStr::from_ptr(result.message) }.to_str().unwrap_or("")
} else { "" };
println!("{{\"status\":{},\"message\":\"{}\"}}", result.success, msg);
} else {
println!("授权状态: {}", if result.success != 0 { "有效" } else { "无效" });
}
craft_destroy(handle);
}
Commands::Info => {
let handle = init_engine();
if handle.is_null() { eprintln!("错误: 初始化失败"); return; }
let info_ptr = craft_get_license_info(handle);
if !info_ptr.is_null() {
let info = unsafe { &*info_ptr };
println!("授权状态: {}", if info.is_licensed != 0 { "已授权" } else { "未授权" });
if info.expiration_date > 0 {
println!("过期时间戳: {}", info.expiration_date);
}
if info.feature_count > 0 {
println!("功能特性 ({}):", info.feature_count);
let names = unsafe { std::slice::from_raw_parts(info.feature_names, info.feature_count as usize) };
let values = unsafe { std::slice::from_raw_parts(info.feature_values, info.feature_count as usize) };
for idx in 0..info.feature_count as usize {
let name = if idx < names.len() && !names[idx].is_null() {
unsafe { std::ffi::CStr::from_ptr(names[idx]) }
.to_str().unwrap_or("?")
} else { "?" };
let val = if idx < values.len() { values[idx] } else { 0 };
println!(" {}: {}", name, if val != 0 { "ON" } else { "OFF" });
}
}
craft_free_license_info(info_ptr);
} else {
println!("无法获取授权信息");
}
craft_destroy(handle);
}
Commands::Release => {
let handle = init_engine();
if handle.is_null() { eprintln!("错误: 初始化失败"); return; }
let result = craft_release(handle);
if result.success != 0 {
println!("OK: 授权已撤销");
config.sn = None;
config.save(&config_path);
} else {
eprintln!("错误: 撤销失败");
}
craft_destroy(handle);
}
Commands::Migrate { sn } => {
println!("正在迁移授权到新 SN: {} ...", sn);
let handle = init_engine();
if handle.is_null() { eprintln!("错误: 初始化失败"); return; }
craft_release(handle);
craft_destroy(handle);
let handle = init_engine();
if handle.is_null() { eprintln!("错误: 初始化失败"); return; }
let c_sn = CString::new(sn.as_str()).unwrap();
let result = craft_activate(handle, c_sn.as_ptr(), std::ptr::null());
if result.success != 0 {
println!("OK: 迁移成功");
config.sn = Some(sn.clone());
config.save(&config_path);
} else {
eprintln!("错误: 迁移失败");
}
craft_destroy(handle);
}
Commands::DeviceId => {
let fp = device::collect();
if cli.json {
println!("{{\"device_id\":\"{}\"}}", fp.composite_hash);
} else {
println!("设备指纹: {}", fp.composite_hash);
}
}
Commands::Heartbeat => {
let handle = init_engine();
if handle.is_null() { eprintln!("错误: 初始化失败"); return; }
let result = craft_heartbeat(handle);
println!("心跳: {}", if result.success != 0 { "OK" } else { "FAIL" });
craft_destroy(handle);
}
Commands::Config { action } => {
handle_config(action, &mut config, &config_path);
}
}
}
fn init_engine() -> *mut craftlabs_auth_core::CraftContext {
let config_json = r#"{"provider":"selfhosted","schemaVersion":1,"scenario":"floating"}"#;
let c = CString::new(config_json).unwrap();
craft_initialize(c.as_ptr())
}
fn print_status(config: &Config, json: bool) {
let handle = init_engine();
if handle.is_null() { eprintln!("错误: 初始化失败"); return; }
let result = craft_check_license(handle);
let fp = device::collect();
if json {
println!("{{\"licensed\":{},\"device_id\":\"{}\",\"api\":\"{}\",\"sn\":{}}}",
result.success, fp.composite_hash, config.api_base_url,
config.sn.as_deref().map(|s| format!("\"{}\"", s)).unwrap_or("null".into()));
} else {
println!("授权状态: {}", if result.success != 0 { "有效" } else { "无效" });
println!("设备指纹: {}", fp.composite_hash);
println!("API 地址: {}", config.api_base_url);
if let Some(sn) = &config.sn {
println!("绑定 SN: {}", sn);
}
}
craft_destroy(handle);
}
fn handle_config(action: &[String], config: &mut Config, path: &PathBuf) {
if action.is_empty() {
println!("API 地址: {}", config.api_base_url);
println!("绑定 SN: {}", config.sn.as_deref().unwrap_or("(无)"));
println!("配置路径: {}", path.display());
return;
}
match action[0].as_str() {
"set-api" if action.len() > 1 => {
config.api_base_url = action[1].clone();
config.save(path);
println!("OK: API 地址已更新");
}
"set-sn" if action.len() > 1 => {
config.sn = Some(action[1].clone());
config.save(path);
println!("OK: SN 已设置");
}
_ => eprintln!("用法: craft config [set-api <url>|set-sn <sn>]"),
}
}
async fn sync_activation(config: &Config, sn: &str) {
let client = PlatformClient::new(&config.api_base_url);
match client.report_activation(sn, &device::collect().composite_hash).await {
Ok(_) => println!("平台同步成功"),
Err(e) => eprintln!("平台同步失败: {} (不影响本地授权)", e),
}
}