Merge pull request #2599 from Start9Labs/bugfix/wifi

fix wifi types
This commit is contained in:
Matt Hill
2024-04-10 13:49:02 -06:00
committed by GitHub
19 changed files with 124 additions and 91 deletions

View File

@@ -11,10 +11,6 @@ export type ServerInfo = {
hostname: string;
version: string;
lastBackup: string | null;
/**
* Used in the wifi to determine the region to set the system to
*/
lastWifiRegion: string | null;
eosVersionCompat: string;
lanAddress: string;
onionAddress: string;
@@ -24,7 +20,7 @@ export type ServerInfo = {
torAddress: string;
ipInfo: { [key: string]: IpInfo };
statusInfo: ServerStatus;
wifi: WifiInfo | null;
wifi: WifiInfo;
unreadNotificationCount: number;
passwordHash: string;
pubkey: string;

View File

@@ -1,8 +1,8 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type WifiInfo = {
interface: string;
interface: string | null;
ssids: Array<string>;
selected: string | null;
connected: string | null;
lastRegion: string | null;
};

View File

@@ -96,11 +96,7 @@ pub async fn recover_full_embassy(
.with_kind(ErrorKind::PasswordHashGeneration)?;
let db = ctx.db().await?;
db.put(
&ROOT,
&Database::init(&os_backup.account, ctx.config.wifi_interface.clone())?,
)
.await?;
db.put(&ROOT, &Database::init(&os_backup.account)?).await?;
drop(db);
init(&ctx.config).await?;

View File

@@ -91,8 +91,6 @@ impl ClientConfig {
pub struct ServerConfig {
#[arg(short = 'c', long = "config")]
pub config: Option<PathBuf>,
#[arg(long = "wifi-interface")]
pub wifi_interface: Option<String>,
#[arg(long = "ethernet-interface")]
pub ethernet_interface: Option<String>,
#[arg(skip)]
@@ -117,7 +115,6 @@ impl ContextConfig for ServerConfig {
self.config.take()
}
fn merge_with(&mut self, other: Self) {
self.wifi_interface = self.wifi_interface.take().or(other.wifi_interface);
self.ethernet_interface = self.ethernet_interface.take().or(other.ethernet_interface);
self.os_partitions = self.os_partitions.take().or(other.os_partitions);
self.bind_rpc = self.bind_rpc.take().or(other.bind_rpc);

View File

@@ -26,7 +26,7 @@ use crate::init::check_time_is_synchronized;
use crate::lxc::{LxcContainer, LxcManager};
use crate::middleware::auth::HashSessionToken;
use crate::net::net_controller::NetController;
use crate::net::utils::find_eth_iface;
use crate::net::utils::{find_eth_iface, find_wifi_iface};
use crate::net::wifi::WpaCli;
use crate::prelude::*;
use crate::service::ServiceMap;
@@ -132,6 +132,8 @@ impl RpcContext {
});
}
let wifi_interface = find_wifi_iface().await?;
let seed = Arc::new(RpcContextSeed {
is_closed: AtomicBool::new(false),
datadir: config.datadir().to_path_buf(),
@@ -141,7 +143,7 @@ impl RpcContext {
ErrorKind::Filesystem,
)
})?,
wifi_interface: config.wifi_interface.clone(),
wifi_interface: wifi_interface.clone(),
ethernet_interface: if let Some(eth) = config.ethernet_interface.clone() {
eth
} else {
@@ -158,8 +160,7 @@ impl RpcContext {
lxc_manager: Arc::new(LxcManager::new()),
open_authed_websockets: Mutex::new(BTreeMap::new()),
rpc_stream_continuations: Mutex::new(BTreeMap::new()),
wifi_manager: config
.wifi_interface
wifi_manager: wifi_interface
.clone()
.map(|i| Arc::new(RwLock::new(WpaCli::init(i)))),
current_secret: Arc::new(

View File

@@ -27,9 +27,9 @@ pub struct Database {
pub private: Private,
}
impl Database {
pub fn init(account: &AccountInfo, wifi_interface: Option<String>) -> Result<Self, Error> {
pub fn init(account: &AccountInfo) -> Result<Self, Error> {
Ok(Self {
public: Public::init(account, wifi_interface)?,
public: Public::init(account)?,
private: Private {
key_store: KeyStore::new(account)?,
password: account.password.clone(),

View File

@@ -1,4 +1,4 @@
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::net::{Ipv4Addr, Ipv6Addr};
use chrono::{DateTime, Utc};
@@ -35,7 +35,7 @@ pub struct Public {
pub ui: Value,
}
impl Public {
pub fn init(account: &AccountInfo, wifi_interface: Option<String>) -> Result<Self, Error> {
pub fn init(account: &AccountInfo) -> Result<Self, Error> {
let lan_address = account.hostname.lan_address().parse().unwrap();
Ok(Self {
server_info: ServerInfo {
@@ -45,7 +45,6 @@ impl Public {
version: Current::new().semver().into(),
hostname: account.hostname.no_dot_host_name(),
last_backup: None,
last_wifi_region: None,
eos_version_compat: Current::new().compat().clone(),
lan_address,
onion_address: account.tor_key.public().get_onion_address(),
@@ -60,12 +59,7 @@ impl Public {
shutting_down: false,
restarting: false,
},
wifi: wifi_interface.map(|interface| WifiInfo {
interface,
ssids: Vec::new(),
connected: None,
selected: None,
}),
wifi: WifiInfo::default(),
unread_notification_count: 0,
password_hash: account.password.clone(),
pubkey: ssh_key::PublicKey::from(&account.ssh_key)
@@ -117,9 +111,6 @@ pub struct ServerInfo {
pub version: Version,
#[ts(type = "string | null")]
pub last_backup: Option<DateTime<Utc>>,
/// Used in the wifi to determine the region to set the system to
#[ts(type = "string | null")]
pub last_wifi_region: Option<CountryCode>,
#[ts(type = "string")]
pub eos_version_compat: VersionRange,
#[ts(type = "string")]
@@ -132,7 +123,7 @@ pub struct ServerInfo {
pub ip_info: BTreeMap<String, IpInfo>,
#[serde(default)]
pub status_info: ServerStatus,
pub wifi: Option<WifiInfo>,
pub wifi: WifiInfo,
#[ts(type = "number")]
pub unread_notification_count: u64,
pub password_hash: String,
@@ -202,15 +193,16 @@ pub struct UpdateProgress {
pub downloaded: u64,
}
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
#[ts(export)]
pub struct WifiInfo {
pub interface: String,
pub ssids: Vec<String>,
pub interface: Option<String>,
pub ssids: BTreeSet<String>,
pub selected: Option<String>,
pub connected: Option<String>,
#[ts(type = "string | null")]
pub last_region: Option<CountryCode>,
}
#[derive(Debug, Deserialize, Serialize, TS)]

View File

@@ -232,15 +232,12 @@ pub async fn init(cfg: &ServerConfig) -> Result<InitResult, Error> {
.invoke(crate::ErrorKind::OpenSsl)
.await?;
if let Some(wifi_interface) = &cfg.wifi_interface {
crate::net::wifi::synchronize_wpa_supplicant_conf(
&cfg.datadir().join("main"),
wifi_interface,
&server_info.last_wifi_region,
)
.await?;
tracing::info!("Synchronized WiFi");
}
crate::net::wifi::synchronize_wpa_supplicant_conf(
&cfg.datadir().join("main"),
&mut server_info.wifi,
)
.await?;
tracing::info!("Synchronized WiFi");
let should_rebuild = tokio::fs::metadata(SYSTEM_REBUILD_PATH).await.is_ok()
|| &*server_info.version < &emver::Version::new(0, 3, 2, 0)

View File

@@ -16,6 +16,8 @@ use tracing::instrument;
use ts_rs::TS;
use crate::context::{CliContext, RpcContext};
use crate::db::model::public::WifiInfo;
use crate::net::utils::find_wifi_iface;
use crate::prelude::*;
use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat};
use crate::util::Invoke;
@@ -137,6 +139,18 @@ pub async fn add(ctx: RpcContext, AddParams { ssid, password }: AddParams) -> Re
ErrorKind::Wifi,
));
}
ctx.db
.mutate(|db| {
db.as_public_mut()
.as_server_info_mut()
.as_wifi_mut()
.as_ssids_mut()
.mutate(|s| {
s.insert(ssid);
Ok(())
})
})
.await?;
Ok(())
}
#[derive(Deserialize, Serialize, Parser, TS)]
@@ -190,6 +204,17 @@ pub async fn connect(ctx: RpcContext, SsidParams { ssid }: SsidParams) -> Result
ErrorKind::Wifi,
));
}
ctx.db
.mutate(|db| {
let wifi = db.as_public_mut().as_server_info_mut().as_wifi_mut();
wifi.as_ssids_mut().mutate(|s| {
s.insert(ssid.clone());
Ok(())
})?;
wifi.as_selected_mut().ser(&Some(ssid))
})
.await?;
Ok(())
}
@@ -215,11 +240,23 @@ pub async fn delete(ctx: RpcContext, SsidParams { ssid }: SsidParams) -> Result<
}
wpa_supplicant.remove_network(ctx.db.clone(), &ssid).await?;
ctx.db
.mutate(|db| {
let wifi = db.as_public_mut().as_server_info_mut().as_wifi_mut();
wifi.as_ssids_mut().mutate(|s| {
s.remove(&ssid.0);
Ok(())
})?;
wifi.as_selected_mut()
.map_mutate(|s| Ok(s.filter(|s| s == &ssid.0)))
})
.await?;
Ok(())
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WiFiInfo {
pub struct WifiListInfo {
ssids: HashMap<Ssid, SignalStrength>,
connected: Option<Ssid>,
country: Option<CountryCode>,
@@ -228,7 +265,7 @@ pub struct WiFiInfo {
}
#[derive(serde::Serialize, serde::Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct WifiListInfo {
pub struct WifiListInfoLow {
strength: SignalStrength,
security: Vec<String>,
}
@@ -239,8 +276,8 @@ pub struct WifiListOut {
strength: SignalStrength,
security: Vec<String>,
}
pub type WifiList = HashMap<Ssid, WifiListInfo>;
fn display_wifi_info(params: WithIoFormat<Empty>, info: WiFiInfo) {
pub type WifiList = HashMap<Ssid, WifiListInfoLow>;
fn display_wifi_info(params: WithIoFormat<Empty>, info: WifiListInfo) {
use prettytable::*;
if let Some(format) = params.format {
@@ -330,7 +367,7 @@ fn display_wifi_list(params: WithIoFormat<Empty>, info: Vec<WifiListOut>) {
// #[command(display(display_wifi_info))]
#[instrument(skip_all)]
pub async fn get(ctx: RpcContext, _: Empty) -> Result<WiFiInfo, Error> {
pub async fn get(ctx: RpcContext, _: Empty) -> Result<WifiListInfo, Error> {
let wifi_manager = wifi_manager(&ctx)?;
let wpa_supplicant = wifi_manager.read().await;
let (list_networks, current_res, country_res, ethernet_res, signal_strengths) = tokio::join!(
@@ -368,7 +405,7 @@ pub async fn get(ctx: RpcContext, _: Empty) -> Result<WiFiInfo, Error> {
})
.collect();
let current = current_res?;
Ok(WiFiInfo {
Ok(WifiListInfo {
ssids,
connected: current,
country: country_res?,
@@ -477,7 +514,7 @@ impl SignalStrength {
}
#[derive(Debug, Clone)]
pub struct WifiInfo {
pub struct WifiInfoLow {
ssid: Ssid,
device: Option<String>,
}
@@ -604,7 +641,7 @@ impl WpaCli {
Ok(())
}
#[instrument(skip_all)]
pub async fn list_networks_low(&self) -> Result<BTreeMap<NetworkId, WifiInfo>, Error> {
pub async fn list_networks_low(&self) -> Result<BTreeMap<NetworkId, WifiInfoLow>, Error> {
let r = Command::new("nmcli")
.arg("-t")
.arg("c")
@@ -623,13 +660,13 @@ impl WpaCli {
if !connection_type.contains("wireless") {
return None;
}
let info = WifiInfo {
let info = WifiInfoLow {
ssid: name,
device: device.map(|x| x.to_owned()),
};
Some((uuid, info))
})
.collect::<BTreeMap<NetworkId, WifiInfo>>())
.collect::<BTreeMap<NetworkId, WifiInfoLow>>())
}
#[instrument(skip_all)]
@@ -652,7 +689,7 @@ impl WpaCli {
values.next()?.split(' ').map(|x| x.to_owned()).collect();
Some((
ssid,
WifiListInfo {
WifiListInfoLow {
strength: signal,
security,
},
@@ -686,7 +723,8 @@ impl WpaCli {
db.mutate(|d| {
d.as_public_mut()
.as_server_info_mut()
.as_last_wifi_region_mut()
.as_wifi_mut()
.as_last_region_mut()
.ser(&new_country)
})
.await
@@ -837,9 +875,12 @@ impl TypedValueParser for CountryCodeParser {
#[instrument(skip_all)]
pub async fn synchronize_wpa_supplicant_conf<P: AsRef<Path>>(
main_datadir: P,
wifi_iface: &str,
last_country_code: &Option<CountryCode>,
wifi: &mut WifiInfo,
) -> Result<(), Error> {
wifi.interface = find_wifi_iface().await?;
let Some(wifi_iface) = &wifi.interface else {
return Ok(());
};
let persistent = main_datadir.as_ref().join("system-connections");
tracing::debug!("persistent: {:?}", persistent);
// let supplicant = Path::new("/etc/wpa_supplicant.conf");
@@ -863,7 +904,7 @@ pub async fn synchronize_wpa_supplicant_conf<P: AsRef<Path>>(
.arg("up")
.invoke(ErrorKind::Wifi)
.await?;
if let Some(last_country_code) = last_country_code {
if let Some(last_country_code) = wifi.last_region {
tracing::info!("Setting the region");
let _ = Command::new("iw")
.arg("reg")

View File

@@ -17,7 +17,7 @@ use crate::disk::mount::filesystem::{MountType, ReadWrite};
use crate::disk::mount::guard::{GenericMountGuard, MountGuard, TmpMountGuard};
use crate::disk::util::{DiskInfo, PartitionTable};
use crate::disk::OsPartitionInfo;
use crate::net::utils::{find_eth_iface, find_wifi_iface};
use crate::net::utils::find_eth_iface;
use crate::util::serde::IoFormat;
use crate::util::Invoke;
use crate::ARCH;
@@ -140,7 +140,6 @@ pub async fn execute(
)
})?;
let eth_iface = find_eth_iface().await?;
let wifi_iface = find_wifi_iface().await?;
overwrite |= disk.guid.is_none() && disk.partitions.iter().all(|p| p.guid.is_none());
@@ -260,7 +259,6 @@ pub async fn execute(
IoFormat::Yaml.to_vec(&ServerConfig {
os_partitions: Some(part_info.clone()),
ethernet_interface: Some(eth_iface),
wifi_interface: wifi_iface,
..Default::default()
})?,
)

View File

@@ -419,11 +419,7 @@ async fn fresh_setup(
) -> Result<(Hostname, OnionAddressV3, X509), Error> {
let account = AccountInfo::new(start_os_password, root_ca_start_time().await?)?;
let db = ctx.db().await?;
db.put(
&ROOT,
&Database::init(&account, ctx.config.wifi_interface.clone())?,
)
.await?;
db.put(&ROOT, &Database::init(&account)?).await?;
drop(db);
init(&ctx.config).await?;
Ok((

4
web/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "startos-ui",
"version": "0.3.5.1",
"version": "0.3.5.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "startos-ui",
"version": "0.3.5.1",
"version": "0.3.5.2",
"dependencies": {
"@angular/animations": "^14.1.0",
"@angular/common": "^14.1.0",

View File

@@ -22,6 +22,12 @@
<ion-label class="label montserrat" routerLinkActive="label_selected">
{{ page.title }}
</ion-label>
<ion-icon
*ngIf="page.url === '/system' && (wifiConnected$ | async)"
color="warning"
size="small"
name="warning"
></ion-icon>
<ion-icon
*ngIf="page.url === '/system' && (showEOSUpdate$ | async)"
color="success"

View File

@@ -64,6 +64,8 @@ export class MenuComponent {
'unreadNotificationCount',
)
readonly wifiConnected$ = this.patch.watch$('serverInfo', 'wifi', 'selected')
readonly snekScore$ = this.patch.watch$('ui', 'gaming', 'snake', 'highScore')
readonly showEOSUpdate$ = this.eosService.showUpdate$

View File

@@ -50,7 +50,8 @@
<ion-item
*ngFor="let button of cat.value"
button
[style.display]="(button.title === 'Repair Disk' && !(showDiskRepair$ | async)) ? 'none' : 'block'"
[style.display]="((button.title === 'Repair Disk' && !(showDiskRepair$ | async))) || (button.title === 'WiFi' && !(wifiConnected$ | async)) ? 'none' : 'block'"
[color]="button.title === 'WiFi' ? 'warning' : ''"
[detail]="button.detail"
[disabled]="button.disabled$ | async"
(click)="button.action()"
@@ -59,7 +60,6 @@
<ion-label>
<h2>{{ button.title }}</h2>
<p *ngIf="button.description">{{ button.description }}</p>
<!-- "Create Backup" button only -->
<p *ngIf="button.title === 'Create Backup'">
<ng-container *ngIf="server.statusInfo as statusInfo">

View File

@@ -40,6 +40,7 @@ export class ServerShowPage {
readonly server$ = this.patch.watch$('serverInfo')
readonly showUpdate$ = this.eosService.showUpdate$
readonly showDiskRepair$ = this.ClientStorageService.showDiskRepair$
readonly wifiConnected$ = this.patch.watch$('serverInfo', 'wifi', 'selected')
constructor(
private readonly alertCtrl: AlertController,
@@ -553,7 +554,7 @@ export class ServerShowPage {
},
{
title: 'WiFi',
description: 'Add or remove WiFi networks',
description: 'WiFi is deprecated. Click to learn more.',
icon: 'wifi',
action: () =>
this.navCtrl.navigateForward(['wifi'], { relativeTo: this.route }),

View File

@@ -16,21 +16,22 @@
<ion-content class="ion-padding-top with-widgets">
<ion-item-group>
<!-- always -->
<ion-item>
<ion-item color="warning" class="ion-margin">
<ion-icon slot="start" name="warning-outline"></ion-icon>
<ion-label>
<h2>
Adding WiFi credentials to your StartOS allows you to remove the
Ethernet cable and move the device anywhere you want. StartOS will
automatically connect to available networks.
<a
href="https://docs.start9.com/0.3.5.x/user-manual/wifi"
target="_blank"
rel="noreferrer"
>
View instructions
</a>
</h2>
<h2 style="font-weight: bold">WiFi is deprecated</h2>
<p style="font-weight: 600">
WiFi will be eliminated from StartOS in version v0.4.0, expected soon.
Before then, we highly recommend switching your server to a direct,
Ethernet connection, which is faster and more reliable. If using
Ethernet is not possible for you, we recommend using a WiFi extender
instead.
</p>
</ion-label>
<ion-button slot="end" color="light" (click)="openDocs()">
Learn More
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-button>
</ion-item>
<ion-item-divider>Country</ion-item-divider>

View File

@@ -1,4 +1,4 @@
import { Component } from '@angular/core'
import { Component, Inject } from '@angular/core'
import {
ActionSheetController,
AlertController,
@@ -14,6 +14,7 @@ import { RR } from 'src/app/services/api/api.types'
import { pauseFor, ErrorToastService } from '@start9labs/shared'
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
import { ConfigService } from 'src/app/services/config.service'
import { WINDOW } from '@ng-web-apis/common'
@Component({
selector: 'wifi',
@@ -36,12 +37,21 @@ export class WifiPage {
private readonly errToast: ErrorToastService,
private readonly actionCtrl: ActionSheetController,
private readonly config: ConfigService,
@Inject(WINDOW) private readonly windowRef: Window,
) {}
async ngOnInit() {
await this.getWifi()
}
async openDocs() {
this.windowRef.open(
'https://docs.start9.com/user-manual/wifi.html',
'_blank',
'noreferrer',
)
}
async getWifi(timeout: number = 0): Promise<void> {
this.loading = true
try {

View File

@@ -55,7 +55,6 @@ export const mockPatchData: DataModel = {
ipv6Range: 'FE80:CD00:0000:0CDE:1257:0000:211E:729CD/64',
},
},
lastWifiRegion: null,
unreadNotificationCount: 4,
// password is asdfasdf
passwordHash:
@@ -79,7 +78,7 @@ export const mockPatchData: DataModel = {
interface: 'wlan0',
ssids: [],
selected: null,
connected: null,
lastRegion: null,
},
},
packageData: {