diff --git a/fish/functions/ff-ssh.fish b/fish/functions/ff-ssh.fish index 3cff726..d75493e 100644 --- a/fish/functions/ff-ssh.fish +++ b/fish/functions/ff-ssh.fish @@ -29,6 +29,43 @@ function __ff_ssh_ensure_keys end end +function __ff_ssh_has_toplevel_include --argument ssh_config + awk ' + tolower($1) == "host" || tolower($1) == "match" { saw_block = 1 } + tolower($1) == "include" { + for (i = 2; i <= NF; i++) { + if (tolower($i) ~ /(^|\/)config\.d\/\*/) { + if (!saw_block) found_ok = 1 + } + } + } + END { exit(found_ok ? 0 : 1) } + ' "$ssh_config" +end + +function __ff_ssh_prepend_include --argument ssh_config include_line + set -l tmp (mktemp) + awk -v include_line="$include_line" ' + function is_config_d_include() { + if (tolower($1) != "include") { + return 0 + } + for (i = 2; i <= NF; i++) { + if (tolower($i) ~ /(^|\/)config\.d\/\*/) { + return 1 + } + } + return 0 + } + BEGIN { print include_line } + is_config_d_include() { next } + { print } + ' "$ssh_config" > $tmp + cp $tmp "$ssh_config" + rm -f $tmp + chmod 600 "$ssh_config" +end + function __ff_ssh_ensure_config_d set -l ssh_config "$HOME/.ssh/config" set -l include_line "Include ~/.ssh/config.d/*" @@ -36,15 +73,22 @@ function __ff_ssh_ensure_config_d chmod 700 "$HOME/.ssh" if not test -f $ssh_config - printf '%s\n' $include_line > $ssh_config + printf '%s\n' "$include_line" > $ssh_config chmod 600 $ssh_config echo "Created $ssh_config with $include_line" - else if grep -qE 'Include[[:space:]]+.*config\.d' $ssh_config - echo "config.d Include already in $ssh_config" - else - printf '\n# config.d\n%s\n' $include_line >> $ssh_config - echo "Appended to $ssh_config: $include_line" + return end + + chmod 600 $ssh_config + if __ff_ssh_has_toplevel_include $ssh_config + echo "config.d Include already in $ssh_config" + return + end + + set -l backup "$ssh_config.backup."(date +%Y%m%d%H%M%S) + cp -p $ssh_config $backup + __ff_ssh_prepend_include $ssh_config "$include_line" + echo "Prepended to $ssh_config: $include_line (backup $backup)" end function __ff_ssh_resolve_path --argument path @@ -58,22 +102,62 @@ function __ff_ssh_resolve_path --argument path end function __ff_ssh_remote_has_include --argument host - ssh -q $host 'test -f ~/.ssh/config && grep -qE "Include[[:space:]]+.*config\.d" ~/.ssh/config' + set -l script 'test -f ~/.ssh/config && awk " + tolower(\$1) == \"host\" || tolower(\$1) == \"match\" { saw_block = 1 } + tolower(\$1) == \"include\" { + for (i = 2; i <= NF; i++) { + if (tolower(\$i) ~ /(^|\/)config\.d\/\*/) { + if (!saw_block) found_ok = 1 + } + } + } + END { exit(found_ok ? 0 : 1) } + " ~/.ssh/config' + printf '%s\n' "$script" | ssh -qT $host sh -s end function __ff_ssh_remote_ensure_include --argument host - ssh $host 'mkdir -p ~/.ssh/config.d && chmod 700 ~/.ssh - if [ -f ~/.ssh/config ] && grep -qE "Include[[:space:]]+.*config\.d" ~/.ssh/config; then - echo "Remote config.d Include already exists" - elif [ -f ~/.ssh/config ]; then - printf "\n# config.d\nInclude ~/.ssh/config.d/*\n" >> ~/.ssh/config - chmod 600 ~/.ssh/config - echo "Appended config.d Include on remote" - else - printf "%s\n" "Include ~/.ssh/config.d/*" > ~/.ssh/config - chmod 600 ~/.ssh/config - echo "Created remote config with config.d Include" - fi' + set -l script 'mkdir -p ~/.ssh/config.d && chmod 700 ~/.ssh +include_line="Include ~/.ssh/config.d/*" +has_toplevel() { + awk " + tolower(\$1) == \"host\" || tolower(\$1) == \"match\" { saw_block = 1 } + tolower(\$1) == \"include\" { + for (i = 2; i <= NF; i++) { + if (tolower(\$i) ~ /(^|\/)config\.d\/\*/) { + if (!saw_block) found_ok = 1 + } + } + } + END { exit(found_ok ? 0 : 1) } + " ~/.ssh/config +} +if [ ! -f ~/.ssh/config ]; then + printf "%s\n" "$include_line" > ~/.ssh/config + chmod 600 ~/.ssh/config + echo "Created remote config with config.d Include" +elif has_toplevel; then + echo "Remote config.d Include already exists" +else + tmp=$(mktemp) + awk -v include_line="$include_line" " + function is_config_d_include() { + if (tolower(\$1) != \"include\") return 0 + for (i = 2; i <= NF; i++) { + if (tolower(\$i) ~ /(^|\/)config\.d\/\*/) return 1 + } + return 0 + } + BEGIN { print include_line } + is_config_d_include() { next } + { print } + " ~/.ssh/config > "$tmp" + cp "$tmp" ~/.ssh/config + rm -f "$tmp" + chmod 600 ~/.ssh/config + echo "Prepended config.d Include on remote" +fi' + printf '%s\n' "$script" | ssh -T $host sh -s end function __ff_ssh_resolve_copy_src --argument file diff --git a/script/ssh2.sh b/script/ssh2.sh index 5fcf913..d8cec24 100755 --- a/script/ssh2.sh +++ b/script/ssh2.sh @@ -166,6 +166,21 @@ find_alias_file() { return 1 } +# Include 出现在 Host/Match 之后会被当成该块的条件引入,必须写在文件最前面。 +ssh_config_has_toplevel_include() { + awk ' + tolower($1) == "host" || tolower($1) == "match" { saw_block = 1 } + tolower($1) == "include" { + for (i = 2; i <= NF; i++) { + if (tolower($i) ~ /(^|\/)config\.d\/\*/) { + if (!saw_block) found_ok = 1 + } + } + } + END { exit(found_ok ? 0 : 1) } + ' "$1" +} + ensure_config_include() { mkdir -p "$SSH_CONFIG_DIR" chmod 700 "$SSH_DIR" "$SSH_CONFIG_DIR" @@ -177,17 +192,8 @@ ensure_config_include() { return fi - if awk ' - tolower($1) == "include" { - for (i = 2; i <= NF; i++) { - if (tolower($i) ~ /(^|\/)config\.d\/\*/) { - found = 1 - } - } - } - END { exit(found ? 0 : 1) } - ' "$SSH_CONFIG"; then - chmod 600 "$SSH_CONFIG" + chmod 600 "$SSH_CONFIG" + if ssh_config_has_toplevel_include "$SSH_CONFIG"; then return fi @@ -198,18 +204,26 @@ ensure_config_include() { cp -p "$SSH_CONFIG" "$backup" awk -v include_line="$INCLUDE_LINE" ' - BEGIN { - print "# Managed SSH config fragments" - print include_line - print "" + function is_config_d_include() { + if (tolower($1) != "include") { + return 0 + } + for (i = 2; i <= NF; i++) { + if (tolower($i) ~ /(^|\/)config\.d\/\*/) { + return 1 + } + } + return 0 } + BEGIN { print include_line } + is_config_d_include() { next } { print } ' "$SSH_CONFIG" >"$tmp" cp "$tmp" "$SSH_CONFIG" rm -f "$tmp" chmod 600 "$SSH_CONFIG" - echo "已在 $SSH_CONFIG 中加入 config.d,并备份到 $backup" + echo "已在 $SSH_CONFIG 第一行加入 config.d Include,并备份到 $backup" } alias_name=""