KeiSeiKit-1.0/_primitives/_rust/kei-provision/src/main.rs
Parfii-bot 0be354a920 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

141 lines
3.9 KiB
Rust

//! kei-provision — unified VPS provisioner CLI.
//!
//! USAGE
//! kei-provision <backend> create <name> [--type T] [--location L]
//! [--image I] [--ssh-key K]
//! [--firewall F] [--user-data PATH]
//! kei-provision <backend> status <name>
//! kei-provision <backend> destroy <name> [--force]
//! kei-provision <backend> list
//!
//! <backend>: hetzner | vultr
//!
//! ENV (RULE 0.8 — secrets single source)
//! HCLOUD_TOKEN — Hetzner API token
//! VULTR_API_KEY — Vultr API key
//!
//! Source via: `source ~/.claude/secrets/.env` before invocation.
use clap::{Parser, Subcommand};
use kei_provision::{resolve, CreateOpts};
use std::path::PathBuf;
use std::process::ExitCode;
#[derive(Parser, Debug)]
#[command(
name = "kei-provision",
about = "Unified VPS provisioner — Hetzner, Vultr, (future) AWS/DO/Linode."
)]
struct Cli {
/// Backend to use.
backend: String,
#[command(subcommand)]
cmd: Cmd,
}
#[derive(Subcommand, Debug)]
enum Cmd {
/// Create a server (idempotent — returns existing IP if name/label taken).
Create {
name: String,
/// Server type / plan (hetzner: `cx22`; vultr: `vc2-1c-1gb`).
#[arg(long)]
r#type: Option<String>,
/// Datacenter (hetzner: `fsn1`; vultr: `ams`).
#[arg(long)]
location: Option<String>,
/// Image / OS (hetzner: `debian-12`; vultr: os-id string).
#[arg(long)]
image: Option<String>,
/// SSH key id/name.
#[arg(long = "ssh-key")]
ssh_key: Option<String>,
/// Firewall id/name.
#[arg(long)]
firewall: Option<String>,
/// cloud-init user-data file.
#[arg(long = "user-data")]
user_data: Option<PathBuf>,
},
/// Print server info (absent ⇒ "absent" line, exit 0).
Status { name: String },
/// Destroy server (idempotent on absent).
Destroy {
name: String,
#[arg(long)]
force: bool,
},
/// List all servers on this account.
List,
}
fn main() -> ExitCode {
let cli = Cli::parse();
let backend = match resolve(&cli.backend) {
Ok(b) => b,
Err(e) => {
eprintln!("kei-provision: {e}");
return ExitCode::from(1);
}
};
match run(&*backend, cli.cmd) {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("kei-provision [{}]: {e}", backend.name());
ExitCode::from(2)
}
}
}
fn run(b: &dyn kei_provision::Backend, cmd: Cmd) -> anyhow::Result<()> {
match cmd {
Cmd::Create {
name,
r#type,
location,
image,
ssh_key,
firewall,
user_data,
} => {
let opts = CreateOpts {
server_type: r#type,
location,
image,
ssh_key,
firewall,
user_data_path: user_data,
};
let info = b.create(&name, &opts)?;
println!("{}", info.ipv4.unwrap_or_else(|| "-".into()));
}
Cmd::Status { name } => match b.status(&name)? {
None => println!("absent"),
Some(i) => print_status(&i),
},
Cmd::Destroy { name, force } => b.destroy(&name, force)?,
Cmd::List => {
for i in b.list()? {
println!(
"{}\t{}\t{}\t{}",
i.name,
i.status,
i.ipv4.unwrap_or_else(|| "-".into()),
i.id
);
}
}
}
Ok(())
}
fn print_status(i: &kei_provision::ServerInfo) {
println!("name={}", i.name);
println!("id={}", i.id);
println!("status={}", i.status);
println!(
"ipv4={}",
i.ipv4.clone().unwrap_or_else(|| "-".into())
);
}