feat: initial OpenFUT Launcher

GUI desktop app (egui/eframe) that manages the openfut-core and
openfut-bridge services with a single window.

- Dashboard: start/stop each service independently or together; live
  status indicators; quick-status for cert and hosts
- Logs: real-time stdout/stderr from both processes, colour-coded by
  level, follow mode and manual clear
- Setup: add/remove EA hostname redirects in /etc/hosts (pkexec/sudo);
  install the bridge TLS cert into the system CA store
- Config: all binary paths and env vars editable in-app; persisted to
  ~/.config/openfut-launcher/config.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-06-25 20:02:02 -07:00
commit fc894a7f77
9 changed files with 5306 additions and 0 deletions
+129
View File
@@ -0,0 +1,129 @@
use std::process::Command;
/// EA FUT hostnames the bridge needs to intercept.
const INTERCEPT_HOSTS: &[&str] = &[
"fut.ea.com",
"utas.mob.v4.fut.ea.com",
"utas.s2.fut.ea.com",
];
const HOSTS_MARKER_START: &str = "# BEGIN openfut-launcher";
const HOSTS_MARKER_END: &str = "# END openfut-launcher";
// ── Cert installation ─────────────────────────────────────────────────────────
/// Find the bridge cert in the given captures dir.
pub fn find_bridge_cert(captures_dir: &str) -> Option<std::path::PathBuf> {
let p = std::path::Path::new(captures_dir).join("bridge_cert.pem");
p.exists().then_some(p)
}
/// Install the bridge cert into the system CA store.
/// On Linux, copies to /usr/local/share/ca-certificates/ and runs
/// update-ca-certificates via pkexec (polkit graphical sudo).
pub fn install_cert(cert_src: &std::path::Path) -> anyhow::Result<()> {
let dest = std::path::Path::new("/usr/local/share/ca-certificates/openfut-bridge.crt");
// Write a small shell script that pkexec can run as root
let script = format!(
"cp '{}' '{}' && update-ca-certificates",
cert_src.display(),
dest.display()
);
let status = Command::new("pkexec")
.args(["sh", "-c", &script])
.status();
match status {
Ok(s) if s.success() => Ok(()),
Ok(s) => anyhow::bail!("pkexec exited with status {s}"),
Err(e) => {
// pkexec not available — try sudo
let status = Command::new("sudo")
.args(["sh", "-c", &script])
.status()?;
if status.success() {
Ok(())
} else {
anyhow::bail!("cert install failed: {e}")
}
}
}
}
// ── Hosts file management ─────────────────────────────────────────────────────
pub fn hosts_configured() -> bool {
let content = std::fs::read_to_string("/etc/hosts").unwrap_or_default();
content.contains(HOSTS_MARKER_START)
}
/// Add OpenFUT intercept entries to /etc/hosts (requires root).
pub fn add_hosts_entries() -> anyhow::Result<()> {
let current = std::fs::read_to_string("/etc/hosts")?;
if current.contains(HOSTS_MARKER_START) {
return Ok(());
}
let entries: String = INTERCEPT_HOSTS
.iter()
.map(|h| format!("127.0.0.1 {h}\n"))
.collect();
let addition = format!("\n{HOSTS_MARKER_START}\n{entries}{HOSTS_MARKER_END}\n");
let new_content = format!("{current}{addition}");
write_hosts(&new_content)
}
/// Remove OpenFUT intercept entries from /etc/hosts (requires root).
pub fn remove_hosts_entries() -> anyhow::Result<()> {
let current = std::fs::read_to_string("/etc/hosts")?;
if !current.contains(HOSTS_MARKER_START) {
return Ok(());
}
let mut out = String::new();
let mut skip = false;
for line in current.lines() {
if line.trim() == HOSTS_MARKER_START {
skip = true;
continue;
}
if line.trim() == HOSTS_MARKER_END {
skip = false;
continue;
}
if !skip {
out.push_str(line);
out.push('\n');
}
}
write_hosts(out.trim_end())
}
fn write_hosts(content: &str) -> anyhow::Result<()> {
// Write to a temp file, then mv into place with elevated privileges
let tmp = "/tmp/openfut_hosts_tmp";
std::fs::write(tmp, content)?;
let script = format!("mv '{tmp}' /etc/hosts && chmod 644 /etc/hosts");
let status = Command::new("pkexec").args(["sh", "-c", &script]).status();
match status {
Ok(s) if s.success() => Ok(()),
_ => {
let s = Command::new("sudo")
.args(["sh", "-c", &script])
.status()?;
if s.success() {
Ok(())
} else {
anyhow::bail!("failed to write /etc/hosts")
}
}
}
}