From 3ccbc626ff2d5a3209d566f1ada0db2c8a2249ff Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Fri, 9 Jun 2023 15:47:11 -0600 Subject: [PATCH] experimental features for zram and reset tor (#2299) * experimental features for zram and reset tor * zram backend --------- Co-authored-by: Aiden McClelland --- backend/src/db/model.rs | 3 + backend/src/lib.rs | 1 + backend/src/system.rs | 55 +++++- .../app/app/preloader/preloader.component.ts | 1 + .../experimental-features.module.ts | 24 +++ .../experimental-features.page.html | 36 ++++ .../experimental-features.page.scss | 0 .../experimental-features.page.ts | 156 ++++++++++++++++++ .../server-routes/server-routing.module.ts | 7 + .../server-show/server-show.page.ts | 11 ++ .../ui/src/app/services/api/api.types.ts | 5 + .../app/services/api/embassy-api.service.ts | 2 + .../services/api/embassy-live-api.service.ts | 4 + .../services/api/embassy-mock-api.service.ts | 12 ++ .../ui/src/app/services/api/mock-patch.ts | 1 + .../src/app/services/patch-db/data-model.ts | 1 + libs/models/src/errors.rs | 2 + 17 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 frontend/projects/ui/src/app/pages/server-routes/experimental-features/experimental-features.module.ts create mode 100644 frontend/projects/ui/src/app/pages/server-routes/experimental-features/experimental-features.page.html create mode 100644 frontend/projects/ui/src/app/pages/server-routes/experimental-features/experimental-features.page.scss create mode 100644 frontend/projects/ui/src/app/pages/server-routes/experimental-features/experimental-features.page.ts diff --git a/backend/src/db/model.rs b/backend/src/db/model.rs index ae017c699..7a6fe1f7f 100644 --- a/backend/src/db/model.rs +++ b/backend/src/db/model.rs @@ -80,6 +80,7 @@ impl Database { .map(|x| format!("{x:X}")) .join(":"), system_start_time: Utc::now().to_rfc3339(), + zram: false, }, package_data: AllPackageData::default(), ui: serde_json::from_str(include_str!("../../../frontend/patchdb-ui-seed.json")) @@ -117,6 +118,8 @@ pub struct ServerInfo { pub pubkey: String, pub ca_fingerprint: String, pub system_start_time: String, + #[serde(default)] + pub zram: bool, } #[derive(Debug, Deserialize, Serialize, HasModel)] diff --git a/backend/src/lib.rs b/backend/src/lib.rs index ae445b538..a5b64164e 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -85,6 +85,7 @@ pub fn main_api() -> Result<(), RpcError> { #[command(subcommands( system::time, + system::experimental, system::logs, system::kernel_logs, system::metrics, diff --git a/backend/src/system.rs b/backend/src/system.rs index 17819689c..fee405e22 100644 --- a/backend/src/system.rs +++ b/backend/src/system.rs @@ -24,6 +24,59 @@ use crate::{Error, ErrorKind, ResultExt}; pub const SYSTEMD_UNIT: &'static str = "embassyd"; +#[command(subcommands(zram))] +pub async fn experimental() -> Result<(), Error> { + Ok(()) +} + +#[command(display(display_none))] +pub async fn zram(#[context] ctx: RpcContext, #[arg] enable: bool) -> Result<(), Error> { + let mut db = ctx.db.handle(); + let zram = crate::db::DatabaseModel::new() + .server_info() + .zram() + .get_mut(&mut db) + .await?; + if enable == *zram { + return Ok(()); + } + if enable { + let mem_info = get_mem_info().await?; + Command::new("modprobe") + .arg("zram") + .invoke(ErrorKind::Zram) + .await?; + tokio::fs::write("/sys/block/zram0/comp_algorithm", "lz4") + .await + .with_kind(ErrorKind::Zram)?; + tokio::fs::write( + "/sys/block/zram0/disksize", + format!("{}M", mem_info.total.0 as u64 / 4), + ) + .await + .with_kind(ErrorKind::Zram)?; + Command::new("mkswap") + .arg("/dev/zram0") + .invoke(ErrorKind::Zram) + .await?; + Command::new("swapon") + .arg("-p") + .arg("5") + .arg("/dev/zram0") + .invoke(ErrorKind::Zram) + .await?; + } else { + Command::new("swapoff") + .arg("/dev/zram0") + .invoke(ErrorKind::Zram) + .await?; + tokio::fs::write("/sys/block/zram0/reset", "1") + .await + .with_kind(ErrorKind::Zram)?; + } + Ok(()) +} + #[command] pub async fn time() -> Result { Ok(Utc::now().to_rfc3339()) @@ -699,7 +752,7 @@ async fn get_mem_info() -> Result { let swap_total = MebiBytes(swap_total_k as f64 / 1024.0); let swap_free = MebiBytes(swap_free_k as f64 / 1024.0); let swap_used = MebiBytes((swap_total_k - swap_free_k) as f64 / 1024.0); - let percentage_used = Percentage(used.0 / total.0 * 100.0); + let percentage_used = Percentage((total.0 - available.0) / total.0 * 100.0); Ok(MetricsMemory { percentage_used, total, diff --git a/frontend/projects/ui/src/app/app/preloader/preloader.component.ts b/frontend/projects/ui/src/app/app/preloader/preloader.component.ts index 53cd4db10..27d1750ec 100644 --- a/frontend/projects/ui/src/app/app/preloader/preloader.component.ts +++ b/frontend/projects/ui/src/app/app/preloader/preloader.component.ts @@ -40,6 +40,7 @@ const ICONS = [ 'file-tray-stacked-outline', 'finger-print-outline', 'flash-outline', + 'flash-off-outline', 'folder-open-outline', 'globe-outline', 'grid-outline', diff --git a/frontend/projects/ui/src/app/pages/server-routes/experimental-features/experimental-features.module.ts b/frontend/projects/ui/src/app/pages/server-routes/experimental-features/experimental-features.module.ts new file mode 100644 index 000000000..86e374b17 --- /dev/null +++ b/frontend/projects/ui/src/app/pages/server-routes/experimental-features/experimental-features.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core' +import { CommonModule } from '@angular/common' +import { Routes, RouterModule } from '@angular/router' +import { IonicModule } from '@ionic/angular' +import { ExperimentalFeaturesPage } from './experimental-features.page' +import { EmverPipesModule } from '@start9labs/shared' + +const routes: Routes = [ + { + path: '', + component: ExperimentalFeaturesPage, + }, +] + +@NgModule({ + imports: [ + CommonModule, + IonicModule, + RouterModule.forChild(routes), + EmverPipesModule, + ], + declarations: [ExperimentalFeaturesPage], +}) +export class ExperimentalFeaturesPageModule {} diff --git a/frontend/projects/ui/src/app/pages/server-routes/experimental-features/experimental-features.page.html b/frontend/projects/ui/src/app/pages/server-routes/experimental-features/experimental-features.page.html new file mode 100644 index 000000000..337941fe2 --- /dev/null +++ b/frontend/projects/ui/src/app/pages/server-routes/experimental-features/experimental-features.page.html @@ -0,0 +1,36 @@ + + + + + + Experimental Features + + + + + + + + +

Reset Tor

+

+ Reset the Tor deamon on your server may resolve Tor connectivity + issues. +

+
+
+ + + +

{{ server.zram ? 'Disable' : 'Enable' }} zram

+

+ Enabling zram may improve server performance, especially on low RAM + devices +

+
+
+
+
diff --git a/frontend/projects/ui/src/app/pages/server-routes/experimental-features/experimental-features.page.scss b/frontend/projects/ui/src/app/pages/server-routes/experimental-features/experimental-features.page.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/projects/ui/src/app/pages/server-routes/experimental-features/experimental-features.page.ts b/frontend/projects/ui/src/app/pages/server-routes/experimental-features/experimental-features.page.ts new file mode 100644 index 000000000..ae0025204 --- /dev/null +++ b/frontend/projects/ui/src/app/pages/server-routes/experimental-features/experimental-features.page.ts @@ -0,0 +1,156 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core' +import { + AlertController, + LoadingController, + ToastController, +} from '@ionic/angular' +import { PatchDB } from 'patch-db-client' +import { ApiService } from 'src/app/services/api/embassy-api.service' +import { ConfigService } from 'src/app/services/config.service' +import { DataModel } from 'src/app/services/patch-db/data-model' +import { ErrorToastService } from '@start9labs/shared' + +@Component({ + selector: 'experimental-features', + templateUrl: './experimental-features.page.html', + styleUrls: ['./experimental-features.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ExperimentalFeaturesPage { + readonly server$ = this.patch.watch$('server-info') + + constructor( + private readonly toastCtrl: ToastController, + private readonly patch: PatchDB, + private readonly config: ConfigService, + private readonly alertCtrl: AlertController, + private readonly loadingCtrl: LoadingController, + private readonly api: ApiService, + private readonly errToast: ErrorToastService, + ) {} + + async presentAlertResetTor() { + const isTor = this.config.isTor() + const shared = + 'Optionally wipe state to forcibly acquire new guard nodes. It is recommended to try without wiping state first.' + const alert = await this.alertCtrl.create({ + header: isTor ? 'Warning' : 'Confirm', + message: isTor + ? `You are currently connected over Tor. If you reset the Tor daemon, you will loose connectivity until it comes back online.

${shared}` + : `Reset Tor?

${shared}`, + inputs: [ + { + label: 'Wipe state', + type: 'checkbox', + value: 'wipe', + handler: val => { + console.error(val) + }, + }, + ], + buttons: [ + { + text: 'Cancel', + role: 'cancel', + }, + { + text: 'Reset', + handler: (value: string[]) => { + console.error(value) + this.resetTor(value.some(v => 'wipe')) + }, + cssClass: 'enter-click', + }, + ], + cssClass: isTor ? 'alert-warning-message' : '', + }) + await alert.present() + } + + async presentAlertZram(enabled: boolean) { + const alert = await this.alertCtrl.create({ + header: enabled ? 'Confirm' : 'Warning', + message: enabled + ? 'Are you sure you want to disable zram?' + : 'zram on StartOS is experimental. It may increase performance of you server, especially if it is a low RAM device.', + buttons: [ + { + text: 'Cancel', + role: 'cancel', + }, + { + text: enabled ? 'Disable' : 'Enable', + handler: () => { + this.toggleZram(enabled) + }, + cssClass: 'enter-click', + }, + ], + cssClass: enabled ? '' : 'alert-warning-message', + }) + await alert.present() + } + + private async resetTor(wipeState: boolean) { + const loader = await this.loadingCtrl.create({ + message: 'Resetting Tor...', + }) + await loader.present() + + try { + await this.api.resetTor({ + 'wipe-state': wipeState, + reason: 'User triggered', + }) + const toast = await this.toastCtrl.create({ + header: 'Tor reset in progress', + position: 'bottom', + duration: 4000, + buttons: [ + { + side: 'start', + icon: 'close', + handler: () => { + return true + }, + }, + ], + }) + await toast.present() + } catch (e: any) { + this.errToast.present(e) + } finally { + loader.dismiss() + } + } + + private async toggleZram(enabled: boolean) { + const loader = await this.loadingCtrl.create({ + message: enabled ? 'Disabling zram...' : 'Enabling zram', + }) + await loader.present() + + try { + await this.api.toggleZram({ enable: !enabled }) + const toast = await this.toastCtrl.create({ + header: `Zram ${enabled ? 'disabled' : 'enabled'}`, + position: 'bottom', + duration: 4000, + buttons: [ + { + side: 'start', + icon: 'close', + handler: () => { + return true + }, + }, + ], + }) + await toast.present() + } catch (e: any) { + this.errToast.present(e) + } finally { + loader.dismiss() + } + } +} diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-routing.module.ts b/frontend/projects/ui/src/app/pages/server-routes/server-routing.module.ts index c6afcbdf2..bd773ddf7 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-routing.module.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/server-routing.module.ts @@ -80,6 +80,13 @@ const routes: Routes = [ loadChildren: () => import('./wifi/wifi.module').then(m => m.WifiPageModule), }, + { + path: 'experimental-features', + loadChildren: () => + import('./experimental-features/experimental-features.module').then( + m => m.ExperimentalFeaturesPageModule, + ), + }, ] @NgModule({ diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts b/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts index 2e6287b50..a50e4fe5a 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts @@ -436,6 +436,17 @@ export class ServerShowPage { detail: true, disabled$: of(false), }, + { + title: 'Experimental Features', + description: 'Try out new and potentially unstable new features', + icon: 'flask-outline', + action: () => + this.navCtrl.navigateForward(['experimental-features'], { + relativeTo: this.route, + }), + detail: true, + disabled$: of(false), + }, ], Insights: [ { diff --git a/frontend/projects/ui/src/app/services/api/api.types.ts b/frontend/projects/ui/src/app/services/api/api.types.ts index 7c8ca9bd6..9a353d109 100644 --- a/frontend/projects/ui/src/app/services/api/api.types.ts +++ b/frontend/projects/ui/src/app/services/api/api.types.ts @@ -68,6 +68,11 @@ export module RR { } // net.tor.reset export type ResetTorRes = null + export type ToggleZramReq = { + enable: boolean + } // server.experimental.zram + export type ToggleZramRes = null + // sessions export type GetSessionsReq = {} // sessions.list diff --git a/frontend/projects/ui/src/app/services/api/embassy-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-api.service.ts index 20169943c..7e2645ca2 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-api.service.ts @@ -113,6 +113,8 @@ export abstract class ApiService { abstract resetTor(params: RR.ResetTorReq): Promise + abstract toggleZram(params: RR.ToggleZramReq): Promise + // marketplace URLs abstract marketplaceProxy( diff --git a/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts index c8a1b48e5..cc17cc65b 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts @@ -196,6 +196,10 @@ export class LiveApiService extends ApiService { return this.rpcRequest({ method: 'net.tor.reset', params }) } + async toggleZram(params: RR.ToggleZramReq): Promise { + return this.rpcRequest({ method: 'server.experimental.zram', params }) + } + // marketplace URLs async marketplaceProxy( diff --git a/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts index 166a2978a..ff0ba1f7f 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts @@ -327,6 +327,18 @@ export class MockApiService extends ApiService { return null } + async toggleZram(params: RR.ToggleZramReq): Promise { + await pauseFor(2000) + const patch = [ + { + op: PatchOp.REPLACE, + path: '/server-info/zram', + value: params.enable, + }, + ] + return this.withRevision(patch, null) + } + // marketplace URLs async marketplaceProxy( diff --git a/frontend/projects/ui/src/app/services/api/mock-patch.ts b/frontend/projects/ui/src/app/services/api/mock-patch.ts index f5eaaea5a..16ce771aa 100644 --- a/frontend/projects/ui/src/app/services/api/mock-patch.ts +++ b/frontend/projects/ui/src/app/services/api/mock-patch.ts @@ -73,6 +73,7 @@ export const mockPatchData: DataModel = { pubkey: 'npub1sg6plzptd64u62a878hep2kev88swjh3tw00gjsfl8f237lmu63q0uf63m', 'ca-fingerprint': 'SHA-256: 63 2B 11 99 44 40 17 DF 37 FC C3 DF 0F 3D 15', 'system-start-time': new Date(new Date().valueOf() - 360042).toUTCString(), + zram: false, }, 'package-data': { bitcoind: { diff --git a/frontend/projects/ui/src/app/services/patch-db/data-model.ts b/frontend/projects/ui/src/app/services/patch-db/data-model.ts index 6f87edfb4..43839ae43 100644 --- a/frontend/projects/ui/src/app/services/patch-db/data-model.ts +++ b/frontend/projects/ui/src/app/services/patch-db/data-model.ts @@ -77,6 +77,7 @@ export interface ServerInfo { pubkey: string 'ca-fingerprint': string 'system-start-time': string + zram: boolean } export interface IpInfo { diff --git a/libs/models/src/errors.rs b/libs/models/src/errors.rs index 332560f7b..a9f3a943b 100644 --- a/libs/models/src/errors.rs +++ b/libs/models/src/errors.rs @@ -75,6 +75,7 @@ pub enum ErrorKind { Grub = 64, Systemd = 65, OpenSsh = 66, + Zram = 67, } impl ErrorKind { pub fn as_str(&self) -> &'static str { @@ -146,6 +147,7 @@ impl ErrorKind { Grub => "Grub Error", Systemd => "Systemd Error", OpenSsh => "OpenSSH Error", + Zram => "Zram Error", } } }