Implement per-distro installation flow
This commit is contained in:
parent
caf870d05e
commit
b9b60e9b6c
21 changed files with 1109 additions and 33 deletions
88
crates/aim-core/src/platform/capabilities.rs
Normal file
88
crates/aim-core/src/platform/capabilities.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
use std::fs::{self, OpenOptions};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct DesktopHelpers {
|
||||
pub update_desktop_database: bool,
|
||||
pub gtk_update_icon_cache: bool,
|
||||
pub update_desktop_database_path: Option<PathBuf>,
|
||||
pub gtk_update_icon_cache_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct WritableRoots {
|
||||
pub payload: bool,
|
||||
pub desktop_entries: bool,
|
||||
pub icons: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct HostCapabilities {
|
||||
pub is_immutable: bool,
|
||||
pub is_nix: bool,
|
||||
pub has_desktop_session: bool,
|
||||
pub helpers: DesktopHelpers,
|
||||
pub writable_roots: WritableRoots,
|
||||
}
|
||||
|
||||
impl HostCapabilities {
|
||||
pub fn immutable_user_only() -> Self {
|
||||
Self {
|
||||
is_immutable: true,
|
||||
has_desktop_session: true,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn probe_desktop_helpers(search_paths: &[&Path]) -> DesktopHelpers {
|
||||
let update_desktop_database_path = command_path(search_paths, "update-desktop-database");
|
||||
let gtk_update_icon_cache_path = command_path(search_paths, "gtk-update-icon-cache");
|
||||
|
||||
DesktopHelpers {
|
||||
update_desktop_database: update_desktop_database_path.is_some(),
|
||||
gtk_update_icon_cache: gtk_update_icon_cache_path.is_some(),
|
||||
update_desktop_database_path,
|
||||
gtk_update_icon_cache_path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn probe_writable_roots(payload: &Path, desktop_entries: &Path, icons: &Path) -> WritableRoots {
|
||||
WritableRoots {
|
||||
payload: is_writable_dir(payload),
|
||||
desktop_entries: is_writable_dir(desktop_entries),
|
||||
icons: is_writable_dir(icons),
|
||||
}
|
||||
}
|
||||
|
||||
fn command_path(search_paths: &[&Path], executable: &str) -> Option<PathBuf> {
|
||||
search_paths
|
||||
.iter()
|
||||
.map(|path| path.join(executable))
|
||||
.find(|candidate| is_executable_file(candidate))
|
||||
}
|
||||
|
||||
fn is_executable_file(path: &Path) -> bool {
|
||||
path.is_file()
|
||||
}
|
||||
|
||||
fn is_writable_dir(path: &Path) -> bool {
|
||||
if !path.is_dir() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let probe_path = path.join(".aim-write-test");
|
||||
let result = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&probe_path);
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let _ = fs::remove_file(&probe_path);
|
||||
true
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
75
crates/aim-core/src/platform/distro.rs
Normal file
75
crates/aim-core/src/platform/distro.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum DistroFamily {
|
||||
Debian,
|
||||
Fedora,
|
||||
Arch,
|
||||
OpenSuse,
|
||||
Alpine,
|
||||
Nix,
|
||||
Immutable,
|
||||
Generic,
|
||||
}
|
||||
|
||||
pub fn detect_distro_family(os_release: &str) -> DistroFamily {
|
||||
let id = lookup_field(os_release, "ID");
|
||||
let id_like = lookup_field(os_release, "ID_LIKE");
|
||||
let variant_id = lookup_field(os_release, "VARIANT_ID");
|
||||
|
||||
if matches_any(id, id_like, &["nixos"]) {
|
||||
return DistroFamily::Nix;
|
||||
}
|
||||
|
||||
if matches_field(variant_id, &["silverblue", "kinoite", "coreos", "aurora"])
|
||||
|| matches_any(
|
||||
id,
|
||||
id_like,
|
||||
&["silverblue", "kinoite", "ublue", "fedora-immutable"],
|
||||
)
|
||||
{
|
||||
return DistroFamily::Immutable;
|
||||
}
|
||||
|
||||
if matches_any(id, id_like, &["fedora", "rhel", "centos"]) {
|
||||
return DistroFamily::Fedora;
|
||||
}
|
||||
|
||||
if matches_any(id, id_like, &["debian", "ubuntu"]) {
|
||||
return DistroFamily::Debian;
|
||||
}
|
||||
|
||||
if matches_any(id, id_like, &["arch", "manjaro"]) {
|
||||
return DistroFamily::Arch;
|
||||
}
|
||||
|
||||
if matches_any(id, id_like, &["opensuse", "suse", "sles"]) {
|
||||
return DistroFamily::OpenSuse;
|
||||
}
|
||||
|
||||
if matches_any(id, id_like, &["alpine"]) {
|
||||
return DistroFamily::Alpine;
|
||||
}
|
||||
|
||||
DistroFamily::Generic
|
||||
}
|
||||
|
||||
fn lookup_field<'a>(os_release: &'a str, key: &str) -> Option<&'a str> {
|
||||
os_release
|
||||
.lines()
|
||||
.find_map(|line| line.strip_prefix(&format!("{key}=")))
|
||||
.map(trim_value)
|
||||
}
|
||||
|
||||
fn trim_value(value: &str) -> &str {
|
||||
value.trim().trim_matches('"')
|
||||
}
|
||||
|
||||
fn matches_any(id: Option<&str>, id_like: Option<&str>, needles: &[&str]) -> bool {
|
||||
matches_field(id, needles) || matches_field(id_like, needles)
|
||||
}
|
||||
|
||||
fn matches_field(field: Option<&str>, needles: &[&str]) -> bool {
|
||||
field
|
||||
.into_iter()
|
||||
.flat_map(|value| value.split_ascii_whitespace())
|
||||
.any(|candidate| needles.contains(&candidate))
|
||||
}
|
||||
|
|
@ -1,5 +1,18 @@
|
|||
pub mod capabilities;
|
||||
pub mod distro;
|
||||
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub use crate::domain::app::InstallScope;
|
||||
pub use capabilities::{DesktopHelpers, HostCapabilities, WritableRoots};
|
||||
pub use distro::{DistroFamily, detect_distro_family};
|
||||
|
||||
const OS_RELEASE_PATH_ENV: &str = "AIM_OS_RELEASE_PATH";
|
||||
const HELPER_PATHS_ENV: &str = "AIM_HELPER_PATHS";
|
||||
|
||||
pub fn user_managed_appimages_dir(home_dir: &Path) -> PathBuf {
|
||||
home_dir.join(".local/lib/aim/appimages")
|
||||
}
|
||||
|
|
@ -23,3 +36,71 @@ pub fn system_applications_dir() -> PathBuf {
|
|||
pub fn system_icons_dir() -> PathBuf {
|
||||
PathBuf::from("/usr/share/icons/hicolor/256x256/apps")
|
||||
}
|
||||
|
||||
pub fn probe_live_host(
|
||||
home_dir: &Path,
|
||||
requested_scope: InstallScope,
|
||||
) -> io::Result<(DistroFamily, HostCapabilities)> {
|
||||
let os_release = load_os_release()?;
|
||||
let family = detect_distro_family(&os_release);
|
||||
let helper_paths = helper_search_paths();
|
||||
let helper_refs = helper_paths
|
||||
.iter()
|
||||
.map(PathBuf::as_path)
|
||||
.collect::<Vec<_>>();
|
||||
let helpers = capabilities::probe_desktop_helpers(&helper_refs);
|
||||
let (payload_root, desktop_root, icon_root) = match requested_scope {
|
||||
InstallScope::User => (
|
||||
user_managed_appimages_dir(home_dir),
|
||||
user_applications_dir(home_dir),
|
||||
user_icons_dir(home_dir),
|
||||
),
|
||||
InstallScope::System => (
|
||||
system_managed_appimages_dir(),
|
||||
system_applications_dir(),
|
||||
system_icons_dir(),
|
||||
),
|
||||
};
|
||||
|
||||
Ok((
|
||||
family,
|
||||
HostCapabilities {
|
||||
is_immutable: family == DistroFamily::Immutable,
|
||||
is_nix: family == DistroFamily::Nix,
|
||||
has_desktop_session: env::var_os("DISPLAY").is_some()
|
||||
|| env::var_os("WAYLAND_DISPLAY").is_some()
|
||||
|| env::var_os("XDG_CURRENT_DESKTOP").is_some(),
|
||||
helpers,
|
||||
writable_roots: capabilities::probe_writable_roots(
|
||||
&payload_root,
|
||||
&desktop_root,
|
||||
&icon_root,
|
||||
),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn load_os_release() -> io::Result<String> {
|
||||
let path = env::var_os(OS_RELEASE_PATH_ENV)
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from("/etc/os-release"));
|
||||
|
||||
match fs::read_to_string(path) {
|
||||
Ok(contents) => Ok(contents),
|
||||
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(String::new()),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
fn helper_search_paths() -> Vec<PathBuf> {
|
||||
let mut paths = Vec::new();
|
||||
|
||||
if let Some(extra_paths) = env::var_os(HELPER_PATHS_ENV) {
|
||||
paths.extend(env::split_paths(&extra_paths));
|
||||
}
|
||||
if let Some(system_paths) = env::var_os("PATH") {
|
||||
paths.extend(env::split_paths(&system_paths));
|
||||
}
|
||||
|
||||
paths
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue