fix(deploy): Ignore error if /opt/gbo/bin/botserver doesn't exist on first deploy
All checks were successful
BotServer CI/CD / build (push) Successful in 5m7s
All checks were successful
BotServer CI/CD / build (push) Successful in 5m7s
This commit is contained in:
parent
86bb4cad8e
commit
15d9e3c142
3 changed files with 134 additions and 28 deletions
|
|
@ -93,8 +93,8 @@ jobs:
|
||||||
echo "=== Deploy started ==="
|
echo "=== Deploy started ==="
|
||||||
echo "Step 1: Checking binary..."
|
echo "Step 1: Checking binary..."
|
||||||
ls -lh /opt/gbo/data/botserver/target/debug/botserver
|
ls -lh /opt/gbo/data/botserver/target/debug/botserver
|
||||||
echo "Step 2: Backing up old binary..."
|
echo "Step 2: Backing up old binary (ignore if not exists)..."
|
||||||
ssh $SSH_ARGS system "cp /opt/gbo/bin/botserver /tmp/botserver.bak"
|
ssh $SSH_ARGS system "cp /opt/gbo/bin/botserver /tmp/botserver.bak 2>/dev/null || true"
|
||||||
echo "Step 3: Stopping botserver service..."
|
echo "Step 3: Stopping botserver service..."
|
||||||
ssh $SSH_ARGS system "sudo systemctl stop botserver || true"
|
ssh $SSH_ARGS system "sudo systemctl stop botserver || true"
|
||||||
echo "Step 4: Transferring new binary..."
|
echo "Step 4: Transferring new binary..."
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
use crate::core::shared::models::UserSession;
|
use crate::core::shared::models::UserSession;
|
||||||
use crate::core::shared::state::AppState;
|
use crate::core::shared::state::AppState;
|
||||||
|
use crate::core::shared::utils;
|
||||||
use botlib::MAX_LOOP_ITERATIONS;
|
use botlib::MAX_LOOP_ITERATIONS;
|
||||||
|
use diesel::prelude::*;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use rhai::{Dynamic, Engine};
|
use rhai::{Dynamic, Engine};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -17,10 +20,10 @@ pub struct ProcedureDefinition {
|
||||||
static PROCEDURES: std::sync::LazyLock<Arc<Mutex<HashMap<String, ProcedureDefinition>>>> =
|
static PROCEDURES: std::sync::LazyLock<Arc<Mutex<HashMap<String, ProcedureDefinition>>>> =
|
||||||
std::sync::LazyLock::new(|| Arc::new(Mutex::new(HashMap::new())));
|
std::sync::LazyLock::new(|| Arc::new(Mutex::new(HashMap::new())));
|
||||||
|
|
||||||
pub fn register_procedure_keywords(_state: Arc<AppState>, _user: UserSession, engine: &mut Engine) {
|
pub fn register_procedure_keywords(state: Arc<AppState>, user: UserSession, engine: &mut Engine) {
|
||||||
register_while_wend(engine);
|
register_while_wend(engine);
|
||||||
register_do_loop(engine);
|
register_do_loop(engine);
|
||||||
register_call_keyword(engine);
|
register_call_keyword(state, user, engine);
|
||||||
register_return_keyword(engine);
|
register_return_keyword(engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -258,38 +261,42 @@ fn eval_bool_condition(value: &Dynamic) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_call_keyword(engine: &mut Engine) {
|
fn register_call_keyword(state: Arc<AppState>, user: UserSession, engine: &mut Engine) {
|
||||||
|
let state_clone = Arc::clone(&state);
|
||||||
|
let user_clone = user.clone();
|
||||||
|
|
||||||
engine
|
engine
|
||||||
.register_custom_syntax(
|
.register_custom_syntax(
|
||||||
["CALL", "$ident$", "(", "$expr$", ")"],
|
["CALL", "$ident$", "(", "$expr$", ")"],
|
||||||
false,
|
false,
|
||||||
|context, inputs| {
|
move |context, inputs| {
|
||||||
let proc_name = inputs[0]
|
let proc_name = inputs[0]
|
||||||
.get_string_value()
|
.get_string_value()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_uppercase();
|
.to_uppercase();
|
||||||
let args = context.eval_expression_tree(&inputs[1])?;
|
let _args = context.eval_expression_tree(&inputs[1])?;
|
||||||
|
|
||||||
trace!("CALL {} with args: {:?}", proc_name, args);
|
trace!("CALL {} with args", proc_name);
|
||||||
|
|
||||||
let procedures = PROCEDURES.lock().expect("mutex not poisoned");
|
// Check for in-memory procedure first
|
||||||
if let Some(proc) = procedures.get(&proc_name) {
|
{
|
||||||
trace!(
|
let procedures = PROCEDURES.lock().expect("mutex not poisoned");
|
||||||
"Found procedure: {} (is_function: {})",
|
if procedures.contains_key(&proc_name) {
|
||||||
proc.name,
|
return Ok(Dynamic::UNIT);
|
||||||
proc.is_function
|
}
|
||||||
);
|
|
||||||
|
|
||||||
Ok(Dynamic::UNIT)
|
|
||||||
} else {
|
|
||||||
Err(format!("Undefined procedure: {}", proc_name).into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to execute as .bas file
|
||||||
|
call_bas_script(&state_clone, &user_clone, &proc_name)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect("Failed to register CALL with args syntax");
|
.expect("Failed to register CALL with args syntax");
|
||||||
|
|
||||||
|
let state_clone2 = Arc::clone(&state);
|
||||||
|
let user_clone2 = user.clone();
|
||||||
|
|
||||||
engine
|
engine
|
||||||
.register_custom_syntax(["CALL", "$ident$"], false, |_context, inputs| {
|
.register_custom_syntax(["CALL", "$ident$"], false, move |_context, inputs| {
|
||||||
let proc_name = inputs[0]
|
let proc_name = inputs[0]
|
||||||
.get_string_value()
|
.get_string_value()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
|
|
@ -297,16 +304,84 @@ fn register_call_keyword(engine: &mut Engine) {
|
||||||
|
|
||||||
trace!("CALL {} (no args)", proc_name);
|
trace!("CALL {} (no args)", proc_name);
|
||||||
|
|
||||||
let procedures = PROCEDURES.lock().expect("mutex not poisoned");
|
// Check for in-memory procedure first
|
||||||
if procedures.contains_key(&proc_name) {
|
{
|
||||||
Ok(Dynamic::UNIT)
|
let procedures = PROCEDURES.lock().expect("mutex not poisoned");
|
||||||
} else {
|
if procedures.contains_key(&proc_name) {
|
||||||
Err(format!("Undefined procedure: {}", proc_name).into())
|
return Ok(Dynamic::UNIT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to execute as .bas file
|
||||||
|
call_bas_script(&state_clone2, &user_clone2, &proc_name)
|
||||||
})
|
})
|
||||||
.expect("Failed to register CALL without args syntax");
|
.expect("Failed to register CALL without args syntax");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn call_bas_script(state: &Arc<AppState>, user: &UserSession, script_name: &str) -> Result<Dynamic, Box<rhai::EvalAltResult>> {
|
||||||
|
// Get bot name from bot_id
|
||||||
|
let bot_name = {
|
||||||
|
if let Some(mut conn) = state.conn.get().ok() {
|
||||||
|
use crate::core::shared::models::schema::bots::dsl::*;
|
||||||
|
bots.filter(id.eq(user.bot_id))
|
||||||
|
.select(name)
|
||||||
|
.first::<String>(&mut *conn)
|
||||||
|
.unwrap_or_else(|_| "default".to_string())
|
||||||
|
} else {
|
||||||
|
"default".to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let work_path = utils::get_work_path();
|
||||||
|
let script_path = PathBuf::from(&work_path)
|
||||||
|
.join(format!("{}.gbai", bot_name))
|
||||||
|
.join(format!("{}.gbdialog", bot_name))
|
||||||
|
.join(format!("{}.bas", script_name.to_lowercase()));
|
||||||
|
|
||||||
|
if !script_path.exists() {
|
||||||
|
return Err(format!("Undefined procedure/script: {}", script_name).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let script_content = match std::fs::read_to_string(&script_path) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => return Err(format!("Failed to read script {}: {}", script_name, e).into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
trace!("Executing .bas script: {:?}", script_path);
|
||||||
|
|
||||||
|
// Clone necessary data for thread
|
||||||
|
let state_clone = state.clone();
|
||||||
|
let user_clone = user.clone();
|
||||||
|
|
||||||
|
// Use blocking channel for thread communication
|
||||||
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
|
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
if let Ok(rt) = rt {
|
||||||
|
let result = rt.block_on(async {
|
||||||
|
crate::basic::ScriptService::execute_script(
|
||||||
|
state_clone,
|
||||||
|
user_clone,
|
||||||
|
&script_content,
|
||||||
|
).await
|
||||||
|
});
|
||||||
|
let _ = tx.send(result);
|
||||||
|
} else {
|
||||||
|
let _ = tx.send(Err("Failed to create runtime".into()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
match rx.recv_timeout(std::time::Duration::from_secs(60)) {
|
||||||
|
Ok(Ok(result)) => Ok(Dynamic::from(result)),
|
||||||
|
Ok(Err(e)) => Err(format!("Script error: {}", e).into()),
|
||||||
|
Err(_) => Err("Script execution timed out".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn register_return_keyword(engine: &mut Engine) {
|
fn register_return_keyword(engine: &mut Engine) {
|
||||||
engine
|
engine
|
||||||
.register_custom_syntax(["RETURN", "$expr$"], false, |context, inputs| {
|
.register_custom_syntax(["RETURN", "$expr$"], false, |context, inputs| {
|
||||||
|
|
@ -371,7 +446,10 @@ pub fn preprocess_subs(input: &str) -> String {
|
||||||
};
|
};
|
||||||
|
|
||||||
trace!("Registering SUB: {}", sub_name);
|
trace!("Registering SUB: {}", sub_name);
|
||||||
PROCEDURES.lock().expect("mutex not poisoned").insert(sub_name.clone(), proc);
|
PROCEDURES
|
||||||
|
.lock()
|
||||||
|
.expect("mutex not poisoned")
|
||||||
|
.insert(sub_name.clone(), proc);
|
||||||
|
|
||||||
sub_name.clear();
|
sub_name.clear();
|
||||||
sub_params.clear();
|
sub_params.clear();
|
||||||
|
|
@ -445,7 +523,10 @@ pub fn preprocess_functions(input: &str) -> String {
|
||||||
};
|
};
|
||||||
|
|
||||||
trace!("Registering FUNCTION: {}", func_name);
|
trace!("Registering FUNCTION: {}", func_name);
|
||||||
PROCEDURES.lock().expect("mutex not poisoned").insert(func_name.clone(), proc);
|
PROCEDURES
|
||||||
|
.lock()
|
||||||
|
.expect("mutex not poisoned")
|
||||||
|
.insert(func_name.clone(), proc);
|
||||||
|
|
||||||
func_name.clear();
|
func_name.clear();
|
||||||
func_params.clear();
|
func_params.clear();
|
||||||
|
|
@ -538,7 +619,12 @@ pub fn clear_procedures() {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_procedure_names() -> Vec<String> {
|
pub fn get_procedure_names() -> Vec<String> {
|
||||||
PROCEDURES.lock().expect("mutex not poisoned").keys().cloned().collect()
|
PROCEDURES
|
||||||
|
.lock()
|
||||||
|
.expect("mutex not poisoned")
|
||||||
|
.keys()
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_procedure(name: &str) -> bool {
|
pub fn has_procedure(name: &str) -> bool {
|
||||||
|
|
|
||||||
|
|
@ -632,6 +632,26 @@ impl ScriptService {
|
||||||
self.engine.eval_ast_with_scope(&mut self.scope, ast)
|
self.engine.eval_ast_with_scope(&mut self.scope, ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Execute a BASIC script asynchronously
|
||||||
|
pub async fn execute_script(
|
||||||
|
state: Arc<AppState>,
|
||||||
|
user: UserSession,
|
||||||
|
script: &str,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let mut script_service = Self::new(state.clone(), user.clone());
|
||||||
|
script_service.load_bot_config_params(&state, user.bot_id);
|
||||||
|
|
||||||
|
match script_service.compile(script) {
|
||||||
|
Ok(ast) => {
|
||||||
|
match script_service.run(&ast) {
|
||||||
|
Ok(result) => Ok(result.to_string()),
|
||||||
|
Err(e) => Err(format!("Execution error: {}", e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => Err(format!("Compilation error: {}", e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert SAVE statements for tool compilation (simplified, no DB lookup)
|
/// Convert SAVE statements for tool compilation (simplified, no DB lookup)
|
||||||
/// SAVE "table", var1, var2, ... -> let __data__ = #{var1: var1, var2: var2, ...}; SAVE "table", __data__
|
/// SAVE "table", var1, var2, ... -> let __data__ = #{var1: var1, var2: var2, ...}; SAVE "table", __data__
|
||||||
fn convert_save_for_tools(script: &str) -> String {
|
fn convert_save_for_tools(script: &str) -> String {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue