Opinionated Android 15+ Linux Terminal Setup
android linux command-line-tools

Initial Commit

+1199
+2
.gitignore
··· 1 + /target 2 + oh-my-droid.toml
+498
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 = "anstream" 7 + version = "0.6.20" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" 10 + dependencies = [ 11 + "anstyle", 12 + "anstyle-parse", 13 + "anstyle-query", 14 + "anstyle-wincon", 15 + "colorchoice", 16 + "is_terminal_polyfill", 17 + "utf8parse", 18 + ] 19 + 20 + [[package]] 21 + name = "anstyle" 22 + version = "1.0.11" 23 + source = "registry+https://github.com/rust-lang/crates.io-index" 24 + checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" 25 + 26 + [[package]] 27 + name = "anstyle-parse" 28 + version = "0.2.7" 29 + source = "registry+https://github.com/rust-lang/crates.io-index" 30 + checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 31 + dependencies = [ 32 + "utf8parse", 33 + ] 34 + 35 + [[package]] 36 + name = "anstyle-query" 37 + version = "1.1.4" 38 + source = "registry+https://github.com/rust-lang/crates.io-index" 39 + checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" 40 + dependencies = [ 41 + "windows-sys", 42 + ] 43 + 44 + [[package]] 45 + name = "anstyle-wincon" 46 + version = "3.0.10" 47 + source = "registry+https://github.com/rust-lang/crates.io-index" 48 + checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" 49 + dependencies = [ 50 + "anstyle", 51 + "once_cell_polyfill", 52 + "windows-sys", 53 + ] 54 + 55 + [[package]] 56 + name = "anyhow" 57 + version = "1.0.99" 58 + source = "registry+https://github.com/rust-lang/crates.io-index" 59 + checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" 60 + 61 + [[package]] 62 + name = "bitflags" 63 + version = "2.9.1" 64 + source = "registry+https://github.com/rust-lang/crates.io-index" 65 + checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 66 + 67 + [[package]] 68 + name = "cfg-if" 69 + version = "1.0.1" 70 + source = "registry+https://github.com/rust-lang/crates.io-index" 71 + checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" 72 + 73 + [[package]] 74 + name = "clap" 75 + version = "4.5.45" 76 + source = "registry+https://github.com/rust-lang/crates.io-index" 77 + checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" 78 + dependencies = [ 79 + "clap_builder", 80 + ] 81 + 82 + [[package]] 83 + name = "clap_builder" 84 + version = "4.5.44" 85 + source = "registry+https://github.com/rust-lang/crates.io-index" 86 + checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" 87 + dependencies = [ 88 + "anstream", 89 + "anstyle", 90 + "clap_lex", 91 + "strsim", 92 + ] 93 + 94 + [[package]] 95 + name = "clap_lex" 96 + version = "0.7.5" 97 + source = "registry+https://github.com/rust-lang/crates.io-index" 98 + checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" 99 + 100 + [[package]] 101 + name = "colorchoice" 102 + version = "1.0.4" 103 + source = "registry+https://github.com/rust-lang/crates.io-index" 104 + checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 105 + 106 + [[package]] 107 + name = "dirs" 108 + version = "6.0.0" 109 + source = "registry+https://github.com/rust-lang/crates.io-index" 110 + checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" 111 + dependencies = [ 112 + "dirs-sys", 113 + ] 114 + 115 + [[package]] 116 + name = "dirs-sys" 117 + version = "0.5.0" 118 + source = "registry+https://github.com/rust-lang/crates.io-index" 119 + checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" 120 + dependencies = [ 121 + "libc", 122 + "option-ext", 123 + "redox_users", 124 + "windows-sys", 125 + ] 126 + 127 + [[package]] 128 + name = "equivalent" 129 + version = "1.0.2" 130 + source = "registry+https://github.com/rust-lang/crates.io-index" 131 + checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 132 + 133 + [[package]] 134 + name = "getrandom" 135 + version = "0.2.16" 136 + source = "registry+https://github.com/rust-lang/crates.io-index" 137 + checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 138 + dependencies = [ 139 + "cfg-if", 140 + "libc", 141 + "wasi", 142 + ] 143 + 144 + [[package]] 145 + name = "hashbrown" 146 + version = "0.15.5" 147 + source = "registry+https://github.com/rust-lang/crates.io-index" 148 + checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 149 + 150 + [[package]] 151 + name = "indexmap" 152 + version = "2.10.0" 153 + source = "registry+https://github.com/rust-lang/crates.io-index" 154 + checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" 155 + dependencies = [ 156 + "equivalent", 157 + "hashbrown", 158 + ] 159 + 160 + [[package]] 161 + name = "is_terminal_polyfill" 162 + version = "1.70.1" 163 + source = "registry+https://github.com/rust-lang/crates.io-index" 164 + checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 165 + 166 + [[package]] 167 + name = "itoa" 168 + version = "1.0.15" 169 + source = "registry+https://github.com/rust-lang/crates.io-index" 170 + checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 171 + 172 + [[package]] 173 + name = "libc" 174 + version = "0.2.175" 175 + source = "registry+https://github.com/rust-lang/crates.io-index" 176 + checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" 177 + 178 + [[package]] 179 + name = "libredox" 180 + version = "0.1.9" 181 + source = "registry+https://github.com/rust-lang/crates.io-index" 182 + checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" 183 + dependencies = [ 184 + "bitflags", 185 + "libc", 186 + ] 187 + 188 + [[package]] 189 + name = "libyml" 190 + version = "0.0.5" 191 + source = "registry+https://github.com/rust-lang/crates.io-index" 192 + checksum = "3302702afa434ffa30847a83305f0a69d6abd74293b6554c18ec85c7ef30c980" 193 + dependencies = [ 194 + "anyhow", 195 + "version_check", 196 + ] 197 + 198 + [[package]] 199 + name = "memchr" 200 + version = "2.7.5" 201 + source = "registry+https://github.com/rust-lang/crates.io-index" 202 + checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 203 + 204 + [[package]] 205 + name = "oh-my-droid" 206 + version = "0.1.0" 207 + dependencies = [ 208 + "anyhow", 209 + "clap", 210 + "dirs", 211 + "owo-colors", 212 + "serde", 213 + "serde_yml", 214 + "toml", 215 + ] 216 + 217 + [[package]] 218 + name = "once_cell_polyfill" 219 + version = "1.70.1" 220 + source = "registry+https://github.com/rust-lang/crates.io-index" 221 + checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 222 + 223 + [[package]] 224 + name = "option-ext" 225 + version = "0.2.0" 226 + source = "registry+https://github.com/rust-lang/crates.io-index" 227 + checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 228 + 229 + [[package]] 230 + name = "owo-colors" 231 + version = "4.2.2" 232 + source = "registry+https://github.com/rust-lang/crates.io-index" 233 + checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" 234 + 235 + [[package]] 236 + name = "proc-macro2" 237 + version = "1.0.97" 238 + source = "registry+https://github.com/rust-lang/crates.io-index" 239 + checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" 240 + dependencies = [ 241 + "unicode-ident", 242 + ] 243 + 244 + [[package]] 245 + name = "quote" 246 + version = "1.0.40" 247 + source = "registry+https://github.com/rust-lang/crates.io-index" 248 + checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 249 + dependencies = [ 250 + "proc-macro2", 251 + ] 252 + 253 + [[package]] 254 + name = "redox_users" 255 + version = "0.5.2" 256 + source = "registry+https://github.com/rust-lang/crates.io-index" 257 + checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" 258 + dependencies = [ 259 + "getrandom", 260 + "libredox", 261 + "thiserror", 262 + ] 263 + 264 + [[package]] 265 + name = "ryu" 266 + version = "1.0.20" 267 + source = "registry+https://github.com/rust-lang/crates.io-index" 268 + checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 269 + 270 + [[package]] 271 + name = "serde" 272 + version = "1.0.219" 273 + source = "registry+https://github.com/rust-lang/crates.io-index" 274 + checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 275 + dependencies = [ 276 + "serde_derive", 277 + ] 278 + 279 + [[package]] 280 + name = "serde_derive" 281 + version = "1.0.219" 282 + source = "registry+https://github.com/rust-lang/crates.io-index" 283 + checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 284 + dependencies = [ 285 + "proc-macro2", 286 + "quote", 287 + "syn", 288 + ] 289 + 290 + [[package]] 291 + name = "serde_spanned" 292 + version = "1.0.0" 293 + source = "registry+https://github.com/rust-lang/crates.io-index" 294 + checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" 295 + dependencies = [ 296 + "serde", 297 + ] 298 + 299 + [[package]] 300 + name = "serde_yml" 301 + version = "0.0.12" 302 + source = "registry+https://github.com/rust-lang/crates.io-index" 303 + checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" 304 + dependencies = [ 305 + "indexmap", 306 + "itoa", 307 + "libyml", 308 + "memchr", 309 + "ryu", 310 + "serde", 311 + "version_check", 312 + ] 313 + 314 + [[package]] 315 + name = "strsim" 316 + version = "0.11.1" 317 + source = "registry+https://github.com/rust-lang/crates.io-index" 318 + checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 319 + 320 + [[package]] 321 + name = "syn" 322 + version = "2.0.105" 323 + source = "registry+https://github.com/rust-lang/crates.io-index" 324 + checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" 325 + dependencies = [ 326 + "proc-macro2", 327 + "quote", 328 + "unicode-ident", 329 + ] 330 + 331 + [[package]] 332 + name = "thiserror" 333 + version = "2.0.14" 334 + source = "registry+https://github.com/rust-lang/crates.io-index" 335 + checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" 336 + dependencies = [ 337 + "thiserror-impl", 338 + ] 339 + 340 + [[package]] 341 + name = "thiserror-impl" 342 + version = "2.0.14" 343 + source = "registry+https://github.com/rust-lang/crates.io-index" 344 + checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" 345 + dependencies = [ 346 + "proc-macro2", 347 + "quote", 348 + "syn", 349 + ] 350 + 351 + [[package]] 352 + name = "toml" 353 + version = "0.9.5" 354 + source = "registry+https://github.com/rust-lang/crates.io-index" 355 + checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" 356 + dependencies = [ 357 + "indexmap", 358 + "serde", 359 + "serde_spanned", 360 + "toml_datetime", 361 + "toml_parser", 362 + "toml_writer", 363 + "winnow", 364 + ] 365 + 366 + [[package]] 367 + name = "toml_datetime" 368 + version = "0.7.0" 369 + source = "registry+https://github.com/rust-lang/crates.io-index" 370 + checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" 371 + dependencies = [ 372 + "serde", 373 + ] 374 + 375 + [[package]] 376 + name = "toml_parser" 377 + version = "1.0.2" 378 + source = "registry+https://github.com/rust-lang/crates.io-index" 379 + checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" 380 + dependencies = [ 381 + "winnow", 382 + ] 383 + 384 + [[package]] 385 + name = "toml_writer" 386 + version = "1.0.2" 387 + source = "registry+https://github.com/rust-lang/crates.io-index" 388 + checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" 389 + 390 + [[package]] 391 + name = "unicode-ident" 392 + version = "1.0.18" 393 + source = "registry+https://github.com/rust-lang/crates.io-index" 394 + checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 395 + 396 + [[package]] 397 + name = "utf8parse" 398 + version = "0.2.2" 399 + source = "registry+https://github.com/rust-lang/crates.io-index" 400 + checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 401 + 402 + [[package]] 403 + name = "version_check" 404 + version = "0.9.5" 405 + source = "registry+https://github.com/rust-lang/crates.io-index" 406 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 407 + 408 + [[package]] 409 + name = "wasi" 410 + version = "0.11.1+wasi-snapshot-preview1" 411 + source = "registry+https://github.com/rust-lang/crates.io-index" 412 + checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 413 + 414 + [[package]] 415 + name = "windows-link" 416 + version = "0.1.3" 417 + source = "registry+https://github.com/rust-lang/crates.io-index" 418 + checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 419 + 420 + [[package]] 421 + name = "windows-sys" 422 + version = "0.60.2" 423 + source = "registry+https://github.com/rust-lang/crates.io-index" 424 + checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 425 + dependencies = [ 426 + "windows-targets", 427 + ] 428 + 429 + [[package]] 430 + name = "windows-targets" 431 + version = "0.53.3" 432 + source = "registry+https://github.com/rust-lang/crates.io-index" 433 + checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" 434 + dependencies = [ 435 + "windows-link", 436 + "windows_aarch64_gnullvm", 437 + "windows_aarch64_msvc", 438 + "windows_i686_gnu", 439 + "windows_i686_gnullvm", 440 + "windows_i686_msvc", 441 + "windows_x86_64_gnu", 442 + "windows_x86_64_gnullvm", 443 + "windows_x86_64_msvc", 444 + ] 445 + 446 + [[package]] 447 + name = "windows_aarch64_gnullvm" 448 + version = "0.53.0" 449 + source = "registry+https://github.com/rust-lang/crates.io-index" 450 + checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" 451 + 452 + [[package]] 453 + name = "windows_aarch64_msvc" 454 + version = "0.53.0" 455 + source = "registry+https://github.com/rust-lang/crates.io-index" 456 + checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" 457 + 458 + [[package]] 459 + name = "windows_i686_gnu" 460 + version = "0.53.0" 461 + source = "registry+https://github.com/rust-lang/crates.io-index" 462 + checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" 463 + 464 + [[package]] 465 + name = "windows_i686_gnullvm" 466 + version = "0.53.0" 467 + source = "registry+https://github.com/rust-lang/crates.io-index" 468 + checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" 469 + 470 + [[package]] 471 + name = "windows_i686_msvc" 472 + version = "0.53.0" 473 + source = "registry+https://github.com/rust-lang/crates.io-index" 474 + checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" 475 + 476 + [[package]] 477 + name = "windows_x86_64_gnu" 478 + version = "0.53.0" 479 + source = "registry+https://github.com/rust-lang/crates.io-index" 480 + checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" 481 + 482 + [[package]] 483 + name = "windows_x86_64_gnullvm" 484 + version = "0.53.0" 485 + source = "registry+https://github.com/rust-lang/crates.io-index" 486 + checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" 487 + 488 + [[package]] 489 + name = "windows_x86_64_msvc" 490 + version = "0.53.0" 491 + source = "registry+https://github.com/rust-lang/crates.io-index" 492 + checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" 493 + 494 + [[package]] 495 + name = "winnow" 496 + version = "0.7.12" 497 + source = "registry+https://github.com/rust-lang/crates.io-index" 498 + checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
+13
Cargo.toml
··· 1 + [package] 2 + name = "oh-my-droid" 3 + version = "0.1.0" 4 + edition = "2024" 5 + 6 + [dependencies] 7 + anyhow = "1.0.99" 8 + clap = "4.5.45" 9 + dirs = "6.0.0" 10 + owo-colors = "4.2.2" 11 + serde = { version = "1.0.219", features = ["serde_derive", "derive"] } 12 + serde_yml = "0.0.12" 13 + toml = "0.9.5"
+381
src/apply.rs
··· 1 + use std::{collections::HashMap, path::Path}; 2 + 3 + use anyhow::{Context, Error}; 4 + use owo_colors::OwoColorize; 5 + 6 + use crate::command::{run_command, run_command_without_local_path}; 7 + 8 + #[derive(Debug)] 9 + pub enum SetupStep<'a> { 10 + AptGet(&'a [String]), 11 + Pkgx(&'a HashMap<String, String>), 12 + Curl(&'a HashMap<String, String>), 13 + Mise(&'a HashMap<String, String>), 14 + BleSh(bool), 15 + Nix(&'a HashMap<String, String>), 16 + Stow(&'a HashMap<String, String>), 17 + OhMyPosh(&'a str), 18 + Zoxide(bool), 19 + Alias(&'a HashMap<String, String>), 20 + Paths, 21 + } 22 + 23 + impl<'a> SetupStep<'a> { 24 + pub fn run(&self) -> Result<(), Error> { 25 + match self { 26 + SetupStep::AptGet(pkgs) => install_apt(pkgs), 27 + SetupStep::Pkgx(map) => install_pkgx(map), 28 + SetupStep::Curl(map) => run_curl_installers(map), 29 + SetupStep::Mise(map) => setup_mise(map), 30 + SetupStep::BleSh(enabled) => enable_blesh(*enabled), 31 + SetupStep::Nix(map) => setup_nix(map), 32 + SetupStep::Stow(map) => setup_stow(map), 33 + SetupStep::OhMyPosh(theme) => setup_oh_my_posh(theme), 34 + SetupStep::Zoxide(enabled) => enable_zoxide(*enabled), 35 + SetupStep::Alias(map) => setup_alias(map), 36 + SetupStep::Paths => setup_paths(), 37 + } 38 + } 39 + 40 + pub fn format_dry_run(&self) -> String { 41 + match self { 42 + SetupStep::AptGet(pkgs) => { 43 + let pkg_list = pkgs 44 + .iter() 45 + .map(|p| format!(" - {}", p.green())) 46 + .collect::<Vec<_>>() 47 + .join("\n"); 48 + format!( 49 + "{} {}\n{}", 50 + "AptGet".blue().bold(), 51 + "(Install system packages via apt-get)".italic(), 52 + pkg_list 53 + ) 54 + } 55 + SetupStep::Pkgx(map) => { 56 + let pkg_list = map 57 + .iter() 58 + .map(|(k, v)| format!(" - {}: {}", k.green(), v.cyan())) 59 + .collect::<Vec<_>>() 60 + .join("\n"); 61 + format!( 62 + "{} {}\n{}", 63 + "Pkgx".blue().bold(), 64 + "(Install tools via pkgx)".italic(), 65 + pkg_list 66 + ) 67 + } 68 + SetupStep::Curl(map) => { 69 + let curl_list = map 70 + .iter() 71 + .map(|(k, v)| format!(" - {}: {}", k.green(), v.cyan())) 72 + .collect::<Vec<_>>() 73 + .join("\n"); 74 + format!( 75 + "{} {}\n{}", 76 + "Curl".blue().bold(), 77 + "(Run curl-based installers)".italic(), 78 + curl_list 79 + ) 80 + } 81 + SetupStep::Mise(map) => { 82 + let mise_list = map 83 + .iter() 84 + .map(|(k, v)| format!(" - {}: {}", k.green(), v.cyan())) 85 + .collect::<Vec<_>>() 86 + .join("\n"); 87 + format!( 88 + "{} {}\n{}", 89 + "Mise".blue().bold(), 90 + "(Configure tools via mise)".italic(), 91 + mise_list 92 + ) 93 + } 94 + SetupStep::BleSh(enabled) => { 95 + format!( 96 + "{} {}\n - Enabled: {}", 97 + "BleSh".blue().bold(), 98 + "(Enable ble.sh shell enhancements)".italic(), 99 + enabled.to_string().green() 100 + ) 101 + } 102 + SetupStep::Zoxide(enabled) => { 103 + format!( 104 + "{} {}\n - Enabled: {}", 105 + "Zoxide".blue().bold(), 106 + "(Enable zoxide for directory navigation)".italic(), 107 + enabled.to_string().green() 108 + ) 109 + } 110 + SetupStep::Nix(map) => { 111 + let nix_list = map 112 + .iter() 113 + .map(|(k, v)| format!(" - {}: {}", k.green(), v.cyan())) 114 + .collect::<Vec<_>>() 115 + .join("\n"); 116 + format!( 117 + "{} {}\n{}", 118 + "Nix".blue().bold(), 119 + "(Install tools via nix)".italic(), 120 + nix_list 121 + ) 122 + } 123 + SetupStep::Stow(map) => { 124 + let stow_list = map 125 + .iter() 126 + .map(|(k, v)| format!(" - {}: {}", k.green(), v.cyan())) 127 + .collect::<Vec<_>>() 128 + .join("\n"); 129 + format!( 130 + "{} {}\n{}", 131 + "Stow".blue().bold(), 132 + "(Manage dotfiles via stow)".italic(), 133 + stow_list 134 + ) 135 + } 136 + SetupStep::OhMyPosh(theme) => { 137 + format!( 138 + "{} {}\n - Theme: {}", 139 + "OhMyPosh".blue().bold(), 140 + "(Setup Oh My Posh for shell prompt)".italic(), 141 + theme.green() 142 + ) 143 + } 144 + SetupStep::Alias(map) => { 145 + let alias_list = map 146 + .iter() 147 + .map(|(k, v)| format!(" - {}: {}", k.green(), v.cyan())) 148 + .collect::<Vec<_>>() 149 + .join("\n"); 150 + format!( 151 + "{} {}\n{}", 152 + "Alias".blue().bold(), 153 + "(Setup shell aliases)".italic(), 154 + alias_list 155 + ) 156 + } 157 + SetupStep::Paths => { 158 + format!( 159 + "{} {}\n{}", 160 + "Paths".blue().bold(), 161 + "(Setup paths for binaries)".italic(), 162 + " - ~/.local/bin".green() 163 + ) 164 + } 165 + } 166 + } 167 + } 168 + 169 + fn install_apt(pkgs: &[String]) -> Result<(), Error> { 170 + if pkgs.is_empty() { 171 + return Ok(()); 172 + } 173 + 174 + run_command("sudo", &["apt-get", "update"]).context("Failed to run apt-get update")?; 175 + if !Path::new("/etc/apt/sources.list.d/vscode.list").exists() { 176 + run_command("sudo", &["apt-get", "install", "-y", "wget", "curl", "gpg"])?; 177 + run_command( 178 + "bash", 179 + &[ 180 + "-c", 181 + "wget -qO- https://packages.microsoft.com/keys/microsoft.asc | sudo gpg --dearmor > packages.microsoft.gpg", 182 + ], 183 + )?; 184 + run_command( 185 + "sudo", 186 + &[ 187 + "install", 188 + "-D", 189 + "-o", 190 + "root", 191 + "-g", 192 + "root", 193 + "-m", 194 + "644", 195 + "packages.microsoft.gpg", 196 + "/etc/apt/keyrings/packages.microsoft.gpg", 197 + ], 198 + )?; 199 + run_command( 200 + "bash", 201 + &[ 202 + "-c", 203 + "echo 'deb [arch=amd64,arm64,armhf signed-by=/etc/apt/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main' | sudo tee /etc/apt/sources.list.d/vscode.list", 204 + ], 205 + )?; 206 + run_command("rm", &["-f", "packages.microsoft.gpg"])?; 207 + run_command("sudo", &["apt-get", "update"]).context("Failed to run apt-get update")?; 208 + } 209 + 210 + if !Path::new("/etc/apt/sources.list.d/mise.list").exists() { 211 + run_command("bash", &[ 212 + "-c", 213 + "wget -qO - https://mise.jdx.dev/gpg-key.pub | gpg --dearmor | sudo tee /etc/apt/keyrings/mise-archive-keyring.gpg 1> /dev/null 214 + "])?; 215 + run_command( 216 + "bash", 217 + &[ 218 + "-c", 219 + "echo 'deb [signed-by=/etc/apt/keyrings/mise-archive-keyring.gpg arch=amd64,arm64] https://mise.jdx.dev/deb stable main' | sudo tee /etc/apt/sources.list.d/mise.list", 220 + ], 221 + )?; 222 + run_command("sudo", &["apt-get", "update"]).context("Failed to run apt-get update")?; 223 + } 224 + 225 + let mut args: Vec<&str> = vec!["apt-get", "install", "-y"]; 226 + args.extend(pkgs.iter().map(|s| s.as_str())); 227 + run_command("sudo", &args).context("Failed to run apt-get install")?; 228 + 229 + run_command( 230 + "sudo", 231 + &["rm", "-rf", "/etc/apt/sources.list.d/vscode.list"], 232 + )?; 233 + 234 + Ok(()) 235 + } 236 + 237 + fn install_pkgx(map: &HashMap<String, String>) -> Result<(), Error> { 238 + for (name, ver) in map { 239 + run_command("pkgm", &["install", &format!("{name}@{ver}")]) 240 + .context(format!("Failed to install {name} via pkgx"))?; 241 + } 242 + run_command("pkgm", &["uninstall", "curl"]).context("Failed to uninstall curl via pkgx")?; 243 + Ok(()) 244 + } 245 + 246 + fn run_curl_installers(map: &HashMap<String, String>) -> Result<(), Error> { 247 + for (name, url) in map { 248 + run_command("bash", &["-c", &format!("curl -fsSL {} | bash -s", url)]) 249 + .context(format!("Failed to run curl installer for {name}"))?; 250 + } 251 + Ok(()) 252 + } 253 + 254 + fn setup_mise(map: &HashMap<String, String>) -> Result<(), Error> { 255 + if !Path::new("/usr/bin/mise").exists() { 256 + run_command("sudo", &["apt-get", "install", "-y", "mise"]) 257 + .context("Failed to install mise")?; 258 + } 259 + 260 + run_command( 261 + "bash", 262 + &[ 263 + "-c", 264 + "sed -i '/mise/d' ~/.bashrc || echo 'No existing mise line found in .bashrc'", 265 + ], 266 + )?; 267 + run_command( 268 + "bash", 269 + &[ 270 + "-c", 271 + "echo '\neval $(mise activate bash)' | tee -a ~/.bashrc", 272 + ], 273 + )?; 274 + 275 + for (tool, ver) in map { 276 + run_command("mise", &["use", "-g", &format!("{tool}@{ver}")]) 277 + .context(format!("Failed to configure {tool} via mise"))?; 278 + } 279 + Ok(()) 280 + } 281 + 282 + fn enable_blesh(enabled: bool) -> Result<(), Error> { 283 + let home = dirs::home_dir().ok_or_else(|| Error::msg("Failed to get home directory"))?; 284 + let blesh_path = home.join("ble.sh"); 285 + if enabled && !blesh_path.exists() { 286 + run_command_without_local_path( 287 + "bash", 288 + &[ 289 + "-c", "rm -rf ~/local/bin/gettext* &&git clone --recursive --depth 1 --shallow-submodules https://github.com/akinomyoga/ble.sh.git", 290 + ], 291 + ) 292 + .context("Failed to clone ble.sh repository")?; 293 + run_command_without_local_path("make", &["-C", "ble.sh"]) 294 + .context("Failed to build ble.sh")?; 295 + run_command_without_local_path( 296 + "bash", 297 + &[ 298 + "-c", 299 + "grep 'source ble' ~/.bashrc || echo '\nsource ble.sh/out/ble.sh' | tee -a ~/.bashrc", 300 + ], 301 + ) 302 + .context("Failed to add ble.sh to .bashrc")?; 303 + } 304 + Ok(()) 305 + } 306 + 307 + fn enable_zoxide(enabled: bool) -> Result<(), Error> { 308 + if enabled { 309 + run_command("bash", &["-c", "curl -sSL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | bash"]) 310 + .context("Failed to install zoxide")?; 311 + run_command( 312 + "bash", 313 + &[ 314 + "-c", 315 + "grep zoxide ~/.bashrc || echo '\neval \"$(zoxide init bash)\"' | tee -a ~/.bashrc", 316 + ], 317 + ) 318 + .context("Failed to add zoxide initialization to .bashrc")?; 319 + } 320 + Ok(()) 321 + } 322 + 323 + fn setup_nix(_map: &HashMap<String, String>) -> Result<(), Error> { 324 + // nix logic here 325 + Ok(()) 326 + } 327 + 328 + fn setup_stow(_map: &HashMap<String, String>) -> Result<(), Error> { 329 + // stow logic here 330 + Ok(()) 331 + } 332 + 333 + fn setup_oh_my_posh(theme: &str) -> Result<(), Error> { 334 + run_command( 335 + "bash", 336 + &[ 337 + "-c", 338 + "sed -i '/oh-my-posh/d' ~/.bashrc || echo 'No existing oh-my-posh line found in .bashrc'", 339 + ], 340 + )?; 341 + run_command("bash", &["-c", &format!("echo 'eval \"$(oh-my-posh init bash --config $HOME/.cache/oh-my-posh/themes/{}.omp.json)\"' >> ~/.bashrc", theme)]) 342 + .context("Failed to set up Oh My Posh")?; 343 + Ok(()) 344 + } 345 + 346 + fn setup_alias(map: &HashMap<String, String>) -> Result<(), Error> { 347 + for (alias, command) in map { 348 + run_command( 349 + "bash", 350 + &["-c", &format!("sed -i '/alias {}/d' ~/.bashrc", alias)], 351 + )?; 352 + run_command( 353 + "bash", 354 + &[ 355 + "-c", 356 + &format!("echo 'alias {}=\"{}\"' >> ~/.bashrc", alias, command), 357 + ], 358 + ) 359 + .context(format!( 360 + "Failed to set up alias {} for command {}", 361 + alias, command 362 + ))?; 363 + } 364 + Ok(()) 365 + } 366 + 367 + fn setup_paths() -> Result<(), Error> { 368 + let home = dirs::home_dir().ok_or_else(|| Error::msg("Failed to get home directory"))?; 369 + let local_bin = home.join(".local/bin"); 370 + if !local_bin.exists() { 371 + std::fs::create_dir_all(&local_bin).context("Failed to create ~/.local/bin directory")?; 372 + } 373 + 374 + run_command( 375 + "bash", 376 + &["-c", "grep -q 'export PATH=\"$HOME/.local/bin:$PATH\"' ~/.bashrc || echo 'export PATH=\"$HOME/.local/bin:$PATH\"' >> ~/.bashrc"], 377 + ) 378 + .context("Failed to add ~/.local/bin to PATH in .bashrc")?; 379 + 380 + Ok(()) 381 + }
+15
src/cmd/init.rs
··· 1 + use anyhow::Error; 2 + use owo_colors::OwoColorize; 3 + 4 + use crate::{config::Configuration, consts::CONFIG_FILE}; 5 + 6 + pub fn init() -> Result<(), Error> { 7 + let cfg = Configuration::default(); 8 + let toml_str = toml::to_string(&cfg)?; 9 + std::fs::write(CONFIG_FILE, toml_str)?; 10 + println!( 11 + "Initial configuration file {} created successfully.", 12 + CONFIG_FILE.green() 13 + ); 14 + Ok(()) 15 + }
+2
src/cmd/mod.rs
··· 1 + pub mod init; 2 + pub mod setup;
+40
src/cmd/setup.rs
··· 1 + use anyhow::Error; 2 + use owo_colors::OwoColorize; 3 + 4 + use crate::{config::Configuration, consts::CONFIG_FILE}; 5 + 6 + pub fn setup(dry_run: bool, no_confirm: bool) -> Result<(), Error> { 7 + let mut cfg = Configuration::default(); 8 + 9 + if std::path::Path::new(CONFIG_FILE).exists() { 10 + let toml_str = std::fs::read_to_string(CONFIG_FILE)?; 11 + cfg = toml::from_str(&toml_str)?; 12 + } 13 + 14 + if !no_confirm && !dry_run { 15 + match std::path::Path::new(CONFIG_FILE).exists() { 16 + true => { 17 + println!( 18 + "This will set up your environment with the default configuration from {}.\nDo you want to continue? (y/N)", 19 + CONFIG_FILE.green() 20 + ); 21 + } 22 + false => { 23 + println!( 24 + "This wil set up your environment with the default configuration.\nDo you want to continue? (y/N)", 25 + ); 26 + } 27 + } 28 + 29 + let mut input = String::new(); 30 + std::io::stdin().read_line(&mut input)?; 31 + if !input.trim().eq_ignore_ascii_case("y") { 32 + println!("Setup cancelled."); 33 + return Ok(()); 34 + } 35 + } 36 + 37 + cfg.setup_environment(dry_run)?; 38 + 39 + Ok(()) 40 + }
+36
src/command.rs
··· 1 + use std::process::Command; 2 + 3 + use anyhow::Error; 4 + use owo_colors::OwoColorize; 5 + 6 + pub fn run_command(cmd: &str, args: &[&str]) -> Result<(), Error> { 7 + println!( 8 + "{} {} {}", 9 + "=>".green(), 10 + cmd.green(), 11 + args.join(" ").green() 12 + ); 13 + Command::new(cmd) 14 + .args(args) 15 + .env( 16 + "PATH", 17 + format!( 18 + "{}/.local/bin:{}", 19 + std::env::var("HOME")?, 20 + std::env::var("PATH")? 21 + ), 22 + ) 23 + .status()?; 24 + Ok(()) 25 + } 26 + 27 + pub fn run_command_without_local_path(cmd: &str, args: &[&str]) -> Result<(), Error> { 28 + println!( 29 + "{} {} {}", 30 + "=>".green(), 31 + cmd.green(), 32 + args.join(" ").green() 33 + ); 34 + Command::new(cmd).args(args).status()?; 35 + Ok(()) 36 + }
+143
src/config.rs
··· 1 + use anyhow::Result; 2 + use owo_colors::OwoColorize; 3 + use serde::{Deserialize, Serialize}; 4 + use std::collections::HashMap; 5 + 6 + use crate::apply::SetupStep; 7 + 8 + #[derive(Debug, Clone, Serialize, Deserialize)] 9 + pub struct OhMyPosh { 10 + #[serde(skip_serializing_if = "Option::is_none")] 11 + pub theme: Option<String>, 12 + } 13 + 14 + #[derive(Debug, Clone, Serialize, Deserialize)] 15 + pub struct Configuration { 16 + #[serde(skip_serializing_if = "Option::is_none")] 17 + pub stow: Option<HashMap<String, String>>, 18 + 19 + #[serde(skip_serializing_if = "Option::is_none")] 20 + pub mise: Option<HashMap<String, String>>, 21 + 22 + #[serde(skip_serializing_if = "Option::is_none")] 23 + pub nix: Option<HashMap<String, String>>, 24 + 25 + #[serde(skip_serializing_if = "Option::is_none")] 26 + #[serde(rename = "apt-get")] 27 + pub apt_get: Option<Vec<String>>, 28 + 29 + #[serde(skip_serializing_if = "Option::is_none")] 30 + pub pkgx: Option<HashMap<String, String>>, 31 + 32 + #[serde(skip_serializing_if = "Option::is_none")] 33 + pub curl: Option<HashMap<String, String>>, 34 + 35 + #[serde(skip_serializing_if = "Option::is_none")] 36 + #[serde(rename = "ble.sh")] 37 + pub blesh: Option<bool>, 38 + 39 + #[serde(skip_serializing_if = "Option::is_none")] 40 + pub oh_my_posh: Option<OhMyPosh>, 41 + 42 + #[serde(skip_serializing_if = "Option::is_none")] 43 + pub zoxide: Option<bool>, 44 + 45 + #[serde(skip_serializing_if = "Option::is_none")] 46 + pub alias: Option<HashMap<String, String>>, 47 + } 48 + 49 + impl Configuration { 50 + pub fn setup_environment(&self, dry_run: bool) -> Result<()> { 51 + let steps: Vec<SetupStep> = vec![ 52 + Some(SetupStep::Paths), 53 + self.apt_get.as_deref().map(SetupStep::AptGet), 54 + self.curl.as_ref().map(SetupStep::Curl), 55 + self.pkgx.as_ref().map(SetupStep::Pkgx), 56 + self.mise.as_ref().map(SetupStep::Mise), 57 + self.blesh.map(SetupStep::BleSh), 58 + self.zoxide.map(SetupStep::Zoxide), 59 + self.nix.as_ref().map(SetupStep::Nix), 60 + self.stow.as_ref().map(SetupStep::Stow), 61 + self.oh_my_posh 62 + .as_ref() 63 + .map(|omp| SetupStep::OhMyPosh(omp.theme.as_deref().unwrap_or("tokyonight_storm"))), 64 + self.alias.as_ref().map(SetupStep::Alias), 65 + ] 66 + .into_iter() 67 + .flatten() 68 + .collect(); 69 + 70 + if dry_run { 71 + println!("{}", "=== Dry Run: Environment Setup ===".yellow().bold()); 72 + println!("Steps to be executed ({} total):", steps.len()); 73 + for (i, step) in steps.iter().enumerate() { 74 + println!("\n=> Step {}:\n{}", i + 1, step.format_dry_run()); 75 + } 76 + println!("{}", "=== Dry Run Complete ===".yellow().bold()); 77 + } else { 78 + for step in steps { 79 + step.run()?; 80 + } 81 + } 82 + Ok(()) 83 + } 84 + } 85 + 86 + impl Default for Configuration { 87 + fn default() -> Self { 88 + Configuration { 89 + apt_get: Some( 90 + vec![ 91 + "build-essential", 92 + "curl", 93 + "git", 94 + "gawk", 95 + "wget", 96 + "unzip", 97 + "autoconf", 98 + "automake", 99 + "cmake", 100 + "tmux", 101 + "openssh-server", 102 + "openssh-client", 103 + "httpie", 104 + "code", 105 + "screenfetch", 106 + ] 107 + .into_iter() 108 + .map(String::from) 109 + .collect(), 110 + ), 111 + pkgx: Some(HashMap::from([ 112 + ("tig".into(), "latest".into()), 113 + ("rg".into(), "latest".into()), 114 + ("jq".into(), "latest".into()), 115 + ("neovim.io".into(), "latest".into()), 116 + ("fzf".into(), "latest".into()), 117 + ("zellij".into(), "latest".into()), 118 + ("glow".into(), "latest".into()), 119 + ("gh".into(), "latest".into()), 120 + ("eza".into(), "latest".into()), 121 + ])), 122 + curl: Some(HashMap::from([ 123 + ( 124 + "oh-my-posh".into(), 125 + "https://ohmyposh.dev/install.sh".into(), 126 + ), 127 + ("atuin".into(), "https://setup.atuin.sh".into()), 128 + ("bun".into(), "https://bun.sh/install".into()), 129 + ("deno".into(), "https://deno.land/install.sh".into()), 130 + ("pkgx".into(), "https://pkgx.sh".into()), 131 + ])), 132 + mise: Some(HashMap::from([("node".into(), "latest".into())])), 133 + blesh: Some(true), 134 + zoxide: Some(true), 135 + nix: None, 136 + stow: None, 137 + oh_my_posh: Some(OhMyPosh { 138 + theme: Some("tokyonight_storm".into()), 139 + }), 140 + alias: Some(HashMap::from([("ls".into(), "eza -lh".into())])), 141 + } 142 + } 143 + }
+1
src/consts.rs
··· 1 + pub const CONFIG_FILE: &str = "oh-my-droid.toml";
+68
src/main.rs
··· 1 + use anyhow::Error; 2 + use clap::{Command, arg}; 3 + use owo_colors::OwoColorize; 4 + 5 + use crate::{ 6 + cmd::{init::init, setup::setup}, 7 + consts::CONFIG_FILE, 8 + }; 9 + 10 + pub mod apply; 11 + pub mod cmd; 12 + pub mod command; 13 + pub mod config; 14 + pub mod consts; 15 + 16 + fn cli() -> Command { 17 + let banner = format!( 18 + "{}\nTurn a fresh {} into a fully-configured, beautiful, and modern web development system by running a single command.", 19 + r#" 20 + ______ _________ ______________ 21 + _________ /_ _______ ________ __ ______ /_______________(_)_____ / 22 + _ __ \_ __ \ __ __ `__ \_ / / / _ __ /__ ___/ __ \_ /_ __ / 23 + / /_/ / / / / _ / / / / / /_/ / / /_/ / _ / / /_/ / / / /_/ / 24 + \____//_/ /_/ /_/ /_/ /_/_\__, / \__,_/ /_/ \____//_/ \__,_/ 25 + /____/ 26 + 27 + "# 28 + .green(), 29 + "Android 15+ Linux Terminal".green() 30 + ); 31 + 32 + Command::new("oh-my-droid") 33 + .version(env!("CARGO_PKG_VERSION")) 34 + .author("Tsiry Sandratraina <tsiry.sndr@rocksky.app>") 35 + .about(&banner) 36 + .subcommand(Command::new("init").about(&format!( 37 + "Write the initial configuration file {}.", 38 + CONFIG_FILE.green() 39 + ))) 40 + .subcommand( 41 + Command::new("setup") 42 + .about("Set up the environment with the default configuration.") 43 + .arg(arg!(-d --"dry-run" "Simulate the setup process without making any changes.")) 44 + .arg(arg!(-y --"yes" "Skip confirmation prompts during setup.")), 45 + ) 46 + .arg(arg!(-d --"dry-run" "Simulate the setup process without making any changes.")) 47 + .arg(arg!(-y --"yes" "Skip confirmation prompts during setup.")) 48 + } 49 + 50 + fn main() -> Result<(), Error> { 51 + let matches = cli().get_matches(); 52 + 53 + match matches.subcommand() { 54 + Some(("init", _)) => init()?, 55 + Some(("setup", args)) => { 56 + let yes = args.get_flag("yes"); 57 + let dry_run = args.get_flag("dry-run"); 58 + setup(dry_run, yes)? 59 + } 60 + _ => { 61 + let yes = matches.get_flag("yes"); 62 + let dry_run = matches.get_flag("dry-run"); 63 + setup(dry_run, yes)? 64 + } 65 + } 66 + 67 + Ok(()) 68 + }