feat: add deploy command (all ai test + fix this all)
This commit is contained in:
parent
ae3c21c5a0
commit
44d0fd22e1
3 changed files with 334 additions and 1 deletions
332
src/commands/system/deploy.rs
Normal file
332
src/commands/system/deploy.rs
Normal file
|
|
@ -0,0 +1,332 @@
|
||||||
|
use crate::client::Client;
|
||||||
|
use crate::client::ClientEvent;
|
||||||
|
use crate::client::Player;
|
||||||
|
use crate::commands::Command;
|
||||||
|
use crate::commands::argument::{ArgumentSpec, ParsedArguments};
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use std::io::{BufRead, BufReader, Read};
|
||||||
|
use std::process::{Child, Command as SysCommand, Stdio};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use tokio::task;
|
||||||
|
|
||||||
|
pub struct DeployCommand {
|
||||||
|
running: Arc<Mutex<Option<Child>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeployCommand {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
DeployCommand {
|
||||||
|
running: Arc::new(Mutex::new(None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Command for DeployCommand {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"deploy"
|
||||||
|
}
|
||||||
|
fn aliases(&self) -> &[&'static str] {
|
||||||
|
&["redeploy", "update"]
|
||||||
|
}
|
||||||
|
fn category(&self) -> &'static str {
|
||||||
|
"system"
|
||||||
|
}
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
"Pulls, builds, and restarts copper via systemctl."
|
||||||
|
}
|
||||||
|
fn argument_spec(&self) -> &'static [ArgumentSpec] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
async fn constructed(&mut self, _: Client) {}
|
||||||
|
async fn event(&mut self, _: Client, _: ClientEvent) {}
|
||||||
|
|
||||||
|
async fn execute(&mut self, client: Client, player: Player, _args: ParsedArguments) {
|
||||||
|
let admin_id = "3bff3f33e6dc0410fdc61d13";
|
||||||
|
if player._id != admin_id {
|
||||||
|
client.message("You are not authorized to deploy.").await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.message("Starting deploy sequence...").await;
|
||||||
|
|
||||||
|
client.message("Running `git pull`...").await;
|
||||||
|
let git_pull = SysCommand::new("git")
|
||||||
|
.arg("pull")
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn();
|
||||||
|
|
||||||
|
match git_pull {
|
||||||
|
Ok(mut child) => {
|
||||||
|
let stdout = child.stdout.take().unwrap();
|
||||||
|
let stderr = child.stderr.take().unwrap();
|
||||||
|
let mut reader = BufReader::new(stdout);
|
||||||
|
let mut err_reader = BufReader::new(stderr);
|
||||||
|
|
||||||
|
let mut output = String::new();
|
||||||
|
let mut err_output = String::new();
|
||||||
|
|
||||||
|
reader.read_to_string(&mut output).unwrap_or(0);
|
||||||
|
err_reader.read_to_string(&mut err_output).unwrap_or(0);
|
||||||
|
|
||||||
|
client
|
||||||
|
.message(format!("git pull output:\n{}", output))
|
||||||
|
.await;
|
||||||
|
if !err_output.is_empty() {
|
||||||
|
client
|
||||||
|
.message(format!("git pull error:\n{}", err_output))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = child.wait().unwrap();
|
||||||
|
if !status.success() {
|
||||||
|
client.message("git pull failed. Aborting.").await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
client
|
||||||
|
.message(format!("Failed to run git pull: {}", e))
|
||||||
|
.await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.message("Running `cargo run --release`...").await;
|
||||||
|
let mut cargo_child = match SysCommand::new("cargo")
|
||||||
|
.arg("run")
|
||||||
|
.arg("--release")
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
{
|
||||||
|
Ok(child) => child,
|
||||||
|
Err(e) => {
|
||||||
|
client
|
||||||
|
.message(format!("Failed to start cargo run: {}", e))
|
||||||
|
.await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let cargo_stdout = cargo_child.stdout.take().unwrap();
|
||||||
|
let cargo_stderr = cargo_child.stderr.take().unwrap();
|
||||||
|
|
||||||
|
let client_clone = client.clone();
|
||||||
|
let found_connected = Arc::new(Mutex::new(false));
|
||||||
|
let found_connected_cargo = found_connected.clone();
|
||||||
|
|
||||||
|
task::spawn_blocking(async move || {
|
||||||
|
let reader = BufReader::new(cargo_stdout);
|
||||||
|
for line in reader.lines() {
|
||||||
|
match line {
|
||||||
|
Ok(l) => {
|
||||||
|
let msg = format!("cargo: {}", l);
|
||||||
|
let _ = client_clone.message(msg.clone()).await;
|
||||||
|
if l.contains("[CONNECTED]") {
|
||||||
|
let mut found = found_connected_cargo.blocking_lock();
|
||||||
|
*found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let client_clone2 = client.clone();
|
||||||
|
task::spawn_blocking(async move || {
|
||||||
|
let reader = BufReader::new(cargo_stderr);
|
||||||
|
for line in reader.lines() {
|
||||||
|
match line {
|
||||||
|
Ok(l) => {
|
||||||
|
let msg = format!("cargo stderr: {}", l);
|
||||||
|
let _ = client_clone2.message(msg.clone()).await;
|
||||||
|
}
|
||||||
|
Err(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
loop {
|
||||||
|
{
|
||||||
|
let found = found_connected.lock().await;
|
||||||
|
if *found {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
client
|
||||||
|
.message(
|
||||||
|
"Detected [CONNECTED] from cargo run --release. Proceeding to systemctl restart.",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
client.message("Running `systemctl daemon-reload`...").await;
|
||||||
|
let daemon_reload = SysCommand::new("systemctl")
|
||||||
|
.arg("daemon-reload")
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn();
|
||||||
|
|
||||||
|
match daemon_reload {
|
||||||
|
Ok(mut child) => {
|
||||||
|
let stdout = child.stdout.take().unwrap();
|
||||||
|
let stderr = child.stderr.take().unwrap();
|
||||||
|
let mut reader = BufReader::new(stdout);
|
||||||
|
let mut err_reader = BufReader::new(stderr);
|
||||||
|
|
||||||
|
let mut output = String::new();
|
||||||
|
let mut err_output = String::new();
|
||||||
|
|
||||||
|
reader.read_to_string(&mut output).unwrap_or(0);
|
||||||
|
err_reader.read_to_string(&mut err_output).unwrap_or(0);
|
||||||
|
|
||||||
|
client
|
||||||
|
.message(format!("daemon-reload output:\n{}", output))
|
||||||
|
.await;
|
||||||
|
if !err_output.is_empty() {
|
||||||
|
client
|
||||||
|
.message(format!("daemon-reload error:\n{}", err_output))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = child.wait().unwrap();
|
||||||
|
if !status.success() {
|
||||||
|
client
|
||||||
|
.message("systemctl daemon-reload failed. Aborting.")
|
||||||
|
.await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
client
|
||||||
|
.message(format!("Failed to run systemctl daemon-reload: {}", e))
|
||||||
|
.await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client
|
||||||
|
.message("Running `systemctl restart copper`...")
|
||||||
|
.await;
|
||||||
|
let restart_copper = SysCommand::new("systemctl")
|
||||||
|
.arg("restart")
|
||||||
|
.arg("copper")
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn();
|
||||||
|
|
||||||
|
match restart_copper {
|
||||||
|
Ok(mut child) => {
|
||||||
|
let stdout = child.stdout.take().unwrap();
|
||||||
|
let stderr = child.stderr.take().unwrap();
|
||||||
|
let mut reader = BufReader::new(stdout);
|
||||||
|
let mut err_reader = BufReader::new(stderr);
|
||||||
|
|
||||||
|
let mut output = String::new();
|
||||||
|
let mut err_output = String::new();
|
||||||
|
|
||||||
|
reader.read_to_string(&mut output).unwrap_or(0);
|
||||||
|
err_reader.read_to_string(&mut err_output).unwrap_or(0);
|
||||||
|
|
||||||
|
client
|
||||||
|
.message(format!("restart copper output:\n{}", output))
|
||||||
|
.await;
|
||||||
|
if !err_output.is_empty() {
|
||||||
|
client
|
||||||
|
.message(format!("restart copper error:\n{}", err_output))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = child.wait().unwrap();
|
||||||
|
if !status.success() {
|
||||||
|
client
|
||||||
|
.message("systemctl restart copper failed. Aborting.")
|
||||||
|
.await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
client
|
||||||
|
.message(format!("Failed to run systemctl restart copper: {}", e))
|
||||||
|
.await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client
|
||||||
|
.message("Waiting for [CONNECTED] from systemctl copper...")
|
||||||
|
.await;
|
||||||
|
let copper_status = SysCommand::new("journalctl")
|
||||||
|
.arg("-u")
|
||||||
|
.arg("copper")
|
||||||
|
.arg("-n")
|
||||||
|
.arg("50")
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn();
|
||||||
|
|
||||||
|
match copper_status {
|
||||||
|
Ok(mut child) => {
|
||||||
|
let stdout = child.stdout.take().unwrap();
|
||||||
|
let reader = BufReader::new(stdout);
|
||||||
|
|
||||||
|
let mut found = false;
|
||||||
|
for line in reader.lines() {
|
||||||
|
match line {
|
||||||
|
Ok(l) => {
|
||||||
|
let msg = format!("copper log: {}", l);
|
||||||
|
client.message(msg.clone()).await;
|
||||||
|
if l.contains("[CONNECTED]") {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
client
|
||||||
|
.message("Detected [CONNECTED] from systemctl copper.")
|
||||||
|
.await;
|
||||||
|
} else {
|
||||||
|
client
|
||||||
|
.message("Did not detect [CONNECTED] from systemctl copper.")
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
let _ = child.wait();
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
client
|
||||||
|
.message(format!("Failed to read copper logs: {}", e))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 6: Kill cargo run --release
|
||||||
|
client.message("Killing cargo run --release...").await;
|
||||||
|
let mut running = self.running.lock().await;
|
||||||
|
if let Some(child) = running.as_mut() {
|
||||||
|
match child.kill() {
|
||||||
|
Ok(_) => {
|
||||||
|
client.message("cargo run --release killed.").await;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
client
|
||||||
|
.message(format!("Failed to kill cargo run: {}", e))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
client.message("No running cargo process found.").await;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.message("Deploy sequence complete.").await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
use crate::submods;
|
use crate::submods;
|
||||||
|
|
||||||
submods!(follow, help, launch, test, translate, about);
|
submods!(follow, help, launch, test, translate, about, deploy);
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
LaunchCommand,
|
LaunchCommand,
|
||||||
FollowCommand::new(),
|
FollowCommand::new(),
|
||||||
TestCommand::new(),
|
TestCommand::new(),
|
||||||
|
DeployCommand::new(),
|
||||||
BalanceCommand::new(arc_pool.clone()),
|
BalanceCommand::new(arc_pool.clone()),
|
||||||
InventoryCommand::new(arc_pool.clone()),
|
InventoryCommand::new(arc_pool.clone()),
|
||||||
FishCommand::new(arc_pool.clone()),
|
FishCommand::new(arc_pool.clone()),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue