Skip to content

Instantly share code, notes, and snippets.

@njames
Last active February 8, 2026 22:18
Show Gist options
  • Select an option

  • Save njames/7f259f07dc1b40062798b8fb1cb7c706 to your computer and use it in GitHub Desktop.

Select an option

Save njames/7f259f07dc1b40062798b8fb1cb7c706 to your computer and use it in GitHub Desktop.
create a config file for a VM ssh target that changes IP addresses
#!/usr/bin/env bash
set -euo pipefail
# hv-sshconf.sh - Create per-host SSH config snippet from Hyper-V VM IPv4 (via Windows PowerShell)
#
# Usage:
# hv-sshconf.sh <VM_NAME> [HOST_ALIAS] [IDENTITY_FILE]
#
# Examples:
# hv-sshconf.sh ub-app02
# hv-sshconf.sh "Ubuntu 24.04" ubuntu-2404 ~/.ssh/keys/ubuntu_ed25519
#
# Notes:
# - Runs in WSL. Calls Windows PowerShell (pwsh.exe/powershell.exe) to query Hyper-V.
# - Ensures ~/.ssh/config contains an Include line for ~/.ssh/conf.d/*.conf at TOP of file.
# - Writes ~/.ssh/conf.d/<HOST_ALIAS>.conf with HostName set to VM IPv4.
VM_NAME="${1:-}"
HOST_ALIAS="${2:-}"
IDENTITY_FILE="${3:-}"
if [[ -z "$VM_NAME" ]]; then
echo "Usage: $0 <VM_NAME> [HOST_ALIAS] [IDENTITY_FILE]" >&2
exit 1
fi
if [[ -z "$HOST_ALIAS" ]]; then
HOST_ALIAS="$VM_NAME"
fi
# Expand ~ if used
if [[ -z "${IDENTITY_FILE}" ]]; then
IDENTITY_FILE="$HOME/.ssh/id_ed25519"
else
IDENTITY_FILE="${IDENTITY_FILE/#\~/$HOME}"
fi
SSH_DIR="$HOME/.ssh"
CONF_DIR="$SSH_DIR/conf.d"
CONF_FILE="$CONF_DIR/${HOST_ALIAS}.conf"
MAIN_CONFIG="$SSH_DIR/config"
INCLUDE_LINE="Include $CONF_DIR/*.conf"
mkdir -p "$CONF_DIR"
touch "$MAIN_CONFIG"
# Permissions (OpenSSH can ignore config if too permissive)
chmod 700 "$SSH_DIR" "$CONF_DIR"
chmod 600 "$MAIN_CONFIG" || true
# Prefer pwsh.exe if available, otherwise powershell.exe
PS_BIN="powershell.exe"
command -v pwsh.exe >/dev/null 2>&1 && PS_BIN="pwsh.exe"
# Query Hyper-V for IPv4, excluding IPv6 and APIPA (169.*)
IPV4="$("$PS_BIN" -NoProfile -Command "
\$ips = (Get-VMNetworkAdapter -VMName '$VM_NAME').IPAddresses
\$ips |
Where-Object { \$_ -match '^\d{1,3}(\.\d{1,3}){3}$' -and \$_ -notlike '169.*' } |
Select-Object -First 1
" | tr -d '\r')"
if [[ -z "$IPV4" ]]; then
echo "Could not determine IPv4 for VM '$VM_NAME'." >&2
echo "Try from Windows:" >&2
echo " $PS_BIN -NoProfile -Command \"(Get-VMNetworkAdapter -VMName '$VM_NAME').IPAddresses\"" >&2
exit 2
fi
# --- Ensure Include line exists and is at the TOP of ~/.ssh/config ---
# Strategy:
# 1) Remove any existing matching Include lines anywhere in file (case-insensitive, tolerant of whitespace).
# 2) Prepend the Include line to the top.
# 3) Keep the rest of the config intact.
tmp="$(mktemp)"
# Remove CRLF just in case, then remove any Include that already points at conf.d/*.conf
# (We match any line starting with "Include" containing "conf.d" and "*.conf".)
sed -e 's/\r$//' \
-e '/^[[:space:]]*Include[[:space:]].*conf\.d\/\*\.conf[[:space:]]*$/Id' \
"$MAIN_CONFIG" > "$tmp"
# Prepend Include line and a blank line (only if config not empty already)
{
echo "$INCLUDE_LINE"
echo ""
cat "$tmp"
} > "$MAIN_CONFIG"
rm -f "$tmp"
chmod 600 "$MAIN_CONFIG" || true
# --- Write the per-host snippet ---
cat > "$CONF_FILE" <<EOF
Host ${HOST_ALIAS}
User njames
HostName ${IPV4}
PreferredAuthentications publickey
IdentityFile ${IDENTITY_FILE}
EOF
# Normalize line endings and permissions
sed -i 's/\r$//' "$CONF_FILE"
chmod 600 "$CONF_FILE"
echo "Wrote: $CONF_FILE"
echo "VM: $VM_NAME"
echo "Host: $HOST_ALIAS"
echo "IPv4: $IPV4"
echo ""
echo "Verify:"
echo " ssh -G ${HOST_ALIAS} | grep -iE '^(hostname|user|identityfile)\\b'"
echo "Test:"
echo " ssh ${HOST_ALIAS}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment