Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add automatic local freerdp installation and docker/podman support #195

Draft
wants to merge 3 commits into
base: rewrite
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 0 additions & 110 deletions scripts/install_quickemu.py

This file was deleted.

5 changes: 4 additions & 1 deletion winapps-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@ default-run = "winapps-cli"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = "4.3.11"
clap = "4.3"
time = "0.3"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["time"] }
winapps = { path = "../winapps" }
52 changes: 10 additions & 42 deletions winapps-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use clap::{arg, Command};
use tracing::info;
use winapps::freerdp::freerdp_back::Freerdp;
use winapps::quickemu::{create_vm, kill_vm, start_vm};
use winapps::{unwrap_or_panic, RemoteClient};

fn cli() -> Command {
Expand All @@ -16,19 +16,15 @@ fn cli() -> Command {
.about("Connects to app on remote")
.arg(arg!(<APP> "App to open")),
)
.subcommand(
Command::new("vm")
.about("Manage a windows 10 vm using quickemu")
.subcommand_required(true)
.arg_required_else_help(true)
.allow_external_subcommands(true)
.subcommand(Command::new("create").about("Create a windows 10 vm using quickget"))
.subcommand(Command::new("start").about("Start the vm"))
.subcommand(Command::new("kill").about("Kill the running VM")),
)
}

