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
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:
152
scripts/reboot-recovery/collect-windows99-vmx-source-locate-check.sh
Executable file
152
scripts/reboot-recovery/collect-windows99-vmx-source-locate-check.sh
Executable 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
|
||||
@@ -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
|
||||
122
scripts/reboot-recovery/windows99-vmx-source-locate-check.ps1
Normal file
122
scripts/reboot-recovery/windows99-vmx-source-locate-check.ps1
Normal 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"
|
||||
}
|
||||
Reference in New Issue
Block a user