diff --git a/README.md b/README.md index a086c79..dcf78a5 100644 --- a/README.md +++ b/README.md @@ -203,7 +203,7 @@ Use this workflow to build locally, then push the same stack to a remote host: ``` (Answer “y” to the rebuild prompt in `setup.sh`, or run the rebuild manually.) -2. **Package & Push for Remote Deploy** +2. **Package & Push for Remote Deploy** *(also available via the interactive `./deploy.sh` prompt by choosing “Remote host”)* ```bash ./deploy.sh --yes \ --remote-host docker-server \ @@ -233,7 +233,7 @@ ssh docker-server ' ./setup.sh --module-config sam --playerbot-max-bots 3000 ./scripts/rebuild-with-modules.sh --yes ``` -2. **Migrate Stack to Remote** +2. **Migrate Stack to Remote** *(select “Remote host” when running `./deploy.sh` interactively, or call it non-interactively as shown below)* ```bash ./deploy.sh --yes \ --remote-host docker-server \ diff --git a/build.sh b/build.sh index f72ae2b..d5f3dd1 100755 --- a/build.sh +++ b/build.sh @@ -185,6 +185,17 @@ detect_rebuild_reasons(){ reasons+=("Module changes detected (sentinel file present)") fi + # Check if source repository is freshly cloned (no previous build state) + local storage_path + storage_path="$(read_env STORAGE_PATH_LOCAL "./local-storage")" + if [[ "$storage_path" != /* ]]; then + storage_path="$ROOT_DIR/$storage_path" + fi + local last_deployed="$storage_path/modules/.last_deployed" + if [ ! -f "$last_deployed" ]; then + reasons+=("Fresh source repository setup - initial build required") + fi + # Check if any C++ modules are enabled but modules-latest images don't exist local any_cxx_modules=0 local var @@ -209,7 +220,9 @@ detect_rebuild_reasons(){ fi fi - printf '%s\n' "${reasons[@]}" + if [ ${#reasons[@]} -gt 0 ]; then + printf '%s\n' "${reasons[@]}" + fi } confirm_build(){ @@ -217,7 +230,17 @@ confirm_build(){ if [ ${#reasons[@]} -eq 0 ] && [ "$FORCE_REBUILD" = "0" ]; then info "No build required - all images are up to date" - return 1 # No build needed + if [ "$ASSUME_YES" -ne 1 ] && [ -t 0 ]; then + local reply + read -r -p "Build anyway? [y/N]: " reply + reply="${reply:-n}" + case "$reply" in + [Yy]*) return 0 ;; # Proceed with build + *) return 1 ;; # Skip build + esac + else + return 1 # No build needed (non-interactive or --yes flag) + fi fi # Skip duplicate output if called from deploy.sh (reasons already shown) diff --git a/deploy.sh b/deploy.sh index 6407fe8..117b011 100755 --- a/deploy.sh +++ b/deploy.sh @@ -24,6 +24,7 @@ REMOTE_PORT="22" REMOTE_IDENTITY="" REMOTE_PROJECT_DIR="" REMOTE_SKIP_STORAGE=0 +REMOTE_ARGS_PROVIDED=0 COMPILE_MODULE_VARS=( MODULE_AOE_LOOT MODULE_LEARN_SPELLS MODULE_FIREWORKS MODULE_INDIVIDUAL_PROGRESSION MODULE_AHBOT MODULE_AUTOBALANCE @@ -58,6 +59,141 @@ show_realm_ready(){ printf '%b\n\n' "${GREEN}🗡️ May your server bring epic adventures!${NC}" } +show_remote_plan(){ + local plan_host="${REMOTE_HOST:-}" + local plan_user="${REMOTE_USER:-}" + local plan_dir="${REMOTE_PROJECT_DIR:-~/acore-compose}" + + printf '\n%b\n' "${BLUE}🧭 Remote Deployment Plan${NC}" + printf '%b\n' "${YELLOW}├─ Validate build status locally${NC}" + printf '%b\n' "${YELLOW}└─ Package & sync to ${plan_user}@${plan_host}:${plan_dir}${NC}" +} + +maybe_select_deploy_target(){ + if [ "$REMOTE_MODE" -eq 1 ]; then + return + fi + if [ "$ASSUME_YES" -eq 1 ] || [ ! -t 0 ]; then + return + fi + echo + echo "Select deployment target:" + echo " 1) Local host (current machine)" + echo " 2) Remote host (package for SSH deployment)" + local choice + read -rp "Choice [1]: " choice + case "${choice:-1}" in + 2) + REMOTE_MODE=1 + REMOTE_ARGS_PROVIDED=0 + ;; + *) + ;; + esac +} + +collect_remote_details(){ + if [ "$REMOTE_MODE" -ne 1 ]; then + return + fi + + local interactive=0 + if [ -t 0 ] && [ "$ASSUME_YES" -ne 1 ]; then + interactive=1 + fi + + if [ -z "$REMOTE_HOST" ] && [ "$interactive" -eq 1 ]; then + while true; do + read -rp "Remote host (hostname or IP): " REMOTE_HOST + [ -n "$REMOTE_HOST" ] && break + echo " Please enter a hostname or IP." + done + fi + + if [ -z "$REMOTE_USER" ] && [ "$interactive" -eq 1 ]; then + local default_user="$USER" + read -rp "SSH username [${default_user}]: " REMOTE_USER + REMOTE_USER="${REMOTE_USER:-$default_user}" + fi + if [ -z "$REMOTE_USER" ] && [ -n "$USER" ]; then + REMOTE_USER="$USER" + fi + + if [ -z "$REMOTE_PORT" ]; then + REMOTE_PORT="22" + fi + if [ "$interactive" -eq 1 ]; then + local port_input + read -rp "SSH port [${REMOTE_PORT}]: " port_input + REMOTE_PORT="${port_input:-$REMOTE_PORT}" + fi + + if [ "$interactive" -eq 1 ]; then + local identity_input + local identity_prompt="SSH identity file (leave blank for default)" + if [ -n "$REMOTE_IDENTITY" ]; then + identity_prompt="${identity_prompt} [${REMOTE_IDENTITY}]" + fi + read -rp "${identity_prompt}: " identity_input + [ -n "$identity_input" ] && REMOTE_IDENTITY="$identity_input" + fi + if [ -n "$REMOTE_IDENTITY" ]; then + REMOTE_IDENTITY="${REMOTE_IDENTITY/#\~/$HOME}" + fi + + if [ -z "$REMOTE_PROJECT_DIR" ]; then + REMOTE_PROJECT_DIR="~/acore-compose" + fi + if [ "$interactive" -eq 1 ]; then + local dir_input + read -rp "Remote project directory [${REMOTE_PROJECT_DIR}]: " dir_input + REMOTE_PROJECT_DIR="${dir_input:-$REMOTE_PROJECT_DIR}" + fi + + if [ "$interactive" -eq 1 ] && [ "$REMOTE_ARGS_PROVIDED" -eq 0 ]; then + local sync_answer + read -rp "Sync storage directory to remote host? [Y/n]: " sync_answer + sync_answer="${sync_answer:-Y}" + case "${sync_answer,,}" in + n|no) REMOTE_SKIP_STORAGE=1 ;; + *) REMOTE_SKIP_STORAGE=0 ;; + esac + fi +} + +validate_remote_configuration(){ + if [ "$REMOTE_MODE" -ne 1 ]; then + return + fi + if [ -z "$REMOTE_HOST" ]; then + err "Remote deployment requires a hostname or IP." + exit 1 + fi + if [ -z "$REMOTE_USER" ]; then + err "Remote deployment requires an SSH username." + exit 1 + fi + REMOTE_PORT="${REMOTE_PORT:-22}" + if ! [[ "$REMOTE_PORT" =~ ^[0-9]+$ ]]; then + err "Invalid SSH port: $REMOTE_PORT" + exit 1 + fi + if [ -n "$REMOTE_IDENTITY" ]; then + REMOTE_IDENTITY="${REMOTE_IDENTITY/#\~/$HOME}" + if [ ! -f "$REMOTE_IDENTITY" ]; then + err "Remote identity file not found: $REMOTE_IDENTITY" + exit 1 + fi + fi + if [ -z "$REMOTE_PROJECT_DIR" ]; then + REMOTE_PROJECT_DIR="~/acore-compose" + fi + if [ ! -f "$ROOT_DIR/scripts/migrate-stack.sh" ]; then + err "Migration script not found: $ROOT_DIR/scripts/migrate-stack.sh" + exit 1 + fi +} + usage(){ cat </dev/null; then + echo "ℹ️ Destination storage path $dest_root not accessible (likely remote storage - skipping sync)." + echo "ℹ️ Module sync will be handled by the remote deployment." + return + fi if command -v rsync >/dev/null 2>&1; then rsync -a --delete "$src_modules"/ "$dest_modules"/