fix(reboot): add windows99 vmx source locate check
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m17s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled

This commit is contained in:
Your Name
2026-07-03 12:20:34 +08:00
parent df42c32b22
commit 5e25565e1b
7 changed files with 368 additions and 19 deletions

View File

@@ -0,0 +1,152 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TARGET_HOST="${WINDOWS99_HOST:-192.168.0.99}"
TARGET_USER="${WINDOWS99_USER:-wooo}"
TARGET_PORT="${WINDOWS99_PORT:-22}"
VIA_HOST="${WINDOWS99_VIA_HOST:-}"
CONNECT_TIMEOUT="${WINDOWS99_CONNECT_TIMEOUT:-8}"
KNOWN_HOSTS_FILE="${WINDOWS99_KNOWN_HOSTS_FILE:-/tmp/awoooi-windows99-vmx-locate-known_hosts}"
LOCAL_LOCATE_SCRIPT="${WINDOWS99_VMX_LOCATE_SCRIPT:-${SCRIPT_DIR}/windows99-vmx-source-locate-check.ps1}"
INCLUDE_IDENTITY_METADATA=0
usage() {
cat <<'USAGE'
usage: collect-windows99-vmx-source-locate-check.sh [options]
Runs the Windows99 VMX source locate check through public-key SSH only. This is
check-mode evidence: it does not read secrets, write files, start VMs, restart
services, apply registry changes, or reboot hosts.
Options:
--host HOST Windows99 host. Default: 192.168.0.99
--user USER Windows99 SSH user. Default: wooo
--port PORT Windows99 SSH port. Default: 22
--via-host USER@HOST Optional SSH jump host, e.g. wooo@192.168.0.110
--timeout SECONDS SSH connect timeout. Default: 8
--include-identity-metadata Read bounded VMX identity keys after path-only scan
-h, --help Show this help
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
--host)
shift
TARGET_HOST="${1:?--host requires a value}"
;;
--user)
shift
TARGET_USER="${1:?--user requires a value}"
;;
--port)
shift
TARGET_PORT="${1:?--port requires a value}"
;;
--via-host)
shift
VIA_HOST="${1:?--via-host requires a value}"
;;
--timeout)
shift
CONNECT_TIMEOUT="${1:?--timeout requires a value}"
;;
--include-identity-metadata)
INCLUDE_IDENTITY_METADATA=1
;;
-h|--help)
usage
exit 0
;;
*)
printf 'error=unknown_argument:%s\n' "$1" >&2
usage >&2
exit 64
;;
esac
shift
done
if [[ ! -f "$LOCAL_LOCATE_SCRIPT" ]]; then
printf 'schema_version=windows99_vmx_source_locate_collector_v1\n'
printf 'status=blocked_local_locate_script_missing\n'
printf 'local_locate_script=%s\n' "$LOCAL_LOCATE_SCRIPT"
printf 'secret_value_read=false\n'
printf 'remote_write_performed=false\n'
printf 'vm_power_change_performed=false\n'
exit 75
fi
payload_file="$(mktemp "${TMPDIR:-/tmp}/awoooi-windows99-vmx-locate.XXXXXX")"
trap 'rm -f "$payload_file"' EXIT
python3 - "$LOCAL_LOCATE_SCRIPT" "$INCLUDE_IDENTITY_METADATA" "$payload_file" <<'PY'
import pathlib
import sys
path = pathlib.Path(sys.argv[1])
include_identity = sys.argv[2] == "1"
output = pathlib.Path(sys.argv[3])
script = path.read_text(encoding="utf-8")
if include_identity:
script = script.replace(
" [switch]$IncludeIdentityMetadata\n)",
" [switch]$IncludeIdentityMetadata = $true\n)",
)
output.write_text(script, encoding="utf-8")
PY
SSH_OPTS=(
-o BatchMode=yes
-o PreferredAuthentications=publickey
-o PubkeyAuthentication=yes
-o PasswordAuthentication=no
-o KbdInteractiveAuthentication=no
-o NumberOfPasswordPrompts=0
-o ConnectTimeout="${CONNECT_TIMEOUT}"
-o ConnectionAttempts=1
-o StrictHostKeyChecking=no
-o UserKnownHostsFile="${KNOWN_HOSTS_FILE}"
)
bootstrap_command="$(
python3 - <<'PY'
import base64
bootstrap = "& ([scriptblock]::Create([Console]::In.ReadToEnd()))"
print(base64.b64encode(bootstrap.encode("utf-16le")).decode("ascii"))
PY
)"
REMOTE_PS=(
powershell
-NoProfile
-ExecutionPolicy
Bypass
-EncodedCommand
"$bootstrap_command"
)
quote_words() {
printf '%q ' "$@"
}
printf 'schema_version=windows99_vmx_source_locate_collector_v1\n'
printf 'target_host=%s\n' "$TARGET_HOST"
printf 'target_user=%s\n' "$TARGET_USER"
printf 'via_host=%s\n' "${VIA_HOST:-none}"
printf 'include_identity_metadata=%s\n' "$INCLUDE_IDENTITY_METADATA"
printf 'secret_value_read=false\n'
printf 'remote_write_performed=false\n'
printf 'vm_power_change_performed=false\n'
printf 'windows_service_restart_performed=false\n'
printf 'windows_registry_apply_performed=false\n'
if [[ -n "$VIA_HOST" ]]; then
inner_ssh=(ssh "${SSH_OPTS[@]}" -p "$TARGET_PORT" "${TARGET_USER}@${TARGET_HOST}" "${REMOTE_PS[@]}")
inner_command="$(quote_words "${inner_ssh[@]}")"
ssh "${SSH_OPTS[@]}" "$VIA_HOST" "bash -lc $(printf '%q' "$inner_command")" <"$payload_file"
else
ssh "${SSH_OPTS[@]}" -p "$TARGET_PORT" "${TARGET_USER}@${TARGET_HOST}" "${REMOTE_PS[@]}" <"$payload_file"
fi

