├── .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 |      7 |   
  8 |
  7 |   
  8 | 
  9 | 
 10 | [](./LICENSE)
 11 | 
 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 | [95m                                  .
 2 |                                 .::.
 3 | [95m                               :::::
 4 |                              .::::::.
 5 | [94m                           .::::::::. [0mWelcome to the Maestro installer!
 6 | [94m                         .::::::::::.
 7 |                        .::::::::::::
 8 |                      ::::::::::::::
 9 | [92m                  .:------::::::::.
10 |                 .--------------::
11 |               :------=---------.
12 |            .-===--=[97m+[92m=--------:
13 | [93m         .-======[97m++[93m==------:
14 |         -======[97m+*[93m=======-.
15 |        ======[97m+*+[93m======-.
16 |       -====[97m+*+[93m======:
17 | [91m      ====[97m*+[91m=====-.
18 |       ==[97m+*[91m====-.
19 |       =[97m++[91m=-:.
20 |      [97m:=:[91m.           [97m.:-=+++++==--:::.. [30m[107mPress ENTER[0m
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 | 
--------------------------------------------------------------------------------