ALPHA: wire is a tool to deploy nixos systems wire.althaea.zone/
at stable 173 lines 5.4 kB view raw
1// SPDX-License-Identifier: AGPL-3.0-or-later 2// Copyright 2024-2025 wire Contributors 3 4#![deny(clippy::pedantic)] 5#![feature(sync_nonpoison)] 6#![feature(nonpoison_mutex)] 7#![feature(assert_matches)] 8 9use std::process::Command; 10use std::sync::Arc; 11use std::sync::atomic::AtomicBool; 12 13use crate::cli::Cli; 14use crate::cli::Partitions; 15use crate::cli::ToSubCommandModifiers; 16use crate::sigint::handle_signals; 17use crate::tracing_setup::setup_logging; 18use clap::CommandFactory; 19use clap::Parser; 20use clap_complete::CompleteEnv; 21use miette::IntoDiagnostic; 22use miette::Result; 23use signal_hook::consts::SIGINT; 24use signal_hook_tokio::Signals; 25use tracing::error; 26use tracing::warn; 27use wire_core::cache::InspectionCache; 28use wire_core::commands::common::get_hive_node_names; 29use wire_core::hive::Hive; 30use wire_core::hive::get_hive_location; 31use wire_core::hive::node::ApplyObjective; 32use wire_core::hive::node::Objective; 33use wire_core::hive::node::should_apply_locally; 34 35#[macro_use] 36extern crate enum_display_derive; 37 38mod apply; 39mod cli; 40mod sigint; 41mod tracing_setup; 42 43#[cfg(feature = "dhat-heap")] 44#[global_allocator] 45static ALLOC: dhat::Alloc = dhat::Alloc; 46 47#[tokio::main] 48async fn main() -> Result<()> { 49 #[cfg(feature = "dhat-heap")] 50 let _profiler = dhat::Profiler::new_heap(); 51 CompleteEnv::with_factory(Cli::command).complete(); 52 53 let args = Cli::parse(); 54 55 let modifiers = args.to_subcommand_modifiers(); 56 // disable progress when running inspect mode. 57 setup_logging( 58 &args.verbose, 59 !matches!(args.command, cli::Commands::Inspect { .. }) && !&args.no_progress, 60 ); 61 62 #[cfg(debug_assertions)] 63 if args.markdown_help { 64 clap_markdown::print_help_markdown::<Cli>(); 65 return Ok(()); 66 } 67 68 if !check_nix_available() { 69 miette::bail!("Nix is not available on this system."); 70 } 71 72 let signals = Signals::new([SIGINT]).into_diagnostic()?; 73 let signals_handle = signals.handle(); 74 let should_shutdown = Arc::new(AtomicBool::new(false)); 75 let signals_task = tokio::spawn(handle_signals(signals, should_shutdown.clone())); 76 77 let location = get_hive_location(args.path, modifiers).await?; 78 let cache = InspectionCache::new().await; 79 80 match args.command { 81 cli::Commands::Apply(apply_args) => { 82 let mut hive = Hive::new_from_path(&location, cache.clone(), modifiers).await?; 83 let goal: wire_core::hive::node::Goal = apply_args.goal.clone().try_into().unwrap(); 84 85 // Respect user's --always-build-local arg 86 hive.force_always_local(apply_args.always_build_local)?; 87 88 apply::apply( 89 &mut hive, 90 should_shutdown, 91 location, 92 apply_args.common, 93 Partitions::default(), 94 |name, node| { 95 Objective::Apply(ApplyObjective { 96 goal, 97 no_keys: apply_args.no_keys, 98 reboot: apply_args.reboot, 99 substitute_on_destination: apply_args.substitute_on_destination, 100 should_apply_locally: should_apply_locally( 101 node.allow_local_deployment, 102 &name.0, 103 ), 104 handle_unreachable: apply_args.handle_unreachable.clone().into(), 105 }) 106 }, 107 modifiers, 108 ) 109 .await?; 110 } 111 cli::Commands::Build(build_args) => { 112 let mut hive = Hive::new_from_path(&location, cache.clone(), modifiers).await?; 113 114 apply::apply( 115 &mut hive, 116 should_shutdown, 117 location, 118 build_args.common, 119 build_args.partition.unwrap_or_default(), 120 |_name, _node| Objective::BuildLocally, 121 modifiers, 122 ) 123 .await?; 124 } 125 cli::Commands::Inspect { json, selection } => println!("{}", { 126 match selection { 127 cli::Inspection::Full => { 128 let hive = Hive::new_from_path(&location, cache.clone(), modifiers).await?; 129 if json { 130 serde_json::to_string(&hive).into_diagnostic()? 131 } else { 132 warn!("use --json to output something scripting suitable"); 133 format!("{hive}") 134 } 135 } 136 cli::Inspection::Names => { 137 serde_json::to_string(&get_hive_node_names(&location, modifiers).await?) 138 .into_diagnostic()? 139 } 140 } 141 }), 142 } 143 144 if let Some(cache) = cache { 145 cache.gc().await.into_diagnostic()?; 146 } 147 148 signals_handle.close(); 149 signals_task.await.into_diagnostic()?; 150 151 Ok(()) 152} 153 154fn check_nix_available() -> bool { 155 match Command::new("nix") 156 .stdout(std::process::Stdio::null()) 157 .stderr(std::process::Stdio::null()) 158 .spawn() 159 { 160 Ok(_) => true, 161 Err(e) => { 162 if let std::io::ErrorKind::NotFound = e.kind() { 163 false 164 } else { 165 error!( 166 "Something weird happened checking for nix availability, {}", 167 e 168 ); 169 false 170 } 171 } 172 } 173}