View File

@@ -0,0 +1,52 @@
from __future__ import annotations
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
PS1 = ROOT / "scripts/reboot-recovery/windows99-vmx-source-locate-check.ps1"
COLLECTOR = (
ROOT / "scripts/reboot-recovery/collect-windows99-vmx-source-locate-check.sh"
)
def test_windows99_vmx_source_locate_check_is_no_write_check_mode() -> None:
text = PS1.read_text(encoding="utf-8")
assert "AWOOOI_WINDOWS99_VMX_LOCATE_CHECK=1" in text
assert 'ProgressPreference = "SilentlyContinue"' in text
assert "secret_value_read=false" in text
assert "remote_write_performed=false" in text
assert "vm_power_change_performed=false" in text
assert "expected_vmx_exists" in text
assert "candidate_vmx_count" in text
assert "VMX_IDENTITY_SUMMARY" in text
assert "identity_unassigned_ubuntu_candidate_count" in text
assert "single_identity_unassigned_ubuntu_candidate_requires_confirmation" in text
assert "locate_status=" in text
assert "Start-VM" not in text
assert "vmrun" not in text
assert "Set-Content" not in text
assert "New-Item" not in text
assert "Register-ScheduledTask" not in text
assert "Set-ItemProperty" not in text
def test_windows99_vmx_source_locate_collector_uses_publickey_only() -> None:
text = COLLECTOR.read_text(encoding="utf-8")
assert "windows99_vmx_source_locate_collector_v1" in text
assert "BatchMode=yes" in text
assert "PasswordAuthentication=no" in text
assert "KbdInteractiveAuthentication=no" in text
assert "NumberOfPasswordPrompts=0" in text
assert "--via-host" in text
assert "--include-identity-metadata" in text
assert "[Console]::In.ReadToEnd()" in text
assert "bootstrap_command" in text
assert "payload_file" in text
assert "secret_value_read=false" in text
assert "remote_write_performed=false" in text
assert "vm_power_change_performed=false" in text
assert "scp " not in text
assert "sudo " not in text

View File

