ALPHA: wire is a tool to deploy nixos systems
wire.althaea.zone/
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}