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

add installer script and support for nix setup

+278 -12
+61 -1
README.md
··· 12 12 - [ble.sh](https://github.com/akinomyoga/ble.sh) integration for a better terminal experience 13 13 - [Pkgx](https://github.com/pkgxdev/pkgx) package manager for easy software installation 14 14 - [Mise](https://github.com/jdx/mise) integration for a modern command-line experience 15 + - [Stow](https://www.gnu.org/software/stow) integration for managing dotfiles 16 + - [Nix](https://github.com/NixOS/nix) package manager 15 17 - SSH support for secure remote access 16 18 - Pre-installed [VS Code](https://code.visualstudio.com/) 17 19 - Pre-installed [NeoVim](https://neovim.io/) 18 20 - [Oh My Posh](https://ohmyposh.dev/) integration for a beautiful prompt 19 - - Alias setup for ls: `alias ls='eza -lh'` 21 + - Alias setup for ls: `alias ls='eza -lh'` 22 + 23 + 24 + ![Preview](./preview.png) 25 + 26 + 27 + ## Installation 28 + 29 + Run the following command to install `oh-my-droid`: 30 + 31 + ```bash 32 + curl -sSfL https://raw.githubusercontent.com/tsirysndr/oh-my-droid/main/install.sh | bash 33 + ``` 34 + 35 + ## Configuration 36 + 37 + Run the following command to create an initial configuration `oh-my-droid.toml`: 38 + 39 + ```bash 40 + oh-my-droid init 41 + ``` 42 + 43 + ```toml 44 + apt-get = ["build-essential", "curl", "git", "gawk", "wget", "unzip", "autoconf", "automake", "cmake", "tmux", "openssh-server", "openssh-client", "httpie", "code", "screenfetch", "stow"] 45 + "ble.sh" = true 46 + zoxide = true 47 + 48 + [stow] 49 + git = "github:tsirysndr/android-dotfiles" 50 + 51 + [mise] 52 + node = "latest" 53 + 54 + [pkgx] 55 + tig = "latest" 56 + rg = "latest" 57 + fzf = "latest" 58 + zellij = "latest" 59 + glow = "latest" 60 + eza = "latest" 61 + "neovim.io" = "latest" 62 + gh = "latest" 63 + jq = "latest" 64 + 65 + [curl] 66 + oh-my-posh = "https://ohmyposh.dev/install.sh" 67 + atuin = "https://setup.atuin.sh" 68 + pkgx = "https://pkgx.sh" 69 + deno = "https://deno.land/install.sh" 70 + bun = "https://bun.sh/install" 71 + 72 + [oh_my_posh] 73 + theme = "tokyonight_storm" 74 + 75 + [alias] 76 + ls = "eza -lh" 77 + ``` 78 + 79 + You can customize it and run `oh-my-droid setup` to apply the changes.
+99
install.sh
··· 1 + #!/usr/bin/env bash 2 + 3 + set -e -o pipefail 4 + 5 + readonly MAGENTA="$(tput setaf 5 2>/dev/null || echo '')" 6 + readonly GREEN="$(tput setaf 2 2>/dev/null || echo '')" 7 + readonly CYAN="$(tput setaf 6 2>/dev/null || echo '')" 8 + readonly ORANGE="$(tput setaf 3 2>/dev/null || echo '')" 9 + readonly NO_COLOR="$(tput sgr0 2>/dev/null || echo '')" 10 + 11 + if ! command -v curl >/dev/null 2>&1; then 12 + echo "Error: curl is required to install oh-my-droid." 13 + exit 1 14 + fi 15 + 16 + if ! command -v tar >/dev/null 2>&1; then 17 + echo "Error: tar is required to install oh-my-droid." 18 + exit 1 19 + fi 20 + 21 + export PATH="$HOME/.local/bin:$PATH" 22 + 23 + RELEASE_URL="https://api.github.com/repos/tsirysndr/oh-my-droid/releases/latest" 24 + 25 + function detect_os() { 26 + # Determine the operating system 27 + OS=$(uname -s) 28 + if [ "$OS" = "Linux" ]; then 29 + # Determine the CPU architecture 30 + ARCH=$(uname -m) 31 + if [ "$ARCH" = "aarch64" ]; then 32 + ASSET_NAME="_aarch64-unknown-linux-gnu.tar.gz" 33 + elif [ "$ARCH" = "x86_64" ]; then 34 + ASSET_NAME="_x86_64-unknown-linux-gnu.tar.gz" 35 + else 36 + echo "Unsupported architecture: $ARCH" 37 + exit 1 38 + fi 39 + else 40 + echo "Unsupported operating system: $OS" 41 + echo "This script only supports Linux." 42 + exit 1 43 + fi; 44 + } 45 + 46 + detect_os 47 + 48 + # Retrieve the download URL for the desired asset 49 + DOWNLOAD_URL=$(curl -sSL $RELEASE_URL | grep -o "browser_download_url.*$ASSET_NAME\"" | cut -d ' ' -f 2) 50 + 51 + ASSET_NAME=$(basename $DOWNLOAD_URL) 52 + 53 + INSTALL_DIR="/usr/local/bin" 54 + 55 + DOWNLOAD_URL=`echo $DOWNLOAD_URL | tr -d '\"'` 56 + 57 + # Download the asset 58 + curl -SL $DOWNLOAD_URL -o /tmp/$ASSET_NAME 59 + 60 + # Extract the asset 61 + tar -xzf /tmp/$ASSET_NAME -C /tmp 62 + 63 + # Set the correct permissions for the binary 64 + chmod +x /tmp/oh-my-droid 65 + 66 + if command -v sudo >/dev/null 2>&1; then 67 + sudo mv /tmp/oh-my-droid $INSTALL_DIR 68 + else 69 + mv /tmp/oh-my-droid $INSTALL_DIR 70 + fi 71 + 72 + # Clean up temporary files 73 + rm /tmp/$ASSET_NAME 74 + 75 + cat << EOF 76 + ${CYAN} 77 + ______ _________ ______________ 78 + _________ /_ _______ ________ __ ______ /_______________(_)_____ / 79 + _ __ \\_ __ \\ __ __ `__ \\_ / / / _ __ /__ ___/ __ \\_ /_ __ / 80 + / /_/ / / / / _ / / / / / /_/ / / /_/ / _ / / /_/ / / / /_/ / 81 + \\____//_/ /_/ /_/ /_/ /_/_\\__, / \\__,_/ /_/ \\____//_/ \\__,_/ 82 + /____/ 83 + 84 + ${NO_COLOR} 85 + 86 + Opinionated Android 15+ Linux Terminal Setup 87 + 88 + ${GREEN}https://github.com/tsirysndr/oh-my-droid${NO_COLOR} 89 + 90 + Please file an issue if you encounter any problems! 91 + 92 + =============================================================================== 93 + 94 + Installation completed! 🎉 95 + 96 + EOF 97 + 98 + oh-my-droid setup 99 +
preview.png

This is a binary file and will not be displayed.

+68 -4
src/apply.rs
··· 17 17 OhMyPosh(&'a str), 18 18 Zoxide(bool), 19 19 Alias(&'a HashMap<String, String>), 20 + Ssh, 20 21 Paths, 21 22 } 22 23 ··· 33 34 SetupStep::OhMyPosh(theme) => setup_oh_my_posh(theme), 34 35 SetupStep::Zoxide(enabled) => enable_zoxide(*enabled), 35 36 SetupStep::Alias(map) => setup_alias(map), 37 + SetupStep::Ssh => setup_ssh(), 36 38 SetupStep::Paths => setup_paths(), 37 39 } 38 40 } ··· 162 164 " - ~/.local/bin".green() 163 165 ) 164 166 } 167 + SetupStep::Ssh => { 168 + format!( 169 + "{} {}\n{}", 170 + "SSH".blue().bold(), 171 + "(Setup SSH keys and configuration)".italic(), 172 + " - Generate SSH key pair".green() 173 + ) 174 + } 165 175 } 166 176 } 167 177 } ··· 286 296 run_command_without_local_path( 287 297 "bash", 288 298 &[ 289 - "-c", "rm -rf ~/.local/bin/gettext* &&git clone --recursive --depth 1 --shallow-submodules https://github.com/akinomyoga/ble.sh.git", 299 + "-c", "rm -rf ~/.local/bin/gettext* && git clone --recursive --depth 1 --shallow-submodules https://github.com/akinomyoga/ble.sh.git", 290 300 ], 291 301 ) 292 302 .context("Failed to clone ble.sh repository")?; ··· 321 331 } 322 332 323 333 fn setup_nix(_map: &HashMap<String, String>) -> Result<(), Error> { 324 - // nix logic here 334 + run_command( 335 + "bash", 336 + &[ 337 + "-c", 338 + "type nix || curl -fsSL https://install.determinate.systems/nix | sh -s -- install --determinate", 339 + ], 340 + ) 341 + .context("Failed to install nix")?; 325 342 Ok(()) 326 343 } 327 344 328 - fn setup_stow(_map: &HashMap<String, String>) -> Result<(), Error> { 329 - // stow logic here 345 + fn setup_stow(map: &HashMap<String, String>) -> Result<(), Error> { 346 + if map.is_empty() { 347 + return Ok(()); 348 + } 349 + 350 + let repo = map 351 + .get("git") 352 + .ok_or_else(|| Error::msg("No repo specified for stow"))?; 353 + 354 + let repo = if repo.starts_with("github:") { 355 + repo.replace("github:", "https://github.com/") 356 + } else if repo.starts_with("tangled:") { 357 + repo.replace("tangled:", "https://tangled.sh/") 358 + } else { 359 + repo.to_string() 360 + }; 361 + 362 + let home = dirs::home_dir().ok_or_else(|| Error::msg("Failed to get home directory"))?; 363 + 364 + if !Path::new(&home.join(".dotfiles")).exists() { 365 + run_command("bash", &["-c", &format!("git clone {} ~/.dotfiles", repo)]) 366 + .context("Failed to clone dotfiles repository")?; 367 + } else { 368 + run_command("bash", &["-c", "git -C ~/.dotfiles pull"]) 369 + .context("Failed to update dotfiles repository")?; 370 + } 371 + 372 + run_command("bash", &["-c", "stow -d ~/.dotfiles -t ~ -- ."]) 373 + .context("Failed to stow dotfiles")?; 374 + 330 375 Ok(()) 331 376 } 332 377 ··· 379 424 380 425 Ok(()) 381 426 } 427 + 428 + fn setup_ssh() -> Result<(), Error> { 429 + let home = dirs::home_dir().ok_or_else(|| Error::msg("Failed to get home directory"))?; 430 + let ssh_dir = home.join(".ssh"); 431 + if !ssh_dir.exists() { 432 + std::fs::create_dir_all(&ssh_dir).context("Failed to create ~/.ssh directory")?; 433 + run_command("chmod", &["700", ssh_dir.to_str().unwrap()]) 434 + .context("Failed to set permissions for ~/.ssh directory")?; 435 + } 436 + 437 + if ssh_dir.join("id_ed25519").exists() { 438 + println!("SSH key already exists. Skipping key generation."); 439 + return Ok(()); 440 + } 441 + 442 + run_command("ssh-keygen", &["-t", "ed25519"]).context("Failed to generate SSH key")?; 443 + 444 + Ok(()) 445 + }
+17 -4
src/command.rs
··· 1 - use std::process::Command; 1 + use std::process::{self, Command}; 2 2 3 3 use anyhow::Error; 4 4 use owo_colors::OwoColorize; ··· 10 10 cmd.green(), 11 11 args.join(" ").green() 12 12 ); 13 - Command::new(cmd) 13 + let status = Command::new(cmd) 14 14 .args(args) 15 15 .env( 16 16 "PATH", 17 17 format!( 18 - "{}/.local/bin:{}", 18 + "{}:{}/.local/bin:{}", 19 + "/nix/var/nix/profiles/default/bin", 19 20 std::env::var("HOME")?, 20 21 std::env::var("PATH")? 21 22 ), 22 23 ) 23 24 .status()?; 25 + 26 + if !status.success() { 27 + println!("Command failed: {}", status); 28 + process::exit(status.code().unwrap_or(1)); 29 + } 30 + 24 31 Ok(()) 25 32 } 26 33 ··· 31 38 cmd.green(), 32 39 args.join(" ").green() 33 40 ); 34 - Command::new(cmd).args(args).status()?; 41 + let status = Command::new(cmd).args(args).status()?; 42 + 43 + if !status.success() { 44 + println!("Command failed: {}", status); 45 + process::exit(status.code().unwrap_or(1)); 46 + } 47 + 35 48 Ok(()) 36 49 }
+33 -3
src/config.rs
··· 1 - use anyhow::Result; 1 + use anyhow::{Context, Error, Result}; 2 2 use owo_colors::OwoColorize; 3 3 use serde::{Deserialize, Serialize}; 4 - use std::collections::HashMap; 4 + use std::{collections::HashMap, process::Command}; 5 5 6 6 use crate::apply::SetupStep; 7 7 ··· 48 48 49 49 impl Configuration { 50 50 pub fn setup_environment(&self, dry_run: bool) -> Result<()> { 51 + let output = Command::new("df") 52 + .args(&["-BG", "--output=size", "/"]) 53 + .output() 54 + .context("Failed to check disk size")?; 55 + 56 + let stdout = String::from_utf8_lossy(&output.stdout); 57 + let disk_size = stdout 58 + .lines() 59 + .nth(1) 60 + .unwrap_or("0") 61 + .replace("G", "") 62 + .trim() 63 + .parse::<u64>() 64 + .unwrap_or(0); 65 + 66 + if disk_size < 7 { 67 + println!("Disk size: {}GB", disk_size); 68 + return Err(Error::msg("Insufficient disk size: >= 7GB required")); 69 + } 70 + 51 71 let steps: Vec<SetupStep> = vec![ 52 72 Some(SetupStep::Paths), 53 73 self.apt_get.as_deref().map(SetupStep::AptGet), ··· 62 82 .as_ref() 63 83 .map(|omp| SetupStep::OhMyPosh(omp.theme.as_deref().unwrap_or("tokyonight_storm"))), 64 84 self.alias.as_ref().map(SetupStep::Alias), 85 + Some(SetupStep::Ssh), 65 86 ] 66 87 .into_iter() 67 88 .flatten() ··· 78 99 for step in steps { 79 100 step.run()?; 80 101 } 102 + println!("{}", "Environment setup completed successfully 🎉".green()); 103 + println!("You can now open a new terminal to see the changes."); 104 + println!( 105 + "Or run {} to apply the changes to the current terminal session.", 106 + "source ~/.bashrc".green() 107 + ); 81 108 } 82 109 Ok(()) 83 110 } ··· 134 161 blesh: Some(true), 135 162 zoxide: Some(true), 136 163 nix: None, 137 - stow: None, 164 + stow: Some(HashMap::from([( 165 + "git".into(), 166 + "github:tsirysndr/android-dotfiles".into(), 167 + )])), 138 168 oh_my_posh: Some(OhMyPosh { 139 169 theme: Some("tokyonight_storm".into()), 140 170 }),