@@ -0,0 +1,122 @@
param(
[string]$Alias = "111",
[string]$ExpectedVmxPath = "D:\Documents\Virtual Machines\192.168.0.111_Ubuntu_64-bit\192.168.0.111_Ubuntu_64-bit.vmx",
[string[]]$DiscoveryRoot = @(
"D:\Documents\Virtual Machines",
"D:\Downloads",
"D:\VMs",
"E:\VMs",
"C:\VMs",
"C:\Users\Public\Documents\Virtual Machines"
),
[int]$MaxResults = 120,
[switch]$IncludeIdentityMetadata
)
$ErrorActionPreference = "SilentlyContinue"
$ProgressPreference = "SilentlyContinue"
$identityPatterns = "^(displayName|guestOS|ethernet0\.addressType|ethernet0\.generatedAddress|uuid\.bios|uuid\.location|scsi0:0\.fileName|ide1:0\.fileName)\s*="
Write-Output "AWOOOI_WINDOWS99_VMX_LOCATE_CHECK=1"
Write-Output "target_alias=$Alias"
Write-Output "secret_value_read=false"
Write-Output "remote_write_performed=false"
Write-Output "vm_power_change_performed=false"
Write-Output "windows_service_restart_performed=false"
Write-Output "windows_registry_apply_performed=false"
Write-Output "identity_metadata_read=$([int][bool]$IncludeIdentityMetadata)"
Write-Output "expected_vmx_path=$ExpectedVmxPath"
Write-Output "expected_vmx_exists=$([int](Test-Path -LiteralPath $ExpectedVmxPath))"
$candidateCount = 0
$aliasHintCount = 0
$unassignedUbuntuCandidateCount = 0
$identityTargetHintCount = 0
$identityUnassignedUbuntuCandidateCount = 0
foreach ($root in $DiscoveryRoot) {
$rootExists = Test-Path -LiteralPath $root
Write-Output "ROOT path=$root exists=$([int]$rootExists)"
if (-not $rootExists) {
continue
}
$files = @(
Get-ChildItem -LiteralPath $root -Recurse -Filter "*.vmx" -File -ErrorAction SilentlyContinue |
Select-Object -First $MaxResults
)
foreach ($file in $files) {
$candidateCount += 1
$aliasHint = [int]($file.FullName -match [regex]::Escape($Alias))
$ubuntuHint = [int]($file.FullName -match "Ubuntu|ubuntu")
if ($aliasHint -eq 1) {
$aliasHintCount += 1
}
if ($aliasHint -eq 0 -and $ubuntuHint -eq 1) {
$unassignedUbuntuCandidateCount += 1
}
Write-Output "VMX_FILE path=$($file.FullName) alias_hint=$aliasHint ubuntu_hint=$ubuntuHint size=$($file.Length)"
if ($IncludeIdentityMetadata) {
$identityLines = @(
Select-String -LiteralPath $file.FullName -Pattern $identityPatterns -ErrorAction SilentlyContinue |
Select-Object -First 20
)
$displayName = ""
foreach ($match in $identityLines) {
$line = $match.Line -replace "`r", ""
if ($line -match "^displayName\s*=\s*`"(.+)`"") {
$displayName = $Matches[1]
}
Write-Output "VMX_IDENTITY_FIELD path=$($file.FullName) $line"
}
$knownNonTargetAlias = [int]($displayName -match "192\.168\.0\.(110|112|120|121|188)")
$targetAliasIdentityHint = [int]($displayName -match "192\.168\.0\.$Alias")
$genericUnassignedIdentityHint = [int](
$ubuntuHint -eq 1 -and
$knownNonTargetAlias -eq 0 -and
$targetAliasIdentityHint -eq 0
)
if ($targetAliasIdentityHint -eq 1) {
$identityTargetHintCount += 1
}
if ($genericUnassignedIdentityHint -eq 1) {
$identityUnassignedUbuntuCandidateCount += 1
}
Write-Output "VMX_IDENTITY_SUMMARY path=$($file.FullName) display_name=$displayName known_non_target_alias=$knownNonTargetAlias target_alias_identity_hint=$targetAliasIdentityHint generic_unassigned_identity_hint=$genericUnassignedIdentityHint"
}
}
}
Write-Output "candidate_vmx_count=$candidateCount"
Write-Output "alias_hint_candidate_count=$aliasHintCount"
Write-Output "unassigned_ubuntu_candidate_count=$unassignedUbuntuCandidateCount"
Write-Output "identity_target_hint_count=$identityTargetHintCount"
Write-Output "identity_unassigned_ubuntu_candidate_count=$identityUnassignedUbuntuCandidateCount"
if ((Test-Path -LiteralPath $ExpectedVmxPath)) {
Write-Output "locate_status=expected_vmx_path_present"
Write-Output "safe_next_step=rerun_windows99_vmware_verify_collector_no_secret"
} elseif ($aliasHintCount -gt 0) {
Write-Output "locate_status=alias_hint_candidate_found"
Write-Output "safe_next_step=build_scoped_relink_dry_run_for_alias_hint_candidate_then_post_verify"
} elseif ($IncludeIdentityMetadata -and $identityTargetHintCount -gt 0) {
Write-Output "locate_status=identity_target_hint_candidate_found"
Write-Output "safe_next_step=build_scoped_relink_dry_run_for_identity_hint_candidate_then_post_verify"
} elseif ($IncludeIdentityMetadata -and $identityUnassignedUbuntuCandidateCount -eq 1) {
Write-Output "locate_status=single_identity_unassigned_ubuntu_candidate_requires_confirmation"
Write-Output "safe_next_step=confirm_single_unassigned_ubuntu_candidate_is_target_alias_before_scoped_relink_dry_run"
} elseif ($IncludeIdentityMetadata -and $identityUnassignedUbuntuCandidateCount -gt 1) {
Write-Output "locate_status=multiple_identity_unassigned_ubuntu_candidates_requires_confirmation"
Write-Output "safe_next_step=confirm_candidate_identity_before_any_relink_or_restore"
} elseif ($unassignedUbuntuCandidateCount -eq 1) {
Write-Output "locate_status=single_unassigned_ubuntu_candidate_requires_identity_confirmation"
Write-Output "safe_next_step=confirm_unassigned_ubuntu_candidate_identity_before_scoped_relink_dry_run"
} elseif ($unassignedUbuntuCandidateCount -gt 1) {
Write-Output "locate_status=multiple_unassigned_ubuntu_candidates_requires_identity_confirmation"
Write-Output "safe_next_step=confirm_candidate_identity_before_any_relink_or_restore"
} else {
Write-Output "locate_status=no_candidate_found"
Write-Output "safe_next_step=restore_vmx_source_from_verified_backup_artifact_then_post_verify_no_vm_power_change"
}