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:
funman300
2026-06-26 09:20:04 -07:00
parent 099241d9ca
commit 3b965f1f3f
2 changed files with 86 additions and 1 deletions
+65 -1
View File
@@ -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.");
+21
View File
@@ -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.