308 lines
10 KiB
Rust

#[macro_use] extern crate failure;
#[macro_use] extern crate serde_derive;
extern crate libc;
extern crate clap;
extern crate toml;
extern crate termcolor;
extern crate walkdir;
use failure::Error;
use clap::{App,Arg,ArgMatches,SubCommand};
use clap::AppSettings::*;
use std::process::exit;
use std::cell::RefCell;
use std::result;
pub type Result<T> = result::Result<T,Error>;
thread_local! {
pub static VERBOSE: RefCell<bool> = RefCell::new(true);
}
pub fn verbose() -> bool {
VERBOSE.with(|f| *f.borrow())
}
macro_rules! warn {
($e:expr) => { println!("[!]: {}", $e); };
($fmt:expr, $($arg:tt)+) => { println!("[!]: {}", format!($fmt, $($arg)+)); };
}
macro_rules! info {
($e:expr) => { if ::verbose() { println!("[+]: {}", $e); } };
($fmt:expr, $($arg:tt)+) => { if ::verbose() { println!("[+]: {}", format!($fmt, $($arg)+)); } };
}
mod manager;
mod realm;
mod util;
mod systemd;
mod config;
mod network;
mod appimg;
use realm::{Realm,RealmSymlinks};
use manager::RealmManager;
use config::RealmConfig;
use systemd::Systemd;
use network::NetworkConfig;
fn main() {
let app = App::new("citadel-realms")
.about("Subgraph Citadel realm management")
.after_help("'realms help <command>' to display help for an individual subcommand\n")
.global_settings(&[ColoredHelp, DisableVersion, DeriveDisplayOrder, VersionlessSubcommands ])
.arg(Arg::with_name("help").long("help").hidden(true))
.arg(Arg::with_name("quiet")
.long("quiet")
.help("Don't display extra output"))
.subcommand(SubCommand::with_name("list")
.arg(Arg::with_name("help").long("help").hidden(true))
.about("Display list of all realms"))
.subcommand(SubCommand::with_name("shell")
.arg(Arg::with_name("help").long("help").hidden(true))
.about("Open shell in current or named realm")
.arg(Arg::with_name("realm-name")
.help("Name of a realm to open shell in. Use current realm if omitted."))
.arg(Arg::with_name("root-shell")
.long("root")
.help("Open shell as root instead of user account.")))
.subcommand(SubCommand::with_name("terminal")
.arg(Arg::with_name("help").long("help").hidden(true))
.about("Launch terminal in current or named realm")
.arg(Arg::with_name("realm-name")
.help("Name of realm to open terminal in. Use current realm if omitted.")))
.subcommand(SubCommand::with_name("start")
.arg(Arg::with_name("help").long("help").hidden(true))
.about("Start named realm or default realm")
.arg(Arg::with_name("realm-name")
.help("Name of realm to start. Use default realm if omitted.")))
.subcommand(SubCommand::with_name("stop")
.arg(Arg::with_name("help").long("help").hidden(true))
.about("Stop a running realm by name")
.arg(Arg::with_name("realm-name")
.required(true)
.help("Name of realm to stop.")))
.subcommand(SubCommand::with_name("default")
.arg(Arg::with_name("help").long("help").hidden(true))
.about("Choose a realm to start automatically on boot")
.arg(Arg::with_name("realm-name")
.help("Name of a realm to set as default. Display current default realm if omitted.")))
.subcommand(SubCommand::with_name("current")
.arg(Arg::with_name("help").long("help").hidden(true))
.about("Choose a realm to set as 'current' realm")
.arg(Arg::with_name("realm-name")
.help("Name of a realm to set as current, will start if necessary. Display current realm name if omitted.")))
.subcommand(SubCommand::with_name("run")
.arg(Arg::with_name("help").long("help").hidden(true))
.about("Execute a command in named realm or current realm")
.arg(Arg::with_name("realm-name")
.help("Name of realm to run command in, start if necessary. Use current realm if omitted."))
.arg(Arg::with_name("args")
.required(true)
.last(true)
.allow_hyphen_values(true)
.multiple(true)))
.subcommand(SubCommand::with_name("update-appimg")
.arg(Arg::with_name("help").long("help").hidden(true))
.about("Launch shell to update application image")
.arg(Arg::with_name("appimg-name")
.long("appimg")
.help("Name of application image in /storage/appimg directory. Default is to use base.appimg")
.takes_value(true)))
.subcommand(SubCommand::with_name("new")
.arg(Arg::with_name("help").long("help").hidden(true))
.about("Create a new realm with the name provided")
.arg(Arg::with_name("realm-name")
.required(true)
.help("Name to assign to newly created realm")))
.subcommand(SubCommand::with_name("remove")
.arg(Arg::with_name("help").long("help").hidden(true))
.about("Remove realm by name")
.arg(Arg::with_name("no-confirm")
.long("no-confirm")
.help("Do not prompt for confirmation."))
.arg(Arg::with_name("remove-home")
.long("remove-home")
.help("Also remove home directory with --no-confirm rather than moving it to /realms/removed-homes"))
.arg(Arg::with_name("realm-name")
.help("Name of realm to remove")
.required(true)));
let matches = app.get_matches();
if matches.is_present("quiet") {
VERBOSE.with(|f| *f.borrow_mut() = false);
}
let result = match matches.subcommand() {
("list", _) => do_list(),
("start", Some(m)) => do_start(m),
("stop", Some(m)) => do_stop(m),
("default", Some(m)) => do_default(m),
("current", Some(m)) => do_current(m),
("run", Some(m)) => do_run(m),
("shell", Some(m)) => do_shell(m),
("terminal", Some(m)) => do_terminal(m),
("new", Some(m)) => do_new(m),
("remove", Some(m)) => do_remove(m),
("update-appimg", _) => do_update_appimg(),
_ => {
let _ = do_list();
exit(0);
},
};
if let Err(e) = result {
warn!("{}", e);
exit(1);
}
}
fn is_root() -> bool {
unsafe {
libc::geteuid() == 0
}
}
fn require_root() -> Result<()> {
if !is_root() {
bail!("You need to do that as root")
}
Ok(())
}
fn do_list() -> Result<()> {
let manager = RealmManager::load()?;
println!();
manager.list()?;
println!("\n 'realms help' for list of commands\n");
Ok(())
}
fn do_start(matches: &ArgMatches) -> Result<()> {
require_root()?;
let mut manager = RealmManager::load()?;
match matches.value_of("realm-name") {
Some(name) => manager.start_named_realm(name)?,
None => manager.start_default()?,
};
Ok(())
}
fn do_stop(matches: &ArgMatches) -> Result<()> {
require_root()?;
let name = matches.value_of("realm-name").unwrap();
let mut manager = RealmManager::load()?;
manager.stop_realm(name)?;
Ok(())
}
fn do_default(matches: &ArgMatches) -> Result<()> {
let manager = RealmManager::load()?;
match matches.value_of("realm-name") {
Some(name) => {
require_root()?;
manager.set_default_by_name(name)?;
},
None => {
if let Some(name) = manager.default_realm_name() {
println!("Default Realm: {}", name);
} else {
println!("No default realm.");
}
},
}
Ok(())
}
fn do_current(matches: &ArgMatches) -> Result<()> {
let manager = RealmManager::load()?;
match matches.value_of("realm-name") {
Some(name) => {
require_root()?;
manager.set_current_by_name(name)?;
},
None => {
if let Some(name) = manager.current_realm_name() {
println!("Current Realm: {}", name);
} else {
println!("No current realm.");
}
},
}
Ok(())
}
fn do_run(matches: &ArgMatches) -> Result<()> {
let args: Vec<&str> = matches.values_of("args").unwrap().collect();
let mut v = Vec::new();
for arg in args {
v.push(arg.to_string());
}
let manager = RealmManager::load()?;
manager.run_in_realm(matches.value_of("realm-name"), &v, true)?;
Ok(())
}
fn do_shell(matches: &ArgMatches) -> Result<()> {
let manager = RealmManager::load()?;
let root = matches.is_present("root-shell");
manager.launch_shell(matches.value_of("realm-name"), root)?;
Ok(())
}
fn do_terminal(matches: &ArgMatches) -> Result<()> {
let manager = RealmManager::load()?;
manager.launch_terminal(matches.value_of("realm-name"))?;
Ok(())
}
fn do_new(matches: &ArgMatches) -> Result<()> {
require_root()?;
let name = matches.value_of("realm-name").unwrap();
let mut manager = RealmManager::load()?;
manager.new_realm(name)?;
Ok(())
}
fn do_remove(matches: &ArgMatches) -> Result<()> {
require_root()?;
let confirm = !matches.is_present("no-confirm");
let save_home = !matches.is_present("remove-home");
let name = matches.value_of("realm-name").unwrap();
let mut manager = RealmManager::load()?;
manager.remove_realm(name, confirm, save_home)?;
Ok(())
}
fn do_update_appimg() -> Result<()> {
require_root()?;
let manager = RealmManager::load()?;
manager.base_appimg_update()
}