Last active
February 8, 2026 22:18
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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