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

Merge pull request #1 from tsirysndr/feat/tailscale

feat: add support for tailscale vpn

authored by tsiry-sandratraina.com and committed by

GitHub 8f9205f4 c1b54a92

+98 -10
+6 -1
README.md
··· 44 44 apt-get = ["build-essential", "curl", "git", "gawk", "wget", "unzip", "autoconf", "automake", "cmake", "tmux", "openssh-server", "openssh-client", "httpie", "code", "screenfetch", "stow"] 45 45 "ble.sh" = true 46 46 zoxide = true 47 + tailscale = false 47 48 48 49 [stow] 49 50 git = "github:tsirysndr/android-dotfiles" ··· 74 75 75 76 [alias] 76 77 ls = "eza -lh" 78 + 79 + [ssh] 80 + port = 8022 81 + authorized_keys = [] 77 82 ``` 78 83 79 - You can customize it and run `oh-my-droid setup` to apply the changes. 84 + You can customize it and run `oh-my-droid apply` to apply the changes.
+68 -7
src/apply.rs
··· 3 3 use anyhow::{Context, Error}; 4 4 use owo_colors::OwoColorize; 5 5 6 - use crate::command::{run_command, run_command_without_local_path}; 6 + use crate::{ 7 + command::{run_command, run_command_without_local_path}, 8 + config::SshConfig, 9 + }; 7 10 8 11 #[derive(Debug)] 9 12 pub enum SetupStep<'a> { ··· 17 20 OhMyPosh(&'a str), 18 21 Zoxide(bool), 19 22 Alias(&'a HashMap<String, String>), 20 - Ssh, 23 + Ssh(&'a SshConfig), 21 24 Paths, 25 + Tailscale(bool), 22 26 } 23 27 24 28 impl<'a> SetupStep<'a> { ··· 34 38 SetupStep::OhMyPosh(theme) => setup_oh_my_posh(theme), 35 39 SetupStep::Zoxide(enabled) => enable_zoxide(*enabled), 36 40 SetupStep::Alias(map) => setup_alias(map), 37 - SetupStep::Ssh => setup_ssh(), 41 + SetupStep::Ssh(config) => setup_ssh(config), 38 42 SetupStep::Paths => setup_paths(), 43 + SetupStep::Tailscale(enabled) => enable_tailscale(*enabled), 39 44 } 40 45 } 41 46 ··· 164 169 " - ~/.local/bin".green() 165 170 ) 166 171 } 167 - SetupStep::Ssh => { 172 + SetupStep::Ssh(config) => { 168 173 format!( 169 - "{} {}\n{}", 174 + "{} {}\n - Port: {}\n - Authorized Keys: {}", 170 175 "SSH".blue().bold(), 171 176 "(Setup SSH keys and configuration)".italic(), 172 - " - Generate SSH key pair".green() 177 + config.port.unwrap_or(0).to_string().green(), 178 + config 179 + .authorized_keys 180 + .as_ref() 181 + .map(|keys| { 182 + keys.iter() 183 + .map(|key| format!(" - {}", key.green())) 184 + .collect::<Vec<_>>() 185 + .join("\n") 186 + }) 187 + .unwrap_or_else(|| " - None".into()) 188 + ) 189 + } 190 + SetupStep::Tailscale(enabled) => { 191 + format!( 192 + "{} {}\n - Enabled: {}", 193 + "Tailscale".blue().bold(), 194 + "(Install and configure Tailscale VPN)".italic(), 195 + enabled.to_string().green() 173 196 ) 174 197 } 175 198 } ··· 425 448 Ok(()) 426 449 } 427 450 428 - fn setup_ssh() -> Result<(), Error> { 451 + fn setup_ssh(config: &SshConfig) -> Result<(), Error> { 429 452 let home = dirs::home_dir().ok_or_else(|| Error::msg("Failed to get home directory"))?; 430 453 let ssh_dir = home.join(".ssh"); 431 454 if !ssh_dir.exists() { ··· 434 457 .context("Failed to set permissions for ~/.ssh directory")?; 435 458 } 436 459 460 + if let Some(port) = config.port { 461 + run_command( 462 + "bash", 463 + &[ 464 + "-c", 465 + &format!( 466 + "sudo sed -i -E '/^[#[:space:]]*Port[[:space:]]+[0-9]+/d' /etc/ssh/sshd_config && echo \"Port {port}\" | sudo tee -a /etc/ssh/sshd_config >/dev/null && sudo sshd -t" 467 + ), 468 + ], 469 + ) 470 + .context("Failed to update SSH config with port")?; 471 + run_command("sudo", &["systemctl", "reload", "ssh"]) 472 + .context("Failed to restart SSH service")?; 473 + } 474 + 475 + if let Some(authorized_keys) = &config.authorized_keys { 476 + for key in authorized_keys { 477 + run_command( 478 + "bash", 479 + &["-c", &format!("echo '{}' > ~/.ssh/authorized_keys", key)], 480 + )?; 481 + } 482 + } 483 + 437 484 if ssh_dir.join("id_ed25519").exists() { 438 485 println!("SSH key already exists. Skipping key generation."); 439 486 return Ok(()); ··· 443 490 444 491 Ok(()) 445 492 } 493 + 494 + fn enable_tailscale(enabled: bool) -> Result<(), Error> { 495 + if enabled { 496 + run_command( 497 + "bash", 498 + &["-c", "curl -fsSL https://tailscale.com/install.sh | sh"], 499 + ) 500 + .context("Failed to install Tailscale")?; 501 + run_command("bash", &["-c", "sudo tailscale up"]).context("Failed to enable Tailscale")?; 502 + run_command("bash", &["-c", "sudo tailscale ip"]) 503 + .context("Failed to check Tailscale status")?; 504 + } 505 + Ok(()) 506 + }
+22 -1
src/config.rs
··· 12 12 } 13 13 14 14 #[derive(Debug, Clone, Serialize, Deserialize)] 15 + pub struct SshConfig { 16 + #[serde(skip_serializing_if = "Option::is_none")] 17 + pub port: Option<usize>, 18 + 19 + #[serde(skip_serializing_if = "Option::is_none")] 20 + pub authorized_keys: Option<Vec<String>>, 21 + } 22 + 23 + #[derive(Debug, Clone, Serialize, Deserialize)] 15 24 pub struct Configuration { 16 25 #[serde(skip_serializing_if = "Option::is_none")] 17 26 pub stow: Option<HashMap<String, String>>, ··· 44 53 45 54 #[serde(skip_serializing_if = "Option::is_none")] 46 55 pub alias: Option<HashMap<String, String>>, 56 + 57 + #[serde(skip_serializing_if = "Option::is_none")] 58 + pub tailscale: Option<bool>, 59 + 60 + #[serde(skip_serializing_if = "Option::is_none")] 61 + pub ssh: Option<SshConfig>, 47 62 } 48 63 49 64 impl Configuration { ··· 82 97 .as_ref() 83 98 .map(|omp| SetupStep::OhMyPosh(omp.theme.as_deref().unwrap_or("tokyonight_storm"))), 84 99 self.alias.as_ref().map(SetupStep::Alias), 85 - Some(SetupStep::Ssh), 100 + self.ssh.as_ref().map(SetupStep::Ssh), 101 + self.tailscale.map(SetupStep::Tailscale), 86 102 ] 87 103 .into_iter() 88 104 .flatten() ··· 169 185 theme: Some("tokyonight_storm".into()), 170 186 }), 171 187 alias: Some(HashMap::from([("ls".into(), "eza -lh".into())])), 188 + tailscale: Some(false), 189 + ssh: Some(SshConfig { 190 + port: Some(8022), 191 + authorized_keys: Some(vec![]), 192 + }), 172 193 } 173 194 } 174 195 }
+2 -1
src/main.rs
··· 41 41 Command::new("setup") 42 42 .about("Set up the environment with the default configuration.") 43 43 .arg(arg!(-d --"dry-run" "Simulate the setup process without making any changes.")) 44 - .arg(arg!(-y --"yes" "Skip confirmation prompts during setup.")), 44 + .arg(arg!(-y --"yes" "Skip confirmation prompts during setup.")) 45 + .alias("apply"), 45 46 ) 46 47 .arg(arg!(-d --"dry-run" "Simulate the setup process without making any changes.")) 47 48 .arg(arg!(-y --"yes" "Skip confirmation prompts during setup."))