├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── base_packages.txt ├── build.sh ├── clean.sh ├── grub.cfg ├── lang ├── en.json └── fr.json ├── rustfmt.toml └── src ├── install ├── grub.cfg └── mod.rs ├── lang.rs ├── main.rs ├── prompt ├── mod.rs ├── motd └── term.rs └── util.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | iso_build/ 4 | maestro.iso 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "anyhow" 22 | version = "1.0.77" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "c9d19de80eff169429ac1e9f48fffb163916b448a44e8e046186232046d9e1f9" 25 | 26 | [[package]] 27 | name = "argon2" 28 | version = "0.5.2" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9" 31 | dependencies = [ 32 | "base64ct", 33 | "blake2", 34 | "cpufeatures", 35 | "password-hash", 36 | ] 37 | 38 | [[package]] 39 | name = "autocfg" 40 | version = "1.1.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 43 | 44 | [[package]] 45 | name = "backtrace" 46 | version = "0.3.69" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 49 | dependencies = [ 50 | "addr2line", 51 | "cc", 52 | "cfg-if", 53 | "libc", 54 | "miniz_oxide", 55 | "object", 56 | "rustc-demangle", 57 | ] 58 | 59 | [[package]] 60 | name = "base64ct" 61 | version = "1.6.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 64 | 65 | [[package]] 66 | name = "bitflags" 67 | version = "1.3.2" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 70 | 71 | [[package]] 72 | name = "bitflags" 73 | version = "2.4.1" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" 76 | 77 | [[package]] 78 | name = "blake2" 79 | version = "0.10.6" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" 82 | dependencies = [ 83 | "digest", 84 | ] 85 | 86 | [[package]] 87 | name = "block-buffer" 88 | version = "0.10.4" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 91 | dependencies = [ 92 | "generic-array", 93 | ] 94 | 95 | [[package]] 96 | name = "bytes" 97 | version = "1.5.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 100 | 101 | [[package]] 102 | name = "bzip2" 103 | version = "0.4.4" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" 106 | dependencies = [ 107 | "bzip2-sys", 108 | "libc", 109 | ] 110 | 111 | [[package]] 112 | name = "bzip2-sys" 113 | version = "0.1.11+1.0.8" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" 116 | dependencies = [ 117 | "cc", 118 | "libc", 119 | "pkg-config", 120 | ] 121 | 122 | [[package]] 123 | name = "cc" 124 | version = "1.0.83" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 127 | dependencies = [ 128 | "libc", 129 | ] 130 | 131 | [[package]] 132 | name = "cfg-if" 133 | version = "1.0.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 136 | 137 | [[package]] 138 | name = "common" 139 | version = "0.1.0" 140 | source = "git+https://github.com/llenotre/blimp#9e05261924a60a21a9e52d6233ec08763808ad24" 141 | dependencies = [ 142 | "anyhow", 143 | "bytes", 144 | "bzip2", 145 | "flate2", 146 | "futures", 147 | "futures-util", 148 | "serde", 149 | "serde_json", 150 | "tar", 151 | "tokio", 152 | "xz2", 153 | ] 154 | 155 | [[package]] 156 | name = "cpufeatures" 157 | version = "0.2.11" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" 160 | dependencies = [ 161 | "libc", 162 | ] 163 | 164 | [[package]] 165 | name = "crc32fast" 166 | version = "1.3.2" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 169 | dependencies = [ 170 | "cfg-if", 171 | ] 172 | 173 | [[package]] 174 | name = "crypto-common" 175 | version = "0.1.6" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 178 | dependencies = [ 179 | "generic-array", 180 | "typenum", 181 | ] 182 | 183 | [[package]] 184 | name = "digest" 185 | version = "0.10.7" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 188 | dependencies = [ 189 | "block-buffer", 190 | "crypto-common", 191 | "subtle", 192 | ] 193 | 194 | [[package]] 195 | name = "errno" 196 | version = "0.3.8" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" 199 | dependencies = [ 200 | "libc", 201 | "windows-sys", 202 | ] 203 | 204 | [[package]] 205 | name = "fdisk" 206 | version = "0.1.0" 207 | source = "git+https://github.com/llenotre/maestro-utils#53e7e8928457f68e46773eaf94ef6c09b2859cc9" 208 | dependencies = [ 209 | "libc", 210 | "utils", 211 | ] 212 | 213 | [[package]] 214 | name = "filetime" 215 | version = "0.2.23" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" 218 | dependencies = [ 219 | "cfg-if", 220 | "libc", 221 | "redox_syscall", 222 | "windows-sys", 223 | ] 224 | 225 | [[package]] 226 | name = "flate2" 227 | version = "1.0.28" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" 230 | dependencies = [ 231 | "crc32fast", 232 | "miniz_oxide", 233 | ] 234 | 235 | [[package]] 236 | name = "futures" 237 | version = "0.3.30" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 240 | dependencies = [ 241 | "futures-channel", 242 | "futures-core", 243 | "futures-executor", 244 | "futures-io", 245 | "futures-sink", 246 | "futures-task", 247 | "futures-util", 248 | ] 249 | 250 | [[package]] 251 | name = "futures-channel" 252 | version = "0.3.30" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 255 | dependencies = [ 256 | "futures-core", 257 | "futures-sink", 258 | ] 259 | 260 | [[package]] 261 | name = "futures-core" 262 | version = "0.3.30" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 265 | 266 | [[package]] 267 | name = "futures-executor" 268 | version = "0.3.30" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 271 | dependencies = [ 272 | "futures-core", 273 | "futures-task", 274 | "futures-util", 275 | ] 276 | 277 | [[package]] 278 | name = "futures-io" 279 | version = "0.3.30" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 282 | 283 | [[package]] 284 | name = "futures-macro" 285 | version = "0.3.30" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 288 | dependencies = [ 289 | "proc-macro2", 290 | "quote", 291 | "syn", 292 | ] 293 | 294 | [[package]] 295 | name = "futures-sink" 296 | version = "0.3.30" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 299 | 300 | [[package]] 301 | name = "futures-task" 302 | version = "0.3.30" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 305 | 306 | [[package]] 307 | name = "futures-util" 308 | version = "0.3.30" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 311 | dependencies = [ 312 | "futures-channel", 313 | "futures-core", 314 | "futures-io", 315 | "futures-macro", 316 | "futures-sink", 317 | "futures-task", 318 | "memchr", 319 | "pin-project-lite", 320 | "pin-utils", 321 | "slab", 322 | ] 323 | 324 | [[package]] 325 | name = "generic-array" 326 | version = "0.14.7" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 329 | dependencies = [ 330 | "typenum", 331 | "version_check", 332 | ] 333 | 334 | [[package]] 335 | name = "getrandom" 336 | version = "0.2.11" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" 339 | dependencies = [ 340 | "cfg-if", 341 | "libc", 342 | "wasi", 343 | ] 344 | 345 | [[package]] 346 | name = "gimli" 347 | version = "0.28.1" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 350 | 351 | [[package]] 352 | name = "hermit-abi" 353 | version = "0.3.3" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" 356 | 357 | [[package]] 358 | name = "itoa" 359 | version = "1.0.10" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 362 | 363 | [[package]] 364 | name = "libc" 365 | version = "0.2.151" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" 368 | 369 | [[package]] 370 | name = "linux-raw-sys" 371 | version = "0.4.12" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" 374 | 375 | [[package]] 376 | name = "lzma-sys" 377 | version = "0.1.20" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" 380 | dependencies = [ 381 | "cc", 382 | "libc", 383 | "pkg-config", 384 | ] 385 | 386 | [[package]] 387 | name = "maestro_install" 388 | version = "0.1.0" 389 | dependencies = [ 390 | "common", 391 | "fdisk", 392 | "libc", 393 | "serde", 394 | "serde_json", 395 | "utils", 396 | ] 397 | 398 | [[package]] 399 | name = "memchr" 400 | version = "2.7.1" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 403 | 404 | [[package]] 405 | name = "miniz_oxide" 406 | version = "0.7.1" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 409 | dependencies = [ 410 | "adler", 411 | ] 412 | 413 | [[package]] 414 | name = "num_cpus" 415 | version = "1.16.0" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 418 | dependencies = [ 419 | "hermit-abi", 420 | "libc", 421 | ] 422 | 423 | [[package]] 424 | name = "object" 425 | version = "0.32.2" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 428 | dependencies = [ 429 | "memchr", 430 | ] 431 | 432 | [[package]] 433 | name = "password-hash" 434 | version = "0.5.0" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" 437 | dependencies = [ 438 | "base64ct", 439 | "rand_core", 440 | "subtle", 441 | ] 442 | 443 | [[package]] 444 | name = "pin-project-lite" 445 | version = "0.2.13" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 448 | 449 | [[package]] 450 | name = "pin-utils" 451 | version = "0.1.0" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 454 | 455 | [[package]] 456 | name = "pkg-config" 457 | version = "0.3.28" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" 460 | 461 | [[package]] 462 | name = "proc-macro2" 463 | version = "1.0.71" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" 466 | dependencies = [ 467 | "unicode-ident", 468 | ] 469 | 470 | [[package]] 471 | name = "quote" 472 | version = "1.0.33" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 475 | dependencies = [ 476 | "proc-macro2", 477 | ] 478 | 479 | [[package]] 480 | name = "rand_core" 481 | version = "0.6.4" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 484 | dependencies = [ 485 | "getrandom", 486 | ] 487 | 488 | [[package]] 489 | name = "redox_syscall" 490 | version = "0.4.1" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 493 | dependencies = [ 494 | "bitflags 1.3.2", 495 | ] 496 | 497 | [[package]] 498 | name = "rustc-demangle" 499 | version = "0.1.23" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 502 | 503 | [[package]] 504 | name = "rustix" 505 | version = "0.38.28" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" 508 | dependencies = [ 509 | "bitflags 2.4.1", 510 | "errno", 511 | "libc", 512 | "linux-raw-sys", 513 | "windows-sys", 514 | ] 515 | 516 | [[package]] 517 | name = "ryu" 518 | version = "1.0.16" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" 521 | 522 | [[package]] 523 | name = "serde" 524 | version = "1.0.193" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" 527 | dependencies = [ 528 | "serde_derive", 529 | ] 530 | 531 | [[package]] 532 | name = "serde_derive" 533 | version = "1.0.193" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" 536 | dependencies = [ 537 | "proc-macro2", 538 | "quote", 539 | "syn", 540 | ] 541 | 542 | [[package]] 543 | name = "serde_json" 544 | version = "1.0.108" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" 547 | dependencies = [ 548 | "itoa", 549 | "ryu", 550 | "serde", 551 | ] 552 | 553 | [[package]] 554 | name = "slab" 555 | version = "0.4.9" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 558 | dependencies = [ 559 | "autocfg", 560 | ] 561 | 562 | [[package]] 563 | name = "subtle" 564 | version = "2.5.0" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 567 | 568 | [[package]] 569 | name = "syn" 570 | version = "2.0.43" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" 573 | dependencies = [ 574 | "proc-macro2", 575 | "quote", 576 | "unicode-ident", 577 | ] 578 | 579 | [[package]] 580 | name = "tar" 581 | version = "0.4.40" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" 584 | dependencies = [ 585 | "filetime", 586 | "libc", 587 | "xattr", 588 | ] 589 | 590 | [[package]] 591 | name = "tokio" 592 | version = "1.35.1" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" 595 | dependencies = [ 596 | "backtrace", 597 | "num_cpus", 598 | "pin-project-lite", 599 | ] 600 | 601 | [[package]] 602 | name = "typenum" 603 | version = "1.17.0" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 606 | 607 | [[package]] 608 | name = "unicode-ident" 609 | version = "1.0.12" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 612 | 613 | [[package]] 614 | name = "utils" 615 | version = "0.1.0" 616 | source = "git+https://github.com/llenotre/maestro-utils#53e7e8928457f68e46773eaf94ef6c09b2859cc9" 617 | dependencies = [ 618 | "argon2", 619 | "libc", 620 | "rand_core", 621 | ] 622 | 623 | [[package]] 624 | name = "version_check" 625 | version = "0.9.4" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 628 | 629 | [[package]] 630 | name = "wasi" 631 | version = "0.11.0+wasi-snapshot-preview1" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 634 | 635 | [[package]] 636 | name = "windows-sys" 637 | version = "0.52.0" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 640 | dependencies = [ 641 | "windows-targets", 642 | ] 643 | 644 | [[package]] 645 | name = "windows-targets" 646 | version = "0.52.0" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" 649 | dependencies = [ 650 | "windows_aarch64_gnullvm", 651 | "windows_aarch64_msvc", 652 | "windows_i686_gnu", 653 | "windows_i686_msvc", 654 | "windows_x86_64_gnu", 655 | "windows_x86_64_gnullvm", 656 | "windows_x86_64_msvc", 657 | ] 658 | 659 | [[package]] 660 | name = "windows_aarch64_gnullvm" 661 | version = "0.52.0" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" 664 | 665 | [[package]] 666 | name = "windows_aarch64_msvc" 667 | version = "0.52.0" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" 670 | 671 | [[package]] 672 | name = "windows_i686_gnu" 673 | version = "0.52.0" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" 676 | 677 | [[package]] 678 | name = "windows_i686_msvc" 679 | version = "0.52.0" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" 682 | 683 | [[package]] 684 | name = "windows_x86_64_gnu" 685 | version = "0.52.0" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" 688 | 689 | [[package]] 690 | name = "windows_x86_64_gnullvm" 691 | version = "0.52.0" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" 694 | 695 | [[package]] 696 | name = "windows_x86_64_msvc" 697 | version = "0.52.0" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" 700 | 701 | [[package]] 702 | name = "xattr" 703 | version = "1.2.0" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "914566e6413e7fa959cc394fb30e563ba80f3541fbd40816d4c05a0fc3f2a0f1" 706 | dependencies = [ 707 | "libc", 708 | "linux-raw-sys", 709 | "rustix", 710 | ] 711 | 712 | [[package]] 713 | name = "xz2" 714 | version = "0.1.7" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" 717 | dependencies = [ 718 | "lzma-sys", 719 | ] 720 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "maestro_install" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | common = { git = "https://github.com/llenotre/blimp" } 8 | fdisk = { git = "https://github.com/llenotre/maestro-utils" } 9 | libc = "*" 10 | serde = { version = "1.0.147", features = ["derive"] } 11 | serde_json = "1.0.87" 12 | utils = { git = "https://github.com/llenotre/maestro-utils" } 13 | 14 | [profile.release] 15 | strip = true 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Luc Lenôtre 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > **Note**: this software is **NOT** currently tested against the latest Maestro kernel. See the last section of [this blog article](https://blog.lenot.re/a/page-cache). 2 | 3 |

