xtask-todo-lib 0.1.32

Todo workspace library and cargo devshell subcommand
Documentation
use std::path::{Path, PathBuf};
use std::process::Command;

use super::super::super::config::{
    ENV_DEVSHELL_VM_DISABLE_PODMAN_SSH_HOME, ENV_DEVSHELL_VM_SKIP_PODMAN_BOOTSTRAP,
};
use super::{VmError, MSG_PODMAN_INSTALL};

fn link_podman_machine_into_ssh_home(ssh_home: &Path) -> std::io::Result<()> {
    let real_profile = match std::env::var("USERPROFILE") {
        Ok(s) if !s.trim().is_empty() => PathBuf::from(s.trim()),
        _ => return Ok(()),
    };
    let real_podman = real_profile
        .join(".local")
        .join("share")
        .join("containers")
        .join("podman");
    if !real_podman.is_dir() {
        return Ok(());
    }
    let link = ssh_home
        .join(".local")
        .join("share")
        .join("containers")
        .join("podman");
    if link.exists() {
        return Ok(());
    }
    if let Some(parent) = link.parent() {
        std::fs::create_dir_all(parent)?;
    }
    std::os::windows::fs::symlink_dir(&real_podman, &link)
}

fn ssh_home_for_podman() -> std::io::Result<PathBuf> {
    let home = std::env::temp_dir().join("cargo-devshell-ssh-home");
    let ssh = home.join(".ssh");
    std::fs::create_dir_all(&ssh)?;
    let kh = ssh.join("known_hosts");
    if !kh.exists() {
        std::fs::write(&kh, "")?;
    }
    link_podman_machine_into_ssh_home(&home).unwrap_or_else(|e| {
        eprintln!(
            "dev_shell: could not symlink %USERPROFILE%\\.local\\share\\containers\\podman into isolated profile (SSH workaround): {e}\n\
             Or set {}=1 to use your real profile (and fix or unlock .ssh\\known_hosts).",
            ENV_DEVSHELL_VM_DISABLE_PODMAN_SSH_HOME
        );
    });
    Ok(home)
}

fn apply_windows_podman_profile_env(cmd: &mut Command, home: &Path) {
    cmd.env("USERPROFILE", home);
    cmd.env("HOME", home);
    if let Some(s) = home.to_str() {
        let b = s.as_bytes();
        if b.len() >= 2 && b[1] == b':' {
            if let (Some(drive), Some(rest)) = (s.get(..2), s.get(2..)) {
                cmd.env("HOMEDRIVE", drive);
                cmd.env("HOMEPATH", rest);
            }
        }
    }
}

fn apply_podman_ssh_home_env(cmd: &mut Command) {
    if std::env::var(ENV_DEVSHELL_VM_DISABLE_PODMAN_SSH_HOME).is_ok() {
        return;
    }
    match ssh_home_for_podman() {
        Ok(home) => apply_windows_podman_profile_env(cmd, &home),
        Err(e) => {
            eprintln!("dev_shell: could not create temp HOME for podman (SSH known_hosts workaround): {e}");
        }
    }
}

pub(super) fn podman_command() -> Command {
    let mut c = Command::new("podman");
    apply_podman_ssh_home_env(&mut c);
    c
}

pub(super) fn podman_version_succeeds() -> bool {
    podman_command()
        .args(["--version"])
        .output()
        .map(|o| o.status.success())
        .unwrap_or(false)
}

pub(super) fn try_install_podman_via_winget() {
    if !Command::new("winget")
        .args(["--version"])
        .output()
        .map(|o| o.status.success())
        .unwrap_or(false)
    {
        eprintln!("dev_shell: winget not found — install Podman manually from https://podman.io/ or use Chocolatey/choco install podman.");
        return;
    }
    eprintln!("dev_shell: Podman not on PATH; trying: winget install -e --id Podman.Podman");
    let status = Command::new("winget")
        .args([
            "install",
            "-e",
            "--id",
            "Podman.Podman",
            "--accept-package-agreements",
            "--accept-source-agreements",
        ])
        .status();
    match status {
        Ok(s) if s.success() => eprintln!(
            "dev_shell: winget reported success. Open a NEW terminal, then run:  podman version"
        ),
        Ok(_) => eprintln!(
            "dev_shell: winget install failed — try an elevated (Administrator) terminal, or install from https://podman.io/"
        ),
        Err(e) => eprintln!("dev_shell: could not run winget: {e}"),
    }
}

pub(super) fn try_podman_engine_ready() {
    if std::env::var(ENV_DEVSHELL_VM_SKIP_PODMAN_BOOTSTRAP).is_ok() {
        return;
    }
    if podman_command()
        .args(["info"])
        .output()
        .map(|o| o.status.success())
        .unwrap_or(false)
    {
        return;
    }
    eprintln!("dev_shell: podman info failed — trying: podman machine start");
    let _ = podman_command().args(["machine", "start"]).status();
    std::thread::sleep(std::time::Duration::from_secs(2));
}

pub(super) fn podman_not_available_error() -> VmError {
    VmError::Ipc(MSG_PODMAN_INSTALL.to_string())
}