fn main() {
tracing_subscriber::fmt()
// .with_timer(tracing_subscriber::fmt::time::uptime())
.without_time()
.with_target(false)
.init();

let cli = cli();
let matches = cli.clone().get_matches();

Expand All @@ -37,48 +33,20 @@ fn main() {

match matches.subcommand() {
Some(("check", _)) => {
println!("Checking remote connection");
info!("Checking remote connection");

client.check_depends(config);
}
Some(("connect", _)) => {
println!("Connecting to remote");
info!("Connecting to remote");

client.run_app(config, None);
}
Some(("run", sub_matches)) => {
println!("Connecting to app on remote");
info!("Connecting to app on remote");

client.run_app(config, sub_matches.get_one::<String>("APP"));
}

Some(("vm", command)) => {
match command.subcommand() {
Some(("create", _)) => {
println!("Creating windows 10 vm..");
create_vm(config);
}
Some(("start", _)) => {
println!("Starting vm..");
start_vm(config);
}

Some(("kill", _)) => {
println!("Killing vm..");
kill_vm(config);
}

Some((_, _)) => {
unwrap_or_panic!(
cli.about("Command not found, try existing ones!")
.print_help(),
"Couldn't print help"
);
}
_ => unreachable!(),
};
}

Some((_, _)) => {
unwrap_or_panic!(
cli.about("Command not found, try existing ones!")
Expand Down
22 changes: 15 additions & 7 deletions winapps/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.75"
derive-new = "0.5.9"
home = "0.5.5"
serde = { version = "1.0.171", features = ["derive"] }
thiserror = "1.0.49"
toml = "0.8.2"
tracing = "0.1.37"
anyhow = "1.0"
sevenz-rust={version="0.2", features=["zstd"]}
derive-new = "0.5"
home = "0.5"
indicatif = { version="0.17", features = ["tokio"] }
reqwest = { version = "0.12", features = ["stream"] }
serde = { version = "1.0", features = ["derive"] }
tempfile = "3.11"
thiserror = "1.0"
tokio = { version = "1.37", features = ["full"] }
tokio-stream = "0.1"
tokio-util = {version="0.7", features=["io"]}
toml = "0.8"
tracing = "0.1"
tracing-subscriber = "0.3"
102 changes: 97 additions & 5 deletions winapps/src/freerdp.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,113 @@
pub mod freerdp_back {
use crate::{get_data_dir, unwrap_or_exit, Config, RemoteClient};
use indicatif::ProgressBar;
use std::cmp::min;
use std::fs::File;
use std::process::{Command, Stdio};
use tokio_stream::StreamExt;
use tracing::{info, warn};

use crate::{unwrap_or_exit, Config, RemoteClient};

pub struct Freerdp {}

impl Freerdp {
fn get_freerdp() -> Command {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(Freerdp::install_freerdp());
Command::new(get_data_dir().join("usr/bin/xfreerdp"))
}

async fn install_freerdp() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason why we're doing the installation here ? I think this won't work for NixOS for instance, maybe we should have it elsewhere

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to assume the user is able to install freerdp on his side or use some other script for installation than putting inside the new rewrite as for Nix/NixOS we'll already need to patch this else this won't work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding Nix, I've packed and re-packaged the rewrite twice already and the Command::new does not find the xfreerdp binary, even when I wrap the executable to prefix PATH...

Copy link
Member Author

@LDprg LDprg Nov 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am generally not happy with the way I implemented this.
But I haven't got any better solution.

Since I think shipping freerdp from our side could eliminate alot of version problems.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oskardotglobal : if you could share the package I could take a look.

@LDprg : That would help yes but I'm not sure we can provide the package for all distros in the Rust code, I mean this will take a lot of time to get right and maintain up to date.

Copy link

@AkechiShiro AkechiShiro Nov 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding rust-nightly, maybe this would be useful? : https://github.com/nix-community/fenix @oskardotglobal

I also was able to use the packages without using Flakes, I think we should try to send them upstream in Nixpkgs and we can also let the users who want to use the Flake come to the repo and use it.

If the PR upstream to Nix gets denied, then we'll have tried to upstream it but most likely we'll rework on the rewrite to address any heavy issues or blocks for upstreaming the package in Nixpkgs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AkechiShiro I did use fenix for projects before. It is a great way to use rust with nix.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AkechiShiro I've used the Oxalica rust-overlay, which is based on Fenix; however, I don't think I would be able to use that in a Nixpkgs, would I?
The package definitely builds without flakes using flake-compat but that still requires us to write a flake.

Copy link

@AkechiShiro AkechiShiro Nov 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it would not be usable in Nixpkgs I think, I'll have a look at some packages in Nixpkgs and check but I'd guess they override the rust package and bump the version most likely.

Copy link

@AkechiShiro AkechiShiro Nov 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oskardotglobal I think we'd be forced to override all rust related packages to force usage of nightly Rust builds if this were to land in Nixpkgs (flakeless)

if get_data_dir().join("usr/bin/xfreerdp").exists() {
return;
}

let freerdp_file = "freerdp-3.6.3-1.fc41.x86_64.rpm";

let bar = ProgressBar::new(1);

bar.set_style(indicatif::ProgressStyle::with_template(
"{spinner:.green} [{elapsed}] {wide_bar:.cyan/blue} {bytes}/{total_bytes} {bytes_per_sec} {msg} ({eta})",
).unwrap().progress_chars("#>-"));
bar.set_message(format!("Starting {}", freerdp_file));

bar.tick();

let response = reqwest::get(
"https://kojipkgs.fedoraproject.org/packages/freerdp/3.6.3/1.fc41/x86_64/"
.to_owned()
+ freerdp_file,
)
.await
.unwrap();

let total_size = response.content_length().unwrap_or(0);

bar.set_length(total_size);
bar.set_message(format!("Downloading {}", freerdp_file));

let mut downloaded: u64 = 0;

let mut stream = response.bytes_stream().map(|result| {
result
.inspect(|result| {
let new = min(downloaded + (result.len() as u64), total_size);
downloaded = new;
bar.set_position(new);
})
.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))
});

let mut file = tokio::fs::File::create(get_data_dir().join(freerdp_file))
.await
.unwrap();

while let Some(item) = stream.next().await {
tokio::io::copy(&mut item.unwrap().as_ref(), &mut file)
.await
.unwrap();
}

let mut rpm2cpio = Command::new("rpm2cpio");
rpm2cpio.stdin(Stdio::from(
File::open(get_data_dir().join(freerdp_file)).unwrap(),
));
rpm2cpio.stdout(Stdio::piped());
rpm2cpio.stderr(Stdio::null());
rpm2cpio.current_dir(get_data_dir());

let rpm2cpio = unwrap_or_exit!(
rpm2cpio.spawn(),
"rpm2cpio execution failed! Check if rpm2cpio (rpm) is installed!",
);

let mut cpio = Command::new("cpio");
cpio.stdin(Stdio::from(rpm2cpio.stdout.unwrap()));
cpio.stdout(Stdio::null());
cpio.stderr(Stdio::null());
cpio.current_dir(get_data_dir());
cpio.arg("-idmv");

unwrap_or_exit!(
cpio.spawn(),
"cpio execution failed! Check if cpio is installed!",
);
}
}

impl RemoteClient for Freerdp {
fn check_depends(&self, config: Config) {
let mut xfreerdp = Command::new("xfreerdp");
let mut xfreerdp = Freerdp::get_freerdp();
xfreerdp.stdout(Stdio::null());
xfreerdp.stderr(Stdio::null());
xfreerdp.args(["-h"]);

unwrap_or_exit!(
xfreerdp.spawn(),
"Freerdp execution failed! It needs to be installed!",
"Freerdp execution failed! Try to delete {}, to force a reinstall.",
get_data_dir().join("usr").display(),
);

info!("Freerdp found!");
Expand All @@ -30,7 +122,7 @@ pub mod freerdp_back {
}

fn run_app(&self, config: Config, app: Option<&String>) {
let mut xfreerdp = Command::new("xfreerdp");
let mut xfreerdp = Freerdp::get_freerdp();
xfreerdp.stdout(Stdio::null());
xfreerdp.stderr(Stdio::null());
match app {
Expand Down
17 changes: 3 additions & 14 deletions winapps/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
pub mod errors;
pub mod freerdp;
pub mod quickemu;

use crate::errors::WinappsError;
use derive_new::new;
Expand All @@ -27,16 +26,6 @@ pub struct Config {
host: HostConfig,
#[new(value = "RemoteConfig::new()")]
rdp: RemoteConfig,
#[new(value = "VmConfig::new()")]
vm: VmConfig,
}

#[derive(new, Debug, Deserialize, Serialize)]
pub struct VmConfig {
#[new(value = "\"windows-10\".to_string()")]
short_name: String,
#[new(value = "\"windows-10-22H2\".to_string()")]
name: String,
}

#[derive(new, Debug, Deserialize, Serialize)]
Expand All @@ -49,11 +38,11 @@ pub struct HostConfig {
pub struct RemoteConfig {
#[new(value = "\"127.0.0.1\".to_string()")]
host: String,
#[new(value = "\"WORKGROUP\".to_string()")]
#[new(value = "\"\".to_string()")]
domain: String,
#[new(value = "\"Quickemu\".to_string()")]
#[new(value = "\"Docker\".to_string()")]
username: String,
#[new(value = "\"quickemu\".to_string()")]
#[new(value = "\"\".to_string()")]
password: String,
}

Expand Down
Loading
Loading