From c755f752a0a621c742011af183627c11eeca6ce7 Mon Sep 17 00:00:00 2001 From: David Zhong <91637806+davnotdev@users.noreply.github.com> Date: Sat, 9 Mar 2024 17:52:59 -0800 Subject: [PATCH] Implement launching a local daemon (#261) Allow AppImage and non-systemd systems to launch a local burrow daemon. --- README.md | 3 +- burrow-gtk/build-aux/build_appimage.sh | 6 +- burrow-gtk/src/components/app.rs | 21 +++- burrow-gtk/src/components/mod.rs | 1 + .../src/components/settings/daemon_group.rs | 111 ++++++++++++++++++ .../src/components/settings/diag_group.rs | 16 +-- burrow-gtk/src/components/settings/mod.rs | 5 +- burrow-gtk/src/components/settings_screen.rs | 41 +++++-- burrow-gtk/src/diag.rs | 15 ++- 9 files changed, 192 insertions(+), 27 deletions(-) create mode 100644 burrow-gtk/src/components/settings/daemon_group.rs diff --git a/README.md b/README.md index 7492039..89914d0 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,14 @@ Burrow is an open source tool for burrowing through firewalls, built by teenager ## Contributing -Burrow is fully open source, you can fork the repo and start contributing easily. For more information and in-depth discussions, visit the `#burrow` channel on the [Hack Club Slack](https://hackclub.com/slack/), here you can ask for help and talk with other people interested in burrow! For more information on how to contribute, please see [CONTRIBUTING.md] +Burrow is fully open source, you can fork the repo and start contributing easily. For more information and in-depth discussions, visit the `#burrow` channel on the [Hack Club Slack](https://hackclub.com/slack/), here you can ask for help and talk with other people interested in burrow! Checkout [GETTING_STARTED.md](./docs/GETTING_STARTED.md) for build instructions and [GTK_APP.md](./docs/GTK_APP.md) for the Linux app. The project structure is divided in the following folders: ``` Apple/ # Xcode project for burrow on macOS and iOS burrow/ # Higher-level API library for tun and tun-async +burrow-gtk/ # GTK project for burrow on Linux tun/ # Low-level interface to OS networking src/ tokio/ # Async/Tokio code diff --git a/burrow-gtk/build-aux/build_appimage.sh b/burrow-gtk/build-aux/build_appimage.sh index 248cca7..cd58c17 100755 --- a/burrow-gtk/build-aux/build_appimage.sh +++ b/burrow-gtk/build-aux/build_appimage.sh @@ -5,6 +5,7 @@ set -ex BURROW_GTK_ROOT="$(readlink -f $(dirname -- "$(readlink -f -- "$BASH_SOURCE")")/..)" BURROW_GTK_BUILD="$BURROW_GTK_ROOT/build-appimage" LINUXDEPLOY_VERSION="${LINUXDEPLOY_VERSION:-"1-alpha-20240109-1"}" +BURROW_BUILD_TYPE="${BURROW_BUILD_TYPE:-"release"}" if [ "$BURROW_GTK_ROOT" != $(pwd) ]; then echo "Make sure to cd into burrow-gtk" @@ -21,8 +22,9 @@ elif [ "$ARCHITECTURE" == "aarch64" ]; then chmod a+x /tmp/linuxdeploy fi -meson setup $BURROW_GTK_BUILD --bindir bin --prefix /usr +meson setup $BURROW_GTK_BUILD --bindir bin --prefix /usr --buildtype $BURROW_BUILD_TYPE meson compile -C $BURROW_GTK_BUILD DESTDIR=AppDir meson install -C $BURROW_GTK_BUILD -/tmp/linuxdeploy --appimage-extract-and-run --appdir $BURROW_GTK_BUILD/AppDir --output appimage +cargo b --$BURROW_BUILD_TYPE --manifest-path=../Cargo.toml +/tmp/linuxdeploy --appimage-extract-and-run --appdir $BURROW_GTK_BUILD/AppDir -e $BURROW_GTK_BUILD/../../target/$BURROW_BUILD_TYPE/burrow --output appimage mv *.AppImage $BURROW_GTK_BUILD diff --git a/burrow-gtk/src/components/app.rs b/burrow-gtk/src/components/app.rs index 57348ef..62c98c0 100644 --- a/burrow-gtk/src/components/app.rs +++ b/burrow-gtk/src/components/app.rs @@ -6,7 +6,7 @@ const RECONNECT_POLL_TIME: Duration = Duration::from_secs(5); pub struct App { daemon_client: Arc>>, - _settings_screen: Controller, + settings_screen: Controller, switch_screen: AsyncController, } @@ -109,7 +109,7 @@ impl AsyncComponent for App { let model = App { daemon_client, switch_screen, - _settings_screen: settings_screen, + settings_screen, }; AsyncComponentParts { model, widgets } @@ -132,14 +132,23 @@ impl AsyncComponent for App { disconnected_daemon_client = true; self.switch_screen .emit(switch_screen::SwitchScreenMsg::DaemonDisconnect); + self.settings_screen + .emit(settings_screen::SettingsScreenMsg::DaemonStateChange) } } if disconnected_daemon_client || daemon_client.is_none() { - *daemon_client = DaemonClient::new().await.ok(); - if daemon_client.is_some() { - self.switch_screen - .emit(switch_screen::SwitchScreenMsg::DaemonReconnect); + match DaemonClient::new().await { + Ok(new_daemon_client) => { + *daemon_client = Some(new_daemon_client); + self.switch_screen + .emit(switch_screen::SwitchScreenMsg::DaemonReconnect); + self.settings_screen + .emit(settings_screen::SettingsScreenMsg::DaemonStateChange) + } + Err(_e) => { + // TODO: Handle Error + } } } } diff --git a/burrow-gtk/src/components/mod.rs b/burrow-gtk/src/components/mod.rs index b1cc938..b134809 100644 --- a/burrow-gtk/src/components/mod.rs +++ b/burrow-gtk/src/components/mod.rs @@ -18,3 +18,4 @@ mod settings_screen; mod switch_screen; pub use app::*; +pub use settings::{DaemonGroupMsg, DiagGroupMsg}; diff --git a/burrow-gtk/src/components/settings/daemon_group.rs b/burrow-gtk/src/components/settings/daemon_group.rs new file mode 100644 index 0000000..3817ca6 --- /dev/null +++ b/burrow-gtk/src/components/settings/daemon_group.rs @@ -0,0 +1,111 @@ +use super::*; +use std::process::Command; + +#[derive(Debug)] +pub struct DaemonGroup { + system_setup: SystemSetup, + daemon_client: Arc>>, + already_running: bool, +} + +pub struct DaemonGroupInit { + pub daemon_client: Arc>>, + pub system_setup: SystemSetup, +} + +#[derive(Debug)] +pub enum DaemonGroupMsg { + LaunchLocal, + DaemonStateChange, +} + +#[relm4::component(pub, async)] +impl AsyncComponent for DaemonGroup { + type Init = DaemonGroupInit; + type Input = DaemonGroupMsg; + type Output = (); + type CommandOutput = (); + + view! { + #[name(group)] + adw::PreferencesGroup { + #[watch] + set_sensitive: + (model.system_setup == SystemSetup::AppImage || model.system_setup == SystemSetup::Other) && + !model.already_running, + set_title: "Local Daemon", + set_description: Some("Run Local Daemon"), + + gtk::Button { + set_label: "Launch", + connect_clicked => DaemonGroupMsg::LaunchLocal + } + } + } + + async fn init( + init: Self::Init, + root: Self::Root, + sender: AsyncComponentSender, + ) -> AsyncComponentParts { + // Should be impossible to panic here + let model = DaemonGroup { + system_setup: init.system_setup, + daemon_client: init.daemon_client.clone(), + already_running: init.daemon_client.lock().await.is_some(), + }; + + let widgets = view_output!(); + + AsyncComponentParts { model, widgets } + } + + async fn update( + &mut self, + msg: Self::Input, + _sender: AsyncComponentSender, + _root: &Self::Root, + ) { + match msg { + DaemonGroupMsg::LaunchLocal => { + let burrow_original_bin = std::env::vars() + .find(|(k, _)| k == "APPDIR") + .map(|(_, v)| v + "/usr/bin/burrow") + .unwrap_or("/usr/bin/burrow".to_owned()); + + let mut burrow_bin = + String::from_utf8(Command::new("mktemp").output().unwrap().stdout).unwrap(); + burrow_bin.pop(); + + let privileged_spawn_script = format!( + r#"TEMP=$(mktemp -p /root) +cp {} $TEMP +chmod +x $TEMP +setcap CAP_NET_BIND_SERVICE,CAP_NET_ADMIN+eip $TEMP +mv $TEMP /tmp/burrow-detached-daemon"#, + burrow_original_bin + ) + .replace('\n', "&&"); + + // TODO: Handle error condition + + Command::new("pkexec") + .arg("sh") + .arg("-c") + .arg(privileged_spawn_script) + .arg(&burrow_bin) + .output() + .unwrap(); + + Command::new("/tmp/burrow-detached-daemon") + .env("RUST_LOG", "debug") + .arg("daemon") + .spawn() + .unwrap(); + } + DaemonGroupMsg::DaemonStateChange => { + self.already_running = self.daemon_client.lock().await.is_some(); + } + } + } +} diff --git a/burrow-gtk/src/components/settings/diag_group.rs b/burrow-gtk/src/components/settings/diag_group.rs index be542cd..a15e0ea 100644 --- a/burrow-gtk/src/components/settings/diag_group.rs +++ b/burrow-gtk/src/components/settings/diag_group.rs @@ -1,11 +1,10 @@ use super::*; -use diag::{StatusTernary, SystemSetup}; #[derive(Debug)] pub struct DiagGroup { daemon_client: Arc>>, - init_system: SystemSetup, + system_setup: SystemSetup, service_installed: StatusTernary, socket_installed: StatusTernary, socket_enabled: StatusTernary, @@ -14,19 +13,20 @@ pub struct DiagGroup { pub struct DiagGroupInit { pub daemon_client: Arc>>, + pub system_setup: SystemSetup, } impl DiagGroup { async fn new(daemon_client: Arc>>) -> Result { - let setup = SystemSetup::new(); + let system_setup = SystemSetup::new(); let daemon_running = daemon_client.lock().await.is_some(); Ok(Self { - service_installed: setup.is_service_installed()?, - socket_installed: setup.is_socket_installed()?, - socket_enabled: setup.is_socket_enabled()?, + service_installed: system_setup.is_service_installed()?, + socket_installed: system_setup.is_socket_installed()?, + socket_enabled: system_setup.is_socket_enabled()?, daemon_running, - init_system: setup, + system_setup, daemon_client, }) } @@ -52,7 +52,7 @@ impl AsyncComponent for DiagGroup { adw::ActionRow { #[watch] - set_title: &format!("Init System: {}", model.init_system) + set_title: &format!("System Type: {}", model.system_setup) }, adw::ActionRow { #[watch] diff --git a/burrow-gtk/src/components/settings/mod.rs b/burrow-gtk/src/components/settings/mod.rs index 53f46d4..aa87db2 100644 --- a/burrow-gtk/src/components/settings/mod.rs +++ b/burrow-gtk/src/components/settings/mod.rs @@ -1,5 +1,8 @@ use super::*; +use diag::{StatusTernary, SystemSetup}; +mod daemon_group; mod diag_group; -pub use diag_group::{DiagGroup, DiagGroupInit}; +pub use daemon_group::{DaemonGroup, DaemonGroupInit, DaemonGroupMsg}; +pub use diag_group::{DiagGroup, DiagGroupInit, DiagGroupMsg}; diff --git a/burrow-gtk/src/components/settings_screen.rs b/burrow-gtk/src/components/settings_screen.rs index 0a29e43..971f262 100644 --- a/burrow-gtk/src/components/settings_screen.rs +++ b/burrow-gtk/src/components/settings_screen.rs @@ -1,17 +1,24 @@ use super::*; +use diag::SystemSetup; pub struct SettingsScreen { - _diag_group: AsyncController, + diag_group: AsyncController, + daemon_group: AsyncController, } pub struct SettingsScreenInit { pub daemon_client: Arc>>, } +#[derive(Debug, PartialEq, Eq)] +pub enum SettingsScreenMsg { + DaemonStateChange, +} + #[relm4::component(pub)] impl SimpleComponent for SettingsScreen { type Init = SettingsScreenInit; - type Input = (); + type Input = SettingsScreenMsg; type Output = (); view! { @@ -24,21 +31,41 @@ impl SimpleComponent for SettingsScreen { root: &Self::Root, sender: ComponentSender, ) -> ComponentParts { + let system_setup = SystemSetup::new(); + let diag_group = settings::DiagGroup::builder() .launch(settings::DiagGroupInit { + system_setup, daemon_client: Arc::clone(&init.daemon_client), }) - .forward(sender.input_sender(), |_| ()); + .forward(sender.input_sender(), |_| { + SettingsScreenMsg::DaemonStateChange + }); + + let daemon_group = settings::DaemonGroup::builder() + .launch(settings::DaemonGroupInit { + system_setup, + daemon_client: Arc::clone(&init.daemon_client), + }) + .forward(sender.input_sender(), |_| { + SettingsScreenMsg::DaemonStateChange + }); let widgets = view_output!(); widgets.preferences.add(diag_group.widget()); + widgets.preferences.add(daemon_group.widget()); - let model = SettingsScreen { - _diag_group: diag_group, - }; + let model = SettingsScreen { diag_group, daemon_group }; ComponentParts { model, widgets } } - fn update(&mut self, _: Self::Input, _sender: ComponentSender) {} + fn update(&mut self, _: Self::Input, _sender: ComponentSender) { + // Currently, `SettingsScreenMsg` only has one variant, so the if is ambiguous. + // + // if let SettingsScreenMsg::DaemonStateChange = msg { + self.diag_group.emit(DiagGroupMsg::Refresh); + self.daemon_group.emit(DaemonGroupMsg::DaemonStateChange); + // } + } } diff --git a/burrow-gtk/src/diag.rs b/burrow-gtk/src/diag.rs index 348293e..ab4757e 100644 --- a/burrow-gtk/src/diag.rs +++ b/burrow-gtk/src/diag.rs @@ -15,15 +15,18 @@ pub enum StatusTernary { // Realistically, we may not explicitly "support" non-systemd platforms which would simply this // code greatly. // Along with replacing [`StatusTernary`] with good old [`bool`]. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SystemSetup { Systemd, + AppImage, Other, } impl SystemSetup { pub fn new() -> Self { - if Command::new("systemctl").arg("--version").output().is_ok() { + if is_appimage() { + SystemSetup::AppImage + } else if Command::new("systemctl").arg("--version").output().is_ok() { SystemSetup::Systemd } else { SystemSetup::Other @@ -33,6 +36,7 @@ impl SystemSetup { pub fn is_service_installed(&self) -> Result { match self { SystemSetup::Systemd => Ok(fs::metadata(SYSTEMD_SERVICE_LOC).is_ok().into()), + SystemSetup::AppImage => Ok(StatusTernary::NA), SystemSetup::Other => Ok(StatusTernary::NA), } } @@ -40,6 +44,7 @@ impl SystemSetup { pub fn is_socket_installed(&self) -> Result { match self { SystemSetup::Systemd => Ok(fs::metadata(SYSTEMD_SOCKET_LOC).is_ok().into()), + SystemSetup::AppImage => Ok(StatusTernary::NA), SystemSetup::Other => Ok(StatusTernary::NA), } } @@ -55,6 +60,7 @@ impl SystemSetup { let output = String::from_utf8(output)?; Ok((output == "enabled\n").into()) } + SystemSetup::AppImage => Ok(StatusTernary::NA), SystemSetup::Other => Ok(StatusTernary::NA), } } @@ -74,7 +80,12 @@ impl Display for SystemSetup { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { SystemSetup::Systemd => "Systemd", + SystemSetup::AppImage => "AppImage", SystemSetup::Other => "Other", }) } } + +pub fn is_appimage() -> bool { + std::env::vars().any(|(k, _)| k == "APPDIR") +}