mirror of
https://github.com/winapps-org/winapps.git
synced 2025-06-06 15:17:19 +02:00
Merge pull request #26 from oskardotglobal/rewrite
Creating and running the VM via quickemu
This commit is contained in:
commit
d2bc015e94
110
scripts/install_quickemu
Normal file
110
scripts/install_quickemu
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import platform
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def _(c: str):
|
||||||
|
"""Execute the command `c` and print it"""
|
||||||
|
print("> " + c)
|
||||||
|
os.system(c)
|
||||||
|
|
||||||
|
|
||||||
|
def clone_repo():
|
||||||
|
if os.path.exists(os.path.expanduser("~/.local/share/quickemu")):
|
||||||
|
print("📦 quickemu is already installed. Updating...")
|
||||||
|
update_quickemu()
|
||||||
|
return
|
||||||
|
|
||||||
|
print("📦 Cloning quickemu...")
|
||||||
|
|
||||||
|
_("git clone --filter=blob:none https://github.com/quickemu-project/quickemu ~/.local/share/quickemu")
|
||||||
|
_("mkdir -p ~/.local/bin")
|
||||||
|
_("ln -s ~/.local/share/quickemu/quickemu ~/.local/bin/quickemu")
|
||||||
|
_("ln -s ~/.local/share/quickemu/macrecovery ~/.local/bin/macrecovery")
|
||||||
|
_("ln -s ~/.local/share/quickemu/quickget ~/.local/bin/quickget")
|
||||||
|
_("ln -s ~/.local/share/quickemu/windowskey ~/.local/bin/windowskey")
|
||||||
|
|
||||||
|
print("Installation complete.")
|
||||||
|
print("⚠️ Make sure ~/.local/bin is in your PATH.")
|
||||||
|
|
||||||
|
|
||||||
|
def update_quickemu():
|
||||||
|
print("📦 Updating quickemu...")
|
||||||
|
|
||||||
|
_("cd ~/.local/share/quickemu")
|
||||||
|
_("git pull")
|
||||||
|
|
||||||
|
print("Update complete.")
|
||||||
|
print("⚠️ Make sure ~/.local/bin is in your PATH.")
|
||||||
|
|
||||||
|
|
||||||
|
def install_fedora():
|
||||||
|
print("📦 Installing dependencies...")
|
||||||
|
|
||||||
|
_("sudo dnf install qemu bash coreutils edk2-tools grep jq lsb procps python3 genisoimage usbutils"
|
||||||
|
+ " util-linux sed spice-gtk-tools swtpm wget xdg-user-dirs xrandr unzip socat -y")
|
||||||
|
|
||||||
|
clone_repo()
|
||||||
|
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def install_deb():
|
||||||
|
print("📦 Installing dependencies...")
|
||||||
|
|
||||||
|
_("sudo apt update")
|
||||||
|
_("sudo apt install qemu bash coreutils ovmf grep jq lsb-base procps python3 genisoimage usbutils"
|
||||||
|
+ " util-linux sed spice-client-gtk libtss2-tcti-swtpm0 wget xdg-user-dirs zsync unzip socat -y")
|
||||||
|
|
||||||
|
clone_repo()
|
||||||
|
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def install_ubuntu():
|
||||||
|
print("⚠️ Adding ppa...")
|
||||||
|
|
||||||
|
_("sudo apt-add-repository ppa:flexiondotorg/quickemu")
|
||||||
|
_("sudo apt update")
|
||||||
|
_("sudo apt install quickemu -y")
|
||||||
|
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("⚠️ This script requires elevated privileges (sudo). You will be asked for your password.")
|
||||||
|
|
||||||
|
os_release = platform.freedesktop_os_release()
|
||||||
|
|
||||||
|
distro_id = os_release.get("ID_LIKE")
|
||||||
|
distro_id_like = os_release.get("ID")
|
||||||
|
|
||||||
|
if not distro_id and not distro_id_like:
|
||||||
|
print("❌ Couldn't fetch distro, is os-release installed?")
|
||||||
|
|
||||||
|
if distro_id == "ubuntu" \
|
||||||
|
or distro_id_like == "ubuntu":
|
||||||
|
install_ubuntu()
|
||||||
|
elif distro_id == "debian" \
|
||||||
|
or distro_id_like == "debian" \
|
||||||
|
or shutil.which("apt"):
|
||||||
|
install_deb()
|
||||||
|
elif distro_id == "fedora" \
|
||||||
|
or distro_id_like == "fedora" \
|
||||||
|
or shutil.which("dnf"):
|
||||||
|
install_fedora()
|
||||||
|
else:
|
||||||
|
if distro_id:
|
||||||
|
print("❌ Unsupported distro: ", distro_id)
|
||||||
|
elif distro_id_like:
|
||||||
|
print("❌ Unsupported distro: ", distro_id_like)
|
||||||
|
else:
|
||||||
|
print("❌ Unsupported distro. Couldn't fetch data from os-release and couldn't find dnf or apt on PATH.")
|
||||||
|
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print("❌ Unsupported platform.")
|
||||||
|
sys.exit(1)
|
@ -1,6 +1,7 @@
|
|||||||
use clap::Command;
|
use clap::Command;
|
||||||
use winapps::RemoteClient;
|
|
||||||
use winapps::freerdp::freerdp_back::Freerdp;
|
use winapps::freerdp::freerdp_back::Freerdp;
|
||||||
|
use winapps::quickemu::{create_vm, kill_vm, start_vm};
|
||||||
|
use winapps::RemoteClient;
|
||||||
|
|
||||||
fn cli() -> Command {
|
fn cli() -> Command {
|
||||||
Command::new("winapps-cli")
|
Command::new("winapps-cli")
|
||||||
@ -10,27 +11,60 @@ fn cli() -> Command {
|
|||||||
.allow_external_subcommands(true)
|
.allow_external_subcommands(true)
|
||||||
.subcommand(Command::new("check").about("Checks remote connection"))
|
.subcommand(Command::new("check").about("Checks remote connection"))
|
||||||
.subcommand(Command::new("connect").about("Connects to remote"))
|
.subcommand(Command::new("connect").about("Connects to remote"))
|
||||||
|
.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() {
|
fn main() {
|
||||||
let cli = cli();
|
let cli = cli();
|
||||||
let matches = cli.clone().get_matches();
|
let matches = cli.clone().get_matches();
|
||||||
|
|
||||||
let client: &dyn RemoteClient = &Freerdp{};
|
let config = winapps::load_config(None);
|
||||||
|
let client: &dyn RemoteClient = &Freerdp {};
|
||||||
|
|
||||||
match matches.subcommand() {
|
match matches.subcommand() {
|
||||||
Some(("check", _)) => {
|
Some(("check", _)) => {
|
||||||
println!("Checking remote connection");
|
println!("Checking remote connection");
|
||||||
|
|
||||||
let config = winapps::load_config(None);
|
|
||||||
client.check_depends(config);
|
client.check_depends(config);
|
||||||
}
|
}
|
||||||
Some(("connect", _)) => {
|
Some(("connect", _)) => {
|
||||||
println!("Connecting to remote");
|
println!("Connecting to remote");
|
||||||
|
|
||||||
let config = winapps::load_config(None);
|
|
||||||
client.run_app(config, "explorer");
|
client.run_app(config, "explorer");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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((_, _)) => {
|
||||||
|
cli.about("Command not found, try existing ones!")
|
||||||
|
.print_help()
|
||||||
|
.expect("Couldn't print help");
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Some((_, _)) => {
|
Some((_, _)) => {
|
||||||
cli.about("Command not found, try existing ones!")
|
cli.about("Command not found, try existing ones!")
|
||||||
.print_help()
|
.print_help()
|
||||||
|
@ -7,5 +7,6 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
derive-new = "0.5.9"
|
derive-new = "0.5.9"
|
||||||
|
home = "0.5.5"
|
||||||
serde = { version = "1.0.171", features = ["derive"] }
|
serde = { version = "1.0.171", features = ["derive"] }
|
||||||
toml = "0.7.6"
|
toml = "0.7.6"
|
||||||
|
@ -1,23 +1,24 @@
|
|||||||
pub mod freerdp_back {
|
pub mod freerdp_back {
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
use crate::{RemoteClient, Config};
|
use crate::{Config, RemoteClient};
|
||||||
|
|
||||||
pub struct Freerdp {}
|
pub struct Freerdp {}
|
||||||
|
|
||||||
impl RemoteClient for Freerdp {
|
impl RemoteClient for Freerdp {
|
||||||
fn check_depends(&self, _config: Config) {
|
fn check_depends(&self, _config: Config) {
|
||||||
|
|
||||||
let mut xfreerdp = Command::new("xfreerdp");
|
let mut xfreerdp = Command::new("xfreerdp");
|
||||||
xfreerdp.stdout(Stdio::null());
|
xfreerdp.stdout(Stdio::null());
|
||||||
xfreerdp.args(["-h"]);
|
xfreerdp.args(["-h"]);
|
||||||
xfreerdp.spawn().expect("Freerdp execution failed! It needs to be installed!");
|
xfreerdp
|
||||||
|
.spawn()
|
||||||
|
.expect("Freerdp execution failed! It needs to be installed!");
|
||||||
println!("Freerdp found!");
|
println!("Freerdp found!");
|
||||||
|
|
||||||
println!("Checks success!");
|
println!("Checks success!");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_app(&self, config: Config, _app: &str) {
|
fn run_app(&self, _config: Config, _app: &str) {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
|
pub mod quickemu;
|
||||||
|
|
||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
|
use home::home_dir;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
@ -21,6 +25,16 @@ pub struct Config {
|
|||||||
host: HostConfig,
|
host: HostConfig,
|
||||||
#[new(value = "RemoteConfig::new()")]
|
#[new(value = "RemoteConfig::new()")]
|
||||||
rdp: RemoteConfig,
|
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)]
|
#[derive(new, Debug, Deserialize, Serialize)]
|
||||||
@ -41,35 +55,86 @@ pub struct RemoteConfig {
|
|||||||
password: String,
|
password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_config(path: Option<&str>) -> Config {
|
pub fn get_config_file(path: Option<&str>) -> PathBuf {
|
||||||
let config = env::var("XDG_CONFIG_HOME").expect("Could not find the home path!");
|
let default = match env::var("XDG_CONFIG_HOME") {
|
||||||
let default = &format!("{}{}", config, "/winapps/");
|
Ok(dir) => PathBuf::from(dir).join("winapps"),
|
||||||
let path = Path::new(path.unwrap_or(default));
|
Err(_) => {
|
||||||
let config = Config::new();
|
println!("Couldn't read XDG_CONFIG_HOME, falling back to ~/.config");
|
||||||
|
home_dir()
|
||||||
|
.expect("Could not find the home path!")
|
||||||
|
.join(".config/winapps")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = Path::new(path.unwrap_or(default.to_str().unwrap()));
|
||||||
|
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
println!("{:?} does not exist! Creating...", path.to_str());
|
println!("{:?} does not exist! Creating...", path);
|
||||||
fs::create_dir_all(path).expect("Failed to create directory");
|
fs::create_dir_all(path).expect("Failed to create directory");
|
||||||
}
|
}
|
||||||
|
|
||||||
let config_file = path.join("config.toml");
|
if !path.is_dir() {
|
||||||
|
panic!("Config directory {:?} is not a directory!", path);
|
||||||
if !config_file.exists() {
|
|
||||||
let mut config_file =
|
|
||||||
File::create(&config_file).expect("Failed to create configuration file");
|
|
||||||
|
|
||||||
let gen_config =
|
|
||||||
toml::to_string(&config).expect("Failed to generate default configuration");
|
|
||||||
write!(config_file, "{}", gen_config).expect("Failed to write configuration file");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let config_file = fs::read_to_string(config_file).expect("Failed to read configuration file");
|
path.join("config.toml")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_config(path: Option<&str>) -> Config {
|
||||||
|
let config = Config::new();
|
||||||
|
let config_path = get_config_file(path);
|
||||||
|
|
||||||
|
if !config_path.exists() {
|
||||||
|
save_config(&config, path).expect("Failed to write default configuration");
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
let config_file = fs::read_to_string(config_path).expect("Failed to read configuration file");
|
||||||
let config: Config =
|
let config: Config =
|
||||||
toml::from_str(config_file.as_str()).expect("Failed to parse the configuration");
|
toml::from_str(config_file.as_str()).expect("Failed to parse the configuration");
|
||||||
|
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn save_config(config: &Config, path: Option<&str>) -> std::io::Result<()> {
|
||||||
|
let config_path = get_config_file(path);
|
||||||
|
let serialized_config = toml::to_string(&config).expect("Failed to serialize configuration");
|
||||||
|
|
||||||
|
let mut config_file = match config_path.exists() {
|
||||||
|
true => File::open(&config_path).expect("Failed to open configuration file"),
|
||||||
|
false => File::create(&config_path).expect("Failed to create configuration file"),
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(config_file, "{}", serialized_config)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_data_dir() -> PathBuf {
|
||||||
|
let data_dir = match env::var("XDG_DATA_HOME") {
|
||||||
|
Ok(dir) => PathBuf::from(dir).join("winapps"),
|
||||||
|
Err(_) => {
|
||||||
|
println!("Couldn't read XDG_DATA_HOME, falling back to ~/.local/share");
|
||||||
|
home_dir()
|
||||||
|
.expect("Could not find the home path!")
|
||||||
|
.join(".local/share/winapps")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !data_dir.exists() {
|
||||||
|
let dir = data_dir.clone();
|
||||||
|
println!(
|
||||||
|
"Data directory {:?} does not exist! Creating...",
|
||||||
|
dir.to_str()
|
||||||
|
);
|
||||||
|
fs::create_dir_all(dir).expect("Failed to create directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data_dir.is_dir() {
|
||||||
|
panic!("Data directory {:?} is not a directory!", data_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
data_dir
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add(left: usize, right: usize) -> usize {
|
pub fn add(left: usize, right: usize) -> usize {
|
||||||
left + right
|
left + right
|
||||||
}
|
}
|
||||||
|
89
winapps/src/quickemu.rs
Normal file
89
winapps/src/quickemu.rs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
use crate::{get_data_dir, save_config, Config};
|
||||||
|
use std::fs;
|
||||||
|
use std::process::exit;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
pub fn create_vm(mut config: Config) {
|
||||||
|
let data_dir = get_data_dir();
|
||||||
|
|
||||||
|
let output = match Command::new("quickget")
|
||||||
|
.current_dir(data_dir)
|
||||||
|
.arg("windows")
|
||||||
|
.arg("10")
|
||||||
|
.output()
|
||||||
|
{
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to execute quickget: {}", e);
|
||||||
|
println!("Please make sure quickget is installed and in your PATH");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
config.vm.name = "windows-10-22H2".to_string();
|
||||||
|
config.vm.short_name = "windows-10".to_string();
|
||||||
|
|
||||||
|
save_config(&config, None).expect("Failed to save config, VM will not start properly");
|
||||||
|
|
||||||
|
println!("{}", String::from_utf8_lossy(&output.stdout));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_vm(config: Config) {
|
||||||
|
let data_dir = get_data_dir();
|
||||||
|
|
||||||
|
let command = match Command::new("quickemu")
|
||||||
|
.current_dir(data_dir)
|
||||||
|
.args([
|
||||||
|
"--ignore-msrs-always",
|
||||||
|
"--vm",
|
||||||
|
&format!("{}.conf", config.vm.name),
|
||||||
|
"--display",
|
||||||
|
"none",
|
||||||
|
])
|
||||||
|
.spawn()
|
||||||
|
{
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to execute quickemu: {}", e);
|
||||||
|
println!("Please make sure quickemu is installed and in your PATH");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let output = match command.wait_with_output() {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to gather output from quickemu: {}", e);
|
||||||
|
println!("Please make sure quickemu is installed and in your PATH");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("{}", String::from_utf8_lossy(&output.stdout));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kill_vm(config: Config) {
|
||||||
|
let data_dir = get_data_dir();
|
||||||
|
|
||||||
|
match fs::read_to_string(
|
||||||
|
data_dir.join(format!("{}/{}.pid", config.vm.short_name, config.vm.name)),
|
||||||
|
) {
|
||||||
|
Ok(pid) => {
|
||||||
|
let pid = pid.trim();
|
||||||
|
|
||||||
|
println!("Killing VM with PID {}", pid);
|
||||||
|
|
||||||
|
match Command::new("kill").arg(pid).spawn() {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to kill VM: {}", e);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to read PID file: {}", e);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user