@@ -60,20 +60,54 @@ function _ci_ssh_discover_files() {
6060 cand+=(/root/.ssh/* .pub)
6161 cand+=(/etc/ssh/authorized_keys /etc/ssh/authorized_keys.d/* )
6262 shopt -u nullglob
63- printf ' %s\n ' " ${cand[@]} "
63+ printf ' %s\0 ' " ${cand[@]} "
6464}
6565
6666# ------------------------------------------------------------------------------
67- # _ci_ssh_count_keys - Counts available SSH keys on the system
68- # ------------------------------------------------------------------------------
69- function _ci_ssh_count_keys() {
70- local count=0
71- while IFS= read -r file; do
72- [[ -f " $file " && -r " $file " ]] || continue
73- local keys=$( _ci_ssh_extract_keys_from_file " $file " | wc -l)
74- count=$(( count + keys))
75- done < <( _ci_ssh_discover_files)
76- echo " $count "
67+ # _ci_ssh_build_choices - Builds whiptail checklist from SSH key files
68+ #
69+ # Sets: CI_SSH_CHOICES (array), CI_SSH_COUNT (int), CI_SSH_MAPFILE (path)
70+ # ------------------------------------------------------------------------------
71+ function _ci_ssh_build_choices() {
72+ local -a files=(" $@ " )
73+ CI_SSH_CHOICES=()
74+ CI_SSH_COUNT=0
75+ CI_SSH_MAPFILE=" $( mktemp) "
76+ local id key typ fp cmt base
77+
78+ for f in " ${files[@]} " ; do
79+ [[ -f " $f " && -r " $f " ]] || continue
80+ base=" $( basename -- " $f " ) "
81+ # Skip known_hosts and private keys
82+ case " $base " in
83+ known_hosts | known_hosts.* | config) continue ;;
84+ id_* ) [[ " $f " != * .pub ]] && continue ;;
85+ esac
86+
87+ while IFS= read -r key; do
88+ [[ -n " $key " ]] || continue
89+
90+ typ=" "
91+ fp=" "
92+ cmt=" "
93+ read -r _typ _b64 _cmt <<< " $key"
94+ typ=" ${_typ:- key} "
95+ cmt=" ${_cmt:- } "
96+
97+ # Get fingerprint via ssh-keygen if available
98+ if command -v ssh-keygen > /dev/null 2>&1 ; then
99+ fp=" $( printf ' %s\n' " $key " | ssh-keygen -lf - 2> /dev/null | awk ' {print $2}' ) "
100+ fi
101+
102+ # Shorten long comments
103+ [[ ${# cmt} -gt 40 ]] && cmt=" ${cmt: 0: 37} ..."
104+
105+ CI_SSH_COUNT=$(( CI_SSH_COUNT + 1 ))
106+ id=" K${CI_SSH_COUNT} "
107+ echo " ${id} |${key} " >> " $CI_SSH_MAPFILE "
108+ CI_SSH_CHOICES+=(" $id " " [$typ ] ${fp: +$fp }${cmt: +$cmt } — ${base} " " OFF" )
109+ done < <( _ci_ssh_extract_keys_from_file " $f " )
110+ done
77111}
78112
79113# ------------------------------------------------------------------------------
@@ -84,35 +118,49 @@ function _ci_ssh_count_keys() {
84118# ------------------------------------------------------------------------------
85119function configure_cloudinit_ssh_keys() {
86120 local backtitle=" Proxmox VE Helper Scripts"
87- local default_key_count=$( _ci_ssh_count_keys)
88121 local ssh_key_mode
89122
90123 # Create temp file for selected keys
91124 CLOUDINIT_SSH_KEYS_TEMP=" $( mktemp) "
125+ : > " $CLOUDINIT_SSH_KEYS_TEMP "
126+
127+ # Discover keys and build choices
128+ IFS=$' \0' read -r -d ' ' -a _def_files < <( _ci_ssh_discover_files && printf ' \0' )
129+ _ci_ssh_build_choices " ${_def_files[@]} "
130+ local default_key_count=" $CI_SSH_COUNT "
92131
93132 if [[ " $default_key_count " -gt 0 ]]; then
94133 ssh_key_mode=$( whiptail --backtitle " $backtitle " --title " SSH KEY SOURCE" --menu \
95134 " Provision SSH keys for Cloud-Init VM:" 14 72 4 \
96- " host " " Import all keys from host ( $ default_key_count found )" \
135+ " found " " Select from detected keys ( ${ default_key_count} )" \
97136 " manual" " Paste a single public key" \
98- " file " " Specify path to authorized_keys file " \
137+ " folder " " Scan another folder (path or glob) " \
99138 " none" " No SSH keys (password auth only)" 3>&1 1>&2 2>&3 ) || return 1
100139 else
101140 ssh_key_mode=$( whiptail --backtitle " $backtitle " --title " SSH KEY SOURCE" --menu \
102141 " No host keys detected. Choose:" 12 72 3 \
103142 " manual" " Paste a single public key" \
104- " file " " Specify path to authorized_keys file " \
143+ " folder " " Scan another folder (path or glob) " \
105144 " none" " No SSH keys (password auth only)" 3>&1 1>&2 2>&3 ) || return 1
106145 fi
107146
108147 case " $ssh_key_mode " in
109- host)
110- # Import all keys from host
111- while IFS= read -r file; do
112- _ci_ssh_extract_keys_from_file " $file " >> " $CLOUDINIT_SSH_KEYS_TEMP "
113- done < <( _ci_ssh_discover_files)
114- local imported=$( wc -l < " $CLOUDINIT_SSH_KEYS_TEMP " )
115- echo -e " ${ROOTSSH:- 🔑 }${BOLD}${DGN} SSH Keys: ${BGN}${imported} keys imported from host${CL} "
148+ found)
149+ # Show checklist with individual keys
150+ local selection
151+ selection=$( whiptail --backtitle " $backtitle " --title " SELECT SSH KEYS" \
152+ --checklist " Select one or more keys to import:" 20 140 10 " ${CI_SSH_CHOICES[@]} " 3>&1 1>&2 2>&3 ) || return 1
153+
154+ for tag in $selection ; do
155+ tag=" ${tag% \" } "
156+ tag=" ${tag# \" } "
157+ local line
158+ line=$( grep -E " ^${tag} \|" " $CI_SSH_MAPFILE " | head -n1 | cut -d' |' -f2-)
159+ [[ -n " $line " ]] && printf ' %s\n' " $line " >> " $CLOUDINIT_SSH_KEYS_TEMP "
160+ done
161+ local imported
162+ imported=$( wc -l < " $CLOUDINIT_SSH_KEYS_TEMP " )
163+ echo -e " ${ROOTSSH:- 🔑 }${BOLD}${DGN} SSH Keys: ${BGN}${imported} key(s) selected${CL} "
116164 ;;
117165 manual)
118166 local pubkey
@@ -124,33 +172,53 @@ function configure_cloudinit_ssh_keys() {
124172 else
125173 echo -e " ${ROOTSSH:- 🔑 }${BOLD}${DGN} SSH Keys: ${BGN} none (empty input)${CL} "
126174 CLOUDINIT_SSH_KEYS=" "
127- rm -f " $CLOUDINIT_SSH_KEYS_TEMP "
175+ rm -f " $CLOUDINIT_SSH_KEYS_TEMP " " $CI_SSH_MAPFILE " 2> /dev/null
128176 return 0
129177 fi
130178 ;;
131- file)
132- local keyfile
133- keyfile=$( whiptail --backtitle " $backtitle " --title " SSH KEY FILE" \
134- --inputbox " Enter path to authorized_keys file:" 10 60 " /root/.ssh/authorized_keys" 3>&1 1>&2 2>&3 ) || return 1
135- if [[ -f " $keyfile " ]]; then
136- _ci_ssh_extract_keys_from_file " $keyfile " > " $CLOUDINIT_SSH_KEYS_TEMP "
137- local imported=$( wc -l < " $CLOUDINIT_SSH_KEYS_TEMP " )
138- echo -e " ${ROOTSSH:- 🔑 }${BOLD}${DGN} SSH Keys: ${BGN}${imported} keys from ${keyfile}${CL} "
139- else
140- echo -e " ${ROOTSSH:- 🔑 }${BOLD}${RD} File not found: ${keyfile}${CL} "
141- CLOUDINIT_SSH_KEYS=" "
142- rm -f " $CLOUDINIT_SSH_KEYS_TEMP "
143- return 1
179+ folder)
180+ local glob_path
181+ glob_path=$( whiptail --backtitle " $backtitle " --title " SCAN FOLDER/GLOB" \
182+ --inputbox " Enter a folder or glob to scan (e.g. /root/.ssh/*.pub):" 10 72 3>&1 1>&2 2>&3 ) || return 1
183+ if [[ -n " $glob_path " ]]; then
184+ shopt -s nullglob
185+ local -a _scan_files=($glob_path )
186+ shopt -u nullglob
187+ if [[ " ${# _scan_files[@]} " -gt 0 ]]; then
188+ _ci_ssh_build_choices " ${_scan_files[@]} "
189+ if [[ " $CI_SSH_COUNT " -gt 0 ]]; then
190+ local folder_selection
191+ folder_selection=$( whiptail --backtitle " $backtitle " --title " SELECT FOLDER KEYS" \
192+ --checklist " Select key(s) to import:" 20 140 10 " ${CI_SSH_CHOICES[@]} " 3>&1 1>&2 2>&3 ) || return 1
193+ for tag in $folder_selection ; do
194+ tag=" ${tag% \" } "
195+ tag=" ${tag# \" } "
196+ local line
197+ line=$( grep -E " ^${tag} \|" " $CI_SSH_MAPFILE " | head -n1 | cut -d' |' -f2-)
198+ [[ -n " $line " ]] && printf ' %s\n' " $line " >> " $CLOUDINIT_SSH_KEYS_TEMP "
199+ done
200+ local imported
201+ imported=$( wc -l < " $CLOUDINIT_SSH_KEYS_TEMP " )
202+ echo -e " ${ROOTSSH:- 🔑 }${BOLD}${DGN} SSH Keys: ${BGN}${imported} key(s) from folder${CL} "
203+ else
204+ whiptail --backtitle " $backtitle " --msgbox " No keys found in: $glob_path " 8 60
205+ fi
206+ else
207+ whiptail --backtitle " $backtitle " --msgbox " Path/glob returned no files." 8 60
208+ fi
144209 fi
145210 ;;
146211 none | * )
147212 echo -e " ${ROOTSSH:- 🔑 }${BOLD}${DGN} SSH Keys: ${BGN} none (password auth only)${CL} "
148213 CLOUDINIT_SSH_KEYS=" "
149- rm -f " $CLOUDINIT_SSH_KEYS_TEMP "
214+ rm -f " $CLOUDINIT_SSH_KEYS_TEMP " " $CI_SSH_MAPFILE " 2> /dev/null
150215 return 0
151216 ;;
152217 esac
153218
219+ # Cleanup mapfile
220+ rm -f " $CI_SSH_MAPFILE " 2> /dev/null
221+
154222 # Set the variable for setup_cloud_init to use
155223 if [[ -s " $CLOUDINIT_SSH_KEYS_TEMP " ]]; then
156224 CLOUDINIT_SSH_KEYS=" $CLOUDINIT_SSH_KEYS_TEMP "
0 commit comments