├── .github ├── FUNDING.yml ├── dependabot.yml ├── lichen_logo.png └── workflows │ └── ci.yaml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── LICENSES └── MPL-2.0.txt ├── README.md ├── crates ├── installer │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── account.rs │ │ ├── engine.rs │ │ ├── lib.rs │ │ ├── model.rs │ │ ├── partitions.rs │ │ ├── selections.rs │ │ ├── steps │ │ ├── cleanup.rs │ │ ├── context.rs │ │ ├── mod.rs │ │ ├── packaging.rs │ │ ├── partitions.rs │ │ └── postinstall.rs │ │ └── systemd.rs └── system │ ├── Cargo.toml │ ├── README.md │ └── src │ ├── disk │ ├── disks.rs │ ├── mod.rs │ └── partition.rs │ ├── lib.rs │ └── locale │ ├── iso_3166.rs │ ├── iso_639_2.rs │ ├── iso_639_3.rs │ ├── mod.rs │ └── registry.rs ├── lichen_cli ├── Cargo.toml └── src │ └── main.rs ├── lichen_ipc ├── Cargo.toml ├── README.md ├── build.rs └── src │ ├── com.serpentos.lichen.disks.varlink │ ├── com_serpentos_lichen_disks.rs │ ├── disks.rs │ ├── lib.rs │ └── main.rs ├── rustfmt.toml └── selections ├── README.md ├── base.json ├── cosmic.json ├── develop.json ├── gnome.json ├── kernel-common.json └── kernel-desktop.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [ikeycode] 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: "cargo" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | day: "tuesday" 10 | time: "10:00" 11 | timezone: "Europe/London" 12 | groups: 13 | cargo: 14 | patterns: 15 | - "*" 16 | -------------------------------------------------------------------------------- /.github/lichen_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AerynOS/lichen/990a7913eb69341233551f59ca13da437612d1ad/.github/lichen_logo.png -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | schedule: 9 | - cron: '30 2 * * *' 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | name: Build & Test Project 18 | 19 | steps: 20 | - name: Checkout source 21 | uses: actions/checkout@v4 22 | 23 | - name: Install Rust 24 | uses: dtolnay/rust-toolchain@stable 25 | with: 26 | components: rustfmt, clippy 27 | 28 | - name: Check Formatting 29 | run: cargo fmt --all -- --check 30 | 31 | - name: Cargo Cache 32 | uses: Swatinem/rust-cache@v2 33 | 34 | - name: Build project 35 | run: cargo build 36 | 37 | - name: Test project 38 | run: cargo test --workspace 39 | 40 | - name: Run clippy 41 | uses: giraffate/clippy-action@v1 42 | with: 43 | reporter: 'github-pr-check' 44 | clippy_flags: --workspace --no-deps 45 | filter_mode: nofilter 46 | github_token: ${{ secrets.GITHUB_TOKEN }} 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 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 = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "ansi_term" 31 | version = "0.12.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 34 | dependencies = [ 35 | "winapi", 36 | ] 37 | 38 | [[package]] 39 | name = "anstream" 40 | version = "0.6.18" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 43 | dependencies = [ 44 | "anstyle", 45 | "anstyle-parse", 46 | "anstyle-query", 47 | "anstyle-wincon", 48 | "colorchoice", 49 | "is_terminal_polyfill", 50 | "utf8parse", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle" 55 | version = "1.0.10" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 58 | 59 | [[package]] 60 | name = "anstyle-parse" 61 | version = "0.2.6" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 64 | dependencies = [ 65 | "utf8parse", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle-query" 70 | version = "1.1.2" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 73 | dependencies = [ 74 | "windows-sys 0.59.0", 75 | ] 76 | 77 | [[package]] 78 | name = "anstyle-wincon" 79 | version = "3.0.6" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 82 | dependencies = [ 83 | "anstyle", 84 | "windows-sys 0.59.0", 85 | ] 86 | 87 | [[package]] 88 | name = "autocfg" 89 | version = "1.4.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 92 | 93 | [[package]] 94 | name = "backtrace" 95 | version = "0.3.71" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 98 | dependencies = [ 99 | "addr2line", 100 | "cc", 101 | "cfg-if 1.0.0", 102 | "libc", 103 | "miniz_oxide", 104 | "object", 105 | "rustc-demangle", 106 | ] 107 | 108 | [[package]] 109 | name = "bitflags" 110 | version = "2.6.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 113 | dependencies = [ 114 | "serde", 115 | ] 116 | 117 | [[package]] 118 | name = "bumpalo" 119 | version = "3.16.0" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 122 | 123 | [[package]] 124 | name = "cc" 125 | version = "1.2.4" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" 128 | dependencies = [ 129 | "shlex", 130 | ] 131 | 132 | [[package]] 133 | name = "cfg-if" 134 | version = "0.1.10" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 137 | 138 | [[package]] 139 | name = "cfg-if" 140 | version = "1.0.0" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 143 | 144 | [[package]] 145 | name = "cfg_aliases" 146 | version = "0.2.1" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 149 | 150 | [[package]] 151 | name = "chainerror" 152 | version = "0.7.1" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "0ce1bb7fb0c258a6600d699950da347a7a9dad66c3ce815769b5f11cf8fce78e" 155 | 156 | [[package]] 157 | name = "chrono" 158 | version = "0.4.39" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" 161 | dependencies = [ 162 | "num-traits", 163 | ] 164 | 165 | [[package]] 166 | name = "chrono-tz" 167 | version = "0.10.0" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "cd6dd8046d00723a59a2f8c5f295c515b9bb9a331ee4f8f3d4dd49e428acd3b6" 170 | dependencies = [ 171 | "chrono", 172 | "chrono-tz-build", 173 | "phf", 174 | ] 175 | 176 | [[package]] 177 | name = "chrono-tz-build" 178 | version = "0.4.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7" 181 | dependencies = [ 182 | "parse-zoneinfo", 183 | "phf_codegen", 184 | ] 185 | 186 | [[package]] 187 | name = "clap" 188 | version = "4.5.23" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" 191 | dependencies = [ 192 | "clap_builder", 193 | "clap_derive", 194 | ] 195 | 196 | [[package]] 197 | name = "clap_builder" 198 | version = "4.5.23" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" 201 | dependencies = [ 202 | "anstream", 203 | "anstyle", 204 | "clap_lex", 205 | "strsim", 206 | ] 207 | 208 | [[package]] 209 | name = "clap_derive" 210 | version = "4.5.18" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 213 | dependencies = [ 214 | "heck", 215 | "proc-macro2", 216 | "quote", 217 | "syn 2.0.90", 218 | ] 219 | 220 | [[package]] 221 | name = "clap_lex" 222 | version = "0.7.4" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 225 | 226 | [[package]] 227 | name = "cliclack" 228 | version = "0.3.5" 229 | source = "git+https://github.com/ikeycode/cliclack.git?rev=35a1882c601b90bf1398c3cb867cc6b20bbe9ce9#35a1882c601b90bf1398c3cb867cc6b20bbe9ce9" 230 | dependencies = [ 231 | "console", 232 | "indicatif", 233 | "once_cell", 234 | "strsim", 235 | "termsize", 236 | "textwrap", 237 | "zeroize", 238 | ] 239 | 240 | [[package]] 241 | name = "color-eyre" 242 | version = "0.6.3" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" 245 | dependencies = [ 246 | "backtrace", 247 | "color-spantrace", 248 | "eyre", 249 | "indenter", 250 | "once_cell", 251 | "owo-colors", 252 | "tracing-error", 253 | "url", 254 | ] 255 | 256 | [[package]] 257 | name = "color-spantrace" 258 | version = "0.2.1" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" 261 | dependencies = [ 262 | "once_cell", 263 | "owo-colors", 264 | "tracing-core", 265 | "tracing-error", 266 | ] 267 | 268 | [[package]] 269 | name = "colorchoice" 270 | version = "1.0.3" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 273 | 274 | [[package]] 275 | name = "console" 276 | version = "0.15.8" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" 279 | dependencies = [ 280 | "encode_unicode", 281 | "lazy_static", 282 | "libc", 283 | "unicode-width 0.1.14", 284 | "windows-sys 0.52.0", 285 | ] 286 | 287 | [[package]] 288 | name = "crc" 289 | version = "3.2.1" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" 292 | dependencies = [ 293 | "crc-catalog", 294 | ] 295 | 296 | [[package]] 297 | name = "crc-catalog" 298 | version = "2.4.0" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" 301 | 302 | [[package]] 303 | name = "crossterm" 304 | version = "0.28.1" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 307 | dependencies = [ 308 | "bitflags", 309 | "crossterm_winapi", 310 | "futures-core", 311 | "mio", 312 | "parking_lot", 313 | "rustix", 314 | "serde", 315 | "signal-hook", 316 | "signal-hook-mio", 317 | "winapi", 318 | ] 319 | 320 | [[package]] 321 | name = "crossterm_winapi" 322 | version = "0.9.1" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 325 | dependencies = [ 326 | "winapi", 327 | ] 328 | 329 | [[package]] 330 | name = "dialoguer" 331 | version = "0.11.0" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" 334 | dependencies = [ 335 | "console", 336 | "fuzzy-matcher", 337 | "shell-words", 338 | "tempfile", 339 | "thiserror 1.0.69", 340 | "zeroize", 341 | ] 342 | 343 | [[package]] 344 | name = "displaydoc" 345 | version = "0.2.5" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 348 | dependencies = [ 349 | "proc-macro2", 350 | "quote", 351 | "syn 2.0.90", 352 | ] 353 | 354 | [[package]] 355 | name = "encode_unicode" 356 | version = "0.3.6" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 359 | 360 | [[package]] 361 | name = "env_filter" 362 | version = "0.1.2" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" 365 | dependencies = [ 366 | "log", 367 | "regex", 368 | ] 369 | 370 | [[package]] 371 | name = "env_logger" 372 | version = "0.10.2" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" 375 | dependencies = [ 376 | "humantime", 377 | "is-terminal", 378 | "log", 379 | "regex", 380 | "termcolor", 381 | ] 382 | 383 | [[package]] 384 | name = "env_logger" 385 | version = "0.11.5" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" 388 | dependencies = [ 389 | "anstream", 390 | "anstyle", 391 | "env_filter", 392 | "humantime", 393 | "log", 394 | ] 395 | 396 | [[package]] 397 | name = "errno" 398 | version = "0.3.10" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 401 | dependencies = [ 402 | "libc", 403 | "windows-sys 0.59.0", 404 | ] 405 | 406 | [[package]] 407 | name = "eyre" 408 | version = "0.6.12" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 411 | dependencies = [ 412 | "indenter", 413 | "once_cell", 414 | ] 415 | 416 | [[package]] 417 | name = "fastrand" 418 | version = "2.3.0" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 421 | 422 | [[package]] 423 | name = "form_urlencoded" 424 | version = "1.2.1" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 427 | dependencies = [ 428 | "percent-encoding", 429 | ] 430 | 431 | [[package]] 432 | name = "fs-err" 433 | version = "3.0.0" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "8bb60e7409f34ef959985bc9d9c5ee8f5db24ee46ed9775850548021710f807f" 436 | dependencies = [ 437 | "autocfg", 438 | "tokio", 439 | ] 440 | 441 | [[package]] 442 | name = "futures-core" 443 | version = "0.3.31" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 446 | 447 | [[package]] 448 | name = "fuzzy-matcher" 449 | version = "0.3.7" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" 452 | dependencies = [ 453 | "thread_local", 454 | ] 455 | 456 | [[package]] 457 | name = "getopts" 458 | version = "0.2.21" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 461 | dependencies = [ 462 | "unicode-width 0.1.14", 463 | ] 464 | 465 | [[package]] 466 | name = "getrandom" 467 | version = "0.2.15" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 470 | dependencies = [ 471 | "cfg-if 1.0.0", 472 | "libc", 473 | "wasi", 474 | ] 475 | 476 | [[package]] 477 | name = "gimli" 478 | version = "0.28.1" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 481 | 482 | [[package]] 483 | name = "gpt" 484 | version = "3.1.0" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "8283e7331b8c93b9756e0cfdbcfb90312852f953c6faf9bf741e684cc3b6ad69" 487 | dependencies = [ 488 | "bitflags", 489 | "crc", 490 | "log", 491 | "uuid", 492 | ] 493 | 494 | [[package]] 495 | name = "gpt" 496 | version = "4.0.0" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "ffa5448a0d9d541f1840c0e1b5fe513360861ca83c4b920619f54efe277f9254" 499 | dependencies = [ 500 | "bitflags", 501 | "crc", 502 | "simple-bytes", 503 | "uuid", 504 | ] 505 | 506 | [[package]] 507 | name = "heck" 508 | version = "0.5.0" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 511 | 512 | [[package]] 513 | name = "hermit-abi" 514 | version = "0.4.0" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" 517 | 518 | [[package]] 519 | name = "human_bytes" 520 | version = "0.4.3" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "91f255a4535024abf7640cb288260811fc14794f62b063652ed349f9a6c2348e" 523 | 524 | [[package]] 525 | name = "humantime" 526 | version = "2.1.0" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 529 | 530 | [[package]] 531 | name = "icu_collections" 532 | version = "1.5.0" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 535 | dependencies = [ 536 | "displaydoc", 537 | "yoke", 538 | "zerofrom", 539 | "zerovec", 540 | ] 541 | 542 | [[package]] 543 | name = "icu_locid" 544 | version = "1.5.0" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 547 | dependencies = [ 548 | "displaydoc", 549 | "litemap", 550 | "tinystr", 551 | "writeable", 552 | "zerovec", 553 | ] 554 | 555 | [[package]] 556 | name = "icu_locid_transform" 557 | version = "1.5.0" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 560 | dependencies = [ 561 | "displaydoc", 562 | "icu_locid", 563 | "icu_locid_transform_data", 564 | "icu_provider", 565 | "tinystr", 566 | "zerovec", 567 | ] 568 | 569 | [[package]] 570 | name = "icu_locid_transform_data" 571 | version = "1.5.0" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 574 | 575 | [[package]] 576 | name = "icu_normalizer" 577 | version = "1.5.0" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 580 | dependencies = [ 581 | "displaydoc", 582 | "icu_collections", 583 | "icu_normalizer_data", 584 | "icu_properties", 585 | "icu_provider", 586 | "smallvec", 587 | "utf16_iter", 588 | "utf8_iter", 589 | "write16", 590 | "zerovec", 591 | ] 592 | 593 | [[package]] 594 | name = "icu_normalizer_data" 595 | version = "1.5.0" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 598 | 599 | [[package]] 600 | name = "icu_properties" 601 | version = "1.5.1" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 604 | dependencies = [ 605 | "displaydoc", 606 | "icu_collections", 607 | "icu_locid_transform", 608 | "icu_properties_data", 609 | "icu_provider", 610 | "tinystr", 611 | "zerovec", 612 | ] 613 | 614 | [[package]] 615 | name = "icu_properties_data" 616 | version = "1.5.0" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 619 | 620 | [[package]] 621 | name = "icu_provider" 622 | version = "1.5.0" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 625 | dependencies = [ 626 | "displaydoc", 627 | "icu_locid", 628 | "icu_provider_macros", 629 | "stable_deref_trait", 630 | "tinystr", 631 | "writeable", 632 | "yoke", 633 | "zerofrom", 634 | "zerovec", 635 | ] 636 | 637 | [[package]] 638 | name = "icu_provider_macros" 639 | version = "1.5.0" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 642 | dependencies = [ 643 | "proc-macro2", 644 | "quote", 645 | "syn 2.0.90", 646 | ] 647 | 648 | [[package]] 649 | name = "idna" 650 | version = "1.0.3" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 653 | dependencies = [ 654 | "idna_adapter", 655 | "smallvec", 656 | "utf8_iter", 657 | ] 658 | 659 | [[package]] 660 | name = "idna_adapter" 661 | version = "1.2.0" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 664 | dependencies = [ 665 | "icu_normalizer", 666 | "icu_properties", 667 | ] 668 | 669 | [[package]] 670 | name = "indenter" 671 | version = "0.3.3" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 674 | 675 | [[package]] 676 | name = "indicatif" 677 | version = "0.17.9" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" 680 | dependencies = [ 681 | "console", 682 | "number_prefix", 683 | "portable-atomic", 684 | "unicode-width 0.2.0", 685 | "web-time", 686 | ] 687 | 688 | [[package]] 689 | name = "indoc" 690 | version = "2.0.5" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" 693 | 694 | [[package]] 695 | name = "installer" 696 | version = "0.1.0" 697 | dependencies = [ 698 | "fs-err", 699 | "human_bytes", 700 | "libc", 701 | "log", 702 | "serde", 703 | "serde_json", 704 | "system", 705 | "thiserror 2.0.7", 706 | "topology", 707 | ] 708 | 709 | [[package]] 710 | name = "is-terminal" 711 | version = "0.4.13" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" 714 | dependencies = [ 715 | "hermit-abi", 716 | "libc", 717 | "windows-sys 0.52.0", 718 | ] 719 | 720 | [[package]] 721 | name = "is_terminal_polyfill" 722 | version = "1.70.1" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 725 | 726 | [[package]] 727 | name = "itoa" 728 | version = "1.0.14" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 731 | 732 | [[package]] 733 | name = "js-sys" 734 | version = "0.3.76" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" 737 | dependencies = [ 738 | "once_cell", 739 | "wasm-bindgen", 740 | ] 741 | 742 | [[package]] 743 | name = "lazy_static" 744 | version = "1.5.0" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 747 | 748 | [[package]] 749 | name = "libc" 750 | version = "0.2.168" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" 753 | 754 | [[package]] 755 | name = "lichen_cli" 756 | version = "0.1.0" 757 | dependencies = [ 758 | "chrono-tz", 759 | "cliclack", 760 | "color-eyre", 761 | "console", 762 | "crossterm", 763 | "dialoguer", 764 | "env_logger 0.11.5", 765 | "indicatif", 766 | "indoc", 767 | "installer", 768 | "nix", 769 | ] 770 | 771 | [[package]] 772 | name = "lichen_ipc" 773 | version = "0.1.0" 774 | dependencies = [ 775 | "clap", 776 | "color-eyre", 777 | "log", 778 | "nix", 779 | "pretty_env_logger", 780 | "serde", 781 | "serde_derive", 782 | "serde_json", 783 | "system", 784 | "varlink", 785 | "varlink_generator", 786 | ] 787 | 788 | [[package]] 789 | name = "linux-raw-sys" 790 | version = "0.4.14" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 793 | 794 | [[package]] 795 | name = "litemap" 796 | version = "0.7.4" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" 799 | 800 | [[package]] 801 | name = "lock_api" 802 | version = "0.4.12" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 805 | dependencies = [ 806 | "autocfg", 807 | "scopeguard", 808 | ] 809 | 810 | [[package]] 811 | name = "log" 812 | version = "0.4.22" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 815 | 816 | [[package]] 817 | name = "memchr" 818 | version = "2.7.4" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 821 | 822 | [[package]] 823 | name = "memoffset" 824 | version = "0.9.1" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" 827 | dependencies = [ 828 | "autocfg", 829 | ] 830 | 831 | [[package]] 832 | name = "miniz_oxide" 833 | version = "0.7.4" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 836 | dependencies = [ 837 | "adler", 838 | ] 839 | 840 | [[package]] 841 | name = "mio" 842 | version = "1.0.3" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 845 | dependencies = [ 846 | "libc", 847 | "log", 848 | "wasi", 849 | "windows-sys 0.52.0", 850 | ] 851 | 852 | [[package]] 853 | name = "nix" 854 | version = "0.29.0" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 857 | dependencies = [ 858 | "bitflags", 859 | "cfg-if 1.0.0", 860 | "cfg_aliases", 861 | "libc", 862 | ] 863 | 864 | [[package]] 865 | name = "num-traits" 866 | version = "0.2.19" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 869 | dependencies = [ 870 | "autocfg", 871 | ] 872 | 873 | [[package]] 874 | name = "number_prefix" 875 | version = "0.4.0" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 878 | 879 | [[package]] 880 | name = "object" 881 | version = "0.32.2" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 884 | dependencies = [ 885 | "memchr", 886 | ] 887 | 888 | [[package]] 889 | name = "once_cell" 890 | version = "1.20.2" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 893 | 894 | [[package]] 895 | name = "owo-colors" 896 | version = "3.5.0" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" 899 | 900 | [[package]] 901 | name = "parking_lot" 902 | version = "0.12.3" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 905 | dependencies = [ 906 | "lock_api", 907 | "parking_lot_core", 908 | ] 909 | 910 | [[package]] 911 | name = "parking_lot_core" 912 | version = "0.9.10" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 915 | dependencies = [ 916 | "cfg-if 1.0.0", 917 | "libc", 918 | "redox_syscall", 919 | "smallvec", 920 | "windows-targets", 921 | ] 922 | 923 | [[package]] 924 | name = "parse-zoneinfo" 925 | version = "0.3.1" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" 928 | dependencies = [ 929 | "regex", 930 | ] 931 | 932 | [[package]] 933 | name = "peg" 934 | version = "0.6.3" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "9f76678828272f177ac33b7e2ac2e3e73cc6c1cd1e3e387928aa69562fa51367" 937 | dependencies = [ 938 | "peg-macros", 939 | "peg-runtime", 940 | ] 941 | 942 | [[package]] 943 | name = "peg-macros" 944 | version = "0.6.3" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "636d60acf97633e48d266d7415a9355d4389cea327a193f87df395d88cd2b14d" 947 | dependencies = [ 948 | "peg-runtime", 949 | "proc-macro2", 950 | "quote", 951 | ] 952 | 953 | [[package]] 954 | name = "peg-runtime" 955 | version = "0.6.3" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "9555b1514d2d99d78150d3c799d4c357a3e2c2a8062cd108e93a06d9057629c5" 958 | 959 | [[package]] 960 | name = "percent-encoding" 961 | version = "2.3.1" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 964 | 965 | [[package]] 966 | name = "phf" 967 | version = "0.11.2" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" 970 | dependencies = [ 971 | "phf_shared", 972 | ] 973 | 974 | [[package]] 975 | name = "phf_codegen" 976 | version = "0.11.2" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" 979 | dependencies = [ 980 | "phf_generator", 981 | "phf_shared", 982 | ] 983 | 984 | [[package]] 985 | name = "phf_generator" 986 | version = "0.11.2" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" 989 | dependencies = [ 990 | "phf_shared", 991 | "rand", 992 | ] 993 | 994 | [[package]] 995 | name = "phf_shared" 996 | version = "0.11.2" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" 999 | dependencies = [ 1000 | "siphasher", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "pin-project-lite" 1005 | version = "0.2.15" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 1008 | 1009 | [[package]] 1010 | name = "portable-atomic" 1011 | version = "1.10.0" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" 1014 | 1015 | [[package]] 1016 | name = "pretty_env_logger" 1017 | version = "0.5.0" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" 1020 | dependencies = [ 1021 | "env_logger 0.10.2", 1022 | "log", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "proc-macro2" 1027 | version = "1.0.92" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 1030 | dependencies = [ 1031 | "unicode-ident", 1032 | ] 1033 | 1034 | [[package]] 1035 | name = "quote" 1036 | version = "1.0.37" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 1039 | dependencies = [ 1040 | "proc-macro2", 1041 | ] 1042 | 1043 | [[package]] 1044 | name = "rand" 1045 | version = "0.8.5" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1048 | dependencies = [ 1049 | "rand_core", 1050 | ] 1051 | 1052 | [[package]] 1053 | name = "rand_core" 1054 | version = "0.6.4" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1057 | 1058 | [[package]] 1059 | name = "redox_syscall" 1060 | version = "0.5.8" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" 1063 | dependencies = [ 1064 | "bitflags", 1065 | ] 1066 | 1067 | [[package]] 1068 | name = "regex" 1069 | version = "1.11.1" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1072 | dependencies = [ 1073 | "aho-corasick", 1074 | "memchr", 1075 | "regex-automata", 1076 | "regex-syntax", 1077 | ] 1078 | 1079 | [[package]] 1080 | name = "regex-automata" 1081 | version = "0.4.9" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1084 | dependencies = [ 1085 | "aho-corasick", 1086 | "memchr", 1087 | "regex-syntax", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "regex-syntax" 1092 | version = "0.8.5" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1095 | 1096 | [[package]] 1097 | name = "rustc-demangle" 1098 | version = "0.1.24" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1101 | 1102 | [[package]] 1103 | name = "rustix" 1104 | version = "0.38.42" 1105 | source = "registry+https://github.com/rust-lang/crates.io-index" 1106 | checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" 1107 | dependencies = [ 1108 | "bitflags", 1109 | "errno", 1110 | "libc", 1111 | "linux-raw-sys", 1112 | "windows-sys 0.59.0", 1113 | ] 1114 | 1115 | [[package]] 1116 | name = "ryu" 1117 | version = "1.0.18" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1120 | 1121 | [[package]] 1122 | name = "scopeguard" 1123 | version = "1.2.0" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1126 | 1127 | [[package]] 1128 | name = "serde" 1129 | version = "1.0.216" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" 1132 | dependencies = [ 1133 | "serde_derive", 1134 | ] 1135 | 1136 | [[package]] 1137 | name = "serde_derive" 1138 | version = "1.0.216" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" 1141 | dependencies = [ 1142 | "proc-macro2", 1143 | "quote", 1144 | "syn 2.0.90", 1145 | ] 1146 | 1147 | [[package]] 1148 | name = "serde_json" 1149 | version = "1.0.133" 1150 | source = "registry+https://github.com/rust-lang/crates.io-index" 1151 | checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" 1152 | dependencies = [ 1153 | "itoa", 1154 | "memchr", 1155 | "ryu", 1156 | "serde", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "sharded-slab" 1161 | version = "0.1.7" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1164 | dependencies = [ 1165 | "lazy_static", 1166 | ] 1167 | 1168 | [[package]] 1169 | name = "shell-words" 1170 | version = "1.1.0" 1171 | source = "registry+https://github.com/rust-lang/crates.io-index" 1172 | checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" 1173 | 1174 | [[package]] 1175 | name = "shlex" 1176 | version = "1.3.0" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1179 | 1180 | [[package]] 1181 | name = "signal-hook" 1182 | version = "0.3.17" 1183 | source = "registry+https://github.com/rust-lang/crates.io-index" 1184 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 1185 | dependencies = [ 1186 | "libc", 1187 | "signal-hook-registry", 1188 | ] 1189 | 1190 | [[package]] 1191 | name = "signal-hook-mio" 1192 | version = "0.2.4" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 1195 | dependencies = [ 1196 | "libc", 1197 | "mio", 1198 | "signal-hook", 1199 | ] 1200 | 1201 | [[package]] 1202 | name = "signal-hook-registry" 1203 | version = "1.4.2" 1204 | source = "registry+https://github.com/rust-lang/crates.io-index" 1205 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1206 | dependencies = [ 1207 | "libc", 1208 | ] 1209 | 1210 | [[package]] 1211 | name = "simple-bytes" 1212 | version = "0.2.14" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "c11532d9d241904f095185f35dcdaf930b1427a94d5b01d7002d74ba19b44cc4" 1215 | 1216 | [[package]] 1217 | name = "siphasher" 1218 | version = "0.3.11" 1219 | source = "registry+https://github.com/rust-lang/crates.io-index" 1220 | checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 1221 | 1222 | [[package]] 1223 | name = "smallvec" 1224 | version = "1.13.2" 1225 | source = "registry+https://github.com/rust-lang/crates.io-index" 1226 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1227 | 1228 | [[package]] 1229 | name = "smawk" 1230 | version = "0.3.2" 1231 | source = "registry+https://github.com/rust-lang/crates.io-index" 1232 | checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" 1233 | 1234 | [[package]] 1235 | name = "stable_deref_trait" 1236 | version = "1.2.0" 1237 | source = "registry+https://github.com/rust-lang/crates.io-index" 1238 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1239 | 1240 | [[package]] 1241 | name = "strsim" 1242 | version = "0.11.1" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1245 | 1246 | [[package]] 1247 | name = "superblock" 1248 | version = "0.1.0" 1249 | source = "git+https://github.com/serpent-os/blsforme.git#5076b78850231a21431727fefedd16aaa9d87ee9" 1250 | dependencies = [ 1251 | "log", 1252 | "thiserror 2.0.7", 1253 | "uuid", 1254 | ] 1255 | 1256 | [[package]] 1257 | name = "syn" 1258 | version = "1.0.109" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1261 | dependencies = [ 1262 | "proc-macro2", 1263 | "quote", 1264 | "unicode-ident", 1265 | ] 1266 | 1267 | [[package]] 1268 | name = "syn" 1269 | version = "2.0.90" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" 1272 | dependencies = [ 1273 | "proc-macro2", 1274 | "quote", 1275 | "unicode-ident", 1276 | ] 1277 | 1278 | [[package]] 1279 | name = "synstructure" 1280 | version = "0.13.1" 1281 | source = "registry+https://github.com/rust-lang/crates.io-index" 1282 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1283 | dependencies = [ 1284 | "proc-macro2", 1285 | "quote", 1286 | "syn 2.0.90", 1287 | ] 1288 | 1289 | [[package]] 1290 | name = "system" 1291 | version = "0.1.0" 1292 | dependencies = [ 1293 | "fs-err", 1294 | "gpt 4.0.0", 1295 | "serde", 1296 | "serde_json", 1297 | "superblock", 1298 | "thiserror 2.0.7", 1299 | ] 1300 | 1301 | [[package]] 1302 | name = "tempfile" 1303 | version = "3.14.0" 1304 | source = "registry+https://github.com/rust-lang/crates.io-index" 1305 | checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" 1306 | dependencies = [ 1307 | "cfg-if 1.0.0", 1308 | "fastrand", 1309 | "once_cell", 1310 | "rustix", 1311 | "windows-sys 0.59.0", 1312 | ] 1313 | 1314 | [[package]] 1315 | name = "termcolor" 1316 | version = "1.4.1" 1317 | source = "registry+https://github.com/rust-lang/crates.io-index" 1318 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 1319 | dependencies = [ 1320 | "winapi-util", 1321 | ] 1322 | 1323 | [[package]] 1324 | name = "termsize" 1325 | version = "0.1.9" 1326 | source = "registry+https://github.com/rust-lang/crates.io-index" 1327 | checksum = "6f11ff5c25c172608d5b85e2fb43ee9a6d683a7f4ab7f96ae07b3d8b590368fd" 1328 | dependencies = [ 1329 | "libc", 1330 | "winapi", 1331 | ] 1332 | 1333 | [[package]] 1334 | name = "textwrap" 1335 | version = "0.16.1" 1336 | source = "registry+https://github.com/rust-lang/crates.io-index" 1337 | checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" 1338 | dependencies = [ 1339 | "smawk", 1340 | "unicode-linebreak", 1341 | "unicode-width 0.1.14", 1342 | ] 1343 | 1344 | [[package]] 1345 | name = "thiserror" 1346 | version = "1.0.69" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1349 | dependencies = [ 1350 | "thiserror-impl 1.0.69", 1351 | ] 1352 | 1353 | [[package]] 1354 | name = "thiserror" 1355 | version = "2.0.7" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767" 1358 | dependencies = [ 1359 | "thiserror-impl 2.0.7", 1360 | ] 1361 | 1362 | [[package]] 1363 | name = "thiserror-impl" 1364 | version = "1.0.69" 1365 | source = "registry+https://github.com/rust-lang/crates.io-index" 1366 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1367 | dependencies = [ 1368 | "proc-macro2", 1369 | "quote", 1370 | "syn 2.0.90", 1371 | ] 1372 | 1373 | [[package]] 1374 | name = "thiserror-impl" 1375 | version = "2.0.7" 1376 | source = "registry+https://github.com/rust-lang/crates.io-index" 1377 | checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36" 1378 | dependencies = [ 1379 | "proc-macro2", 1380 | "quote", 1381 | "syn 2.0.90", 1382 | ] 1383 | 1384 | [[package]] 1385 | name = "thread_local" 1386 | version = "1.1.8" 1387 | source = "registry+https://github.com/rust-lang/crates.io-index" 1388 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1389 | dependencies = [ 1390 | "cfg-if 1.0.0", 1391 | "once_cell", 1392 | ] 1393 | 1394 | [[package]] 1395 | name = "tinystr" 1396 | version = "0.7.6" 1397 | source = "registry+https://github.com/rust-lang/crates.io-index" 1398 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1399 | dependencies = [ 1400 | "displaydoc", 1401 | "zerovec", 1402 | ] 1403 | 1404 | [[package]] 1405 | name = "tokio" 1406 | version = "1.42.0" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" 1409 | dependencies = [ 1410 | "backtrace", 1411 | "pin-project-lite", 1412 | ] 1413 | 1414 | [[package]] 1415 | name = "topology" 1416 | version = "0.1.0" 1417 | source = "git+https://github.com/serpent-os/blsforme.git#5076b78850231a21431727fefedd16aaa9d87ee9" 1418 | dependencies = [ 1419 | "gpt 3.1.0", 1420 | "log", 1421 | "nix", 1422 | "superblock", 1423 | "thiserror 2.0.7", 1424 | ] 1425 | 1426 | [[package]] 1427 | name = "tracing" 1428 | version = "0.1.41" 1429 | source = "registry+https://github.com/rust-lang/crates.io-index" 1430 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1431 | dependencies = [ 1432 | "pin-project-lite", 1433 | "tracing-core", 1434 | ] 1435 | 1436 | [[package]] 1437 | name = "tracing-core" 1438 | version = "0.1.33" 1439 | source = "registry+https://github.com/rust-lang/crates.io-index" 1440 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 1441 | dependencies = [ 1442 | "once_cell", 1443 | "valuable", 1444 | ] 1445 | 1446 | [[package]] 1447 | name = "tracing-error" 1448 | version = "0.2.1" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" 1451 | dependencies = [ 1452 | "tracing", 1453 | "tracing-subscriber", 1454 | ] 1455 | 1456 | [[package]] 1457 | name = "tracing-subscriber" 1458 | version = "0.3.19" 1459 | source = "registry+https://github.com/rust-lang/crates.io-index" 1460 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 1461 | dependencies = [ 1462 | "sharded-slab", 1463 | "thread_local", 1464 | "tracing-core", 1465 | ] 1466 | 1467 | [[package]] 1468 | name = "uds_windows" 1469 | version = "1.1.0" 1470 | source = "registry+https://github.com/rust-lang/crates.io-index" 1471 | checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" 1472 | dependencies = [ 1473 | "memoffset", 1474 | "tempfile", 1475 | "winapi", 1476 | ] 1477 | 1478 | [[package]] 1479 | name = "unicode-ident" 1480 | version = "1.0.14" 1481 | source = "registry+https://github.com/rust-lang/crates.io-index" 1482 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 1483 | 1484 | [[package]] 1485 | name = "unicode-linebreak" 1486 | version = "0.1.5" 1487 | source = "registry+https://github.com/rust-lang/crates.io-index" 1488 | checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" 1489 | 1490 | [[package]] 1491 | name = "unicode-width" 1492 | version = "0.1.14" 1493 | source = "registry+https://github.com/rust-lang/crates.io-index" 1494 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1495 | 1496 | [[package]] 1497 | name = "unicode-width" 1498 | version = "0.2.0" 1499 | source = "registry+https://github.com/rust-lang/crates.io-index" 1500 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 1501 | 1502 | [[package]] 1503 | name = "unix_socket" 1504 | version = "0.5.0" 1505 | source = "registry+https://github.com/rust-lang/crates.io-index" 1506 | checksum = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564" 1507 | dependencies = [ 1508 | "cfg-if 0.1.10", 1509 | "libc", 1510 | ] 1511 | 1512 | [[package]] 1513 | name = "url" 1514 | version = "2.5.4" 1515 | source = "registry+https://github.com/rust-lang/crates.io-index" 1516 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 1517 | dependencies = [ 1518 | "form_urlencoded", 1519 | "idna", 1520 | "percent-encoding", 1521 | ] 1522 | 1523 | [[package]] 1524 | name = "utf16_iter" 1525 | version = "1.0.5" 1526 | source = "registry+https://github.com/rust-lang/crates.io-index" 1527 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 1528 | 1529 | [[package]] 1530 | name = "utf8_iter" 1531 | version = "1.0.4" 1532 | source = "registry+https://github.com/rust-lang/crates.io-index" 1533 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1534 | 1535 | [[package]] 1536 | name = "utf8parse" 1537 | version = "0.2.2" 1538 | source = "registry+https://github.com/rust-lang/crates.io-index" 1539 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1540 | 1541 | [[package]] 1542 | name = "uuid" 1543 | version = "1.11.0" 1544 | source = "registry+https://github.com/rust-lang/crates.io-index" 1545 | checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" 1546 | dependencies = [ 1547 | "getrandom", 1548 | ] 1549 | 1550 | [[package]] 1551 | name = "valuable" 1552 | version = "0.1.0" 1553 | source = "registry+https://github.com/rust-lang/crates.io-index" 1554 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1555 | 1556 | [[package]] 1557 | name = "varlink" 1558 | version = "11.0.1" 1559 | source = "registry+https://github.com/rust-lang/crates.io-index" 1560 | checksum = "409e275987d74665c23610c0959c133360cafd761c1a6ddb1ca6d0685c8cef5d" 1561 | dependencies = [ 1562 | "libc", 1563 | "serde", 1564 | "serde_derive", 1565 | "serde_json", 1566 | "tempfile", 1567 | "uds_windows", 1568 | "unix_socket", 1569 | "winapi", 1570 | ] 1571 | 1572 | [[package]] 1573 | name = "varlink_generator" 1574 | version = "10.1.0" 1575 | source = "registry+https://github.com/rust-lang/crates.io-index" 1576 | checksum = "2d8ff746c5b65d4bfb3a50f630b85cfb6a9d59f18720126e3ebd6bc98527fa51" 1577 | dependencies = [ 1578 | "chainerror", 1579 | "getopts", 1580 | "proc-macro2", 1581 | "quote", 1582 | "syn 1.0.109", 1583 | "varlink_parser", 1584 | ] 1585 | 1586 | [[package]] 1587 | name = "varlink_parser" 1588 | version = "4.2.0" 1589 | source = "registry+https://github.com/rust-lang/crates.io-index" 1590 | checksum = "35fb9f3c1e8ccb33cdb6c84a4477ef3f3884ce6f4b70514ef1fbf7686eae921e" 1591 | dependencies = [ 1592 | "ansi_term", 1593 | "chainerror", 1594 | "peg", 1595 | ] 1596 | 1597 | [[package]] 1598 | name = "wasi" 1599 | version = "0.11.0+wasi-snapshot-preview1" 1600 | source = "registry+https://github.com/rust-lang/crates.io-index" 1601 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1602 | 1603 | [[package]] 1604 | name = "wasm-bindgen" 1605 | version = "0.2.99" 1606 | source = "registry+https://github.com/rust-lang/crates.io-index" 1607 | checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" 1608 | dependencies = [ 1609 | "cfg-if 1.0.0", 1610 | "once_cell", 1611 | "wasm-bindgen-macro", 1612 | ] 1613 | 1614 | [[package]] 1615 | name = "wasm-bindgen-backend" 1616 | version = "0.2.99" 1617 | source = "registry+https://github.com/rust-lang/crates.io-index" 1618 | checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" 1619 | dependencies = [ 1620 | "bumpalo", 1621 | "log", 1622 | "proc-macro2", 1623 | "quote", 1624 | "syn 2.0.90", 1625 | "wasm-bindgen-shared", 1626 | ] 1627 | 1628 | [[package]] 1629 | name = "wasm-bindgen-macro" 1630 | version = "0.2.99" 1631 | source = "registry+https://github.com/rust-lang/crates.io-index" 1632 | checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" 1633 | dependencies = [ 1634 | "quote", 1635 | "wasm-bindgen-macro-support", 1636 | ] 1637 | 1638 | [[package]] 1639 | name = "wasm-bindgen-macro-support" 1640 | version = "0.2.99" 1641 | source = "registry+https://github.com/rust-lang/crates.io-index" 1642 | checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" 1643 | dependencies = [ 1644 | "proc-macro2", 1645 | "quote", 1646 | "syn 2.0.90", 1647 | "wasm-bindgen-backend", 1648 | "wasm-bindgen-shared", 1649 | ] 1650 | 1651 | [[package]] 1652 | name = "wasm-bindgen-shared" 1653 | version = "0.2.99" 1654 | source = "registry+https://github.com/rust-lang/crates.io-index" 1655 | checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" 1656 | 1657 | [[package]] 1658 | name = "web-time" 1659 | version = "1.1.0" 1660 | source = "registry+https://github.com/rust-lang/crates.io-index" 1661 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 1662 | dependencies = [ 1663 | "js-sys", 1664 | "wasm-bindgen", 1665 | ] 1666 | 1667 | [[package]] 1668 | name = "winapi" 1669 | version = "0.3.9" 1670 | source = "registry+https://github.com/rust-lang/crates.io-index" 1671 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1672 | dependencies = [ 1673 | "winapi-i686-pc-windows-gnu", 1674 | "winapi-x86_64-pc-windows-gnu", 1675 | ] 1676 | 1677 | [[package]] 1678 | name = "winapi-i686-pc-windows-gnu" 1679 | version = "0.4.0" 1680 | source = "registry+https://github.com/rust-lang/crates.io-index" 1681 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1682 | 1683 | [[package]] 1684 | name = "winapi-util" 1685 | version = "0.1.9" 1686 | source = "registry+https://github.com/rust-lang/crates.io-index" 1687 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 1688 | dependencies = [ 1689 | "windows-sys 0.59.0", 1690 | ] 1691 | 1692 | [[package]] 1693 | name = "winapi-x86_64-pc-windows-gnu" 1694 | version = "0.4.0" 1695 | source = "registry+https://github.com/rust-lang/crates.io-index" 1696 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1697 | 1698 | [[package]] 1699 | name = "windows-sys" 1700 | version = "0.52.0" 1701 | source = "registry+https://github.com/rust-lang/crates.io-index" 1702 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1703 | dependencies = [ 1704 | "windows-targets", 1705 | ] 1706 | 1707 | [[package]] 1708 | name = "windows-sys" 1709 | version = "0.59.0" 1710 | source = "registry+https://github.com/rust-lang/crates.io-index" 1711 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1712 | dependencies = [ 1713 | "windows-targets", 1714 | ] 1715 | 1716 | [[package]] 1717 | name = "windows-targets" 1718 | version = "0.52.6" 1719 | source = "registry+https://github.com/rust-lang/crates.io-index" 1720 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1721 | dependencies = [ 1722 | "windows_aarch64_gnullvm", 1723 | "windows_aarch64_msvc", 1724 | "windows_i686_gnu", 1725 | "windows_i686_gnullvm", 1726 | "windows_i686_msvc", 1727 | "windows_x86_64_gnu", 1728 | "windows_x86_64_gnullvm", 1729 | "windows_x86_64_msvc", 1730 | ] 1731 | 1732 | [[package]] 1733 | name = "windows_aarch64_gnullvm" 1734 | version = "0.52.6" 1735 | source = "registry+https://github.com/rust-lang/crates.io-index" 1736 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1737 | 1738 | [[package]] 1739 | name = "windows_aarch64_msvc" 1740 | version = "0.52.6" 1741 | source = "registry+https://github.com/rust-lang/crates.io-index" 1742 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1743 | 1744 | [[package]] 1745 | name = "windows_i686_gnu" 1746 | version = "0.52.6" 1747 | source = "registry+https://github.com/rust-lang/crates.io-index" 1748 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1749 | 1750 | [[package]] 1751 | name = "windows_i686_gnullvm" 1752 | version = "0.52.6" 1753 | source = "registry+https://github.com/rust-lang/crates.io-index" 1754 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1755 | 1756 | [[package]] 1757 | name = "windows_i686_msvc" 1758 | version = "0.52.6" 1759 | source = "registry+https://github.com/rust-lang/crates.io-index" 1760 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1761 | 1762 | [[package]] 1763 | name = "windows_x86_64_gnu" 1764 | version = "0.52.6" 1765 | source = "registry+https://github.com/rust-lang/crates.io-index" 1766 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1767 | 1768 | [[package]] 1769 | name = "windows_x86_64_gnullvm" 1770 | version = "0.52.6" 1771 | source = "registry+https://github.com/rust-lang/crates.io-index" 1772 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1773 | 1774 | [[package]] 1775 | name = "windows_x86_64_msvc" 1776 | version = "0.52.6" 1777 | source = "registry+https://github.com/rust-lang/crates.io-index" 1778 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1779 | 1780 | [[package]] 1781 | name = "write16" 1782 | version = "1.0.0" 1783 | source = "registry+https://github.com/rust-lang/crates.io-index" 1784 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 1785 | 1786 | [[package]] 1787 | name = "writeable" 1788 | version = "0.5.5" 1789 | source = "registry+https://github.com/rust-lang/crates.io-index" 1790 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 1791 | 1792 | [[package]] 1793 | name = "yoke" 1794 | version = "0.7.5" 1795 | source = "registry+https://github.com/rust-lang/crates.io-index" 1796 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 1797 | dependencies = [ 1798 | "serde", 1799 | "stable_deref_trait", 1800 | "yoke-derive", 1801 | "zerofrom", 1802 | ] 1803 | 1804 | [[package]] 1805 | name = "yoke-derive" 1806 | version = "0.7.5" 1807 | source = "registry+https://github.com/rust-lang/crates.io-index" 1808 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 1809 | dependencies = [ 1810 | "proc-macro2", 1811 | "quote", 1812 | "syn 2.0.90", 1813 | "synstructure", 1814 | ] 1815 | 1816 | [[package]] 1817 | name = "zerofrom" 1818 | version = "0.1.5" 1819 | source = "registry+https://github.com/rust-lang/crates.io-index" 1820 | checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" 1821 | dependencies = [ 1822 | "zerofrom-derive", 1823 | ] 1824 | 1825 | [[package]] 1826 | name = "zerofrom-derive" 1827 | version = "0.1.5" 1828 | source = "registry+https://github.com/rust-lang/crates.io-index" 1829 | checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" 1830 | dependencies = [ 1831 | "proc-macro2", 1832 | "quote", 1833 | "syn 2.0.90", 1834 | "synstructure", 1835 | ] 1836 | 1837 | [[package]] 1838 | name = "zeroize" 1839 | version = "1.8.1" 1840 | source = "registry+https://github.com/rust-lang/crates.io-index" 1841 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 1842 | dependencies = [ 1843 | "zeroize_derive", 1844 | ] 1845 | 1846 | [[package]] 1847 | name = "zeroize_derive" 1848 | version = "1.4.2" 1849 | source = "registry+https://github.com/rust-lang/crates.io-index" 1850 | checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" 1851 | dependencies = [ 1852 | "proc-macro2", 1853 | "quote", 1854 | "syn 2.0.90", 1855 | ] 1856 | 1857 | [[package]] 1858 | name = "zerovec" 1859 | version = "0.10.4" 1860 | source = "registry+https://github.com/rust-lang/crates.io-index" 1861 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 1862 | dependencies = [ 1863 | "yoke", 1864 | "zerofrom", 1865 | "zerovec-derive", 1866 | ] 1867 | 1868 | [[package]] 1869 | name = "zerovec-derive" 1870 | version = "0.10.3" 1871 | source = "registry+https://github.com/rust-lang/crates.io-index" 1872 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 1873 | dependencies = [ 1874 | "proc-macro2", 1875 | "quote", 1876 | "syn 2.0.90", 1877 | ] 1878 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "crates/*", 4 | "lichen_cli", 5 | "lichen_ipc" 6 | ] 7 | default-members = [ 8 | "lichen_cli" 9 | ] 10 | resolver = "2" 11 | 12 | [workspace.dependencies] 13 | bitflags = "2.6.0" 14 | chrono-tz = "0.10.0" 15 | color-eyre = { version = "0.6.3", features = ["issue-url"] } 16 | crossterm = { version = "0.28.1", features = ["serde", "event-stream"] } 17 | env_logger = "0.11.5" 18 | fs-err = { version = "3.0.0", features = ["tokio"] } 19 | human_bytes = "0.4.3" 20 | libc = "0.2.155" 21 | log = "0.4.22" 22 | nix = "0.29" 23 | serde = { version = "1.0.204", features = ["derive"] } 24 | serde_derive = "1.0.204" 25 | serde_json = "1.0.120" 26 | superblock = { git = "https://github.com/serpent-os/blsforme.git"} 27 | thiserror = "2.0.3" 28 | topology = { git = "https://github.com/serpent-os/blsforme.git"} 29 | varlink = { version = "11.0.1" } 30 | varlink_generator = { version = "10.1.0 "} 31 | 32 | [workspace.lints.rust] 33 | rust_2018_idioms = { level = "warn", priority = -1 } 34 | semicolon_in_expressions_from_macros = "warn" 35 | unused_import_braces = "warn" 36 | unused_qualifications = "warn" 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at https://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /LICENSES/MPL-2.0.txt: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at https://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lichen 2 | 3 | A heavily in-development, experimental, and early days installer for Serpent OS to meet a deadline. 4 | 5 | ## Build & Test 6 | 7 | cargo build 8 | sudo ./target/debug/lichen 9 | 10 | To quit the installer, press `ESC` to switch to command mode, then press `q`. 11 | 12 | ## License 13 | 14 | `lichen` is available under the terms of the [MPL-2.0](https://spdx.org/licenses/MPL-2.0.html) 15 | -------------------------------------------------------------------------------- /crates/installer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "installer" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | libc.workspace = true 8 | log.workspace = true 9 | human_bytes.workspace = true 10 | serde.workspace = true 11 | serde_json.workspace = true 12 | system = { path = "../system" } 13 | thiserror.workspace = true 14 | topology.workspace = true 15 | fs-err.workspace = true 16 | 17 | [lints] 18 | workspace = true 19 | -------------------------------------------------------------------------------- /crates/installer/README.md: -------------------------------------------------------------------------------- 1 | # installer 2 | 3 | This crate provides the core model of the installer, absorbing the various features of `system` detection and a way to execute the steps. 4 | -------------------------------------------------------------------------------- /crates/installer/src/account.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | /// Identifies an account 6 | #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)] 7 | pub struct Account { 8 | /// User ID 9 | pub uid: libc::uid_t, 10 | 11 | /// Group ID 12 | pub gid: libc::gid_t, 13 | 14 | /// Account name 15 | pub username: String, 16 | 17 | /// Human username string 18 | pub gecos: Option, 19 | 20 | /// Home directory 21 | pub homedir: String, 22 | 23 | /// Which shell to use 24 | pub shell: String, 25 | 26 | /// New password 27 | pub password: Option, 28 | 29 | /// Builtin user? (root) 30 | pub builtin: bool, 31 | } 32 | 33 | impl Default for Account { 34 | fn default() -> Self { 35 | Self { 36 | uid: 1000, 37 | gid: 1000, 38 | username: "user".into(), 39 | gecos: None, 40 | homedir: "/home/user".into(), 41 | shell: "/bin/bash".into(), 42 | password: None, 43 | builtin: false, 44 | } 45 | } 46 | } 47 | 48 | impl Account { 49 | /// Return an account definition for the root account 50 | pub fn root() -> Self { 51 | Self { 52 | uid: 0, 53 | gid: 0, 54 | username: "root".to_string(), 55 | homedir: "/root".to_string(), 56 | builtin: true, 57 | ..Default::default() 58 | } 59 | } 60 | 61 | /// New account with the given username 62 | pub fn new>(username: S) -> Self { 63 | Self { 64 | username: username.as_ref().to_string(), 65 | ..Default::default() 66 | } 67 | } 68 | 69 | /// Update the IDs 70 | pub fn with_id(self, uid: libc::uid_t, gid: libc::gid_t) -> Self { 71 | Self { uid, gid, ..self } 72 | } 73 | 74 | /// Update the gecos 75 | pub fn with_gecos>(self, gecos: S) -> Self { 76 | Self { 77 | gecos: Some(gecos.as_ref().to_string()), 78 | ..self 79 | } 80 | } 81 | 82 | /// Update the shell 83 | pub fn with_shell>(self, shell: S) -> Self { 84 | Self { 85 | shell: shell.as_ref().to_string(), 86 | ..self 87 | } 88 | } 89 | 90 | /// Update the password 91 | pub fn with_password>(self, p: P) -> Self { 92 | Self { 93 | password: Some(p.as_ref().to_string()), 94 | ..self 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /crates/installer/src/engine.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! Concrete implementation of the isntaller 6 | 7 | use std::path::Path; 8 | 9 | use system::{ 10 | disk::{self, Disk}, 11 | locale::{self, Locale}, 12 | }; 13 | use thiserror::Error; 14 | use topology::disk::Builder; 15 | 16 | use crate::{ 17 | steps::{ 18 | self, AddRepo, BindMount, Cleanup, Context, CreateAccount, EmitFstab, FormatPartition, FstabEntry, 19 | InstallPackages, MountPartition, SetLocale, SetPassword, Step, Unmount, 20 | }, 21 | BootPartition, Model, SystemPartition, 22 | }; 23 | 24 | #[derive(Debug, Error)] 25 | pub enum Error { 26 | #[error("disk: {0}")] 27 | Disk(#[from] disk::Error), 28 | 29 | #[error("locale: {0}")] 30 | Locale(#[from] locale::Error), 31 | 32 | #[error("missing mandatory partition: {0}")] 33 | MissingPartition(&'static str), 34 | 35 | #[error("steps: {0}")] 36 | Steps(#[from] steps::Error), 37 | 38 | #[error("unknown locale code: {0}")] 39 | UnknownLocale(String), 40 | 41 | #[error("topology: {0}")] 42 | Topology(#[from] topology::disk::Error), 43 | } 44 | 45 | /// The installer does some initial probing and is used with a Model 46 | /// to build an execution routine 47 | pub struct Installer { 48 | /// Complete locale registry 49 | locale_registry: locale::Registry, 50 | 51 | /// Boot partitions 52 | boot_parts: Vec, 53 | 54 | /// System partitions 55 | system_parts: Vec, 56 | } 57 | 58 | impl Installer { 59 | /// Return a newly initialised installer 60 | pub fn new() -> Result { 61 | let locale_registry = locale::Registry::new()?; 62 | let disks = Disk::discover()?; 63 | 64 | // Figure out where we live right now and exclude the rootfs 65 | let probe = Builder::default().build()?; 66 | let root_nodes = match probe.get_rootfs_device("/") { 67 | Ok(device) => { 68 | let mut nodes = probe.get_device_chain(&device.path).unwrap_or_default(); 69 | nodes.push(device.path.into()); 70 | nodes 71 | } 72 | _ => { 73 | vec![] 74 | } 75 | }; 76 | 77 | // Exclude parent block devices related to `/` partition 78 | let parents = root_nodes 79 | .iter() 80 | .filter_map(|n| probe.get_device_parent(n)) 81 | .collect::>(); 82 | 83 | let mut boot_parts = vec![]; 84 | let mut system_parts = vec![]; 85 | for disk in disks.iter().filter(|d| !parents.iter().any(|r| *r == d.path)) { 86 | let parts = match disk.partitions() { 87 | Ok(parts) => parts, 88 | Err(e) => { 89 | log::trace!("Failed to get partitions for `{disk}`: {e}"); 90 | continue; 91 | } 92 | }; 93 | 94 | // Exclude partitions related to `/` partition 95 | let parts = parts 96 | .into_iter() 97 | .filter(|p| !root_nodes.iter().any(|r| *r == p.path)) 98 | .collect::>(); 99 | if let Some(esp) = parts 100 | .iter() 101 | .find(|p| matches!(p.kind, disk::PartitionKind::ESP)) 102 | .cloned() 103 | { 104 | let xbootldr = parts 105 | .iter() 106 | .find(|p| matches!(p.kind, disk::PartitionKind::XBOOTLDR)) 107 | .cloned(); 108 | boot_parts.push(BootPartition { 109 | esp, 110 | xbootldr, 111 | parent_desc: disk.to_string(), 112 | }) 113 | } 114 | let others = parts 115 | .iter() 116 | .filter(|p| matches!(p.kind, disk::PartitionKind::Regular)) 117 | .cloned(); 118 | system_parts.extend(others.map(|p| SystemPartition { 119 | partition: p, 120 | mountpoint: None, 121 | parent_desc: disk.to_string(), 122 | })); 123 | } 124 | 125 | Ok(Self { 126 | locale_registry, 127 | system_parts, 128 | boot_parts, 129 | }) 130 | } 131 | 132 | /// Allow access to locale registry (mapping IDs) 133 | pub fn locales(&self) -> &locale::Registry { 134 | &self.locale_registry 135 | } 136 | 137 | /// Generate/load the locale map 138 | pub fn locales_for_ids>>(&self, ids: S) -> Result>, Error> { 139 | let res = ids 140 | .into_iter() 141 | .filter_map(|id| self.locale_registry.locale(id)) 142 | .collect::>(); 143 | 144 | Ok(res) 145 | } 146 | 147 | /// Return references to the discovered boot partitions 148 | pub fn boot_partitions(&self) -> &[BootPartition] { 149 | &self.boot_parts 150 | } 151 | 152 | /// Return references to the discovered system partitions 153 | pub fn system_partitions(&self) -> &[SystemPartition] { 154 | &self.system_parts 155 | } 156 | 157 | /// build the model into a set of install steps 158 | pub fn compile_to_steps<'a>( 159 | &'a self, 160 | model: &'a Model<'_>, 161 | context: &'a impl Context<'a>, 162 | ) -> Result<(Vec, Vec>), Error> { 163 | let mut s: Vec> = vec![]; 164 | let mut c: Vec = vec![]; 165 | let boot_part = &model.boot_partition.esp; 166 | 167 | let root_partition = model 168 | .partitions 169 | .iter() 170 | .find(|p| { 171 | if let Some(mount) = p.mountpoint.as_ref() { 172 | mount == "/" 173 | } else { 174 | false 175 | } 176 | }) 177 | .ok_or(Error::MissingPartition("/"))?; 178 | 179 | // Must format and mount `/` before we can add more mounts 180 | s.push(Step::format(FormatPartition { 181 | partition: &root_partition.partition, 182 | filesystem: model.rootfs_type.clone(), 183 | })); 184 | s.push(Step::mount(MountPartition { 185 | partition: &root_partition.partition, 186 | mountpoint: context.root().clone(), 187 | })); 188 | c.push(Cleanup::unmount(Unmount { 189 | mountpoint: context.root().clone(), 190 | })); 191 | 192 | // Mount the ESP 193 | s.push(Step::mount(MountPartition { 194 | partition: boot_part, 195 | mountpoint: context.root().join("efi"), 196 | })); 197 | c.push(Cleanup::unmount(Unmount { 198 | mountpoint: context.root().join("efi"), 199 | })); 200 | 201 | // Mount xbootldr at `/boot` if present 202 | if let Some(xbootldr) = model.boot_partition.xbootldr.as_ref() { 203 | s.push(Step::mount(MountPartition { 204 | partition: xbootldr, 205 | mountpoint: context.root().join("boot"), 206 | })); 207 | c.push(Cleanup::unmount(Unmount { 208 | mountpoint: context.root().join("boot"), 209 | })); 210 | }; 211 | 212 | // Populate vfs bind mounts 213 | let (mounts, unmounts) = self.create_vfs_mounts(context.root()); 214 | s.extend(mounts); 215 | c.extend(unmounts); 216 | 217 | // HAX: 218 | s.push(Step::add_repo(AddRepo { 219 | uri: "https://dev.serpentos.com/volatile/x86_64/stone.index".into(), 220 | name: "unstable".into(), 221 | priority: 0, 222 | })); 223 | s.push(Step::install_packages(InstallPackages { 224 | names: model.packages.iter().cloned().collect::>(), 225 | })); 226 | 227 | // Update any passwords 228 | for account in model.accounts.iter() { 229 | if !account.builtin { 230 | s.push(Step::create_user(CreateAccount { account })); 231 | } 232 | if let Some(password) = account.password.clone() { 233 | s.push(Step::set_password(SetPassword { account, password })); 234 | } 235 | } 236 | 237 | // System locale 238 | if let Some(locale) = model.locale { 239 | s.push(Step::set_locale(SetLocale { locale })); 240 | } 241 | 242 | // System timezone 243 | if let Some(timezone) = model.timezone.as_ref() { 244 | s.push(Step::set_timezone(steps::SetTimezone { timezone })); 245 | } 246 | 247 | // Ensure we get a machine-id.. 248 | s.push(Step::set_machine_id()); 249 | 250 | // Write the fstab 251 | let fstab = EmitFstab::default().with_entries([ 252 | FstabEntry::Comment(format!( 253 | "{} at time of installation", 254 | root_partition.partition.path.display() 255 | )), 256 | FstabEntry::try_from(root_partition)?, 257 | ]); 258 | s.push(Step::emit_fstab(fstab)); 259 | 260 | // Get the sync call in for unmounts 261 | c.push(Cleanup::sync_fs()); 262 | // Lastly, flip cleanups to front in reverse (due to mounts) 263 | c.reverse(); 264 | Ok((c, s)) 265 | } 266 | 267 | fn create_vfs_mounts(&self, prefix: &Path) -> (Vec>, Vec) { 268 | const PARTS: &[(&str, &str); 5] = &[ 269 | ("/dev", "dev"), 270 | ("/dev/shm", "dev/shm"), 271 | ("/dev/pts", "dev/pts"), 272 | ("/proc", "proc"), 273 | ("/sys", "sys"), 274 | ]; 275 | PARTS 276 | .iter() 277 | .map(|(source, dest)| { 278 | ( 279 | Step::bind_mount(BindMount { 280 | source: source.into(), 281 | dest: prefix.join(dest), 282 | }), 283 | Cleanup::unmount(Unmount { 284 | mountpoint: prefix.join(dest), 285 | }), 286 | ) 287 | }) 288 | .collect::<(Vec<_>, Vec<_>)>() 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /crates/installer/src/lib.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! Lichen installer APIs 6 | 7 | mod model; 8 | 9 | pub use model::Model; 10 | 11 | mod account; 12 | pub use account::Account; 13 | 14 | mod engine; 15 | pub use engine::Installer; 16 | 17 | mod partitions; 18 | pub use partitions::{BootPartition, SystemPartition}; 19 | 20 | pub mod systemd; 21 | 22 | pub use system::locale::Locale; 23 | 24 | pub mod steps; 25 | 26 | pub mod selections; 27 | -------------------------------------------------------------------------------- /crates/installer/src/model.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | use std::collections::BTreeSet; 6 | 7 | use system::locale::Locale; 8 | 9 | use crate::{Account, BootPartition, SystemPartition}; 10 | 11 | /// Core model for the installation target 12 | #[derive(Debug)] 13 | pub struct Model<'a> { 14 | /// All accounts in the system. 15 | pub accounts: BTreeSet, 16 | 17 | /// The boot partition to use 18 | pub boot_partition: BootPartition, 19 | 20 | /// The system partitions to use/mount 21 | pub partitions: Vec, 22 | 23 | /// System locale to set 24 | pub locale: Option<&'a Locale<'a>>, 25 | 26 | /// Timezone ID 27 | pub timezone: Option, 28 | 29 | /// Package selections 30 | pub packages: BTreeSet, 31 | 32 | /// rootfs format 33 | pub rootfs_type: String, 34 | } 35 | -------------------------------------------------------------------------------- /crates/installer/src/partitions.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! A higher abstraction over partitions for the purposes of 6 | //! installer usage. 7 | //! Quite simply we only care about the difference in a regular 8 | //! partition, and a boot partition. 9 | 10 | use std::fmt::Display; 11 | 12 | use human_bytes::human_bytes; 13 | use system::disk; 14 | 15 | /// A boot partition is an EFI System Partition which may or may 16 | /// not be paired with an `XBOOTLDR` partition, relative to its location 17 | /// on the same GPT disk. 18 | /// This is a requirement per the Boot Loader Specification. 19 | #[derive(Debug, Clone)] 20 | pub struct BootPartition { 21 | pub(crate) esp: disk::Partition, 22 | pub(crate) xbootldr: Option, 23 | pub(crate) parent_desc: String, 24 | } 25 | 26 | impl Display for BootPartition { 27 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 28 | let opt_string = if let Some(xbootldr) = self.xbootldr.as_ref() { 29 | format!( 30 | "with XBOOTLDR {} ({}) ", 31 | xbootldr.path.display(), 32 | human_bytes(xbootldr.size as f64) 33 | ) 34 | } else { 35 | "".into() 36 | }; 37 | f.write_fmt(format_args!( 38 | "{} ({}) {}[on {}]", 39 | self.esp.path.display(), 40 | human_bytes(self.esp.size as f64), 41 | opt_string, 42 | self.parent_desc 43 | )) 44 | } 45 | } 46 | 47 | /// A system partition is simply a regular partition with a specified mountpoint 48 | /// within the root. 49 | #[derive(Debug, Clone)] 50 | pub struct SystemPartition { 51 | pub(crate) partition: disk::Partition, 52 | 53 | /// Where will it be mounted 54 | pub mountpoint: Option, 55 | 56 | pub(crate) parent_desc: String, 57 | } 58 | 59 | impl Display for SystemPartition { 60 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 61 | f.write_fmt(format_args!( 62 | "{} ({}) [on {}]", 63 | self.partition.path.display(), 64 | human_bytes(self.partition.size as f64), 65 | self.parent_desc 66 | )) 67 | } 68 | } 69 | 70 | impl AsRef for SystemPartition { 71 | fn as_ref(&self) -> &disk::Partition { 72 | &self.partition 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /crates/installer/src/selections.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! Deserialising of selections from JSON files 6 | 7 | use std::{ 8 | collections::{BTreeMap, BTreeSet}, 9 | fmt::Display, 10 | str::FromStr, 11 | }; 12 | 13 | use serde::Deserialize; 14 | use thiserror::Error; 15 | 16 | /// Selection handling errors 17 | #[derive(Debug, Error)] 18 | pub enum Error { 19 | #[error("serde: {0}")] 20 | Deserialize(#[from] serde_json::Error), 21 | 22 | #[error("unknown group")] 23 | UnknownGroup(String), 24 | } 25 | 26 | #[derive(Debug, Default, Deserialize)] 27 | pub struct Group { 28 | /// Simple list-selection name for this selection group 29 | pub name: String, 30 | 31 | /// User-visible summary for this selection group 32 | pub summary: String, 33 | 34 | /// User visible description for this selection group 35 | pub description: String, 36 | 37 | /// Optionally a set of selection groups forming the basis of this one 38 | #[serde(default)] 39 | pub depends: Vec, 40 | 41 | /// A set of package names (moss-encoded) that form this selection 42 | pub required: Vec, 43 | } 44 | 45 | impl Display for Group { 46 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 47 | f.write_str(&self.summary) 48 | } 49 | } 50 | 51 | impl FromStr for Group { 52 | type Err = Error; 53 | 54 | /// Encapsulate serde_json::from_str() 55 | fn from_str(s: &str) -> Result { 56 | let us = serde_json::from_str(s)?; 57 | Ok(us) 58 | } 59 | } 60 | 61 | /// Simple selections management 62 | #[derive(Default)] 63 | pub struct Manager { 64 | groups: BTreeMap, 65 | } 66 | 67 | impl Manager { 68 | /// convenience: new Manager 69 | pub fn new() -> Self { 70 | Self::default() 71 | } 72 | 73 | /// Take ownership of some groups 74 | pub fn with_groups>(self, groups: I) -> Self { 75 | Self { 76 | groups: groups 77 | .into_iter() 78 | .map(|g| (g.name.clone(), g)) 79 | .collect::>(), 80 | } 81 | } 82 | 83 | /// Add a group to the manager 84 | pub fn insert(&mut self, g: Group) { 85 | self.groups.insert(g.name.clone(), g); 86 | } 87 | 88 | /// Return an iterator of references to the groups 89 | pub fn groups(&self) -> impl Iterator { 90 | self.groups.values() 91 | } 92 | 93 | /// privatwly recurse for string deps 94 | fn get_deps(&self, name: &str) -> Result, Error> { 95 | let group = self.groups.get(name).ok_or_else(|| Error::UnknownGroup(name.into()))?; 96 | let mut depends = group.depends.clone(); 97 | 98 | // Recursively build parent deps 99 | for parent in group.depends.iter() { 100 | let parent_deps = self.get_deps(parent)?; 101 | depends.extend(parent_deps) 102 | } 103 | 104 | Ok(depends) 105 | } 106 | 107 | /// Given the selected IDs, what are the total selections? 108 | pub fn selections_with<'a, I: IntoIterator>(&'a self, ids: I) -> Result, Error> { 109 | let mut selected_ids = BTreeSet::new(); 110 | for item in ids.into_iter() { 111 | let deps = self.get_deps(item)?; 112 | selected_ids.extend(deps); 113 | selected_ids.insert(item.into()); 114 | } 115 | let core = selected_ids.into_iter().filter_map(|id| self.groups.get(&id)); 116 | Ok(core.flat_map(|g| g.required.clone()).collect::>()) 117 | } 118 | } 119 | 120 | #[cfg(test)] 121 | mod tests { 122 | use std::str::FromStr; 123 | 124 | use crate::selections::{Group, Manager}; 125 | 126 | #[test] 127 | fn test_decode() { 128 | let d = Group::from_str(include_str!("../../../selections/develop.json")).expect("Failed to decode base JSON"); 129 | let b = Group::from_str(include_str!("../../../selections/base.json")).expect("Failed to decode base JSON"); 130 | 131 | let manager = Manager::new().with_groups([d, b]); 132 | let pkgs_partial = manager.selections_with(["base"]).expect("Needed single set of data"); 133 | let pkgs = manager 134 | .selections_with(["develop"]) 135 | .expect("Needed empty set of base selections"); 136 | assert_eq!(pkgs_partial.len(), 35); 137 | assert_eq!(pkgs.len(), 38); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /crates/installer/src/steps/cleanup.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! Lichen cleanup steps 6 | //! Despite the fact we could trivially implement as a `Drop` or some 7 | //! other Rust idiom, we still need to provide active feedback to the user 8 | //! for whatever step is currently running. 9 | //! 10 | //! To that effect we provide a mirror of [`Step`] by way of a Cleanup. 11 | 12 | use super::{partitions, Context}; 13 | 14 | /// Encapsulate the cleanup stages 15 | pub enum Cleanup { 16 | /// Unmount a mountpoint 17 | Unmount(Box), 18 | 19 | /// Sync filesystems pre unmount 20 | Sync(Box), 21 | } 22 | 23 | impl<'a> Cleanup { 24 | /// Create new unmount cleanup stage 25 | pub fn unmount(unmount: partitions::Unmount) -> Self { 26 | Self::Unmount(Box::new(unmount)) 27 | } 28 | 29 | /// Create new sync helper 30 | pub fn sync_fs() -> Self { 31 | Self::Sync(Box::new(partitions::SyncFS {})) 32 | } 33 | 34 | /// Return cleanup step title 35 | pub fn title(&self) -> String { 36 | match &self { 37 | Self::Unmount(s) => s.title(), 38 | Self::Sync(s) => s.title(), 39 | } 40 | } 41 | 42 | /// Fully describe cleanup step 43 | pub fn describe(&self) -> String { 44 | match &self { 45 | Self::Unmount(s) => s.describe(), 46 | Self::Sync(s) => s.describe(), 47 | } 48 | } 49 | 50 | /// Execute the cleanup step 51 | pub fn execute(&self, context: &impl Context<'a>) -> Result<(), super::Error> { 52 | match &self { 53 | Self::Unmount(s) => Ok(s.execute(context)?), 54 | Self::Sync(s) => Ok(s.execute(context)?), 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /crates/installer/src/steps/context.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! Lichen step context 6 | 7 | use std::{ 8 | fmt::Debug, 9 | path::PathBuf, 10 | process::{Command, Output}, 11 | }; 12 | 13 | /// Context for the steps that are executing 14 | /// The context provides access to the core installation variables as 15 | /// well as simplified paths for executing commands in a consistent 16 | /// fashion. 17 | pub trait Context<'a>: Sized + Debug + Send { 18 | /// Return the root directory of the installation 19 | fn root(&'a self) -> &'a PathBuf; 20 | 21 | /// Run the command asynchronously via the context 22 | fn run_command(&self, cmd: &mut Command) -> Result<(), super::Error>; 23 | 24 | /// Run command, capture the output 25 | /// Accepts optional string to write as stdin 26 | fn run_command_captured(&self, cmd: &mut Command, input: Option<&str>) -> Result; 27 | } 28 | -------------------------------------------------------------------------------- /crates/installer/src/steps/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! Lichen installation steps 6 | 7 | use std::{fmt::Debug, process::ExitStatus}; 8 | use thiserror::Error; 9 | 10 | mod context; 11 | pub use context::Context; 12 | 13 | #[derive(Debug, Error)] 14 | pub enum Error { 15 | #[error("io: {0}")] 16 | IO(#[from] std::io::Error), 17 | 18 | #[error("unknown filesystem")] 19 | UnknownFilesystem, 20 | 21 | #[error("no mountpoint given")] 22 | NoMountpoint, 23 | 24 | #[error("command `{program}` exited with {status}")] 25 | CommandFailed { program: String, status: ExitStatus }, 26 | } 27 | 28 | #[derive(Debug)] 29 | pub enum Step<'a> { 30 | AddRepo(Box), 31 | Bind(Box), 32 | CreateUser(Box>), 33 | Format(Box>), 34 | Install(Box), 35 | Mount(Box>), 36 | SetPassword(Box>), 37 | SetLocale(Box>), 38 | SetMachineID(Box), 39 | SetTimezone(Box>), 40 | WriteFstab(Box), 41 | } 42 | 43 | impl<'a> Step<'a> { 44 | /// Create new repo step 45 | pub fn add_repo(r: AddRepo) -> Self { 46 | Self::AddRepo(Box::new(r)) 47 | } 48 | 49 | pub fn create_user(u: CreateAccount<'a>) -> Self { 50 | Self::CreateUser(Box::new(u)) 51 | } 52 | 53 | pub fn install_packages(p: InstallPackages) -> Self { 54 | Self::Install(Box::new(p)) 55 | } 56 | 57 | /// Create new FormatPartition step 58 | pub fn format(f: FormatPartition<'a>) -> Self { 59 | Self::Format(Box::new(f)) 60 | } 61 | 62 | /// Create new MountPartition step 63 | pub fn mount(m: MountPartition<'a>) -> Self { 64 | Self::Mount(Box::new(m)) 65 | } 66 | 67 | /// Create new bind mount 68 | pub fn bind_mount(b: BindMount) -> Self { 69 | Self::Bind(Box::new(b)) 70 | } 71 | 72 | /// Set system locale 73 | pub fn set_locale(l: SetLocale<'a>) -> Self { 74 | Self::SetLocale(Box::new(l)) 75 | } 76 | 77 | /// Set system timezone 78 | pub fn set_timezone(t: SetTimezone<'a>) -> Self { 79 | Self::SetTimezone(Box::new(t)) 80 | } 81 | 82 | /// Set an account password 83 | pub fn set_password(a: SetPassword<'a>) -> Self { 84 | Self::SetPassword(Box::new(a)) 85 | } 86 | 87 | /// Construct a dbus/systemd machine id 88 | pub fn set_machine_id() -> Self { 89 | Self::SetMachineID(Box::new(SetMachineID {})) 90 | } 91 | 92 | // Emit the given fstab 93 | pub fn emit_fstab(f: EmitFstab) -> Self { 94 | Self::WriteFstab(Box::new(f)) 95 | } 96 | 97 | /// Return a unique short ID name for the steps 98 | pub fn name(&self) -> &'static str { 99 | match &self { 100 | Step::AddRepo(_) => "add-repo", 101 | Step::Bind(_) => "bind-mount", 102 | Step::CreateUser(_) => "create-user", 103 | Step::Format(_) => "format-partition", 104 | Step::Install(_) => "install-packages", 105 | Step::Mount(_) => "mount-partition", 106 | Step::SetPassword(_) => "set-password", 107 | Step::SetLocale(_) => "set-locale", 108 | Step::SetTimezone(_) => "set-timezone", 109 | Step::SetMachineID(_) => "set-machine-id", 110 | Step::WriteFstab(_) => "write-fstab", 111 | } 112 | } 113 | 114 | /// Return the display title for a step 115 | pub fn title(&self) -> String { 116 | match &self { 117 | Step::AddRepo(s) => s.title(), 118 | Step::Bind(s) => s.title(), 119 | Step::CreateUser(s) => s.title(), 120 | Step::Format(s) => s.title(), 121 | Step::Install(s) => s.title(), 122 | Step::Mount(s) => s.title(), 123 | Step::SetPassword(s) => s.title(), 124 | Step::SetLocale(s) => s.title(), 125 | Step::SetTimezone(s) => s.title(), 126 | Step::SetMachineID(s) => s.title(), 127 | Step::WriteFstab(s) => s.title(), 128 | } 129 | } 130 | 131 | /// Describe the action/context for the step 132 | pub fn describe(&self) -> String { 133 | match &self { 134 | Step::AddRepo(s) => s.describe(), 135 | Step::Bind(s) => s.describe(), 136 | Step::CreateUser(s) => s.describe(), 137 | Step::Format(s) => s.describe(), 138 | Step::Install(s) => s.describe(), 139 | Step::Mount(s) => s.describe(), 140 | Step::SetPassword(s) => s.describe(), 141 | Step::SetLocale(s) => s.describe(), 142 | Step::SetTimezone(s) => s.describe(), 143 | Step::SetMachineID(s) => s.describe(), 144 | Step::WriteFstab(s) => s.describe(), 145 | } 146 | } 147 | 148 | /// Execute a step asynchronously. Implementations can opt-in to async. 149 | pub fn execute(&self, context: &'a impl Context<'a>) -> Result<(), Error> { 150 | match &self { 151 | Step::AddRepo(s) => Ok(s.execute(context)?), 152 | Step::Bind(s) => Ok(s.execute(context)?), 153 | Step::CreateUser(s) => Ok(s.execute(context)?), 154 | Step::Format(s) => Ok(s.execute(context)?), 155 | Step::Install(s) => Ok(s.execute(context)?), 156 | Step::Mount(s) => Ok(s.execute(context)?), 157 | Step::SetPassword(s) => Ok(s.execute(context)?), 158 | Step::SetLocale(s) => Ok(s.execute(context)?), 159 | Step::SetTimezone(s) => Ok(s.execute(context)?), 160 | Step::SetMachineID(s) => Ok(s.execute(context)?), 161 | Step::WriteFstab(s) => Ok(s.execute(context)?), 162 | } 163 | } 164 | 165 | /// Determine whether an indeterminate progress spinner is needed 166 | /// In the CLI frontend this is abused to hide the progressbar when invoking moss. 167 | pub fn is_indeterminate(&self) -> bool { 168 | !matches!(self, Step::Install(_)) 169 | } 170 | } 171 | 172 | mod partitions; 173 | 174 | pub use partitions::{BindMount, FormatPartition, MountPartition, Unmount}; 175 | 176 | mod packaging; 177 | pub use packaging::{AddRepo, InstallPackages}; 178 | 179 | mod cleanup; 180 | pub use cleanup::Cleanup; 181 | 182 | mod postinstall; 183 | pub use postinstall::{CreateAccount, EmitFstab, FstabEntry, SetLocale, SetMachineID, SetPassword, SetTimezone}; 184 | -------------------------------------------------------------------------------- /crates/installer/src/steps/packaging.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! Package management encapsulation (moss only) 6 | use std::process::Command; 7 | 8 | use super::Context; 9 | 10 | /// Add a repository to the target disk 11 | #[derive(Debug)] 12 | pub struct AddRepo { 13 | pub(crate) uri: String, 14 | pub(crate) name: String, 15 | pub(crate) priority: u64, 16 | } 17 | 18 | impl<'a> AddRepo { 19 | /// Basic display title 20 | pub(super) fn title(&self) -> String { 21 | format!("Add repo {}", self.name) 22 | } 23 | 24 | /// Render the action 25 | pub(super) fn describe(&self) -> String { 26 | format!("{} (priority {})", self.uri, self.priority) 27 | } 28 | 29 | /// Run moss against the target, adding a repo 30 | pub(super) fn execute(&self, context: &'a impl Context<'a>) -> Result<(), super::Error> { 31 | let mut cmd = Command::new("moss"); 32 | cmd.arg("-D"); 33 | cmd.arg(context.root()); 34 | cmd.args(["repo", "add", &self.name, &self.uri, "-p"]); 35 | cmd.arg(self.priority.to_string()); 36 | cmd.arg("-y"); 37 | 38 | // Run 39 | context.run_command(&mut cmd) 40 | } 41 | } 42 | 43 | /// Install packages to destdir 44 | #[derive(Debug)] 45 | pub struct InstallPackages { 46 | pub(crate) names: Vec, 47 | } 48 | 49 | impl<'a> InstallPackages { 50 | /// Basic display title 51 | pub(super) fn title(&self) -> String { 52 | "Install".into() 53 | } 54 | 55 | /// Render the action 56 | pub(super) fn describe(&self) -> String { 57 | "packages to sysroot".into() 58 | } 59 | 60 | /// Run moss against the target, adding a repo 61 | pub(super) fn execute(&self, context: &'a impl Context<'a>) -> Result<(), super::Error> { 62 | let mut cmd = Command::new("moss"); 63 | cmd.arg("-D"); 64 | cmd.arg(context.root()); 65 | cmd.arg("install"); 66 | cmd.args(&self.names); 67 | cmd.arg("-y"); 68 | 69 | context.run_command(&mut cmd) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /crates/installer/src/steps/partitions.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! Partititon formatting 6 | 7 | use std::{path::PathBuf, process::Command}; 8 | 9 | use fs_err as fs; 10 | use system::disk::Partition; 11 | 12 | use super::Context; 13 | 14 | /// Format a partition 15 | #[derive(Debug)] 16 | pub struct FormatPartition<'a> { 17 | /// What partition are we formatting 18 | pub(crate) partition: &'a Partition, 19 | 20 | /// What filesystem would you like it to have 21 | pub(crate) filesystem: String, 22 | } 23 | 24 | impl<'a> FormatPartition<'a> { 25 | pub(super) fn execute(&self, context: &impl Context<'a>) -> Result<(), super::Error> { 26 | let fs = self.filesystem.to_lowercase(); 27 | let (exec, args) = match fs.as_str() { 28 | "ext4" => ("mkfs.ext4", ["-F", &self.partition.path.display().to_string()]), 29 | "xfs" => ("mkfs.xfs", ["-f", &self.partition.path.display().to_string()]), 30 | "f2fs" => ("mkfs.f2fs", ["-f", &self.partition.path.display().to_string()]), 31 | _ => unimplemented!(), 32 | }; 33 | log::info!("Formatting {} as {}", self.partition.path.display(), self.filesystem); 34 | log::trace!("Running: {exec:?} w/ {args:?}"); 35 | 36 | // For now we drop output, but we'll wire up stdout/stderr in context 37 | let mut cmd = Command::new(exec); 38 | cmd.args(args); 39 | let _ = context.run_command_captured(&mut cmd, None)?; 40 | Ok(()) 41 | } 42 | 43 | pub(super) fn title(&self) -> String { 44 | "Format partition".into() 45 | } 46 | 47 | pub(super) fn describe(&self) -> String { 48 | format!("{} as {}", self.partition.path.display(), self.filesystem) 49 | } 50 | } 51 | 52 | /// Mount a given partition 53 | #[derive(Debug)] 54 | pub struct MountPartition<'a> { 55 | /// Which partition? 56 | pub(crate) partition: &'a Partition, 57 | 58 | /// Where are we mounting it? 59 | pub(crate) mountpoint: PathBuf, 60 | } 61 | 62 | impl<'a> MountPartition<'a> { 63 | pub(super) fn execute(&self, context: &impl Context<'a>) -> Result<(), super::Error> { 64 | log::info!( 65 | "Mounting {} to {}", 66 | self.partition.path.display(), 67 | self.mountpoint.display() 68 | ); 69 | 70 | // Ensure target exists 71 | fs::create_dir_all(&self.mountpoint)?; 72 | let source = self.partition.path.to_string_lossy().to_string(); 73 | let dest = self.mountpoint.to_string_lossy().to_string(); 74 | let mut cmd = Command::new("mount"); 75 | cmd.args([&source, &dest]); 76 | 77 | let _ = context.run_command_captured(&mut cmd, None)?; 78 | Ok(()) 79 | } 80 | 81 | pub(super) fn title(&self) -> String { 82 | "Mount filesystem".into() 83 | } 84 | 85 | pub(super) fn describe(&self) -> String { 86 | format!("{} as {}", self.partition.path.display(), self.mountpoint.display()) 87 | } 88 | } 89 | 90 | /// Bind mount a source dir into a target dir 91 | #[derive(Debug)] 92 | pub struct BindMount { 93 | /// The source directory 94 | pub(crate) source: PathBuf, 95 | 96 | /// Destination directory 97 | pub(crate) dest: PathBuf, 98 | } 99 | 100 | impl<'a> BindMount { 101 | pub(super) fn execute(&self, context: &impl Context<'a>) -> Result<(), super::Error> { 102 | log::info!("Bind mounting {} to {}", self.source.display(), self.dest.display()); 103 | 104 | // Ensure target exists 105 | fs::create_dir_all(&self.dest)?; 106 | let source = self.source.to_string_lossy().to_string(); 107 | let dest = self.dest.to_string_lossy().to_string(); 108 | let mut cmd = Command::new("mount"); 109 | cmd.args(["--bind", &source, &dest]); 110 | 111 | let _ = context.run_command_captured(&mut cmd, None)?; 112 | Ok(()) 113 | } 114 | 115 | pub(super) fn title(&self) -> String { 116 | "Bind mount filesystem".into() 117 | } 118 | 119 | pub(super) fn describe(&self) -> String { 120 | format!("{} on {}", self.source.display(), self.dest.display()) 121 | } 122 | } 123 | 124 | /// Unmount a given mountpoint 125 | #[derive(Debug)] 126 | pub struct Unmount { 127 | pub(crate) mountpoint: PathBuf, 128 | } 129 | 130 | impl<'a> Unmount { 131 | pub(super) fn title(&self) -> String { 132 | "Unmount".to_string() 133 | } 134 | 135 | pub(super) fn describe(&self) -> String { 136 | format!("{}", &self.mountpoint.display()) 137 | } 138 | 139 | pub(super) fn execute(&self, context: &impl Context<'a>) -> Result<(), super::Error> { 140 | log::info!("Unmounting {}", self.mountpoint.display()); 141 | 142 | let dest = self.mountpoint.to_string_lossy().to_string(); 143 | let mut cmd = Command::new("umount"); 144 | cmd.arg(dest); 145 | 146 | let _ = context.run_command_captured(&mut cmd, None)?; 147 | Ok(()) 148 | } 149 | } 150 | 151 | /// A cleanup helper that invokes `sync` 152 | pub struct SyncFS {} 153 | 154 | impl<'a> SyncFS { 155 | pub(super) fn title(&self) -> String { 156 | "Sync".into() 157 | } 158 | 159 | pub(super) fn describe(&self) -> String { 160 | "filesystems".into() 161 | } 162 | 163 | pub(super) fn execute(&self, context: &impl Context<'a>) -> Result<(), super::Error> { 164 | log::info!("Syncing filesystems"); 165 | 166 | let mut cmd = Command::new("sync"); 167 | let _ = context.run_command_captured(&mut cmd, None); 168 | Ok(()) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /crates/installer/src/steps/postinstall.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! Post-installation tasks 6 | 7 | use std::{fmt::Display, process::Command}; 8 | 9 | use fs_err as fs; 10 | use system::locale::Locale; 11 | 12 | use crate::{Account, SystemPartition}; 13 | 14 | use super::{Context, Error}; 15 | 16 | /// Configure an account on the system 17 | #[derive(Debug)] 18 | pub struct SetPassword<'a> { 19 | pub(crate) account: &'a Account, 20 | pub(crate) password: String, 21 | } 22 | 23 | /// Create an account 24 | #[derive(Debug)] 25 | pub struct CreateAccount<'a> { 26 | pub(crate) account: &'a Account, 27 | } 28 | 29 | impl<'a> SetPassword<'a> { 30 | pub(super) fn title(&self) -> String { 31 | "Set account password".to_string() 32 | } 33 | 34 | pub(super) fn describe(&self) -> String { 35 | self.account.username.clone() 36 | } 37 | 38 | /// Execute to configure the account 39 | pub(super) fn execute(&self, context: &'a impl Context<'a>) -> Result<(), Error> { 40 | let mut cmd = Command::new("chroot"); 41 | cmd.arg(context.root().clone()); 42 | cmd.arg("chpasswd"); 43 | 44 | let password_text = format!("{}:{}\n", &self.account.username, self.password); 45 | context.run_command_captured(&mut cmd, Some(&password_text))?; 46 | 47 | Ok(()) 48 | } 49 | } 50 | 51 | impl<'a> CreateAccount<'a> { 52 | pub(super) fn title(&self) -> String { 53 | "Create account".to_string() 54 | } 55 | 56 | pub(super) fn describe(&self) -> String { 57 | self.account.username.clone() 58 | } 59 | 60 | pub(super) fn execute(&self, context: &'a impl Context<'a>) -> Result<(), Error> { 61 | let mut cmd = Command::new("chroot"); 62 | cmd.arg(context.root().clone()); 63 | cmd.arg("useradd"); 64 | cmd.arg(self.account.username.clone()); 65 | cmd.args(["-m", "-U", "-G", "audio,adm,wheel,render,kvm,input,users"]); 66 | 67 | if let Some(gecos) = self.account.gecos.as_ref() { 68 | cmd.arg("-C"); 69 | cmd.arg(gecos.clone()); 70 | } 71 | cmd.arg("-s"); 72 | cmd.arg(self.account.shell.clone()); 73 | context.run_command_captured(&mut cmd, None)?; 74 | Ok(()) 75 | } 76 | } 77 | 78 | /// Update locale in `locale.conf` 79 | #[derive(Debug)] 80 | pub struct SetLocale<'a> { 81 | pub(crate) locale: &'a Locale<'a>, 82 | } 83 | 84 | impl<'a> SetLocale<'a> { 85 | pub(super) fn title(&self) -> String { 86 | "Set system locale".to_string() 87 | } 88 | 89 | pub(super) fn describe(&self) -> String { 90 | self.locale.display_name.clone() 91 | } 92 | 93 | pub(super) fn execute(&self, context: &'a impl Context<'a>) -> Result<(), Error> { 94 | let contents = format!("LANG={}\n", self.locale.name); 95 | let path = context.root().join("etc").join("locale.conf"); 96 | fs::write(path, &contents)?; 97 | 98 | Ok(()) 99 | } 100 | } 101 | 102 | // Update the timezone 103 | #[derive(Debug)] 104 | pub struct SetTimezone<'a> { 105 | pub(crate) timezone: &'a str, 106 | } 107 | 108 | impl<'a> SetTimezone<'a> { 109 | pub(super) fn title(&self) -> String { 110 | "Set system timezone".to_string() 111 | } 112 | 113 | pub(super) fn describe(&self) -> String { 114 | self.timezone.to_string() 115 | } 116 | 117 | pub(super) fn execute(&self, context: &'a impl Context<'a>) -> Result<(), Error> { 118 | fs::remove_file(context.root().join("etc").join("localtime")).ok(); 119 | std::os::unix::fs::symlink( 120 | format!("../usr/share/zoneinfo/{}", self.timezone), 121 | context.root().join("etc").join("localtime"), 122 | )?; 123 | 124 | Ok(()) 125 | } 126 | } 127 | 128 | /// Set a machine ID up in the root 129 | #[derive(Debug)] 130 | pub struct SetMachineID {} 131 | 132 | impl<'a> SetMachineID { 133 | pub(super) fn title(&self) -> String { 134 | "Allocate machine-id".to_string() 135 | } 136 | 137 | pub(super) fn describe(&self) -> String { 138 | "via systemd-machine-id-setup".to_string() 139 | } 140 | 141 | pub(super) fn execute(&self, context: &'a impl Context<'a>) -> Result<(), Error> { 142 | let file = context.root().join("etc").join("machine-id"); 143 | if file.exists() { 144 | fs::remove_file(file)?; 145 | } 146 | 147 | let mut cmd = Command::new("chroot"); 148 | cmd.arg(context.root().clone()); 149 | cmd.arg("systemd-machine-id-setup"); 150 | context.run_command_captured(&mut cmd, None)?; 151 | 152 | Ok(()) 153 | } 154 | } 155 | 156 | #[derive(Debug)] 157 | pub enum FstabEntry { 158 | Comment(String), 159 | Device { 160 | fs: String, 161 | mountpoint: String, 162 | kind: String, 163 | opts: String, 164 | dump: u8, 165 | pass: u8, 166 | }, 167 | } 168 | 169 | impl Display for FstabEntry { 170 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 171 | match self { 172 | FstabEntry::Comment(c) => f.write_fmt(format_args!("# {c}")), 173 | FstabEntry::Device { 174 | fs, 175 | mountpoint, 176 | kind, 177 | opts, 178 | dump, 179 | pass, 180 | } => f.write_fmt(format_args!("{fs}\t{mountpoint}\t{kind}\t{opts}\t{dump}\t{pass}")), 181 | } 182 | } 183 | } 184 | 185 | // Emit the fstab 186 | #[derive(Debug)] 187 | pub struct EmitFstab { 188 | entries: Vec, 189 | } 190 | 191 | impl Default for EmitFstab { 192 | fn default() -> Self { 193 | Self { 194 | entries: vec![ 195 | // template header 196 | FstabEntry::Comment("/etc/fstab: static filesystem information.".to_string()), 197 | FstabEntry::Comment(String::new()), 198 | FstabEntry::Comment(" ".to_string()), 199 | FstabEntry::Comment(String::new()), 200 | FstabEntry::Comment("/dev/ROOT / ext3 noatime 0 1".to_string()), 201 | FstabEntry::Comment("/dev/SWAP none swap sw 0 0".to_string()), 202 | FstabEntry::Comment("/dev/fd0 /mnt/floppy auto noauto 0 0".to_string()), 203 | // proc 204 | FstabEntry::Device { 205 | fs: "none".into(), 206 | mountpoint: "/proc".into(), 207 | kind: "proc".into(), 208 | opts: "nosuid,noexec".into(), 209 | dump: 0, 210 | pass: 0, 211 | }, 212 | // shm 213 | FstabEntry::Device { 214 | fs: "none".into(), 215 | mountpoint: "/dev/shm".into(), 216 | kind: "tmpfs".into(), 217 | opts: "defaults".into(), 218 | dump: 0, 219 | pass: 0, 220 | }, 221 | ], 222 | } 223 | } 224 | } 225 | 226 | impl TryFrom<&SystemPartition> for FstabEntry { 227 | type Error = Error; 228 | fn try_from(value: &SystemPartition) -> Result { 229 | // Honestly, this is a bit ext4 centric, no ssd care given 230 | let s = Self::Device { 231 | // NOTE: This is always PartUUID for us, we only do GPT. 232 | fs: format!("PARTUUID={}", &value.partition.uuid), 233 | mountpoint: value.mountpoint.clone().ok_or(Error::NoMountpoint)?, 234 | kind: value 235 | .partition 236 | .sb 237 | .as_ref() 238 | .map(|sb| sb.to_string()) 239 | .ok_or(Error::UnknownFilesystem)?, 240 | opts: "rw,errors=remount-ro".to_string(), 241 | dump: 0, 242 | pass: 1, 243 | }; 244 | 245 | Ok(s) 246 | } 247 | } 248 | 249 | impl<'a> EmitFstab { 250 | // Create with a bunch of entries 251 | pub fn with_entries(self, entries: impl IntoIterator) -> Self { 252 | Self { 253 | entries: self.entries.into_iter().chain(entries).collect::>(), 254 | } 255 | } 256 | 257 | pub(super) fn title(&self) -> String { 258 | "Generate fstab".into() 259 | } 260 | 261 | pub(super) fn describe(&self) -> String { 262 | "".into() 263 | } 264 | 265 | /// Write the filesystem table 266 | pub(super) fn execute(&self, context: &'a impl Context<'a>) -> Result<(), Error> { 267 | let file = context.root().join("etc").join("fstab"); 268 | let entries = self.entries.iter().map(|e| e.to_string()).collect::>(); 269 | fs::write(file, entries.join("\n"))?; 270 | Ok(()) 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /crates/installer/src/systemd.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! systemd helpers 6 | 7 | use std::{io, process::Command, string::FromUtf8Error}; 8 | 9 | use thiserror::Error; 10 | 11 | #[derive(Debug, Error)] 12 | pub enum Error { 13 | #[error("io: {0}")] 14 | IO(#[from] io::Error), 15 | 16 | #[error("utf8 decoding: {0}")] 17 | Utf8(#[from] FromUtf8Error), 18 | } 19 | 20 | /// List all locales according to localectl 21 | pub fn localectl_list_locales() -> Result, Error> { 22 | let output = Command::new("localectl").arg("list-locales").output()?; 23 | let text = String::from_utf8(output.stdout)?; 24 | Ok(text.lines().map(|l| l.to_string()).collect::>()) 25 | } 26 | -------------------------------------------------------------------------------- /crates/system/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "system" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | gpt = "4.0.0" 8 | thiserror.workspace = true 9 | serde_json.workspace = true 10 | serde.workspace = true 11 | superblock.workspace = true 12 | fs-err.workspace = true 13 | 14 | [lints] 15 | workspace = true 16 | -------------------------------------------------------------------------------- /crates/system/README.md: -------------------------------------------------------------------------------- 1 | # system 2 | 3 | This crate provides various probes and data APIs to learn more about the 4 | underlying system, such as locales, disk data, etc. 5 | -------------------------------------------------------------------------------- /crates/system/src/disk/disks.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! Disk management 6 | 7 | use std::{ 8 | fmt::Display, 9 | path::{Path, PathBuf}, 10 | }; 11 | 12 | use fs_err as fs; 13 | use gpt::GptConfig; 14 | 15 | use super::{Error, Partition}; 16 | 17 | /// Indicates type of disk device 18 | #[derive(Debug)] 19 | #[allow(clippy::upper_case_acronyms)] 20 | pub enum Kind { 21 | /// Hard disk drive 22 | HDD, 23 | 24 | /// Solid State device 25 | SSD, 26 | } 27 | 28 | impl Display for Kind { 29 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 30 | match &self { 31 | Kind::HDD => f.write_str("HDD"), 32 | Kind::SSD => f.write_str("SSD"), 33 | } 34 | } 35 | } 36 | 37 | /// Basic physical device mapping 38 | #[derive(Debug)] 39 | pub struct Disk { 40 | pub path: PathBuf, 41 | pub kind: Kind, 42 | pub model: Option, 43 | pub vendor: Option, 44 | pub block_size: u64, 45 | pub size: u64, 46 | } 47 | 48 | impl Display for Disk { 49 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 50 | let vendor = self.vendor.as_ref().map(|v| format!("{} ", v)).unwrap_or_default(); 51 | 52 | let description = if let Some(model) = self.model.as_ref() { 53 | format!("{}{}", vendor, model) 54 | } else { 55 | vendor.to_string() 56 | }; 57 | 58 | f.write_fmt(format_args!("{} ({})", description, &self.kind)) 59 | } 60 | } 61 | 62 | impl Disk { 63 | /// Build a Disk from the given sysfs path 64 | pub fn from_sysfs_path(path: impl AsRef) -> Result { 65 | let path = path.as_ref(); 66 | let device_link = path.join("device"); 67 | let slavedir = path.join("slaves"); 68 | 69 | let file_name = path.file_name().ok_or(Error::InvalidDisk)?; 70 | 71 | // Ensure the device link is present (no virtual ram0 device, etc) 72 | if !device_link.exists() { 73 | return Err(Error::InvalidDisk); 74 | } 75 | 76 | // Root level devices, not interested in child partitions as yet. 77 | let ancestors = fs::read_dir(slavedir)?.filter_map(|m| m.ok()).collect::>(); 78 | if !ancestors.is_empty() { 79 | return Err(Error::InvalidDisk); 80 | } 81 | 82 | // SSD or HDD? 83 | let rotational = path.join("queue").join("rotational"); 84 | let kind = if rotational.exists() { 85 | match str::parse::(fs::read_to_string(rotational)?.trim())? { 86 | 0 => Kind::SSD, 87 | _ => Kind::HDD, 88 | } 89 | } else { 90 | Kind::HDD 91 | }; 92 | 93 | // additioal metadata. 94 | 95 | let vendor = fs::read_to_string(device_link.join("vendor")) 96 | .ok() 97 | .map(|f| f.trim().to_string()); 98 | let model = fs::read_to_string(device_link.join("model")) 99 | .ok() 100 | .map(|f| f.trim().to_string()); 101 | let block_size = str::parse::(fs::read_to_string(path.join("queue").join("physical_block_size"))?.trim())?; 102 | let size = str::parse::(fs::read_to_string(path.join("size"))?.trim())?; 103 | 104 | let path = PathBuf::from("/dev").join(file_name); 105 | 106 | Ok(Self { 107 | path, 108 | kind, 109 | vendor, 110 | model, 111 | block_size, 112 | size, 113 | }) 114 | } 115 | 116 | /// Discover all disks on the system 117 | pub fn discover() -> Result, Error> { 118 | let disks = fs::read_dir("/sys/class/block")? 119 | .filter_map(|f| { 120 | let f = f.ok()?; 121 | Disk::from_sysfs_path(f.path()).ok() 122 | }) 123 | .collect::>(); 124 | Ok(disks) 125 | } 126 | 127 | /// Return all partitions on the disk if it is GPT 128 | pub fn partitions(&self) -> Result, Error> { 129 | let path = self.path.clone(); 130 | let path: &PathBuf = &path; 131 | let device = Box::new(fs::File::open(path)?); 132 | let table = GptConfig::default().writable(false).open_from_device(device)?; 133 | let block_size = table.logical_block_size(); 134 | let mut parts = vec![]; 135 | for (_, part) in table.partitions().iter() { 136 | parts.push(Partition::from(part, block_size)?) 137 | } 138 | Ok(parts) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /crates/system/src/disk/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! Disk management 6 | 7 | use std::{io, num::ParseIntError}; 8 | 9 | use thiserror::Error; 10 | 11 | /// Error reporting for disks 12 | #[derive(Debug, Error)] 13 | pub enum Error { 14 | #[error("io: {0}")] 15 | IO(#[from] io::Error), 16 | 17 | #[error("gpt: {0}")] 18 | Gpt(#[from] gpt::GptError), 19 | 20 | #[error("numbers: {0}")] 21 | Numbers(#[from] ParseIntError), 22 | 23 | #[error("invalid disk")] 24 | InvalidDisk, 25 | 26 | #[error("superblock: {0}")] 27 | Superblock(#[from] superblock::Error), 28 | } 29 | 30 | mod disks; 31 | pub use disks::Disk; 32 | mod partition; 33 | pub use disks::Kind as DiskKind; 34 | pub use partition::Kind as PartitionKind; 35 | pub use partition::Partition; 36 | 37 | pub use superblock::Kind as SuperblockKind; 38 | pub use superblock::Superblock; 39 | -------------------------------------------------------------------------------- /crates/system/src/disk/partition.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! Partition APIs 6 | 7 | use std::fmt::Display; 8 | use std::io::{Cursor, Read}; 9 | use std::path::PathBuf; 10 | 11 | use fs_err as fs; 12 | use gpt::disk::LogicalBlockSize; 13 | use gpt::partition_types; 14 | 15 | /// Partition on a GPT disk 16 | #[derive(Debug, Clone, Default)] 17 | pub struct Partition { 18 | pub path: PathBuf, 19 | pub kind: Kind, 20 | pub size: u64, 21 | pub uuid: String, 22 | pub sb: Option, 23 | } 24 | 25 | impl Display for Partition { 26 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 27 | f.write_str(&self.path.display().to_string()) 28 | } 29 | } 30 | /// Specialised type of partition 31 | #[derive(Debug, Clone, Default)] 32 | #[allow(clippy::upper_case_acronyms)] 33 | pub enum Kind { 34 | ESP, 35 | XBOOTLDR, 36 | #[default] 37 | Regular, 38 | } 39 | 40 | /// Superblock scanning, self contained 41 | fn scan_superblock(path: &PathBuf) -> Result { 42 | let fi = fs::File::open(path)?; 43 | let mut buffer: Vec = Vec::with_capacity(2 * 1024 * 1024); 44 | fi.take(2 * 1024 * 1024).read_to_end(&mut buffer)?; 45 | let mut cursor = Cursor::new(&buffer); 46 | let sb = superblock::for_reader(&mut cursor)?; 47 | Ok(sb.kind()) 48 | } 49 | 50 | impl Partition { 51 | /// Construct new Partition from the given GPT Partition and block size 52 | pub fn from(value: &gpt::partition::Partition, block_size: &LogicalBlockSize) -> Result { 53 | let uuid = value.part_guid.hyphenated().to_string(); 54 | let path = fs::canonicalize(format!("/dev/disk/by-partuuid/{}", uuid))?; 55 | let kind = match value.part_type_guid { 56 | partition_types::EFI => Kind::ESP, 57 | partition_types::FREEDESK_BOOT => Kind::XBOOTLDR, 58 | _ => Kind::Regular, 59 | }; 60 | let sb = scan_superblock(&path).ok(); 61 | let size = value.bytes_len(*block_size)?; 62 | Ok(Self { 63 | path, 64 | kind, 65 | size, 66 | uuid, 67 | sb, 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /crates/system/src/lib.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! Integration with various system APIs to enumerate locales, timezones, etc. 6 | 7 | pub mod disk; 8 | pub mod locale; 9 | -------------------------------------------------------------------------------- /crates/system/src/locale/iso_3166.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! Parsing for ISO-3166-1 files from iso-codes 6 | //! Essentially, loading of territories. 7 | 8 | use serde::Deserialize; 9 | 10 | use super::Territory; 11 | 12 | /// Wrap the document stream from JSON into referenced 13 | /// entries in the input text 14 | #[derive(Deserialize)] 15 | pub struct Document<'a> { 16 | #[serde(rename = "3166-1", borrow)] 17 | pub entries: Vec>, 18 | } 19 | 20 | /// Maps an entry from iso-codes to a Rusty struct. 21 | #[derive(Deserialize)] 22 | pub struct Entry<'a> { 23 | /// Two-element code identifying the entry 24 | #[serde(rename = "alpha_2", borrow)] 25 | pub code2: &'a str, 26 | 27 | /// Three-element code identifying the entry 28 | #[serde(rename = "alpha_3", borrow)] 29 | pub code3: &'a str, 30 | 31 | /// Unicode flag representation 32 | #[serde(borrow)] 33 | pub flag: &'a str, 34 | 35 | /// Normalised name 36 | #[serde(borrow)] 37 | pub name: &'a str, 38 | 39 | /// Unique territory 40 | #[serde(borrow)] 41 | pub numeric: &'a str, 42 | 43 | /// Formal name if present 44 | #[serde(borrow)] 45 | pub official_name: Option<&'a str>, 46 | } 47 | 48 | impl From<&Entry<'_>> for Territory { 49 | fn from(value: &Entry<'_>) -> Self { 50 | if let Some(display) = value.official_name { 51 | Self { 52 | code: value.code3.into(), 53 | code2: value.code2.into(), 54 | display_name: display.into(), 55 | flag: value.flag.into(), 56 | } 57 | } else { 58 | Self { 59 | code: value.code3.into(), 60 | code2: value.code2.into(), 61 | display_name: value.name.into(), 62 | flag: value.flag.into(), 63 | } 64 | } 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use super::Document; 71 | 72 | #[test] 73 | fn basic_load() { 74 | const TEST_DATA: &str = r#" 75 | { 76 | "3166-1": [ 77 | 78 | { 79 | "alpha_2": "IN", 80 | "alpha_3": "IND", 81 | "flag": "🇮🇳", 82 | "name": "India", 83 | "numeric": "356", 84 | "official_name": "Republic of India" 85 | }, 86 | { 87 | "alpha_2": "IO", 88 | "alpha_3": "IOT", 89 | "flag": "🇮🇴", 90 | "name": "British Indian Ocean Territory", 91 | "numeric": "086" 92 | }, 93 | { 94 | "alpha_2": "IE", 95 | "alpha_3": "IRL", 96 | "flag": "🇮🇪", 97 | "name": "Ireland", 98 | "numeric": "372" 99 | } 100 | ] 101 | } 102 | "#; 103 | let loaded = serde_json::from_str::>(TEST_DATA).expect("Failed to decode ISO-3166 JSON"); 104 | 105 | let ie = loaded 106 | .entries 107 | .iter() 108 | .find(|e| e.code3 == "IRL") 109 | .expect("Failed to find locale"); 110 | assert_eq!(ie.name, "Ireland"); 111 | eprintln!("Ireland: {}", ie.flag); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /crates/system/src/locale/iso_639_2.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! Parsing for ISO-639 files from iso-codes 6 | //! Essentially, loading of languages 7 | 8 | use serde::Deserialize; 9 | 10 | use super::Language; 11 | 12 | /// JSON document for ISO-639-2 13 | #[derive(Deserialize)] 14 | pub struct Document<'a> { 15 | #[serde(rename = "639-2", borrow)] 16 | pub entries: Vec>, 17 | } 18 | 19 | /// A single entry in the JSON document 20 | #[derive(Deserialize)] 21 | pub struct Entry<'a> { 22 | #[serde(rename = "alpha_2", borrow)] 23 | pub code2: Option<&'a str>, 24 | 25 | #[serde(rename = "alpha_3", borrow)] 26 | pub code3: &'a str, 27 | 28 | /// Official display name 29 | #[serde(borrow)] 30 | pub name: &'a str, 31 | 32 | /// Common name (optional) 33 | #[serde(borrow)] 34 | pub common_name: Option<&'a str>, 35 | 36 | /// Three letter bibliographic 37 | pub bibliographic: Option<&'a str>, 38 | } 39 | 40 | impl From<&Entry<'_>> for Language { 41 | /// Convert iso entry into Language 42 | fn from(value: &Entry<'_>) -> Self { 43 | Self { 44 | code: value.code3.into(), 45 | code2: value.code2.map(|v| v.into()), 46 | display_name: value.name.into(), 47 | inverted_name: None, 48 | } 49 | } 50 | } 51 | 52 | #[cfg(test)] 53 | mod tests { 54 | use super::Document; 55 | 56 | #[test] 57 | fn load_iso_639_2() { 58 | const TEST_DATA: &str = r#" 59 | { 60 | "639-2": [ 61 | { 62 | "alpha_2": "gd", 63 | "alpha_3": "gla", 64 | "name": "Gaelic; Scottish Gaelic" 65 | }, 66 | { 67 | "alpha_2": "ga", 68 | "alpha_3": "gle", 69 | "name": "Irish" 70 | }, 71 | { 72 | "alpha_2": "gl", 73 | "alpha_3": "glg", 74 | "name": "Galician" 75 | } 76 | ] 77 | } 78 | "#; 79 | 80 | let loaded = serde_json::from_str::>(TEST_DATA).expect("Failed to decode ISO-639-2 data"); 81 | let ga = loaded 82 | .entries 83 | .iter() 84 | .find(|i| i.code3 == "gle") 85 | .expect("Failed to find GLE"); 86 | assert_eq!(ga.name, "Irish"); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /crates/system/src/locale/iso_639_3.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! Parsing for ISO-639-3 JSON files 6 | use serde::Deserialize; 7 | 8 | use super::Language; 9 | 10 | /// JSON document for 639-3 11 | #[derive(Deserialize)] 12 | pub struct Document<'a> { 13 | #[serde(rename = "639-3", borrow)] 14 | pub entries: Vec>, 15 | } 16 | 17 | /// Language scope 18 | #[derive(Deserialize)] 19 | pub enum Scope { 20 | #[serde(rename = "I")] 21 | Individual, 22 | 23 | #[serde(rename = "M")] 24 | Macrolanguage, 25 | 26 | #[serde(rename = "S")] 27 | Special, 28 | } 29 | 30 | #[derive(Deserialize)] 31 | pub enum Kind { 32 | #[serde(rename = "A")] 33 | Ancient, 34 | #[serde(rename = "C")] 35 | Constructed, 36 | #[serde(rename = "E")] 37 | Extinct, 38 | #[serde(rename = "H")] 39 | Historical, 40 | #[serde(rename = "L")] 41 | Living, 42 | #[serde(rename = "S")] 43 | Special, 44 | } 45 | 46 | /// Single entry in the JSON document 47 | #[derive(Deserialize)] 48 | pub struct Entry<'a> { 49 | /// Three letter code 50 | #[serde(rename = "alpha_3", borrow)] 51 | pub code: &'a str, 52 | 53 | /// Sometimes a 2 letter code is present 54 | #[serde(rename = "alpha_2", borrow)] 55 | pub code2: Option<&'a str>, 56 | 57 | /// Official name 58 | #[serde(borrow)] 59 | pub name: &'a str, 60 | 61 | /// Inverted name 62 | #[serde(borrow)] 63 | pub inverted_name: Option<&'a str>, 64 | 65 | /// Scope of the language 66 | pub scope: Scope, 67 | 68 | /// Type of language 69 | #[serde(rename = "type")] 70 | pub kind: Kind, 71 | 72 | /// Three letter bibliographic 73 | pub bibliographic: Option<&'a str>, 74 | 75 | /// Common name (optional) 76 | #[serde(borrow)] 77 | pub common_name: Option<&'a str>, 78 | } 79 | 80 | impl From<&Entry<'_>> for Language { 81 | fn from(value: &Entry<'_>) -> Self { 82 | let display = if let Some(name) = value.common_name { 83 | name.into() 84 | } else { 85 | value.name.into() 86 | }; 87 | Self { 88 | code: value.code.into(), 89 | code2: value.code2.map(|v| v.into()), 90 | display_name: display, 91 | inverted_name: value.inverted_name.map(|v| v.into()), 92 | } 93 | } 94 | } 95 | 96 | #[cfg(test)] 97 | mod tests { 98 | use super::{Document, Kind, Scope}; 99 | 100 | #[test] 101 | fn load_iso_639_3() { 102 | const TEST_DATA: &str = r#" 103 | { 104 | "639-3": [ 105 | { 106 | "alpha_3": "gld", 107 | "name": "Nanai", 108 | "scope": "I", 109 | "type": "L" 110 | }, 111 | { 112 | "alpha_2": "ga", 113 | "alpha_3": "gle", 114 | "name": "Irish", 115 | "scope": "I", 116 | "type": "L" 117 | }, 118 | { 119 | "alpha_2": "gl", 120 | "alpha_3": "glg", 121 | "name": "Galician", 122 | "scope": "I", 123 | "type": "L" 124 | } 125 | ] 126 | } 127 | "#; 128 | 129 | let loaded = serde_json::from_str::>(TEST_DATA).expect("Failed to decode ISO-639-3 data"); 130 | let ga = loaded 131 | .entries 132 | .iter() 133 | .find(|i| i.code == "gle") 134 | .expect("Failed to find GLE"); 135 | assert!(matches!(ga.scope, Scope::Individual)); 136 | assert!(matches!(ga.kind, Kind::Living)); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /crates/system/src/locale/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | use std::fmt; 6 | 7 | use thiserror::Error; 8 | 9 | /// Locale joins Territory + Language 10 | #[derive(Debug)] 11 | pub struct Locale<'a> { 12 | pub name: String, 13 | pub display_name: String, 14 | pub language: &'a Language, 15 | pub territory: &'a Territory, 16 | pub modifier: Option, 17 | pub codeset: Option, 18 | } 19 | 20 | impl fmt::Display for Locale<'_> { 21 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 22 | f.write_str(&self.display_name) 23 | } 24 | } 25 | 26 | /// Sane representation for UI purposes 27 | #[derive(PartialEq, Eq, Debug)] 28 | pub struct Territory { 29 | pub code: String, 30 | pub code2: String, 31 | pub display_name: String, 32 | pub flag: String, 33 | } 34 | 35 | /// Simplistic language representation 36 | #[derive(PartialEq, Eq, Debug)] 37 | pub struct Language { 38 | pub code: String, 39 | pub code2: Option, 40 | pub display_name: String, 41 | pub inverted_name: Option, 42 | } 43 | 44 | #[derive(Debug, Error)] 45 | pub enum Error { 46 | #[error("io: {0}")] 47 | IO(#[from] std::io::Error), 48 | 49 | #[error("json parsing error: {0}")] 50 | Serde(#[from] serde_json::Error), 51 | } 52 | 53 | mod iso_3166; 54 | mod iso_639_2; 55 | mod iso_639_3; 56 | 57 | mod registry; 58 | pub use registry::Registry; 59 | -------------------------------------------------------------------------------- /crates/system/src/locale/registry.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! Registry of languages and territories. 6 | 7 | use std::collections::HashMap; 8 | 9 | use fs_err as fs; 10 | 11 | use super::{iso_3166, iso_639_2, iso_639_3, Error, Language, Locale, Territory}; 12 | 13 | /// All ISO codes are expected to live in this location 14 | const ISO_CODES_BASE: &str = "/usr/share/iso-codes/json"; 15 | 16 | /// Manage locales + territories 17 | pub struct Registry { 18 | places: Vec, 19 | places_lookup: HashMap, 20 | languages: Vec, 21 | languages_lookup: HashMap, 22 | } 23 | 24 | impl Registry { 25 | /// Create a new locale registry from the system iso-code JSON definitions 26 | pub fn new() -> Result { 27 | let places = Self::load_territories()?; 28 | let mut places_lookup = HashMap::new(); 29 | for (index, item) in places.iter().enumerate() { 30 | places_lookup.insert(item.code2.to_lowercase(), index); 31 | places_lookup.insert(item.code.to_lowercase(), index); 32 | } 33 | 34 | // Convert all languages into usable ones with mapping 35 | let mut languages = Self::load_languages_2()?; 36 | languages.extend(Self::load_languages_3()?); 37 | let mut languages_lookup = HashMap::new(); 38 | for (index, language) in languages.iter().enumerate() { 39 | if let Some(code2) = language.code2.as_ref() { 40 | languages_lookup.insert(code2.to_lowercase(), index); 41 | } 42 | languages_lookup.insert(language.code.to_lowercase(), index); 43 | } 44 | 45 | Ok(Self { 46 | places, 47 | places_lookup, 48 | languages, 49 | languages_lookup, 50 | }) 51 | } 52 | 53 | /// Load all the territories 54 | fn load_territories() -> Result, Error> { 55 | // Load the territories in 56 | let territories = format!("{}/iso_3166-1.json", ISO_CODES_BASE); 57 | let contents = fs::read_to_string(territories)?; 58 | let parser = serde_json::from_str::>(&contents)?; 59 | 60 | Ok(parser.entries.iter().map(|e| e.into()).collect::>()) 61 | } 62 | 63 | /// Load the 2 DB 64 | fn load_languages_2() -> Result, Error> { 65 | let languages = format!("{}/iso_639-2.json", ISO_CODES_BASE); 66 | let contents = fs::read_to_string(languages)?; 67 | let parser = serde_json::from_str::>(&contents)?; 68 | 69 | Ok(parser.entries.iter().map(|e| e.into()).collect::>()) 70 | } 71 | 72 | /// Load the 3 DB 73 | fn load_languages_3() -> Result, Error> { 74 | let languages = format!("{}/iso_639-3.json", ISO_CODES_BASE); 75 | let contents = fs::read_to_string(languages)?; 76 | let parser = serde_json::from_str::>(&contents)?; 77 | 78 | Ok(parser.entries.iter().map(|e| e.into()).collect::>()) 79 | } 80 | 81 | /// Retrieve the territory for the given (lower-case) code 82 | pub fn territory(&self, id: impl AsRef) -> Option<&Territory> { 83 | if let Some(idx) = self.places_lookup.get(id.as_ref()) { 84 | self.places.get(*idx) 85 | } else { 86 | None 87 | } 88 | } 89 | 90 | /// Retrieve the language for the given (lower-case) code 91 | pub fn language(&self, id: impl AsRef) -> Option<&Language> { 92 | if let Some(idx) = self.languages_lookup.get(id.as_ref()) { 93 | self.languages.get(*idx) 94 | } else { 95 | None 96 | } 97 | } 98 | 99 | /// Attempt to retrieve a locale combination 100 | pub fn locale(&self, id: impl AsRef) -> Option> { 101 | let id = id.as_ref().to_lowercase(); 102 | 103 | // Handle .codeset 104 | let (left, codeset) = if let Some(idx) = id.find('.') { 105 | id.split_at(idx) 106 | } else { 107 | (id.as_str(), "") 108 | }; 109 | 110 | // Fix "utf8" codeset 111 | let codeset = if codeset.is_empty() { 112 | None 113 | } else { 114 | Some( 115 | codeset 116 | .replace("utf8", "UTF-8") 117 | .chars() 118 | .skip(1) 119 | .collect::() 120 | .to_uppercase(), 121 | ) 122 | }; 123 | 124 | // Now handle a modifier 125 | let (code, modifier) = if let Some(idx) = left.find('@') { 126 | left.split_at(idx) 127 | } else { 128 | (left, "") 129 | }; 130 | let modifier = if modifier.is_empty() { 131 | None 132 | } else { 133 | Some(modifier.chars().skip(1).collect::().to_uppercase()) 134 | }; 135 | 136 | // Split on '_' and map into language/territory 137 | let (l_code, t_code) = code.split_once('_')?; 138 | let language = self.language(l_code)?; 139 | let territory = self.territory(t_code)?; 140 | 141 | // Cook functioning names/ids with fixed formatting 142 | let display_name = format!("{} ({})", &language.display_name, &territory.display_name); 143 | let mut new_id = Vec::new(); 144 | new_id.push(l_code.into()); 145 | new_id.push("_".into()); 146 | new_id.push(t_code.to_uppercase()); 147 | if let Some(m) = modifier.as_ref() { 148 | new_id.push(format!("@{m}")); 149 | } 150 | if let Some(codeset) = codeset.as_ref() { 151 | new_id.push(format!(".{}", codeset)); 152 | } 153 | 154 | Some(Locale { 155 | name: new_id.into_iter().collect(), 156 | display_name, 157 | language, 158 | territory, 159 | codeset, 160 | modifier, 161 | }) 162 | } 163 | } 164 | 165 | #[cfg(test)] 166 | mod tests { 167 | use std::process::Command; 168 | 169 | use super::Registry; 170 | 171 | #[test] 172 | fn test_territory() { 173 | let r = Registry::new().expect("Failed to initialise registry"); 174 | let ie = r.territory("ie").expect("Cannot find Ireland by ie"); 175 | let irl = r.territory("irl").expect("Cannot find Ireland by irl"); 176 | assert_eq!(ie, irl); 177 | assert_eq!(irl.display_name, "Ireland"); 178 | 179 | let dk = r.territory("dk").expect("Cannot find Denmark by dk"); 180 | assert_eq!(dk.display_name, "Kingdom of Denmark"); 181 | eprintln!("dk = {dk:?}"); 182 | } 183 | 184 | #[test] 185 | fn test_language() { 186 | let r = Registry::new().expect("Failed to initialise registry"); 187 | let en = r.language("en").expect("Cannot find English by en"); 188 | assert_eq!(en.display_name, "English"); 189 | 190 | let dan = r.language("dan").expect("Cannot find Danish by dan"); 191 | let dn = r.language("da").expect("Cannot find Danish by dn"); 192 | assert_eq!(dan, dn); 193 | } 194 | 195 | #[test] 196 | fn test_locale() { 197 | let r = Registry::new().expect("Failed to initialise registry"); 198 | let en_ie = r.locale("en_IE.UTF-8").expect("Failed to find en_IE.UTF-8"); 199 | assert_eq!(en_ie.display_name, "English (Ireland)"); 200 | let ga_ie = r.locale("ga_IE.UTF-8").expect("Failed to find ga_IE.UTF-8"); 201 | assert_eq!(ga_ie.display_name, "Irish (Ireland)"); 202 | 203 | eprintln!("en_IE = {en_ie:?}"); 204 | eprintln!("ga_IE = {ga_ie:?}"); 205 | } 206 | 207 | #[test] 208 | fn test_get_locales() { 209 | let r = Registry::new().expect("Failed to initialise registry"); 210 | let output = Command::new("localectl") 211 | .arg("list-locales") 212 | .output() 213 | .expect("Failed to run localectl"); 214 | let output = String::from_utf8(output.stdout).expect("Cannot decode output"); 215 | for line in output.lines() { 216 | if line == "C.UTF-8" { 217 | continue; 218 | } 219 | eprintln!("looking up {line}"); 220 | let locale = r 221 | .locale(line) 222 | .unwrap_or_else(|| panic!("Failed to find a predefined locale {line}")); 223 | eprintln!("locale {line} = {locale:?}"); 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /lichen_cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lichen_cli" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | color-eyre.workspace = true 8 | chrono-tz.workspace = true 9 | crossterm.workspace = true 10 | cliclack = { git = "https://github.com/ikeycode/cliclack.git", rev = "35a1882c601b90bf1398c3cb867cc6b20bbe9ce9" } 11 | dialoguer = { version = "0.11.0", features = ["completion", "fuzzy-matcher", "fuzzy-select"] } 12 | indicatif = "0.17.8" 13 | indoc = "2.0.5" 14 | installer = { path = "../crates/installer" } 15 | console = "0.15.8" 16 | nix.workspace = true 17 | env_logger.workspace = true 18 | 19 | [lints] 20 | workspace = true 21 | -------------------------------------------------------------------------------- /lichen_cli/src/main.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | //! Super basic CLI runner for lichen 6 | 7 | use std::{ 8 | io::Write, 9 | path::PathBuf, 10 | process::{Command, Output, Stdio}, 11 | str::FromStr, 12 | time::Duration, 13 | }; 14 | 15 | use color_eyre::eyre::ensure; 16 | use console::{set_colors_enabled, style}; 17 | use crossterm::style::Stylize; 18 | use indicatif::ProgressStyle; 19 | use indoc::indoc; 20 | use installer::{ 21 | selections::{self, Group}, 22 | steps::Context, 23 | systemd, Account, BootPartition, Installer, Locale, SystemPartition, 24 | }; 25 | use nix::libc::geteuid; 26 | 27 | #[derive(Debug)] 28 | struct CliContext { 29 | root: PathBuf, 30 | } 31 | 32 | impl<'a> Context<'a> for CliContext { 33 | /// Return root of our ops 34 | fn root(&'a self) -> &'a PathBuf { 35 | &self.root 36 | } 37 | 38 | /// Run a step command 39 | /// Right now all output is dumped to stdout/stderr 40 | fn run_command(&self, cmd: &mut Command) -> Result<(), installer::steps::Error> { 41 | let status = cmd.spawn()?.wait()?; 42 | if !status.success() { 43 | let program = cmd.get_program().to_string_lossy().into(); 44 | return Err(installer::steps::Error::CommandFailed { program, status }); 45 | } 46 | Ok(()) 47 | } 48 | 49 | /// Run a step command, capture stdout 50 | fn run_command_captured(&self, cmd: &mut Command, input: Option<&str>) -> Result { 51 | cmd.stdin(Stdio::piped()); 52 | cmd.stdout(Stdio::piped()); 53 | cmd.stderr(Stdio::piped()); 54 | let mut ps = cmd.spawn()?; 55 | let mut stdin = ps.stdin.take().expect("stdin failure"); 56 | 57 | if let Some(input) = input { 58 | stdin.write_all(input.as_bytes())?; 59 | } 60 | drop(stdin); 61 | 62 | let output = ps.wait_with_output()?; 63 | Ok(output) 64 | } 65 | } 66 | 67 | /// Ask the user what locale to use 68 | fn ask_locale<'a>(locales: &'a [Locale<'a>]) -> color_eyre::Result<&'a Locale<'a>> { 69 | let locales_disp = locales.iter().enumerate().map(|(i, l)| (i, l, "")).collect::>(); 70 | let index = cliclack::select("Pick a locale") 71 | .items(locales_disp.as_slice()) 72 | .initial_value(0) 73 | .filter_mode() 74 | .set_size(20) 75 | .interact()?; 76 | 77 | Ok(&locales[index]) 78 | } 79 | 80 | fn ask_timezone() -> color_eyre::Result { 81 | let variants = chrono_tz::TZ_VARIANTS 82 | .iter() 83 | .enumerate() 84 | .map(|(i, v)| (i, v, "")) 85 | .collect::>(); 86 | let index = cliclack::select("Pick a timezone") 87 | .items(variants.as_slice()) 88 | .initial_value(0) 89 | .filter_mode() 90 | .set_size(10) 91 | .interact()?; 92 | 93 | Ok(chrono_tz::TZ_VARIANTS[index].to_string()) 94 | } 95 | 96 | /// Pick an ESP please... 97 | fn ask_esp(parts: &[BootPartition]) -> color_eyre::Result<&BootPartition> { 98 | let parts_disp = parts 99 | .iter() 100 | .enumerate() 101 | .map(|(i, p)| (i, p.to_string(), "")) 102 | .collect::>(); 103 | ensure!( 104 | !parts_disp.is_empty(), 105 | "No disk with an available EFI system partition found. Exiting." 106 | ); 107 | let index = cliclack::select("Pick EFI system partition (ESP) + Linux extended boot partition (XBOOTLDR)") 108 | .items(parts_disp.as_slice()) 109 | .initial_value(0) 110 | .interact()?; 111 | Ok(&parts[index]) 112 | } 113 | 114 | /// Where's it going? 115 | fn ask_rootfs(parts: &[SystemPartition]) -> color_eyre::Result<&SystemPartition> { 116 | let parts_disp = parts 117 | .iter() 118 | .enumerate() 119 | .map(|(i, p)| (i, p.to_string(), "")) 120 | .collect::>(); 121 | ensure!( 122 | !parts_disp.is_empty(), 123 | "No disk with an available Linux partition for the system install root found. Exiting." 124 | ); 125 | let index = cliclack::select("Pick a suitably sized partition for the system install root (>20GiB)") 126 | .items(parts_disp.as_slice()) 127 | .initial_value(0) 128 | .interact()?; 129 | Ok(&parts[index]) 130 | } 131 | 132 | fn ask_filesystem() -> color_eyre::Result { 133 | let variants = [ 134 | ("xfs", "xfs", "Recommended (fast w/ moss hardlink rollbacks)"), 135 | ( 136 | "f2fs", 137 | "f2fs", 138 | "Not Recommended (surprisingly slow w/ moss hardlink rollbacks)", 139 | ), 140 | ( 141 | "ext4", 142 | "ext4", 143 | "Not Recommended (slow, limited moss hardlink rollback capacity)", 144 | ), 145 | ]; 146 | let index = cliclack::select("Pick a suitable filesystem for the system install root ('/')") 147 | .items(&variants) 148 | .initial_value("xfs") 149 | .interact()?; 150 | Ok(index.into()) 151 | } 152 | 153 | // Grab a password for the root account 154 | fn ask_password() -> color_eyre::Result { 155 | let password = cliclack::password("You'll need to set a default root (administrator) password").interact()?; 156 | let confirmed = cliclack::password("Confirm your password") 157 | .validate_interactively(move |v: &String| { 158 | if *v != password { 159 | return Err("Those passwords do not match"); 160 | } 161 | Ok(()) 162 | }) 163 | .interact()?; 164 | Ok(confirmed) 165 | } 166 | 167 | fn create_user() -> color_eyre::Result { 168 | cliclack::log::info("We now need to create a default (admin) user")?; 169 | let username: String = cliclack::input("Username?").interact()?; 170 | let password = cliclack::password("Pick a password").interact()?; 171 | let confirmed = cliclack::password("Now confirm the password") 172 | .validate_interactively(move |v: &String| { 173 | if *v != password { 174 | return Err("Those passwords do not match"); 175 | } 176 | Ok(()) 177 | }) 178 | .interact()?; 179 | Ok(Account::new(username) 180 | .with_password(confirmed) 181 | .with_shell("/usr/bin/bash")) 182 | } 183 | 184 | fn ask_desktop<'a>(desktops: &'a [&Group]) -> color_eyre::Result<&'a Group> { 185 | let displayable = desktops 186 | .iter() 187 | .enumerate() 188 | .map(|(i, d)| (i, &d.summary, &d.description)) 189 | .collect::>(); 190 | let index = cliclack::select("Pick a desktop environment to use") 191 | .items(displayable.as_slice()) 192 | .initial_value(1) 193 | .interact()?; 194 | 195 | Ok(desktops[index]) 196 | } 197 | 198 | fn main() -> color_eyre::Result<()> { 199 | env_logger::init(); 200 | color_eyre::install().unwrap(); 201 | set_colors_enabled(true); 202 | 203 | let euid = unsafe { geteuid() }; 204 | ensure!(euid == 0, "lichen must be run as root. Re-run with sudo."); 205 | 206 | let partition_detection_warning = indoc! {" 207 | The installer currently does not attempt to detect if there is a file system 208 | on detected ESP (and XBOOTLDR) partitions. 209 | 210 | Please ensure that the EFI system partition (ESP) and the Linux extended boot 211 | (XBOOTLDR) partition are both formatted as FAT32. 212 | 213 | It may be a good idea to check this in gparted (or fdisk) now: 214 | - The EFI system partition (>=256MiB) should have the flag 'esp' in gparted 215 | - This corresponds to type 1 in fdisk. 216 | - The Linux extended boot partition (storing kernels and initrds, 4GiB) 217 | should have the flag 'bls_boot' in gparted 218 | - This corresponds to type 142 in fdisk. 219 | 220 | NOTE: Users planning to re-install Serpent OS later on, may want to reserve 221 | space for a separate /home partition (not handled by this installer). 222 | 223 | If changes need to be made to partitions, please do so now before continuing. 224 | "}; 225 | cliclack::log::warning(format!( 226 | "{} This is an alpha quality Serpent OS installer.\n\n{}", 227 | style("Warning:").bold(), 228 | partition_detection_warning 229 | ))?; 230 | 231 | let should_continue = cliclack::confirm("Are you ready to have lichen detect your partitions?").interact()?; 232 | ensure!(should_continue, "User chose to abort before detecting partitions."); 233 | 234 | cliclack::intro(style("Install Serpent OS").bold())?; 235 | 236 | // Test selection management, force GNOME 237 | let selections = selections::Manager::new().with_groups([ 238 | Group::from_str(include_str!("../../selections/base.json"))?, 239 | Group::from_str(include_str!("../../selections/cosmic.json"))?, 240 | Group::from_str(include_str!("../../selections/develop.json"))?, 241 | Group::from_str(include_str!("../../selections/gnome.json"))?, 242 | Group::from_str(include_str!("../../selections/kernel-common.json"))?, 243 | Group::from_str(include_str!("../../selections/kernel-desktop.json"))?, 244 | ]); 245 | 246 | let desktops = selections 247 | .groups() 248 | .filter(|g| g.name == "cosmic" || g.name == "gnome") 249 | .collect::>(); 250 | 251 | let sp = cliclack::spinner(); 252 | sp.start("Loading"); 253 | 254 | // Load all the things 255 | let inst = Installer::new()?; 256 | let boots = inst.boot_partitions(); 257 | let parts = inst.system_partitions(); 258 | let locales = inst.locales_for_ids(systemd::localectl_list_locales()?)?; 259 | 260 | sp.clear(); 261 | 262 | // TODO: The smart move would be to actually probe the partitions for a valid FS here, 263 | // because we will want to optionally set the partition type and format them 264 | // to the correct fs if this hasn't already been done. 265 | let esp = ask_esp(boots)?; 266 | 267 | let mut rootfs = ask_rootfs(parts)?.clone(); 268 | rootfs.mountpoint = Some("/".into()); 269 | let fs = ask_filesystem()?; 270 | 271 | let selected_desktop = ask_desktop(&desktops)?; 272 | let selected_locale = ask_locale(&locales)?; 273 | let timezone = ask_timezone()?; 274 | let keyboard_layout_warning = indoc! {" 275 | Note that the keyboard layout for the current virtual terminal is controlled 276 | via the GNOME Settings application. 277 | 278 | If a new keyboard layout is added there, please be aware that it may be 279 | necessary to exit the installer, open a new virtual terminal, and restart the 280 | installer in the new virtual terminal. 281 | 282 | Otherwise, the desired keyboard layout may not be active when entering user 283 | passwords in the following steps. 284 | "}; 285 | cliclack::log::warning(keyboard_layout_warning)?; 286 | let rootpw = ask_password()?; 287 | let user_account = create_user()?; 288 | 289 | let summary = |title: &str, value: &str| format!("{}: {}", style(title).bold(), value); 290 | 291 | let note = [ 292 | summary("Locale", &selected_locale.to_string()), 293 | summary("Timezone", &timezone), 294 | summary("Bootloader", &esp.to_string()), 295 | summary("Root (/) partition", &rootfs.to_string()), 296 | summary("Root (/) filesystem", &fs), 297 | ]; 298 | 299 | cliclack::note("Installation summary", note.join("\n"))?; 300 | 301 | let model = installer::Model { 302 | accounts: [Account::root().with_password(rootpw), user_account].into(), 303 | boot_partition: esp.to_owned(), 304 | partitions: [rootfs.clone()].into(), 305 | locale: Some(selected_locale), 306 | timezone: Some(timezone), 307 | rootfs_type: fs, 308 | packages: selections.selections_with(["develop", &selected_desktop.name, "kernel-desktop"])?, 309 | }; 310 | 311 | let y = cliclack::confirm("Do you want to install?").interact()?; 312 | if !y { 313 | cliclack::outro_cancel("No changes have been made to your system")?; 314 | return Ok(()); 315 | } 316 | 317 | cliclack::outro("Now proceeding with installation")?; 318 | 319 | // TODO: Use proper temp directory 320 | let context = CliContext { 321 | root: "/tmp/lichen".into(), 322 | }; 323 | let (cleanups, steps) = inst.compile_to_steps(&model, &context)?; 324 | let multi = indicatif::MultiProgress::new(); 325 | let total = indicatif::ProgressBar::new(steps.len() as u64 + cleanups.len() as u64).with_style( 326 | ProgressStyle::with_template("\n|{bar:20.cyan/blue}| {pos}/{len}") 327 | .unwrap() 328 | .progress_chars("■≡=- "), 329 | ); 330 | 331 | let total = multi.add(total); 332 | for step in steps { 333 | total.inc(1); 334 | if step.is_indeterminate() { 335 | let progress_bar = multi.insert_before( 336 | &total, 337 | indicatif::ProgressBar::new(1) 338 | .with_message(format!("{} {}", step.title().blue(), step.describe().bold(),)) 339 | .with_style( 340 | ProgressStyle::with_template(" {spinner} {wide_msg} ") 341 | .unwrap() 342 | .tick_chars("--=≡■≡=--"), 343 | ), 344 | ); 345 | progress_bar.enable_steady_tick(Duration::from_millis(150)); 346 | step.execute(&context)?; 347 | } else { 348 | multi.println(format!("{} {}", step.title().blue(), step.describe().bold()))?; 349 | multi.suspend(|| step.execute(&context))?; 350 | } 351 | } 352 | 353 | // Execute all the cleanups 354 | for cleanup in cleanups { 355 | let progress_bar = multi.insert_before( 356 | &total, 357 | indicatif::ProgressBar::new(1) 358 | .with_message(format!("{} {}", cleanup.title().yellow(), cleanup.describe().bold(),)) 359 | .with_style( 360 | ProgressStyle::with_template(" {spinner} {wide_msg} ") 361 | .unwrap() 362 | .tick_chars("--=≡■≡=--"), 363 | ), 364 | ); 365 | progress_bar.enable_steady_tick(Duration::from_millis(150)); 366 | total.inc(1); 367 | cleanup.execute(&context)?; 368 | } 369 | let home_note = indoc!( 370 | " 371 | NOTE: If you reserved space for a separate /home partition above, now would 372 | be a good time to format it with your filesystem of choice, and to 373 | ensure that it is enabled in the /etc/fstab file in the new install. 374 | 375 | Remember to copy/move the new /home/${USER} directory created by the 376 | installer in the / partition to the new /home partition. 377 | " 378 | ); 379 | let installer_success = format!( 380 | "🎉 🥳 Succesfully installed {}! Reboot now to start using it!", 381 | style("Serpent OS").bold() 382 | ); 383 | multi.clear()?; 384 | println!("\n{}\n{}\n", home_note, installer_success); 385 | 386 | Ok(()) 387 | } 388 | -------------------------------------------------------------------------------- /lichen_ipc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lichen_ipc" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [build-dependencies] 7 | varlink_generator.workspace = true 8 | 9 | [dependencies] 10 | log.workspace = true 11 | system = { path = "../crates/system"} 12 | nix.workspace = true 13 | serde.workspace = true 14 | serde_derive.workspace = true 15 | serde_json.workspace = true 16 | varlink.workspace = true 17 | clap = { version = "4.5.21", features = ["derive"] } 18 | color-eyre.workspace = true 19 | pretty_env_logger = { version = "0.5.0" } 20 | 21 | [lints] 22 | workspace = true 23 | -------------------------------------------------------------------------------- /lichen_ipc/README.md: -------------------------------------------------------------------------------- 1 | # lichen_ipc 2 | 3 | This crate contains the `lichen_ipc` executable, a helper backend for the lichen 4 | installer that communicates using the [varlink](https://varlink.org) protocol over 5 | a local UNIX socket. 6 | 7 | It is intended to be launched by a compatible frontend via some escalation system, such 8 | as `pkexec` or `sudo`, ensuring that the frontend can communicate with the backend without 9 | running as root itself. 10 | 11 | It is in a very early stage, but eventually will be the entire backend of our installer. 12 | 13 | ## Testing 14 | 15 | First, launch the backend: 16 | 17 | ```sh 18 | pkexec ./target/debug/lichen_ipc --varlink=unix:@testinglichen 19 | ``` 20 | 21 | Then, in another terminal, run the [varlink cli](https://crates.io/crates/varlink-cli) 22 | 23 | ### Disk enumeration 24 | 25 | ```sh 26 | varlink call unix:@testinglichen/com.serpentos.lichen.disks.GetDisks 27 | ``` 28 | ```json 29 | { 30 | "disks": [ 31 | { 32 | "block_size": 512, 33 | "kind": "ssd", 34 | "model": "WD_BLACK SN770 1TB", 35 | "path": "/dev/nvme0n1", 36 | "size": 1953525168, 37 | "vendor": null 38 | } 39 | ] 40 | } 41 | ``` 42 | 43 | ### Partition enumeration 44 | 45 | ```sh 46 | varlink call unix:@testinglichen/com.serpentos.lichen.disks.GetPartitions '{"disk": "/dev/sda"}' 47 | ``` 48 | 49 | ```json 50 | { 51 | "partitions": [ 52 | { 53 | "kind": "esp", 54 | "path": "/dev/nvme0n1p1", 55 | "size": 134217728, 56 | "superblock_kind": "unknown", 57 | "uuid": "3a1338aa-d98b-45ef-b72b-62f9752ef2d2" 58 | }, 59 | { 60 | "kind": "xbootldr", 61 | "path": "/dev/nvme0n1p2", 62 | "size": 1073741824, 63 | "superblock_kind": "unknown", 64 | "uuid": "915127ba-acd8-45db-a865-a9d8329d6f26" 65 | }, 66 | { 67 | "kind": "regular", 68 | "path": "/dev/nvme0n1p3", 69 | "size": 4294967296, 70 | "superblock_kind": "unknown", 71 | "uuid": "2b89b961-4145-498f-8fb7-7b3178905c75" 72 | }, 73 | { 74 | "kind": "regular", 75 | "path": "/dev/nvme0n1p4", 76 | "size": 994700165120, 77 | "superblock_kind": "ext4", 78 | "uuid": "e1ee771f-0c94-45fe-b544-2d22e50784e0" 79 | } 80 | ] 81 | } 82 | ``` 83 | -------------------------------------------------------------------------------- /lichen_ipc/build.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | fn main() { 6 | println!("cargo:rerun-if-changed=src/com.serpentos.lichen.disks.varlink"); 7 | varlink_generator::cargo_build_tosource("src/com.serpentos.lichen.disks.varlink", true); 8 | } 9 | -------------------------------------------------------------------------------- /lichen_ipc/src/com.serpentos.lichen.disks.varlink: -------------------------------------------------------------------------------- 1 | # Disk enumeration APIs for Lichen 2 | interface com.serpentos.lichen.disks 3 | 4 | type Disk( 5 | kind: (ssd, hdd), 6 | path: string, 7 | model: ?string, 8 | vendor: ?string, 9 | size: int, 10 | block_size: int 11 | ) 12 | 13 | type Partition( 14 | path: string, 15 | kind: (esp, xbootldr, regular), 16 | size: int, 17 | uuid: string, 18 | superblock_kind: (btrfs, ext4, f2fs, luks2, xfs, unknown) 19 | ) 20 | 21 | error DiskError( 22 | message: string 23 | ) 24 | 25 | # Enumerate all known disks 26 | method GetDisks() -> (disks:[]Disk) 27 | method GetPartitions(disk: string) -> (partitions:[]Partition) 28 | -------------------------------------------------------------------------------- /lichen_ipc/src/com_serpentos_lichen_disks.rs: -------------------------------------------------------------------------------- 1 | #![doc = "This file was automatically generated by the varlink rust generator"] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | use serde_derive::{Deserialize, Serialize}; 5 | use std::io::BufRead; 6 | use std::sync::{Arc, RwLock}; 7 | use varlink::{self, CallTrait}; 8 | #[allow(dead_code)] 9 | #[derive(Clone, PartialEq, Debug)] 10 | #[allow(clippy::enum_variant_names)] 11 | pub enum ErrorKind { 12 | Varlink_Error, 13 | VarlinkReply_Error, 14 | DiskError(Option), 15 | } 16 | impl ::std::fmt::Display for ErrorKind { 17 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 18 | match self { 19 | ErrorKind::Varlink_Error => write!(f, "Varlink Error"), 20 | ErrorKind::VarlinkReply_Error => write!(f, "Varlink error reply"), 21 | ErrorKind::DiskError(v) => write!(f, "com.serpentos.lichen.disks.DiskError: {:#?}", v), 22 | } 23 | } 24 | } 25 | pub struct Error( 26 | pub ErrorKind, 27 | pub Option>, 28 | pub Option<&'static str>, 29 | ); 30 | impl Error { 31 | #[allow(dead_code)] 32 | pub fn kind(&self) -> &ErrorKind { 33 | &self.0 34 | } 35 | } 36 | impl From for Error { 37 | fn from(e: ErrorKind) -> Self { 38 | Error(e, None, None) 39 | } 40 | } 41 | impl std::error::Error for Error { 42 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 43 | self.1 44 | .as_ref() 45 | .map(|e| e.as_ref() as &(dyn std::error::Error + 'static)) 46 | } 47 | } 48 | impl std::fmt::Display for Error { 49 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 50 | std::fmt::Display::fmt(&self.0, f) 51 | } 52 | } 53 | impl std::fmt::Debug for Error { 54 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 55 | use std::error::Error as StdError; 56 | if let Some(ref o) = self.2 { 57 | std::fmt::Display::fmt(o, f)?; 58 | } 59 | std::fmt::Debug::fmt(&self.0, f)?; 60 | if let Some(e) = self.source() { 61 | std::fmt::Display::fmt("\nCaused by:\n", f)?; 62 | std::fmt::Debug::fmt(&e, f)?; 63 | } 64 | Ok(()) 65 | } 66 | } 67 | #[allow(dead_code)] 68 | pub type Result = std::result::Result; 69 | impl From for Error { 70 | fn from(e: varlink::Error) -> Self { 71 | match e.kind() { 72 | varlink::ErrorKind::VarlinkErrorReply(r) => Error( 73 | ErrorKind::from(r), 74 | Some(Box::from(e)), 75 | Some(concat!(file!(), ":", line!(), ": ")), 76 | ), 77 | _ => Error( 78 | ErrorKind::Varlink_Error, 79 | Some(Box::from(e)), 80 | Some(concat!(file!(), ":", line!(), ": ")), 81 | ), 82 | } 83 | } 84 | } 85 | #[allow(dead_code)] 86 | impl Error { 87 | pub fn source_varlink_kind(&self) -> Option<&varlink::ErrorKind> { 88 | use std::error::Error as StdError; 89 | let mut s: &dyn StdError = self; 90 | while let Some(c) = s.source() { 91 | let k = self 92 | .source() 93 | .and_then(|e| e.downcast_ref::()) 94 | .map(|e| e.kind()); 95 | if k.is_some() { 96 | return k; 97 | } 98 | s = c; 99 | } 100 | None 101 | } 102 | } 103 | impl From<&varlink::Reply> for ErrorKind { 104 | #[allow(unused_variables)] 105 | fn from(e: &varlink::Reply) -> Self { 106 | match e { 107 | varlink::Reply { error: Some(ref t), .. } if t == "com.serpentos.lichen.disks.DiskError" => match e { 108 | varlink::Reply { 109 | parameters: Some(p), .. 110 | } => match serde_json::from_value(p.clone()) { 111 | Ok(v) => ErrorKind::DiskError(v), 112 | Err(_) => ErrorKind::DiskError(None), 113 | }, 114 | _ => ErrorKind::DiskError(None), 115 | }, 116 | _ => ErrorKind::VarlinkReply_Error, 117 | } 118 | } 119 | } 120 | pub trait VarlinkCallError: varlink::CallTrait { 121 | fn reply_disk_error(&mut self, r#message: String) -> varlink::Result<()> { 122 | self.reply_struct(varlink::Reply::error( 123 | "com.serpentos.lichen.disks.DiskError", 124 | Some(serde_json::to_value(DiskError_Args { r#message }).map_err(varlink::map_context!())?), 125 | )) 126 | } 127 | } 128 | impl<'a> VarlinkCallError for varlink::Call<'a> {} 129 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 130 | pub enum r#Disk_kind { 131 | r#ssd, 132 | r#hdd, 133 | } 134 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 135 | pub struct r#Disk { 136 | pub r#kind: Disk_kind, 137 | pub r#path: String, 138 | pub r#model: Option, 139 | pub r#vendor: Option, 140 | pub r#size: i64, 141 | pub r#block_size: i64, 142 | } 143 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 144 | pub enum r#Partition_kind { 145 | r#esp, 146 | r#xbootldr, 147 | r#regular, 148 | } 149 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 150 | pub enum r#Partition_superblock_kind { 151 | r#btrfs, 152 | r#ext4, 153 | r#f2fs, 154 | r#luks2, 155 | r#xfs, 156 | r#unknown, 157 | } 158 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 159 | pub struct r#Partition { 160 | pub r#path: String, 161 | pub r#kind: Partition_kind, 162 | pub r#size: i64, 163 | pub r#uuid: String, 164 | pub r#superblock_kind: Partition_superblock_kind, 165 | } 166 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 167 | pub struct DiskError_Args { 168 | pub r#message: String, 169 | } 170 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 171 | pub struct GetDisks_Reply { 172 | pub r#disks: Vec, 173 | } 174 | impl varlink::VarlinkReply for GetDisks_Reply {} 175 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 176 | pub struct GetDisks_Args {} 177 | pub trait Call_GetDisks: VarlinkCallError { 178 | fn reply(&mut self, r#disks: Vec) -> varlink::Result<()> { 179 | self.reply_struct(GetDisks_Reply { r#disks }.into()) 180 | } 181 | } 182 | impl<'a> Call_GetDisks for varlink::Call<'a> {} 183 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 184 | pub struct GetPartitions_Reply { 185 | pub r#partitions: Vec, 186 | } 187 | impl varlink::VarlinkReply for GetPartitions_Reply {} 188 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 189 | pub struct GetPartitions_Args { 190 | pub r#disk: String, 191 | } 192 | pub trait Call_GetPartitions: VarlinkCallError { 193 | fn reply(&mut self, r#partitions: Vec) -> varlink::Result<()> { 194 | self.reply_struct(GetPartitions_Reply { r#partitions }.into()) 195 | } 196 | } 197 | impl<'a> Call_GetPartitions for varlink::Call<'a> {} 198 | pub trait VarlinkInterface { 199 | fn get_disks(&self, call: &mut dyn Call_GetDisks) -> varlink::Result<()>; 200 | fn get_partitions(&self, call: &mut dyn Call_GetPartitions, r#disk: String) -> varlink::Result<()>; 201 | fn call_upgraded(&self, _call: &mut varlink::Call, _bufreader: &mut dyn BufRead) -> varlink::Result> { 202 | Ok(Vec::new()) 203 | } 204 | } 205 | pub trait VarlinkClientInterface { 206 | fn get_disks(&mut self) -> varlink::MethodCall; 207 | fn get_partitions(&mut self, r#disk: String) 208 | -> varlink::MethodCall; 209 | } 210 | #[allow(dead_code)] 211 | pub struct VarlinkClient { 212 | connection: Arc>, 213 | } 214 | impl VarlinkClient { 215 | #[allow(dead_code)] 216 | pub fn new(connection: Arc>) -> Self { 217 | VarlinkClient { connection } 218 | } 219 | } 220 | impl VarlinkClientInterface for VarlinkClient { 221 | fn get_disks(&mut self) -> varlink::MethodCall { 222 | varlink::MethodCall::::new( 223 | self.connection.clone(), 224 | "com.serpentos.lichen.disks.GetDisks", 225 | GetDisks_Args {}, 226 | ) 227 | } 228 | fn get_partitions( 229 | &mut self, 230 | r#disk: String, 231 | ) -> varlink::MethodCall { 232 | varlink::MethodCall::::new( 233 | self.connection.clone(), 234 | "com.serpentos.lichen.disks.GetPartitions", 235 | GetPartitions_Args { r#disk }, 236 | ) 237 | } 238 | } 239 | #[allow(dead_code)] 240 | pub struct VarlinkInterfaceProxy { 241 | inner: Box, 242 | } 243 | #[allow(dead_code)] 244 | pub fn new(inner: Box) -> VarlinkInterfaceProxy { 245 | VarlinkInterfaceProxy { inner } 246 | } 247 | impl varlink::Interface for VarlinkInterfaceProxy { 248 | fn get_description(&self) -> &'static str { 249 | "# Disk enumeration APIs for Lichen\ninterface com.serpentos.lichen.disks\n\ntype Disk(\n kind: (ssd, hdd),\n path: string,\n model: ?string,\n vendor: ?string,\n size: int,\n block_size: int\n)\n\ntype Partition(\n path: string,\n kind: (esp, xbootldr, regular),\n size: int,\n uuid: string,\n superblock_kind: (btrfs, ext4, f2fs, luks2, xfs, unknown)\n)\n\nerror DiskError(\n message: string\n)\n\n# Enumerate all known disks\nmethod GetDisks() -> (disks:[]Disk)\nmethod GetPartitions(disk: string) -> (partitions:[]Partition)\n" 250 | } 251 | fn get_name(&self) -> &'static str { 252 | "com.serpentos.lichen.disks" 253 | } 254 | fn call_upgraded(&self, call: &mut varlink::Call, bufreader: &mut dyn BufRead) -> varlink::Result> { 255 | self.inner.call_upgraded(call, bufreader) 256 | } 257 | fn call(&self, call: &mut varlink::Call) -> varlink::Result<()> { 258 | let req = call.request.unwrap(); 259 | match req.method.as_ref() { 260 | "com.serpentos.lichen.disks.GetDisks" => self.inner.get_disks(call as &mut dyn Call_GetDisks), 261 | "com.serpentos.lichen.disks.GetPartitions" => { 262 | if let Some(args) = req.parameters.clone() { 263 | let args: GetPartitions_Args = match serde_json::from_value(args) { 264 | Ok(v) => v, 265 | Err(e) => { 266 | let es = format!("{}", e); 267 | let _ = call.reply_invalid_parameter(es.clone()); 268 | return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); 269 | } 270 | }; 271 | self.inner 272 | .get_partitions(call as &mut dyn Call_GetPartitions, args.r#disk) 273 | } else { 274 | call.reply_invalid_parameter("parameters".into()) 275 | } 276 | } 277 | m => call.reply_method_not_found(String::from(m)), 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /lichen_ipc/src/disks.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | use log::debug; 6 | 7 | use crate::disks_ipc; 8 | use std::collections::HashMap; 9 | use std::sync::{Arc, RwLock}; 10 | 11 | /// Disk Service struct for Lichen 12 | pub struct Service { 13 | disks_cache: Arc>>, 14 | parts_cache: Arc>>>, 15 | } 16 | 17 | impl Default for Service { 18 | fn default() -> Self { 19 | Self::new() 20 | } 21 | } 22 | 23 | impl Service { 24 | /// Creates a new instance of Service 25 | pub fn new() -> Self { 26 | Self { 27 | disks_cache: Arc::new(RwLock::new(Vec::new())), 28 | parts_cache: Arc::new(RwLock::new(HashMap::new())), 29 | } 30 | } 31 | } 32 | 33 | impl disks_ipc::VarlinkInterface for Service { 34 | /// Retrieves the list of disks 35 | fn get_disks(&self, call: &mut dyn disks_ipc::Call_GetDisks) -> varlink::Result<()> { 36 | if let Ok(read_cell) = self.disks_cache.read() { 37 | if !read_cell.is_empty() { 38 | debug!("restoring from disk cache"); 39 | call.reply(read_cell.clone())?; 40 | return Ok(()); 41 | } 42 | } 43 | 44 | // Acquire a lock 45 | let mut cell = match self.disks_cache.write() { 46 | Ok(c) => c, 47 | Err(_) => return call.reply_disk_error("Cannot lock cache".to_string()), 48 | }; 49 | 50 | match system::disk::Disk::discover() { 51 | Ok(disks) => { 52 | let ret = disks 53 | .iter() 54 | .map(|d| disks_ipc::Disk { 55 | kind: match d.kind { 56 | system::disk::DiskKind::HDD => disks_ipc::Disk_kind::hdd, 57 | system::disk::DiskKind::SSD => disks_ipc::Disk_kind::ssd, 58 | }, 59 | path: d.path.to_string_lossy().to_string(), 60 | model: d.model.clone(), 61 | vendor: d.vendor.clone(), 62 | size: d.size as i64, 63 | block_size: d.block_size as i64, 64 | }) 65 | .collect::>(); 66 | cell.extend(ret.clone()); 67 | call.reply(ret)?; 68 | } 69 | Err(e) => return call.reply_disk_error(e.to_string()), 70 | } 71 | 72 | Ok(()) 73 | } 74 | 75 | /// Retrieves the list of partitions for a given disk 76 | fn get_partitions(&self, call: &mut dyn disks_ipc::Call_GetPartitions, disk: String) -> varlink::Result<()> { 77 | if let Ok(read_cell) = self.parts_cache.read() { 78 | if let Some(cache) = read_cell.get(&disk) { 79 | debug!("restoring from partition cache"); 80 | call.reply(cache.clone())?; 81 | return Ok(()); 82 | } 83 | } 84 | 85 | // Not cached, so acquire write lock 86 | let mut cell = match self.parts_cache.write() { 87 | Ok(c) => c, 88 | Err(e) => return call.reply_disk_error(e.to_string()), 89 | }; 90 | 91 | match system::disk::Disk::from_sysfs_path(disk.replace("/dev/", "/sys/class/block/")) { 92 | Ok(disk) => match disk.partitions() { 93 | Ok(partitions) => { 94 | let res = partitions 95 | .iter() 96 | .map(|p| disks_ipc::Partition { 97 | path: p.path.to_string_lossy().to_string(), 98 | kind: match p.kind { 99 | system::disk::PartitionKind::ESP => disks_ipc::Partition_kind::esp, 100 | system::disk::PartitionKind::XBOOTLDR => disks_ipc::Partition_kind::xbootldr, 101 | system::disk::PartitionKind::Regular => disks_ipc::Partition_kind::regular, 102 | }, 103 | size: p.size as i64, 104 | uuid: p.uuid.clone(), 105 | superblock_kind: if let Some(sb) = p.sb.as_ref() { 106 | match sb { 107 | system::disk::SuperblockKind::Btrfs => disks_ipc::Partition_superblock_kind::btrfs, 108 | system::disk::SuperblockKind::Ext4 => disks_ipc::Partition_superblock_kind::ext4, 109 | system::disk::SuperblockKind::LUKS2 => disks_ipc::Partition_superblock_kind::luks2, 110 | system::disk::SuperblockKind::F2FS => disks_ipc::Partition_superblock_kind::f2fs, 111 | system::disk::SuperblockKind::XFS => disks_ipc::Partition_superblock_kind::xfs, 112 | } 113 | } else { 114 | disks_ipc::Partition_superblock_kind::unknown 115 | }, 116 | }) 117 | .collect::>(); 118 | 119 | // Cache the partitions 120 | cell.insert(disk.path.to_string_lossy().to_string(), res.clone()); 121 | call.reply(res)?; 122 | } 123 | Err(e) => return call.reply_disk_error(e.to_string()), 124 | }, 125 | Err(e) => return call.reply_disk_error(e.to_string()), 126 | } 127 | 128 | Ok(()) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /lichen_ipc/src/lib.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | #[allow( 6 | dead_code, 7 | elided_lifetimes_in_paths, 8 | unused_imports, 9 | unused_qualifications, 10 | clippy::needless_lifetimes 11 | )] 12 | mod com_serpentos_lichen_disks; 13 | pub mod disks_ipc { 14 | pub use crate::com_serpentos_lichen_disks::*; 15 | } 16 | 17 | pub mod disks; 18 | -------------------------------------------------------------------------------- /lichen_ipc/src/main.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | use std::env; 6 | 7 | use clap::Parser; 8 | use color_eyre::eyre::bail; 9 | use lichen_ipc::{disks, disks_ipc}; 10 | use pretty_env_logger::formatted_builder; 11 | use varlink::VarlinkService; 12 | 13 | /// Command line arguments parser 14 | #[derive(Parser)] 15 | struct Cli { 16 | /// Varlink socket address 17 | #[clap(long)] 18 | varlink: Option, 19 | } 20 | 21 | fn main() -> color_eyre::Result<()> { 22 | formatted_builder() 23 | .filter_level(log::LevelFilter::Info) 24 | .parse_default_env() 25 | .init(); 26 | 27 | color_eyre::install().unwrap(); 28 | 29 | let args = Cli::parse(); 30 | let socket = if let Some(varlink) = args.varlink { 31 | varlink 32 | } else if let Ok(varlink_address) = env::var("VARLINK_ADDRESS") { 33 | varlink_address 34 | } else { 35 | bail!("Usage: lichen-ipc --varlink "); 36 | }; 37 | 38 | // bind our interfaces to the varlink service 39 | let interface = disks_ipc::new(Box::new(disks::Service::new())); 40 | let service = VarlinkService::new( 41 | "Serpent OS", 42 | "Lichen Installer", 43 | "0.1", 44 | "https://serpentos.com/", 45 | vec![Box::new(interface)], 46 | ); 47 | 48 | log::info!("lichen-ipc now listening on {socket}"); 49 | 50 | varlink::listen( 51 | service, 52 | &socket, 53 | &varlink::ListenConfig { 54 | idle_timeout: 0, 55 | ..Default::default() 56 | }, 57 | )?; 58 | 59 | Ok(()) 60 | } 61 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | max_width = 120 3 | -------------------------------------------------------------------------------- /selections/README.md: -------------------------------------------------------------------------------- 1 | # selections 2 | 3 | This directory contains some selections for the target installation experience. 4 | 5 | ### base 6 | 7 | The mandatory bottom layer for all systems 8 | 9 | ### develop 10 | 11 | A set of additional packages to make Serpent OS development easier. 12 | 13 | ### gnome 14 | 15 | A full GNOME desktop featuring GDM 16 | 17 | ### kernel-common 18 | 19 | Required common pieces for kernels 20 | 21 | ### kernel-desktop 22 | 23 | Deskop specific kernel, firmware, etc. 24 | -------------------------------------------------------------------------------- /selections/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "base", 3 | "summary": "Base system", 4 | "description": "The base system for all Serpent OS installations.", 5 | "required": [ 6 | "bash-completion", 7 | "binary(bash)", 8 | "binary(dash)", 9 | "binary(file)", 10 | "binary(find)", 11 | "binary(gawk)", 12 | "binary(grep)", 13 | "binary(gzip)", 14 | "binary(less)", 15 | "binary(moss)", 16 | "binary(nano)", 17 | "binary(ping)", 18 | "binary(ps)", 19 | "binary(python)", 20 | "binary(screen)", 21 | "binary(sed)", 22 | "binary(setfont)", 23 | "binary(ssh)", 24 | "binary(sudo)", 25 | "binary(tar)", 26 | "binary(unzip)", 27 | "binary(wget)", 28 | "binary(which)", 29 | "sysbinary(fsck.ext4)", 30 | "sysbinary(fsck.f2fs)", 31 | "sysbinary(fsck.xfs)", 32 | "ca-certificates", 33 | "uutils-coreutils", 34 | "dbus", 35 | "dbus-broker", 36 | "iproute2", 37 | "shadow", 38 | "systemd", 39 | "tzdata", 40 | "util-linux" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /selections/cosmic.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cosmic", 3 | "summary": "COSMIC Desktop", 4 | "description": "Warning: Provided as an alpha preview", 5 | "depends": [ 6 | "base" 7 | ], 8 | "required": [ 9 | "cosmic-applets", 10 | "cosmic-desktop", 11 | "cosmic-greeter", 12 | "cosmic-bg", 13 | "cosmic-comp", 14 | "cosmic-edit", 15 | "cosmic-files", 16 | "cosmic-icons", 17 | "cosmic-launcher", 18 | "cosmic-notifications", 19 | "cosmic-osd", 20 | "cosmic-panel", 21 | "cosmic-player", 22 | "cosmic-randr", 23 | "cosmic-screenshot", 24 | "cosmic-session", 25 | "cosmic-settings", 26 | "cosmic-settings-daemon", 27 | "cosmic-store", 28 | "cosmic-term", 29 | "cosmic-wallpapers", 30 | "cosmic-workspaces", 31 | "serpent-artwork", 32 | "xdg-desktop-portal-cosmic", 33 | "xdg-desktop-portal-gtk", 34 | "flatpak", 35 | "font-firacode", 36 | "font-hack", 37 | "font-noto", 38 | "font-noto-emoji", 39 | "firefox", 40 | "liberation-fonts-ttf", 41 | "sysbinary(plymouthd)" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /selections/develop.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "develop", 3 | "summary": "Development tooling", 4 | "description": "Install software for packaging and development.", 5 | "depends": [ 6 | "base" 7 | ], 8 | "required": [ 9 | "binary(boulder)", 10 | "binary(git)", 11 | "binary(zed)" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /selections/gnome.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gnome", 3 | "summary": "GNOME Desktop", 4 | "description": "Recommended for most users", 5 | "depends": [ 6 | "base" 7 | ], 8 | "required": [ 9 | "binary(celluloid)", 10 | "binary(file-roller)", 11 | "binary(firefox)", 12 | "binary(gnome-calculator)", 13 | "binary(gnome-calendar)", 14 | "binary(gnome-clocks)", 15 | "binary(gnome-control-center)", 16 | "binary(gnome-disks)", 17 | "binary(gnome-software)", 18 | "binary(gnome-text-editor)", 19 | "binary(gnome-tweaks)", 20 | "binary(gnome-weather)", 21 | "binary(loupe)", 22 | "binary(nautilus)", 23 | "binary(papers)", 24 | "binary(ptyxis)", 25 | "binary(resources)", 26 | "binary(snapshot)", 27 | "binary(starship)", 28 | "flatpak", 29 | "font-hack", 30 | "font-noto", 31 | "font-noto-emoji", 32 | "gnome-backgrounds", 33 | "liberation-fonts-ttf", 34 | "loupe", 35 | "serpent-gnome-defaults", 36 | "sysbinary(gdm)", 37 | "sysbinary(plymouthd)" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /selections/kernel-common.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kernel-common", 3 | "summary": "Common kernel runtime", 4 | "description": "Common kernel runtime", 5 | "required": [ 6 | "kmod", 7 | "linux-firmware", 8 | "lvm2" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /selections/kernel-desktop.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kernel-desktop", 3 | "summary": "Desktop kernel", 4 | "description": "Kernel optimized for desktop use", 5 | "depends": [ 6 | "kernel-common" 7 | ], 8 | "required": [ 9 | "linux-desktop", 10 | "linux-firmware", 11 | "linux-firmware-amd", 12 | "linux-firmware-intel", 13 | "linux-firmware-iwlwifi", 14 | "linux-firmware-nvidia" 15 | ] 16 | } 17 | --------------------------------------------------------------------------------