4 | 5 | 6 | logo 7 | 8 |

9 | 10 | [![MIT license](https://img.shields.io/badge/license-MIT-blue.svg?style=for-the-badge&logo=book)](./LICENSE) 11 | ![Version](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fmaestro-os%2Fmaestro-install%2Fmaster%2FCargo.toml&query=%24.package.version&style=for-the-badge&label=version) 12 | 13 | 14 | 15 | # About 16 | 17 | The installer for the Maestro operating system is shipped under the ISO format, allowing to use it on a USB stick for example. 18 | 19 | 20 | 21 | # Build the installer 22 | 23 | > Required only if cross compiling (building for a different target than your current system): 24 | > 25 | > First, specify the `TARGET` environment variable with the triplet of the target system. Example: 26 | > 27 | > ```sh 28 | > export TARGET="i686-unknown-linux-musl" 29 | > ``` 30 | > 31 | > This following targets are available: 32 | > - `x86_64-unknown-linux-musl` 33 | > - `i686-unknown-linux-musl` 34 | > 35 | > Then, you need to [build a toolchain](https://github.com/maestro-os/blimp/tree/master/cross). 36 | 37 | 38 | 39 | ## Build packages 40 | 41 | 42 | The installer requires a minimum set of packages. The list of those packages is in the `base_packages.txt` file. 43 | 44 | To build those packages, you first need to install [Maestro's package manager](https://github.com/maestro-os/blimp) on your local computer. 45 | 46 | Package descriptors can be found [here](https://github.com/maestro-os/blimp-packages). Clone the repository: 47 | 48 | ```sh 49 | git clone https://github.com/maestro-os/blimp-packages 50 | ``` 51 | 52 | Create a local repository for built packages: 53 | 54 | ```sh 55 | mkdir local_repo/ 56 | export LOCAL_REPO="local_repo/" # Required later when building the ISO image 57 | ``` 58 | 59 | 60 | ### Temporary fixes 61 | 62 | Since the build system is not yet working entirely, dependencies that are required for building packages are not installed automatically. Thus, the following fixes are currently required: 63 | 64 | > Manually build and install the package `maestro-build` to be able to build kernel modules: 65 | > 66 | > ```sh 67 | > blimp-builder blimp-packages/maestro-build local_repo/ # Build 68 | > sudo LOCAL_REPO="$LOCAL_REPO" blimp install maestro-build # Install from local repository 69 | > ``` 70 | 71 | > Patch `blimp` to disable network support (because it requires `libssl` as a dependency, which is not yet supported): 72 | > 73 | > ```sh 74 | > sed -i 's/--features network//' blimp-packages/blimp/build-hook 75 | > ``` 76 | 77 | 78 | 79 | ### Build required packages 80 | 81 | > Now, if you are cross compiling, [setup the package manager for cross compilation](https://github.com/maestro-os/blimp#cross-compilation). 82 | 83 | Compile packages required by the installer (excluding the kernel): 84 | 85 | ```sh 86 | for pkg in $(cat base_packages.txt); do 87 | blimp-builder blimp-packages/$pkg $LOCAL_REPO 88 | done 89 | ``` 90 | 91 | 92 | 93 | ## Build the ISO 94 | 95 | The following command builds the ISO: 96 | 97 | ```sh 98 | ./build.sh 99 | ``` 100 | 101 | The resulting ISO is then named `maestro.iso` 102 | 103 | 104 | 105 | # Usage 106 | 107 | ## Flash the ISO 108 | 109 | This step is required only if you want to install the OS on a physical machine. 110 | 111 | If installing on a virtual machine, just insert the ISO as a CD-ROM. The VM is required to have a disk on which the system will be installed. 112 | 113 | First, make sure no filesystem belonging to the device is mounted. Flashing the ISO while a filesystem is mounted might corrupt the image. 114 | 115 | > **Warning**: the following action is destructive. Make sure you have no important data on the device you are flashing. 116 | > 117 | > Make also sure that you select the right device too. Flashing the wrong disk might erase your system. 118 | 119 | The following command to flashes the ISO (where `XXX` is the device on which you want to flash): 120 | 121 | ```sh 122 | dd if= of=/dev/XXX status=progress 123 | ``` 124 | 125 | Then, eject the device. It is now ready to be used as a bootable device to install the OS! 126 | 127 | 128 | 129 | ## Installation 130 | 131 | First, plug the installation device on the computer. Then, you can just follow the instructions to install the system. 132 | 133 | > **Note**: Do not install the system on a computer with important data. This OS and its installer are still work-in-progress softwares. 134 | -------------------------------------------------------------------------------- /base_packages.txt: -------------------------------------------------------------------------------- 1 | bash 2 | blimp 3 | coreutils 4 | grub 5 | maestro 6 | maestro-ps2 7 | maestro-utils 8 | solfege 9 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # If not specified, set build target to default 6 | if [ -z $TARGET ]; then 7 | TARGET=i686-unknown-linux-musl 8 | fi 9 | 10 | GRUB_ROOT=iso_build/iso 11 | INITRAMFS_ROOT=iso_build/mnt 12 | 13 | # Setup grub config 14 | mkdir -pv $GRUB_ROOT/boot/grub 15 | cp -v grub.cfg $GRUB_ROOT/boot/grub/ 16 | 17 | # Create directories hierarchy 18 | mkdir -pv $INITRAMFS_ROOT/{dev,etc,lang,proc,sbin,tmp,usr/lib/blimp} 19 | 20 | # Compile and install installer 21 | cargo build --release --target $TARGET -Zbuild-std 22 | cp -v target/$TARGET/release/maestro_install $INITRAMFS_ROOT/sbin/install 23 | cp -v lang/* $INITRAMFS_ROOT/lang/ 24 | 25 | # Copy packages required to be installed on the system 26 | if [ ! -z "$LOCAL_REPO" ]; then 27 | mkdir -pv "$INITRAMFS_ROOT/local_repo" 28 | for name in $(cat base_packages.txt); do 29 | cp -rv "$LOCAL_REPO/$name" "$INITRAMFS_ROOT/local_repo" 30 | done 31 | fi 32 | 33 | # Install packages required by the installer 34 | yes | SYSROOT="$INITRAMFS_ROOT" blimp install grub maestro maestro-ps2 maestro-utils solfege 35 | 36 | # Move kernel to GRUB 37 | mv -v $INITRAMFS_ROOT/boot/maestro $GRUB_ROOT/boot/ 38 | 39 | # Solfege setup 40 | echo 'install' >$INITRAMFS_ROOT/etc/hostname 41 | echo '/sbin/install' >$INITRAMFS_ROOT/etc/solfege/startup 42 | 43 | # Create ISO file 44 | cd $INITRAMFS_ROOT; find . | cpio -o >../../$GRUB_ROOT/boot/initramfs; cd ../.. 45 | grub-mkrescue -o maestro.iso iso_build/iso/ 46 | -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rfv iso_build/ 4 | rm -rfv maestro.iso 5 | cargo clean 6 | -------------------------------------------------------------------------------- /grub.cfg: -------------------------------------------------------------------------------- 1 | menuentry "Install Maestro" { 2 | multiboot2 /boot/maestro 3 | module2 /boot/initramfs 4 | } 5 | -------------------------------------------------------------------------------- /lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "en", 3 | "display_name": "English", 4 | 5 | "locale": "en_US.UTF-8" 6 | } 7 | -------------------------------------------------------------------------------- /lang/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fr", 3 | "display_name": "Français", 4 | 5 | "locale": "fr_FR.UTF-8" 6 | } 7 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | blank_lines_upper_bound = 1 2 | comment_width = 99 3 | condense_wildcard_suffixes = true 4 | hard_tabs = true 5 | hex_literal_case = "Lower" 6 | newline_style = "Unix" 7 | reorder_impl_items = true 8 | struct_lit_single_line = false 9 | use_field_init_shorthand = true 10 | wrap_comments = true 11 | -------------------------------------------------------------------------------- /src/install/grub.cfg: -------------------------------------------------------------------------------- 1 | menuentry "Maestro" { 2 | multiboot2 /maestro -root 8 3 3 | } 4 | -------------------------------------------------------------------------------- /src/install/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module handles the installation procedure. 2 | 3 | use crate::lang::Language; 4 | use crate::prompt::InstallPrompt; 5 | use common::repository::Repository; 6 | use common::Environment; 7 | use fdisk::disk; 8 | use fdisk::disk::Disk; 9 | use fdisk::guid::GUID; 10 | use fdisk::partition::PartitionTable; 11 | use fdisk::partition::PartitionTableType; 12 | use fdisk::partition::{Partition, PartitionType}; 13 | use serde::Deserialize; 14 | use serde::Serialize; 15 | use std::error::Error; 16 | use std::fmt; 17 | use std::fs; 18 | use std::fs::OpenOptions; 19 | use std::fs::Permissions; 20 | use std::io::ErrorKind; 21 | use std::io::Write; 22 | use std::os::unix::fs::chown; 23 | use std::os::unix::prelude::PermissionsExt; 24 | use std::path::Path; 25 | use std::path::PathBuf; 26 | use std::process::Command; 27 | use std::str::FromStr; 28 | use utils::user; 29 | use utils::user::Group; 30 | use utils::user::Shadow; 31 | use utils::user::User; 32 | use utils::util::get_timestamp; 33 | 34 | // TODO Use InstallProgress instead of printing directly 35 | 36 | /// Structure representing a partition to be created. 37 | #[derive(Clone, Deserialize, Serialize)] 38 | pub struct PartitionDesc { 39 | /// The start offset of the partition in sectors. 40 | pub start: u64, 41 | /// The size of the partition in sectors. 42 | pub size: u64, 43 | 44 | /// The partition type. 45 | pub part_type: String, 46 | 47 | /// Tells whether the partition is bootable. 48 | pub bootable: bool, 49 | 50 | /// The path at which the partition is to be mounted for installation. 51 | /// 52 | /// If None, the partition shouldn't be mounted. 53 | pub mount_path: Option, 54 | } 55 | 56 | impl fmt::Display for PartitionDesc { 57 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 58 | write!(fmt, "start: {}, size: {} sectors", self.start, self.size)?; 59 | if self.bootable { 60 | write!(fmt, ", bootable")?; 61 | } 62 | if let Some(mount_path) = &self.mount_path { 63 | write!(fmt, ", mount path: {} ", mount_path.display())?; 64 | } 65 | Ok(()) 66 | } 67 | } 68 | 69 | /// Structure storing installation informations. 70 | #[derive(Clone, Default, Deserialize, Serialize)] 71 | pub struct InstallInfo { 72 | /// The system's language. 73 | pub lang: Option, 74 | /// The system's country. 75 | pub country: String, 76 | /// The system's timezone. 77 | pub tz: String, 78 | 79 | /// The system's hostname. 80 | pub hostname: String, 81 | 82 | /// Admin username. 83 | pub admin_user: String, 84 | /// Hashed admin password. 85 | pub admin_pass: String, 86 | 87 | /// The path to the disk on which the system is to be installed. 88 | pub selected_disk: PathBuf, 89 | /// The partition scheme to be used. 90 | pub partitions: Vec, 91 | } 92 | 93 | impl InstallInfo { 94 | /// Creates partitions on the disk. 95 | fn partition_disks(&self) -> Result<(), Box> { 96 | println!( 97 | "Create partition table on `{}`...", 98 | self.selected_disk.display() 99 | ); 100 | 101 | let partitions = self 102 | .partitions 103 | .iter() 104 | .map(|desc| { 105 | // TODO handle error 106 | let part_type = PartitionType::from_str(desc.part_type.as_str()).unwrap(); 107 | let uuid = GUID::random(); 108 | Partition { 109 | start: desc.start, 110 | size: desc.size, 111 | 112 | part_type, 113 | 114 | uuid: Some(uuid), 115 | 116 | bootable: desc.bootable, 117 | } 118 | }) 119 | .collect(); 120 | let partition_table = PartitionTable { 121 | table_type: PartitionTableType::GPT, 122 | partitions, 123 | }; 124 | 125 | let mut disk = Disk::read(self.selected_disk.clone())?.unwrap(); 126 | disk.partition_table = partition_table; 127 | disk.write()?; 128 | disk::read_partitions(&self.selected_disk)?; 129 | 130 | Ok(()) 131 | } 132 | 133 | /// Creates a filesystem on each partition. 134 | fn create_filesystems(&self) -> Result<(), Box> { 135 | for (i, part) in self.partitions.iter().enumerate() { 136 | if part.mount_path.is_none() { 137 | continue; 138 | } 139 | 140 | // TODO support nvme 141 | let dev_path = format!("{}{}", self.selected_disk.display(), i + 1); 142 | 143 | println!("Create filesystem on `{dev_path}`"); 144 | 145 | // TODO use ext4 146 | let status = Command::new("mkfs.ext2").arg(dev_path).status()?; 147 | if !status.success() { 148 | return Err(format!("Filesystem creation failed!").into()); 149 | } 150 | } 151 | Ok(()) 152 | } 153 | 154 | /// Mounts filesystems to install the system on them. 155 | fn mount_filesystems(&self) -> Result<(), Box> { 156 | // Ensure partitions are mount in the right order 157 | let mut parts: Vec<(usize, &PartitionDesc)> = self.partitions.iter().enumerate().collect(); 158 | parts.sort_unstable_by(|(_, a), (_, b)| a.mount_path.cmp(&b.mount_path)); 159 | 160 | for (i, part) in parts { 161 | let Some(mnt_path) = &part.mount_path else { 162 | continue; 163 | }; 164 | 165 | // TODO support nvme 166 | let dev_path = format!("{}{}", self.selected_disk.display(), i + 1); 167 | let mnt_path = common::util::concat_paths(Path::new("/mnt"), mnt_path); 168 | 169 | println!("Mount `{dev_path}` at `{}`", mnt_path.display()); 170 | 171 | // Perform mount 172 | fs::create_dir_all(&mnt_path)?; 173 | let status = Command::new("mount") 174 | .arg(dev_path) 175 | .arg(&mnt_path) 176 | .status()?; 177 | if !status.success() { 178 | return Err(format!("Cannot mount partition at `{}`", mnt_path.display()).into()); 179 | } 180 | } 181 | Ok(()) 182 | } 183 | 184 | /// Creates the folder hierarchy on the disk. 185 | /// 186 | /// `mnt_path` is the path to the root filesystem's mountpoint. 187 | fn create_dirs(&self, mnt_path: &Path) -> Result<(), Box> { 188 | let paths = &[ 189 | "bin", 190 | "boot", 191 | "dev", 192 | "etc", 193 | "home", 194 | "lib", 195 | "media", 196 | "mnt", 197 | "opt", 198 | "proc", 199 | "root", 200 | "run", 201 | "sbin", 202 | "srv", 203 | "sys", 204 | "tmp", 205 | "usr", 206 | "var", 207 | "etc/opt", 208 | "etc/sysconfig", 209 | "lib/firmware", 210 | "run/lock", 211 | "run/log", 212 | "usr/bin", 213 | "usr/include", 214 | "usr/lib", 215 | "usr/local", 216 | "usr/sbin", 217 | "usr/share", 218 | "usr/src", 219 | "usr/share/doc", 220 | "usr/share/info", 221 | "usr/share/locale", 222 | "usr/share/man", 223 | "usr/share/misc", 224 | "usr/local/bin", 225 | "usr/local/include", 226 | "usr/local/lib", 227 | "usr/local/sbin", 228 | "usr/local/share", 229 | "usr/local/src", 230 | "usr/local/share/doc", 231 | "usr/local/share/info", 232 | "usr/local/share/locale", 233 | "usr/local/share/man", 234 | "usr/local/share/misc", 235 | "var/cache", 236 | "var/lib", 237 | "var/local", 238 | "var/log", 239 | "var/mail", 240 | "var/opt", 241 | "var/spool", 242 | "var/lib/misc", 243 | ]; 244 | for path in paths { 245 | println!("Create directory `{path}`"); 246 | let path = mnt_path.join(path); 247 | match fs::create_dir(path) { 248 | Ok(_) => {} 249 | Err(e) if e.kind() == ErrorKind::AlreadyExists => {} 250 | Err(e) => return Err(e.into()), 251 | } 252 | } 253 | Ok(()) 254 | } 255 | 256 | /// Installs packages on the system. 257 | /// 258 | /// `mnt_path` is the path to the root filesystem's mountpoint. 259 | fn install_packages(&self, mnt_path: &Path) -> Result<(), Box> { 260 | fs::create_dir_all(mnt_path.join("usr/lib/blimp"))?; 261 | 262 | let env = Environment::with_root(mnt_path.into()).unwrap(); 263 | // TODO add option to use remote repo 264 | let repo = Repository::load("/local_repo".into())?; 265 | 266 | for pkg in repo.list_packages()? { 267 | let name = pkg.get_name(); 268 | let version = pkg.get_version(); 269 | println!("Install `{name}` (version {version})..."); 270 | let archive_path = repo.get_archive_path(name, version); 271 | env.install(&pkg, &archive_path)?; 272 | } 273 | Ok(()) 274 | } 275 | 276 | /// Installs the bootloader. 277 | /// 278 | /// `mnt_path` is the path to the root filesystem's mountpoint. 279 | fn install_bootloader(&self, mnt_path: &Path) -> Result<(), Box> { 280 | let status = Command::new("grub-install") 281 | .arg("--target=i386-pc") 282 | .arg(format!( 283 | "--boot-directory={}", 284 | mnt_path.join("boot").display() 285 | )) 286 | .arg(&self.selected_disk) 287 | .status()?; 288 | if !status.success() { 289 | return Err("Cannot install bootloader".into()); 290 | } 291 | 292 | // Write `grub.cfg` 293 | let mut file = OpenOptions::new() 294 | .create(true) 295 | .truncate(true) 296 | .write(true) 297 | .open(mnt_path.join("boot/grub/grub.cfg"))?; 298 | file.write_all(include_bytes!("grub.cfg"))?; 299 | Ok(()) 300 | } 301 | 302 | /// Sets localization options. 303 | /// 304 | /// `mnt_path` is the path to the root filesystem's mountpoint. 305 | fn set_locales(&self, mnt_path: &Path) -> Result<(), Box> { 306 | let locale = self.lang.as_ref().unwrap().get_locale(); 307 | 308 | let path = mnt_path.join("etc/locale.conf"); 309 | let mut file = OpenOptions::new() 310 | .read(true) 311 | .write(true) 312 | .create(true) 313 | .truncate(true) 314 | .open(path)?; 315 | writeln!(file, "LC_ALL={locale}")?; 316 | writeln!(file, "LANG={locale}")?; 317 | 318 | // TODO generate locale 319 | 320 | Ok(()) 321 | } 322 | 323 | /// Creates the hostname file. 324 | /// 325 | /// `mnt_path` is the path to the root filesystem's mountpoint. 326 | fn set_hostname(&self, mnt_path: &Path) -> Result<(), Box> { 327 | let path = mnt_path.join("etc/hostname"); 328 | let mut file = OpenOptions::new() 329 | .read(true) 330 | .write(true) 331 | .create(true) 332 | .truncate(true) 333 | .open(path)?; 334 | file.write_all(self.hostname.as_bytes())?; 335 | 336 | Ok(()) 337 | } 338 | 339 | /// Creates users and groups. 340 | /// 341 | /// The function creates: 342 | /// - `/etc/passwd` 343 | /// - `/etc/shadow` 344 | /// - `/etc/group` 345 | /// - The home directory for each user 346 | /// 347 | /// `mnt_path` is the path to the root filesystem's mountpoint. 348 | fn create_users(&self, mnt_path: &Path) -> Result<(), Box> { 349 | // Create admin user's home 350 | let admin_uid = 1000; 351 | let admin_home = format!("/home/{}", self.admin_user).into(); 352 | let admin_home_mnt = mnt_path.join(format!("home/{}", self.admin_user)); 353 | fs::create_dir_all(&admin_home_mnt)?; 354 | chown(&admin_home_mnt, Some(admin_uid), Some(admin_uid))?; 355 | 356 | // Write /etc/passwd 357 | let users = [ 358 | User { 359 | login_name: "root".into(), 360 | password: "x".into(), 361 | uid: 0, 362 | gid: 0, 363 | comment: "".into(), 364 | home: "/root".into(), 365 | interpreter: "/bin/bash".into(), 366 | }, 367 | User { 368 | login_name: self.admin_user.clone(), 369 | password: "x".into(), 370 | uid: admin_uid, 371 | gid: admin_uid, 372 | comment: "".into(), 373 | home: admin_home, 374 | interpreter: "/bin/bash".into(), 375 | }, 376 | ]; 377 | let passwd_path = mnt_path.join("etc/passwd"); 378 | user::write_passwd(&passwd_path, &users)?; 379 | fs::set_permissions(passwd_path, Permissions::from_mode(0o644))?; 380 | 381 | // Write /etc/shadow 382 | let last_change = (get_timestamp().as_secs() / 3600 / 24) as u32; 383 | let shadows = [ 384 | Shadow { 385 | login_name: "root".into(), 386 | password: self.admin_pass.clone(), 387 | last_change, 388 | minimum_age: None, 389 | maximum_age: None, 390 | warning_period: None, 391 | inactivity_period: None, 392 | account_expiration: None, 393 | reserved: "".into(), 394 | }, 395 | Shadow { 396 | login_name: self.admin_user.clone(), 397 | password: self.admin_pass.clone(), 398 | last_change, 399 | minimum_age: None, 400 | maximum_age: None, 401 | warning_period: None, 402 | inactivity_period: None, 403 | account_expiration: None, 404 | reserved: "".into(), 405 | }, 406 | ]; 407 | let shadow_path = mnt_path.join("etc/shadow"); 408 | user::write_shadow(&shadow_path, &shadows)?; 409 | fs::set_permissions(shadow_path, Permissions::from_mode(0o600))?; 410 | 411 | // Write /etc/group 412 | let groups = [ 413 | Group { 414 | group_name: "root".into(), 415 | password: "x".into(), 416 | gid: 0, 417 | users_list: "root".into(), 418 | }, 419 | Group { 420 | group_name: self.admin_user.clone(), 421 | password: "x".into(), 422 | gid: admin_uid, 423 | users_list: self.admin_user.clone(), 424 | }, 425 | ]; 426 | let group_path = mnt_path.join("etc/group"); 427 | user::write_group(&group_path, &groups)?; 428 | fs::set_permissions(group_path, Permissions::from_mode(0o644))?; 429 | 430 | Ok(()) 431 | } 432 | 433 | /// Unmounts filesystems to finalize the installation. 434 | /// 435 | /// `mnt_path` is the path to the root filesystem's mountpoint. 436 | fn unmount_filesystems(&self, mnt_path: &Path) -> Result<(), Box> { 437 | let status = Command::new("umount").arg("-R").arg(mnt_path).status()?; 438 | if status.success() { 439 | Ok(()) 440 | } else { 441 | Err("Cannot unmount filesystems".into()) 442 | } 443 | } 444 | 445 | /// Performs the installation operation. 446 | /// 447 | /// `prompt` is the prompt associated with the installation procedure. 448 | pub fn perform_install(&self, prompt: &mut dyn InstallPrompt) -> Result<(), Box> { 449 | let mut progress = InstallProgress { 450 | prompt, 451 | 452 | logs: vec![], 453 | progress: 0, 454 | }; 455 | 456 | let mnt_path = Path::new("/mnt"); 457 | progress.log(&format!("Create directory `{}`\n", mnt_path.display())); 458 | fs::create_dir(&mnt_path)?; 459 | 460 | progress.log("\nPartition disk\n"); 461 | self.partition_disks()?; 462 | 463 | progress.log("\nCreate filesystems\n"); 464 | self.create_filesystems()?; 465 | 466 | progress.log("\nMount filesystems\n"); 467 | self.mount_filesystems()?; 468 | 469 | progress.log("\nCreate directory structure\n"); 470 | self.create_dirs(&mnt_path)?; 471 | 472 | progress.log("\nInstall packages\n"); 473 | self.install_packages(&mnt_path)?; 474 | 475 | progress.log("\nInstall bootloader\n"); 476 | self.install_bootloader(&mnt_path)?; 477 | 478 | progress.log("\nSet locales\n"); 479 | self.set_locales(&mnt_path)?; 480 | 481 | progress.log("\nSet hostname\n"); 482 | self.set_hostname(&mnt_path)?; 483 | 484 | progress.log("\nCreate users and groups\n"); 485 | self.create_users(&mnt_path)?; 486 | 487 | progress.log("\nUnmount filesystems\n"); 488 | self.unmount_filesystems(&mnt_path)?; 489 | 490 | progress.log("\nDone!\n"); 491 | 492 | Ok(()) 493 | } 494 | } 495 | 496 | /// Structure representing the current progress of the installation. 497 | pub struct InstallProgress<'p> { 498 | /// The installation prompt. 499 | prompt: &'p mut dyn InstallPrompt, 500 | 501 | /// Logs. 502 | logs: Vec, 503 | /// Progress in percent, between 0 and 1000. 504 | progress: u16, 505 | } 506 | 507 | impl<'p> InstallProgress<'p> { 508 | /// Inserts the given logs. 509 | pub fn log(&mut self, s: &str) { 510 | print!("{s}"); 511 | 512 | self.logs 513 | .append(&mut s.split('\n').map(str::to_owned).collect()); 514 | // FIXME self.prompt.update_progress(self); 515 | } 516 | 517 | /// Returns an immutable reference to the installation logs. 518 | pub fn get_logs(&self) -> &[String] { 519 | self.logs.as_slice() 520 | } 521 | 522 | /// Returns the current percentage of advancement of the installation, represented by a value 523 | /// between 0 and 1000. 524 | pub fn get_progress(&self) -> u16 { 525 | self.progress 526 | } 527 | 528 | /// Sets the current percentage of advancement of the installation, represented by a value 529 | /// between 0 and 1000. 530 | pub fn set_progress(&mut self, progress: u16) { 531 | self.progress = progress; 532 | // FIXME self.prompt.update_progress(self); 533 | } 534 | } 535 | -------------------------------------------------------------------------------- /src/lang.rs: -------------------------------------------------------------------------------- 1 | //! TODO doc 2 | 3 | use serde::Deserialize; 4 | use serde::Serialize; 5 | use std::collections::HashMap; 6 | use std::fmt; 7 | use std::fs; 8 | use std::fs::File; 9 | use std::io; 10 | use std::io::BufReader; 11 | use std::path::Path; 12 | 13 | /// The path to the languages directory. 14 | const LANGS_PATH: &str = "lang/"; // TODO Use an absolute path 15 | 16 | /// Structure representing a language. 17 | #[derive(Clone, Deserialize, Serialize)] 18 | pub struct Language { 19 | /// The name of the language used to select it. 20 | name: String, 21 | /// The display name of the language. 22 | display_name: String, 23 | 24 | /// The locale corresponding to the language. 25 | locale: String, 26 | } 27 | 28 | impl Language { 29 | /// Returns the list of available languages. 30 | /// 31 | /// The function returns a hashmap where the key is the name of the language and the value is 32 | /// the language itself. 33 | pub fn list() -> io::Result> { 34 | let mut langs = HashMap::new(); 35 | 36 | for e in fs::read_dir(LANGS_PATH)? { 37 | let e = e?; 38 | if !e.file_type()?.is_file() { 39 | continue; 40 | } 41 | 42 | let path = Path::new(LANGS_PATH).join(e.file_name()); 43 | let file = File::open(path)?; 44 | let reader = BufReader::new(file); 45 | 46 | let lang: Self = serde_json::from_reader(reader)?; 47 | langs.insert(lang.name.clone(), lang); 48 | } 49 | 50 | Ok(langs) 51 | } 52 | 53 | /// Returns the locale associated with the language. 54 | pub fn get_locale(&self) -> &str { 55 | &self.locale 56 | } 57 | } 58 | 59 | impl fmt::Display for Language { 60 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 61 | write!(fmt, "{} - {}", self.name, self.display_name) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //! Installation utility for the Maestro operating system. 2 | 3 | mod install; 4 | mod lang; 5 | mod prompt; 6 | mod util; 7 | 8 | use prompt::term::TermPrompt; 9 | use prompt::term::{CODE_RED, CODE_RESET}; 10 | use prompt::InstallPrompt; 11 | use prompt::InstallStep; 12 | use std::env; 13 | use std::process::exit; 14 | 15 | fn main() { 16 | // Get prompt type 17 | let prompt_type = env::args().nth(1); 18 | let prompt_type = prompt_type.as_deref().unwrap_or("term"); 19 | // Create prompt 20 | let mut prompt = match prompt_type { 21 | "term" => TermPrompt::new(), 22 | // TODO Add support for GUI 23 | _ => { 24 | eprintln!("Invalid prompt type: {prompt_type}"); 25 | exit(1); 26 | } 27 | }; 28 | 29 | while let Some(curr_step) = prompt.get_current_step() { 30 | prompt.next_step(); 31 | if matches!(curr_step, InstallStep::Install) { 32 | let infos = prompt.get_infos(); 33 | if let Err(e) = infos.perform_install(&mut prompt) { 34 | eprintln!("{CODE_RED}Installation failed: {e}{CODE_RESET}"); 35 | exit(1); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/prompt/mod.rs: -------------------------------------------------------------------------------- 1 | //! TODO doc 2 | 3 | pub mod term; 4 | 5 | use crate::install::InstallInfo; 6 | use crate::install::InstallProgress; 7 | 8 | /// Enumeration of installation steps. 9 | #[derive(Clone, Copy)] 10 | pub enum InstallStep { 11 | Welcome, 12 | Localization, 13 | SystemInfo, 14 | CreateAdmin, 15 | Partitions, 16 | Install, 17 | Finished, 18 | } 19 | 20 | impl InstallStep { 21 | /// Returns the number of the step. 22 | pub fn get_number(&self) -> u32 { 23 | match self { 24 | Self::Welcome => 0, 25 | Self::Localization => 1, 26 | Self::SystemInfo => 2, 27 | Self::CreateAdmin => 3, 28 | Self::Partitions => 4, 29 | Self::Install => 5, 30 | Self::Finished => 6, 31 | } 32 | } 33 | 34 | /// Returns the name of the step. 35 | pub fn get_name(&self) -> Option<&'static str> { 36 | match self { 37 | Self::Welcome => None, 38 | Self::Localization => Some("Localization"), 39 | Self::SystemInfo => Some("System informations"), 40 | Self::CreateAdmin => Some("Creating administrator user"), 41 | Self::Partitions => Some("Disk partitions"), 42 | Self::Install => Some("Installation"), 43 | Self::Finished => Some("Finished"), 44 | } 45 | } 46 | 47 | /// Returns the step next to the current. 48 | /// If this is the last step, the function returns None. 49 | pub fn get_next(&self) -> Option { 50 | match self { 51 | Self::Welcome => Some(Self::Localization), 52 | Self::Localization => Some(Self::SystemInfo), 53 | Self::SystemInfo => Some(Self::CreateAdmin), 54 | Self::CreateAdmin => Some(Self::Partitions), 55 | Self::Partitions => Some(Self::Install), 56 | Self::Install => Some(Self::Finished), 57 | Self::Finished => None, 58 | } 59 | } 60 | } 61 | 62 | /// Trait to be implemented for each ways of ask the user for informations about the installation. 63 | pub trait InstallPrompt { 64 | /// Returns the current step. 65 | /// If the function returns None, the installation is finished. 66 | fn get_current_step(&self) -> Option; 67 | /// Prompts the next step. 68 | fn next_step(&mut self); 69 | 70 | /// Returns prompted informations. 71 | fn get_infos(&self) -> InstallInfo; 72 | 73 | /// Updates the current progress of the installation. 74 | fn update_progress(&mut self, progress: &InstallProgress); 75 | } 76 | -------------------------------------------------------------------------------- /src/prompt/motd: -------------------------------------------------------------------------------- 1 |  . 2 | .::. 3 |  ::::: 4 | .::::::. 5 |  .::::::::. Welcome to the Maestro installer! 6 |  .::::::::::. 7 | .:::::::::::: 8 | :::::::::::::: 9 |  .:------::::::::. 10 | .--------------:: 11 | :------=---------. 12 | .-===--=+=--------: 13 |  .-======++==------: 14 | -======+*=======-. 15 | ======+*+======-. 16 | -====+*+======: 17 |  ====*+=====-. 18 | ==+*====-. 19 | =++=-:. 20 | :=:. .:-=+++++==--:::.. Press ENTER 21 | -. .-*##*=-: 22 | : :=*#*=. 23 | .:-=+**=: 24 | 25 | -------------------------------------------------------------------------------- /src/prompt/term.rs: -------------------------------------------------------------------------------- 1 | //! This module implements installation prompt from terminal. 2 | 3 | use super::InstallPrompt; 4 | use super::InstallStep; 5 | use crate::install::InstallInfo; 6 | use crate::install::InstallProgress; 7 | use crate::install::PartitionDesc; 8 | use crate::lang::Language; 9 | use crate::util; 10 | use fdisk::disk::Disk; 11 | use std::process::exit; 12 | use utils::util::ByteSize; 13 | 14 | /// Resets text style. 15 | pub const CODE_RESET: &str = "\x1b[0m"; 16 | /// Makes the text red. 17 | pub const CODE_RED: &str = "\x1b[31m"; 18 | /// Makes the text red. 19 | pub const CODE_ORANGE: &str = "\x1b[33m"; 20 | /// Makes the text green. 21 | pub const CODE_GREEN: &str = "\x1b[92m"; 22 | 23 | /// Prompts text from the user on the terminal. 24 | /// 25 | /// Arguments: 26 | /// - `prompt_text` is the text showed to the user while prompting. 27 | /// - `hidden` tells whether the input must be hidden. 28 | /// - `validator` is a function called to check whether the given input is valid. If not, the 29 | /// function can return an error message which is printed, then the function prompts for input 30 | /// again. 31 | /// If no error message is provided, no message is printed and the function prompts for input again 32 | /// directly. 33 | fn prompt Result<(), Option>>( 34 | prompt_text: &str, 35 | hidden: bool, 36 | validator: V, 37 | ) -> String { 38 | loop { 39 | let Some(input) = utils::prompt::prompt(Some(prompt_text), hidden) else { 40 | // TODO 41 | todo!(); 42 | }; 43 | 44 | match validator(&input) { 45 | Ok(()) => return input, 46 | Err(Some(e)) => eprintln!("{CODE_ORANGE}{e}{CODE_RESET}"), 47 | _ => {} 48 | } 49 | } 50 | } 51 | 52 | /// Validator for the `prompt` function which validates non-empty inputs. 53 | fn non_empty_validator(input: &str) -> Result<(), Option> { 54 | if !input.is_empty() { 55 | Ok(()) 56 | } else { 57 | Err(None) 58 | } 59 | } 60 | 61 | /// Structure representing the terminal prompt. 62 | pub struct TermPrompt { 63 | /// The current step. 64 | curr_step: Option, 65 | 66 | /// Install informations. 67 | infos: InstallInfo, 68 | } 69 | 70 | impl TermPrompt { 71 | /// Creates a new instance. 72 | pub fn new() -> Self { 73 | Self { 74 | curr_step: Some(InstallStep::Welcome), 75 | 76 | infos: InstallInfo::default(), 77 | } 78 | } 79 | } 80 | 81 | impl InstallPrompt for TermPrompt { 82 | fn get_current_step(&self) -> Option { 83 | self.curr_step 84 | } 85 | 86 | fn next_step(&mut self) { 87 | let Some(curr_step) = &self.curr_step else { 88 | return; 89 | }; 90 | 91 | if let Some(step_name) = curr_step.get_name() { 92 | println!("|> Step {}: {step_name}", curr_step.get_number()); 93 | println!(); 94 | } 95 | 96 | match curr_step { 97 | InstallStep::Welcome => { 98 | print!("{}", include_str!("motd")); 99 | util::read_line(); 100 | } 101 | 102 | InstallStep::Localization => { 103 | let available_langs = match Language::list() { 104 | Ok(l) => l, 105 | Err(e) => panic!("Could not read languages list. This is a bug. Error: {e}"), 106 | }; 107 | 108 | while self.infos.lang.is_none() { 109 | println!("Type `?` to get the list of available languages."); 110 | let lang = prompt("Type the system's language: ", false, non_empty_validator); 111 | 112 | match lang.as_str() { 113 | "?" => { 114 | println!("Available languages:"); 115 | for (_, l) in available_langs.iter() { 116 | println!("- {l}"); 117 | } 118 | println!(); 119 | } 120 | _ => { 121 | if let Some(lang) = available_langs.get(&lang) { 122 | self.infos.lang = Some(lang.clone()); 123 | } else { 124 | eprintln!( 125 | "\n{CODE_ORANGE}Invalid language `{lang}`!{CODE_RESET}\n" 126 | ); 127 | } 128 | } 129 | } 130 | } 131 | 132 | // TODO Contient/Country 133 | // TODO Timezone 134 | } 135 | 136 | InstallStep::SystemInfo => { 137 | self.infos.hostname = prompt("Type system hostname: ", false, non_empty_validator); 138 | } 139 | 140 | InstallStep::CreateAdmin => { 141 | self.infos.admin_user = prompt("Type admin username: ", false, non_empty_validator); 142 | 143 | loop { 144 | println!(); 145 | let pass = prompt("Type admin/root password: ", true, non_empty_validator); 146 | let pass_confirm = prompt("Confirm admin/root password: ", true, |_| Ok(())); 147 | 148 | // Check correctness 149 | if pass != pass_confirm { 150 | eprintln!("{CODE_ORANGE}Passwords don't match!{CODE_RESET}"); 151 | continue; 152 | } 153 | let pass = match utils::user::hash_password(&pass) { 154 | Ok(p) => p, 155 | Err(e) => { 156 | eprintln!("{CODE_ORANGE}Invalid password: {e}{CODE_RESET}"); 157 | continue; 158 | } 159 | }; 160 | self.infos.admin_pass = pass; 161 | break; 162 | } 163 | } 164 | 165 | InstallStep::Partitions => { 166 | let disks = Disk::list().unwrap_or_else(|e| { 167 | eprintln!("{CODE_RED}Failed to retrieve disks list: {e}{CODE_RESET}"); 168 | exit(1); 169 | }); 170 | // TODO Filter out disks that don't have enough space 171 | if disks.is_empty() { 172 | eprintln!( 173 | "{CODE_RED}No disk is available for installation. Exiting...{CODE_RESET}" 174 | ); 175 | exit(1); 176 | } 177 | 178 | self.infos.selected_disk = loop { 179 | println!("Available disks and partitions:"); 180 | for dev_path in disks.iter() { 181 | let Some(disk) = Disk::read(dev_path.clone()).unwrap_or_else(|e| { 182 | eprintln!("{CODE_RED}Cannot read disk: {e}{CODE_RESET}"); 183 | exit(1); 184 | }) else { 185 | continue; 186 | }; 187 | 188 | println!( 189 | "- {} (sectors: {}, size: {})", 190 | dev_path.display(), 191 | disk.get_size(), 192 | ByteSize::from_sectors_count(disk.get_size()), 193 | ); 194 | 195 | for p in &disk.partition_table.partitions { 196 | println!("\t- {p}"); 197 | } 198 | } 199 | 200 | // If only one disk is available, de facto select it 201 | if disks.len() == 1 { 202 | break disks.first().unwrap().to_str().unwrap().into(); 203 | } 204 | 205 | println!(); 206 | let selected_disk = prompt( 207 | "Select the disk to install the system on: ", 208 | false, 209 | |input| { 210 | let exists = disks 211 | .iter() 212 | .any(|dev_path| dev_path.to_str() == Some(input)); 213 | 214 | if input.is_empty() { 215 | Ok(()) 216 | } else if !exists { 217 | Err(Some(format!("Disk `{input}` doesn't exist"))) 218 | } else { 219 | Ok(()) 220 | } 221 | }, 222 | ); 223 | 224 | if !selected_disk.is_empty() { 225 | break selected_disk.into(); 226 | } 227 | }; 228 | 229 | println!(); 230 | println!( 231 | "Installing system on disk `{}`", 232 | self.infos.selected_disk.display() 233 | ); 234 | println!("Partitioning options:"); 235 | println!("1 - Wipe disk and install system automatically (warning: this operation will destroy all data on the disk)"); 236 | // TODO: 237 | //println!("2 - Manual partitioning (advanced)"); 238 | //println!("3 - Use free space left on disk"); 239 | // TODO Disable option 3 if not enough free space is left on disk 240 | println!(); 241 | println!("NOTE: Other options are not yet available"); 242 | println!(); 243 | 244 | let option = prompt("Select an option: ", false, |input| match input { 245 | "1" => Ok(()), 246 | _ => Err(Some(format!("Invalid option `{input}`"))), 247 | }); 248 | 249 | match option.as_str() { 250 | "1" => { 251 | let disk_path = disks 252 | .into_iter() 253 | .find(|dev_path| dev_path == &self.infos.selected_disk) 254 | .unwrap(); 255 | // TODO handle error 256 | let disk = Disk::read(disk_path).unwrap().unwrap(); 257 | 258 | let bios_boot_part = PartitionDesc { 259 | start: 2048, 260 | size: 2048, 261 | 262 | // BIOS boot 263 | part_type: "21686148-6449-6E6F-744E-656564454649".to_owned(), 264 | 265 | bootable: false, 266 | 267 | mount_path: None, 268 | }; 269 | 270 | let boot_part = PartitionDesc { 271 | start: bios_boot_part.start + bios_boot_part.size, 272 | size: 262144, 273 | 274 | // EFI System 275 | part_type: "C12A7328-F81F-11D2-BA4B-00A0C93EC93B".to_owned(), 276 | 277 | bootable: true, 278 | 279 | mount_path: Some("/boot".into()), 280 | }; 281 | 282 | // TODO swap 283 | 284 | let root_start = boot_part.start + boot_part.size; 285 | let root_part = PartitionDesc { 286 | start: root_start, 287 | size: disk.get_size() - root_start, 288 | 289 | // Linux root (x86) 290 | part_type: "44479540-F297-41B2-9AF7-D131D5F0458A".to_owned(), 291 | 292 | bootable: false, 293 | 294 | mount_path: Some("/".into()), 295 | }; 296 | 297 | self.infos.partitions = vec![ 298 | bios_boot_part, 299 | boot_part, 300 | // TODO swap 301 | root_part, 302 | ]; 303 | } 304 | 305 | "2" => { 306 | // TODO Ask for modifications on existing partitions 307 | todo!(); 308 | } 309 | 310 | "3" => { 311 | // TODO Build partitions table 312 | todo!(); 313 | } 314 | 315 | _ => unreachable!(), 316 | } 317 | 318 | println!(); 319 | println!("The following partitions will be created:"); 320 | for p in self.infos.partitions.iter() { 321 | println!("- {p}"); 322 | } 323 | } 324 | 325 | InstallStep::Install => { 326 | // TODO Add option to export selected options to file 327 | 328 | loop { 329 | let confirm = 330 | prompt("Confirm installation? (y/n) ", false, non_empty_validator); 331 | match confirm.as_str() { 332 | "y" => break, 333 | "n" => { 334 | eprintln!("{CODE_RED}Installation cancelled{CODE_RESET}"); 335 | exit(1); 336 | } 337 | _ => {} 338 | } 339 | } 340 | } 341 | 342 | InstallStep::Finished => { 343 | println!("{CODE_GREEN}Installation is now finished!{CODE_RESET}"); 344 | println!("To start maestro, unplug your installation medium, then press ENTER"); 345 | 346 | util::read_line(); 347 | util::reboot(); 348 | } 349 | } 350 | println!(); 351 | 352 | self.curr_step = curr_step.get_next(); 353 | } 354 | 355 | fn get_infos(&self) -> InstallInfo { 356 | self.infos.clone() 357 | } 358 | 359 | fn update_progress(&mut self, progress: &InstallProgress) { 360 | // TODO 361 | todo!(); 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | //! This module implements utility functions. 2 | 3 | use std::io; 4 | use std::io::BufRead; 5 | use std::process::exit; 6 | use std::process::Command; 7 | 8 | /// Reads a line from the standard input and returns it. 9 | /// 10 | /// If reading fails, the function exits the program. 11 | pub fn read_line() -> String { 12 | let stdin = io::stdin(); 13 | 14 | match stdin.lock().lines().next() { 15 | Some(Ok(line)) => line, 16 | 17 | Some(Err(_)) => { 18 | eprintln!("Failed to read line from input"); 19 | exit(1); 20 | } 21 | 22 | None => exit(0), 23 | } 24 | } 25 | 26 | /// Reboots the system. 27 | /// If the current process doesn't have the permission to reboot the system, the function prints an 28 | /// error, then exits the process. 29 | pub fn reboot() -> ! { 30 | let _ = Command::new("reboot").status(); 31 | 32 | eprintln!("Failed to reboot the system. Exiting..."); 33 | exit(1) 34 | } 35 | --------------------------------------------------------------------------------