feat: add setcap step to Setup tab for port 443 binding
Adds Step 2 in the Setup tab: grants cap_net_bind_service to the bridge binary via pkexec/sudo so it can bind port 443 directly. On success, automatically updates bridge_listen_addr to 0.0.0.0:443 — no iptables rules required. Step re-detects cap state on each frame so it stays accurate after a rebuild clears the capability. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+65
-1
@@ -34,6 +34,7 @@ pub struct LauncherApp {
|
||||
|
||||
// Setup state
|
||||
hook_deployed: bool,
|
||||
bridge_has_cap: bool,
|
||||
cert_path: Option<std::path::PathBuf>,
|
||||
setup_message: Option<(bool, String)>, // (success, text)
|
||||
}
|
||||
@@ -56,6 +57,8 @@ impl LauncherApp {
|
||||
let cert_path = setup::find_bridge_cert(&config.bridge_captures_dir);
|
||||
let hook_deployed =
|
||||
setup::hook_dll_deployed(std::path::Path::new(&config.fifa_game_dir));
|
||||
let bridge_has_cap =
|
||||
setup::bridge_has_cap443(std::path::Path::new(&config.bridge_binary));
|
||||
|
||||
Self {
|
||||
config,
|
||||
@@ -68,6 +71,7 @@ impl LauncherApp {
|
||||
log_tab: 0,
|
||||
log_follow: true,
|
||||
hook_deployed,
|
||||
bridge_has_cap,
|
||||
cert_path,
|
||||
setup_message: None,
|
||||
}
|
||||
@@ -379,10 +383,70 @@ impl LauncherApp {
|
||||
|
||||
ui.add_space(12.0);
|
||||
|
||||
// ── Port 443 capability ───────────────────────────────────────────────
|
||||
ui.group(|ui| {
|
||||
ui.set_min_width(ui.available_width());
|
||||
ui.strong("Step 2 — Grant port 443 capability");
|
||||
ui.add_space(4.0);
|
||||
ui.label("Allows the bridge to bind port 443 directly — the same port FIFA 23 \
|
||||
uses for HTTPS — without running as root. No iptables rules needed.");
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Clone to avoid holding a borrow on config while we mutate it below.
|
||||
let binary_path = std::path::PathBuf::from(&self.config.bridge_binary);
|
||||
self.bridge_has_cap = setup::bridge_has_cap443(&binary_path);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
if self.bridge_has_cap {
|
||||
ui.colored_label(Color32::from_rgb(80, 200, 120), "✔ cap_net_bind_service granted");
|
||||
} else {
|
||||
ui.colored_label(Color32::from_rgb(220, 60, 60), "✘ Not set");
|
||||
ui.add_space(8.0);
|
||||
let binary_exists = binary_path.exists();
|
||||
if ui
|
||||
.add_enabled(binary_exists, egui::Button::new("Grant (requires sudo)"))
|
||||
.clicked()
|
||||
{
|
||||
match setup::setcap_bridge_443(&binary_path) {
|
||||
Ok(()) => {
|
||||
self.bridge_has_cap = true;
|
||||
// Switch listen addr to 443 automatically
|
||||
self.config.bridge_listen_addr = "0.0.0.0:443".into();
|
||||
self.config.save();
|
||||
self.setup_message = Some((
|
||||
true,
|
||||
"cap_net_bind_service granted. Bridge listen addr set to 0.0.0.0:443.".into(),
|
||||
));
|
||||
}
|
||||
Err(e) => self.setup_message = Some((false, format!("setcap failed: {e}"))),
|
||||
}
|
||||
}
|
||||
if !binary_exists {
|
||||
ui.add_space(4.0);
|
||||
ui.colored_label(
|
||||
Color32::from_rgb(220, 150, 0),
|
||||
"⚠ Bridge binary not found — build it first.",
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(4.0);
|
||||
ui.label(
|
||||
egui::RichText::new(
|
||||
"Re-run this step any time the bridge binary is rebuilt (setcap is cleared on recompile).",
|
||||
)
|
||||
.weak()
|
||||
.small(),
|
||||
);
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
|
||||
// ── Cert install ──────────────────────────────────────────────────────
|
||||
ui.group(|ui| {
|
||||
ui.set_min_width(ui.available_width());
|
||||
ui.strong("Step 2 — Install TLS certificate");
|
||||
ui.strong("Step 3 — Install TLS certificate");
|
||||
ui.add_space(4.0);
|
||||
ui.label("Installs the bridge's self-signed cert into the Wine/Proton cert store \
|
||||
so the game accepts HTTPS connections to the bridge.");
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
use std::{path::{Path, PathBuf}, process::Command};
|
||||
|
||||
// ── Port 443 capability ───────────────────────────────────────────────────────
|
||||
|
||||
/// Check whether the bridge binary already has cap_net_bind_service set.
|
||||
pub fn bridge_has_cap443(binary: &Path) -> bool {
|
||||
std::process::Command::new("getcap")
|
||||
.arg(binary)
|
||||
.output()
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).contains("cap_net_bind_service"))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Grant cap_net_bind_service to the bridge binary so it can bind port 443
|
||||
/// without running as root. Uses pkexec (or sudo as fallback).
|
||||
pub fn setcap_bridge_443(binary: &Path) -> anyhow::Result<()> {
|
||||
let script = format!(
|
||||
"setcap cap_net_bind_service=+ep '{}'",
|
||||
binary.to_string_lossy()
|
||||
);
|
||||
run_elevated(&script)
|
||||
}
|
||||
|
||||
// ── Cert installation ─────────────────────────────────────────────────────────
|
||||
|
||||
/// Find the bridge cert in the given captures dir.
|
||||
|
||||
Reference in New Issue
Block a user