lios 0.1.67

A gorgeous GTK4/VTE Linux terminal with live themes, glass backgrounds, session prompt profiles, Sixel images, and safe GPU controls.
use std::env;
use std::ffi::OsString;
use std::fs;
use std::path::PathBuf;
use std::process::Command;

const INSTALLER: &[u8] = include_bytes!("../install-jetson.sh");

#[derive(Debug, PartialEq, Eq)]
enum BootstrapAction {
    Help,
    Version,
    Check,
    Install {
        launch: bool,
        forward: Vec<OsString>,
    },
}

pub fn run() -> i32 {
    let args = env::args_os().skip(1).collect::<Vec<_>>();
    match bootstrap_action(&args) {
        BootstrapAction::Help => {
            print_help();
            0
        }
        BootstrapAction::Version => {
            println!("lios {} (installer)", env!("CARGO_PKG_VERSION"));
            0
        }
        BootstrapAction::Check => run_installer(true, false, &[]),
        BootstrapAction::Install { launch, forward } => run_installer(false, launch, &forward),
    }
}

fn bootstrap_action(args: &[OsString]) -> BootstrapAction {
    match args.first().and_then(|arg| arg.to_str()) {
        Some("-h" | "--help") => BootstrapAction::Help,
        Some("-V" | "--version") => BootstrapAction::Version,
        Some("--check") => BootstrapAction::Check,
        Some("install" | "--install" | "---install") => BootstrapAction::Install {
            launch: false,
            forward: Vec::new(),
        },
        _ => BootstrapAction::Install {
            launch: true,
            forward: args.to_vec(),
        },
    }
}

fn run_installer(check_only: bool, launch: bool, forward: &[OsString]) -> i32 {
    if !cfg!(target_os = "linux") {
        eprintln!("Lios is supported on Linux.");
        return 1;
    }

    let installer_path = installer_path();
    let current_executable = env::current_exe().ok();
    if let Err(error) = fs::write(&installer_path, INSTALLER) {
        eprintln!("Could not prepare the Lios installer: {error}");
        return 1;
    }

    let mut installer = Command::new("sh");
    installer.arg(&installer_path);
    if check_only {
        installer.arg("--check");
    }
    if let Some(install_root) = current_executable
        .as_deref()
        .and_then(|executable| executable.parent())
        .and_then(|bin| bin.parent())
    {
        installer.env("LIOS_INSTALL_ROOT", install_root);
    }
    let status = installer.status();
    let _ = fs::remove_file(&installer_path);
    let Ok(status) = status else {
        eprintln!("Could not start the Lios installer.");
        return 1;
    };
    if !status.success() {
        return status.code().unwrap_or(1);
    }
    if check_only || !launch {
        return 0;
    }

    let Some(executable) = current_executable else {
        eprintln!("Lios installed, but the executable path could not be resolved.");
        return 1;
    };
    match Command::new(executable).args(forward).status() {
        Ok(status) => status.code().unwrap_or(0),
        Err(error) => {
            eprintln!("Lios installed, but could not be launched: {error}");
            1
        }
    }
}

fn installer_path() -> PathBuf {
    env::temp_dir().join(format!("lios-install-{}.sh", std::process::id()))
}

fn print_help() {
    println!(
        "Lios Terminal installer\n\nUsage:\n  lios\n  lios install\n  lios --check\n\nThe first run installs native GTK/VTE dependencies, replaces this bootstrap with the full Lios terminal, creates the desktop launcher, and starts Lios."
    );
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn no_arguments_installs_then_launches() {
        assert_eq!(
            bootstrap_action(&[]),
            BootstrapAction::Install {
                launch: true,
                forward: Vec::new()
            }
        );
    }

    #[test]
    fn install_only_provisions_native_binary() {
        assert_eq!(
            bootstrap_action(&[OsString::from("install")]),
            BootstrapAction::Install {
                launch: false,
                forward: Vec::new()
            }
        );
    }

    #[test]
    fn launch_arguments_are_forwarded_after_install() {
        let args = vec![OsString::from("--theme"), OsString::from("bronco")];
        assert_eq!(
            bootstrap_action(&args),
            BootstrapAction::Install {
                launch: true,
                forward: args
            }
        );
    }
}