fix(reboot): support windows99 verifier via host
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 1m1s
CD Pipeline / build-and-deploy (push) Successful in 4m28s
CD Pipeline / post-deploy-checks (push) Successful in 1m45s

This commit is contained in:
Your Name
2026-07-03 13:53:28 +08:00
parent 41454d30bd
commit 30c89a0cab
7 changed files with 100 additions and 19 deletions

View File

@@ -4,6 +4,7 @@ set -u
MODE="check"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TARGET_HOST="${WINDOWS99_HOST:-192.168.0.99}"
VIA_HOST="${WINDOWS99_VIA_HOST:-}"
CONNECT_TIMEOUT="${WINDOWS99_CONNECT_TIMEOUT:-3}"
SSH_TIMEOUT="${WINDOWS99_SSH_TIMEOUT:-3}"
REMOTE_VERIFY_TIMEOUT="${WINDOWS99_REMOTE_VERIFY_TIMEOUT:-45}"
@@ -43,7 +44,7 @@ if ! is_positive_int "${MAX_AUTH_USERS}"; then
fi
usage() {
printf '%s\n' "usage: $0 [--check|--collect] [--host HOST] [--users 'u1 u2'] [--timeout SECONDS] [--max-auth-users N]"
printf '%s\n' "usage: $0 [--check|--collect] [--host HOST] [--via-host USER@HOST] [--users 'u1 u2'] [--timeout SECONDS] [--max-auth-users N]"
}
while [[ $# -gt 0 ]]; do
@@ -58,6 +59,10 @@ while [[ $# -gt 0 ]]; do
shift
TARGET_HOST="${1:-}"
;;
--via-host)
shift
VIA_HOST="${1:-}"
;;
--users)
shift
# shellcheck disable=SC2206
@@ -173,7 +178,7 @@ elif command -v gtimeout >/dev/null 2>&1; then
SSH_TIMEOUT_WRAPPER="gtimeout"
fi
SSH_OPTS=(
SSH_COMMON_OPTS=(
-o BatchMode=yes
-o PreferredAuthentications=publickey
-o PubkeyAuthentication=yes
@@ -186,9 +191,29 @@ SSH_OPTS=(
-o LogLevel=ERROR
-o StrictHostKeyChecking=no
-o UserKnownHostsFile="${KNOWN_HOSTS_FILE}"
)
SSH_TARGET_OPTS=(
"${SSH_COMMON_OPTS[@]}"
-p "${SSH_PORT}"
)
quote_words() {
printf '%q ' "$@"
}
run_with_timeout_wrapper() {
local timeout_seconds="$1"
local timeout_wrapper_seconds="$2"
shift 2
if [[ "${SSH_TIMEOUT_WRAPPER}" == "timeout" ]]; then
timeout "${timeout_wrapper_seconds}s" "$@"
elif [[ "${SSH_TIMEOUT_WRAPPER}" == "gtimeout" ]]; then
gtimeout "${timeout_wrapper_seconds}s" "$@"
else
"$@"
fi
}
run_ssh_timed() {
local user="$1"
local timeout_seconds="$2"
@@ -198,12 +223,20 @@ run_ssh_timed() {
timeout_seconds="${SSH_TIMEOUT}"
timeout_wrapper_seconds="$((SSH_TIMEOUT + 1))"
fi
if [[ "${SSH_TIMEOUT_WRAPPER}" == "timeout" ]]; then
timeout "${timeout_wrapper_seconds}s" ssh "${SSH_OPTS[@]}" -o ConnectTimeout="${timeout_seconds}" "${user}@${TARGET_HOST}" "$@"
elif [[ "${SSH_TIMEOUT_WRAPPER}" == "gtimeout" ]]; then
gtimeout "${timeout_wrapper_seconds}s" ssh "${SSH_OPTS[@]}" -o ConnectTimeout="${timeout_seconds}" "${user}@${TARGET_HOST}" "$@"
local target_ssh=(ssh "${SSH_TARGET_OPTS[@]}" -o ConnectTimeout="${timeout_seconds}" "${user}@${TARGET_HOST}" "$@")
if [[ -n "${VIA_HOST}" ]]; then
local inner_command
inner_command="$(quote_words "${target_ssh[@]}")"
run_with_timeout_wrapper \
"${timeout_seconds}" \
"${timeout_wrapper_seconds}" \
ssh "${SSH_COMMON_OPTS[@]}" -o ConnectTimeout="${timeout_seconds}" \
"${VIA_HOST}" "bash -lc $(printf '%q' "${inner_command}")"
else
ssh "${SSH_OPTS[@]}" -o ConnectTimeout="${timeout_seconds}" "${user}@${TARGET_HOST}" "$@"
run_with_timeout_wrapper \
"${timeout_seconds}" \
"${timeout_wrapper_seconds}" \
"${target_ssh[@]}"
fi
}
@@ -292,6 +325,7 @@ printf '%s\n' "schema_version=windows99_vmware_verify_collector_v1"
printf '%s\n' "dry_run=${DRY_RUN}"
printf '%s\n' "target_host=${TARGET_HOST}"
printf '%s\n' "target_host_alias=99"
printf '%s\n' "via_host=${VIA_HOST:-none}"
printf '%s\n' "connect_timeout_seconds=${CONNECT_TIMEOUT}"
printf '%s\n' "ssh_timeout_seconds=${SSH_TIMEOUT}"
printf '%s\n' "remote_verify_timeout_seconds=${REMOTE_VERIFY_TIMEOUT}"

View File

@@ -18,8 +18,8 @@ VALID_RED_VERIFY = """\
AWOOOI_WINDOWS99_VMWARE_AUTOSTART=1
MODE=Verify
VMRUN_PRESENT=1
HOST111_VMWARE_TARGET=explicit_or_required
VMX alias=111 path= present=0
HOST111_VMWARE_TARGET=not_required_by_default_missing_no_vmx_found_on_99
VMX alias=110 path=D:\\Documents\\Virtual Machines\\110\\110.vmx present=1
VMX alias=188 path=D:\\Documents\\Virtual Machines\\188\\188.vmx present=1
VMX alias=120 path=D:\\Documents\\Virtual Machines\\120\\120.vmx present=1
VMX alias=121 path=D:\\Documents\\Virtual Machines\\121\\121.vmx present=1
@@ -28,7 +28,7 @@ VMWARE_SERVICE name=VMAuthdService present=1 status=Running startup_type=Auto ok
VMWARE_SERVICE name=VMnetDHCP present=1 status=Running startup_type=Auto ok=1
VMWARE_AUTOSTART_TASK name=AWOOOI-Start-VMware-VMs present=1 enabled=1 state=Ready trigger_count=1 action_count=1 ok=1
WINDOWS_UPDATE_POLICY name=NoAutoRebootWithLoggedOnUsers value=1 expected=1 ok=1
VM_POWER alias=111 vmx_present=0 running=0
VM_POWER alias=110 vmx_present=1 running=0
VM_POWER alias=188 vmx_present=1 running=0
VM_POWER alias=120 vmx_present=1 running=0
VM_POWER alias=121 vmx_present=1 running=0
@@ -37,7 +37,6 @@ VMWARE_AUTOSTART_CONFIG_READY=0
VMWARE_AUTOSTART_POWER_READY=0
WINDOWS_UPDATE_NO_AUTO_REBOOT_READY=1
VMWARE_AUTOSTART_VERIFY_READY=0
MISSING_VMX_ALIASES=111
"""
@@ -54,6 +53,23 @@ def _run(input_path: Path, output_path: Path | None = None) -> subprocess.Comple
)
def _run_with_aliases(input_path: Path, aliases: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[
"python3.11",
str(SCRIPT),
"--input",
str(input_path),
"--required-aliases",
aliases,
],
cwd=ROOT,
capture_output=True,
text=True,
check=False,
)
def _kv(stdout: str) -> dict[str, str]:
values: dict[str, str] = {}
for line in stdout.splitlines():
@@ -77,8 +93,8 @@ def test_valid_console_verify_red_artifact_is_accepted_and_normalized(
assert values["schema_version"] == "windows99_console_verify_artifact_validator_v1"
assert values["status"] == "valid_console_verify_artifact_vmware_not_ready"
assert values["missing_required_field_count"] == "0"
assert values["missing_vmx_aliases"] == "111"
assert values["powered_off_aliases"] == "111,188,120,121,112"
assert values["missing_vmx_aliases"] == ""
assert values["powered_off_aliases"] == "110,188,120,121,112"
assert values["secret_value_read"] == "false"
assert normalized.read_text(encoding="utf-8").startswith(
"AWOOOI_WINDOWS99_VMWARE_AUTOSTART=1\n"
@@ -108,6 +124,28 @@ def test_collector_envelope_is_extracted(tmp_path: Path) -> None:
assert "remote_verify_output_begin" not in normalized.read_text(encoding="utf-8")
def test_explicit_host111_artifact_can_be_validated_with_required_alias_override(
tmp_path: Path,
) -> None:
raw = tmp_path / "console-111.txt"
raw.write_text(
VALID_RED_VERIFY.replace(
"HOST111_VMWARE_TARGET=not_required_by_default_missing_no_vmx_found_on_99",
"HOST111_VMWARE_TARGET=explicit_or_required",
)
.replace("VMX alias=110 path=D:\\Documents\\Virtual Machines\\110\\110.vmx present=1", "VMX alias=111 path= present=0")
.replace("VM_POWER alias=110 vmx_present=1 running=0", "VM_POWER alias=111 vmx_present=0 running=0"),
encoding="utf-8",
)
result = _run_with_aliases(raw, "111,188,120,121,112")
assert result.returncode == 0
values = _kv(result.stdout)
assert values["missing_vmx_aliases"] == "111"
assert values["powered_off_aliases"] == "111,188,120,121,112"
def test_secret_like_console_output_is_rejected(tmp_path: Path) -> None:
raw = tmp_path / "console.txt"
raw.write_text(VALID_RED_VERIFY + "password=not-allowed\n", encoding="utf-8")

View File

@@ -50,6 +50,8 @@ def test_collector_contract_forbids_secret_and_runtime_actions() -> None:
assert "NumberOfPasswordPrompts=0" in text
assert "ConnectionAttempts=1" in text
assert "GSSAPIAuthentication=no" in text
assert "--via-host" in text
assert "via_host=" in text
assert "in_memory_stdin_scriptblock" in text
assert "[Console]::In.ReadToEnd()" in text
assert ".\\\\windows99-vmware-autostart.ps1 -Mode Verify" not in text

View File

@@ -10,7 +10,7 @@ from pathlib import Path
SCHEMA_VERSION = "windows99_console_verify_artifact_validator_v1"
DEFAULT_REQUIRED_ALIASES = ("111", "188", "120", "121", "112")
DEFAULT_REQUIRED_ALIASES = ("110", "188", "120", "121", "112")
SENSITIVE_RE = re.compile(
r"(?i)(password|passwd|secret|token|authorization|cookie|private[_ -]?key)\s*[=:]"
)