feat: expire soon notification

This commit is contained in:
kieran 2024-12-21 18:14:31 +00:00
parent 3e7e0a789b
commit 7bfeba0ad1
No known key found for this signature in database
GPG Key ID: DE71CEB3925BE941
6 changed files with 30 additions and 15 deletions

View File

@ -5,16 +5,14 @@ use crate::worker::WorkJob;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use lnvps_db::hydrate::Hydrate; use lnvps_db::hydrate::Hydrate;
use lnvps_db::{LNVpsDb, UserSshKey, Vm, VmOsImage, VmPayment, VmTemplate}; use lnvps_db::{LNVpsDb, UserSshKey, Vm, VmOsImage, VmPayment, VmTemplate};
use log::{debug, error, warn}; use log::{debug, error};
use nostr::util::hex; use nostr::util::hex;
use rocket::futures::{Sink, SinkExt, StreamExt}; use rocket::futures::{Sink, SinkExt, StreamExt};
use rocket::serde::json::Json; use rocket::serde::json::Json;
use rocket::{get, patch, post, routes, Responder, Route, State}; use rocket::{get, patch, post, routes, Responder, Route, State};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ssh_key::PublicKey; use ssh_key::PublicKey;
use std::error::Error;
use std::fmt::Display; use std::fmt::Display;
use std::mem::transmute;
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use ws::Message; use ws::Message;
@ -305,7 +303,7 @@ async fn v1_terminal_proxy(
id: u64, id: u64,
ws: ws::WebSocket, ws: ws::WebSocket,
) -> Result<ws::Channel<'static>, &'static str> { ) -> Result<ws::Channel<'static>, &'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() { if auth.check(&format!("/api/v1/console/{id}"), "GET").is_err() {
return Err("Invalid auth event"); return Err("Invalid auth event");
} }
@ -321,7 +319,7 @@ async fn v1_terminal_proxy(
"Failed to open terminal proxy" "Failed to open terminal proxy"
})?; })?;
let ws = ws.config(Default::default()); let ws = ws.config(Default::default());
Ok(ws.channel(move |mut stream| { Ok(ws.channel(move |stream| {
Box::pin(async move { Box::pin(async move {
let (mut tx_upstream, mut rx_upstream) = ws_upstream.split(); let (mut tx_upstream, mut rx_upstream) = ws_upstream.split();
let (mut tx_client, mut rx_client) = stream.split(); let (mut tx_client, mut rx_client) = stream.split();

View File

@ -1,17 +1,14 @@
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use log::debug; use log::debug;
use reqwest::{ClientBuilder, Method, Url}; use reqwest::{ClientBuilder, Method, Url};
use rocket::futures::{SinkExt, StreamExt};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::fmt::Debug;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio::time::sleep; use tokio::time::sleep;
use tokio_tungstenite::tungstenite::handshake::client::{generate_key, Request}; use tokio_tungstenite::tungstenite::handshake::client::{generate_key, Request};
use tokio_tungstenite::tungstenite::Message;
use tokio_tungstenite::{Connector, MaybeTlsStream, WebSocketStream}; use tokio_tungstenite::{Connector, MaybeTlsStream, WebSocketStream};
pub struct ProxmoxClient { pub struct ProxmoxClient {

View File

@ -3,7 +3,6 @@ use base64::prelude::BASE64_STANDARD;
use base64::Engine; use base64::Engine;
use log::debug; use log::debug;
use nostr::{Event, JsonUtil, Kind, Timestamp}; use nostr::{Event, JsonUtil, Kind, Timestamp};
use reqwest::Url;
use rocket::http::uri::{Absolute, Uri}; use rocket::http::uri::{Absolute, Uri};
use rocket::http::Status; use rocket::http::Status;
use rocket::request::{FromRequest, Outcome}; use rocket::request::{FromRequest, Outcome};

View File

@ -26,7 +26,7 @@ use std::collections::HashSet;
use std::net::IpAddr; use std::net::IpAddr;
use std::ops::Add; use std::ops::Add;
use std::time::Duration; use std::time::Duration;
use tokio::net::{TcpSocket, TcpStream}; use tokio::net::TcpStream;
use tokio_tungstenite::tungstenite::Message; use tokio_tungstenite::tungstenite::Message;
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};

View File

@ -46,5 +46,8 @@ pub trait Provisioner: Send + Sync {
async fn delete_vm(&self, vm_id: u64) -> Result<()>; async fn delete_vm(&self, vm_id: u64) -> Result<()>;
/// Open terminal proxy connection /// Open terminal proxy connection
async fn terminal_proxy(&self, vm_id: u64) -> Result<WebSocketStream<MaybeTlsStream<TcpStream>>>; async fn terminal_proxy(
&self,
vm_id: u64,
) -> Result<WebSocketStream<MaybeTlsStream<TcpStream>>>;
} }

View File

@ -4,7 +4,7 @@ use crate::provisioner::Provisioner;
use crate::settings::{Settings, SmtpConfig}; use crate::settings::{Settings, SmtpConfig};
use crate::status::{VmRunningState, VmState, VmStateCache}; use crate::status::{VmRunningState, VmState, VmStateCache};
use anyhow::Result; use anyhow::Result;
use chrono::{Days, Utc}; use chrono::{DateTime, Days, Utc};
use lettre::message::{MessageBuilder, MultiPart}; use lettre::message::{MessageBuilder, MultiPart};
use lettre::transport::smtp::authentication::Credentials; use lettre::transport::smtp::authentication::Credentials;
use lettre::AsyncTransport; use lettre::AsyncTransport;
@ -40,7 +40,7 @@ pub struct Worker {
tx: UnboundedSender<WorkJob>, tx: UnboundedSender<WorkJob>,
rx: UnboundedReceiver<WorkJob>, rx: UnboundedReceiver<WorkJob>,
client: Option<Client>, client: Option<Client>,
last_check_vms: u64, last_check_vms: DateTime<Utc>,
} }
pub struct WorkerSettings { pub struct WorkerSettings {
@ -74,7 +74,7 @@ impl Worker {
tx, tx,
rx, rx,
client, 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?; self.vm_state_cache.set_state(db_id, state).await?;
if let Ok(db_vm) = self.db.get_vm(db_id).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 // Stop VM if expired and is running
if db_vm.expires < Utc::now() && s.status == VmStatus::Running { if db_vm.expires < Utc::now() && s.status == VmStatus::Running {
info!("Stopping expired VM {}", db_vm.id); info!("Stopping expired VM {}", db_vm.id);
@ -171,7 +187,7 @@ impl Worker {
Ok(()) Ok(())
} }
pub async fn check_vms(&self) -> Result<()> { pub async fn check_vms(&mut self) -> Result<()> {
let hosts = self.db.list_hosts().await?; let hosts = self.db.list_hosts().await?;
for host in hosts { for host in hosts {
let client = get_host_client(&host)?; let client = get_host_client(&host)?;
@ -208,6 +224,8 @@ impl Worker {
self.check_vm(vm.id).await?; self.check_vm(vm.id).await?;
} }
} }
self.last_check_vms = Utc::now();
Ok(()) Ok(())
} }