#!/usr/bin/env bash # Генерирует SSH-ключи и ~/.ssh/config для Git-хостов из local/git-ssh-hosts.conf. # Совместим с macOS bash 3.2 (без declare -A). set -euo pipefail CONFIG_DIR="${CONFIG_DIR:-$HOME/CursorRules/local}" HOSTS_CONFIG="${HOSTS_CONFIG:-$CONFIG_DIR/git-ssh-hosts.conf}" WHAT_IF="${WHAT_IF:-0}" log_step() { printf '[*] %s\n' "$1"; } log_ok() { printf '[+] %s\n' "$1"; } log_warn() { printf '[!] %s\n' "$1"; } cfg_key() { printf '%s' "$1" | tr '.-' '__'; } set_profile_field() { local profile="$1" field="$2" value="$3" eval "__pf_$(cfg_key "$profile")_${field}=\"\$value\"" } get_profile_field() { local profile="$1" field="$2" eval "printf '%s' \"\${__pf_$(cfg_key "$profile")_${field}:-}\"" } set_host_field() { local host="$1" field="$2" value="$3" eval "__hf_$(cfg_key "$host")_${field}=\"\$value\"" } get_host_field() { local host="$1" field="$2" eval "printf '%s' \"\${__hf_$(cfg_key "$host")_${field}:-}\"" } if [[ ! -f "$HOSTS_CONFIG" ]]; then echo "Config not found: $HOSTS_CONFIG" >&2 exit 1 fi SSH_DIR="$HOME/.ssh" SSH_CONFIG="$SSH_DIR/config" mkdir -p "$SSH_DIR" chmod 700 "$SSH_DIR" PROFILE_LIST=() HOST_LIST=() current_section='' current_name='' while IFS= read -r line || [[ -n "$line" ]]; do line="${line%%#*}" line="${line%"${line##*[![:space:]]}"}" [[ -z "$line" ]] && continue if [[ "$line" =~ ^\[profile:(.+)\]$ ]]; then current_section='profile' current_name="${BASH_REMATCH[1]}" PROFILE_LIST+=("$current_name") continue fi if [[ "$line" =~ ^\[host:(.+)\]$ ]]; then current_section='host' current_name="${BASH_REMATCH[1]}" HOST_LIST+=("$current_name") set_host_field "$current_name" HostName '' continue fi if [[ "$line" != *=* ]]; then continue fi key="${line%%=*}" value="${line#*=}" key="${key%"${key##*[![:space:]]}"}" value="${value#"${value%%[![:space:]]*}"}" if [[ "$current_section" == 'profile' ]]; then case "$key" in KeyName|KeyComment|GiteaConfig) set_profile_field "$current_name" "$key" "$value" ;; esac elif [[ "$current_section" == 'host' ]]; then case "$key" in HostName|Port|User|Profile) set_host_field "$current_name" "$key" "$value" ;; esac fi done < "$HOSTS_CONFIG" KEYS_SEEN=() for profile in "${PROFILE_LIST[@]}"; do key_name="$(get_profile_field "$profile" KeyName)" [[ -z "$key_name" ]] && continue seen=0 for existing in "${KEYS_SEEN[@]:-}"; do [[ "$existing" == "$key_name" ]] && seen=1 && break done [[ "$seen" -eq 1 ]] && continue KEYS_SEEN+=("$key_name") key_path="$SSH_DIR/$key_name" comment="$(get_profile_field "$profile" KeyComment)" comment="${comment:-$key_name}" if [[ ! -f "$key_path" ]]; then log_step "Generating $key_name" if [[ "$WHAT_IF" == '1' ]]; then log_warn "WhatIf: ssh-keygen -t ed25519 -f $key_path" else ssh-keygen -t ed25519 -f "$key_path" -N '' -C "$comment" -q chmod 600 "$key_path" log_ok "key $key_name created" fi else log_ok "key $key_name exists" fi done update_ssh_block() { local host_alias="$1" local block="$2" local begin="# BEGIN Setup-SshAccess:$host_alias" local end="# END Setup-SshAccess:$host_alias" local tmp block_file tmp="$(mktemp)" block_file="$(mktemp)" printf '%s\n' "$block" > "$block_file" if [[ -f "$SSH_CONFIG" ]]; then awk -v begin="$begin" -v end="$end" -v blockfile="$block_file" ' $0 == begin { skip=1; next } $0 == end { skip=0; next } skip { next } { print } END { if (blockfile != "") { while ((getline line < blockfile) > 0) { print line } close(blockfile) } } ' "$SSH_CONFIG" > "$tmp" else cp "$block_file" "$tmp" fi rm -f "$block_file" if [[ "$WHAT_IF" != '1' ]]; then mv "$tmp" "$SSH_CONFIG" chmod 600 "$SSH_CONFIG" else rm -f "$tmp" fi } IFS=$'\n' sorted_hosts=($(printf '%s\n' "${HOST_LIST[@]}" | sort)) unset IFS for host_alias in "${sorted_hosts[@]}"; do profile="$(get_host_field "$host_alias" Profile)" key_name="$(get_profile_field "$profile" KeyName)" if [[ -z "$profile" || -z "$key_name" ]]; then echo "Host $host_alias: unknown profile" >&2 exit 1 fi block="# BEGIN Setup-SshAccess:$host_alias Host $host_alias HostName $(get_host_field "$host_alias" HostName) User $(get_host_field "$host_alias" User) Port $(get_host_field "$host_alias" Port) IdentityFile ~/.ssh/$key_name IdentitiesOnly yes AddKeysToAgent yes StrictHostKeyChecking accept-new # END Setup-SshAccess:$host_alias" if [[ "$WHAT_IF" == '1' ]]; then log_step "WhatIf: ssh config block for $host_alias" else update_ssh_block "$host_alias" "$block" log_ok "ssh config: $host_alias" fi done if [[ "$WHAT_IF" != '1' ]]; then eval "$(ssh-agent -s)" >/dev/null for key_name in "${KEYS_SEEN[@]:-}"; do [[ -f "$SSH_DIR/$key_name" ]] && ssh-add "$SSH_DIR/$key_name" >/dev/null 2>&1 || true done fi log_ok 'SSH setup complete'