77#include " cachyos/validation.hpp"
88
99// import gucc
10+ #include " gucc/string_utils.hpp"
1011#include " gucc/subprocess.hpp"
1112
13+ #include < array> // for array
14+ #include < cstdint> // for uint8_t, uint32_t
1215#include < filesystem> // for copy_file, exists
16+ #include < optional> // for optional
1317#include < string> // for string
1418#include < string_view> // for string_view
1519#include < utility> // for move
@@ -26,23 +30,80 @@ namespace {
2630// NOLINTNEXTLINE
2731using namespace cachyos ::installer;
2832
29- constexpr int kTotalSteps = 12 ;
33+ enum class Step : std::uint8_t {
34+ Umount,
35+ Partition,
36+ Fstab,
37+ SystemSettings,
38+ Users,
39+ Base,
40+ Desktop,
41+ Bootloader,
42+ DetectCrypto,
43+ EnableServices,
44+ FinalValidation,
45+ Cleanup,
46+ Count,
47+ };
48+
49+ constexpr std::int32_t kTotalSteps = static_cast <std::int32_t >(Step::Count);
50+
51+ constexpr std::array<std::string_view, kTotalSteps > kStepMessages = {
52+ " Unmounting existing partitions..." sv,
53+ " Partitioning and mounting..." sv,
54+ " Generating fstab..." sv,
55+ " Configuring system settings..." sv,
56+ " Creating user accounts..." sv,
57+ " Installing base system (this may take a while)..." sv,
58+ " Installing desktop environment..." sv,
59+ " Installing bootloader..." sv,
60+ " Detecting encryption state..." sv,
61+ " Enabling system services..." sv,
62+ " Running final validation..." sv,
63+ " Cleaning up..." sv,
64+ };
65+
66+ constexpr auto step_index (Step s) noexcept {
67+ return static_cast <std::int32_t >(s);
68+ }
69+
70+ constexpr auto step_message (Step s) noexcept -> std::string_view {
71+ return kStepMessages [static_cast <std::size_t >(s)];
72+ }
3073
3174auto emit_progress (const ExecutionCallbacks& cb,
3275 ProgressEventType type,
33- int step,
76+ std:: int32_t step,
3477 std::string_view message) noexcept -> void {
3578 if (!cb.on_progress ) {
3679 return ;
3780 }
38- const double fraction = static_cast <double >(step) / static_cast <double >(kTotalSteps );
81+ const auto fraction = static_cast <double >(step) / static_cast <double >(kTotalSteps );
3982 cb.on_progress (ProgressEvent{
4083 .type = type,
4184 .message = std::string{message},
4285 .fraction = fraction,
4386 });
4487}
4588
89+ void emit_step_running (const ExecutionCallbacks& cb, Step s) noexcept {
90+ emit_progress (cb, ProgressEventType::Running, step_index (s), step_message (s));
91+ }
92+
93+ // / Emit a Failed event and return a ValidationResult with the formatted error.
94+ auto fail_step (const ExecutionCallbacks& cb,
95+ Step s,
96+ std::string_view label,
97+ std::string_view error,
98+ std::vector<std::string> prior_warnings) noexcept -> ValidationResult {
99+ emit_progress (cb, ProgressEventType::Failed, step_index (s), label);
100+ return ValidationResult{
101+ .success = false ,
102+ .errors = {fmt::format (" {}: {}" , label, error)},
103+ .warnings = std::move (prior_warnings),
104+ };
105+ }
106+
46107auto mount_selections_from_auto (const std::vector<gucc::fs::Partition>& partitions) noexcept -> MountSelections {
47108 MountSelections mounts{};
48109 for (const auto & part : partitions) {
@@ -68,18 +129,67 @@ auto mount_selections_from_auto(const std::vector<gucc::fs::Partition>& partitio
68129 return mounts;
69130}
70131
71- auto make_failure (std::string message, std::vector<std::string> prior_warnings) noexcept -> ValidationResult {
72- return ValidationResult{
73- .success = false ,
74- .errors = {std::move (message)},
75- .warnings = std::move (prior_warnings),
132+ // / Builds a log-line callback for a step that forwards lines to the user's
133+ // / sink and, when a pacman progress line is recognised, emits an intra-step
134+ // / Running event scaled into this step's slice of the overall progress bar.
135+ auto step_log_callback (const ExecutionCallbacks& cb, Step s) noexcept
136+ -> gucc::utils::SubProcess::LogLineCallback {
137+ if (!cb.on_log_line && !cb.on_progress ) {
138+ return {};
139+ }
140+ const auto idx = step_index (s);
141+ auto msg = std::string{step_message (s)};
142+ return [&cb, idx, msg = std::move (msg)](std::string_view line) {
143+ if (cb.on_log_line ) {
144+ cb.on_log_line (line);
145+ }
146+ if (!cb.on_progress ) {
147+ return ;
148+ }
149+ const auto frac = parse_pacman_progress (line);
150+ if (!frac) {
151+ return ;
152+ }
153+ constexpr auto total = static_cast <double >(kTotalSteps );
154+ const double base = static_cast <double >(idx) / total;
155+ const double step_width = 1.0 / total;
156+ cb.on_progress (ProgressEvent{
157+ .type = ProgressEventType::Running,
158+ .message = msg,
159+ .fraction = base + (*frac * step_width),
160+ });
76161 };
77162}
78163
79164} // namespace
80165
81166namespace cachyos ::installer {
82167
168+ auto parse_pacman_progress (std::string_view line) noexcept -> std::optional<double> {
169+ const auto open = line.find (' (' );
170+ if (open == std::string_view::npos) {
171+ return std::nullopt ;
172+ }
173+ const auto slash = line.find (' /' , open + 1 );
174+ if (slash == std::string_view::npos) {
175+ return std::nullopt ;
176+ }
177+ const auto close = line.find (' )' , slash + 1 );
178+ if (close == std::string_view::npos) {
179+ return std::nullopt ;
180+ }
181+
182+ const auto num_str = gucc::utils::trim (line.substr (open + 1 , slash - open - 1 ));
183+ const auto denom_str = gucc::utils::trim (line.substr (slash + 1 , close - slash - 1 ));
184+
185+ const auto num = gucc::utils::parse_uint<std::uint32_t >(num_str);
186+ const auto denom = gucc::utils::parse_uint<std::uint32_t >(denom_str);
187+ if (!num || !denom || *denom == 0 || *num > *denom) {
188+ return std::nullopt ;
189+ }
190+ return static_cast <double >(*num) / static_cast <double >(*denom);
191+ }
192+
83193auto run (InstallContext& ctx,
84194 const SystemSettings& sys,
85195 const UserSettings& user,
@@ -90,100 +200,95 @@ auto run(InstallContext& ctx,
90200 std::vector<std::string> warnings;
91201
92202 spdlog::info (" Install orchestrator starting..." );
93- emit_progress (callbacks, Started, 0 , " Starting installation..." );
203+ emit_progress (callbacks, Started, 0 , " Starting installation..." sv );
94204
95205 // Step 1: Unmount any existing partitions on the target.
96- emit_progress (callbacks, Running, 0 , " Unmounting existing partitions... " );
206+ emit_step_running (callbacks, Step::Umount );
97207 if (auto res = umount_partitions (mountpoint, ctx.zfs_zpool_names , ctx.swap_device ); !res) {
98208 spdlog::warn (" umount_partitions (pre-install): {}" , res.error ());
99209 warnings.emplace_back (fmt::format (" Pre-install unmount: {}" , res.error ()));
100210 }
101211
102212 // Step 2: Auto-partition and mount.
103- emit_progress (callbacks, Running, 1 , " Partitioning and mounting... " );
213+ emit_step_running (callbacks, Step::Partition );
104214 {
105215 const auto & bios_mode = ctx.system_mode == InstallContext::SystemMode::UEFI ? " UEFI" sv : " BIOS" sv;
106- auto partitions = auto_partition (ctx.device , bios_mode, ctx.bootloader , callbacks);
216+ auto partitions = auto_partition (ctx.device , bios_mode, ctx.bootloader , callbacks);
107217 if (!partitions) {
108- emit_progress (callbacks, Failed, 1 , " Auto-partition failed" );
109- return make_failure (fmt::format (" Auto-partition failed: {}" , partitions.error ()), std::move (warnings));
218+ return fail_step (callbacks, Step::Partition, " Auto-partition failed" sv, partitions.error (), std::move (warnings));
110219 }
111220
112221 const auto mounts = mount_selections_from_auto (*partitions);
113222 auto mount_res = apply_mount_selections (mounts, mountpoint);
114223 if (!mount_res) {
115- emit_progress (callbacks, Failed, 1 , " Mount failed" );
116- return make_failure (fmt::format (" Mount failed: {}" , mount_res.error ()), std::move (warnings));
224+ return fail_step (callbacks, Step::Partition, " Mount failed" sv, mount_res.error (), std::move (warnings));
117225 }
118226
119227 ctx.partition_schema = std::move (mount_res->partitions );
120228 ctx.swap_device = std::move (mount_res->swap_device );
121229 }
122230
123231 // Step 3: Generate fstab.
124- emit_progress (callbacks, Running, 2 , " Generating fstab... " );
232+ emit_step_running (callbacks, Step::Fstab );
125233 if (auto res = generate_fstab (mountpoint); !res) {
126- emit_progress (callbacks, Failed, 2 , " fstab generation failed" );
127- return make_failure (fmt::format (" fstab generation failed: {}" , res.error ()), std::move (warnings));
234+ return fail_step (callbacks, Step::Fstab, " fstab generation failed" sv, res.error (), std::move (warnings));
128235 }
129236
130237 // Step 4: Apply system settings (hostname, locale, keymap, timezone, hw_clock).
131- emit_progress (callbacks, Running, 3 , " Configuring system settings... " );
238+ emit_step_running (callbacks, Step::SystemSettings );
132239 if (auto res = apply_system_settings (sys, mountpoint); !res) {
133- emit_progress (callbacks, Failed, 3 , " System settings failed" );
134- return make_failure (fmt::format (" System settings failed: {}" , res.error ()), std::move (warnings));
240+ return fail_step (callbacks, Step::SystemSettings, " System settings failed" sv, res.error (), std::move (warnings));
135241 }
136242
137243 // Step 5: Root password + user account.
138- emit_progress (callbacks, Running, 4 , " Creating user accounts... " );
244+ emit_step_running (callbacks, Step::Users );
139245 if (auto res = set_root_password (root_password, mountpoint); !res) {
140246 spdlog::error (" set_root_password: {}" , res.error ());
141247 warnings.emplace_back (fmt::format (" set_root_password: {}" , res.error ()));
142248 }
143249 {
144250 gucc::utils::SubProcess child;
145- child.set_log_line_callback (callbacks. on_log_line );
251+ child.set_log_line_callback (step_log_callback ( callbacks, Step::Users) );
146252 if (auto res = create_user (user, mountpoint, ctx.hostcache , child); !res) {
147253 spdlog::error (" create_user: {}" , res.error ());
148254 warnings.emplace_back (fmt::format (" create_user: {}" , res.error ()));
149255 }
150256 }
151257
152258 // Step 6: Base system.
153- emit_progress (callbacks, Running, 5 , " Installing base system (this may take a while)... " );
259+ emit_step_running (callbacks, Step::Base );
154260 {
155261 gucc::utils::SubProcess child;
156- child.set_log_line_callback (callbacks. on_log_line );
262+ child.set_log_line_callback (step_log_callback ( callbacks, Step::Base) );
157263 if (auto res = install_base (ctx, child); !res) {
158- emit_progress (callbacks, Failed, 5 , " Base install failed" );
159- return make_failure (fmt::format (" Base install failed: {}" , res.error ()), std::move (warnings));
264+ return fail_step (callbacks, Step::Base, " Base install failed" sv, res.error (), std::move (warnings));
160265 }
161266 }
162267
163268 // Step 7: Desktop (skipped in server mode).
164- emit_progress (callbacks, Running, 6 , " Installing desktop environment... " );
269+ emit_step_running (callbacks, Step::Desktop );
165270 if (!ctx.server_mode && !ctx.desktop .empty ()) {
166271 gucc::utils::SubProcess child;
167- child.set_log_line_callback (callbacks. on_log_line );
272+ child.set_log_line_callback (step_log_callback ( callbacks, Step::Desktop) );
168273 if (auto res = install_desktop (ctx.desktop , ctx, child); !res) {
169274 spdlog::error (" install_desktop: {}" , res.error ());
170275 warnings.emplace_back (fmt::format (" install_desktop: {}" , res.error ()));
171276 }
172277 }
173278
174279 // Step 8: Bootloader.
175- emit_progress (callbacks, Running, 7 , " Installing bootloader... " );
280+ emit_step_running (callbacks, Step::Bootloader );
176281 {
177282 gucc::utils::SubProcess child;
178- child.set_log_line_callback (callbacks. on_log_line );
283+ child.set_log_line_callback (step_log_callback ( callbacks, Step::Bootloader) );
179284 if (auto res = install_bootloader (ctx, child); !res) {
180285 spdlog::error (" install_bootloader: {}" , res.error ());
181286 warnings.emplace_back (fmt::format (" install_bootloader: {}" , res.error ()));
182287 }
183288 }
184289
185290 // Step 9: Detect post-install crypto state and stash it on the context for kernel-params use.
186- emit_progress (callbacks, Running, 8 , " Detecting encryption state... " );
291+ emit_step_running (callbacks, Step::DetectCrypto );
187292 if (auto res = detect_crypto_root (mountpoint); res) {
188293 ctx.crypto .is_luks = res->is_luks ;
189294 ctx.crypto .is_lvm = res->is_lvm ;
@@ -195,14 +300,14 @@ auto run(InstallContext& ctx,
195300 }
196301
197302 // Step 10: Enable systemd services.
198- emit_progress (callbacks, Running, 9 , " Enabling system services... " );
303+ emit_step_running (callbacks, Step::EnableServices );
199304 if (auto res = enable_services (ctx); !res) {
200305 spdlog::error (" enable_services: {}" , res.error ());
201306 warnings.emplace_back (fmt::format (" enable_services: {}" , res.error ()));
202307 }
203308
204309 // Step 11: Final validation.
205- emit_progress (callbacks, Running, 10 , " Running final validation... " );
310+ emit_step_running (callbacks, Step::FinalValidation );
206311 {
207312 auto check = final_check (ctx);
208313 for (auto & err : check.errors ) {
@@ -216,7 +321,7 @@ auto run(InstallContext& ctx,
216321 }
217322
218323 // Step 12: Copy install log into target and unmount.
219- emit_progress (callbacks, Running, 11 , " Cleaning up... " );
324+ emit_step_running (callbacks, Step::Cleanup );
220325 {
221326 constexpr auto kLogSource = " /tmp/cachyos-install.log" sv;
222327 if (fs::exists (kLogSource )) {
@@ -234,7 +339,7 @@ auto run(InstallContext& ctx,
234339 warnings.emplace_back (fmt::format (" Final umount: {}" , res.error ()));
235340 }
236341
237- emit_progress (callbacks, Completed, kTotalSteps , " Installation complete!" );
342+ emit_progress (callbacks, Completed, kTotalSteps , " Installation complete!" sv );
238343 spdlog::info (" Install orchestrator finished." );
239344
240345 return ValidationResult{
0 commit comments