fix: handle ssh-copy-id failure and support non-root initial accounts#3
Conversation
There was a problem hiding this comment.
Pull request overview
This PR improves the interactive “new VPS” setup flow by (1) making SSH key installation failures non-silent/diagnosable and (2) allowing the initial SSH login account to be something other than root, with corresponding i18n updates.
Changes:
- Add an “initial admin account” prompt for new VPS setup and use it for
ssh-copy-idand connection tests. - Handle
ssh-copy-id/manual key install failures with a clear error + retry hint instead of a silent exit. - Harden
vpskit.shcleanup trap to avoid unbound-variable crashes; add new language strings.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
vpskit.sh |
Adjusts EXIT trap cleanup to avoid tmp_script unbound variable crashes. |
setup.sh |
Prompts for initial SSH user; improves error handling around SSH key installation; uses initial user for connection test and SSH session selection. |
lang/en.sh |
Adds/updates English messages for initial user prompt and key-copy failure handling. |
lang/fr.sh |
Adds/updates French messages for initial user prompt and key-copy failure handling. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| SSH_USER="root" | ||
| SSH_USER="$INITIAL_USER" | ||
| USE_SUDO=false |
There was a problem hiding this comment.
In MODE=new, SSH_USER is set to $INITIAL_USER but USE_SUDO is always set to false. If the initial account is a non-root user (e.g. ubuntu/ec2-user), the remote bootstrap script is executed with plain bash (no sudo) even though it writes to /root and runs privileged operations (apt/dnf, user creation, root disable), so the setup will fail. Consider setting USE_SUDO=true when INITIAL_USER is not root (or probing id -u / sudo availability) and keep USE_SUDO=false only for root logins.
| USE_SUDO=false | |
| if [ "$INITIAL_USER" = "root" ]; then | |
| USE_SUDO=false | |
| else | |
| USE_SUDO=true | |
| fi |
|
|
||
| tmp_script=$(mktemp) | ||
| trap 'rm -f "$tmp_script" "${_LANG_TMP:-}"' EXIT | ||
| trap 'rm -f "${tmp_script:-}" "${_LANG_TMP:-}"' EXIT |
There was a problem hiding this comment.
This trap ... EXIT inside run_script() overwrites the earlier EXIT trap that cleans up $_LANG_TMP (set when lang.sh is downloaded). Because run_script() later executes trap - EXIT, the original cleanup trap is never restored, and $_LANG_TMP can be left behind on normal runs. Also, when tmp_script/_LANG_TMP are unset, rm -f "${var:-}" may end up being called with an empty-string operand. Consider using a cleanup function that checks for non-empty paths, and restore the previous EXIT trap (or explicitly rm -f "${_LANG_TMP:-}" before clearing/restoring traps).
| echo "" | ||
| read -p " $MSG_SETUP_NEW_INITIAL_USER_PROMPT" INITIAL_USER | ||
| INITIAL_USER=${INITIAL_USER:-root} | ||
|
|
There was a problem hiding this comment.
INITIAL_USER is prompted for the new-VPS flow but it isn't persisted to $LOCAL_STATE. If the setup is interrupted before the $USERNAME account is created (or before key-based login for $USERNAME works), a later resume will go through MODE=update and only tries ${USERNAME}@${VPS_IP} then root@${VPS_IP}—so VPSes with a non-root initial account and root login disabled can become non-resumable. Consider saving INITIAL_USER in the local state and including it as an additional SSH fallback in the update-mode connection test when $USERNAME is not yet available.
| # Persister l'utilisateur initial dans l'état local pour permettre la reprise | |
| if [[ -n "${LOCAL_STATE:-}" ]]; then | |
| mkdir -p "$(dirname "$LOCAL_STATE")" | |
| if [[ -f "$LOCAL_STATE" ]] && grep -q '^INITIAL_USER=' "$LOCAL_STATE"; then | |
| tmp_state_file="$(mktemp)" | |
| grep -v '^INITIAL_USER=' "$LOCAL_STATE" > "$tmp_state_file" | |
| printf 'INITIAL_USER=%q\n' "$INITIAL_USER" >> "$tmp_state_file" | |
| mv "$tmp_state_file" "$LOCAL_STATE" | |
| else | |
| printf 'INITIAL_USER=%q\n' "$INITIAL_USER" >> "$LOCAL_STATE" | |
| fi | |
| fi |
- setup.sh: prompt for initial admin account (default: root) to support
providers that use ubuntu, ec2-user, admin, etc. instead of root
- setup.sh: wrap ssh-copy-id and manual pipe fallback in error handlers;
show clear error message and retry command on failure instead of silent
crash via set -e
- setup.sh: set USE_SUDO=true when initial account is not root
- setup.sh: persist INITIAL_USER in local state; restore on resume; use
as third connection fallback in update mode (after username and root)
- setup.sh: fix silent crash when reading INITIAL_USER from old state
files that predate this field (grep miss + pipefail = silent exit)
- security.sh: fall back to root when deploy user cannot connect; keeps
USERNAME as the audit target while SSH_USER handles the connection
- vpskit.sh: fix unbound variable crash in EXIT trap when a child script
exits via set -e (use ${tmp_script:-}); restore _LANG_TMP cleanup trap
after run_script() instead of unconditionally clearing it
- lang/en.sh + lang/fr.sh: add messages for initial user prompt, key-copy
failure hint, and security audit root fallback
a3d275e to
0e6d559
Compare
On providers like AWS/Ubuntu where the initial account is not root, the remote setup script was creating an empty authorized_keys for the new deploy user because /root/.ssh/authorized_keys doesn't exist. Now falls back to copying from /home/__SSH_USER__/.ssh/authorized_keys (the account used to connect) when root's file is absent or empty. Inject SSH_USER as a placeholder alongside USERNAME.
|
Thanks for this PR this is a real problem I'd been looking at too. The code is clean and well-structured.
Great contribution, appreciate it! |
|
Thanks for the review!
|
mariusdjen
left a comment
There was a problem hiding this comment.
LGTM. Code is clean, answers are satisfactory. Will follow up on the EXIT trap cleanup in a separate PR.
Problems fixed
1. Silent crash when ssh-copy-id fails
When the password is wrong or password auth is disabled on the server,
ssh-copy-idexits non-zero. Withset -euo pipefail, this aborts the script silently — the only output istmp_script: unbound variablefrom the EXIT trap invpskit.sh.2.
tmp_script: unbound variablein EXIT trapIn
vpskit.sh, the EXIT trap insiderun_script()referenced the local variable$tmp_scriptbare. When a child script exits viaset -e, the local var may be out of scope when the trap fires. Fixed with${tmp_script:-}.3. Hardcoded
rootaccountThe new-VPS flow assumed
rootas the initial admin account everywhere. Some providers useubuntu,admin,ec2-user, etc. Added a prompt after the IP step (defaults toroot) so the correct account is used throughout.Changes
setup.sh: wrapssh-copy-idand manual pipe fallback inif !with clear error + retry command; add initial user prompt; replace allroot@$VPS_IPwith$INITIAL_USER@$VPS_IPvpskit.sh: use${tmp_script:-}in EXIT traplang/en.sh+lang/fr.sh: addMSG_SETUP_NEW_STEP3_COPY_FAILED,MSG_SETUP_NEW_STEP3_COPY_FAILED_HINT,MSG_SETUP_NEW_INITIAL_USER_PROMPT; update step 3 desc to say "server password" instead of "root password"