Add gpgui-helper (#326)

This commit is contained in:
Kevin Yue
2024-02-21 07:34:14 -05:00
committed by GitHub
parent 5767c252b7
commit 4be877bf8c
53 changed files with 4351 additions and 51 deletions

View File

@@ -12,11 +12,7 @@ pub trait CommandExt {
impl CommandExt for Command {
fn new_pkexec<S: AsRef<OsStr>>(program: S) -> Command {
let mut cmd = Command::new("pkexec");
cmd
.arg("--disable-internal-agent")
.arg("--user")
.arg("root")
.arg(program);
cmd.arg("--user").arg("root").arg(program);
cmd
}

View File

@@ -0,0 +1,68 @@
use std::{collections::HashMap, path::PathBuf, process::Stdio};
use anyhow::bail;
use log::info;
use tokio::{io::AsyncWriteExt, process::Command};
use crate::{process::command_traits::CommandExt, utils, GP_GUI_HELPER_BINARY};
pub struct GuiHelperLauncher<'a> {
program: PathBuf,
envs: Option<&'a HashMap<String, String>>,
api_key: &'a [u8],
gui_version: Option<&'a str>,
}
impl<'a> GuiHelperLauncher<'a> {
pub fn new(api_key: &'a [u8]) -> Self {
Self {
program: GP_GUI_HELPER_BINARY.into(),
envs: None,
api_key,
gui_version: None,
}
}
pub fn envs(mut self, envs: Option<&'a HashMap<String, String>>) -> Self {
self.envs = envs;
self
}
pub fn gui_version(mut self, version: Option<&'a str>) -> Self {
self.gui_version = version;
self
}
pub async fn launch(&self) -> anyhow::Result<()> {
let mut cmd = Command::new(&self.program);
if let Some(envs) = self.envs {
cmd.env_clear();
cmd.envs(envs);
}
cmd.arg("--api-key-on-stdin");
if let Some(gui_version) = self.gui_version {
cmd.arg("--gui-version").arg(gui_version);
}
info!("Launching gpgui-helper");
let mut non_root_cmd = cmd.into_non_root()?;
let mut child = non_root_cmd.kill_on_drop(true).stdin(Stdio::piped()).spawn()?;
let Some(mut stdin) = child.stdin.take() else {
bail!("Failed to open stdin");
};
let api_key = utils::base64::encode(self.api_key);
tokio::spawn(async move {
stdin.write_all(api_key.as_bytes()).await.unwrap();
drop(stdin);
});
let exit_status = child.wait().await?;
info!("gpgui-helper exited with: {}", exit_status);
Ok(())
}
}

View File

@@ -4,30 +4,28 @@ use std::{
process::{ExitStatus, Stdio},
};
use anyhow::bail;
use log::info;
use tokio::{io::AsyncWriteExt, process::Command};
use crate::{utils::base64, GP_GUI_BINARY};
use crate::{process::gui_helper_launcher::GuiHelperLauncher, utils::base64, GP_GUI_BINARY};
use super::command_traits::CommandExt;
pub struct GuiLauncher {
pub struct GuiLauncher<'a> {
version: &'a str,
program: PathBuf,
api_key: Option<Vec<u8>>,
api_key: &'a [u8],
minimized: bool,
envs: Option<HashMap<String, String>>,
}
impl Default for GuiLauncher {
fn default() -> Self {
Self::new()
}
}
impl GuiLauncher {
pub fn new() -> Self {
impl<'a> GuiLauncher<'a> {
pub fn new(version: &'a str, api_key: &'a [u8]) -> Self {
Self {
version,
program: GP_GUI_BINARY.into(),
api_key: None,
api_key,
minimized: false,
envs: None,
}
@@ -38,17 +36,23 @@ impl GuiLauncher {
self
}
pub fn api_key(mut self, api_key: Vec<u8>) -> Self {
self.api_key = Some(api_key);
self
}
pub fn minimized(mut self, minimized: bool) -> Self {
self.minimized = minimized;
self
}
pub async fn launch(&self) -> anyhow::Result<ExitStatus> {
// Check if the program's version
if let Err(err) = self.check_version().await {
info!("Check version failed: {}", err);
// Download the program and replace the current one
self.download_program().await?;
}
self.launch_program().await
}
async fn launch_program(&self) -> anyhow::Result<ExitStatus> {
let mut cmd = Command::new(&self.program);
if let Some(envs) = &self.envs {
@@ -56,33 +60,60 @@ impl GuiLauncher {
cmd.envs(envs);
}
if self.api_key.is_some() {
cmd.arg("--api-key-on-stdin");
}
cmd.arg("--api-key-on-stdin");
if self.minimized {
cmd.arg("--minimized");
}
info!("Launching gpgui");
let mut non_root_cmd = cmd.into_non_root()?;
let mut child = non_root_cmd.kill_on_drop(true).stdin(Stdio::piped()).spawn()?;
let Some(mut stdin) = child.stdin.take() else {
bail!("Failed to open stdin");
};
let mut stdin = child
.stdin
.take()
.ok_or_else(|| anyhow::anyhow!("Failed to open stdin"))?;
if let Some(api_key) = &self.api_key {
let api_key = base64::encode(api_key);
tokio::spawn(async move {
stdin.write_all(api_key.as_bytes()).await.unwrap();
drop(stdin);
});
}
let api_key = base64::encode(self.api_key);
tokio::spawn(async move {
stdin.write_all(api_key.as_bytes()).await.unwrap();
drop(stdin);
});
let exit_status = child.wait().await?;
Ok(exit_status)
}
async fn check_version(&self) -> anyhow::Result<()> {
let cmd = Command::new(&self.program).arg("--version").output().await?;
let output = String::from_utf8_lossy(&cmd.stdout);
// Version string: "gpgui 2.0.0 (2024-02-05)"
let Some(version) = output.split_whitespace().nth(1) else {
bail!("Failed to parse version: {}", output);
};
if version != self.version {
bail!("Version mismatch: expected {}, got {}", self.version, version);
}
info!("Version check passed: {}", version);
Ok(())
}
async fn download_program(&self) -> anyhow::Result<()> {
let gui_helper = GuiHelperLauncher::new(self.api_key);
gui_helper
.envs(self.envs.as_ref())
.gui_version(Some(self.version))
.launch()
.await?;
// Check the version again
self.check_version().await?;
Ok(())
}
}

View File

@@ -1,4 +1,5 @@
pub(crate) mod command_traits;
pub(crate) mod gui_helper_launcher;
pub mod auth_launcher;
#[cfg(feature = "browser-auth")]