mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
Fix/ntp (#2479)
* rework ntp faiure handling and display to user * uptime in seconds * change how we handle ntp --------- Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use digest::Digest;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use ed25519_dalek::SecretKey;
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use openssl::x509::X509;
|
||||
@@ -29,11 +30,11 @@ pub struct AccountInfo {
|
||||
pub root_ca_cert: X509,
|
||||
}
|
||||
impl AccountInfo {
|
||||
pub fn new(password: &str) -> Result<Self, Error> {
|
||||
pub fn new(password: &str, start_time: SystemTime) -> Result<Self, Error> {
|
||||
let server_id = generate_id();
|
||||
let hostname = generate_hostname();
|
||||
let root_ca_key = generate_key()?;
|
||||
let root_ca_cert = make_root_cert(&root_ca_key, &hostname)?;
|
||||
let root_ca_cert = make_root_cert(&root_ca_key, &hostname, start_time)?;
|
||||
Ok(Self {
|
||||
server_id,
|
||||
hostname,
|
||||
|
||||
@@ -15,6 +15,7 @@ use serde::Deserialize;
|
||||
use sqlx::postgres::PgConnectOptions;
|
||||
use sqlx::PgPool;
|
||||
use tokio::sync::{broadcast, oneshot, Mutex, RwLock};
|
||||
use tokio::time::Instant;
|
||||
use tracing::instrument;
|
||||
|
||||
use super::setup::CURRENT_SECRET;
|
||||
@@ -29,7 +30,7 @@ use crate::install::cleanup::{cleanup_failed, uninstall};
|
||||
use crate::manager::ManagerMap;
|
||||
use crate::middleware::auth::HashSessionToken;
|
||||
use crate::net::net_controller::NetController;
|
||||
use crate::net::ssl::SslManager;
|
||||
use crate::net::ssl::{root_ca_start_time, SslManager};
|
||||
use crate::net::wifi::WpaCli;
|
||||
use crate::notifications::NotificationManager;
|
||||
use crate::shutdown::Shutdown;
|
||||
@@ -123,6 +124,7 @@ pub struct RpcContextSeed {
|
||||
pub current_secret: Arc<Jwk>,
|
||||
pub client: Client,
|
||||
pub hardware: Hardware,
|
||||
pub start_time: Instant,
|
||||
}
|
||||
|
||||
pub struct Hardware {
|
||||
@@ -158,7 +160,7 @@ impl RpcContext {
|
||||
base.dns_bind
|
||||
.as_deref()
|
||||
.unwrap_or(&[SocketAddr::from(([127, 0, 0, 1], 53))]),
|
||||
SslManager::new(&account)?,
|
||||
SslManager::new(&account, root_ca_start_time().await?)?,
|
||||
&account.hostname,
|
||||
&account.key,
|
||||
)
|
||||
@@ -214,6 +216,7 @@ impl RpcContext {
|
||||
.build()
|
||||
.with_kind(crate::ErrorKind::ParseUrl)?,
|
||||
hardware: Hardware { devices, ram },
|
||||
start_time: Instant::now(),
|
||||
});
|
||||
|
||||
let res = Self(seed.clone());
|
||||
|
||||
@@ -79,7 +79,7 @@ impl Database {
|
||||
.iter()
|
||||
.map(|x| format!("{x:X}"))
|
||||
.join(":"),
|
||||
system_start_time: Utc::now().to_rfc3339(),
|
||||
ntp_synced: false,
|
||||
zram: true,
|
||||
},
|
||||
package_data: AllPackageData::default(),
|
||||
@@ -125,7 +125,8 @@ pub struct ServerInfo {
|
||||
pub password_hash: String,
|
||||
pub pubkey: String,
|
||||
pub ca_fingerprint: String,
|
||||
pub system_start_time: String,
|
||||
#[serde(default)]
|
||||
pub ntp_synced: bool,
|
||||
#[serde(default)]
|
||||
pub zram: bool,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::fs::Permissions;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::NonDetachingJoinHandle;
|
||||
@@ -19,7 +19,6 @@ use crate::install::PKG_ARCHIVE_DIR;
|
||||
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
|
||||
use crate::prelude::*;
|
||||
use crate::sound::BEP;
|
||||
use crate::system::time;
|
||||
use crate::util::cpupower::{
|
||||
current_governor, get_available_governors, set_governor, GOVERNOR_PERFORMANCE,
|
||||
};
|
||||
@@ -361,15 +360,28 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut warn_time_not_synced = true;
|
||||
for _ in 0..60 {
|
||||
let mut time_not_synced = true;
|
||||
let mut not_made_progress = 0u32;
|
||||
for _ in 0..1800 {
|
||||
if check_time_is_synchronized().await? {
|
||||
warn_time_not_synced = false;
|
||||
time_not_synced = false;
|
||||
break;
|
||||
}
|
||||
let t = SystemTime::now();
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
if t.elapsed()
|
||||
.map(|t| t > Duration::from_secs_f64(1.1))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
not_made_progress = 0;
|
||||
} else {
|
||||
not_made_progress += 1;
|
||||
}
|
||||
if not_made_progress > 30 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if warn_time_not_synced {
|
||||
if time_not_synced {
|
||||
tracing::warn!("Timed out waiting for system time to synchronize");
|
||||
} else {
|
||||
tracing::info!("Syncronized system clock");
|
||||
@@ -385,7 +397,20 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
||||
backup_progress: None,
|
||||
};
|
||||
|
||||
server_info.system_start_time = time().await?;
|
||||
server_info.ntp_synced = if time_not_synced {
|
||||
let db = db.clone();
|
||||
tokio::spawn(async move {
|
||||
while !check_time_is_synchronized().await.unwrap() {
|
||||
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||
}
|
||||
db.mutate(|v| v.as_server_info_mut().as_ntp_synced_mut().ser(&true))
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
false
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
db.mutate(|v| {
|
||||
v.as_server_info_mut().ser(&server_info)?;
|
||||
|
||||
@@ -17,6 +17,9 @@ lazy_static::lazy_static! {
|
||||
ARCH.to_string()
|
||||
}
|
||||
};
|
||||
pub static ref SOURCE_DATE: SystemTime = {
|
||||
std::fs::metadata(std::env::current_exe().unwrap()).unwrap().modified().unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
pub mod account;
|
||||
@@ -62,6 +65,8 @@ pub mod util;
|
||||
pub mod version;
|
||||
pub mod volume;
|
||||
|
||||
use std::time::SystemTime;
|
||||
|
||||
pub use config::Config;
|
||||
pub use error::{Error, ErrorKind, ResultExt};
|
||||
use rpc_toolkit::command;
|
||||
|
||||
@@ -4,8 +4,8 @@ use std::net::IpAddr;
|
||||
use std::path::Path;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
|
||||
use futures::FutureExt;
|
||||
use libc::time_t;
|
||||
use openssl::asn1::{Asn1Integer, Asn1Time};
|
||||
use openssl::bn::{BigNum, MsbOption};
|
||||
use openssl::ec::{EcGroup, EcKey};
|
||||
@@ -19,15 +19,22 @@ use tokio::sync::{Mutex, RwLock};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::account::AccountInfo;
|
||||
use crate::context::{RpcContext};
|
||||
use crate::context::RpcContext;
|
||||
use crate::hostname::Hostname;
|
||||
use crate::init::check_time_is_synchronized;
|
||||
use crate::net::dhcp::ips;
|
||||
use crate::net::keys::{Key, KeyInfo};
|
||||
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
use crate::{Error, ErrorKind, ResultExt, SOURCE_DATE};
|
||||
|
||||
static CERTIFICATE_VERSION: i32 = 2; // X509 version 3 is actually encoded as '2' in the cert because fuck you.
|
||||
|
||||
fn unix_time(time: SystemTime) -> time_t {
|
||||
time.duration_since(UNIX_EPOCH)
|
||||
.map(|d| d.as_secs() as time_t)
|
||||
.or_else(|_| UNIX_EPOCH.elapsed().map(|d| -(d.as_secs() as time_t)))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct CertPair {
|
||||
pub ed25519: X509,
|
||||
@@ -57,9 +64,13 @@ impl CertPair {
|
||||
}),
|
||||
);
|
||||
if cert
|
||||
.not_after()
|
||||
.compare(Asn1Time::days_from_now(30)?.as_ref())?
|
||||
== Ordering::Greater
|
||||
.not_before()
|
||||
.compare(Asn1Time::days_from_now(0)?.as_ref())?
|
||||
== Ordering::Less
|
||||
&& cert
|
||||
.not_after()
|
||||
.compare(Asn1Time::days_from_now(30)?.as_ref())?
|
||||
== Ordering::Greater
|
||||
&& ips.is_superset(&ip)
|
||||
{
|
||||
return Ok(cert.clone());
|
||||
@@ -82,6 +93,14 @@ impl CertPair {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn root_ca_start_time() -> Result<SystemTime, Error> {
|
||||
Ok(if check_time_is_synchronized().await? {
|
||||
SystemTime::now()
|
||||
} else {
|
||||
*SOURCE_DATE
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SslManager {
|
||||
hostname: Hostname,
|
||||
@@ -91,9 +110,13 @@ pub struct SslManager {
|
||||
cert_cache: RwLock<BTreeMap<Key, CertPair>>,
|
||||
}
|
||||
impl SslManager {
|
||||
pub fn new(account: &AccountInfo) -> Result<Self, Error> {
|
||||
pub fn new(account: &AccountInfo, start_time: SystemTime) -> Result<Self, Error> {
|
||||
let int_key = generate_key()?;
|
||||
let int_cert = make_int_cert((&account.root_ca_key, &account.root_ca_cert), &int_key)?;
|
||||
let int_cert = make_int_cert(
|
||||
(&account.root_ca_key, &account.root_ca_cert),
|
||||
&int_key,
|
||||
start_time,
|
||||
)?;
|
||||
Ok(Self {
|
||||
hostname: account.hostname.clone(),
|
||||
root_cert: account.root_ca_cert.clone(),
|
||||
@@ -162,14 +185,20 @@ pub fn generate_key() -> Result<PKey<Private>, Error> {
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub fn make_root_cert(root_key: &PKey<Private>, hostname: &Hostname) -> Result<X509, Error> {
|
||||
pub fn make_root_cert(
|
||||
root_key: &PKey<Private>,
|
||||
hostname: &Hostname,
|
||||
start_time: SystemTime,
|
||||
) -> Result<X509, Error> {
|
||||
let mut builder = X509Builder::new()?;
|
||||
builder.set_version(CERTIFICATE_VERSION)?;
|
||||
|
||||
let embargo = Asn1Time::days_from_now(0)?;
|
||||
let unix_start_time = unix_time(start_time);
|
||||
|
||||
let embargo = Asn1Time::from_unix(unix_start_time)?;
|
||||
builder.set_not_before(&embargo)?;
|
||||
|
||||
let expiration = Asn1Time::days_from_now(3650)?;
|
||||
let expiration = Asn1Time::from_unix(unix_start_time + (10 * 365 * 86400))?;
|
||||
builder.set_not_after(&expiration)?;
|
||||
|
||||
builder.set_serial_number(&*rand_serial()?)?;
|
||||
@@ -216,14 +245,17 @@ pub fn make_root_cert(root_key: &PKey<Private>, hostname: &Hostname) -> Result<X
|
||||
pub fn make_int_cert(
|
||||
signer: (&PKey<Private>, &X509),
|
||||
applicant: &PKey<Private>,
|
||||
start_time: SystemTime,
|
||||
) -> Result<X509, Error> {
|
||||
let mut builder = X509Builder::new()?;
|
||||
builder.set_version(CERTIFICATE_VERSION)?;
|
||||
|
||||
let embargo = Asn1Time::days_from_now(0)?;
|
||||
let unix_start_time = unix_time(start_time);
|
||||
|
||||
let embargo = Asn1Time::from_unix(unix_start_time)?;
|
||||
builder.set_not_before(&embargo)?;
|
||||
|
||||
let expiration = Asn1Time::days_from_now(3650)?;
|
||||
let expiration = Asn1Time::from_unix(unix_start_time + (10 * 365 * 86400))?;
|
||||
builder.set_not_after(&expiration)?;
|
||||
|
||||
builder.set_serial_number(&*rand_serial()?)?;
|
||||
@@ -346,14 +378,7 @@ pub fn make_leaf_cert(
|
||||
let mut builder = X509Builder::new()?;
|
||||
builder.set_version(CERTIFICATE_VERSION)?;
|
||||
|
||||
let embargo = Asn1Time::from_unix(
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|d| d.as_secs() as i64)
|
||||
.or_else(|_| UNIX_EPOCH.elapsed().map(|d| -(d.as_secs() as i64)))
|
||||
.unwrap_or_default()
|
||||
- 86400,
|
||||
)?;
|
||||
let embargo = Asn1Time::from_unix(unix_time(SystemTime::now()) - 86400)?;
|
||||
builder.set_not_before(&embargo)?;
|
||||
|
||||
// Google Apple and Mozilla reject certificate horizons longer than 397 days
|
||||
|
||||
@@ -31,6 +31,7 @@ use crate::disk::REPAIR_DISK_PATH;
|
||||
use crate::hostname::Hostname;
|
||||
use crate::init::{init, InitResult};
|
||||
use crate::middleware::encrypt::EncryptedWire;
|
||||
use crate::net::ssl::root_ca_start_time;
|
||||
use crate::prelude::*;
|
||||
use crate::util::io::{dir_copy, dir_size, Counter};
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
@@ -378,7 +379,7 @@ async fn fresh_setup(
|
||||
ctx: &SetupContext,
|
||||
embassy_password: &str,
|
||||
) -> Result<(Hostname, OnionAddressV3, X509), Error> {
|
||||
let account = AccountInfo::new(embassy_password)?;
|
||||
let account = AccountInfo::new(embassy_password, root_ca_start_time().await?)?;
|
||||
let sqlite_pool = ctx.secret_store().await?;
|
||||
account.save(&sqlite_pool).await?;
|
||||
sqlite_pool.close().await;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::fmt;
|
||||
|
||||
use chrono::Utc;
|
||||
use clap::ArgMatches;
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::FutureExt;
|
||||
use rpc_toolkit::command;
|
||||
@@ -84,9 +85,65 @@ pub async fn zram(#[context] ctx: RpcContext, #[arg] enable: bool) -> Result<(),
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn time() -> Result<String, Error> {
|
||||
Ok(Utc::now().to_rfc3339())
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct TimeInfo {
|
||||
now: String,
|
||||
uptime: u64,
|
||||
}
|
||||
|
||||
fn display_time(arg: TimeInfo, matches: &ArgMatches) {
|
||||
use std::fmt::Write;
|
||||
|
||||
use prettytable::*;
|
||||
|
||||
if matches.is_present("format") {
|
||||
return display_serializable(arg, matches);
|
||||
}
|
||||
|
||||
let days = arg.uptime / (24 * 60 * 60);
|
||||
let days_s = arg.uptime % (24 * 60 * 60);
|
||||
let hours = days_s / (60 * 60);
|
||||
let hours_s = arg.uptime % (60 * 60);
|
||||
let minutes = hours_s / 60;
|
||||
let seconds = arg.uptime % 60;
|
||||
let mut uptime_string = String::new();
|
||||
if days > 0 {
|
||||
write!(&mut uptime_string, "{days} days").unwrap();
|
||||
}
|
||||
if hours > 0 {
|
||||
if !uptime_string.is_empty() {
|
||||
uptime_string += ", ";
|
||||
}
|
||||
write!(&mut uptime_string, "{hours} hours").unwrap();
|
||||
}
|
||||
if minutes > 0 {
|
||||
if !uptime_string.is_empty() {
|
||||
uptime_string += ", ";
|
||||
}
|
||||
write!(&mut uptime_string, "{minutes} minutes").unwrap();
|
||||
}
|
||||
if !uptime_string.is_empty() {
|
||||
uptime_string += ", ";
|
||||
}
|
||||
write!(&mut uptime_string, "{seconds} seconds").unwrap();
|
||||
|
||||
let mut table = Table::new();
|
||||
table.add_row(row![bc -> "NOW", &arg.now]);
|
||||
table.add_row(row![bc -> "UPTIME", &uptime_string]);
|
||||
table.print_tty(false).unwrap();
|
||||
}
|
||||
|
||||
#[command(display(display_time))]
|
||||
pub async fn time(
|
||||
#[context] ctx: RpcContext,
|
||||
#[allow(unused_variables)]
|
||||
#[arg(long = "format")]
|
||||
format: Option<IoFormat>,
|
||||
) -> Result<TimeInfo, Error> {
|
||||
Ok(TimeInfo {
|
||||
now: Utc::now().to_rfc3339(),
|
||||
uptime: ctx.start_time.elapsed().as_secs(),
|
||||
})
|
||||
}
|
||||
|
||||
#[command(
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<p>
|
||||
Download your server's Root CA and
|
||||
<a
|
||||
href="https://docs.start9.com/latest/user-manual/connecting/connecting-lan"
|
||||
href="https://docs.start9.com/0.3.5.x/user-manual/getting-started/connecting-lan"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
style="color: #6866cc; font-weight: bold; text-decoration: none"
|
||||
@@ -104,7 +104,7 @@
|
||||
<span style="font-weight: bold">Note:</span>
|
||||
This address will only work from a Tor-enabled browser.
|
||||
<a
|
||||
href="https://docs.start9.com/latest/user-manual/connecting/connecting-tor"
|
||||
href="https://docs.start9.com/0.3.5.x/user-manual/getting-started/connecting-tor"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
style="color: #6866cc; font-weight: bold; text-decoration: none"
|
||||
|
||||
@@ -53,9 +53,9 @@ export function getErrorMessage(
|
||||
} else if (e.code === 0) {
|
||||
message =
|
||||
'Request Error. Your browser blocked the request. This is usually caused by a corrupt browser cache or an overly aggressive ad blocker. Please clear your browser cache and/or adjust your ad blocker and try again'
|
||||
link = 'https://docs.start9.com/0.3.5.x/support/common-issues#request-error'
|
||||
} else if (!e.message) {
|
||||
message = 'Unknown Error'
|
||||
link = 'https://docs.start9.com/latest/support/faq'
|
||||
} else {
|
||||
message = e.message
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export class CAWizardComponent {
|
||||
|
||||
instructions() {
|
||||
this.windowRef.open(
|
||||
'https://docs.start9.com/0.3.5.x/getting-started/trust-ca/#trust-root-ca',
|
||||
'https://docs.start9.com/0.3.5.x/user-manual/getting-started/trust-ca/#trust-root-ca',
|
||||
'_blank',
|
||||
'noreferrer',
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<h2>
|
||||
For a secure local connection and faster Tor experience,
|
||||
<a
|
||||
href="https://docs.start9.com/0.3.5.x/getting-started/connecting-lan"
|
||||
href="https://docs.start9.com/0.3.5.x/user-manual/getting-started/connecting-lan"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<ion-back-button defaultHref="system"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Monitor</ion-title>
|
||||
<ion-title slot="end"
|
||||
><ion-spinner name="dots" class="fader"></ion-spinner
|
||||
></ion-title>
|
||||
<ion-title slot="end">
|
||||
<ion-spinner name="dots" class="fader"></ion-spinner>
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
@@ -16,28 +16,51 @@
|
||||
<div id="metricSection">
|
||||
<ng-container *ngIf="!loading">
|
||||
<ion-item-group>
|
||||
<ion-item-divider>Time</ion-item-divider>
|
||||
<ion-item>
|
||||
<ion-label>System Time</ion-label>
|
||||
<ion-note slot="end" class="metric-note">
|
||||
<ion-text style="color: white"
|
||||
>{{ systemTime$ | async | date:'MMMM d, y, h:mm a z':'UTC'
|
||||
}}</ion-text
|
||||
>
|
||||
</ion-note>
|
||||
<!-- <ion-item-divider>Time</ion-item-divider> -->
|
||||
<ion-item *ngIf="now$ | async as now; else timeLoading">
|
||||
<ion-label>
|
||||
<h1>System Time</h1>
|
||||
<h2>
|
||||
<ion-text style="color: white">
|
||||
<b>{{ now.value | date:'MMMM d, y, h:mm a z':'UTC' }}</b>
|
||||
</ion-text>
|
||||
</h2>
|
||||
<p *ngIf="!now.synced">
|
||||
<ion-text color="warning">
|
||||
NTP not synced, time could be wrong
|
||||
</ion-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
<ion-note slot="end" class="metric-note"></ion-note>
|
||||
</ion-item>
|
||||
<ng-template #timeLoading>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<h1>System Time</h1>
|
||||
<p>Loading...</p>
|
||||
</ion-label>
|
||||
<ion-note slot="end" class="metric-note"></ion-note>
|
||||
</ion-item>
|
||||
</ng-template>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>System Uptime</ion-label>
|
||||
<ion-note
|
||||
*ngIf="systemUptime$ | async as uptime"
|
||||
slot="end"
|
||||
class="metric-note"
|
||||
>
|
||||
<ion-text style="color: white">
|
||||
<b>{{ uptime.days }}</b> Days, <b>{{ uptime.hours }}</b> Hours,
|
||||
<b>{{ uptime.minutes }}</b> Minutes
|
||||
</ion-text>
|
||||
</ion-note>
|
||||
<ion-label>
|
||||
<h1>System Uptime</h1>
|
||||
<h2>
|
||||
<ion-text style="color: white">
|
||||
<ng-container *ngIf="uptime$ | async as uptime">
|
||||
<b>{{ uptime.days }}</b>
|
||||
Days,
|
||||
<b>{{ uptime.hours }}</b>
|
||||
Hours,
|
||||
<b>{{ uptime.minutes }}</b>
|
||||
Minutes,
|
||||
<b>{{ uptime.seconds }}</b>
|
||||
Seconds
|
||||
</ng-container>
|
||||
</ion-text>
|
||||
</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
|
||||
@@ -50,9 +73,9 @@
|
||||
>
|
||||
<ion-label>{{ metric.key }}</ion-label>
|
||||
<ion-note *ngIf="metric.value" slot="end" class="metric-note">
|
||||
<ion-text style="color: white"
|
||||
>{{ metric.value.value }} {{ metric.value.unit }}</ion-text
|
||||
>
|
||||
<ion-text style="color: white">
|
||||
{{ metric.value.value }} {{ metric.value.unit }}
|
||||
</ion-text>
|
||||
</ion-note>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
|
||||
@@ -14,8 +14,8 @@ export class ServerMetricsPage {
|
||||
going = false
|
||||
metrics: Metrics = {}
|
||||
|
||||
readonly systemTime$ = this.timeService.systemTime$
|
||||
readonly systemUptime$ = this.timeService.systemUptime$
|
||||
readonly now$ = this.timeService.now$
|
||||
readonly uptime$ = this.timeService.uptime$
|
||||
|
||||
constructor(
|
||||
private readonly errToast: ErrorToastService,
|
||||
|
||||
@@ -15,7 +15,32 @@
|
||||
|
||||
<!-- loaded -->
|
||||
<ion-item-group *ngIf="server$ | async as server; else loading">
|
||||
<ion-item *ngIf="isTorHttp" color="warning">
|
||||
<ion-item
|
||||
*ngIf="!server['ntp-synced']"
|
||||
color="warning"
|
||||
class="ion-margin-bottom"
|
||||
>
|
||||
<ion-icon slot="start" name="warning-outline"></ion-icon>
|
||||
<ion-label>
|
||||
<h2 style="font-weight: bold">Clock sync failure</h2>
|
||||
<p style="font-weight: 600">
|
||||
This will cause connectivity issues. Refer to the StartOS docs to
|
||||
resolve the issue.
|
||||
</p>
|
||||
</ion-label>
|
||||
<ion-button
|
||||
slot="end"
|
||||
color="light"
|
||||
href="https://docs.start9.com/0.3.5.x/support/common-issues#clock-sync-failure"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Open Docs
|
||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
|
||||
<ion-item *ngIf="isTorHttp" color="warning" class="ion-margin-bottom">
|
||||
<ion-icon slot="start" name="warning-outline"></ion-icon>
|
||||
<ion-label>
|
||||
<h2 style="font-weight: bold">Http detected</h2>
|
||||
|
||||
@@ -42,7 +42,10 @@ export module RR {
|
||||
export type EchoRes = string
|
||||
|
||||
export type GetSystemTimeReq = {} // server.time
|
||||
export type GetSystemTimeRes = string
|
||||
export type GetSystemTimeRes = {
|
||||
now: string
|
||||
uptime: number // seconds
|
||||
}
|
||||
|
||||
export type GetServerLogsReq = ServerLogsReq // server.logs & server.kernel-logs
|
||||
export type GetServerLogsRes = LogsRes
|
||||
|
||||
@@ -179,7 +179,10 @@ export class MockApiService extends ApiService {
|
||||
params: RR.GetSystemTimeReq,
|
||||
): Promise<RR.GetSystemTimeRes> {
|
||||
await pauseFor(2000)
|
||||
return new Date().toUTCString()
|
||||
return {
|
||||
now: new Date().toUTCString(),
|
||||
uptime: 1234567,
|
||||
}
|
||||
}
|
||||
|
||||
async getServerLogs(
|
||||
|
||||
@@ -70,7 +70,7 @@ export const mockPatchData: DataModel = {
|
||||
hostname: 'random-words',
|
||||
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(),
|
||||
'ntp-synced': false,
|
||||
zram: false,
|
||||
platform: 'x86_64-nonfree',
|
||||
},
|
||||
|
||||
@@ -76,7 +76,7 @@ export interface ServerInfo {
|
||||
hostname: string
|
||||
pubkey: string
|
||||
'ca-fingerprint': string
|
||||
'system-start-time': string
|
||||
'ntp-synced': boolean
|
||||
zram: boolean
|
||||
platform: string
|
||||
}
|
||||
|
||||
@@ -1,54 +1,53 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import {
|
||||
map,
|
||||
shareReplay,
|
||||
startWith,
|
||||
switchMap,
|
||||
take,
|
||||
tap,
|
||||
} from 'rxjs/operators'
|
||||
import { map, shareReplay, startWith, switchMap } from 'rxjs/operators'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel } from './patch-db/data-model'
|
||||
import { ApiService } from './api/embassy-api.service'
|
||||
import { combineLatest, from, timer } from 'rxjs'
|
||||
import { combineLatest, from, interval } from 'rxjs'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class TimeService {
|
||||
private readonly startTimeMs$ = this.patch
|
||||
.watch$('server-info', 'system-start-time')
|
||||
.pipe(map(startTime => new Date(startTime).valueOf()))
|
||||
|
||||
readonly systemTime$ = from(this.apiService.getSystemTime({})).pipe(
|
||||
switchMap(utcStr => {
|
||||
const dateObj = new Date(utcStr)
|
||||
const msRemaining = (60 - dateObj.getSeconds()) * 1000
|
||||
dateObj.setSeconds(0)
|
||||
const current = dateObj.valueOf()
|
||||
return timer(msRemaining, 60000).pipe(
|
||||
private readonly time$ = from(this.apiService.getSystemTime({})).pipe(
|
||||
switchMap(({ now, uptime }) => {
|
||||
const current = new Date(now).valueOf()
|
||||
return interval(1000).pipe(
|
||||
map(index => {
|
||||
const incremented = index + 1
|
||||
const msToAdd = 60000 * incremented
|
||||
return current + msToAdd
|
||||
return {
|
||||
now: current + 1000 * incremented,
|
||||
uptime: uptime + incremented,
|
||||
}
|
||||
}),
|
||||
startWith({
|
||||
now: current,
|
||||
uptime,
|
||||
}),
|
||||
startWith(current),
|
||||
)
|
||||
}),
|
||||
shareReplay({ bufferSize: 1, refCount: true }),
|
||||
)
|
||||
|
||||
readonly systemUptime$ = combineLatest([
|
||||
this.startTimeMs$,
|
||||
this.systemTime$,
|
||||
readonly now$ = combineLatest([
|
||||
this.time$,
|
||||
this.patch.watch$('server-info', 'ntp-synced'),
|
||||
]).pipe(
|
||||
map(([startTime, currentTime]) => {
|
||||
const ms = currentTime - startTime
|
||||
const days = Math.floor(ms / (24 * 60 * 60 * 1000))
|
||||
const daysms = ms % (24 * 60 * 60 * 1000)
|
||||
const hours = Math.floor(daysms / (60 * 60 * 1000))
|
||||
const hoursms = ms % (60 * 60 * 1000)
|
||||
const minutes = Math.floor(hoursms / (60 * 1000))
|
||||
return { days, hours, minutes }
|
||||
map(([time, synced]) => ({
|
||||
value: time.now,
|
||||
synced,
|
||||
})),
|
||||
)
|
||||
|
||||
readonly uptime$ = this.time$.pipe(
|
||||
map(({ uptime }) => {
|
||||
const days = Math.floor(uptime / (24 * 60 * 60))
|
||||
const daysSec = uptime % (24 * 60 * 60)
|
||||
const hours = Math.floor(daysSec / (60 * 60))
|
||||
const hoursSec = uptime % (60 * 60)
|
||||
const minutes = Math.floor(hoursSec / 60)
|
||||
const seconds = uptime % 60
|
||||
return { days, hours, minutes, seconds }
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ impl Rsync {
|
||||
cmd.arg(format!("--exclude={}", exclude));
|
||||
}
|
||||
let mut command = cmd
|
||||
.arg("-acAXH")
|
||||
.arg("-actAXH")
|
||||
.arg("--info=progress2")
|
||||
.arg("--no-inc-recursive")
|
||||
.arg(src.as_ref())
|
||||
|
||||
Reference in New Issue
Block a user