diff --git a/plugins/autodl-remote/.codex-plugin/plugin.json b/plugins/autodl-remote/.codex-plugin/plugin.json index 1a91870..20634e9 100644 --- a/plugins/autodl-remote/.codex-plugin/plugin.json +++ b/plugins/autodl-remote/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "autodl-remote", - "version": "0.6.0", + "version": "0.6.1", "description": "Bash-only SSH account, command, upload, download, job, and shutdown tool layer for AutoDL.", "author": { "name": "haibarazz", diff --git a/plugins/autodl-remote/bin/autodl-remote b/plugins/autodl-remote/bin/autodl-remote index 379f536..9278d33 100755 --- a/plugins/autodl-remote/bin/autodl-remote +++ b/plugins/autodl-remote/bin/autodl-remote @@ -305,6 +305,15 @@ ssh_common_args() { fi } +scp_common_args() { + SCP_ARGS=(-P "$ACCOUNT_PORT" -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ControlMaster=auto -o ControlPersist=600 -o "ControlPath=$APP_DIR/ssh-%C") + mkdir -p "$APP_DIR" + [ -n "$ACCOUNT_KEY" ] && SCP_ARGS+=(-i "$ACCOUNT_KEY") + if [ "$ACCOUNT_AUTH" != "keychain" ] && [ "$ACCOUNT_AUTH" != "prompt" ]; then + SCP_ARGS+=(-o BatchMode=yes) + fi +} + rsync_ssh_command() { local cmd cmd="ssh -p $(shell_quote "$ACCOUNT_PORT") -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ControlMaster=auto -o ControlPersist=600 -o ControlPath=$(shell_quote "$APP_DIR/ssh-%C")" @@ -324,7 +333,7 @@ expect_with_password() { command -v expect >/dev/null 2>&1 || die "Account uses keychain auth, but expect is not available." local password="$1" shift - AUTODL_REMOTE_PASSWORD="$password" expect -c ' + AUTODL_REMOTE_PASSWORD="$password" expect -f /dev/stdin "$@" <<'EXPECT' set timeout -1 set password $env(AUTODL_REMOTE_PASSWORD) spawn {*}$argv @@ -341,7 +350,7 @@ expect_with_password() { } catch wait result exit [lindex $result 3] - ' "$@" +EXPECT } run_transport() { @@ -423,19 +432,151 @@ upload_local_file() { local local_path="$1" remote_path="$2" [ -f "$local_path" ] || die "Local script does not exist or is not a file: $local_path" ensure_remote_dir "$(dirname "$remote_path")" + scp_common_args + run_transport scp "${SCP_ARGS[@]}" "$local_path" "$ACCOUNT_TARGET:$remote_path" +} + +upload_stdin_to_remote() { + local remote_path="$1" + ensure_remote_dir "$(dirname "$remote_path")" ssh_common_args - local scp_args=(-P "$ACCOUNT_PORT") - [ -n "$ACCOUNT_KEY" ] && scp_args+=(-i "$ACCOUNT_KEY") - run_transport scp "${scp_args[@]}" "$local_path" "$ACCOUNT_TARGET:$remote_path" + run_transport ssh "${SSH_ARGS[@]}" "$ACCOUNT_TARGET" "cat > $(shell_quote "$remote_path")" +} + +upload_file_via_ssh() { + local local_path="$1" remote_path="$2" + [ -f "$local_path" ] || die "Local script does not exist or is not a file: $local_path" + upload_stdin_to_remote "$remote_path" < "$local_path" +} + +strip_token_quotes() { + local value="$1" + value="${value%\"}" + value="${value#\"}" + value="${value%\'}" + value="${value#\'}" + printf '%s\n' "$value" +} + +is_dangerous_rm_target() { + local target="$1" recursive="$2" cwd="${3:-$REMOTE_ROOT}" project_root autodl_tmp + target="$(strip_token_quotes "$target")" + [ -n "$target" ] || return 1 + + project_root="${REMOTE_ROOT%/}" + autodl_tmp="/root/autodl-tmp" + + case "$target" in + "\$REMOTE_ROOT"|"\${REMOTE_ROOT}"|"\$PWD"|"\${PWD}") return 0 ;; + esac + + [ "$target" = "/" ] && return 0 + [ "$target" = "/*" ] && return 0 + + if [ "$recursive" = "true" ]; then + case "$target" in + "."|"./"|"./*"|"*"|"~"|"~/"|"~/*") return 0 ;; + esac + fi + + while [ "${target%/}" != "$target" ] && [ "$target" != "/" ]; do + target="${target%/}" + done + + if [ "$target" = "$project_root" ]; then + return 0 + fi + if [ "$target" = "$project_root/*" ]; then + return 0 + fi + if [ "$target" = "$autodl_tmp" ] || [ "$target" = "$autodl_tmp/*" ]; then + return 0 + fi + if [ "$recursive" = "true" ] && [ "$target" = "$cwd" ]; then + return 0 + fi + return 1 +} + +contains_dangerous_rm() { + local cmd="$1" cwd="${2:-$REMOTE_ROOT}" line token recursive + while IFS= read -r line; do + set -- $line + while [ $# -gt 0 ]; do + token="$(strip_token_quotes "$1")" + case "$token" in + sudo|command|builtin|nohup) shift ;; + env) shift; while [ $# -gt 0 ]; do case "$1" in *=*) shift ;; *) break ;; esac; done ;; + *=*) shift ;; + *) break ;; + esac + done + token="$(strip_token_quotes "${1:-}")" + [ "${token##*/}" = "rm" ] || continue + shift + recursive=false + while [ $# -gt 0 ]; do + token="$1" + shift + case "$token" in + --) break ;; + --*) continue ;; + -*) case "$token" in *r*|*R*) recursive=true ;; esac; continue ;; + esac + is_dangerous_rm_target "$token" "$recursive" "$cwd" && return 0 + done + while [ $# -gt 0 ]; do + token="$1" + shift + is_dangerous_rm_target "$token" "$recursive" "$cwd" && return 0 + done + done < "$tmp_local" tmp_name="$(date +%Y%m%d-%H%M%S)-stdin.sh" remote_script="$REMOTE_ROOT/.autodl-remote/tmp/$tmp_name" - upload_local_file "$tmp_local" "$remote_script" - rm -f "$tmp_local" + if [ "$ACCOUNT_AUTH" = "keychain" ]; then + local tmp_local + tmp_local="$(mktemp "${TMPDIR:-/tmp}/autodl-remote-stdin.XXXXXX")" || die "mktemp failed" + cat > "$tmp_local" + upload_local_file "$tmp_local" "$remote_script" || { rm -f "$tmp_local"; return 1; } + rm -f "$tmp_local" + else + upload_stdin_to_remote "$remote_script" || return 1 + fi cmd="$* < $(shell_quote "$remote_script")" else [ $# -gt 0 ] || die "exec requires a command" @@ -681,7 +830,7 @@ cmd_exec() { fi if [ "$allow_dangerous" != "true" ]; then - dangerous_command_check "$cmd" || die "Dangerous remote command blocked. Re-run with --allow-dangerous after explicit approval." + dangerous_command_check "$cmd" "$cwd" || die "Dangerous remote command blocked. Re-run with --allow-dangerous after explicit approval." fi [ -n "$name" ] || name="exec-$(date +%Y%m%d-%H%M%S)" name="$(sanitize_name "$name")" @@ -739,13 +888,11 @@ cmd_put() { fi else [ "$dry_run" != "true" ] || die "dry-run requires rsync." - ssh_common_args - local scp_args=(-P "$ACCOUNT_PORT") - [ -n "$ACCOUNT_KEY" ] && scp_args+=(-i "$ACCOUNT_KEY") + scp_common_args if [ -d "$local_path" ]; then - run_transport scp -r "${scp_args[@]}" "$local_path" "$ACCOUNT_TARGET:$remote_path" + run_transport scp -r "${SCP_ARGS[@]}" "$local_path" "$ACCOUNT_TARGET:$remote_path" else - run_transport scp "${scp_args[@]}" "$local_path" "$ACCOUNT_TARGET:$remote_path" + run_transport scp "${SCP_ARGS[@]}" "$local_path" "$ACCOUNT_TARGET:$remote_path" fi fi write_history put "$local_arg -> $remote_path" @@ -785,12 +932,11 @@ cmd_get() { fi else [ "$dry_run" != "true" ] || die "dry-run requires rsync." - local scp_args=(-P "$ACCOUNT_PORT") - [ -n "$ACCOUNT_KEY" ] && scp_args+=(-i "$ACCOUNT_KEY") + scp_common_args if [ "$kind" = "dir" ]; then - run_transport scp -r "${scp_args[@]}" "$ACCOUNT_TARGET:$remote_path" "$local_path" + run_transport scp -r "${SCP_ARGS[@]}" "$ACCOUNT_TARGET:$remote_path" "$local_path" else - run_transport scp "${scp_args[@]}" "$ACCOUNT_TARGET:$remote_path" "$local_path" + run_transport scp "${SCP_ARGS[@]}" "$ACCOUNT_TARGET:$remote_path" "$local_path" fi fi write_history get "$remote_path -> $local_arg"