From 4edb5cb3b6d91e0c524139071a0eae1220d9c140 Mon Sep 17 00:00:00 2001 From: Kevin Hamer Date: Mon, 28 Sep 2026 14:39:27 -0400 Subject: [PATCH 1/3] Add OPS_SSH_CONNECT_TIMEOUT and improve ops-sync error detection --- README.md | 7 +++++ cmd.sh | 4 +++ ops.sh | 91 ++++++++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 85 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index d017d41..1372aa2 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,13 @@ The following options can be set within your $HOME/.ops/config file. # Default: 0 OPS_PHP_XDEBUG="0" + # Seconds to wait for an SSH connection during 'ops sync' before giving up. + # + # Can also be set in a project's .env file. + # + # Default: 10 + OPS_SSH_CONNECT_TIMEOUT="10" + ## Connect to Services You can connect your app to the following shared services: diff --git a/cmd.sh b/cmd.sh index 18aadaf..c35f552 100644 --- a/cmd.sh +++ b/cmd.sh @@ -147,9 +147,13 @@ cmd-run() { $prefix-$command "$@" fi + local status=$? + if [[ $has_after_command != 0 ]]; then $prefix-$command--after "$@" fi + + return $status } cmd-www() { diff --git a/ops.sh b/ops.sh index 196613a..5b5c99e 100755 --- a/ops.sh +++ b/ops.sh @@ -301,13 +301,22 @@ mariadb-import() { local sqlfile=${2--} - ( - # don't let these commands grab stdin - ops-exec mariadb mariadb -e "DROP DATABASE IF EXISTS $db" - ops-exec mariadb mariadb -e "CREATE DATABASE $db" - ) &2 + exit 1 + fi + + ( + # don't let these commands grab stdin + ops-exec mariadb mariadb -e "DROP DATABASE IF EXISTS $db" + ops-exec mariadb mariadb -e "CREATE DATABASE $db" + ) &2 + exit 1 + fi + + ( + # don't let these commands capture stdin + ops-exec "$OPS_POSTGRES_SERVICE" psql -U postgres -c "DROP DATABASE IF EXISTS $db" + ops-exec "$OPS_POSTGRES_SERVICE" psql -U postgres -c "CREATE DATABASE $db" + ) &2 + else + echo "$(bold ops sync): remote database dump failed." >&2 + fi + + cat "$ssh_log" >&2 + + if grep -q "timed out" "$ssh_log"; then + echo >&2 + echo "The server didn't respond. Its firewall may only allow SSH from certain IPs:" >&2 + echo "connect to the VPN, or ask for your public IP to be allowed." >&2 + echo "If SSH goes through a different host than the site, set" >&2 + echo "OPS_PROJECT_REMOTE_HOST or OPS_PROJECT_REMOTE_SSH_OPTIONS (e.g. -J bastion)." >&2 + fi + + exit 1 + } + if [[ ! -z "$OPS_DEBUG" ]]; then # print out all OPS_ vars echo @@ -775,6 +823,11 @@ ops-sync() { echo fi + if [[ -z "$OPS_PROJECT_REMOTE_HOST" ]]; then + echo "$(bold ops sync): OPS_PROJECT_REMOTE_HOST is not set." >&2 + exit 1 + fi + # sync database if \ [[ $OPS_PROJECT_SYNC_NODB == 0 ]] && \ @@ -787,8 +840,9 @@ ops-sync() { echo "Syncing remote mariadb '$OPS_PROJECT_REMOTE_DB_NAME' to local '$OPS_PROJECT_DB_NAME'..." ops-sync-ssh -C \ - "ops $OPS_PROJECT_REMOTE_DB_TYPE export $OPS_PROJECT_REMOTE_DB_NAME" | \ + "ops $OPS_PROJECT_REMOTE_DB_TYPE export $OPS_PROJECT_REMOTE_DB_NAME" 2>"$ssh_log" | \ $OPS_PROJECT_DB_TYPE-import "$OPS_PROJECT_DB_NAME" + ops-sync-check-ssh "${PIPESTATUS[0]}" elif [[ "$OPS_PROJECT_REMOTE_DB_TYPE" = "mariadb" ]]; then echo "Syncing remote mariadb '$OPS_PROJECT_REMOTE_DB_NAME' to local '$OPS_PROJECT_DB_NAME'..." @@ -803,8 +857,9 @@ ops-sync() { $mysqldump_host \ $mysqldump_user \ $mysqldump_password \ - $OPS_PROJECT_REMOTE_DB_NAME" 2>/dev/null | \ + $OPS_PROJECT_REMOTE_DB_NAME" 2>"$ssh_log" | \ mariadb-import "$OPS_PROJECT_DB_NAME" + ops-sync-check-ssh "${PIPESTATUS[0]}" elif [[ "$OPS_PROJECT_REMOTE_DB_TYPE" = "psql" ]]; then OPS_PROJECT_REMOTE_DB_PORT="${OPS_PROJECT_REMOTE_DB_PORT:-"5432"}" @@ -818,8 +873,9 @@ ops-sync() { ops-sync-ssh -TC "$OPS_PROJECT_REMOTE_PGDUMP_PATH \ $pgdump_host \ - $OPS_PROJECT_REMOTE_DB_NAME" 2>/dev/null | \ + $OPS_PROJECT_REMOTE_DB_NAME" 2>"$ssh_log" | \ psql-import "$OPS_PROJECT_DB_NAME" + ops-sync-check-ssh "${PIPESTATUS[0]}" fi fi @@ -1354,7 +1410,7 @@ main() { docker ps > /dev/null if [[ $? != 0 ]]; then - exit + exit 1 fi validate-config @@ -1407,6 +1463,7 @@ declare -x OPS_DOCKER_UID=${OPS_DOCKER_UID-""} declare -x OPS_DOCKER_VERSION="18" declare -x OPS_DOCKER_COMPOSE_VERSION="1.22" declare -x OPS_PHP_XDEBUG=${OPS_PHP_XDEBUG-"0"} +declare -x OPS_SSH_CONNECT_TIMEOUT=${OPS_SSH_CONNECT_TIMEOUT-"10"} declare -x OPS_DOMAIN=${OPS_DOMAIN-"imarc.io"} declare -x OPS_DOMAIN_ALIASES=${OPS_DOMAIN_ALIASES-""} declare -x OPS_MINIO_ROOT_USER=${OPS_MINIO_ROOT_USER-"minio-user"} From 2e15b4bcaf44b10612437697571e63c7452bc89d Mon Sep 17 00:00:00 2001 From: Kevin Hamer Date: Mon, 28 Sep 2026 16:56:59 -0400 Subject: [PATCH 2/3] Remove OPS_TEST_MODE, exit 1 on unknown commands --- cmd.sh | 14 ++++++++++++-- ops.sh | 14 +++----------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/cmd.sh b/cmd.sh index c35f552..0dfa57f 100644 --- a/cmd.sh +++ b/cmd.sh @@ -34,7 +34,7 @@ cmd-get-doc() { local indent="${3-0}" local pad="" - if [[ $indent > 0 ]]; then + if (( indent > 0 )); then pad=$(seq -f " " -s "" $indent) fi @@ -126,8 +126,14 @@ cmd-run() { }" fi - if [[ ( -z "$command" || $has_hidden_command == 0 && $has_command == 0 ) ]]; then + local unknown_command=0 + + if [[ -z "$command" ]]; then + command='help' + elif [[ $has_hidden_command == 0 && $(type -t $prefix-$command) != 'function' ]]; then + echo "Unknown command: $command" >&2 command='help' + unknown_command=1 fi [[ $(type -t $prefix-$command--before) != 'function' ]] @@ -153,6 +159,10 @@ cmd-run() { $prefix-$command--after "$@" fi + if [[ $unknown_command == 1 ]]; then + return 1 + fi + return $status } diff --git a/ops.sh b/ops.sh index 5b5c99e..86cc003 100755 --- a/ops.sh +++ b/ops.sh @@ -98,14 +98,6 @@ get-version() { awk 'match($0, /([0-9][0-9\.a-z-]+)/) { print substr($0, RSTART, RLENGTH) }' } -run() { - if [[ $OPS_TEST_MODE == 1 ]]; then - echo $@ - else - $@ - fi -} - # Main Commands ops-composer() { @@ -750,7 +742,8 @@ ops-sync() { ( if [[ -z "$OPS_PROJECT_NAME" ]]; then - echo "$(bold ops sync) must be run from a project directory." + echo "$(bold ops sync) must be run from a project directory." >&2 + exit 1 fi cd "$OPS_SITES_DIR/$OPS_PROJECT_NAME" @@ -1406,7 +1399,7 @@ system-help() { } main() { - if [[ "$@" != "system install" ]]; then + if [[ ! ( ( $1 == system || $1 == sys ) && $2 == install ) ]]; then docker ps > /dev/null if [[ $? != 0 ]]; then @@ -1451,7 +1444,6 @@ fi declare -x OPS_ENV="dev" declare -x OPS_CONTAINER_VERSION="0.17.1" declare -x OPS_DEBUG="${OPS_DEBUG}" -declare -x OPS_TEST_MODE="${OPS_TEST_MODE}" declare -x OPS_BACKENDS="${OPS_BACKENDS-"apache-php74 apache-php83 apache-php84"}" declare -x OPS_SERVICES="${OPS_SERVICES-"portainer dashboard mariadb postgres postgres16 redis adminer redis-commander"}" declare -x OPS_EXTRA_SERVICES="${OPS_EXTRA_SERVICES}" From f4854ffc91e6c90ae9ce3694311bed1b7121b239 Mon Sep 17 00:00:00 2001 From: Kevin Hamer Date: Mon, 28 Sep 2026 17:43:49 -0400 Subject: [PATCH 3/3] Replace seq with printf for portable padding --- cmd.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd.sh b/cmd.sh index 0dfa57f..db6240c 100644 --- a/cmd.sh +++ b/cmd.sh @@ -35,7 +35,7 @@ cmd-get-doc() { local pad="" if (( indent > 0 )); then - pad=$(seq -f " " -s "" $indent) + pad=$(printf '%*s' "$indent" '') fi declare -f $1 | \