From 7bfeba0ad110b5ddb8128d085a2b9f6bbb8caa81 Mon Sep 17 00:00:00 2001 From: kieran Date: Sat, 21 Dec 2024 18:14:31 +0000 Subject: [PATCH] feat: expire soon notification --- src/api.rs | 8 +++----- src/host/proxmox.rs | 3 --- src/nip98.rs | 1 - src/provisioner/lnvps.rs | 2 +- src/provisioner/mod.rs | 5 ++++- src/worker.rs | 26 ++++++++++++++++++++++---- 6 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/api.rs b/src/api.rs index 9d16bb2..c68cf90 100644 --- a/src/api.rs +++ b/src/api.rs @@ -5,16 +5,14 @@ use crate::worker::WorkJob; use anyhow::{bail, Result}; use lnvps_db::hydrate::Hydrate; use lnvps_db::{LNVpsDb, UserSshKey, Vm, VmOsImage, VmPayment, VmTemplate}; -use log::{debug, error, warn}; +use log::{debug, error}; use nostr::util::hex; use rocket::futures::{Sink, SinkExt, StreamExt}; use rocket::serde::json::Json; use rocket::{get, patch, post, routes, Responder, Route, State}; use serde::{Deserialize, Serialize}; use ssh_key::PublicKey; -use std::error::Error; use std::fmt::Display; -use std::mem::transmute; use tokio::sync::mpsc::UnboundedSender; use ws::Message; @@ -305,7 +303,7 @@ async fn v1_terminal_proxy( id: u64, ws: ws::WebSocket, ) -> Result, &'static str> { - let auth = Nip98Auth::from_base64(auth).map_err(|e| "Missing or invalid auth param")?; + let auth = Nip98Auth::from_base64(auth).map_err(|_| "Missing or invalid auth param")?; if auth.check(&format!("/api/v1/console/{id}"), "GET").is_err() { return Err("Invalid auth event"); } @@ -321,7 +319,7 @@ async fn v1_terminal_proxy( "Failed to open terminal proxy" })?; let ws = ws.config(Default::default()); - Ok(ws.channel(move |mut stream| { + Ok(ws.channel(move |stream| { Box::pin(async move { let (mut tx_upstream, mut rx_upstream) = ws_upstream.split(); let (mut tx_client, mut rx_client) = stream.split(); diff --git a/src/host/proxmox.rs b/src/host/proxmox.rs index f7bfb4f..4e921e9 100644 --- a/src/host/proxmox.rs +++ b/src/host/proxmox.rs @@ -1,17 +1,14 @@ use anyhow::{anyhow, bail, Result}; use log::debug; use reqwest::{ClientBuilder, Method, Url}; -use rocket::futures::{SinkExt, StreamExt}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use std::fmt::Debug; use std::str::FromStr; -use std::sync::Arc; use std::time::Duration; use tokio::net::TcpStream; use tokio::time::sleep; use tokio_tungstenite::tungstenite::handshake::client::{generate_key, Request}; -use tokio_tungstenite::tungstenite::Message; use tokio_tungstenite::{Connector, MaybeTlsStream, WebSocketStream}; pub struct ProxmoxClient { diff --git a/src/nip98.rs b/src/nip98.rs index 5624fbc..e4230f5 100644 --- a/src/nip98.rs +++ b/src/nip98.rs @@ -3,7 +3,6 @@ use base64::prelude::BASE64_STANDARD; use base64::Engine; use log::debug; use nostr::{Event, JsonUtil, Kind, Timestamp}; -use reqwest::Url; use rocket::http::uri::{Absolute, Uri}; use rocket::http::Status; use rocket::request::{FromRequest, Outcome}; diff --git a/src/provisioner/lnvps.rs b/src/provisioner/lnvps.rs index 55c820a..b948aa7 100644 --- a/src/provisioner/lnvps.rs +++ b/src/provisioner/lnvps.rs @@ -26,7 +26,7 @@ use std::collections::HashSet; use std::net::IpAddr; use std::ops::Add; use std::time::Duration; -use tokio::net::{TcpSocket, TcpStream}; +use tokio::net::TcpStream; use tokio_tungstenite::tungstenite::Message; use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; diff --git a/src/provisioner/mod.rs b/src/provisioner/mod.rs index 89dee47..5404dd8 100644 --- a/src/provisioner/mod.rs +++ b/src/provisioner/mod.rs @@ -46,5 +46,8 @@ pub trait Provisioner: Send + Sync { async fn delete_vm(&self, vm_id: u64) -> Result<()>; /// Open terminal proxy connection - async fn terminal_proxy(&self, vm_id: u64) -> Result>>; + async fn terminal_proxy( + &self, + vm_id: u64, + ) -> Result>>; } diff --git a/src/worker.rs b/src/worker.rs index 0dee6cf..cf7abea 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -4,7 +4,7 @@ use crate::provisioner::Provisioner; use crate::settings::{Settings, SmtpConfig}; use crate::status::{VmRunningState, VmState, VmStateCache}; use anyhow::Result; -use chrono::{Days, Utc}; +use chrono::{DateTime, Days, Utc}; use lettre::message::{MessageBuilder, MultiPart}; use lettre::transport::smtp::authentication::Credentials; use lettre::AsyncTransport; @@ -40,7 +40,7 @@ pub struct Worker { tx: UnboundedSender, rx: UnboundedReceiver, client: Option, - last_check_vms: u64, + last_check_vms: DateTime, } pub struct WorkerSettings { @@ -74,7 +74,7 @@ impl Worker { tx, rx, client, - last_check_vms: Utc::now().timestamp() as u64, + last_check_vms: Utc::now(), } } @@ -102,6 +102,22 @@ impl Worker { self.vm_state_cache.set_state(db_id, state).await?; if let Ok(db_vm) = self.db.get_vm(db_id).await { + const BEFORE_EXPIRE_NOTIFICATION: u64 = 1; + + // Send notification of VM expiring soon + if db_vm.expires < Utc::now().add(Days::new(BEFORE_EXPIRE_NOTIFICATION)) + && db_vm.expires + > self + .last_check_vms + .add(Days::new(BEFORE_EXPIRE_NOTIFICATION)) + { + self.tx.send(WorkJob::SendNotification { + user_id: db_vm.user_id, + title: Some(format!("[VM{}] Expiring Soon", db_vm.id)), + message: format!("Your VM #{} will expire soon, please renew in the next {} days or your VM will be stopped.", db_vm.id, BEFORE_EXPIRE_NOTIFICATION) + })?; + } + // Stop VM if expired and is running if db_vm.expires < Utc::now() && s.status == VmStatus::Running { info!("Stopping expired VM {}", db_vm.id); @@ -171,7 +187,7 @@ impl Worker { Ok(()) } - pub async fn check_vms(&self) -> Result<()> { + pub async fn check_vms(&mut self) -> Result<()> { let hosts = self.db.list_hosts().await?; for host in hosts { let client = get_host_client(&host)?; @@ -208,6 +224,8 @@ impl Worker { self.check_vm(vm.id).await?; } } + + self.last_check_vms = Utc::now(); Ok(()) }