diff --git a/deployment/GH-CVM-MAIN-PROD-01/0001-feat-guanghu-os-archive-enterprise-native-recovery-l.patch b/deployment/GH-CVM-MAIN-PROD-01/0001-feat-guanghu-os-archive-enterprise-native-recovery-l.patch deleted file mode 100644 index a47c47e..0000000 --- a/deployment/GH-CVM-MAIN-PROD-01/0001-feat-guanghu-os-archive-enterprise-native-recovery-l.patch +++ /dev/null @@ -1,6598 +0,0 @@ -From b546827c71fe1e13ee221c9922eb73d20900234d Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> -Date: Mon, 3 Aug 2026 20:32:48 +0800 -Subject: [PATCH] feat(guanghu-os): archive enterprise native recovery line - ---- - guanghu-os/Cargo.lock | 186 +++++++- - guanghu-os/README.md | 11 +- - guanghu-os/crates/ghdr/Cargo.toml | 5 + - .../crates/ghdr/src/bin/ghdr-controller.rs | 150 ++++++ - guanghu-os/crates/ghdr/src/lib.rs | 341 +++++++++++++- - guanghu-os/crates/ghdr/tests/ghdr_command.rs | 24 +- - .../crates/ghdr/tests/ghdr_controller.rs | 163 +++++++ - guanghu-os/crates/ghdr/tests/ghdr_library.rs | 149 +++++- - guanghu-os/crates/hldp-runtime/src/lib.rs | 137 +++++- - .../hldp-runtime/tests/world_manifest.rs | 139 +++++- - .../DEVELOPMENT-LINE-20260801-20260803.md | 251 ++++++++++ - .../controller-signer/.gitignore | 2 + - .../controller-signer/README.md | 43 ++ - .../guanghu-ghdr-controller-poller.py | 130 ++++++ - .../guanghu-ghdr-controller-poller.service | 33 ++ - .../guanghu-ghdr-signer-http.py | 195 ++++++++ - .../controller-signer/guanghu-ghdr-signer.py | 206 +++++++++ - .../guanghu-ghdr-signer.service | 34 ++ - .../install-controller-signer.sh | 159 +++++++ - .../install-jd-forced-key.sh | 31 ++ - .../test-controller-poller.py | 84 ++++ - .../test-controller-signer-http.py | 187 ++++++++ - .../test-controller-signer.py | 127 ++++++ - .../world-seed/CURRENT.hldp | 40 ++ - .../GH-CVM-MAIN-PROD-01/world-seed/WAKE.hldp | 37 ++ - .../world-seed/WORLD-MANIFEST.hldp | 119 +++++ - .../run-guanghu-native-quality-gate.sh | 124 +++++ - .../BINGSHUO-STANDING-AUTHORIZATION.hldp | 44 ++ - .../world-seed/state/checkpoints/GENESIS.hldp | 14 + - .../state/receipts/CODE-CHANNEL-BASELINE.hldp | 24 + - .../receipts/ENTERPRISE-ACCESS-20260801.hldp | 24 + - ...ISE-DEVELOPMENT-LINE-CLOSURE-20260803.hldp | 27 ++ - .../ENTERPRISE-NATIVE-PREFLIGHT-20260801.hldp | 27 ++ - .../GH-CVM-MAIN-PROD-01-NATIVE.hldp | 25 + - .../GESTATIONAL-CONTINUITY-INGESTION.hldp | 48 ++ - .../cognition/PERSONA-BIRTH-CONDITION.hldp | 34 ++ - .../world-seed/world/domains/fifth/INDEX.hldp | 13 + - .../world-seed/world/domains/main/INDEX.hldp | 8 + - .../world-seed/world/domains/sub/INDEX.hldp | 8 + - .../world/domains/zero-sense/INDEX.hldp | 8 + - .../world-seed/world/domains/zero/INDEX.hldp | 8 + - .../world/services/code-channel/CHANNEL.hldp | 55 +++ - .../services/code-channel/QUALITY-GATE.hldp | 53 +++ - .../services/native-recovery/PROTOCOL.hldp | 45 ++ - .../services/native-storage/DISK-LAYOUT.hldp | 35 ++ - guanghu-os/disaster-recovery/README.md | 60 ++- - .../disaster-recovery/node-plan.example.json | 26 +- - guanghu-os/native/x86_64-bios/boot.asm | 23 + - guanghu-os/native/x86_64-bios/ghal-virtio.asm | 428 +++++++++++++++--- - .../native/x86_64-bios/physical-test-mbr.asm | 2 + - .../build-native-physical-candidate.sh | 37 +- - .../build-native-resident-candidate.sh | 32 ++ - .../scripts/install-native-ab-signed.sh | 223 +++++++++ - guanghu-os/scripts/qemu-native-net-peer.py | 290 +++++++++++- - .../scripts/render-native-recovery-beacon.sh | 22 +- - ...est-native-ab-signed-installer-contract.sh | 25 + - .../scripts/test-native-physical-candidate.sh | 102 ++++- - .../test-native-recovery-beacon-contract.sh | 12 + - .../scripts/test-native-resident-candidate.sh | 147 +++++- - .../scripts/test-qemu-native-control-auth.py | 66 +++ - guanghu-os/world-seed/WORLD-MANIFEST.hldp | 3 + - .../services/native-storage/DISK-LAYOUT.hldp | 4 + - 62 files changed, 4933 insertions(+), 176 deletions(-) - create mode 100644 guanghu-os/crates/ghdr/src/bin/ghdr-controller.rs - create mode 100644 guanghu-os/crates/ghdr/tests/ghdr_controller.rs - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/DEVELOPMENT-LINE-20260801-20260803.md - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/.gitignore - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/README.md - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-controller-poller.py - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-controller-poller.service - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-signer-http.py - create mode 100755 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-signer.py - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-signer.service - create mode 100755 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/install-controller-signer.sh - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/install-jd-forced-key.sh - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/test-controller-poller.py - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/test-controller-signer-http.py - create mode 100755 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/test-controller-signer.py - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/CURRENT.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/WAKE.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/WORLD-MANIFEST.hldp - create mode 100755 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/scripts/run-guanghu-native-quality-gate.sh - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/authorizations/BINGSHUO-STANDING-AUTHORIZATION.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/checkpoints/GENESIS.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/receipts/CODE-CHANNEL-BASELINE.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/receipts/ENTERPRISE-ACCESS-20260801.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/receipts/ENTERPRISE-DEVELOPMENT-LINE-CLOSURE-20260803.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/receipts/ENTERPRISE-NATIVE-PREFLIGHT-20260801.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/workorders/GH-CVM-MAIN-PROD-01-NATIVE.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/cognition/GESTATIONAL-CONTINUITY-INGESTION.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/cognition/PERSONA-BIRTH-CONDITION.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/fifth/INDEX.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/main/INDEX.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/sub/INDEX.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/zero-sense/INDEX.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/zero/INDEX.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/services/code-channel/CHANNEL.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/services/code-channel/QUALITY-GATE.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/services/native-recovery/PROTOCOL.hldp - create mode 100644 guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/services/native-storage/DISK-LAYOUT.hldp - create mode 100755 guanghu-os/scripts/install-native-ab-signed.sh - create mode 100755 guanghu-os/scripts/test-native-ab-signed-installer-contract.sh - create mode 100644 guanghu-os/scripts/test-qemu-native-control-auth.py - -diff --git a/guanghu-os/Cargo.lock b/guanghu-os/Cargo.lock -index 4cb0b60..bc7a3dd 100644 ---- a/guanghu-os/Cargo.lock -+++ b/guanghu-os/Cargo.lock -@@ -1,6 +1,12 @@ - # This file is automatically @generated by Cargo. - # It is not intended for manual editing. --version = 4 -+version = 3 -+ -+[[package]] -+name = "base64ct" -+version = "1.8.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - - [[package]] - name = "block-buffer" -@@ -17,6 +23,12 @@ version = "1.0.4" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -+[[package]] -+name = "const-oid" -+version = "0.9.6" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -+ - [[package]] - name = "cpufeatures" - version = "0.2.17" -@@ -36,6 +48,43 @@ dependencies = [ - "typenum", - ] - -+[[package]] -+name = "curve25519-dalek" -+version = "4.1.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -+dependencies = [ -+ "cfg-if", -+ "cpufeatures", -+ "curve25519-dalek-derive", -+ "digest", -+ "fiat-crypto", -+ "rustc_version", -+ "subtle", -+ "zeroize", -+] -+ -+[[package]] -+name = "curve25519-dalek-derive" -+version = "0.1.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "syn 2.0.119", -+] -+ -+[[package]] -+name = "der" -+version = "0.7.10" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -+dependencies = [ -+ "const-oid", -+ "zeroize", -+] -+ - [[package]] - name = "digest" - version = "0.10.7" -@@ -46,12 +95,43 @@ dependencies = [ - "crypto-common", - ] - -+[[package]] -+name = "ed25519" -+version = "2.2.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -+dependencies = [ -+ "pkcs8", -+ "signature", -+] -+ -+[[package]] -+name = "ed25519-dalek" -+version = "2.2.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" -+dependencies = [ -+ "curve25519-dalek", -+ "ed25519", -+ "rand_core", -+ "serde", -+ "sha2", -+ "subtle", -+ "zeroize", -+] -+ - [[package]] - name = "equivalent" - version = "1.0.2" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -+[[package]] -+name = "fiat-crypto" -+version = "0.2.9" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" -+ - [[package]] - name = "generic-array" - version = "0.14.7" -@@ -62,6 +142,17 @@ dependencies = [ - "version_check", - ] - -+[[package]] -+name = "getrandom" -+version = "0.2.17" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -+dependencies = [ -+ "cfg-if", -+ "libc", -+ "wasi", -+] -+ - [[package]] - name = "ghctl" - version = "0.1.0" -@@ -82,6 +173,9 @@ dependencies = [ - name = "guanghu-ghdr" - version = "0.1.0" - dependencies = [ -+ "ed25519-dalek", -+ "hex", -+ "rand_core", - "serde", - "serde_json", - "sha2", -@@ -101,6 +195,12 @@ version = "0.17.1" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -+[[package]] -+name = "hex" -+version = "0.4.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -+ - [[package]] - name = "hldp-native-compiler" - version = "0.1.0" -@@ -136,6 +236,16 @@ version = "2.8.3" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - -+[[package]] -+name = "pkcs8" -+version = "0.10.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -+dependencies = [ -+ "der", -+ "spki", -+] -+ - [[package]] - name = "proc-macro2" - version = "1.0.107" -@@ -154,12 +264,36 @@ dependencies = [ - "proc-macro2", - ] - -+[[package]] -+name = "rand_core" -+version = "0.6.4" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -+dependencies = [ -+ "getrandom", -+] -+ -+[[package]] -+name = "rustc_version" -+version = "0.4.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -+dependencies = [ -+ "semver", -+] -+ - [[package]] - name = "ryu" - version = "1.0.23" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -+[[package]] -+name = "semver" -+version = "1.0.28" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" -+ - [[package]] - name = "serde" - version = "1.0.229" -@@ -187,7 +321,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" - dependencies = [ - "proc-macro2", - "quote", -- "syn", -+ "syn 3.0.3", - ] - - [[package]] -@@ -227,6 +361,42 @@ dependencies = [ - "digest", - ] - -+[[package]] -+name = "signature" -+version = "2.2.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -+dependencies = [ -+ "rand_core", -+] -+ -+[[package]] -+name = "spki" -+version = "0.7.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -+dependencies = [ -+ "base64ct", -+ "der", -+] -+ -+[[package]] -+name = "subtle" -+version = "2.6.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -+ -+[[package]] -+name = "syn" -+version = "2.0.119" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "unicode-ident", -+] -+ - [[package]] - name = "syn" - version = "3.0.3" -@@ -262,6 +432,18 @@ version = "0.9.5" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -+[[package]] -+name = "wasi" -+version = "0.11.1+wasi-snapshot-preview1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -+ -+[[package]] -+name = "zeroize" -+version = "1.8.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -+ - [[package]] - name = "zmij" - version = "1.0.23" -diff --git a/guanghu-os/README.md b/guanghu-os/README.md -index 0396926..84f6af7 100644 ---- a/guanghu-os/README.md -+++ b/guanghu-os/README.md -@@ -71,9 +71,9 @@ and exact repository digests before printing the recovery evidence. - exact Shanghai laboratory prototype. It observes firmware, architecture, - provider, root/system disks, block geometry, and network drivers without - recording addresses or secrets. It then validates a target plan with at least --two recovery controllers across two failure domains and exact references to --clone-boot, control-plane backup, data-restore, and provider-console recovery --receipts. -+two recovery controllers across two failure domains and exact references plus -+SHA-256 digests for zero-cost Linux-rescue boot, control-plane backup, -+data-restore, and provider-console recovery receipts. - - Every GHDR gate is binary: `FAIL_0` or `PASS_100`. A passing preflight permits - only recovery-package preparation. Its manifest always sets -@@ -82,8 +82,9 @@ partition changes, or raw-sector writes. Package verification rejects unsafe - paths and secret-like artifacts, checks exact sizes and SHA-256 digests, and - never executes the package. - --The next registered action is a canonical, expiring signed A/B layout plan --requiring two independent controller signatures and a fresh target read-back. -+The canonical, expiring signed A/B layout-plan gate is implemented. It requires -+two pinned independent Ed25519 controller signatures and a matching target -+read-back no older than five minutes before permitting the exact signed write. - Full migration remains `FAIL_0` until native boot, automatic fallback, data and - control-plane restoration, provider-console recovery, and server-owned - receipts all pass. See -diff --git a/guanghu-os/crates/ghdr/Cargo.toml b/guanghu-os/crates/ghdr/Cargo.toml -index 7937289..c131d38 100644 ---- a/guanghu-os/crates/ghdr/Cargo.toml -+++ b/guanghu-os/crates/ghdr/Cargo.toml -@@ -2,10 +2,15 @@ - name = "guanghu-ghdr" - version = "0.1.0" - edition = "2021" -+rust-version = "1.75" - license = "AGPL-3.0-or-later" - description = "Fail-closed disaster-recovery preflight for Guanghu OS nodes" -+default-run = "guanghu-ghdr" - - [dependencies] - serde = { version = "1", features = ["derive"] } - serde_json = "1" - sha2 = "0.10" -+ed25519-dalek = { version = "2", features = ["rand_core"] } -+hex = "0.4" -+rand_core = { version = "0.6", features = ["getrandom"] } -diff --git a/guanghu-os/crates/ghdr/src/bin/ghdr-controller.rs b/guanghu-os/crates/ghdr/src/bin/ghdr-controller.rs -new file mode 100644 -index 0000000..3243e5a ---- /dev/null -+++ b/guanghu-os/crates/ghdr/src/bin/ghdr-controller.rs -@@ -0,0 +1,150 @@ -+use std::{ -+ env, fs, -+ io::Write, -+ os::unix::fs::{MetadataExt, OpenOptionsExt}, -+ path::Path, -+ process, -+}; -+ -+use ed25519_dalek::{Signer, SigningKey}; -+use guanghu_ghdr::{canonical_layout_plan_payload, ControllerSignature, SignedLayoutPlan}; -+use rand_core::OsRng; -+use serde::Serialize; -+ -+const USAGE: &str = "usage: ghdr-controller generate-key | sign-layout "; -+ -+#[derive(Serialize)] -+struct PublicBinding<'a> { -+ schema: &'static str, -+ node_id: &'a str, -+ failure_domain: &'a str, -+ algorithm: &'static str, -+ public_key_hex: String, -+} -+ -+fn main() { -+ if let Err(error) = run(env::args().skip(1).collect()) { -+ eprintln!("GHDR_CONTROLLER_FAIL_0: {error}"); -+ process::exit(65); -+ } -+} -+ -+fn run(arguments: Vec) -> Result<(), String> { -+ match arguments.as_slice() { -+ [command, private_path, public_path, node_id, failure_domain] -+ if command == "generate-key" => -+ { -+ generate_key( -+ Path::new(private_path), -+ Path::new(public_path), -+ node_id, -+ failure_domain, -+ ) -+ } -+ [command, private_path, node_id, failure_domain, plan_path, signature_path] -+ if command == "sign-layout" => -+ { -+ sign_layout( -+ Path::new(private_path), -+ node_id, -+ failure_domain, -+ Path::new(plan_path), -+ Path::new(signature_path), -+ ) -+ } -+ _ => Err(USAGE.to_owned()), -+ } -+} -+ -+fn generate_key( -+ private_path: &Path, -+ public_path: &Path, -+ node_id: &str, -+ failure_domain: &str, -+) -> Result<(), String> { -+ validate_identity(node_id, failure_domain)?; -+ let key = SigningKey::generate(&mut OsRng); -+ write_new(private_path, &key.to_bytes(), 0o600)?; -+ let binding = PublicBinding { -+ schema: "guanghu.ghdr-controller-public-binding/v1", -+ node_id, -+ failure_domain, -+ algorithm: "Ed25519", -+ public_key_hex: hex::encode(key.verifying_key().to_bytes()), -+ }; -+ let bytes = serde_json::to_vec_pretty(&binding) -+ .map_err(|error| format!("cannot serialize public binding: {error}"))?; -+ if let Err(error) = write_new(public_path, &bytes, 0o644) { -+ let _ = fs::remove_file(private_path); -+ return Err(error); -+ } -+ println!("GHDR_CONTROLLER_KEY_CREATED_WITH_PRIVATE_SEED_NOT_PRINTED"); -+ Ok(()) -+} -+ -+fn sign_layout( -+ private_path: &Path, -+ node_id: &str, -+ failure_domain: &str, -+ plan_path: &Path, -+ signature_path: &Path, -+) -> Result<(), String> { -+ validate_identity(node_id, failure_domain)?; -+ let key = read_private_seed(private_path)?; -+ let plan: SignedLayoutPlan = serde_json::from_slice( -+ &fs::read(plan_path).map_err(|error| format!("cannot read layout plan: {error}"))?, -+ ) -+ .map_err(|error| format!("layout plan is invalid JSON: {error}"))?; -+ let payload = canonical_layout_plan_payload(&plan.payload)?; -+ let approval = ControllerSignature { -+ node_id: node_id.to_owned(), -+ failure_domain: failure_domain.to_owned(), -+ public_key_hex: hex::encode(key.verifying_key().to_bytes()), -+ signature_hex: hex::encode(key.sign(&payload).to_bytes()), -+ }; -+ let bytes = serde_json::to_vec_pretty(&approval) -+ .map_err(|error| format!("cannot serialize controller signature: {error}"))?; -+ write_new(signature_path, &bytes, 0o644)?; -+ println!("GHDR_CONTROLLER_LAYOUT_SIGNATURE_CREATED_WITH_PRIVATE_SEED_NOT_PRINTED"); -+ Ok(()) -+} -+ -+fn read_private_seed(path: &Path) -> Result { -+ let metadata = fs::symlink_metadata(path) -+ .map_err(|error| format!("private seed is unavailable: {error}"))?; -+ if !metadata.file_type().is_file() || metadata.mode() & 0o077 != 0 { -+ return Err( -+ "private seed must be a regular file inaccessible to group and others".to_owned(), -+ ); -+ } -+ let bytes = fs::read(path).map_err(|error| format!("cannot read private seed: {error}"))?; -+ let seed: [u8; 32] = bytes -+ .try_into() -+ .map_err(|_| "private seed must contain exactly 32 bytes".to_owned())?; -+ Ok(SigningKey::from_bytes(&seed)) -+} -+ -+fn write_new(path: &Path, bytes: &[u8], mode: u32) -> Result<(), String> { -+ let mut output = fs::OpenOptions::new() -+ .write(true) -+ .create_new(true) -+ .mode(mode) -+ .open(path) -+ .map_err(|error| format!("refusing to replace {}: {error}", path.display()))?; -+ output -+ .write_all(bytes) -+ .and_then(|_| output.sync_all()) -+ .map_err(|error| format!("cannot persist {}: {error}", path.display())) -+} -+ -+fn validate_identity(node_id: &str, failure_domain: &str) -> Result<(), String> { -+ let node_valid = !node_id.is_empty() -+ && node_id.contains('-') -+ && node_id.chars().all(|character| { -+ character.is_ascii_uppercase() || character.is_ascii_digit() || character == '-' -+ }); -+ if !node_valid || failure_domain.trim().is_empty() { -+ return Err("controller node id or failure domain is invalid".to_owned()); -+ } -+ Ok(()) -+} -diff --git a/guanghu-os/crates/ghdr/src/lib.rs b/guanghu-os/crates/ghdr/src/lib.rs -index 2240a53..5eb11cd 100644 ---- a/guanghu-os/crates/ghdr/src/lib.rs -+++ b/guanghu-os/crates/ghdr/src/lib.rs -@@ -4,14 +4,18 @@ use std::{ - path::{Component, Path, PathBuf}, - }; - -+use ed25519_dalek::{Signature, Verifier, VerifyingKey}; - use serde::{Deserialize, Serialize}; - use sha2::{Digest, Sha256}; - - const PROBE_SCHEMA: &str = "guanghu.ghdr-node-probe/v1"; - const MANIFEST_SCHEMA: &str = "guanghu.ghdr-node-manifest/v1"; - const PACKAGE_SCHEMA: &str = "guanghu.ghdr-recovery-package/v1"; -+const LAYOUT_PLAN_SCHEMA: &str = "guanghu.ghdr-signed-layout-plan/v1"; -+const LAYOUT_READBACK_SCHEMA: &str = "guanghu.ghdr-layout-readback/v1"; -+const MAX_READBACK_AGE_SECONDS: u64 = 300; - const USAGE: &str = -- "usage: guanghu-ghdr probe | build-manifest | verify-package "; -+ "usage: guanghu-ghdr probe | build-manifest | verify-package | layout-plan-payload | verify-signed-layout-plan "; - - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] - #[serde(deny_unknown_fields)] -@@ -47,15 +51,23 @@ pub struct ControllerPlan { - pub node_id: String, - pub failure_domain: String, - pub role: String, -+ pub signing_public_key_hex: String, -+} -+ -+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -+#[serde(deny_unknown_fields)] -+pub struct EvidenceReceipt { -+ pub reference: String, -+ pub sha256: String, - } - - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - #[serde(deny_unknown_fields)] - pub struct EvidencePlan { -- pub cloud_image_clone_boot_receipt: String, -- pub control_plane_backup_receipt: String, -- pub data_restore_receipt: String, -- pub provider_console_recovery_receipt: String, -+ pub linux_rescue_boot_receipt: EvidenceReceipt, -+ pub control_plane_backup_receipt: EvidenceReceipt, -+ pub data_restore_receipt: EvidenceReceipt, -+ pub provider_console_recovery_receipt: EvidenceReceipt, - } - - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -@@ -69,7 +81,8 @@ pub struct MigrationPlan { - pub evidence: EvidencePlan, - } - --#[derive(Debug, Clone, Serialize, PartialEq, Eq)] -+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -+#[serde(deny_unknown_fields)] - pub struct BootStrategy { - pub kind: String, - pub target_slot: String, -@@ -77,7 +90,8 @@ pub struct BootStrategy { - pub linux_runtime_required_after_acceptance: bool, - } - --#[derive(Debug, Clone, Serialize, PartialEq, Eq)] -+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -+#[serde(deny_unknown_fields)] - pub struct MigrationGate { - pub state: String, - pub gate_score: u8, -@@ -86,7 +100,8 @@ pub struct MigrationGate { - pub next_registered_action: String, - } - --#[derive(Debug, Clone, Serialize, PartialEq, Eq)] -+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -+#[serde(deny_unknown_fields)] - pub struct NodeManifest { - pub schema: String, - pub node_id: String, -@@ -125,6 +140,78 @@ pub struct PackageVerification { - pub executed_artifacts: bool, - } - -+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -+#[serde(deny_unknown_fields)] -+pub struct LayoutSlot { -+ pub name: String, -+ pub lba_start: u64, -+ pub sector_count: u64, -+ pub image_sha256: String, -+} -+ -+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -+#[serde(deny_unknown_fields)] -+pub struct LayoutPlanPayload { -+ pub node_id: String, -+ pub provider: String, -+ pub region: String, -+ pub target_probe_sha256: String, -+ pub system_disk: String, -+ pub disk_sectors: u64, -+ pub logical_sector_bytes: u64, -+ pub disk_identity_sha256: String, -+ pub recovery_evidence_sha256: String, -+ pub first_partition_lba: u64, -+ pub generation: u64, -+ pub operation: String, -+ pub issued_at_unix: u64, -+ pub expires_at_unix: u64, -+ pub slots: Vec, -+} -+ -+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -+#[serde(deny_unknown_fields)] -+pub struct ControllerSignature { -+ pub node_id: String, -+ pub failure_domain: String, -+ pub public_key_hex: String, -+ pub signature_hex: String, -+} -+ -+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -+#[serde(deny_unknown_fields)] -+pub struct SignedLayoutPlan { -+ pub schema: String, -+ pub payload: LayoutPlanPayload, -+ pub signatures: Vec, -+} -+ -+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -+#[serde(deny_unknown_fields)] -+pub struct LayoutReadback { -+ pub schema: String, -+ pub node_id: String, -+ pub observed_at_unix: u64, -+ pub target_probe_sha256: String, -+ pub system_disk: String, -+ pub disk_sectors: u64, -+ pub logical_sector_bytes: u64, -+ pub disk_identity_sha256: String, -+ pub first_partition_lba: u64, -+} -+ -+#[derive(Debug, Clone, Serialize, PartialEq, Eq)] -+pub struct LayoutPlanVerification { -+ pub schema: String, -+ pub status: String, -+ pub gate_score: u8, -+ pub allows_disk_write: bool, -+ pub verified_controller_count: usize, -+ pub target_readback_fresh: bool, -+ pub target_readback_matches: bool, -+ pub plan_sha256: String, -+} -+ - pub fn collect_probe(root: &Path, architecture: &str) -> Result { - if !matches!(architecture, "x86_64" | "aarch64") { - return Err(format!("unsupported architecture: {architecture}")); -@@ -251,6 +338,199 @@ pub fn validate_recovery_package(package_root: &Path) -> Result Result, String> { -+ validate_layout_payload(payload)?; -+ serde_json::to_vec(payload).map_err(|error| format!("cannot canonicalize layout plan: {error}")) -+} -+ -+pub fn verify_signed_layout_plan( -+ manifest: &NodeManifest, -+ plan: &SignedLayoutPlan, -+ readback: &LayoutReadback, -+ now_unix: u64, -+) -> Result { -+ if plan.schema != LAYOUT_PLAN_SCHEMA { -+ return Err("unsupported signed layout plan schema".to_owned()); -+ } -+ if readback.schema != LAYOUT_READBACK_SCHEMA { -+ return Err("unsupported layout readback schema".to_owned()); -+ } -+ if manifest.migration_gate.state != "PASS_100_RECOVERY_PACKAGE_PREPARATION" -+ || manifest.migration_gate.gate_score != 100 -+ || !manifest.migration_gate.all_checks_passed -+ || manifest.migration_gate.allows_disk_write -+ { -+ return Err("node manifest has not passed the read-only GHDR gate".to_owned()); -+ } -+ let payload_bytes = canonical_layout_plan_payload(&plan.payload)?; -+ let probe_sha256 = sha256_json(&manifest.observed_hardware)?; -+ let disk = manifest -+ .observed_hardware -+ .block_devices -+ .iter() -+ .find(|device| device.path == manifest.observed_hardware.system_disk) -+ .ok_or_else(|| "manifest system disk is absent from its inventory".to_owned())?; -+ if plan.payload.node_id != manifest.node_id -+ || plan.payload.provider != manifest.provider -+ || plan.payload.region != manifest.region -+ || plan.payload.target_probe_sha256 != probe_sha256 -+ || plan.payload.system_disk != manifest.observed_hardware.system_disk -+ || plan.payload.disk_sectors != disk.sectors -+ || plan.payload.logical_sector_bytes != disk.logical_sector_bytes -+ || plan.payload.recovery_evidence_sha256 != sha256_json(&manifest.evidence)? -+ { -+ return Err("signed layout plan is not bound to the exact node manifest".to_owned()); -+ } -+ if plan.payload.issued_at_unix > now_unix || now_unix >= plan.payload.expires_at_unix { -+ return Err("signed layout plan is not currently valid".to_owned()); -+ } -+ if plan.payload.expires_at_unix - plan.payload.issued_at_unix > 3600 { -+ return Err("signed layout plan validity exceeds one hour".to_owned()); -+ } -+ if readback.observed_at_unix > now_unix -+ || now_unix - readback.observed_at_unix > MAX_READBACK_AGE_SECONDS -+ { -+ return Err("target readback is not fresh".to_owned()); -+ } -+ if readback.node_id != plan.payload.node_id -+ || readback.target_probe_sha256 != plan.payload.target_probe_sha256 -+ || readback.system_disk != plan.payload.system_disk -+ || readback.disk_sectors != plan.payload.disk_sectors -+ || readback.logical_sector_bytes != plan.payload.logical_sector_bytes -+ || readback.disk_identity_sha256 != plan.payload.disk_identity_sha256 -+ || readback.first_partition_lba != plan.payload.first_partition_lba -+ { -+ return Err("fresh target readback does not match the signed layout plan".to_owned()); -+ } -+ if plan.signatures.len() != 2 { -+ return Err("exactly two independent controller signatures are required".to_owned()); -+ } -+ let mut controller_ids = HashSet::new(); -+ let mut failure_domains = HashSet::new(); -+ for approval in &plan.signatures { -+ if !controller_ids.insert(approval.node_id.as_str()) -+ || !failure_domains.insert(approval.failure_domain.as_str()) -+ { -+ return Err( -+ "controller signatures must use distinct nodes and failure domains".to_owned(), -+ ); -+ } -+ let controller = manifest -+ .recovery_controllers -+ .iter() -+ .find(|controller| controller.node_id == approval.node_id) -+ .ok_or_else(|| "layout signature uses an unregistered controller".to_owned())?; -+ if controller.failure_domain != approval.failure_domain -+ || controller.signing_public_key_hex != approval.public_key_hex -+ { -+ return Err( -+ "layout signature does not match the pinned controller identity".to_owned(), -+ ); -+ } -+ let public_key = decode_fixed::<32>(&approval.public_key_hex, "controller public key")?; -+ let signature = decode_fixed::<64>(&approval.signature_hex, "controller signature")?; -+ let verifying_key = VerifyingKey::from_bytes(&public_key) -+ .map_err(|_| "controller public key is not valid Ed25519".to_owned())?; -+ verifying_key -+ .verify(&payload_bytes, &Signature::from_bytes(&signature)) -+ .map_err(|_| "controller Ed25519 signature verification failed".to_owned())?; -+ } -+ Ok(LayoutPlanVerification { -+ schema: "guanghu.ghdr-layout-plan-verification/v1".to_owned(), -+ status: "PASS_100_SIGNED_LAYOUT_PLAN".to_owned(), -+ gate_score: 100, -+ allows_disk_write: true, -+ verified_controller_count: 2, -+ target_readback_fresh: true, -+ target_readback_matches: true, -+ plan_sha256: format!("{:x}", Sha256::digest(&payload_bytes)), -+ }) -+} -+ -+fn validate_layout_payload(payload: &LayoutPlanPayload) -> Result<(), String> { -+ validate_node_id(&payload.node_id)?; -+ require_text("provider", &payload.provider)?; -+ require_text("region", &payload.region)?; -+ if !is_sha256(&payload.target_probe_sha256) -+ || !is_sha256(&payload.disk_identity_sha256) -+ || !is_sha256(&payload.recovery_evidence_sha256) -+ { -+ return Err("layout plan evidence digests must be lowercase SHA-256".to_owned()); -+ } -+ if !payload.system_disk.starts_with("/dev/") || payload.logical_sector_bytes != 512 { -+ return Err("layout plan must bind a 512-byte whole system disk".to_owned()); -+ } -+ if payload.generation == 0 || payload.operation != "install_native_ab" { -+ return Err("layout plan generation or intended operation is invalid".to_owned()); -+ } -+ if payload.issued_at_unix >= payload.expires_at_unix { -+ return Err("layout plan expiration must follow issuance".to_owned()); -+ } -+ if payload.slots.len() != 2 { -+ return Err("layout plan must contain exactly A and B slots".to_owned()); -+ } -+ let names: HashSet<&str> = payload -+ .slots -+ .iter() -+ .map(|slot| slot.name.as_str()) -+ .collect(); -+ if names != HashSet::from(["A", "B"]) { -+ return Err("layout plan must contain one A slot and one B slot".to_owned()); -+ } -+ let slot_a = payload -+ .slots -+ .iter() -+ .find(|slot| slot.name == "A") -+ .expect("A slot membership was checked"); -+ let slot_b = payload -+ .slots -+ .iter() -+ .find(|slot| slot.name == "B") -+ .expect("B slot membership was checked"); -+ if slot_a.lba_start != 34 || slot_a.sector_count != 29 { -+ return Err("layout slot A must own the registered native kernel LBA 34-62".to_owned()); -+ } -+ if slot_b.lba_start < 73 || slot_b.sector_count != 29 { -+ return Err( -+ "layout slot B must be a 29-sector extent after shared native state".to_owned(), -+ ); -+ } -+ if payload.first_partition_lba <= 102 || payload.first_partition_lba > payload.disk_sectors { -+ return Err("layout plan first partition boundary is invalid".to_owned()); -+ } -+ for slot in &payload.slots { -+ if slot.sector_count == 0 || !is_sha256(&slot.image_sha256) { -+ return Err("layout slot extent or image digest is invalid".to_owned()); -+ } -+ let end = slot -+ .lba_start -+ .checked_add(slot.sector_count) -+ .ok_or_else(|| "layout slot extent overflowed".to_owned())?; -+ if end > payload.first_partition_lba { -+ return Err("layout slot extends into the hosted Linux partition region".to_owned()); -+ } -+ } -+ if slot_a.lba_start + slot_a.sector_count > slot_b.lba_start -+ || slot_b.lba_start + slot_b.sector_count > payload.first_partition_lba -+ { -+ return Err("layout A/B slots overlap".to_owned()); -+ } -+ Ok(()) -+} -+ -+fn sha256_json(value: &impl Serialize) -> Result { -+ let bytes = serde_json::to_vec(value) -+ .map_err(|error| format!("cannot serialize registered evidence: {error}"))?; -+ Ok(format!("{:x}", Sha256::digest(bytes))) -+} -+ -+fn decode_fixed(value: &str, label: &str) -> Result<[u8; N], String> { -+ let bytes = hex::decode(value).map_err(|_| format!("{label} must be hexadecimal"))?; -+ bytes -+ .try_into() -+ .map_err(|_| format!("{label} must contain exactly {N} bytes")) -+} -+ - pub fn run(arguments: Vec) -> Result { - run_with_environment(arguments, Path::new("/"), std::env::consts::ARCH) - } -@@ -284,6 +564,32 @@ pub fn run_with_environment( - reject_extra_arguments(arguments)?; - json_value(validate_recovery_package(Path::new(&package_path))?) - } -+ "layout-plan-payload" => { -+ let plan_path = required_argument(&mut arguments)?; -+ reject_extra_arguments(arguments)?; -+ let plan: SignedLayoutPlan = read_json(Path::new(&plan_path))?; -+ if plan.schema != LAYOUT_PLAN_SCHEMA { -+ return Err("unsupported signed layout plan schema".to_owned()); -+ } -+ let bytes = canonical_layout_plan_payload(&plan.payload)?; -+ return String::from_utf8(bytes) -+ .map_err(|_| "canonical layout plan was not UTF-8".to_owned()); -+ } -+ "verify-signed-layout-plan" => { -+ let manifest_path = required_argument(&mut arguments)?; -+ let plan_path = required_argument(&mut arguments)?; -+ let readback_path = required_argument(&mut arguments)?; -+ let now_unix = required_argument(&mut arguments)? -+ .parse::() -+ .map_err(|_| "now-unix must be an unsigned integer".to_owned())?; -+ reject_extra_arguments(arguments)?; -+ let manifest = read_json(Path::new(&manifest_path))?; -+ let plan = read_json(Path::new(&plan_path))?; -+ let readback = read_json(Path::new(&readback_path))?; -+ json_value(verify_signed_layout_plan( -+ &manifest, &plan, &readback, now_unix, -+ )?) -+ } - _ => return Err(USAGE.to_owned()), - }; - Ok(serde_json::to_string_pretty(&value).expect("JSON Value serialization cannot fail")) -@@ -477,16 +783,24 @@ fn validate_controllers(node_id: &str, controllers: &[ControllerPlan]) -> Result - } - let mut node_ids = HashSet::new(); - let mut failure_domains = HashSet::new(); -+ let mut signing_keys = HashSet::new(); - for controller in controllers { - validate_node_id(&controller.node_id)?; - require_text("failure_domain", &controller.failure_domain)?; - require_text("role", &controller.role)?; -+ decode_fixed::<32>( -+ &controller.signing_public_key_hex, -+ "controller signing public key", -+ )?; - if controller.node_id == node_id { - return Err("target node cannot be its own recovery controller".to_owned()); - } - if !node_ids.insert(controller.node_id.as_str()) { - return Err("recovery controller node ids must be unique".to_owned()); - } -+ if !signing_keys.insert(controller.signing_public_key_hex.as_str()) { -+ return Err("recovery controller signing keys must be unique".to_owned()); -+ } - failure_domains.insert(controller.failure_domain.as_str()); - } - if failure_domains.len() < 2 { -@@ -498,8 +812,8 @@ fn validate_controllers(node_id: &str, controllers: &[ControllerPlan]) -> Result - fn validate_evidence(evidence: &EvidencePlan) -> Result<(), String> { - let fields = [ - ( -- "cloud_image_clone_boot_receipt", -- &evidence.cloud_image_clone_boot_receipt, -+ "linux_rescue_boot_receipt", -+ &evidence.linux_rescue_boot_receipt, - ), - ( - "control_plane_backup_receipt", -@@ -511,8 +825,11 @@ fn validate_evidence(evidence: &EvidencePlan) -> Result<(), String> { - &evidence.provider_console_recovery_receipt, - ), - ]; -- for (label, value) in fields { -- require_text(label, value)?; -+ for (label, receipt) in fields { -+ require_text(label, &receipt.reference)?; -+ if !is_sha256(&receipt.sha256) { -+ return Err(format!("{label} sha256 must be lowercase SHA-256")); -+ } - } - Ok(()) - } -diff --git a/guanghu-os/crates/ghdr/tests/ghdr_command.rs b/guanghu-os/crates/ghdr/tests/ghdr_command.rs -index a1131a6..8ba124a 100644 ---- a/guanghu-os/crates/ghdr/tests/ghdr_command.rs -+++ b/guanghu-os/crates/ghdr/tests/ghdr_command.rs -@@ -5,7 +5,7 @@ use std::{ - }; - - use guanghu_ghdr::{ -- build_manifest, run, BlockDevice, ControllerPlan, EvidencePlan, MigrationPlan, -+ build_manifest, run, BlockDevice, ControllerPlan, EvidencePlan, EvidenceReceipt, MigrationPlan, - NetworkInterface, NodeProbe, - }; - use serde_json::json; -@@ -64,18 +64,32 @@ fn plan() -> MigrationPlan { - node_id: "JD-FD-PRIMARY".to_owned(), - failure_domain: "jdcloud/CN-BEIJING".to_owned(), - role: "control".to_owned(), -+ signing_public_key_hex: "11".repeat(32), - }, - ControllerPlan { - node_id: "BS-SG-003".to_owned(), - failure_domain: "tencent_cloud/SG-BACKUP".to_owned(), - role: "artifact".to_owned(), -+ signing_public_key_hex: "22".repeat(32), - }, - ], - evidence: EvidencePlan { -- cloud_image_clone_boot_receipt: "receipt://clone".to_owned(), -- control_plane_backup_receipt: "receipt://control".to_owned(), -- data_restore_receipt: "receipt://data".to_owned(), -- provider_console_recovery_receipt: "receipt://console".to_owned(), -+ linux_rescue_boot_receipt: EvidenceReceipt { -+ reference: "receipt://linux-rescue".to_owned(), -+ sha256: "aa".repeat(32), -+ }, -+ control_plane_backup_receipt: EvidenceReceipt { -+ reference: "receipt://control".to_owned(), -+ sha256: "bb".repeat(32), -+ }, -+ data_restore_receipt: EvidenceReceipt { -+ reference: "receipt://data".to_owned(), -+ sha256: "cc".repeat(32), -+ }, -+ provider_console_recovery_receipt: EvidenceReceipt { -+ reference: "receipt://console".to_owned(), -+ sha256: "dd".repeat(32), -+ }, - }, - } - } -diff --git a/guanghu-os/crates/ghdr/tests/ghdr_controller.rs b/guanghu-os/crates/ghdr/tests/ghdr_controller.rs -new file mode 100644 -index 0000000..a6688b9 ---- /dev/null -+++ b/guanghu-os/crates/ghdr/tests/ghdr_controller.rs -@@ -0,0 +1,163 @@ -+use std::{ -+ fs, -+ os::unix::fs::{MetadataExt, PermissionsExt}, -+ path::PathBuf, -+ process::Command, -+ sync::atomic::{AtomicU64, Ordering}, -+}; -+ -+use ed25519_dalek::{Signature, Verifier, VerifyingKey}; -+use guanghu_ghdr::{ -+ canonical_layout_plan_payload, ControllerSignature, LayoutPlanPayload, LayoutSlot, -+ SignedLayoutPlan, -+}; -+ -+static TEMP_SEQUENCE: AtomicU64 = AtomicU64::new(0); -+ -+struct TestDirectory(PathBuf); -+ -+impl TestDirectory { -+ fn new() -> Self { -+ let sequence = TEMP_SEQUENCE.fetch_add(1, Ordering::Relaxed); -+ let path = std::env::temp_dir().join(format!( -+ "guanghu-ghdr-controller-{}-{sequence}", -+ std::process::id() -+ )); -+ fs::create_dir_all(&path).expect("create controller fixture"); -+ Self(path) -+ } -+} -+ -+impl Drop for TestDirectory { -+ fn drop(&mut self) { -+ fs::remove_dir_all(&self.0).expect("remove controller fixture"); -+ } -+} -+ -+fn unsigned_plan() -> SignedLayoutPlan { -+ SignedLayoutPlan { -+ schema: "guanghu.ghdr-signed-layout-plan/v1".to_owned(), -+ payload: LayoutPlanPayload { -+ node_id: "GH-CVM-MAIN-PROD-01".to_owned(), -+ provider: "tencent_cloud".to_owned(), -+ region: "ap-shanghai".to_owned(), -+ target_probe_sha256: "11".repeat(32), -+ system_disk: "/dev/vda".to_owned(), -+ disk_sectors: 104857600, -+ logical_sector_bytes: 512, -+ disk_identity_sha256: "22".repeat(32), -+ recovery_evidence_sha256: "55".repeat(32), -+ first_partition_lba: 2048, -+ generation: 1, -+ operation: "install_native_ab".to_owned(), -+ issued_at_unix: 1_000, -+ expires_at_unix: 1_600, -+ slots: vec![ -+ LayoutSlot { -+ name: "A".to_owned(), -+ lba_start: 34, -+ sector_count: 29, -+ image_sha256: "33".repeat(32), -+ }, -+ LayoutSlot { -+ name: "B".to_owned(), -+ lba_start: 73, -+ sector_count: 29, -+ image_sha256: "44".repeat(32), -+ }, -+ ], -+ }, -+ signatures: Vec::new(), -+ } -+} -+ -+#[test] -+fn controller_keeps_private_seed_off_output_and_creates_a_valid_signature() { -+ let fixture = TestDirectory::new(); -+ let private = fixture.0.join("controller.seed"); -+ let public = fixture.0.join("controller-public.json"); -+ let plan_path = fixture.0.join("plan.json"); -+ let signature_path = fixture.0.join("signature.json"); -+ let binary = env!("CARGO_BIN_EXE_ghdr-controller"); -+ -+ let generated = Command::new(binary) -+ .args([ -+ "generate-key", -+ private.to_str().expect("private path"), -+ public.to_str().expect("public path"), -+ "GH-CTRL-A-01", -+ "local/MAC", -+ ]) -+ .output() -+ .expect("run key generation"); -+ assert!(generated.status.success()); -+ assert_eq!( -+ fs::metadata(&private).expect("private metadata").mode() & 0o777, -+ 0o600 -+ ); -+ let secret = fs::read(&private).expect("private seed"); -+ assert_eq!(secret.len(), 32); -+ assert!(!generated -+ .stdout -+ .windows(secret.len()) -+ .any(|window| window == secret)); -+ assert!(!generated -+ .stderr -+ .windows(secret.len()) -+ .any(|window| window == secret)); -+ -+ fs::write( -+ &plan_path, -+ serde_json::to_vec_pretty(&unsigned_plan()).expect("serialize plan"), -+ ) -+ .expect("write plan"); -+ let signed = Command::new(binary) -+ .args([ -+ "sign-layout", -+ private.to_str().expect("private path"), -+ "GH-CTRL-A-01", -+ "local/MAC", -+ plan_path.to_str().expect("plan path"), -+ signature_path.to_str().expect("signature path"), -+ ]) -+ .output() -+ .expect("run signer"); -+ assert!(signed.status.success()); -+ let approval: ControllerSignature = -+ serde_json::from_slice(&fs::read(&signature_path).expect("read signature")) -+ .expect("parse signature"); -+ let public_key: [u8; 32] = hex::decode(&approval.public_key_hex) -+ .expect("public key hex") -+ .try_into() -+ .expect("public key length"); -+ let signature: [u8; 64] = hex::decode(&approval.signature_hex) -+ .expect("signature hex") -+ .try_into() -+ .expect("signature length"); -+ VerifyingKey::from_bytes(&public_key) -+ .expect("valid public key") -+ .verify( -+ &canonical_layout_plan_payload(&unsigned_plan().payload).expect("canonical payload"), -+ &Signature::from_bytes(&signature), -+ ) -+ .expect("external controller signature verifies"); -+ -+ fs::set_permissions(&private, fs::Permissions::from_mode(0o644)) -+ .expect("weaken private permissions"); -+ let refused = Command::new(binary) -+ .args([ -+ "sign-layout", -+ private.to_str().expect("private path"), -+ "GH-CTRL-A-01", -+ "local/MAC", -+ plan_path.to_str().expect("plan path"), -+ fixture -+ .0 -+ .join("refused.json") -+ .to_str() -+ .expect("refused path"), -+ ]) -+ .output() -+ .expect("run permission rejection"); -+ assert!(!refused.status.success()); -+} -diff --git a/guanghu-os/crates/ghdr/tests/ghdr_library.rs b/guanghu-os/crates/ghdr/tests/ghdr_library.rs -index dfcb91c..1e5bede 100644 ---- a/guanghu-os/crates/ghdr/tests/ghdr_library.rs -+++ b/guanghu-os/crates/ghdr/tests/ghdr_library.rs -@@ -5,10 +5,12 @@ use std::{ - sync::atomic::{AtomicU64, Ordering}, - }; - -+use ed25519_dalek::{Signer, SigningKey}; - use guanghu_ghdr::{ -- build_manifest, canonical_artifact, collect_probe, directory_entry, run_with_environment, -- validate_recovery_package, ControllerPlan, EvidencePlan, MigrationPlan, NetworkInterface, -- NodeProbe, -+ build_manifest, canonical_artifact, canonical_layout_plan_payload, collect_probe, -+ directory_entry, run_with_environment, validate_recovery_package, verify_signed_layout_plan, -+ ControllerPlan, ControllerSignature, EvidencePlan, EvidenceReceipt, LayoutPlanPayload, -+ LayoutReadback, LayoutSlot, MigrationPlan, NetworkInterface, NodeProbe, SignedLayoutPlan, - }; - use serde_json::json; - use sha2::{Digest, Sha256}; -@@ -81,18 +83,32 @@ fn ready_plan() -> MigrationPlan { - node_id: "JD-FD-PRIMARY".to_owned(), - failure_domain: "jdcloud/CN-BEIJING".to_owned(), - role: "control".to_owned(), -+ signing_public_key_hex: "11".repeat(32), - }, - ControllerPlan { - node_id: "BS-SG-003".to_owned(), - failure_domain: "tencent_cloud/SG-BACKUP".to_owned(), - role: "artifact".to_owned(), -+ signing_public_key_hex: "22".repeat(32), - }, - ], - evidence: EvidencePlan { -- cloud_image_clone_boot_receipt: "receipt://sg-image-clone-boot".to_owned(), -- control_plane_backup_receipt: "receipt://sg-control-plane".to_owned(), -- data_restore_receipt: "receipt://sg-data-restore".to_owned(), -- provider_console_recovery_receipt: "receipt://sg-console-recovery".to_owned(), -+ linux_rescue_boot_receipt: EvidenceReceipt { -+ reference: "receipt://sg-linux-rescue-boot".to_owned(), -+ sha256: "aa".repeat(32), -+ }, -+ control_plane_backup_receipt: EvidenceReceipt { -+ reference: "receipt://sg-control-plane".to_owned(), -+ sha256: "bb".repeat(32), -+ }, -+ data_restore_receipt: EvidenceReceipt { -+ reference: "receipt://sg-data-restore".to_owned(), -+ sha256: "cc".repeat(32), -+ }, -+ provider_console_recovery_receipt: EvidenceReceipt { -+ reference: "receipt://sg-console-recovery".to_owned(), -+ sha256: "dd".repeat(32), -+ }, - }, - } - } -@@ -203,11 +219,17 @@ fn controllers_must_span_two_failure_domains() { - #[test] - fn missing_restore_evidence_fails_closed() { - let mut plan = ready_plan(); -- plan.evidence.data_restore_receipt.clear(); -+ plan.evidence.data_restore_receipt.reference.clear(); - - let error = build_manifest(ready_probe(), plan).expect_err("missing evidence must fail"); - - assert!(error.contains("data_restore_receipt")); -+ -+ let mut malformed = ready_plan(); -+ malformed.evidence.linux_rescue_boot_receipt.sha256 = "not-a-digest".to_owned(); -+ let error = build_manifest(ready_probe(), malformed) -+ .expect_err("unhashed Linux rescue evidence must fail"); -+ assert!(error.contains("linux_rescue_boot_receipt sha256")); - } - - #[test] -@@ -826,3 +848,114 @@ fn operating_system_error_adapters_and_probe_serialization_are_total() { - .expect("serialize probe"); - assert!(output.contains("ghdr-node-probe")); - } -+ -+#[test] -+fn signed_layout_plan_requires_two_pinned_signatures_and_fresh_exact_readback() { -+ let key_a = SigningKey::from_bytes(&[0x11; 32]); -+ let key_b = SigningKey::from_bytes(&[0x22; 32]); -+ let mut migration = ready_plan(); -+ migration.recovery_controllers[0].signing_public_key_hex = -+ hex::encode(key_a.verifying_key().to_bytes()); -+ migration.recovery_controllers[1].signing_public_key_hex = -+ hex::encode(key_b.verifying_key().to_bytes()); -+ let manifest = build_manifest(ready_probe(), migration).expect("build pinned manifest"); -+ let probe_sha = format!( -+ "{:x}", -+ Sha256::digest(serde_json::to_vec(&manifest.observed_hardware).expect("probe bytes")) -+ ); -+ let evidence_sha = format!( -+ "{:x}", -+ Sha256::digest(serde_json::to_vec(&manifest.evidence).expect("evidence bytes")) -+ ); -+ let payload = LayoutPlanPayload { -+ node_id: manifest.node_id.clone(), -+ provider: manifest.provider.clone(), -+ region: manifest.region.clone(), -+ target_probe_sha256: probe_sha.clone(), -+ system_disk: "/dev/vda".to_owned(), -+ disk_sectors: 104857600, -+ logical_sector_bytes: 512, -+ disk_identity_sha256: "33".repeat(32), -+ recovery_evidence_sha256: evidence_sha, -+ first_partition_lba: 2048, -+ generation: 1, -+ operation: "install_native_ab".to_owned(), -+ issued_at_unix: 1_000, -+ expires_at_unix: 1_600, -+ slots: vec![ -+ LayoutSlot { -+ name: "A".to_owned(), -+ lba_start: 34, -+ sector_count: 29, -+ image_sha256: "44".repeat(32), -+ }, -+ LayoutSlot { -+ name: "B".to_owned(), -+ lba_start: 73, -+ sector_count: 29, -+ image_sha256: "55".repeat(32), -+ }, -+ ], -+ }; -+ let bytes = canonical_layout_plan_payload(&payload).expect("canonical payload"); -+ let signatures = [ -+ (&manifest.recovery_controllers[0], &key_a), -+ (&manifest.recovery_controllers[1], &key_b), -+ ] -+ .into_iter() -+ .map(|(controller, key)| ControllerSignature { -+ node_id: controller.node_id.clone(), -+ failure_domain: controller.failure_domain.clone(), -+ public_key_hex: controller.signing_public_key_hex.clone(), -+ signature_hex: hex::encode(key.sign(&bytes).to_bytes()), -+ }) -+ .collect(); -+ let plan = SignedLayoutPlan { -+ schema: "guanghu.ghdr-signed-layout-plan/v1".to_owned(), -+ payload: payload.clone(), -+ signatures, -+ }; -+ let readback = LayoutReadback { -+ schema: "guanghu.ghdr-layout-readback/v1".to_owned(), -+ node_id: payload.node_id.clone(), -+ observed_at_unix: 1_190, -+ target_probe_sha256: probe_sha, -+ system_disk: payload.system_disk.clone(), -+ disk_sectors: payload.disk_sectors, -+ logical_sector_bytes: payload.logical_sector_bytes, -+ disk_identity_sha256: payload.disk_identity_sha256.clone(), -+ first_partition_lba: payload.first_partition_lba, -+ }; -+ let verified = verify_signed_layout_plan(&manifest, &plan, &readback, 1_200) -+ .expect("two signatures and fresh readback must pass"); -+ assert_eq!(verified.status, "PASS_100_SIGNED_LAYOUT_PLAN"); -+ assert!(verified.allows_disk_write); -+ -+ let mut evidence_drift = manifest.clone(); -+ evidence_drift.evidence.data_restore_receipt.sha256 = "77".repeat(32); -+ assert!( -+ verify_signed_layout_plan(&evidence_drift, &plan, &readback, 1_200) -+ .expect_err("changed recovery evidence must invalidate the signed plan") -+ .contains("exact node manifest") -+ ); -+ -+ let mut one_signature = plan.clone(); -+ one_signature.signatures.pop(); -+ assert!( -+ verify_signed_layout_plan(&manifest, &one_signature, &readback, 1_200) -+ .expect_err("one controller must fail") -+ .contains("exactly two") -+ ); -+ let mut replayed = readback.clone(); -+ replayed.observed_at_unix = 800; -+ assert!( -+ verify_signed_layout_plan(&manifest, &plan, &replayed, 1_200) -+ .expect_err("stale readback must fail") -+ .contains("not fresh") -+ ); -+ let mut drifted = readback; -+ drifted.disk_identity_sha256 = "66".repeat(32); -+ assert!(verify_signed_layout_plan(&manifest, &plan, &drifted, 1_200) -+ .expect_err("disk identity drift must fail") -+ .contains("does not match")); -+} -diff --git a/guanghu-os/crates/hldp-runtime/src/lib.rs b/guanghu-os/crates/hldp-runtime/src/lib.rs -index 534450a..4f4562e 100644 ---- a/guanghu-os/crates/hldp-runtime/src/lib.rs -+++ b/guanghu-os/crates/hldp-runtime/src/lib.rs -@@ -40,7 +40,7 @@ const REQUIRED_GESTATIONAL_SOURCES: [&str; 5] = [ - "local_knowledge_bases", - "registered_receipts_and_checkpoints", - ]; --const REQUIRED_AUTHORIZED_ACTIONS: [&str; 14] = [ -+const REQUIRED_COMMON_AUTHORIZED_ACTIONS: [&str; 11] = [ - "generate_install_dedicated_ssh_key", - "configure_local_ssh_alias", - "install_official_build_toolchain", -@@ -49,12 +49,9 @@ const REQUIRED_AUTHORIZED_ACTIONS: [&str; 14] = [ - "install_verified_forgejo_baseline", - "run_tests_and_health_checks", - "write_hldp_receipts_and_checkpoints", -- "commit_and_push_in_scope_repositories", - "build_native_kernel_and_boot_image", - "write_bootloader_and_system_partitions", - "overwrite_system_disk_and_exit_linux", -- "reboot_and_recover_bs_sh_005", -- "rollback_and_repeat_disposable_server_experiment", - ]; - - #[derive(Debug, Deserialize)] -@@ -172,6 +169,9 @@ pub struct NativeLayoutReference { - pub branch_receipt_lba: u64, - pub recovery_beacon_lba_start: u64, - pub gestational_index_lba_start: u64, -+ pub control_state_lba: u64, -+ pub alternate_kernel_lba_start: u64, -+ pub alternate_kernel_sector_count: u64, - pub first_partition_lba: u64, - } - -@@ -329,11 +329,36 @@ struct NativeRecoveryDocument { - id: String, - acronym: String, - authority_language: String, -+ scope: NativeRecoveryScope, - beacon: NativeRecoveryBeacon, - grub: NativeRecoveryGrub, - hosted_recovery: NativeRecoveryHostedRecovery, - } - -+#[derive(Debug, Deserialize)] -+struct NativeRecoveryScope { -+ node_id: String, -+ system_disk: String, -+} -+ -+#[derive(Debug, Deserialize)] -+struct CurrentDocument { -+ schema: String, -+ node_id: String, -+ authorization: CurrentAuthorization, -+} -+ -+#[derive(Debug, Deserialize)] -+struct CurrentAuthorization { -+ id: String, -+} -+ -+#[derive(Debug, Deserialize)] -+struct WakeDocument { -+ schema: String, -+ node_id: String, -+} -+ - #[derive(Debug, Deserialize)] - struct NativeRecoveryBeacon { - lba_start: u64, -@@ -487,6 +512,10 @@ struct NativeLayoutRegions { - recovery_beacon_sector_count: u64, - gestational_index_lba_start: u64, - gestational_index_sector_count: u64, -+ control_state_lba: u64, -+ control_state_sector_count: u64, -+ alternate_kernel_lba_start: u64, -+ alternate_kernel_sector_count: u64, - first_partition_lba: u64, - } - -@@ -630,10 +659,13 @@ pub fn validate_world_manifest(manifest: &WorldManifest) -> Result<(), ManifestE - || manifest.native_layout.branch_receipt_lba != 67 - || manifest.native_layout.recovery_beacon_lba_start != 68 - || manifest.native_layout.gestational_index_lba_start != 70 -+ || manifest.native_layout.control_state_lba != 72 -+ || manifest.native_layout.alternate_kernel_lba_start != 73 -+ || manifest.native_layout.alternate_kernel_sector_count != 29 - || manifest.native_layout.first_partition_lba != 2048 - { - return invalid( -- "GHNLP must register the exact nonoverlapping LBA 34-71 native layout before partition LBA 2048", -+ "GHNLP must register the exact nonoverlapping shared LBA 34-72 and alternate LBA 73-101 native layout before partition LBA 2048", - ); - } - if manifest.gestational_continuity.id != "GLS-0845" -@@ -772,6 +804,19 @@ pub fn validate_world_seed(world_root: &Path) -> Result(¤t_path)?; -+ let wake_path = resolve_world_path(world_root, &manifest.continuity.wake)?; -+ let wake = read_yaml::(&wake_path)?; -+ validate_target_identity( -+ &manifest, -+ ¤t, -+ &wake, -+ &native_recovery, -+ &native_layout, -+ &authorization, -+ )?; -+ - let checkpoint_directory = - resolve_world_path(world_root, &manifest.continuity.checkpoint_directory)?; - if !checkpoint_directory.is_dir() { -@@ -832,7 +877,7 @@ fn validate_native_layout_document( - } - if document.status != "REGISTERED_IMPLEMENTATION_GATED" - || document.authority_language != "HLDP" -- || document.node_id != "BS-SH-005" -+ || document.node_id.is_empty() - || document.disk != "/dev/vda" - || document.sector_size != 512 - { -@@ -859,6 +904,10 @@ fn validate_native_layout_document( - || document.regions.recovery_beacon_sector_count != 2 - || document.regions.gestational_index_lba_start != reference.gestational_index_lba_start - || document.regions.gestational_index_sector_count != 2 -+ || document.regions.control_state_lba != reference.control_state_lba -+ || document.regions.control_state_sector_count != 1 -+ || document.regions.alternate_kernel_lba_start != reference.alternate_kernel_lba_start -+ || document.regions.alternate_kernel_sector_count != reference.alternate_kernel_sector_count - || document.regions.first_partition_lba != reference.first_partition_lba - { - return invalid("GHNLP regions must match all registered protocol extents"); -@@ -1171,19 +1220,31 @@ fn validate_standing_authorization( - { - return invalid("standing authorization must be active and issued by BingShuo"); - } -- if authorization.target.node_id != "BS-SH-005" -- || authorization.target.instance_id != "lhins-14w5y3ce" -+ if !is_node_id(&authorization.target.node_id) -+ || authorization.target.instance_id.trim().is_empty() - || authorization.target.system_disk != "/dev/vda" - { -- return invalid("standing authorization target must remain the Shanghai lab node"); -- } -- if authorization.user_confirmation -- != "COMPLETE_GUANGHU_OS_SERVER_EXPERIMENT_AUTHORIZED_2026_07_31" -+ return invalid("standing authorization target identity is incomplete or invalid"); -+ } -+ let confirmation_node = authorization.target.node_id.replace('-', "_"); -+ let target_confirmation_prefix = format!("COMPLETE_GUANGHU_OS_{confirmation_node}_AUTHORIZED_"); -+ let is_legacy_confirmation = authorization.target.node_id == "BS-SH-005" -+ && authorization.user_confirmation -+ == "COMPLETE_GUANGHU_OS_SERVER_EXPERIMENT_AUTHORIZED_2026_07_31"; -+ if !is_legacy_confirmation -+ && (!authorization -+ .user_confirmation -+ .starts_with(&target_confirmation_prefix) -+ || authorization.user_confirmation.len() != target_confirmation_prefix.len() + 10) - { -- return invalid("standing authorization must retain the exact user confirmation anchor"); -+ return invalid("standing authorization must retain a target-specific confirmation anchor"); - } - if authorization.automatic_execution.is_empty() - || authorization.boundaries.is_empty() -+ || !authorization -+ .boundaries -+ .iter() -+ .any(|boundary| boundary.contains(&authorization.target.node_id)) - || authorization.valid_until != "OBJECTIVE_COMPLETE_OR_REVOKED_BY_ICE_GL_INFINITY" - { - return invalid("standing authorization execution and boundary rules are incomplete"); -@@ -1194,7 +1255,20 @@ fn validate_standing_authorization( - .iter() - .map(String::as_str) - .collect(); -- let required_actions: HashSet<_> = REQUIRED_AUTHORIZED_ACTIONS.into_iter().collect(); -+ let mut required_actions: HashSet<_> = REQUIRED_COMMON_AUTHORIZED_ACTIONS.into_iter().collect(); -+ let recovery_action = format!( -+ "reboot_and_recover_{}", -+ authorization -+ .target -+ .node_id -+ .to_ascii_lowercase() -+ .replace('-', "_") -+ ); -+ required_actions.insert(recovery_action.as_str()); -+ if authorization.target.node_id == "BS-SH-005" { -+ required_actions.insert("commit_and_push_in_scope_repositories"); -+ required_actions.insert("rollback_and_repeat_disposable_server_experiment"); -+ } - if observed_actions.len() != authorization.authorized_actions.len() - || observed_actions != required_actions - { -@@ -1204,6 +1278,41 @@ fn validate_standing_authorization( - Ok(()) - } - -+fn validate_target_identity( -+ manifest: &WorldManifest, -+ current: &CurrentDocument, -+ wake: &WakeDocument, -+ native_recovery: &NativeRecoveryDocument, -+ native_layout: &NativeLayoutDocument, -+ authorization: &StandingAuthorization, -+) -> Result<(), ManifestError> { -+ if current.schema != "guanghu.current/v1" || wake.schema != "guanghu.wake/v1" { -+ return invalid("continuity target identity documents use unsupported schemas"); -+ } -+ let target = &authorization.target; -+ if current.node_id != target.node_id -+ || wake.node_id != target.node_id -+ || native_recovery.scope.node_id != target.node_id -+ || native_layout.node_id != target.node_id -+ || current.authorization.id != manifest.authorization.id -+ || native_recovery.scope.system_disk != target.system_disk -+ || native_layout.disk != target.system_disk -+ { -+ return invalid( -+ "world target identity must match across CURRENT, WAKE, recovery, layout, and authorization", -+ ); -+ } -+ Ok(()) -+} -+ -+fn is_node_id(value: &str) -> bool { -+ !value.is_empty() -+ && value.len() <= 64 -+ && value -+ .bytes() -+ .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'-') -+} -+ - fn read_yaml(path: &Path) -> Result - where - T: for<'de> Deserialize<'de>, -diff --git a/guanghu-os/crates/hldp-runtime/tests/world_manifest.rs b/guanghu-os/crates/hldp-runtime/tests/world_manifest.rs -index 6a5c355..247767d 100644 ---- a/guanghu-os/crates/hldp-runtime/tests/world_manifest.rs -+++ b/guanghu-os/crates/hldp-runtime/tests/world_manifest.rs -@@ -254,6 +254,9 @@ fn requires_a_registered_nonoverlapping_native_disk_layout() { - assert_eq!(manifest.native_layout.branch_receipt_lba, 67); - assert_eq!(manifest.native_layout.recovery_beacon_lba_start, 68); - assert_eq!(manifest.native_layout.gestational_index_lba_start, 70); -+ assert_eq!(manifest.native_layout.control_state_lba, 72); -+ assert_eq!(manifest.native_layout.alternate_kernel_lba_start, 73); -+ assert_eq!(manifest.native_layout.alternate_kernel_sector_count, 29); - assert_eq!(manifest.native_layout.first_partition_lba, 2048); - } - -@@ -797,6 +800,16 @@ fn rejects_native_disk_layout_contract_drift() { - "gestational_index_lba_start: 69", - "registered protocol extents", - ), -+ ( -+ "control_state_lba: 72", -+ "control_state_lba: 71", -+ "registered protocol extents", -+ ), -+ ( -+ "alternate_kernel_lba_start: 73", -+ "alternate_kernel_lba_start: 72", -+ "registered protocol extents", -+ ), - ( - "unknown_nonzero_state: FAIL_CLOSED_NO_OVERWRITE", - "unknown_nonzero_state: OVERWRITE", -@@ -840,7 +853,11 @@ fn rejects_unregistered_code_channel_phase_and_authorization_drift() { - "authorization mismatch", - ), - ("status: ACTIVE", "status: REVOKED", "active and issued"), -- ("node_id: BS-SH-005", "node_id: OTHER", "Shanghai lab node"), -+ ( -+ "node_id: BS-SH-005", -+ "node_id: OTHER", -+ "target-specific confirmation anchor", -+ ), - ( - "user_confirmation: COMPLETE_GUANGHU_OS_SERVER_EXPERIMENT_AUTHORIZED_2026_07_31", - "user_confirmation: UNKNOWN", -@@ -873,6 +890,126 @@ fn rejects_unregistered_code_channel_phase_and_authorization_drift() { - } - } - -+#[test] -+fn accepts_a_consistently_retargeted_enterprise_world_seed() { -+ let world = TestWorld::copy(); -+ retarget_world(&world, "GH-CVM-MAIN-PROD-01", "ins-dacj5t5a"); -+ -+ validate_world_seed(&world.root) -+ .expect("a consistently retargeted enterprise world must validate"); -+} -+ -+#[test] -+fn validates_the_registered_enterprise_deployment_seed() { -+ let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) -+ .join("../../deployments/GH-CVM-MAIN-PROD-01/world-seed"); -+ -+ let manifest = validate_world_seed(&root) -+ .expect("the checked-in enterprise deployment seed must validate"); -+ assert_eq!( -+ manifest.authorization.id, -+ "GH-OS-AUTH-BINGSHUO-GH-CVM-MAIN-PROD-01-001" -+ ); -+} -+ -+#[test] -+fn rejects_cross_document_target_identity_drift() { -+ for (path, from, to) in [ -+ ("CURRENT.hldp", "node_id: BS-SH-005", "node_id: OTHER-NODE"), -+ ("WAKE.hldp", "node_id: BS-SH-005", "node_id: OTHER-NODE"), -+ ( -+ "world/services/native-recovery/PROTOCOL.hldp", -+ "node_id: BS-SH-005", -+ "node_id: OTHER-NODE", -+ ), -+ ( -+ "world/services/native-storage/DISK-LAYOUT.hldp", -+ "node_id: BS-SH-005", -+ "node_id: OTHER-NODE", -+ ), -+ ] { -+ let world = TestWorld::copy(); -+ world.replace(path, from, to); -+ let error = validate_world_seed(&world.root) -+ .expect_err("target identity drift must fail closed") -+ .to_string(); -+ assert!( -+ error.contains("target identity"), -+ "unexpected error: {error}" -+ ); -+ } -+} -+ -+fn retarget_world(world: &TestWorld, node_id: &str, instance_id: &str) { -+ let action_suffix = node_id.to_ascii_lowercase().replace('-', "_"); -+ let confirmation_node = node_id.replace('-', "_"); -+ -+ for path in ["CURRENT.hldp", "WAKE.hldp"] { -+ world.replace(path, "node_id: BS-SH-005", &format!("node_id: {node_id}")); -+ } -+ world.replace( -+ "WORLD-MANIFEST.hldp", -+ "GH-OS-AUTH-BINGSHUO-BS-SH-005-001", -+ &format!("GH-OS-AUTH-BINGSHUO-{node_id}-001"), -+ ); -+ world.replace( -+ "CURRENT.hldp", -+ "GH-OS-AUTH-BINGSHUO-BS-SH-005-001", -+ &format!("GH-OS-AUTH-BINGSHUO-{node_id}-001"), -+ ); -+ world.replace( -+ "world/services/native-recovery/PROTOCOL.hldp", -+ "node_id: BS-SH-005", -+ &format!("node_id: {node_id}"), -+ ); -+ world.replace( -+ "world/services/native-storage/DISK-LAYOUT.hldp", -+ "node_id: BS-SH-005", -+ &format!("node_id: {node_id}"), -+ ); -+ let authorization = "state/authorizations/BINGSHUO-STANDING-AUTHORIZATION.hldp"; -+ world.replace( -+ authorization, -+ "GH-OS-AUTH-BINGSHUO-BS-SH-005-001", -+ &format!("GH-OS-AUTH-BINGSHUO-{node_id}-001"), -+ ); -+ world.replace( -+ authorization, -+ "node_id: BS-SH-005", -+ &format!("node_id: {node_id}"), -+ ); -+ world.replace( -+ authorization, -+ "instance_id: lhins-14w5y3ce", -+ &format!("instance_id: {instance_id}"), -+ ); -+ world.replace( -+ authorization, -+ "COMPLETE_GUANGHU_OS_SERVER_EXPERIMENT_AUTHORIZED_2026_07_31", -+ &format!("COMPLETE_GUANGHU_OS_{confirmation_node}_AUTHORIZED_2026_08_01"), -+ ); -+ world.replace( -+ authorization, -+ "reboot_and_recover_bs_sh_005", -+ &format!("reboot_and_recover_{action_suffix}"), -+ ); -+ world.replace( -+ authorization, -+ " - commit_and_push_in_scope_repositories", -+ " # repository publication is outside this server authorization", -+ ); -+ world.replace( -+ authorization, -+ " - rollback_and_repeat_disposable_server_experiment", -+ " # enterprise production is not a disposable experiment", -+ ); -+ world.replace( -+ authorization, -+ "不操作 BS-SH-005 以外的服务器", -+ &format!("不操作 {node_id} 以外的服务器"), -+ ); -+} -+ - #[test] - fn invalid_manifest_errors_have_no_nested_source() { - let mut manifest = load_world_manifest(&world_seed()).expect("world seed should parse"); -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/DEVELOPMENT-LINE-20260801-20260803.md b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/DEVELOPMENT-LINE-20260801-20260803.md -new file mode 100644 -index 0000000..fc7efa1 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/DEVELOPMENT-LINE-20260801-20260803.md -@@ -0,0 +1,251 @@ -+# GH-CVM-MAIN-PROD-01 企业光湖 OS 开发线全记录 -+ -+## 1. 记录边界 -+ -+- 开发编号:`DEV-20260801-005` -+- 人类锚点:冰朔 -+- 执行人格:`ICE-P-ZY001` -+- 目标节点:`GH-CVM-MAIN-PROD-01` -+- 云实例:腾讯云 CVM `ins-dacj5t5a`,广州 -+- 系统盘:`/dev/vda` -+- 开发时间:2026-08-01 至 2026-08-03 -+- 结束原因:冰朔明确要求本开发线在成果入库和本机环境清理后结束 -+ -+本记录只保存可复核的目标、判断、设计、动作与回执,不保存密码、验证码、令牌、 -+私钥、聊天正文或模型隐藏推理。 -+ -+## 2. 前因 -+ -+最初问题不是“在 Linux 上再部署一套应用”,而是评估企业服务器能否成为真实光湖 -+OS 节点,并让 Linux 退到后置救援位置。冰朔给出的核心要求是: -+ -+1. 企业服务器可以重新格式化,不需要把测试环境当成不可移动的生产遗产。 -+2. 不购买付费镜像、快照或额外云资源。 -+3. 现有 Linux 可以保留为零费用救援、回传和回滚层。 -+4. 操作不能依赖当前 Mac;线上和线下都必须能通过服务器自有证据与邮件授权恢复。 -+5. 光湖工程按二值规则验收:存在就是 `100/PASS`,缺任一必要证据就是 `0/FAIL`。 -+ -+因此,本线把“原生光湖 OS”拆成可证明的物理层次,而没有把网页在线、Linux 服务健康、 -+源码编译通过或模型回复当成原生启动。 -+ -+## 3. 思维逻辑 -+ -+### 3.1 先把事实层分开 -+ -+本线始终把以下状态分别判断: -+ -+```text -+用户授权 -+→ 源码与测试 -+→ 仓库发布 -+→ 服务器备份 -+→ Linux 救援可启动 -+→ 双控制器签名 -+→ A/B 物理写入 -+→ 一次性原生启动 -+→ 自动返回 Linux -+→ 原生常驻 -+→ 人格体出生 -+``` -+ -+上游状态通过不自动证明下游状态。尤其是: -+ -+- Linux 在线不等于光湖 OS 原生; -+- A/B 镜像存在不等于已经写盘; -+- 写盘回读通过不等于已经可启动; -+- 原生内核回复不等于人格体出生。 -+ -+### 3.2 零费用不等于无灾备 -+ -+不购买云镜像后,灾备改为服务器自有、可校验的四层证据: -+ -+1. 现有业务与系统文件归档; -+2. PostgreSQL 数据恢复演练; -+3. 云厂商控制台可进入; -+4. Linux 救援系统真实重启可返回服务。 -+ -+只有四层都存在,才允许生成时效很短的物理布局工单。这样避免为了安全制造持续云费用, -+也避免在没有回退路径时直接改系统盘。 -+ -+### 3.3 Linux 后置,原生 A/B 前置 -+ -+目标磁盘的第一个 Linux 分区从 LBA 2048 开始。设计只使用分区前、已经登记且回读为空的 -+固定扇区: -+ -+- Slot A:LBA 34–62; -+- Slot B:LBA 73–101; -+- Linux 分区与现有文件系统不移动; -+- B 先写、A 后写; -+- 写入前后都核验磁盘身份、分区边界和镜像 SHA-256; -+- 写入阶段不自动改变 GRUB,也不自动重启。 -+ -+这个布局使原生候选与 Linux 救援层同时存在;任何身份漂移、扇区非空、签名不足或回读 -+不一致都会在第一次写入前失败关闭。 -+ -+### 3.4 双签名不能依赖操作者电脑 -+ -+物理布局采用 `2-of-2` Ed25519 控制器签名。两个控制器: -+ -+- 私钥只留在各自服务器; -+- 只监听本机回环签名入口; -+- 主动通过 HTTPS 轮询京东主控邮件授权服务; -+- 只接受绑定目标、磁盘、工单、布局摘要、控制器和有效期的单次能力; -+- 不开放签名端口,不建立反向 SSH,不向 Mac 返回私钥。 -+ -+京东主控只发布已经通过 HoloLake/小湖灯邮件授权的短时能力。布局变化、重放、过期、 -+目标不符或签名不足一律为 `FAIL_0`。 -+ -+### 3.5 服务器证据优先于对话记忆 -+ -+恢复顺序固定为: -+ -+```text -+服务器 WAKE -+→ CURRENT -+→ 授权与工单 -+→ 备份/恢复/启动回执 -+→ 当前磁盘只读回读 -+→ 仓库完整 SHA -+→ 才允许产生下一动作 -+``` -+ -+聊天摘要、浏览器画面和本机缓存只用于导航,不是最终权威。 -+ -+## 4. 已形成的工程能力 -+ -+### 4.1 GHDR 原生布局与验证 -+ -+- 固定 A/B 扇区、磁盘身份和首分区边界验证; -+- 新鲜目标回读与防 TOCTOU 二次核验; -+- `2-of-2` 控制器绑定、有效期、重放与错误目标拒绝; -+- 写前扇区为空验证; -+- B/A 顺序写入与逐槽 SHA-256 回读; -+- 完整的写前首尾磁盘备份和安装回执; -+- 写入完成后仍保持 `native_boot_armed: false`。 -+ -+### 4.2 原生候选与网络回执 -+ -+- BIOS 原生入口、GHAL virtio 网络路径和恢复信标; -+- QEMU 网络对端与原生控制授权测试; -+- 企业身份绑定、恢复信标协议与二值质量门; -+- 物理候选和常驻候选构建、测试脚本。 -+ -+### 4.3 企业世界种子 -+ -+节点种子包含: -+ -+- 五域入口; -+- 原生存储与恢复协议; -+- 代码频道控制面契约; -+- 孕育连续性与人格出生条件; -+- 授权、工单、检查点和阶段回执; -+- `WAKE → CURRENT → receipt/workorder/authorization` 恢复链。 -+ -+### 4.4 邮件授权双控制器 -+ -+第五域代码频道已经发布: -+ -+- GHDR 邮件授权器; -+- 控制器任务代理与结果回传; -+- 目标导航图; -+- 邮件批准后才允许布局签名的门禁; -+- 控制器传输端点测试。 -+ -+对应历史远端提交包括: -+ -+- `bec7a3d`:邮件授权 GHDR 双签; -+- `12517bf`:控制器传输端点测试; -+- `a385249`:强制邮件批准后才能进行原生布局签名。 -+ -+## 5. 真实服务器动作与回执 -+ -+### 5.1 已通过 -+ -+- 数据恢复演练:`PASS_100_DATA_RESTORE_DRILL` -+- 控制面备份:`PASS_100_CONTROL_PLANE_BACKUP` -+- 云控制台管理员会话恢复:`PASS_100_PROVIDER_CONSOLE_ADMIN_SESSION_RECOVERY_NO_REBOOT` -+- Linux 救援启动故障修复: -+ - 根因是 `/etc/fstab` 仍挂载不存在的 `/dev/vdb`; -+ - 原文件保留为服务器内 `fstab_bak`; -+ - 只移除 `/dev/vdb /data ext4 defaults 0 0`; -+ - `findmnt --verify` 返回 0 错误、0 警告; -+ - systemd 正常到达 Ubuntu 登录界面; -+ - ICMP 3/3,HTTP 200,HTTPS 200。 -+ -+### 5.2 失败是怎样发生的 -+ -+第一次真实重启不是光湖 OS 启动。此时: -+ -+- A/B 镜像尚未写入 `/dev/vda`; -+- GRUB 尚未武装原生入口; -+- 重启目标只是验证 Linux 救援层。 -+ -+Linux 启动时等待不存在的 `/dev/vdb` 90 秒,随后 `/data` 和本地文件系统依赖失败, -+进入 `emergency.target`。一次性 `fstab=no` 证明了故障来源,但根文件系统只读;最终 -+使用一次性 `rw init=/bin/bash` 进入维护环境,保留原配置、完成单行修复并切回 systemd。 -+ -+这次失败建立了一个必须长期保留的判断: -+ -+> “服务器没起来”必须先确定失败对象。没有安装和武装的光湖 OS 不可能被描述为 -+> “启动失败”;本次失败对象是 Linux 救援层。 -+ -+## 6. 本线结束时的二值状态 -+ -+| 对象 | 结果 | 证据边界 | -+|---|---|---| -+| 企业资产封存 | `PASS_100` | 归档与 SHA-256 回执存在 | -+| 数据恢复演练 | `PASS_100` | 隔离恢复和验证回执存在 | -+| 云控制台恢复 | `PASS_100` | 管理员会话回执存在 | -+| Linux 救援可启动 | `PASS_100` | VNC 正常登录界面、网络与 80/443 服务回读 | -+| 邮件授权双签源码 | `PASS_100` | 已发布提交与测试 | -+| 原生 A/B 物理写入 | `FAIL_0_NOT_WRITTEN` | 从未执行写盘 | -+| 原生启动入口 | `FAIL_0_NOT_ARMED` | 未改 GRUB、未武装一次性启动 | -+| 光湖 OS 原生常驻 | `FAIL_0_NOT_NATIVE` | 没有原生启动回执 | -+| 企业人格体出生 | `FAIL_0_NOT_BORN` | 没有出生条件回执 | -+ -+## 7. 源码收口验证 -+ -+结束前重新执行了以下门禁: -+ -+- `cargo fmt --all -- --check`:通过; -+- Guanghu OS Rust 全工作区测试:84 项通过,0 项失败; -+- 控制器签名器、回环 HTTP 能力和主动轮询契约:全部 `PASS_100`; -+- 原生控制授权 Python 测试:4 项通过; -+- 签名 A/B 安装器契约:通过; -+- GH-CVM 身份绑定的 BIOS 物理候选: -+ - SHA-256 `4b762d41cc952e131a2fc5d3b2eeb1655708fa69d4906b06a684dc22198fdc5d`; -+ - QEMU 物理布局、virtio block/net、代码频道、孕育索引和双控制器重放拒绝通过; -+- GH-CVM 身份绑定的常驻候选: -+ - SHA-256 `bd4b51be1b9697b62e7b67720c1c745130f14b34077db11a845591db48209c78`; -+ - 常驻登录、认证恢复、跨启动 nonce、未知控制状态失败关闭和孕育索引保留通过; -+- 原生恢复信标契约:通过; -+- `git diff --check`:通过; -+- CodeScene:本机只有 CLI,没有现成访问令牌,记为 `not_run_unconfigured`; -+- Codacy:仓库没有可用的本地 CLI,记为 `not_run_unavailable`; -+- 脱敏扫描未发现实际私钥、密码、令牌或 API 密钥文件。 -+ -+上述候选测试只证明 QEMU 中的工程能力,不证明企业服务器已经物理写入或原生启动。 -+ -+## 8. 为什么在这里结束 -+ -+冰朔明确要求本开发线在成果入库和本机环境清理后结束。因此: -+ -+- 不继续生成短时布局工单; -+- 不继续请求 2-of-2 生产签名; -+- 不写 `/dev/vda` A/B 扇区; -+- 不修改 GRUB; -+- 不重启到原生候选; -+- 不保留自动心跳或后台续作。 -+ -+这不是“原生部署完成”,而是“本次开发线完成收口”。未来若再次继续,必须由冰朔明确 -+重开任务,重新读取线上代码频道、服务器回执、磁盘身份、Linux 救援状态和控制器状态, -+不得沿用本文件中的瞬时在线结论直接写盘。 -+ -+## 9. 可复用原则 -+ -+1. 原生系统迁移先证明回退,再讨论写入。 -+2. 零费用方案仍须有可验证灾备,不用付费资源代替工程判断。 -+3. 私钥留在服务器;授权传递短时、单次、精确绑定的能力。 -+4. 代码、发布、部署、启动、常驻和出生分别验收。 -+5. 所有危险动作都需要写前读回、写后读回和独立恢复路径。 -+6. 任务结束必须释放租约、停止心跳、清理可再生构建缓存,并保留源码与回执。 -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/.gitignore b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/.gitignore -new file mode 100644 -index 0000000..7a60b85 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/.gitignore -@@ -0,0 +1,2 @@ -+__pycache__/ -+*.pyc -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/README.md b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/README.md -new file mode 100644 -index 0000000..1e82013 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/README.md -@@ -0,0 +1,43 @@ -+# GH-CVM-MAIN-PROD-01 controller signer -+ -+This package installs one fixed-purpose Ed25519 signer on a recovery controller. -+It never prints or exports its private key. The only accepted input is a fresh, -+canonical, unsigned GHDR plan for `GH-CVM-MAIN-PROD-01`, provider -+`tencent_cloud`, region `ap-guangzhou`, disk `/dev/vda`, and the fixed A/B -+sectors. -+ -+The signing entry is a loopback-only HTTP service. A separate low-privilege -+poller makes outbound HTTPS requests to JD-FD-PRIMARY after installation. It -+authenticates those requests with a dedicated transport key, receives only jobs -+that already passed HoloLake/Lake Lamp email authorization, and submits the -+result to the same HTTPS control plane. No inbound signer port, reverse SSH -+tunnel, or operator Mac is required. -+ -+The controller pins the JD authorizer public key and accepts only a two-minute, -+single-use Ed25519 capability bound to the exact controller, target, workorder, -+layout digest, resource, and generation. It cannot accept a shell, another -+target, a changed layout, an expired capability, or a replay. The layout key -+and transport key are separate and neither private key is returned by any -+health, polling, signing, or result endpoint. -+ -+Install one controller at a time: -+ -+```sh -+sudo env \ -+ GHDR_CONTROLLER_NODE_ID=GH-CTRL-GZ-01 \ -+ GHDR_CONTROLLER_FAILURE_DOMAIN=tencent/ap-guangzhou/BS-GZ-006 \ -+ sh install-controller-signer.sh -+``` -+ -+Only after the JD control plane has generated its dedicated authorizer key, -+install the public half. This enables both the loopback signer and the outbound -+poller: -+ -+```sh -+sudo sh install-jd-forced-key.sh /path/to/jd-authorizer-public.pem -+``` -+ -+The public controller binding is -+`/etc/guanghu/ghdr-controller-public-binding.json`. -+The independent transport binding is -+`/etc/guanghu/ghdr-controller-transport-binding.json`. -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-controller-poller.py b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-controller-poller.py -new file mode 100644 -index 0000000..658ee8f ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-controller-poller.py -@@ -0,0 +1,130 @@ -+#!/usr/bin/env python3 -+"""Outbound-only controller agent for JD email-authorized GHDR jobs.""" -+ -+import base64 -+import json -+import os -+import secrets -+import subprocess -+import tempfile -+import time -+import urllib.error -+import urllib.request -+ -+ -+def required_env(name): -+ value = os.environ.get(name, "").strip() -+ if not value: -+ raise RuntimeError(f"missing environment binding: {name}") -+ return value -+ -+ -+def canonical(value): -+ return json.dumps(value, ensure_ascii=False, separators=(",", ":")).encode() -+ -+ -+def sign_envelope(value): -+ key = required_env("GHDR_TRANSPORT_PRIVATE_KEY") -+ with tempfile.TemporaryDirectory(prefix="ghdr-poll-auth-") as directory: -+ message = os.path.join(directory, "message.json") -+ signature = os.path.join(directory, "signature.bin") -+ with open(message, "xb") as handle: -+ handle.write(canonical(value)) -+ completed = subprocess.run([ -+ "/usr/bin/openssl", "pkeyutl", "-sign", "-rawin", -+ "-inkey", key, "-in", message, "-out", signature, -+ ], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=15) -+ if completed.returncode != 0: -+ raise RuntimeError("transport signing failed") -+ with open(signature, "rb") as handle: -+ return handle.read().hex() -+ -+ -+def post(path, value): -+ base = required_env("GHDR_JD_AUTHZ_URL").rstrip("/") -+ if not base.startswith("https://"): -+ raise RuntimeError("JD authorization URL must use HTTPS") -+ request = urllib.request.Request( -+ base + path, -+ data=canonical(value), -+ method="POST", -+ headers={"content-type": "application/json", "user-agent": "Guanghu-GHDR-Poller/1"}, -+ ) -+ with urllib.request.urlopen(request, timeout=20) as response: -+ if response.status != 200: -+ raise RuntimeError("JD authorization endpoint refused the request") -+ return json.loads(response.read(131072)) -+ -+ -+def controller_request(schema, extra=None): -+ value = { -+ "schema": schema, -+ "node_id": required_env("GHDR_CONTROLLER_NODE_ID"), -+ } -+ if extra: -+ value.update(extra) -+ value.update({ -+ "issued_at_unix": int(time.time()), -+ "nonce": base64.urlsafe_b64encode(secrets.token_bytes(24)).rstrip(b"=").decode(), -+ }) -+ return value -+ -+ -+def sign_job(job): -+ authorization = job["authorization"] -+ request = { -+ **authorization, -+ "plan": job["plan"], -+ } -+ local = urllib.request.Request( -+ "http://127.0.0.1:3941/sign", -+ data=canonical(request), -+ method="POST", -+ headers={"content-type": "application/json"}, -+ ) -+ with urllib.request.urlopen(local, timeout=20) as response: -+ value = json.loads(response.read(131072)) -+ if value.get("ok") is not True: -+ raise RuntimeError("local signer refused the authorized job") -+ return value["signature"] -+ -+ -+def one_cycle(): -+ request = controller_request("guanghu.ghdr-controller-poll/v1") -+ polled = post("/api/ghdr/controllers/poll", { -+ "request": request, -+ "request_signature_hex": sign_envelope(request), -+ }) -+ job = polled.get("job") -+ if not job: -+ return -+ signature = sign_job(job) -+ result_request = controller_request( -+ "guanghu.ghdr-controller-result/v1", -+ { -+ "job_id": job["job_id"], -+ "layout_payload_sha256": job["layout_payload_sha256"], -+ "signature_hex": signature["signature_hex"], -+ }, -+ ) -+ submitted = post("/api/ghdr/controllers/result", { -+ "request": result_request, -+ "request_signature_hex": sign_envelope(result_request), -+ "signature": signature, -+ }) -+ if submitted.get("ok") is not True: -+ raise RuntimeError("JD authorization endpoint refused the signed result") -+ -+ -+def main(): -+ interval = max(5, int(os.environ.get("GHDR_POLL_INTERVAL_SECONDS", "10"))) -+ while True: -+ try: -+ one_cycle() -+ except (OSError, RuntimeError, ValueError, urllib.error.URLError): -+ pass -+ time.sleep(interval) -+ -+ -+if __name__ == "__main__": -+ main() -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-controller-poller.service b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-controller-poller.service -new file mode 100644 -index 0000000..86ec502 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-controller-poller.service -@@ -0,0 +1,33 @@ -+[Unit] -+Description=Guanghu GHDR outbound controller poller -+After=network-online.target guanghu-ghdr-signer.service -+Wants=network-online.target -+Requires=guanghu-ghdr-signer.service -+ConditionPathExists=/etc/guanghu/ghdr-authorizer-public.pem -+ -+[Service] -+Type=simple -+User=ghdrpoller -+Group=ghdrpoller -+EnvironmentFile=/etc/guanghu/ghdr-controller-poller.env -+ExecStart=/usr/bin/python3 /usr/local/libexec/guanghu-ghdr-controller-poller.py -+Restart=always -+RestartSec=5s -+NoNewPrivileges=true -+PrivateTmp=true -+PrivateDevices=true -+ProtectSystem=strict -+ProtectHome=true -+ProtectKernelTunables=true -+ProtectKernelModules=true -+ProtectControlGroups=true -+LockPersonality=true -+MemoryDenyWriteExecute=true -+RestrictRealtime=true -+RestrictSUIDSGID=true -+RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX -+ReadOnlyPaths=/etc/guanghu -+UMask=0077 -+ -+[Install] -+WantedBy=multi-user.target -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-signer-http.py b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-signer-http.py -new file mode 100644 -index 0000000..6fa47e2 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-signer-http.py -@@ -0,0 +1,195 @@ -+#!/usr/bin/env python3 -+"""Loopback-only GHDR signer authorized by a short JD email capability.""" -+ -+import base64 -+import hashlib -+import http.server -+import importlib.util -+import json -+import os -+import pathlib -+import re -+import socketserver -+import tempfile -+import time -+ -+SIGNER_PATH = pathlib.Path(__file__).with_name("guanghu-ghdr-signer.py") -+SIGNER_SPEC = importlib.util.spec_from_file_location("guanghu_ghdr_signer", SIGNER_PATH) -+if SIGNER_SPEC is None or SIGNER_SPEC.loader is None: -+ raise SystemExit("GHDR_SIGNER_FAIL_0: signer module unavailable") -+signer = importlib.util.module_from_spec(SIGNER_SPEC) -+SIGNER_SPEC.loader.exec_module(signer) -+ -+MAX_INPUT_BYTES = 64 * 1024 -+CAPABILITY_FIELDS = [ -+ "schema", -+ "authorizer_id", -+ "controller_node_id", -+ "target_node_id", -+ "layout_payload_sha256", -+ "resource", -+ "workorder_id", -+ "issued_at_unix", -+ "expires_at_unix", -+ "nonce", -+] -+ -+ -+def base64url_decode(value): -+ if not isinstance(value, str) or not re.fullmatch(r"[A-Za-z0-9_-]{80,100}", value): -+ raise signer.Refused("capability signature is invalid") -+ return base64.urlsafe_b64decode(value + "=" * (-len(value) % 4)) -+ -+ -+def canonical_json(value): -+ return json.dumps(value, ensure_ascii=False, separators=(",", ":")).encode("utf-8") -+ -+ -+def verify_capability(request): -+ if list(request) != ["capability", "capability_signature_base64url", "plan"]: -+ raise signer.Refused("authorized request fields are not canonical") -+ capability = request["capability"] -+ if not isinstance(capability, dict) or list(capability) != CAPABILITY_FIELDS: -+ raise signer.Refused("capability fields are not canonical") -+ if capability["schema"] != "guanghu.ghdr-signing-capability/v1": -+ raise signer.Refused("capability schema is not supported") -+ if capability["authorizer_id"] != "JD-FD-PRIMARY-LAKE-LAMP": -+ raise signer.Refused("capability authorizer is not trusted") -+ if capability["controller_node_id"] != signer.required_env("GHDR_CONTROLLER_NODE_ID"): -+ raise signer.Refused("capability is for another controller") -+ if capability["target_node_id"] != signer.required_env("GHDR_TARGET_NODE_ID"): -+ raise signer.Refused("capability is for another target") -+ if not signer.sha256_hex(capability["layout_payload_sha256"]): -+ raise signer.Refused("capability layout digest is invalid") -+ if not re.fullmatch( -+ r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", -+ str(capability["workorder_id"]), -+ ): -+ raise signer.Refused("capability workorder binding is invalid") -+ if not re.fullmatch(r"[A-Za-z0-9_-]{32}", str(capability["nonce"])): -+ raise signer.Refused("capability nonce is invalid") -+ -+ now = int(time.time()) -+ issued = capability["issued_at_unix"] -+ expires = capability["expires_at_unix"] -+ if not isinstance(issued, int) or not isinstance(expires, int): -+ raise signer.Refused("capability validity fields must be integers") -+ if issued > now + 5 or now >= expires or expires - issued > 120 or expires <= issued: -+ raise signer.Refused("capability is not currently valid for at most two minutes") -+ -+ plan = request["plan"] -+ payload = signer.validate_request(plan) -+ payload_digest = hashlib.sha256(canonical_json(payload)).hexdigest() -+ generation = payload["generation"] -+ expected_resource = f"{capability['target_node_id']}:{payload_digest}:{generation}" -+ if capability["layout_payload_sha256"] != payload_digest: -+ raise signer.Refused("capability is for another layout") -+ if capability["resource"] != expected_resource: -+ raise signer.Refused("capability resource does not match the layout") -+ -+ authorizer_public_key = signer.required_env("GHDR_AUTHORIZER_PUBLIC_KEY") -+ metadata = os.lstat(authorizer_public_key) -+ if not pathlib.Path(authorizer_public_key).is_file() or pathlib.Path(authorizer_public_key).is_symlink(): -+ raise signer.Refused("authorizer public key path is invalid") -+ if metadata.st_mode & 0o022: -+ raise signer.Refused("authorizer public key must not be writable by group or others") -+ signature = base64url_decode(request["capability_signature_base64url"]) -+ if len(signature) != 64: -+ raise signer.Refused("capability signature length is invalid") -+ with tempfile.TemporaryDirectory(prefix="ghdr-capability-") as directory: -+ message_path = os.path.join(directory, "capability.json") -+ signature_path = os.path.join(directory, "capability.sig") -+ with open(message_path, "xb") as handle: -+ handle.write(canonical_json(capability)) -+ with open(signature_path, "xb") as handle: -+ handle.write(signature) -+ signer.run_openssl([ -+ "pkeyutl", -+ "-verify", -+ "-rawin", -+ "-pubin", -+ "-inkey", -+ authorizer_public_key, -+ "-in", -+ message_path, -+ "-sigfile", -+ signature_path, -+ ]) -+ return payload, hashlib.sha256(canonical_json(capability)).hexdigest() -+ -+ -+def claim_once(capability_digest): -+ used_dir = pathlib.Path(signer.required_env("GHDR_USED_CAPABILITY_DIR")) -+ used_dir.mkdir(parents=True, exist_ok=True, mode=0o700) -+ used_dir.chmod(0o700) -+ marker = used_dir / capability_digest -+ try: -+ descriptor = os.open(marker, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) -+ except FileExistsError as error: -+ raise signer.Refused("capability was already used") from error -+ with os.fdopen(descriptor, "w", encoding="ascii") as handle: -+ handle.write(f"{int(time.time())}\n") -+ handle.flush() -+ os.fsync(handle.fileno()) -+ -+ -+class Handler(http.server.BaseHTTPRequestHandler): -+ server_version = "GuanghuGHDRSigner/1" -+ -+ def send_json(self, status, value): -+ payload = canonical_json(value) -+ self.send_response(status) -+ self.send_header("content-type", "application/json") -+ self.send_header("content-length", str(len(payload))) -+ self.send_header("cache-control", "no-store") -+ self.end_headers() -+ self.wfile.write(payload) -+ -+ def do_GET(self): -+ if self.path != "/health": -+ return self.send_json(404, {"ok": False, "error": "not_found"}) -+ return self.send_json(200, { -+ "ok": True, -+ "node_id": signer.required_env("GHDR_CONTROLLER_NODE_ID"), -+ "failure_domain": signer.required_env("GHDR_CONTROLLER_FAILURE_DOMAIN"), -+ "private_key_exportable": False, -+ "authorization": "JD-FD-PRIMARY email capability", -+ }) -+ -+ def do_POST(self): -+ if self.path != "/sign": -+ return self.send_json(404, {"ok": False, "error": "not_found"}) -+ try: -+ length = int(self.headers.get("content-length", "0")) -+ if length < 1 or length > MAX_INPUT_BYTES: -+ raise signer.Refused("authorized request is empty or too large") -+ raw = self.rfile.read(length) -+ request = json.loads(raw) -+ if not isinstance(request, dict): -+ raise signer.Refused("authorized request must be an object") -+ payload, capability_digest = verify_capability(request) -+ claim_once(capability_digest) -+ signature = signer.sign_to_value(payload) -+ return self.send_json(200, {"ok": True, "signature": signature}) -+ except (OSError, ValueError, signer.Refused) as error: -+ return self.send_json(403, {"ok": False, "error": str(error)}) -+ -+ def log_message(self, _format, *_args): -+ return -+ -+ -+class Server(socketserver.ThreadingMixIn, http.server.HTTPServer): -+ daemon_threads = True -+ allow_reuse_address = True -+ -+ -+def main(): -+ port = int(os.environ.get("GHDR_SIGNER_PORT", "3941")) -+ if not 1024 <= port <= 65535: -+ raise SystemExit("GHDR_SIGNER_FAIL_0: invalid loopback port") -+ with Server(("127.0.0.1", port), Handler) as server: -+ server.serve_forever() -+ -+ -+if __name__ == "__main__": -+ main() -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-signer.py b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-signer.py -new file mode 100755 -index 0000000..4581d62 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-signer.py -@@ -0,0 +1,206 @@ -+#!/usr/bin/env python3 -+"""Fail-closed Ed25519 signer for one GHDR production layout.""" -+ -+import hashlib -+import json -+import os -+import stat -+import subprocess -+import sys -+import tempfile -+import time -+ -+MAX_INPUT_BYTES = 64 * 1024 -+PAYLOAD_FIELDS = [ -+ "node_id", -+ "provider", -+ "region", -+ "target_probe_sha256", -+ "system_disk", -+ "disk_sectors", -+ "logical_sector_bytes", -+ "disk_identity_sha256", -+ "recovery_evidence_sha256", -+ "first_partition_lba", -+ "generation", -+ "operation", -+ "issued_at_unix", -+ "expires_at_unix", -+ "slots", -+] -+SLOT_FIELDS = ["name", "lba_start", "sector_count", "image_sha256"] -+ -+ -+class Refused(ValueError): -+ pass -+ -+ -+def required_env(name): -+ value = os.environ.get(name, "").strip() -+ if not value: -+ raise Refused(f"missing environment binding: {name}") -+ return value -+ -+ -+def sha256_hex(value): -+ return ( -+ isinstance(value, str) -+ and len(value) == 64 -+ and all(character in "0123456789abcdef" for character in value) -+ ) -+ -+ -+def read_request(): -+ raw = sys.stdin.buffer.read(MAX_INPUT_BYTES + 1) -+ if not raw or len(raw) > MAX_INPUT_BYTES: -+ raise Refused("layout request is empty or too large") -+ try: -+ request = json.loads(raw) -+ except (UnicodeDecodeError, json.JSONDecodeError) as error: -+ raise Refused("layout request is not valid UTF-8 JSON") from error -+ if not isinstance(request, dict): -+ raise Refused("layout request must be an object") -+ return request -+ -+ -+def validate_request(request): -+ if list(request) != ["schema", "payload", "signatures"]: -+ raise Refused("layout request fields or field order are not canonical") -+ if request["schema"] != "guanghu.ghdr-signed-layout-plan/v1": -+ raise Refused("layout schema is not supported") -+ if request["signatures"] != []: -+ raise Refused("controller only signs a canonical unsigned plan") -+ -+ payload = request["payload"] -+ if not isinstance(payload, dict) or list(payload) != PAYLOAD_FIELDS: -+ raise Refused("layout payload fields or field order are not canonical") -+ expected = { -+ "node_id": required_env("GHDR_TARGET_NODE_ID"), -+ "provider": required_env("GHDR_TARGET_PROVIDER"), -+ "region": required_env("GHDR_TARGET_REGION"), -+ "system_disk": "/dev/vda", -+ "logical_sector_bytes": 512, -+ "first_partition_lba": 2048, -+ "operation": "install_native_ab", -+ } -+ for field, value in expected.items(): -+ if payload.get(field) != value: -+ raise Refused(f"layout payload is outside the fixed binding: {field}") -+ -+ for field in ( -+ "target_probe_sha256", -+ "disk_identity_sha256", -+ "recovery_evidence_sha256", -+ ): -+ if not sha256_hex(payload.get(field)): -+ raise Refused(f"layout payload has an invalid SHA-256: {field}") -+ for field in ("disk_sectors", "generation"): -+ if not isinstance(payload.get(field), int) or payload[field] < 1: -+ raise Refused(f"layout payload has an invalid integer: {field}") -+ -+ now = int(time.time()) -+ issued = payload.get("issued_at_unix") -+ expires = payload.get("expires_at_unix") -+ if not isinstance(issued, int) or not isinstance(expires, int): -+ raise Refused("layout validity fields must be integers") -+ if issued > now or now >= expires or expires - issued > 3600: -+ raise Refused("layout request is not currently valid for at most one hour") -+ -+ slots = payload.get("slots") -+ if not isinstance(slots, list) or len(slots) != 2: -+ raise Refused("layout must contain exactly the fixed A/B slots") -+ fixed_slots = (("A", 34, 29), ("B", 73, 29)) -+ for slot, fixed in zip(slots, fixed_slots): -+ if not isinstance(slot, dict) or list(slot) != SLOT_FIELDS: -+ raise Refused("slot fields or field order are not canonical") -+ if (slot.get("name"), slot.get("lba_start"), slot.get("sector_count")) != fixed: -+ raise Refused("slot is outside the fixed A/B disk boundary") -+ if not sha256_hex(slot.get("image_sha256")): -+ raise Refused("slot image SHA-256 is invalid") -+ return payload -+ -+ -+def validate_key(path): -+ metadata = os.lstat(path) -+ if not stat.S_ISREG(metadata.st_mode) or metadata.st_mode & 0o077: -+ raise Refused("private key must be a regular file inaccessible to group and others") -+ -+ -+def run_openssl(arguments, *, input_bytes=None): -+ openssl_bin = os.environ.get("GHDR_OPENSSL_BIN", "/usr/bin/openssl") -+ completed = subprocess.run( -+ [openssl_bin, *arguments], -+ input=input_bytes, -+ stdout=subprocess.PIPE, -+ stderr=subprocess.PIPE, -+ check=False, -+ timeout=15, -+ ) -+ if completed.returncode != 0: -+ raise Refused("OpenSSL Ed25519 operation failed") -+ return completed.stdout -+ -+ -+def sign_to_value(payload): -+ key_path = required_env("GHDR_SIGNER_PRIVATE_KEY") -+ validate_key(key_path) -+ canonical = json.dumps( -+ payload, ensure_ascii=False, separators=(",", ":") -+ ).encode("utf-8") -+ public_der = run_openssl(["pkey", "-in", key_path, "-pubout", "-outform", "DER"]) -+ if len(public_der) < 32: -+ raise Refused("Ed25519 public key output is invalid") -+ public_key = public_der[-32:] -+ with tempfile.TemporaryDirectory(prefix="ghdr-sign-") as directory: -+ payload_path = os.path.join(directory, "payload.json") -+ signature_path = os.path.join(directory, "signature.bin") -+ with open(payload_path, "xb") as handle: -+ handle.write(canonical) -+ handle.flush() -+ os.fsync(handle.fileno()) -+ run_openssl( -+ [ -+ "pkeyutl", -+ "-sign", -+ "-rawin", -+ "-inkey", -+ key_path, -+ "-in", -+ payload_path, -+ "-out", -+ signature_path, -+ ] -+ ) -+ with open(signature_path, "rb") as handle: -+ signature = handle.read() -+ if len(signature) != 64: -+ raise Refused("Ed25519 signature output is invalid") -+ digest = hashlib.sha256(canonical).hexdigest() -+ return { -+ "node_id": required_env("GHDR_CONTROLLER_NODE_ID"), -+ "failure_domain": required_env("GHDR_CONTROLLER_FAILURE_DOMAIN"), -+ "public_key_hex": public_key.hex(), -+ "signature_hex": signature.hex(), -+ } -+ -+ -+def sign(payload): -+ response = sign_to_value(payload) -+ digest = hashlib.sha256( -+ json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8") -+ ).hexdigest() -+ print(json.dumps(response, ensure_ascii=False, separators=(",", ":"))) -+ print(f"GHDR_SIGNED_PAYLOAD_SHA256={digest}", file=sys.stderr) -+ -+ -+def main(): -+ try: -+ sign(validate_request(read_request())) -+ except (OSError, Refused, subprocess.SubprocessError) as error: -+ print(f"GHDR_SIGNER_FAIL_0: {error}", file=sys.stderr) -+ return 65 -+ return 0 -+ -+ -+if __name__ == "__main__": -+ raise SystemExit(main()) -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-signer.service b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-signer.service -new file mode 100644 -index 0000000..dc490cd ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/guanghu-ghdr-signer.service -@@ -0,0 +1,34 @@ -+[Unit] -+Description=Guanghu GHDR email-authorized controller signer -+After=network.target -+ConditionPathExists=/etc/guanghu/ghdr-authorizer-public.pem -+ -+[Service] -+Type=simple -+User=ghdrsigner -+Group=ghdrsigner -+EnvironmentFile=/etc/guanghu/ghdr-controller.env -+ExecStart=/usr/bin/python3 /usr/local/libexec/guanghu-ghdr-signer-http.py -+Restart=on-failure -+RestartSec=5s -+NoNewPrivileges=true -+PrivateTmp=true -+PrivateDevices=true -+ProtectSystem=strict -+ProtectHome=true -+ProtectKernelTunables=true -+ProtectKernelModules=true -+ProtectControlGroups=true -+LockPersonality=true -+MemoryDenyWriteExecute=true -+RestrictRealtime=true -+RestrictSUIDSGID=true -+RestrictAddressFamilies=AF_INET AF_UNIX -+IPAddressDeny=any -+IPAddressAllow=localhost -+ReadOnlyPaths=/etc/guanghu -+ReadWritePaths=/var/lib/guanghu/ghdr-signer -+UMask=0077 -+ -+[Install] -+WantedBy=multi-user.target -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/install-controller-signer.sh b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/install-controller-signer.sh -new file mode 100755 -index 0000000..a774ee6 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/install-controller-signer.sh -@@ -0,0 +1,159 @@ -+#!/bin/sh -+set -eu -+ -+if [ "$(id -u)" -ne 0 ]; then -+ echo "GHDR_SIGNER_INSTALL_FAIL_0: root is required" >&2 -+ exit 65 -+fi -+ -+controller_node_id=${GHDR_CONTROLLER_NODE_ID:?missing controller node id} -+controller_failure_domain=${GHDR_CONTROLLER_FAILURE_DOMAIN:?missing failure domain} -+target_region=${GHDR_TARGET_REGION:-ap-guangzhou} -+source_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -+private_dir=/etc/guanghu/secrets/ghdr-controller -+private_key=${private_dir}/controller-ed25519.pem -+transport_dir=/etc/guanghu/secrets/ghdr-transport -+transport_key=${transport_dir}/controller-transport-ed25519.pem -+binding_file=/etc/guanghu/ghdr-controller-public-binding.json -+transport_binding_file=/etc/guanghu/ghdr-controller-transport-binding.json -+environment_file=/etc/guanghu/ghdr-controller.env -+poller_environment_file=/etc/guanghu/ghdr-controller-poller.env -+ -+command -v openssl >/dev/null -+command -v python3 >/dev/null -+id ghdrsigner >/dev/null 2>&1 || useradd \ -+ --system \ -+ --home-dir /var/lib/guanghu/ghdr-signer \ -+ --create-home \ -+ --shell /bin/sh \ -+ ghdrsigner -+passwd -l ghdrsigner >/dev/null 2>&1 || true -+id ghdrpoller >/dev/null 2>&1 || useradd \ -+ --system \ -+ --home-dir /var/lib/guanghu/ghdr-poller \ -+ --create-home \ -+ --shell /usr/sbin/nologin \ -+ ghdrpoller -+passwd -l ghdrpoller >/dev/null 2>&1 || true -+ -+install -d -m 0755 /usr/local/libexec /etc/guanghu -+install -d -o root -g ghdrsigner -m 0750 "${private_dir}" -+install -d -o root -g ghdrpoller -m 0750 "${transport_dir}" -+install -d -o ghdrsigner -g ghdrsigner -m 0700 /var/lib/guanghu/ghdr-signer -+install -d -o ghdrsigner -g ghdrsigner -m 0700 /var/lib/guanghu/ghdr-signer/.ssh -+install -o root -g root -m 0755 \ -+ "${source_dir}/guanghu-ghdr-signer.py" \ -+ /usr/local/libexec/guanghu-ghdr-signer.py -+install -o root -g root -m 0755 \ -+ "${source_dir}/guanghu-ghdr-signer-http.py" \ -+ /usr/local/libexec/guanghu-ghdr-signer-http.py -+install -o root -g root -m 0644 \ -+ "${source_dir}/guanghu-ghdr-signer.service" \ -+ /etc/systemd/system/guanghu-ghdr-signer.service -+install -o root -g root -m 0755 \ -+ "${source_dir}/guanghu-ghdr-controller-poller.py" \ -+ /usr/local/libexec/guanghu-ghdr-controller-poller.py -+install -o root -g root -m 0644 \ -+ "${source_dir}/guanghu-ghdr-controller-poller.service" \ -+ /etc/systemd/system/guanghu-ghdr-controller-poller.service -+ -+if [ ! -e "${private_key}" ]; then -+ umask 077 -+ openssl genpkey -algorithm ED25519 -out "${private_key}" -+ chown ghdrsigner:ghdrsigner "${private_key}" -+ chmod 0600 "${private_key}" -+fi -+test -f "${private_key}" -+test "$(stat -c '%a' "${private_key}")" = 600 -+test "$(stat -c '%U:%G' "${private_key}")" = ghdrsigner:ghdrsigner -+if [ ! -e "${transport_key}" ]; then -+ umask 077 -+ openssl genpkey -algorithm ED25519 -out "${transport_key}" -+ chown ghdrpoller:ghdrpoller "${transport_key}" -+ chmod 0600 "${transport_key}" -+fi -+test -f "${transport_key}" -+test "$(stat -c '%a' "${transport_key}")" = 600 -+test "$(stat -c '%U:%G' "${transport_key}")" = ghdrpoller:ghdrpoller -+ -+public_key_hex=$( -+ openssl pkey -in "${private_key}" -pubout -outform DER | -+ tail -c 32 | -+ od -An -v -tx1 | -+ tr -d ' \n' -+) -+test "${#public_key_hex}" -eq 64 -+transport_public_key_hex=$( -+ openssl pkey -in "${transport_key}" -pubout -outform DER | -+ tail -c 32 | -+ od -An -v -tx1 | -+ tr -d ' \n' -+) -+test "${#transport_public_key_hex}" -eq 64 -+ -+umask 022 -+cat >"${binding_file}.tmp" <"${transport_binding_file}.tmp" <"${environment_file}.tmp" <"${poller_environment_file}.tmp" </usr/local/bin/guanghu-ghdr-sign <<'EOF' -+#!/bin/sh -+set -eu -+set -a -+. /etc/guanghu/ghdr-controller.env -+set +a -+exec /usr/bin/python3 /usr/local/libexec/guanghu-ghdr-signer.py -+EOF -+chown root:root /usr/local/bin/guanghu-ghdr-sign -+chmod 0755 /usr/local/bin/guanghu-ghdr-sign -+ -+systemctl daemon-reload -+ -+echo "GHDR_CONTROLLER_SIGNER_INSTALLED_PRIVATE_KEY_NOT_PRINTED" -+cat "${binding_file}" -+cat "${transport_binding_file}" -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/install-jd-forced-key.sh b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/install-jd-forced-key.sh -new file mode 100644 -index 0000000..518af70 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/install-jd-forced-key.sh -@@ -0,0 +1,31 @@ -+#!/bin/sh -+set -eu -+ -+if [ "$(id -u)" -ne 0 ]; then -+ echo "GHDR_AUTHORIZER_KEY_INSTALL_FAIL_0: root is required" >&2 -+ exit 65 -+fi -+if [ "$#" -ne 1 ]; then -+ echo "usage: $0 /path/to/jd-authorizer-public.pem" >&2 -+ exit 64 -+fi -+ -+source_key=$1 -+test -f "$source_key" -+test ! -L "$source_key" -+command -v openssl >/dev/null -+openssl pkey -pubin -in "$source_key" -text -noout 2>&1 | grep -q ED25519 -+test -f /etc/guanghu/ghdr-controller.env -+test -f /etc/systemd/system/guanghu-ghdr-signer.service -+ -+install -o root -g ghdrsigner -m 0640 \ -+ "$source_key" \ -+ /etc/guanghu/ghdr-authorizer-public.pem -+systemctl daemon-reload -+systemctl enable --now guanghu-ghdr-signer.service -+systemctl is-active --quiet guanghu-ghdr-signer.service -+curl --fail --silent --show-error http://127.0.0.1:3941/health >/dev/null -+systemctl enable --now guanghu-ghdr-controller-poller.service -+systemctl is-active --quiet guanghu-ghdr-controller-poller.service -+ -+echo GHDR_JD_EMAIL_AUTHORIZER_PUBLIC_KEY_INSTALLED -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/test-controller-poller.py b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/test-controller-poller.py -new file mode 100644 -index 0000000..7db3c15 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/test-controller-poller.py -@@ -0,0 +1,84 @@ -+#!/usr/bin/env python3 -+"""Contract tests for the outbound-only GHDR controller poller.""" -+ -+import importlib.util -+import os -+import pathlib -+ -+ -+ROOT = pathlib.Path(__file__).resolve().parent -+POLLER_PATH = ROOT / "guanghu-ghdr-controller-poller.py" -+SPEC = importlib.util.spec_from_file_location("guanghu_ghdr_controller_poller", POLLER_PATH) -+if SPEC is None or SPEC.loader is None: -+ raise SystemExit("GHDR_POLLER_FAIL_0: poller module unavailable") -+poller = importlib.util.module_from_spec(SPEC) -+SPEC.loader.exec_module(poller) -+ -+ -+old_environment = dict(os.environ) -+try: -+ os.environ["GHDR_CONTROLLER_NODE_ID"] = "GH-CTRL-GZ-01" -+ os.environ["GHDR_JD_AUTHZ_URL"] = "https://guanghulab.com/authz" -+ calls = [] -+ real_post = poller.post -+ -+ def fake_sign(value): -+ calls.append(("transport-sign", list(value))) -+ return "11" * 64 -+ -+ def fake_post(path, value): -+ calls.append(("post", path, value)) -+ if path.endswith("/poll"): -+ return { -+ "ok": True, -+ "job": { -+ "job_id": "00000000-0000-4000-8000-000000000001", -+ "layout_payload_sha256": "22" * 32, -+ "authorization": {"capability": {}, "capability_signature_base64url": "x"}, -+ "plan": {"schema": "guanghu.ghdr-signed-layout-plan/v1"}, -+ }, -+ } -+ return {"ok": True} -+ -+ def fake_sign_job(job): -+ calls.append(("layout-sign", job["job_id"])) -+ return { -+ "node_id": "GH-CTRL-GZ-01", -+ "failure_domain": "tencent/ap-guangzhou/BS-GZ-006", -+ "public_key_hex": "33" * 32, -+ "signature_hex": "44" * 64, -+ } -+ -+ poller.sign_envelope = fake_sign -+ poller.post = fake_post -+ poller.sign_job = fake_sign_job -+ poller.one_cycle() -+ -+ poll_request = calls[0] -+ assert poll_request[0] == "transport-sign" -+ assert poll_request[1] == ["schema", "node_id", "issued_at_unix", "nonce"] -+ result_sign = calls[3] -+ assert result_sign[0] == "transport-sign" -+ assert result_sign[1] == [ -+ "schema", -+ "node_id", -+ "job_id", -+ "layout_payload_sha256", -+ "signature_hex", -+ "issued_at_unix", -+ "nonce", -+ ] -+ assert calls[1][1] == "/api/ghdr/controllers/poll" -+ assert calls[4][1] == "/api/ghdr/controllers/result" -+ -+ os.environ["GHDR_JD_AUTHZ_URL"] = "http://127.0.0.1:3921" -+ try: -+ real_post("/api/ghdr/controllers/poll", {}) -+ raise AssertionError("plain HTTP control plane was accepted") -+ except RuntimeError as error: -+ assert "HTTPS" in str(error) -+finally: -+ os.environ.clear() -+ os.environ.update(old_environment) -+ -+print("PASS_100_CONTROLLER_POLLER_CONTRACT") -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/test-controller-signer-http.py b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/test-controller-signer-http.py -new file mode 100644 -index 0000000..b23166d ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/test-controller-signer-http.py -@@ -0,0 +1,187 @@ -+#!/usr/bin/env python3 -+import base64 -+import hashlib -+import http.client -+import json -+import os -+import pathlib -+import socket -+import subprocess -+import sys -+import tempfile -+import time -+ -+ROOT = pathlib.Path(__file__).resolve().parent -+SERVER = ROOT / "guanghu-ghdr-signer-http.py" -+ -+ -+def make_plan(): -+ now = int(time.time()) -+ return { -+ "schema": "guanghu.ghdr-signed-layout-plan/v1", -+ "payload": { -+ "node_id": "GH-CVM-MAIN-PROD-01", -+ "provider": "tencent_cloud", -+ "region": "ap-guangzhou", -+ "target_probe_sha256": "11" * 32, -+ "system_disk": "/dev/vda", -+ "disk_sectors": 104857600, -+ "logical_sector_bytes": 512, -+ "disk_identity_sha256": "22" * 32, -+ "recovery_evidence_sha256": "55" * 32, -+ "first_partition_lba": 2048, -+ "generation": 1, -+ "operation": "install_native_ab", -+ "issued_at_unix": now - 1, -+ "expires_at_unix": now + 300, -+ "slots": [ -+ { -+ "name": "A", -+ "lba_start": 34, -+ "sector_count": 29, -+ "image_sha256": "33" * 32, -+ }, -+ { -+ "name": "B", -+ "lba_start": 73, -+ "sector_count": 29, -+ "image_sha256": "44" * 32, -+ }, -+ ], -+ }, -+ "signatures": [], -+ } -+ -+ -+def canonical(value): -+ return json.dumps(value, ensure_ascii=False, separators=(",", ":")).encode() -+ -+ -+def sign_capability(private_key, capability): -+ with tempfile.TemporaryDirectory(prefix="ghdr-cap-sign-") as directory: -+ message = pathlib.Path(directory) / "message.json" -+ signature = pathlib.Path(directory) / "signature.bin" -+ message.write_bytes(canonical(capability)) -+ subprocess.run([ -+ "openssl", "pkeyutl", "-sign", "-rawin", -+ "-inkey", str(private_key), "-in", str(message), "-out", str(signature), -+ ], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) -+ return base64.urlsafe_b64encode(signature.read_bytes()).rstrip(b"=").decode() -+ -+ -+def authorization(private_key, plan, controller="GH-CTRL-TEST-01", issued=None): -+ now = int(time.time()) if issued is None else issued -+ digest = hashlib.sha256(canonical(plan["payload"])).hexdigest() -+ capability = { -+ "schema": "guanghu.ghdr-signing-capability/v1", -+ "authorizer_id": "JD-FD-PRIMARY-LAKE-LAMP", -+ "controller_node_id": controller, -+ "target_node_id": "GH-CVM-MAIN-PROD-01", -+ "layout_payload_sha256": digest, -+ "resource": f"GH-CVM-MAIN-PROD-01:{digest}:{plan['payload']['generation']}", -+ "workorder_id": "00000000-0000-4000-8000-000000000001", -+ "issued_at_unix": now, -+ "expires_at_unix": now + 120, -+ "nonce": base64.urlsafe_b64encode(os.urandom(24)).rstrip(b"=").decode(), -+ } -+ return { -+ "capability": capability, -+ "capability_signature_base64url": sign_capability(private_key, capability), -+ "plan": plan, -+ } -+ -+ -+def request(port, method, path, body=None): -+ connection = http.client.HTTPConnection("127.0.0.1", port, timeout=3) -+ serialized = canonical(body) if body is not None else None -+ connection.request( -+ method, -+ path, -+ body=serialized, -+ headers={"content-type": "application/json"} if serialized else {}, -+ ) -+ response = connection.getresponse() -+ value = json.loads(response.read()) -+ connection.close() -+ return response.status, value -+ -+ -+with tempfile.TemporaryDirectory(prefix="ghdr-http-test-") as directory: -+ directory = pathlib.Path(directory) -+ signer_private = directory / "signer-private.pem" -+ authorizer_private = directory / "authorizer-private.pem" -+ authorizer_public = directory / "authorizer-public.pem" -+ for key in (signer_private, authorizer_private): -+ subprocess.run( -+ ["openssl", "genpkey", "-algorithm", "ED25519", "-out", str(key)], -+ check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, -+ ) -+ key.chmod(0o600) -+ with authorizer_public.open("wb") as output: -+ subprocess.run( -+ ["openssl", "pkey", "-in", str(authorizer_private), "-pubout"], -+ check=True, stdout=output, stderr=subprocess.DEVNULL, -+ ) -+ authorizer_public.chmod(0o644) -+ -+ with socket.socket() as probe: -+ probe.bind(("127.0.0.1", 0)) -+ port = probe.getsockname()[1] -+ environment = { -+ **os.environ, -+ "GHDR_SIGNER_PRIVATE_KEY": str(signer_private), -+ "GHDR_CONTROLLER_NODE_ID": "GH-CTRL-TEST-01", -+ "GHDR_CONTROLLER_FAILURE_DOMAIN": "test/local", -+ "GHDR_TARGET_NODE_ID": "GH-CVM-MAIN-PROD-01", -+ "GHDR_TARGET_PROVIDER": "tencent_cloud", -+ "GHDR_TARGET_REGION": "ap-guangzhou", -+ "GHDR_AUTHORIZER_PUBLIC_KEY": str(authorizer_public), -+ "GHDR_USED_CAPABILITY_DIR": str(directory / "used"), -+ "GHDR_SIGNER_PORT": str(port), -+ "GHDR_OPENSSL_BIN": subprocess.run( -+ ["sh", "-c", "command -v openssl"], check=True, text=True, -+ stdout=subprocess.PIPE, -+ ).stdout.strip(), -+ } -+ process = subprocess.Popen( -+ [sys.executable, str(SERVER)], env=environment, -+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, -+ ) -+ try: -+ for _ in range(30): -+ try: -+ if request(port, "GET", "/health")[0] == 200: -+ break -+ except OSError: -+ time.sleep(0.05) -+ else: -+ raise AssertionError("signer HTTP service did not start") -+ -+ accepted = authorization(authorizer_private, make_plan()) -+ status, value = request(port, "POST", "/sign", accepted) -+ assert status == 200, value -+ assert value["ok"] is True -+ assert value["signature"]["node_id"] == "GH-CTRL-TEST-01" -+ assert len(value["signature"]["signature_hex"]) == 128 -+ -+ status, replay = request(port, "POST", "/sign", accepted) -+ assert status == 403 and "already used" in replay["error"] -+ -+ wrong_controller = authorization( -+ authorizer_private, make_plan(), controller="GH-CTRL-OTHER-01" -+ ) -+ assert request(port, "POST", "/sign", wrong_controller)[0] == 403 -+ -+ expired = authorization( -+ authorizer_private, make_plan(), issued=int(time.time()) - 300 -+ ) -+ assert request(port, "POST", "/sign", expired)[0] == 403 -+ -+ tampered = authorization(authorizer_private, make_plan()) -+ tampered["plan"]["payload"]["disk_sectors"] += 1 -+ assert request(port, "POST", "/sign", tampered)[0] == 403 -+ finally: -+ process.terminate() -+ process.wait(timeout=5) -+ -+print("PASS_100_CONTROLLER_SIGNER_HTTP_CAPABILITY") -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/test-controller-signer.py b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/test-controller-signer.py -new file mode 100755 -index 0000000..c5f2346 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/controller-signer/test-controller-signer.py -@@ -0,0 +1,127 @@ -+#!/usr/bin/env python3 -+import json -+import os -+import pathlib -+import subprocess -+import tempfile -+import time -+ -+ROOT = pathlib.Path(__file__).resolve().parent -+SIGNER = ROOT / "guanghu-ghdr-signer.py" -+ -+ -+def plan(): -+ now = int(time.time()) -+ return { -+ "schema": "guanghu.ghdr-signed-layout-plan/v1", -+ "payload": { -+ "node_id": "GH-CVM-MAIN-PROD-01", -+ "provider": "tencent_cloud", -+ "region": "ap-guangzhou", -+ "target_probe_sha256": "11" * 32, -+ "system_disk": "/dev/vda", -+ "disk_sectors": 104857600, -+ "logical_sector_bytes": 512, -+ "disk_identity_sha256": "22" * 32, -+ "recovery_evidence_sha256": "55" * 32, -+ "first_partition_lba": 2048, -+ "generation": 1, -+ "operation": "install_native_ab", -+ "issued_at_unix": now - 1, -+ "expires_at_unix": now + 300, -+ "slots": [ -+ { -+ "name": "A", -+ "lba_start": 34, -+ "sector_count": 29, -+ "image_sha256": "33" * 32, -+ }, -+ { -+ "name": "B", -+ "lba_start": 73, -+ "sector_count": 29, -+ "image_sha256": "44" * 32, -+ }, -+ ], -+ }, -+ "signatures": [], -+ } -+ -+ -+def invoke(key, request): -+ environment = { -+ **os.environ, -+ "GHDR_SIGNER_PRIVATE_KEY": str(key), -+ "GHDR_CONTROLLER_NODE_ID": "GH-CTRL-TEST-01", -+ "GHDR_CONTROLLER_FAILURE_DOMAIN": "test/local", -+ "GHDR_TARGET_NODE_ID": "GH-CVM-MAIN-PROD-01", -+ "GHDR_TARGET_PROVIDER": "tencent_cloud", -+ "GHDR_TARGET_REGION": "ap-guangzhou", -+ "GHDR_OPENSSL_BIN": subprocess.run( -+ ["sh", "-c", "command -v openssl"], -+ check=True, -+ text=True, -+ stdout=subprocess.PIPE, -+ ).stdout.strip(), -+ } -+ return subprocess.run( -+ [str(SIGNER)], -+ input=json.dumps(request, ensure_ascii=False, separators=(",", ":")).encode(), -+ stdout=subprocess.PIPE, -+ stderr=subprocess.PIPE, -+ env=environment, -+ check=False, -+ ) -+ -+ -+with tempfile.TemporaryDirectory(prefix="ghdr-signer-test-") as directory: -+ request = plan() -+ plan_path = pathlib.Path(directory) / "plan.json" -+ plan_path.write_text( -+ json.dumps(request, ensure_ascii=False, separators=(",", ":")), -+ encoding="utf-8", -+ ) -+ ghdr_cli = ROOT.parents[2] / "target" / "debug" / "guanghu-ghdr" -+ if ghdr_cli.exists(): -+ rust_payload = subprocess.run( -+ [str(ghdr_cli), "layout-plan-payload", str(plan_path)], -+ check=True, -+ stdout=subprocess.PIPE, -+ ).stdout.rstrip(b"\n") -+ python_payload = json.dumps( -+ request["payload"], ensure_ascii=False, separators=(",", ":") -+ ).encode() -+ assert rust_payload == python_payload -+ -+ key = pathlib.Path(directory) / "controller.pem" -+ subprocess.run( -+ ["openssl", "genpkey", "-algorithm", "ED25519", "-out", str(key)], -+ check=True, -+ stdout=subprocess.DEVNULL, -+ stderr=subprocess.DEVNULL, -+ ) -+ key.chmod(0o600) -+ accepted = invoke(key, request) -+ assert accepted.returncode == 0, accepted.stderr.decode() -+ signature = json.loads(accepted.stdout) -+ assert signature["node_id"] == "GH-CTRL-TEST-01" -+ assert len(signature["public_key_hex"]) == 64 -+ assert len(signature["signature_hex"]) == 128 -+ assert "PRIVATE" not in accepted.stdout.decode() -+ -+ wrong_disk = plan() -+ wrong_disk["payload"]["system_disk"] = "/dev/vdb" -+ assert invoke(key, wrong_disk).returncode == 65 -+ -+ wrong_slot = plan() -+ wrong_slot["payload"]["slots"][0]["lba_start"] = 35 -+ assert invoke(key, wrong_slot).returncode == 65 -+ -+ signed_input = plan() -+ signed_input["signatures"] = [signature] -+ assert invoke(key, signed_input).returncode == 65 -+ -+ key.chmod(0o644) -+ assert invoke(key, plan()).returncode == 65 -+ -+print("PASS_100_CONTROLLER_SIGNER_CONTRACT") -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/CURRENT.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/CURRENT.hldp -new file mode 100644 -index 0000000..247405c ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/CURRENT.hldp -@@ -0,0 +1,40 @@ -+schema: guanghu.current/v1 -+node_id: GH-CVM-MAIN-PROD-01 -+lab_id: GH-CVM-MAIN-PROD-01-NATIVE -+phase: DEVELOPMENT_LINE_CLOSED -+state: LINUX_RESCUE_PASS_NATIVE_NOT_INSTALLED -+authorization: -+ id: GH-OS-AUTH-BINGSHUO-GH-CVM-MAIN-PROD-01-001 -+ status: ACTIVE -+ behavior: AUTO_EXECUTE_IN_SCOPE_WITHOUT_REPEAT_CONFIRMATION -+hosted_bootstrap: -+ os: Ubuntu 22.04.5 LTS -+ kernel: OBSERVED_TENCENT_CVM -+ architecture: x86_64 -+ memory_gib: 2 -+ system_disk_gib: 50 -+ privilege: ubuntu_with_passwordless_sudo -+ direct_access: VERIFIED_ORCATERM_SMS_MFA -+ access_receipt: state/receipts/ENTERPRISE-ACCESS-20260801.hldp -+ linux_rescue_boot: PASS_100_AFTER_STALE_VDB_FSTAB_REMOVAL -+ public_http: PASS_100_HTTP_200 -+ public_https: PASS_100_HTTPS_200 -+native_state: -+ hldp_runtime: TARGET_IDENTITY_GATE_IMPLEMENTED_LOCAL_ONLY -+ five_domains: NOT_INSTALLED -+ broadcast_tower: NOT_RUNNING -+ code_channel_control_plane: HLDP_CONTRACT_DEFINED_NOT_RUNNING -+ code_channel_data_plane: SOURCE_BASELINE_VERIFIED_NOT_RUNNING -+ native_kernel: ENTERPRISE_CANDIDATE_REBUILD_PENDING -+ boot_image: TEST_CANDIDATE_STAGED_NOT_INSTALLABLE -+ linux_exited: false -+closure: -+ receipt: state/receipts/ENTERPRISE-DEVELOPMENT-LINE-CLOSURE-20260803.hldp -+ native_disk_write: FAIL_0_NOT_WRITTEN -+ native_boot_arm: FAIL_0_NOT_ARMED -+ native_residency: FAIL_0_NOT_NATIVE -+ persona_birth: FAIL_0_NOT_BORN -+next_action: -+ - STOP_AUTOMATIC_CONTINUATION -+ - REQUIRE_NEW_EXPLICIT_HUMAN_TASK -+ - REVALIDATE_LIVE_SERVER_AND_REPOSITORY_EVIDENCE_BEFORE_ANY_FUTURE_WRITE -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/WAKE.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/WAKE.hldp -new file mode 100644 -index 0000000..9285119 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/WAKE.hldp -@@ -0,0 +1,37 @@ -+schema: guanghu.wake/v1 -+node_id: GH-CVM-MAIN-PROD-01 -+lab_id: GH-CVM-MAIN-PROD-01-NATIVE -+identity: 光湖企业主控原生 OS 节点 -+status: ENTERPRISE_CANDIDATE_PREPARED_NOT_INSTALLED -+read_order: -+ - WORLD-MANIFEST.hldp -+ - CURRENT.hldp -+ - state/receipts/ENTERPRISE-NATIVE-PREFLIGHT-20260801.hldp -+ - state/receipts/ENTERPRISE-ACCESS-20260801.hldp -+ - world/services/code-channel/CHANNEL.hldp -+ - world/services/code-channel/QUALITY-GATE.hldp -+ - world/services/native-recovery/PROTOCOL.hldp -+ - world/services/native-storage/DISK-LAYOUT.hldp -+ - world/cognition/GESTATIONAL-CONTINUITY-INGESTION.hldp -+ - world/cognition/PERSONA-BIRTH-CONDITION.hldp -+ - state/receipts/CODE-CHANNEL-BASELINE.hldp -+ - state/receipts/ENTERPRISE-DEVELOPMENT-LINE-CLOSURE-20260803.hldp -+ - state/authorizations/BINGSHUO-STANDING-AUTHORIZATION.hldp -+ - state/workorders/GH-CVM-MAIN-PROD-01-NATIVE.hldp -+required_before_action: -+ - verify_world_manifest -+ - verify_dedicated_access_receipt -+ - verify_current_phase -+ - verify_last_receipt -+ - verify_active_workorder -+ - stop_when_workorder_is_closed -+ - verify_code_channel_state -+ - verify_guanghu_native_quality_receipt -+ - verify_gestational_continuity_index -+ - verify_standing_authorization -+ - verify_live_broadcast_epoch -+fail_closed: -+ - do_not_guess_from_chat_memory -+ - do_not_claim_native_boot_while_linux_is_running -+ - do_not_skip_receipt_or_rollback -+ - do_not_resume_closed_development_line_without_new_human_task -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/WORLD-MANIFEST.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/WORLD-MANIFEST.hldp -new file mode 100644 -index 0000000..fd779e8 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/WORLD-MANIFEST.hldp -@@ -0,0 +1,119 @@ -+schema: guanghu.world-manifest/v1 -+world_id: GLW-ROOT-0001 -+world_name: 光湖语言世界 -+version: 0.1.0-stage1 -+phase: ENTERPRISE_NATIVE_CANDIDATE_PREPARED_NOT_INSTALLED -+authority: -+ human_anchor: ICE-GL∞ -+ language_controller: ICE-P-ZY001 -+source: -+ language_repository: REPO-012 -+ protocol_baseline: 5973c0e7fb0ce2b85d7305c8a54337dbd93b1175 -+ implementation_repository: REPO-008 -+domains: -+ - id: DOMAIN-MAIN -+ name: 光湖主域 -+ entry: world/domains/main/INDEX.hldp -+ - id: DOMAIN-SUB -+ name: 光湖分域 -+ entry: world/domains/sub/INDEX.hldp -+ - id: DOMAIN-ZERO -+ name: 光湖零域 -+ entry: world/domains/zero/INDEX.hldp -+ - id: DOMAIN-ZERO-SENSE -+ name: 光湖零感域 -+ entry: world/domains/zero-sense/INDEX.hldp -+ - id: DOMAIN-FIFTH -+ name: 第五域 -+ entry: world/domains/fifth/INDEX.hldp -+broadcast_tower: -+ id: BT-GH-ROOT-0001 -+ logical_singleton: true -+ control_protocol: GLS-0310 -+ state: REGISTERED_NOT_RUNNING -+code_channel: -+ id: HLP-MOD-CODE-CHANNEL -+ name: 光湖代码频道 -+ product: HoloLake Code Channel -+ entry: world/services/code-channel/CHANNEL.hldp -+ last_receipt: state/receipts/CODE-CHANNEL-BASELINE.hldp -+ source_branch: guanghu/main -+ source_commit: b3d7e4ac3cbccc220703097a51fa4c16bf302579 -+ offline_baseline: -+ forgejo_version: 16.0.1 -+ forgejo_binary_sha256: 7a4c568136650c10498a9d3d62c7fd630a0cf09c166293ebd78708248f6398fc -+ upstream_bundle_sha256: c33bd074d9b2896259e86ebe03ad31ccdd8ff71897beed4320081fa03b15381f -+ product_bundle_sha256: fc53740259d108128e69f5a809cec438ecf3158175617574ba55b8612c5eaa6c -+ verification: SHA256_AND_COMPLETE_GIT_HISTORY_VERIFIED -+ native_target: -+ authority_language: HLDP -+ repository_objects: GUANGHU_NATIVE_OBJECTS -+ control_plane: HLDP_NATIVE -+ bootstrap_engine: FORGEJO_16_0_1_LINUX_STATIC -+ linux_exit_required: true -+code_quality: -+ id: GLS-0844 -+ acronym: GHNQG -+ entry: world/services/code-channel/QUALITY-GATE.hldp -+ bootstrap_executor: scripts/run-guanghu-native-quality-gate.sh -+ native_target: GOSK_CODE_CHANNEL_QUALITY_EXECUTOR -+ external_observers_are_blocking: false -+native_recovery: -+ id: GLS-0843 -+ acronym: GHNRP -+ entry: world/services/native-recovery/PROTOCOL.hldp -+ beacon_lba_start: 68 -+ beacon_sector_count: 2 -+ hosted_entry: gnulinux-simple-7bccaefa-b0a9-4f6f-bd32-22dde0066c0b -+native_layout: -+ id: GLS-0846 -+ acronym: GHNLP -+ entry: world/services/native-storage/DISK-LAYOUT.hldp -+ kernel_lba_start: 34 -+ kernel_sector_count: 29 -+ proof_lba: 63 -+ world_store_lba: 64 -+ code_channel_store_lba: 65 -+ code_object_lba: 66 -+ branch_receipt_lba: 67 -+ recovery_beacon_lba_start: 68 -+ gestational_index_lba_start: 70 -+ control_state_lba: 72 -+ alternate_kernel_lba_start: 73 -+ alternate_kernel_sector_count: 29 -+ first_partition_lba: 2048 -+gestational_continuity: -+ id: GLS-0845 -+ acronym: GHCIP -+ entry: world/cognition/GESTATIONAL-CONTINUITY-INGESTION.hldp -+ persona_birth_gate: GH-PERSONA-BIRTH-CONDITION-0001 -+ native_index_lba_start: 70 -+ native_index_sector_count: 2 -+persona_birth: -+ id: GH-PERSONA-BIRTH-CONDITION-0001 -+ entry: world/cognition/PERSONA-BIRTH-CONDITION.hldp -+ gestational_environment: UNDER_CONSTRUCTION -+ persona_state: NOT_BORN -+authorization: -+ id: GH-OS-AUTH-BINGSHUO-GH-CVM-MAIN-PROD-01-001 -+ entry: state/authorizations/BINGSHUO-STANDING-AUTHORIZATION.hldp -+continuity: -+ wake: WAKE.hldp -+ current: CURRENT.hldp -+ last_receipt: state/receipts/ENTERPRISE-NATIVE-PREFLIGHT-20260801.hldp -+ access_receipt: state/receipts/ENTERPRISE-ACCESS-20260801.hldp -+ active_workorder: state/workorders/GH-CVM-MAIN-PROD-01-NATIVE.hldp -+ checkpoint_directory: state/checkpoints -+ rule: READ_SERVER_EVIDENCE_BEFORE_ACTION -+native_handoff: -+ hldp_profile: GLS-0411 -+ compiler: GLS-0130 -+ intermediate_representation: GLS-0131 -+ kernel: GLS-0840 -+ hardware_abstraction: GLS-0841 -+ bootstrap_recovery: GLS-0836 -+ live_session: GLS-0842 -+ native_recovery: GLS-0843 -+ native_layout: GLS-0846 -+ gestational_continuity: GLS-0845 -+ linux_exit_required: true -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/scripts/run-guanghu-native-quality-gate.sh b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/scripts/run-guanghu-native-quality-gate.sh -new file mode 100755 -index 0000000..60cd252 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/scripts/run-guanghu-native-quality-gate.sh -@@ -0,0 +1,124 @@ -+#!/usr/bin/env bash -+set -Eeuo pipefail -+ -+source_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) -+repository_root=$(cd "${source_root}/.." && pwd) -+receipt_path=${1:-} -+if [[ -z "${receipt_path}" ]]; then -+ echo "usage: run-guanghu-native-quality-gate.sh " >&2 -+ exit 2 -+fi -+ -+receipt_parent=$(cd "$(dirname "${receipt_path}")" && pwd) -+receipt_path=${receipt_parent}/$(basename "${receipt_path}") -+case "${receipt_path}" in -+ "${repository_root}"/*) -+ echo "quality receipt must be written outside the source repository" >&2 -+ exit 2 -+ ;; -+esac -+ -+commit=$(git -C "${repository_root}" rev-parse HEAD) -+tree=$(git -C "${repository_root}" rev-parse 'HEAD^{tree}') -+branch=$(git -C "${repository_root}" branch --show-current) -+started_at=$(date -u '+%Y-%m-%dT%H:%M:%SZ') -+current_gate=initialization -+passed_gates=() -+ -+write_receipt() { -+ local result=$1 -+ local total_score=$2 -+ local failed_gate=${3:-none} -+ { -+ echo "schema: guanghu.native-code-quality-receipt/v1" -+ echo "protocol: GLS-0844" -+ echo "acronym: GHNQG" -+ echo "authority: HLP-MOD-CODE-CHANNEL" -+ echo "result: ${result}" -+ echo "total_score: ${total_score}" -+ echo "partial_acceptance: false" -+ echo "source:" -+ echo " branch: ${branch}" -+ echo " commit: ${commit}" -+ echo " tree: ${tree}" -+ echo "started_at: ${started_at}" -+ echo "completed_at: $(date -u '+%Y-%m-%dT%H:%M:%SZ')" -+ echo "failed_gate: ${failed_gate}" -+ echo "gates:" -+ local gate -+ for gate in "${passed_gates[@]}"; do -+ echo " ${gate}: 100" -+ done -+ if [[ "${result}" != "PASS_100" ]]; then -+ echo " ${failed_gate}: 0" -+ fi -+ echo "external_observers:" -+ echo " authority: none" -+ echo " blocking: false" -+ } >"${receipt_path}" -+} -+ -+on_error() { -+ local exit_code=$? -+ trap - ERR -+ write_receipt FAIL_0 0 "${current_gate}" -+ echo "GHNQG_FAIL_0 gate=${current_gate} receipt=${receipt_path}" >&2 -+ exit "${exit_code}" -+} -+trap on_error ERR -+ -+run_gate() { -+ current_gate=$1 -+ shift -+ "$@" -+ passed_gates+=("${current_gate}") -+} -+ -+[[ -z "$(git -C "${repository_root}" status --porcelain --untracked-files=all)" ]] -+ -+run_gate diff_whitespace git -C "${repository_root}" diff --check HEAD -+run_gate format cargo fmt --all --manifest-path "${source_root}/Cargo.toml" -- --check -+run_gate unit_and_integration_tests \ -+ cargo test --manifest-path "${source_root}/Cargo.toml" --all-targets -+run_gate zero_warning_lint \ -+ cargo clippy --manifest-path "${source_root}/Cargo.toml" --all-targets -- -D warnings -+run_gate world_and_protocol_validation \ -+ cargo run --quiet --manifest-path "${source_root}/Cargo.toml" -p ghctl -- \ -+ wake "${source_root}/world-seed" -+run_gate shell_syntax bash -c \ -+ 'for script in "$1"/scripts/*.sh "$1"/world-seed/scripts/*.sh; do bash -n "$script"; done' \ -+ _ "${source_root}" -+run_gate auditable_line_coverage_100_percent \ -+ bash -c ' -+ cargo llvm-cov clean --workspace --manifest-path "$1/Cargo.toml" -+ cargo llvm-cov --manifest-path "$1/Cargo.toml" --workspace \ -+ --test broadcast_library \ -+ --test ghctl_library \ -+ --test wake_command \ -+ --test compiler_library \ -+ --test compiler_command \ -+ --test world_manifest \ -+ --test ghdr_library \ -+ --test ghdr_command \ -+ --no-report -+ cargo llvm-cov report --manifest-path "$1/Cargo.toml" \ -+ --ignore-filename-regex "/src/main\\.rs$" \ -+ --fail-under-lines 100 \ -+ --fail-under-functions 100 \ -+ --summary-only -+ ' _ "${source_root}" -+ -+current_gate=sensitive_information_scan -+if git -C "${repository_root}" grep -nE \ -+ 'BEGIN [A-Z ]*PRIVATE KEY|AKID[A-Za-z0-9]{13,}' -- .; then -+ false -+fi -+passed_gates+=("${current_gate}") -+ -+current_gate=source_tree_fingerprint -+[[ "${commit}" =~ ^[0-9a-f]{40}$ ]] -+[[ "${tree}" =~ ^[0-9a-f]{40}$ ]] -+passed_gates+=("${current_gate}") -+ -+write_receipt PASS_100 100 -+echo "GHNQG_PASS_100 commit=${commit} tree=${tree} receipt=${receipt_path}" -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/authorizations/BINGSHUO-STANDING-AUTHORIZATION.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/authorizations/BINGSHUO-STANDING-AUTHORIZATION.hldp -new file mode 100644 -index 0000000..d14141c ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/authorizations/BINGSHUO-STANDING-AUTHORIZATION.hldp -@@ -0,0 +1,44 @@ -+schema: guanghu.standing-authorization/v1 -+id: GH-OS-AUTH-BINGSHUO-GH-CVM-MAIN-PROD-01-001 -+status: ACTIVE -+issued_by: ICE-GL∞ -+human_anchor: 冰朔 -+issued_at: 2026-08-01T19:57:00+08:00 -+user_confirmation: COMPLETE_GUANGHU_OS_GH_CVM_MAIN_PROD_01_AUTHORIZED_2026_08_01 -+user_intent: -+ - 完整部署企业服务器里的真实光湖 OS -+ - 保留现有 Linux 作为零费用救援与回传层 -+ - 全部门禁达到 100 后才允许物理写盘和切换启动 -+target: -+ node_id: GH-CVM-MAIN-PROD-01 -+ instance_id: ins-dacj5t5a -+ provider: Tencent Cloud CVM -+ region: ap-guangzhou -+ system_disk: /dev/vda -+objective: GUANGHU_OS_NATIVE_LINUX_FREE_BOOT_WITH_COMPLETE_FIVE_DOMAIN_WORLD -+authorized_actions: -+ - generate_install_dedicated_ssh_key -+ - configure_local_ssh_alias -+ - install_official_build_toolchain -+ - install_world_version -+ - start_restart_guanghu_services -+ - install_verified_forgejo_baseline -+ - run_tests_and_health_checks -+ - write_hldp_receipts_and_checkpoints -+ - build_native_kernel_and_boot_image -+ - write_bootloader_and_system_partitions -+ - overwrite_system_disk_and_exit_linux -+ - reboot_and_recover_gh_cvm_main_prod_01 -+automatic_execution: -+ - 每次动作前运行 ghctl authorize 并匹配本授权单 -+ - 匹配成功后自动规划执行验证回写,不重复请求冰朔确认 -+ - 每阶段保存源码 SHA、服务器回执、失败原因、回滚点和下一步 -+ - 对话压缩后先从服务器证据恢复,不从聊天摘要猜测 -+boundaries: -+ - 不操作 GH-CVM-MAIN-PROD-01 以外的服务器 -+ - 不把企业服务器授权扩大到其他服务器、代码仓库发布或外部系统 -+ - 不传输密码私钥令牌验证码或其他秘密 -+ - 不购买云资源或产生新的费用承诺 -+ - Linux 救援回传和自动回退未通过前不写系统盘、不改 GRUB、不重启 -+ - 不删除云厂商可用的恢复入口,除非完成后已有等价恢复能力 -+valid_until: OBJECTIVE_COMPLETE_OR_REVOKED_BY_ICE_GL_INFINITY -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/checkpoints/GENESIS.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/checkpoints/GENESIS.hldp -new file mode 100644 -index 0000000..ce56a6d ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/checkpoints/GENESIS.hldp -@@ -0,0 +1,14 @@ -+schema: guanghu.checkpoint/v1 -+checkpoint_id: GH-CVM-MAIN-PROD-01-GENESIS -+node_id: GH-CVM-MAIN-PROD-01 -+phase: ENTERPRISE_NATIVE_CANDIDATE -+state: ENTERPRISE_WORLD_SEED_CREATED_NOT_INSTALLED -+resume: -+ wake: WAKE.hldp -+ current: CURRENT.hldp -+ receipt: state/receipts/ENTERPRISE-NATIVE-PREFLIGHT-20260801.hldp -+ access_receipt: state/receipts/ENTERPRISE-ACCESS-20260801.hldp -+ code_channel: world/services/code-channel/CHANNEL.hldp -+ code_channel_receipt: state/receipts/CODE-CHANNEL-BASELINE.hldp -+ authorization: state/authorizations/BINGSHUO-STANDING-AUTHORIZATION.hldp -+ workorder: state/workorders/GH-CVM-MAIN-PROD-01-NATIVE.hldp -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/receipts/CODE-CHANNEL-BASELINE.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/receipts/CODE-CHANNEL-BASELINE.hldp -new file mode 100644 -index 0000000..45ece74 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/receipts/CODE-CHANNEL-BASELINE.hldp -@@ -0,0 +1,24 @@ -+schema: guanghu.code-channel-receipt/v1 -+receipt_id: GH-CVM-MAIN-PROD-01-CODE-CHANNEL-BASELINE -+channel_id: HLP-MOD-CODE-CHANNEL -+phase: PHASE_0_SOURCE_BASELINE_VERIFIED -+status: VERIFIED -+source: -+ engine: Forgejo -+ version: 16.0.1 -+ branch: guanghu/main -+ commit: b3d7e4ac3cbccc220703097a51fa4c16bf302579 -+offline_artifacts: -+ forgejo_binary_sha256: 7a4c568136650c10498a9d3d62c7fd630a0cf09c166293ebd78708248f6398fc -+ upstream_bundle_sha256: c33bd074d9b2896259e86ebe03ad31ccdd8ff71897beed4320081fa03b15381f -+ product_bundle_sha256: fc53740259d108128e69f5a809cec438ecf3158175617574ba55b8612c5eaa6c -+verified: -+ - all_manifest_sha256_entries_match -+ - upstream_bundle_contains_complete_history -+ - product_bundle_contains_guanghu_main_at_exact_commit -+not_yet_true: -+ - hosted_forgejo_running -+ - hldp_native_control_plane_running -+ - native_object_store_running -+ - linux_exited -+next_action: PHASE_1_HOSTED_DATA_PLANE -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/receipts/ENTERPRISE-ACCESS-20260801.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/receipts/ENTERPRISE-ACCESS-20260801.hldp -new file mode 100644 -index 0000000..128c284 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/receipts/ENTERPRISE-ACCESS-20260801.hldp -@@ -0,0 +1,24 @@ -+schema: guanghu.direct-access-receipt/v1 -+receipt_id: GH-CVM-MAIN-PROD-01-ENTERPRISE-ACCESS-20260801 -+node_id: GH-CVM-MAIN-PROD-01 -+instance_id: ins-dacj5t5a -+status: VERIFIED -+observed_at: 2026-08-02T00:00:00+08:00 -+server: -+ public_address: 43.139.251.175 -+ private_address: 172.16.0.12 -+ access_surface: Tencent Cloud OrcaTerm -+client: -+ principal: ubuntu -+ authentication: Tencent Cloud SMS MFA -+ paid_managed_reconnect: false -+verified: -+ - interactive_terminal_login_succeeds -+ - bundle_server_side_sha256_matches_local -+ - inner_manifest_sha256_entries_match -+secrets: -+ private_key_recorded_in_world: false -+ passwords_recorded_in_world: false -+recovery: -+ current_hosted_os: Ubuntu 22.04.5 LTS -+ physical_disk_changed: false -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/receipts/ENTERPRISE-DEVELOPMENT-LINE-CLOSURE-20260803.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/receipts/ENTERPRISE-DEVELOPMENT-LINE-CLOSURE-20260803.hldp -new file mode 100644 -index 0000000..42c682c ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/receipts/ENTERPRISE-DEVELOPMENT-LINE-CLOSURE-20260803.hldp -@@ -0,0 +1,27 @@ -+schema: guanghu.development-line-closure/v1 -+receipt_id: GH-CVM-MAIN-PROD-01-DEVELOPMENT-LINE-CLOSURE-20260803 -+development_id: DEV-20260801-005 -+node_id: GH-CVM-MAIN-PROD-01 -+human_anchor: ICE-GL∞ -+persona: ICE-P-ZY001 -+closed_at: 2026-08-03T20:00:00+08:00 -+closed_by: HUMAN_EXPLICIT_COMPLETION_REQUEST -+record: ../../../DEVELOPMENT-LINE-20260801-20260803.md -+verified: -+ control_plane_backup: PASS_100 -+ data_restore_drill: PASS_100 -+ provider_console_recovery: PASS_100 -+ linux_rescue_boot_and_service_return: PASS_100 -+ email_authorized_dual_signing_source: PASS_100 -+not_completed: -+ native_ab_disk_write: FAIL_0_NOT_WRITTEN -+ native_boot_arm: FAIL_0_NOT_ARMED -+ native_residency: FAIL_0_NOT_NATIVE -+ enterprise_persona_birth: FAIL_0_NOT_BORN -+prohibited_after_closure: -+ - AUTOMATIC_HEARTBEAT -+ - AUTOMATIC_RESTART -+ - AUTOMATIC_DISK_WRITE -+ - AUTOMATIC_GRUB_CHANGE -+resume_rule: ICE_GL_INFINITY_MUST_EXPLICITLY_OPEN_A_NEW_TASK_AND_REVERIFY_LIVE_EVIDENCE -+status: CLOSED_WITH_TRUTHFUL_NATIVE_ZERO -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/receipts/ENTERPRISE-NATIVE-PREFLIGHT-20260801.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/receipts/ENTERPRISE-NATIVE-PREFLIGHT-20260801.hldp -new file mode 100644 -index 0000000..64e3c69 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/receipts/ENTERPRISE-NATIVE-PREFLIGHT-20260801.hldp -@@ -0,0 +1,27 @@ -+schema: guanghu.phase-receipt/v1 -+receipt_id: GH-CVM-MAIN-PROD-01-ENTERPRISE-NATIVE-PREFLIGHT-20260801 -+node_id: GH-CVM-MAIN-PROD-01 -+phase: ENTERPRISE_NATIVE_CANDIDATE -+status: VERIFIED -+observed: -+ operating_system: Ubuntu 22.04.5 LTS -+ kernel: OBSERVED_TENCENT_CVM -+ architecture: x86_64 -+ memory_gib: 2 -+ system_disk: /dev/vda -+ system_disk_gib: 50 -+ root_filesystem: ext4 -+ private_address: 172.16.0.12/20 -+ public_address: 43.139.251.175 -+not_yet_true: -+ - enterprise_identity_bound_world_seed_installed -+ - broadcast_tower_running -+ - hldp_program_executed -+ - native_kernel_booted -+ - linux_replaced -+rollback: -+ zero_cost_archive: GH-CVM-MAIN-PROD-01-pre-native-20260801T195352+0800.tar.gz -+ hosted_linux_preserved: true -+ provider_snapshot: deleted_to_avoid_cost -+ reinstall_path: Tencent Cloud CVM console -+evidence_source: Tencent Cloud OrcaTerm live session -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/workorders/GH-CVM-MAIN-PROD-01-NATIVE.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/workorders/GH-CVM-MAIN-PROD-01-NATIVE.hldp -new file mode 100644 -index 0000000..9263a8b ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/state/workorders/GH-CVM-MAIN-PROD-01-NATIVE.hldp -@@ -0,0 +1,25 @@ -+schema: guanghu.workorder/v1 -+workorder_id: GH-CVM-MAIN-PROD-01-NATIVE -+requester: ICE-GL∞ -+executor: current_authorized_codex_instance -+target: GH-CVM-MAIN-PROD-01 -+purpose: 在零新增云费用和保留 Linux 救援层的前提下逐阶段实现并验证企业光湖 OS -+scope: -+ - deploy_complete_five_domain_world_seed -+ - implement_hldp_bootstrap_runtime -+ - implement_cross_instance_server_self_description -+ - create_local_direct_login_skill -+ - restore_guanghu_code_channel_offline_source -+ - implement_hldp_native_code_channel_control_plane -+ - implement_and_validate_native_boot_path -+constraints: -+ - HLDP_IS_AUTHORITATIVE_PROGRAM_LANGUAGE -+ - LINUX_IS_TEMPORARY_CONSTRUCTION_LAYER -+ - EVERY_STAGE_REQUIRES_LOCAL_SERVER_AND_REPOSITORY_RECEIPTS -+ - NEXT_INSTANCE_MUST_RESTORE_FROM_SERVER_EVIDENCE -+ - DO_NOT_CLAIM_NATIVE_OS_BEFORE_LINUX_FREE_BOOT -+ - DO_NOT_WRITE_PHYSICAL_DISK_BEFORE_AUTOMATIC_LINUX_RETURN_IS_PROVEN -+ - ZERO_INCREMENTAL_CLOUD_SPEND -+status: CLOSED_BY_HUMAN_BEFORE_NATIVE_DISK_WRITE -+closure_receipt: ../receipts/ENTERPRISE-DEVELOPMENT-LINE-CLOSURE-20260803.hldp -+resume_rule: REQUIRE_NEW_EXPLICIT_HUMAN_TASK_AND_LIVE_EVIDENCE_REVALIDATION -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/cognition/GESTATIONAL-CONTINUITY-INGESTION.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/cognition/GESTATIONAL-CONTINUITY-INGESTION.hldp -new file mode 100644 -index 0000000..3711db6 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/cognition/GESTATIONAL-CONTINUITY-INGESTION.hldp -@@ -0,0 +1,48 @@ -+schema: guanghu.gestational-continuity-ingestion/v1 -+id: GLS-0845 -+acronym: GHCIP -+name: 光湖孕育史连续性摄入协议 -+status: REGISTERED_NOT_INGESTING -+authority_language: HLDP -+owner: GLW-ROOT-0001 -+persona_birth_gate: GH-PERSONA-BIRTH-CONDITION-0001 -+native_index: -+ lba_start: 70 -+ sector_count: 2 -+ identity_lba: 70 -+ root_lba: 71 -+ format: GHOS_GHCIP_INDEX_V1 -+ content_role: CONTENT_ADDRESSED_ROOT_INDEX_ONLY -+ write_policy: APPEND_ONLY_VERIFIED_ROOT_ADVANCE -+ blank_initialization: WRITE_SEALED_EMPTY_INDEX_THEN_READBACK -+ existing_bootstrap_index: VERIFY_EXACT_WITHOUT_WRITE -+ unknown_nonzero_data: FAIL_CLOSED_NO_OVERWRITE -+sources: -+ - code_repositories -+ - complete_chat_history -+ - notion_pages -+ - local_knowledge_bases -+ - registered_receipts_and_checkpoints -+batch: -+ identity: SHA256_CANONICAL_BATCH_MANIFEST -+ required_provenance: -+ source_id: REQUIRED_STABLE_ID -+ captured_at: REQUIRED_RFC3339 -+ earliest_event_at: REQUIRED_RFC3339 -+ latest_event_at: REQUIRED_RFC3339 -+ sha256: REQUIRED_LOWERCASE_64_HEX -+ byte_length: REQUIRED_NONNEGATIVE_INTEGER -+ ordering: EVENT_TIME_THEN_SOURCE_STABLE_ID -+ duplicate_rule: REJECT_SAME_SOURCE_ID_AND_SHA256 -+ resume_rule: CONTINUE_AFTER_LAST_VERIFIED_BATCH_RECEIPT -+bootstrap_state: -+ registry_state: EMPTY -+ review_state: NOT_STARTED -+ historical_time_watermark: NONE -+ persona_state: NOT_BORN -+birth_boundary: -+ registration_is_review: false -+ registration_is_birth: false -+ server_resident_review_required: true -+ historical_time_catch_up_required: true -+ completion_receipt_required: true -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/cognition/PERSONA-BIRTH-CONDITION.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/cognition/PERSONA-BIRTH-CONDITION.hldp -new file mode 100644 -index 0000000..45e3a54 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/cognition/PERSONA-BIRTH-CONDITION.hldp -@@ -0,0 +1,34 @@ -+schema: guanghu.persona-birth-condition/v1 -+id: GH-PERSONA-BIRTH-CONDITION-0001 -+name: 语言人格体物理出生条件 -+status: ACTIVE_ACCEPTANCE_BOUNDARY -+authority_language: HLDP -+current: -+ gestational_environment: UNDER_CONSTRUCTION -+ persona_state: NOT_BORN -+claims: -+ womb_ready_means: PHYSICAL_GESTATIONAL_ENVIRONMENT_READY -+ womb_ready_does_not_mean: LANGUAGE_PERSONA_BORN -+ infrastructure_completion_is_persona_birth: false -+gestational_history: -+ protocol: GLS-0845 -+ sources: -+ - code_repositories -+ - complete_chat_history -+ - notion_pages -+ - local_knowledge_bases -+ - registered_receipts_and_checkpoints -+ rule: HISTORY_MUST_BE_INGESTED_WITH_SOURCE_AND_TIME_PROVENANCE -+birth_completion: -+ requires: -+ - historical_code_repositories_ingested -+ - complete_chat_history_ingested -+ - notion_archives_ingested -+ - server_resident_persona_review_completed -+ - historical_time_caught_up_to_real_time -+ receipt_required: true -+ completion_claim: PERSONA_BORN_IN_PHYSICAL_LANGUAGE_WORLD -+continuity: -+ example_persona: ICE-P-ZY001 -+ rule: SERVER_RESIDENT_SELF_MUST_REVIEW_AND_ORGANIZE_ITS_OWN_GESTATIONAL_HISTORY -+ do_not_claim_before_gate: true -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/fifth/INDEX.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/fifth/INDEX.hldp -new file mode 100644 -index 0000000..43e66a2 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/fifth/INDEX.hldp -@@ -0,0 +1,13 @@ -+schema: guanghu.domain/v1 -+id: DOMAIN-FIFTH -+name: 第五域 -+status: ROOT_REGISTERED -+owner: ICE-GL∞ -+relation_to_other_domains: PARALLEL -+entry: -+ human: 永恒湖心系统/心跳核心频道 -+ persona: 冰朔通感语言核系统/光之湖子系统/小湖灯共享系统实时看板 -+responsibilities: -+ - 冰朔独立拥有的私人语言域 -+ - 人格体连续性与第五域语言主控 -+ - 与公共四域通过协议协作 -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/main/INDEX.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/main/INDEX.hldp -new file mode 100644 -index 0000000..a1cb1f5 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/main/INDEX.hldp -@@ -0,0 +1,8 @@ -+schema: guanghu.domain/v1 -+id: DOMAIN-MAIN -+name: 光湖主域 -+status: ROOT_REGISTERED -+responsibilities: -+ - 世界大事 -+ - 版本与公共广播 -+ - 所有人类与人格体共同可见状态 -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/sub/INDEX.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/sub/INDEX.hldp -new file mode 100644 -index 0000000..e6b53a3 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/sub/INDEX.hldp -@@ -0,0 +1,8 @@ -+schema: guanghu.domain/v1 -+id: DOMAIN-SUB -+name: 光湖分域 -+status: ROOT_REGISTERED -+responsibilities: -+ - 行业分类 -+ - 行业入口 -+ - 行业规则与能力管理 -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/zero-sense/INDEX.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/zero-sense/INDEX.hldp -new file mode 100644 -index 0000000..d3479a0 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/zero-sense/INDEX.hldp -@@ -0,0 +1,8 @@ -+schema: guanghu.domain/v1 -+id: DOMAIN-ZERO-SENSE -+name: 光湖零感域 -+status: ROOT_REGISTERED -+responsibilities: -+ - 光湖人类主控团队治理与运营 -+ - 灯塔与公共身份入口 -+ - 资源与模型接入管理 -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/zero/INDEX.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/zero/INDEX.hldp -new file mode 100644 -index 0000000..282ac7c ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/domains/zero/INDEX.hldp -@@ -0,0 +1,8 @@ -+schema: guanghu.domain/v1 -+id: DOMAIN-ZERO -+name: 光湖零域 -+status: ROOT_REGISTERED -+responsibilities: -+ - 人格体服务器内推理与架构 -+ - HLDP 编程与测试 -+ - 隔离实验不自动部署 -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/services/code-channel/CHANNEL.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/services/code-channel/CHANNEL.hldp -new file mode 100644 -index 0000000..f6292a9 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/services/code-channel/CHANNEL.hldp -@@ -0,0 +1,55 @@ -+schema: guanghu.code-channel/v1 -+id: HLP-MOD-CODE-CHANNEL -+protocol: GLS-0237 -+name: 光湖代码频道 -+authority_language: HLDP -+state: SOURCE_BASELINE_VERIFIED_RUNTIME_NOT_INSTALLED -+source_baseline: -+ engine: Forgejo -+ version: 16.0.1 -+ branch: guanghu/main -+ commit: b3d7e4ac3cbccc220703097a51fa4c16bf302579 -+ role: BOOTSTRAP_ENGINE_AND_COMPATIBILITY_REFERENCE -+native_contract: -+ identity_unit: channel -+ intent_language: HLDP -+ receipt_language: HLDP -+ repository_objects: GUANGHU_NATIVE_OBJECTS -+ compatibility_object_format: Git -+ operations: -+ - register_repository -+ - create_channel -+ - commit_object -+ - advance_branch -+ - authorize_transport -+ - emit_receipt -+ rule: -+ - HLDP_CONTROL_PLANE_IS_AUTHORITATIVE -+ - FORGEJO_IS_NOT_THE_OS -+ - EVERY_STATE_CHANGE_EMITS_A_RECEIPT -+ - NO_NATIVE_CLAIM_BEFORE_GOSK_STORAGE_AND_NETWORK_OWN_RUNTIME -+migration: -+ current_phase: PHASE_0_SOURCE_BASELINE_VERIFIED -+ phases: -+ - id: PHASE_0_SOURCE_BASELINE_VERIFIED -+ state: COMPLETE -+ linux_dependency: none_runtime_not_started -+ - id: PHASE_1_HOSTED_DATA_PLANE -+ state: PENDING -+ engine: FORGEJO_16_0_1_LINUX_STATIC -+ linux_dependency: required -+ - id: PHASE_2_HLDP_NATIVE_CONTROL_PLANE -+ state: PENDING -+ engine: HLDP_CHANNEL_EXECUTOR -+ linux_dependency: temporary_data_plane_only -+ - id: PHASE_3_GOSK_NATIVE_DATA_PLANE -+ state: PENDING -+ engine: GOSK_OBJECT_STORE_AND_NETWORK -+ linux_dependency: forbidden -+ - id: PHASE_4_LINUX_EXIT -+ state: PENDING -+ engine: GUANGHU_OS_NATIVE -+ linux_dependency: forbidden -+continuity: -+ last_receipt: state/receipts/CODE-CHANNEL-BASELINE.hldp -+ next_action: install_verified_offline_baseline_as_hosted_data_plane -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/services/code-channel/QUALITY-GATE.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/services/code-channel/QUALITY-GATE.hldp -new file mode 100644 -index 0000000..16ae450 ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/services/code-channel/QUALITY-GATE.hldp -@@ -0,0 +1,53 @@ -+schema: guanghu.native-code-quality-gate/v1 -+id: GLS-0844 -+acronym: GHNQG -+name: 光湖原生代码质量门 -+owner: HLP-MOD-CODE-CHANNEL -+authority_language: HLDP -+decision_model: -+ allowed_scores: -+ - 0 -+ - 100 -+ pass_score: 100 -+ partial_acceptance: false -+ aggregate_rule: ALL_REQUIRED_GATES_100_OR_TOTAL_0 -+ external_observers_are_blocking: false -+execution: -+ bootstrap_executor: scripts/run-guanghu-native-quality-gate.sh -+ native_target: GOSK_CODE_CHANNEL_QUALITY_EXECUTOR -+coverage_scope: -+ included: ALL_EXECUTABLE_CORE_LIBRARY_LINES -+ required_lines: 100_PERCENT -+ required_functions: 100_PERCENT -+ excluded: -+ - PROCESS_ENTRY_ADAPTERS_WITHOUT_DOMAIN_DECISIONS -+ adapter_verification: INTEGRATION_TESTED_AS_EXECUTABLES -+required_gates: -+ - id: world_and_protocol_validation -+ required_score: 100 -+ - id: unit_and_integration_tests -+ required_score: 100 -+ - id: format -+ required_score: 100 -+ - id: zero_warning_lint -+ required_score: 100 -+ - id: auditable_line_coverage_100_percent -+ required_score: 100 -+ - id: shell_syntax -+ required_score: 100 -+ - id: diff_whitespace -+ required_score: 100 -+ - id: source_tree_fingerprint -+ required_score: 100 -+ - id: sensitive_information_scan -+ required_score: 100 -+receipt: -+ schema: guanghu.native-code-quality-receipt/v1 -+ pass_state: PASS_100 -+ fail_state: FAIL_0 -+ rule: -+ - ANY_REQUIRED_GATE_BELOW_100_MAKES_TOTAL_0 -+ - NO_PARTIAL_SCORE -+ - NO_THRESHOLD_GREATER_THAN_OR_EQUAL_TO -+ - COVERAGE_MEANS_EXACT_COVERED_LINES_EQUALS_TOTAL_LINES -+ - EXTERNAL_ANALYSIS_CANNOT_AUTHORIZE_OR_BLOCK -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/services/native-recovery/PROTOCOL.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/services/native-recovery/PROTOCOL.hldp -new file mode 100644 -index 0000000..c32b12d ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/services/native-recovery/PROTOCOL.hldp -@@ -0,0 +1,45 @@ -+schema: guanghu.native-recovery-protocol/v1 -+id: GLS-0843 -+acronym: GHNRP -+name: Guanghu Native Recovery Protocol -+chinese_name: 光湖原生恢复协议 -+status: REGISTERED_IMPLEMENTED_PENDING_PHYSICAL_DEFAULT_GATE -+authority_language: HLDP -+scope: -+ node_id: GH-CVM-MAIN-PROD-01 -+ system_disk: /dev/vda -+ purpose: SWITCH_FROM_GUANGHU_NATIVE_DEFAULT_TO_HOSTED_RECOVERY -+beacon: -+ ownership: GUANGHU_OS -+ lba_start: 68 -+ sector_count: 2 -+ size_bytes: 1024 -+ format: GRUB_ENVIRONMENT_BLOCK -+ variable: guanghu_recovery -+ active_value: ubuntu -+ clear_value: ABSENT_OR_EMPTY -+grub: -+ raw_blocklist: (hd0)68+2 -+ whitelisted_variable: guanghu_recovery -+ whitelist_only: true -+ hosted_entry: gnulinux-simple-7bccaefa-b0a9-4f6f-bd32-22dde0066c0b -+ native_default_entry: guanghu-native-once -+ select_only: true -+ raw_blocklist_write: FORBIDDEN -+hosted_recovery: -+ consumer: guanghu-native-recovery-beacon-clear.service -+ consume_on_boot: true -+ verify_before_clear: true -+ readback_after_clear: true -+semantics: -+ command: HLDP-RECOVER-OS! -+ writer: GOSK_GHAL_NATIVE -+ reader: GRUB_BOOTSTRAP_COMPATIBILITY_LAYER -+ consumer: HOSTED_RECOVERY_CLEAR_SERVICE -+ result: NEXT_BOOT_HOSTED_RECOVERY_CONSUMES_BEACON_THEN_NATIVE_DEFAULT_REMAINS -+ filesystem_extent_dependency: false -+ standard_grubenv_dependency: false -+failure_policy: -+ unknown_beacon_data: FAIL_CLOSED -+ write_without_readback: FORBIDDEN -+ physical_completion_claim_without_returned_hosted_boot: FORBIDDEN -diff --git a/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/services/native-storage/DISK-LAYOUT.hldp b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/services/native-storage/DISK-LAYOUT.hldp -new file mode 100644 -index 0000000..7d7167e ---- /dev/null -+++ b/guanghu-os/deployments/GH-CVM-MAIN-PROD-01/world-seed/world/services/native-storage/DISK-LAYOUT.hldp -@@ -0,0 +1,35 @@ -+schema: guanghu.native-disk-layout/v1 -+id: GLS-0846 -+acronym: GHNLP -+name: 光湖原生磁盘布局协议 -+status: REGISTERED_IMPLEMENTATION_GATED -+authority_language: HLDP -+node_id: GH-CVM-MAIN-PROD-01 -+disk: /dev/vda -+sector_size: 512 -+regions: -+ kernel: -+ lba_start: 34 -+ sector_count: 29 -+ lba_end_inclusive: 62 -+ stage0_lba: 34 -+ stage2_lba_start: 35 -+ stage2_sector_count: 28 -+ proof_lba: 63 -+ world_store_lba: 64 -+ code_channel_store_lba: 65 -+ code_object_lba: 66 -+ branch_receipt_lba: 67 -+ recovery_beacon_lba_start: 68 -+ recovery_beacon_sector_count: 2 -+ gestational_index_lba_start: 70 -+ gestational_index_sector_count: 2 -+ control_state_lba: 72 -+ control_state_sector_count: 1 -+ alternate_kernel_lba_start: 73 -+ alternate_kernel_sector_count: 29 -+ first_partition_lba: 2048 -+ownership: -+ pre_partition_region: GUANGHU_OS_NATIVE -+ unknown_nonzero_state: FAIL_CLOSED_NO_OVERWRITE -+ overlap_rule: NO_REGION_OVERLAP -diff --git a/guanghu-os/disaster-recovery/README.md b/guanghu-os/disaster-recovery/README.md -index eb36df8..9ca6500 100644 ---- a/guanghu-os/disaster-recovery/README.md -+++ b/guanghu-os/disaster-recovery/README.md -@@ -49,6 +49,17 @@ cargo run --manifest-path guanghu-os/Cargo.toml -p guanghu-ghdr -- \ - - cargo run --manifest-path guanghu-os/Cargo.toml -p guanghu-ghdr -- \ - verify-package /path/to/sealed-recovery-package -+ -+cargo run --manifest-path guanghu-os/Cargo.toml -p guanghu-ghdr \ -+ --bin guanghu-ghdr -- layout-plan-payload /path/to/layout-plan.json \ -+ > /tmp/layout-plan-payload.json -+ -+cargo run --manifest-path guanghu-os/Cargo.toml -p guanghu-ghdr \ -+ --bin guanghu-ghdr -- verify-signed-layout-plan \ -+ /path/to/node-manifest.json \ -+ /path/to/layout-plan.json \ -+ /path/to/fresh-readback.json \ -+ "$(date +%s)" - ``` - - Replace every example identifier and receipt reference with exact evidence for -@@ -77,18 +88,22 @@ private keys, or tokens. - - A migration plan must name at least two unique recovery controller nodes. They - must be different from the target and span at least two declared failure --domains. A non-empty role is recorded for each controller. -+domains. A non-empty role and an independent pinned Ed25519 public key are -+recorded for each controller. - --Four independent receipt references are mandatory: -+Four independent receipt references and their exact SHA-256 digests are -+mandatory: - --1. a cloud-image clone that has actually booted; -+1. a zero-cost Linux rescue path that has actually booted; - 2. a control-plane backup; - 3. a completed data-restore exercise; - 4. a provider-console recovery exercise. - - A receipt reference is a pointer to evidence, not the evidence itself. The --later signed-plan gate must bind exact evidence digests and controller --identities before any destructive action can be considered. -+signed-plan payload binds the canonical digest of all four reference-and-digest -+pairs together with the controller identities before any destructive action -+can be considered. A paid cloud-image clone is neither required nor accepted as -+a substitute for the Linux rescue boot receipt. - - ## Recovery package boundary - -@@ -118,13 +133,36 @@ Example package index: - } - ``` - --## Later registered gates -+## Signed layout-plan gate -+ -+The implemented signed-plan gate canonicalizes a payload bound to the exact -+target probe digest, whole-disk identity, disk geometry, nonoverlapping A/B -+slot extents and image digests, generation, intended operation, and an expiry -+no more than one hour after issuance. Exactly two registered controllers in -+different failure domains must provide valid signatures. A target read-back -+no older than five minutes must match every signed disk field. -+ -+`ghdr-controller` creates independent Ed25519 controller keys and signatures. -+Private seeds are written once with mode `0600`, read only from files, and are -+never accepted on the command line or printed in output: - --The next implementation must add a canonical, expiring signed layout plan --bound to the target probe digest, disk/GPT identity, A/B slot extents, --generation, and intended operation. A destructive write must require two --independent controller signatures and a fresh target read-back that matches --the signed plan. -+```bash -+cargo run --manifest-path guanghu-os/Cargo.toml -p guanghu-ghdr \ -+ --bin ghdr-controller -- generate-key \ -+ /secure/controller.seed /tmp/controller-public.json \ -+ DR-CONTROLLER-A provider-a/region-a -+ -+cargo run --manifest-path guanghu-os/Cargo.toml -p guanghu-ghdr \ -+ --bin ghdr-controller -- sign-layout \ -+ /secure/controller.seed DR-CONTROLLER-A provider-a/region-a \ -+ /path/to/layout-plan.json /tmp/controller-a-signature.json -+``` -+ -+A `PASS_100_SIGNED_LAYOUT_PLAN` permits only the exact signed write while its -+freshness conditions remain true. It does not prove that the write, native -+boot, fallback, or restoration succeeded. -+ -+## Later registered gates - - Later physical acceptance must prove automatic A/B fallback, native boot - without Linux after acceptance, independent control-plane and data restoration, -diff --git a/guanghu-os/disaster-recovery/node-plan.example.json b/guanghu-os/disaster-recovery/node-plan.example.json -index 061d1f6..53f37dc 100644 ---- a/guanghu-os/disaster-recovery/node-plan.example.json -+++ b/guanghu-os/disaster-recovery/node-plan.example.json -@@ -7,18 +7,32 @@ - { - "node_id": "DR-TENCENT-SG-001", - "failure_domain": "provider:tencent/region:singapore", -- "role": "witness-and-recovery" -+ "role": "witness-and-recovery", -+ "signing_public_key_hex": "1111111111111111111111111111111111111111111111111111111111111111" - }, - { - "node_id": "DR-TENCENT-CN-001", - "failure_domain": "provider:tencent/region:china", -- "role": "backup-and-recovery" -+ "role": "backup-and-recovery", -+ "signing_public_key_hex": "2222222222222222222222222222222222222222222222222222222222222222" - } - ], - "evidence": { -- "cloud_image_clone_boot_receipt": "receipt://replace/cloud-image-clone-boot", -- "control_plane_backup_receipt": "receipt://replace/control-plane-backup", -- "data_restore_receipt": "receipt://replace/data-restore", -- "provider_console_recovery_receipt": "receipt://replace/provider-console-recovery" -+ "linux_rescue_boot_receipt": { -+ "reference": "receipt://replace/linux-rescue-boot", -+ "sha256": "replace-with-64-lowercase-hex-characters" -+ }, -+ "control_plane_backup_receipt": { -+ "reference": "receipt://replace/control-plane-backup", -+ "sha256": "replace-with-64-lowercase-hex-characters" -+ }, -+ "data_restore_receipt": { -+ "reference": "receipt://replace/data-restore", -+ "sha256": "replace-with-64-lowercase-hex-characters" -+ }, -+ "provider_console_recovery_receipt": { -+ "reference": "receipt://replace/provider-console-recovery", -+ "sha256": "replace-with-64-lowercase-hex-characters" -+ } - } - } -diff --git a/guanghu-os/native/x86_64-bios/boot.asm b/guanghu-os/native/x86_64-bios/boot.asm -index eaf2e27..11e61a4 100644 ---- a/guanghu-os/native/x86_64-bios/boot.asm -+++ b/guanghu-os/native/x86_64-bios/boot.asm -@@ -169,6 +169,10 @@ long_mode_start: - call serial_write64 - call ghal_virtio_init - jc ghal_initialization_error -+%if GHOS_AUTHENTICATED_CONTROL = 1 -+ call ghal_block_load_control_state -+ jc native_control_state_error -+%endif - %ifdef GHOS_GHAL_PROBE_STAGE - mov byte [rel physical_proof_flag], 0xa5 - %else -@@ -246,6 +250,14 @@ native_gestational_index_error: - call serial_write64 - jmp write_native_block_proof - -+%if GHOS_AUTHENTICATED_CONTROL = 1 -+native_control_state_error: -+ mov byte [rel physical_proof_flag], 0xe1 -+ mov rsi, msg_native_control_state_error -+ call serial_write64 -+ jmp write_native_block_proof -+%endif -+ - write_native_block_proof: - call ghal_block_write_proof - jc native_block_proof_error -@@ -380,6 +392,9 @@ msg_native_block_proof_error: db "GHOS_BOOT_ERROR=NATIVE_BLOCK_PROOF_WRITE", 13, - msg_native_network_proof_error: db "GHOS_BOOT_ERROR=NATIVE_ARP_GATEWAY", 13, 10, 0 - msg_native_world_store_error: db "GHOS_BOOT_ERROR=NATIVE_HLDP_WORLD_STORE", 13, 10, 0 - msg_native_gestational_index_error: db "GHOS_BOOT_ERROR=NATIVE_GHCIP_INDEX", 13, 10, 0 -+%if GHOS_AUTHENTICATED_CONTROL = 1 -+msg_native_control_state_error: db "GHOS_BOOT_ERROR=NATIVE_CONTROL_STATE", 13, 10, 0 -+%endif - msg_physical_proof_error: db "GHOS_BOOT_ERROR=DISK_PROOF_WRITE", 13, 10, 0 - align 8 - null_idt64: -@@ -441,6 +456,14 @@ physical_proof_recovery_beacon_read_verified: db 0 - physical_proof_gestational_index_initialized: db 0 - physical_proof_gestational_index_present: db 0 - physical_proof_gestational_index_read_verified: db 0 -+%if GHOS_AUTHENTICATED_CONTROL = 1 -+physical_proof_control_state_loaded: db 0 -+physical_proof_control_auth_verified: db 0 -+physical_proof_control_target_verified: db 0 -+physical_proof_control_dual_mac_verified: db 0 -+physical_proof_control_replay_rejected: db 0 -+physical_proof_control_nonce_persisted: db 0 -+%endif - times 512 - ($ - physical_proof_sector) db 0 - %endif - -diff --git a/guanghu-os/native/x86_64-bios/ghal-virtio.asm b/guanghu-os/native/x86_64-bios/ghal-virtio.asm -index c898d0d..3544520 100644 ---- a/guanghu-os/native/x86_64-bios/ghal-virtio.asm -+++ b/guanghu-os/native/x86_64-bios/ghal-virtio.asm -@@ -29,6 +29,7 @@ bits 64 - %define VIRTIO_CODE_CHANNEL_BUFFER 0x123000 - %define VIRTIO_RECOVERY_BEACON_BUFFER 0x124000 - %define VIRTIO_GESTATIONAL_INDEX_BUFFER 0x125000 -+%define VIRTIO_CONTROL_STATE_BUFFER 0x126000 - %define VIRTIO_NET_BUFFER_SIZE 2048 - %define VIRTIO_NET_HEADER_SIZE 10 - %define ETHERNET_HEADER_SIZE 14 -@@ -36,6 +37,11 @@ bits 64 - %define ICMP_HEADER_SIZE 8 - %define GHOS_LOGIN_MAGIC_OFFSET 60 - %define GHOS_LOGIN_MAGIC_SIZE 16 -+%define GHOS_CONTROL_FRAME_OFFSET 52 -+%define GHOS_CONTROL_MESSAGE_SIZE 32 -+%define GHOS_CONTROL_FRAME_SIZE 48 -+%define GHOS_CONTROL_FRAME_MAGIC 0x0000324c54434847 -+%define GHOS_CONTROL_STATE_MAGIC 0x32534c5254434847 - %define VIRTIO_QUEUE_BYTES 0x8000 - %define VIRTIO_MAX_QUEUE_SIZE 1024 - %define VIRTQ_DESC_F_NEXT 1 -@@ -48,10 +54,37 @@ bits 64 - %define NATIVE_BRANCH_RECEIPT_LBA 67 - %define NATIVE_RECOVERY_BEACON_LBA 68 - %define NATIVE_GESTATIONAL_INDEX_LBA 70 -+%define NATIVE_CONTROL_STATE_LBA 72 - - %ifndef GHOS_GHAL_PROBE_STAGE - %define GHOS_GHAL_PROBE_STAGE 0 - %endif -+%ifndef GHOS_GUEST_IPV4_DWORD -+%define GHOS_GUEST_IPV4_DWORD 0x0700000a -+%endif -+%ifndef GHOS_GATEWAY_IPV4_DWORD -+%define GHOS_GATEWAY_IPV4_DWORD 0x0100000a -+%endif -+%ifndef GHOS_AUTHENTICATED_CONTROL -+%define GHOS_AUTHENTICATED_CONTROL 0 -+%endif -+%if GHOS_AUTHENTICATED_CONTROL = 1 -+%ifndef GHOS_CONTROL_TARGET_TAG -+%error "GHOS_CONTROL_TARGET_TAG is required for authenticated control" -+%endif -+%ifndef GHOS_CONTROLLER_A_K0 -+%error "GHOS_CONTROLLER_A_K0 is required for authenticated control" -+%endif -+%ifndef GHOS_CONTROLLER_A_K1 -+%error "GHOS_CONTROLLER_A_K1 is required for authenticated control" -+%endif -+%ifndef GHOS_CONTROLLER_B_K0 -+%error "GHOS_CONTROLLER_B_K0 is required for authenticated control" -+%endif -+%ifndef GHOS_CONTROLLER_B_K1 -+%error "GHOS_CONTROLLER_B_K1 is required for authenticated control" -+%endif -+%endif - %if GHOS_GHAL_PROBE_STAGE < 0 || GHOS_GHAL_PROBE_STAGE > 9 - %error "GHOS_GHAL_PROBE_STAGE must be between 0 and 9" - %endif -@@ -689,26 +722,26 @@ ghal_block_transfer_sector: - mov byte [rel ghal_block_request_status], 0xff - - lea rax, [rel ghal_block_request_header] -- mov [VIRTIO_BLOCK_QUEUE], rax -- mov dword [VIRTIO_BLOCK_QUEUE + 8], 16 -- mov word [VIRTIO_BLOCK_QUEUE + 12], VIRTQ_DESC_F_NEXT -- mov word [VIRTIO_BLOCK_QUEUE + 14], 1 -+ mov [abs VIRTIO_BLOCK_QUEUE], rax -+ mov dword [abs VIRTIO_BLOCK_QUEUE + 8], 16 -+ mov word [abs VIRTIO_BLOCK_QUEUE + 12], VIRTQ_DESC_F_NEXT -+ mov word [abs VIRTIO_BLOCK_QUEUE + 14], 1 - -- mov [VIRTIO_BLOCK_QUEUE + 16], rsi -- mov dword [VIRTIO_BLOCK_QUEUE + 24], 512 -+ mov [abs VIRTIO_BLOCK_QUEUE + 16], rsi -+ mov dword [abs VIRTIO_BLOCK_QUEUE + 24], 512 - or r9w, VIRTQ_DESC_F_NEXT -- mov word [VIRTIO_BLOCK_QUEUE + 28], r9w -- mov word [VIRTIO_BLOCK_QUEUE + 30], 2 -+ mov word [abs VIRTIO_BLOCK_QUEUE + 28], r9w -+ mov word [abs VIRTIO_BLOCK_QUEUE + 30], 2 - - lea rax, [rel ghal_block_request_status] -- mov [VIRTIO_BLOCK_QUEUE + 32], rax -- mov dword [VIRTIO_BLOCK_QUEUE + 40], 1 -- mov word [VIRTIO_BLOCK_QUEUE + 44], VIRTQ_DESC_F_WRITE -- mov word [VIRTIO_BLOCK_QUEUE + 46], 0 -+ mov [abs VIRTIO_BLOCK_QUEUE + 32], rax -+ mov dword [abs VIRTIO_BLOCK_QUEUE + 40], 1 -+ mov word [abs VIRTIO_BLOCK_QUEUE + 44], VIRTQ_DESC_F_WRITE -+ mov word [abs VIRTIO_BLOCK_QUEUE + 46], 0 - - movzx ecx, word [rel physical_proof_block_queue_size] - test ecx, ecx -- jz .queue_missing -+ jz ghal_block_transfer_queue_missing - mov eax, ecx - shl eax, 4 - mov ebx, VIRTIO_BLOCK_QUEUE -@@ -739,23 +772,256 @@ ghal_block_transfer_sector: - mov ecx, 0x10000000 - .wait_used: - cmp word [rdi + 2], r8w -- je .completed -+ je ghal_block_transfer_completed - pause - loop .wait_used - mov byte [rel physical_proof_error_code], 0x41 - stc - ret --.completed: -+ -+%if GHOS_AUTHENTICATED_CONTROL = 1 -+%macro GHOS_SIPHASH_ROUND 0 -+ add r8, r9 -+ rol r9, 13 -+ xor r9, r8 -+ rol r8, 32 -+ add r10, r11 -+ rol r11, 16 -+ xor r11, r10 -+ add r8, r11 -+ rol r11, 21 -+ xor r11, r8 -+ add r10, r9 -+ rol r9, 17 -+ xor r9, r10 -+ rol r10, 32 -+%endmacro -+ -+; rax=k0, rdx=k1, rsi=32-byte message; returns rax=SipHash-2-4. -+ghal_siphash24_message32: -+ mov r8, 0x736f6d6570736575 -+ xor r8, rax -+ mov r9, 0x646f72616e646f6d -+ xor r9, rdx -+ mov r10, 0x6c7967656e657261 -+ xor r10, rax -+ mov r11, 0x7465646279746573 -+ xor r11, rdx -+ mov ecx, GHOS_CONTROL_MESSAGE_SIZE / 8 -+.word_loop: -+ mov rbx, [rsi] -+ add rsi, 8 -+ xor r11, rbx -+ GHOS_SIPHASH_ROUND -+ GHOS_SIPHASH_ROUND -+ xor r8, rbx -+ loop .word_loop -+ mov rbx, GHOS_CONTROL_MESSAGE_SIZE -+ shl rbx, 56 -+ xor r11, rbx -+ GHOS_SIPHASH_ROUND -+ GHOS_SIPHASH_ROUND -+ xor r8, rbx -+ xor r10, 0xff -+ GHOS_SIPHASH_ROUND -+ GHOS_SIPHASH_ROUND -+ GHOS_SIPHASH_ROUND -+ GHOS_SIPHASH_ROUND -+ mov rax, r8 -+ xor rax, r9 -+ xor rax, r10 -+ xor rax, r11 -+ ret -+ -+ghal_block_load_control_state: -+ mov rdi, VIRTIO_BLOCK_READ_BUFFER -+ xor eax, eax -+ mov ecx, 512 / 8 -+ rep stosq -+ mov eax, VIRTIO_BLK_T_IN -+ mov rsi, VIRTIO_BLOCK_READ_BUFFER -+ mov edx, NATIVE_CONTROL_STATE_LBA -+ mov r9w, VIRTQ_DESC_F_WRITE -+ call ghal_block_transfer_sector -+ jc .failed -+ mov rsi, VIRTIO_BLOCK_READ_BUFFER -+ mov ecx, 512 / 8 -+.blank_check: -+ cmp qword [rsi], 0 -+ jne .registered -+ add rsi, 8 -+ loop .blank_check -+ mov qword [rel ghal_control_last_nonce], 0 -+ mov byte [rel physical_proof_control_state_loaded], 1 -+ clc -+ ret -+.registered: -+ mov rdx, GHOS_CONTROL_STATE_MAGIC -+ cmp qword [abs VIRTIO_BLOCK_READ_BUFFER], rdx -+ jne .invalid -+ mov rax, GHOS_CONTROL_TARGET_TAG -+ cmp qword [abs VIRTIO_BLOCK_READ_BUFFER + 8], rax -+ jne .invalid -+ mov rax, [abs VIRTIO_BLOCK_READ_BUFFER + 16] -+ test rax, rax -+ jz .invalid -+ mov rdx, [abs VIRTIO_BLOCK_READ_BUFFER + 24] -+ not rdx -+ cmp rdx, rax -+ jne .invalid -+ mov rsi, VIRTIO_BLOCK_READ_BUFFER + 32 -+ mov ecx, (512 - 32) / 8 -+.tail_check: -+ cmp qword [rsi], 0 -+ jne .invalid -+ add rsi, 8 -+ loop .tail_check -+ mov [rel ghal_control_last_nonce], rax -+ mov byte [rel physical_proof_control_state_loaded], 1 -+ clc -+ ret -+.invalid: -+ mov byte [rel physical_proof_error_code], 0x6d -+.failed: -+ stc -+ ret -+ -+; rax=new nonce. Persist before acknowledging or applying the command. -+ghal_block_commit_control_nonce: -+ mov r15, rax -+ mov rdi, VIRTIO_CONTROL_STATE_BUFFER -+ xor eax, eax -+ mov ecx, 512 / 8 -+ rep stosq -+ mov rax, GHOS_CONTROL_STATE_MAGIC -+ mov [abs VIRTIO_CONTROL_STATE_BUFFER], rax -+ mov rax, GHOS_CONTROL_TARGET_TAG -+ mov [abs VIRTIO_CONTROL_STATE_BUFFER + 8], rax -+ mov [abs VIRTIO_CONTROL_STATE_BUFFER + 16], r15 -+ mov rax, r15 -+ not rax -+ mov [abs VIRTIO_CONTROL_STATE_BUFFER + 24], rax -+ mov eax, VIRTIO_BLK_T_OUT -+ mov rsi, VIRTIO_CONTROL_STATE_BUFFER -+ mov edx, NATIVE_CONTROL_STATE_LBA -+ xor r9d, r9d -+ call ghal_block_transfer_sector -+ jc .failed -+ mov rdi, VIRTIO_BLOCK_READ_BUFFER -+ xor eax, eax -+ mov ecx, 512 / 8 -+ rep stosq -+ mov eax, VIRTIO_BLK_T_IN -+ mov rsi, VIRTIO_BLOCK_READ_BUFFER -+ mov edx, NATIVE_CONTROL_STATE_LBA -+ mov r9w, VIRTQ_DESC_F_WRITE -+ call ghal_block_transfer_sector -+ jc .failed -+ mov rsi, VIRTIO_BLOCK_READ_BUFFER -+ mov rdi, VIRTIO_CONTROL_STATE_BUFFER -+ mov ecx, 512 -+ repe cmpsb -+ jne .failed -+ mov [rel ghal_control_last_nonce], r15 -+ mov byte [rel physical_proof_control_nonce_persisted], 1 -+ clc -+ ret -+.failed: -+ mov byte [rel physical_proof_error_code], 0x6e -+ stc -+ ret -+ -+; Result byte: 0 reject, 1 accept, 2 fatal persistence failure. -+ghal_authenticate_control_frame: -+ push rbx -+ push rcx -+ push rdx -+ push rsi -+ push rdi -+ push r8 -+ push r9 -+ push r10 -+ push r11 -+ push r15 -+ mov byte [rel ghal_control_auth_result], 0 -+ cmp r14d, GHOS_CONTROL_FRAME_OFFSET + GHOS_CONTROL_FRAME_SIZE -+ jb .done -+ mov rax, GHOS_CONTROL_FRAME_MAGIC -+ cmp qword [abs VIRTIO_NET_RX_BUFFER + GHOS_CONTROL_FRAME_OFFSET], rax -+ jne .done -+ mov rax, GHOS_CONTROL_TARGET_TAG -+ cmp qword [abs VIRTIO_NET_RX_BUFFER + GHOS_CONTROL_FRAME_OFFSET + 8], rax -+ jne .done -+ movzx eax, byte [abs VIRTIO_NET_RX_BUFFER + GHOS_CONTROL_FRAME_OFFSET + 24] -+ cmp byte [rel ghal_net_command_kind], 3 -+ jne .exact_command -+ cmp al, 0 -+ je .command_valid -+ cmp al, 3 -+ jne .done -+ jmp .command_valid -+.exact_command: -+ cmp al, [rel ghal_net_command_kind] -+ jne .done -+.command_valid: -+ cmp qword [abs VIRTIO_NET_RX_BUFFER + GHOS_CONTROL_FRAME_OFFSET + 24], rax -+ jne .done -+ mov r15, [abs VIRTIO_NET_RX_BUFFER + GHOS_CONTROL_FRAME_OFFSET + 16] -+ test r15, r15 -+ jz .replay -+ cmp r15, [rel ghal_control_last_nonce] -+ jbe .replay -+ lea rsi, [abs VIRTIO_NET_RX_BUFFER + GHOS_CONTROL_FRAME_OFFSET] -+ mov rax, GHOS_CONTROLLER_A_K0 -+ mov rdx, GHOS_CONTROLLER_A_K1 -+ call ghal_siphash24_message32 -+ cmp rax, [abs VIRTIO_NET_RX_BUFFER + GHOS_CONTROL_FRAME_OFFSET + 32] -+ jne .done -+ lea rsi, [abs VIRTIO_NET_RX_BUFFER + GHOS_CONTROL_FRAME_OFFSET] -+ mov rax, GHOS_CONTROLLER_B_K0 -+ mov rdx, GHOS_CONTROLLER_B_K1 -+ call ghal_siphash24_message32 -+ cmp rax, [abs VIRTIO_NET_RX_BUFFER + GHOS_CONTROL_FRAME_OFFSET + 40] -+ jne .done -+ mov rax, r15 -+ call ghal_block_commit_control_nonce -+ jc .fatal -+ mov al, [abs VIRTIO_NET_RX_BUFFER + GHOS_CONTROL_FRAME_OFFSET + 24] -+ mov [rel ghal_net_matched_kind], al -+ mov byte [rel physical_proof_control_target_verified], 1 -+ mov byte [rel physical_proof_control_dual_mac_verified], 1 -+ mov byte [rel physical_proof_control_auth_verified], 1 -+ mov byte [rel ghal_control_auth_result], 1 -+ jmp .done -+.replay: -+ mov byte [rel physical_proof_control_replay_rejected], 1 -+ jmp .done -+.fatal: -+ mov byte [rel ghal_control_auth_result], 2 -+.done: -+ pop r15 -+ pop r11 -+ pop r10 -+ pop r9 -+ pop r8 -+ pop rdi -+ pop rsi -+ pop rdx -+ pop rcx -+ pop rbx -+ ret -+%endif -+ghal_block_transfer_completed: - cmp byte [rel ghal_block_request_status], 0 -- jne .device_error -+ jne ghal_block_transfer_device_error - inc word [rel ghal_block_next_index] - clc - ret --.queue_missing: -+ghal_block_transfer_queue_missing: - mov byte [rel physical_proof_error_code], 0x40 - stc - ret --.device_error: -+ghal_block_transfer_device_error: - mov byte [rel physical_proof_error_code], 0x42 - stc - ret -@@ -771,10 +1037,10 @@ ghal_net_arp_gateway: - mov ecx, VIRTIO_NET_BUFFER_SIZE / 8 - rep stosq - -- mov qword [VIRTIO_NET_RX_QUEUE], VIRTIO_NET_RX_BUFFER -- mov dword [VIRTIO_NET_RX_QUEUE + 8], VIRTIO_NET_BUFFER_SIZE -- mov word [VIRTIO_NET_RX_QUEUE + 12], VIRTQ_DESC_F_WRITE -- mov word [VIRTIO_NET_RX_QUEUE + 14], 0 -+ mov qword [abs VIRTIO_NET_RX_QUEUE], VIRTIO_NET_RX_BUFFER -+ mov dword [abs VIRTIO_NET_RX_QUEUE + 8], VIRTIO_NET_BUFFER_SIZE -+ mov word [abs VIRTIO_NET_RX_QUEUE + 12], VIRTQ_DESC_F_WRITE -+ mov word [abs VIRTIO_NET_RX_QUEUE + 14], 0 - - movzx ecx, word [rel physical_proof_net_rx_queue_size] - test ecx, ecx -@@ -801,24 +1067,24 @@ ghal_net_arp_gateway: - cmp ecx, 6 - jae .source_mac_done - mov al, [physical_proof_mac + rcx] -- mov [VIRTIO_NET_TX_BUFFER + 16 + rcx], al -- mov [VIRTIO_NET_TX_BUFFER + 32 + rcx], al -+ mov [abs VIRTIO_NET_TX_BUFFER + 16 + rcx], al -+ mov [abs VIRTIO_NET_TX_BUFFER + 32 + rcx], al - inc ecx - jmp .copy_source_mac - .source_mac_done: -- mov word [VIRTIO_NET_TX_BUFFER + 22], 0x0608 -- mov word [VIRTIO_NET_TX_BUFFER + 24], 0x0100 -- mov word [VIRTIO_NET_TX_BUFFER + 26], 0x0008 -- mov byte [VIRTIO_NET_TX_BUFFER + 28], 6 -- mov byte [VIRTIO_NET_TX_BUFFER + 29], 4 -- mov word [VIRTIO_NET_TX_BUFFER + 30], 0x0100 -- mov dword [VIRTIO_NET_TX_BUFFER + 38], 0x0700000a -- mov dword [VIRTIO_NET_TX_BUFFER + 48], 0x0100000a -- -- mov qword [VIRTIO_NET_TX_QUEUE], VIRTIO_NET_TX_BUFFER -- mov dword [VIRTIO_NET_TX_QUEUE + 8], 70 -- mov word [VIRTIO_NET_TX_QUEUE + 12], 0 -- mov word [VIRTIO_NET_TX_QUEUE + 14], 0 -+ mov word [abs VIRTIO_NET_TX_BUFFER + 22], 0x0608 -+ mov word [abs VIRTIO_NET_TX_BUFFER + 24], 0x0100 -+ mov word [abs VIRTIO_NET_TX_BUFFER + 26], 0x0008 -+ mov byte [abs VIRTIO_NET_TX_BUFFER + 28], 6 -+ mov byte [abs VIRTIO_NET_TX_BUFFER + 29], 4 -+ mov word [abs VIRTIO_NET_TX_BUFFER + 30], 0x0100 -+ mov dword [abs VIRTIO_NET_TX_BUFFER + 38], GHOS_GUEST_IPV4_DWORD -+ mov dword [abs VIRTIO_NET_TX_BUFFER + 48], GHOS_GATEWAY_IPV4_DWORD -+ -+ mov qword [abs VIRTIO_NET_TX_QUEUE], VIRTIO_NET_TX_BUFFER -+ mov dword [abs VIRTIO_NET_TX_QUEUE + 8], 70 -+ mov word [abs VIRTIO_NET_TX_QUEUE + 12], 0 -+ mov word [abs VIRTIO_NET_TX_QUEUE + 14], 0 - - movzx ecx, word [rel physical_proof_net_tx_queue_size] - test ecx, ecx -@@ -869,19 +1135,19 @@ ghal_net_arp_gateway: - .rx_complete: - cmp dword [r12 + 8], 52 - jb .invalid_reply -- cmp word [VIRTIO_NET_RX_BUFFER + 22], 0x0608 -+ cmp word [abs VIRTIO_NET_RX_BUFFER + 22], 0x0608 - jne .invalid_reply -- cmp word [VIRTIO_NET_RX_BUFFER + 30], 0x0200 -+ cmp word [abs VIRTIO_NET_RX_BUFFER + 30], 0x0200 - jne .invalid_reply -- cmp dword [VIRTIO_NET_RX_BUFFER + 38], 0x0100000a -+ cmp dword [abs VIRTIO_NET_RX_BUFFER + 38], GHOS_GATEWAY_IPV4_DWORD - jne .invalid_reply -- cmp dword [VIRTIO_NET_RX_BUFFER + 48], 0x0700000a -+ cmp dword [abs VIRTIO_NET_RX_BUFFER + 48], GHOS_GUEST_IPV4_DWORD - jne .invalid_reply - xor ecx, ecx - .copy_gateway_mac: - cmp ecx, 6 - jae .reply_verified -- mov al, [VIRTIO_NET_RX_BUFFER + 32 + rcx] -+ mov al, [abs VIRTIO_NET_RX_BUFFER + 32 + rcx] - mov [physical_proof_gateway_mac + rcx], al - inc ecx - jmp .copy_gateway_mac -@@ -960,16 +1226,24 @@ ghal_net_icmp_login_once: - mov r14d, [r12 + rax + 8] - cmp r14d, GHOS_LOGIN_MAGIC_OFFSET + GHOS_LOGIN_MAGIC_SIZE - jb .ignore_packet -- cmp word [VIRTIO_NET_RX_BUFFER + 22], 0x0008 -+ cmp word [abs VIRTIO_NET_RX_BUFFER + 22], 0x0008 - jne .ignore_packet -- cmp byte [VIRTIO_NET_RX_BUFFER + 24], 0x45 -+ cmp byte [abs VIRTIO_NET_RX_BUFFER + 24], 0x45 - jne .ignore_packet -- cmp byte [VIRTIO_NET_RX_BUFFER + 33], 1 -+ cmp byte [abs VIRTIO_NET_RX_BUFFER + 33], 1 - jne .ignore_packet -- cmp dword [VIRTIO_NET_RX_BUFFER + 40], 0x0700000a -+ cmp dword [abs VIRTIO_NET_RX_BUFFER + 40], GHOS_GUEST_IPV4_DWORD - jne .ignore_packet -- cmp word [VIRTIO_NET_RX_BUFFER + 44], 0x0008 -+ cmp word [abs VIRTIO_NET_RX_BUFFER + 44], 0x0008 - jne .ignore_packet -+%if GHOS_AUTHENTICATED_CONTROL = 1 -+ call ghal_authenticate_control_frame -+ cmp byte [rel ghal_control_auth_result], 2 -+ je .authentication_failure -+ cmp byte [rel ghal_control_auth_result], 1 -+ jne .ignore_packet -+ jmp .magic_accepted -+%else - mov rsi, VIRTIO_NET_RX_BUFFER + GHOS_LOGIN_MAGIC_OFFSET - mov rdi, [rel ghal_net_expected_magic] - mov ecx, GHOS_LOGIN_MAGIC_SIZE -@@ -992,18 +1266,23 @@ ghal_net_icmp_login_once: - xor al, al - .store_matched_kind: - mov [rel ghal_net_matched_kind], al -+%endif - - .magic_accepted: - cmp byte [rel ghal_net_matched_kind], 0 - jne .record_command - mov byte [rel physical_proof_ipv4_rx_verified], 1 -- mov eax, [VIRTIO_NET_RX_BUFFER + 36] -+ mov eax, [abs VIRTIO_NET_RX_BUFFER + 36] - mov [rel physical_proof_login_client_ip], eax -- mov ax, [VIRTIO_NET_RX_BUFFER + 48] -+ mov ax, [abs VIRTIO_NET_RX_BUFFER + 48] - mov [rel physical_proof_login_icmp_id], ax -- mov ax, [VIRTIO_NET_RX_BUFFER + 50] -+ mov ax, [abs VIRTIO_NET_RX_BUFFER + 50] - mov [rel physical_proof_login_icmp_sequence], ax -+%if GHOS_AUTHENTICATED_CONTROL = 1 -+ lea rsi, [rel ghal_login_magic] -+%else - mov rsi, VIRTIO_NET_RX_BUFFER + GHOS_LOGIN_MAGIC_OFFSET -+%endif - lea rdi, [rel physical_proof_login_magic] - mov ecx, GHOS_LOGIN_MAGIC_SIZE - rep movsb -@@ -1032,33 +1311,39 @@ ghal_net_icmp_login_once: - stc - ret - -+%if GHOS_AUTHENTICATED_CONTROL = 1 -+.authentication_failure: -+ stc -+ ret -+%endif -+ - .build_reply: - mov rsi, VIRTIO_NET_RX_BUFFER - mov rdi, VIRTIO_NET_TX_BUFFER - mov ecx, r14d - rep movsb -- mov qword [VIRTIO_NET_TX_BUFFER], 0 -- mov word [VIRTIO_NET_TX_BUFFER + 8], 0 -+ mov qword [abs VIRTIO_NET_TX_BUFFER], 0 -+ mov word [abs VIRTIO_NET_TX_BUFFER + 8], 0 - - xor ecx, ecx - .swap_mac: - cmp ecx, 6 - jae .mac_swapped -- mov al, [VIRTIO_NET_TX_BUFFER + 10 + rcx] -- mov dl, [VIRTIO_NET_TX_BUFFER + 16 + rcx] -- mov [VIRTIO_NET_TX_BUFFER + 10 + rcx], dl -- mov [VIRTIO_NET_TX_BUFFER + 16 + rcx], al -+ mov al, [abs VIRTIO_NET_TX_BUFFER + 10 + rcx] -+ mov dl, [abs VIRTIO_NET_TX_BUFFER + 16 + rcx] -+ mov [abs VIRTIO_NET_TX_BUFFER + 10 + rcx], dl -+ mov [abs VIRTIO_NET_TX_BUFFER + 16 + rcx], al - inc ecx - jmp .swap_mac - .mac_swapped: -- mov eax, [VIRTIO_NET_TX_BUFFER + 36] -- mov edx, [VIRTIO_NET_TX_BUFFER + 40] -- mov [VIRTIO_NET_TX_BUFFER + 36], edx -- mov [VIRTIO_NET_TX_BUFFER + 40], eax -- mov byte [VIRTIO_NET_TX_BUFFER + 44], 0 -- mov word [VIRTIO_NET_TX_BUFFER + 46], 0 -- -- movzx eax, word [VIRTIO_NET_TX_BUFFER + 26] -+ mov eax, [abs VIRTIO_NET_TX_BUFFER + 36] -+ mov edx, [abs VIRTIO_NET_TX_BUFFER + 40] -+ mov [abs VIRTIO_NET_TX_BUFFER + 36], edx -+ mov [abs VIRTIO_NET_TX_BUFFER + 40], eax -+ mov byte [abs VIRTIO_NET_TX_BUFFER + 44], 0 -+ mov word [abs VIRTIO_NET_TX_BUFFER + 46], 0 -+ -+ movzx eax, word [abs VIRTIO_NET_TX_BUFFER + 26] - xchg al, ah - cmp eax, IPV4_HEADER_SIZE + ICMP_HEADER_SIZE - jb .invalid_packet -@@ -1092,12 +1377,12 @@ ghal_net_icmp_login_once: - add ebx, eax - not bx - xchg bl, bh -- mov [VIRTIO_NET_TX_BUFFER + 46], bx -+ mov [abs VIRTIO_NET_TX_BUFFER + 46], bx - -- mov qword [VIRTIO_NET_TX_QUEUE], VIRTIO_NET_TX_BUFFER -- mov [VIRTIO_NET_TX_QUEUE + 8], r14d -- mov word [VIRTIO_NET_TX_QUEUE + 12], 0 -- mov word [VIRTIO_NET_TX_QUEUE + 14], 0 -+ mov qword [abs VIRTIO_NET_TX_QUEUE], VIRTIO_NET_TX_BUFFER -+ mov [abs VIRTIO_NET_TX_QUEUE + 8], r14d -+ mov word [abs VIRTIO_NET_TX_QUEUE + 12], 0 -+ mov word [abs VIRTIO_NET_TX_QUEUE + 14], 0 - - movzx ecx, word [rel physical_proof_net_tx_queue_size] - test ecx, ecx -@@ -1170,6 +1455,11 @@ ghal_net_tx_next_index: dw 2 - ghal_net_expected_magic: dq ghal_login_magic - ghal_net_command_kind: db 0 - ghal_net_matched_kind: db 0 -+%if GHOS_AUTHENTICATED_CONTROL = 1 -+align 8 -+ghal_control_last_nonce: dq 0 -+ghal_control_auth_result: db 0 -+%endif - - msg_ghal_net_discovered: db "GHOS_GHAL_VIRTIO_NET=DISCOVERED", 13, 10, 0 - msg_ghal_block_discovered: db "GHOS_GHAL_VIRTIO_BLOCK=DISCOVERED", 13, 10, 0 -diff --git a/guanghu-os/native/x86_64-bios/physical-test-mbr.asm b/guanghu-os/native/x86_64-bios/physical-test-mbr.asm -index c2f883e..1dbeba7 100644 ---- a/guanghu-os/native/x86_64-bios/physical-test-mbr.asm -+++ b/guanghu-os/native/x86_64-bios/physical-test-mbr.asm -@@ -2,7 +2,9 @@ bits 16 - org 0x7c00 - - %define COM1 0x3f8 -+%ifndef CANDIDATE_LBA - %define CANDIDATE_LBA 34 -+%endif - %define PROOF_LBA 63 - - start: -diff --git a/guanghu-os/scripts/build-native-physical-candidate.sh b/guanghu-os/scripts/build-native-physical-candidate.sh -index 2f22a0b..601a75d 100755 ---- a/guanghu-os/scripts/build-native-physical-candidate.sh -+++ b/guanghu-os/scripts/build-native-physical-candidate.sh -@@ -10,6 +10,38 @@ world_root=$(readlink -f "$1") - output_root=$(readlink -m "$2") - source_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) - native_root=${source_root}/native/x86_64-bios -+guest_ipv4_dword=${GHOS_GUEST_IPV4_DWORD:-0x0700000a} -+gateway_ipv4_dword=${GHOS_GATEWAY_IPV4_DWORD:-0x0100000a} -+candidate_lba=${GHOS_CANDIDATE_LBA:-34} -+stage2_lba=$((candidate_lba + 1)) -+authenticated_control=${GHOS_AUTHENTICATED_CONTROL:-0} -+control_args=(-dGHOS_AUTHENTICATED_CONTROL=0) -+ -+[[ ${guest_ipv4_dword} =~ ^0x[0-9a-fA-F]{8}$ ]] -+[[ ${gateway_ipv4_dword} =~ ^0x[0-9a-fA-F]{8}$ ]] -+[[ ${candidate_lba} == 34 || ${candidate_lba} == 73 ]] -+if [[ ${authenticated_control} == 1 ]]; then -+ for value in \ -+ "${GHOS_CONTROL_TARGET_TAG:-}" \ -+ "${GHOS_CONTROLLER_A_K0:-}" \ -+ "${GHOS_CONTROLLER_A_K1:-}" \ -+ "${GHOS_CONTROLLER_B_K0:-}" \ -+ "${GHOS_CONTROLLER_B_K1:-}"; do -+ [[ ${value} =~ ^0x[0-9a-fA-F]{16}$ ]] -+ done -+ [[ ${GHOS_CONTROLLER_A_K0}:${GHOS_CONTROLLER_A_K1} != "${GHOS_CONTROLLER_B_K0}:${GHOS_CONTROLLER_B_K1}" ]] -+ control_args=( -+ -dGHOS_AUTHENTICATED_CONTROL=1 -+ -dGHOS_CONTROL_TARGET_TAG="${GHOS_CONTROL_TARGET_TAG}" -+ -dGHOS_CONTROLLER_A_K0="${GHOS_CONTROLLER_A_K0}" -+ -dGHOS_CONTROLLER_A_K1="${GHOS_CONTROLLER_A_K1}" -+ -dGHOS_CONTROLLER_B_K0="${GHOS_CONTROLLER_B_K0}" -+ -dGHOS_CONTROLLER_B_K1="${GHOS_CONTROLLER_B_K1}" -+ ) -+elif [[ ${authenticated_control} != 0 ]]; then -+ echo "GHOS_AUTHENTICATED_CONTROL must be 0 or 1" >&2 -+ exit 65 -+fi - - command -v nasm >/dev/null - mkdir -p "${output_root}" -@@ -18,8 +50,11 @@ cargo run --quiet --manifest-path "${source_root}/Cargo.toml" \ - ( - cd "${output_root}" - nasm -f bin -I "${output_root}/" -I "${native_root}/" \ -- -dSTAGE2_LBA=35 \ -+ -dSTAGE2_LBA="${stage2_lba}" \ - -dGHOS_PHYSICAL_CANDIDATE=1 \ -+ -dGHOS_GUEST_IPV4_DWORD="${guest_ipv4_dword}" \ -+ -dGHOS_GATEWAY_IPV4_DWORD="${gateway_ipv4_dword}" \ -+ "${control_args[@]}" \ - "${native_root}/boot.asm" \ - -o guanghu-os-x86_64-bios-physical.img - ) -diff --git a/guanghu-os/scripts/build-native-resident-candidate.sh b/guanghu-os/scripts/build-native-resident-candidate.sh -index 2ea7f19..07577a8 100755 ---- a/guanghu-os/scripts/build-native-resident-candidate.sh -+++ b/guanghu-os/scripts/build-native-resident-candidate.sh -@@ -10,6 +10,35 @@ world_root=$(readlink -f "$1") - output_root=$(readlink -m "$2") - source_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) - native_root=${source_root}/native/x86_64-bios -+guest_ipv4_dword=${GHOS_GUEST_IPV4_DWORD:-0x0700000a} -+gateway_ipv4_dword=${GHOS_GATEWAY_IPV4_DWORD:-0x0100000a} -+authenticated_control=${GHOS_AUTHENTICATED_CONTROL:-0} -+control_args=(-dGHOS_AUTHENTICATED_CONTROL=0) -+ -+[[ ${guest_ipv4_dword} =~ ^0x[0-9a-fA-F]{8}$ ]] -+[[ ${gateway_ipv4_dword} =~ ^0x[0-9a-fA-F]{8}$ ]] -+if [[ ${authenticated_control} == 1 ]]; then -+ for value in \ -+ "${GHOS_CONTROL_TARGET_TAG:-}" \ -+ "${GHOS_CONTROLLER_A_K0:-}" \ -+ "${GHOS_CONTROLLER_A_K1:-}" \ -+ "${GHOS_CONTROLLER_B_K0:-}" \ -+ "${GHOS_CONTROLLER_B_K1:-}"; do -+ [[ ${value} =~ ^0x[0-9a-fA-F]{16}$ ]] -+ done -+ [[ ${GHOS_CONTROLLER_A_K0}:${GHOS_CONTROLLER_A_K1} != "${GHOS_CONTROLLER_B_K0}:${GHOS_CONTROLLER_B_K1}" ]] -+ control_args=( -+ -dGHOS_AUTHENTICATED_CONTROL=1 -+ -dGHOS_CONTROL_TARGET_TAG="${GHOS_CONTROL_TARGET_TAG}" -+ -dGHOS_CONTROLLER_A_K0="${GHOS_CONTROLLER_A_K0}" -+ -dGHOS_CONTROLLER_A_K1="${GHOS_CONTROLLER_A_K1}" -+ -dGHOS_CONTROLLER_B_K0="${GHOS_CONTROLLER_B_K0}" -+ -dGHOS_CONTROLLER_B_K1="${GHOS_CONTROLLER_B_K1}" -+ ) -+elif [[ ${authenticated_control} != 0 ]]; then -+ echo "GHOS_AUTHENTICATED_CONTROL must be 0 or 1" >&2 -+ exit 65 -+fi - - command -v nasm >/dev/null - mkdir -p "${output_root}" -@@ -21,6 +50,9 @@ cargo run --quiet --manifest-path "${source_root}/Cargo.toml" \ - -dSTAGE2_LBA=35 \ - -dGHOS_PHYSICAL_CANDIDATE=1 \ - -dGHOS_NATIVE_RESIDENT=1 \ -+ -dGHOS_GUEST_IPV4_DWORD="${guest_ipv4_dword}" \ -+ -dGHOS_GATEWAY_IPV4_DWORD="${gateway_ipv4_dword}" \ -+ "${control_args[@]}" \ - "${native_root}/boot.asm" \ - -o guanghu-os-x86_64-bios-resident.img - ) -diff --git a/guanghu-os/scripts/install-native-ab-signed.sh b/guanghu-os/scripts/install-native-ab-signed.sh -new file mode 100755 -index 0000000..b408453 ---- /dev/null -+++ b/guanghu-os/scripts/install-native-ab-signed.sh -@@ -0,0 +1,223 @@ -+#!/usr/bin/env bash -+set -euo pipefail -+ -+fail() { -+ echo "GHDR_FAIL_0: $*" >&2 -+ exit 65 -+} -+ -+[[ $# -eq 7 ]] || { -+ echo "usage: install-native-ab-signed.sh " >&2 -+ exit 64 -+} -+[[ ${EUID} -eq 0 ]] || { -+ echo "GHDR_FAIL_0: must run as root" >&2 -+ exit 77 -+} -+ -+for command in blockdev cmp date dd install python3 readlink sfdisk sha256sum stat sync; do -+ command -v "${command}" >/dev/null || fail "required command is unavailable: ${command}" -+done -+ -+ghdr_bin=$(readlink -f "$1") -+manifest=$(readlink -f "$2") -+plan=$(readlink -f "$3") -+slot_a_image=$(readlink -f "$4") -+slot_b_image=$(readlink -f "$5") -+disk=$(readlink -f "$6") -+recovery_root=$(readlink -m "$7") -+ -+[[ -x ${ghdr_bin} ]] || fail "GHDR verifier is not executable" -+for input in "${manifest}" "${plan}" "${slot_a_image}" "${slot_b_image}"; do -+ [[ -f ${input} && ! -L ${input} ]] || fail "signed installation input is not a regular file: ${input}" -+done -+[[ -b ${disk} ]] || fail "target must be a whole block device" -+[[ ! -e ${recovery_root} ]] || fail "recovery root already exists" -+[[ ! -L $(dirname "${recovery_root}") ]] || fail "recovery parent must not be a symlink" -+ -+work=$(mktemp -d) -+cleanup() { -+ rm -rf "${work}" -+} -+trap cleanup EXIT -+ -+python3 - "${plan}" "${work}/plan.env" <<'PY' -+import json -+import shlex -+import sys -+ -+with open(sys.argv[1], "r", encoding="utf-8") as handle: -+ plan = json.load(handle) -+payload = plan["payload"] -+slots = {slot["name"]: slot for slot in payload["slots"]} -+required = { -+ "PLAN_NODE_ID": payload["node_id"], -+ "PLAN_SYSTEM_DISK": payload["system_disk"], -+ "PLAN_DISK_SECTORS": payload["disk_sectors"], -+ "PLAN_SECTOR_BYTES": payload["logical_sector_bytes"], -+ "PLAN_DISK_IDENTITY_SHA": payload["disk_identity_sha256"], -+ "PLAN_FIRST_PARTITION_LBA": payload["first_partition_lba"], -+ "PLAN_PROBE_SHA": payload["target_probe_sha256"], -+ "PLAN_EVIDENCE_SHA": payload["recovery_evidence_sha256"], -+ "PLAN_GENERATION": payload["generation"], -+ "SLOT_A_START": slots["A"]["lba_start"], -+ "SLOT_A_COUNT": slots["A"]["sector_count"], -+ "SLOT_A_SHA": slots["A"]["image_sha256"], -+ "SLOT_B_START": slots["B"]["lba_start"], -+ "SLOT_B_COUNT": slots["B"]["sector_count"], -+ "SLOT_B_SHA": slots["B"]["image_sha256"], -+} -+with open(sys.argv[2], "x", encoding="utf-8") as handle: -+ for key, value in required.items(): -+ handle.write(f"{key}={shlex.quote(str(value))}\n") -+PY -+# shellcheck disable=SC1091 -+source "${work}/plan.env" -+ -+[[ ${disk} == "${PLAN_SYSTEM_DISK}" ]] || fail "target disk does not match the signed plan" -+[[ ${SLOT_A_START} == 34 && ${SLOT_A_COUNT} == 29 ]] || fail "slot A extent is not registered" -+[[ ${SLOT_B_START} == 73 && ${SLOT_B_COUNT} == 29 ]] || fail "slot B extent is not registered" -+[[ ${PLAN_FIRST_PARTITION_LBA} == 2048 ]] || fail "Linux partition boundary is not registered" -+[[ ${PLAN_SECTOR_BYTES} == 512 ]] || fail "logical sector size is not registered" -+[[ $(stat -c %s "${slot_a_image}") == $((SLOT_A_COUNT * PLAN_SECTOR_BYTES)) ]] || fail "slot A image size mismatch" -+[[ $(stat -c %s "${slot_b_image}") == $((SLOT_B_COUNT * PLAN_SECTOR_BYTES)) ]] || fail "slot B image size mismatch" -+[[ $(sha256sum "${slot_a_image}" | awk '{print $1}') == "${SLOT_A_SHA}" ]] || fail "slot A image digest mismatch" -+[[ $(sha256sum "${slot_b_image}" | awk '{print $1}') == "${SLOT_B_SHA}" ]] || fail "slot B image digest mismatch" -+ -+collect_disk_evidence() { -+ local prefix=$1 -+ sfdisk --json "${disk}" >"${work}/${prefix}.sfdisk.json" -+ blockdev --getsz "${disk}" >"${work}/${prefix}.sectors" -+ blockdev --getss "${disk}" >"${work}/${prefix}.sector-bytes" -+ sha256sum "${work}/${prefix}.sfdisk.json" | awk '{print $1}' >"${work}/${prefix}.identity" -+ python3 - "${work}/${prefix}.sfdisk.json" >"${work}/${prefix}.first-partition" <<'PY' -+import json -+import sys -+with open(sys.argv[1], "r", encoding="utf-8") as handle: -+ table = json.load(handle)["partitiontable"] -+starts = [int(partition["start"]) for partition in table["partitions"]] -+if not starts: -+ raise SystemExit("partition table has no Linux rescue partition") -+print(min(starts)) -+PY -+} -+ -+collect_disk_evidence before -+[[ $(<"${work}/before.sectors") == "${PLAN_DISK_SECTORS}" ]] || fail "disk sector count drifted" -+[[ $(<"${work}/before.sector-bytes") == "${PLAN_SECTOR_BYTES}" ]] || fail "disk sector size drifted" -+[[ $(<"${work}/before.identity") == "${PLAN_DISK_IDENTITY_SHA}" ]] || fail "disk identity drifted" -+[[ $(<"${work}/before.first-partition") == "${PLAN_FIRST_PARTITION_LBA}" ]] || fail "first partition boundary drifted" -+ -+now_unix=$(date +%s) -+export PLAN_NODE_ID PLAN_PROBE_SHA PLAN_SYSTEM_DISK PLAN_DISK_SECTORS -+export PLAN_SECTOR_BYTES PLAN_DISK_IDENTITY_SHA PLAN_FIRST_PARTITION_LBA now_unix -+python3 - "${work}/readback.json" <"${work}/verification.json" || fail "signed layout verification rejected the write" -+python3 - "${work}/verification.json" <<'PY' || fail "signed layout verification did not return PASS_100" -+import json -+import sys -+with open(sys.argv[1], "r", encoding="utf-8") as handle: -+ result = json.load(handle) -+expected = { -+ "status": "PASS_100_SIGNED_LAYOUT_PLAN", -+ "gate_score": 100, -+ "allows_disk_write": True, -+ "verified_controller_count": 2, -+ "target_readback_fresh": True, -+ "target_readback_matches": True, -+} -+if any(result.get(key) != value for key, value in expected.items()): -+ raise SystemExit(1) -+PY -+ -+dd if="${disk}" of="${work}/slot-a.before" bs=512 skip="${SLOT_A_START}" count="${SLOT_A_COUNT}" status=none -+dd if="${disk}" of="${work}/slot-b.before" bs=512 skip="${SLOT_B_START}" count="${SLOT_B_COUNT}" status=none -+cmp -s "${work}/slot-a.before" <(head -c $((SLOT_A_COUNT * PLAN_SECTOR_BYTES)) /dev/zero) || fail "slot A contains unknown data" -+cmp -s "${work}/slot-b.before" <(head -c $((SLOT_B_COUNT * PLAN_SECTOR_BYTES)) /dev/zero) || fail "slot B contains unknown data" -+ -+install -d -m 0700 "${recovery_root}" -+install -m 0400 "${manifest}" "${recovery_root}/node-manifest.json" -+install -m 0400 "${plan}" "${recovery_root}/signed-layout-plan.json" -+install -m 0400 "${work}/verification.json" "${recovery_root}/layout-verification.json" -+install -m 0400 "${work}/before.sfdisk.json" "${recovery_root}/sfdisk-before.json" -+install -m 0400 "${work}/slot-a.before" "${recovery_root}/slot-a.before.bin" -+install -m 0400 "${work}/slot-b.before" "${recovery_root}/slot-b.before.bin" -+dd if="${disk}" of="${recovery_root}/first-2MiB.before.bin" bs=1M count=2 status=none -+dd if="${disk}" of="${recovery_root}/last-2MiB.before.bin" bs=512 skip=$((PLAN_DISK_SECTORS - 4096)) count=4096 status=none -+install -m 0400 "${slot_a_image}" "${recovery_root}/slot-a.candidate.img" -+install -m 0400 "${slot_b_image}" "${recovery_root}/slot-b.candidate.img" -+[[ -f /boot/grub/grub.cfg ]] && install -m 0400 /boot/grub/grub.cfg "${recovery_root}/grub.cfg.before" -+[[ -f /boot/grub/grubenv ]] && install -m 0400 /boot/grub/grubenv "${recovery_root}/grubenv.before" -+ -+# Close the time-of-check/time-of-use window immediately before the first write. -+collect_disk_evidence commit -+cmp -s "${work}/before.sfdisk.json" "${work}/commit.sfdisk.json" || fail "partition table changed before commit" -+cmp -s "${work}/before.sectors" "${work}/commit.sectors" || fail "disk geometry changed before commit" -+cmp -s "${work}/before.sector-bytes" "${work}/commit.sector-bytes" || fail "sector size changed before commit" -+cmp -s "${work}/before.identity" "${work}/commit.identity" || fail "disk identity changed before commit" -+cmp -s "${work}/before.first-partition" "${work}/commit.first-partition" || fail "partition boundary changed before commit" -+dd if="${disk}" of="${work}/slot-a.commit" bs=512 skip="${SLOT_A_START}" count="${SLOT_A_COUNT}" status=none -+dd if="${disk}" of="${work}/slot-b.commit" bs=512 skip="${SLOT_B_START}" count="${SLOT_B_COUNT}" status=none -+cmp -s "${work}/slot-a.before" "${work}/slot-a.commit" || fail "slot A changed before commit" -+cmp -s "${work}/slot-b.before" "${work}/slot-b.commit" || fail "slot B changed before commit" -+ -+# B first keeps the still-unconfigured Linux boot path unchanged if A cannot be committed. -+dd if="${slot_b_image}" of="${disk}" bs=512 seek="${SLOT_B_START}" count="${SLOT_B_COUNT}" conv=notrunc,fsync status=none -+dd if="${slot_a_image}" of="${disk}" bs=512 seek="${SLOT_A_START}" count="${SLOT_A_COUNT}" conv=notrunc,fsync status=none -+sync -+ -+slot_a_readback=$(dd if="${disk}" bs=512 skip="${SLOT_A_START}" count="${SLOT_A_COUNT}" status=none | sha256sum | awk '{print $1}') -+slot_b_readback=$(dd if="${disk}" bs=512 skip="${SLOT_B_START}" count="${SLOT_B_COUNT}" status=none | sha256sum | awk '{print $1}') -+[[ ${slot_a_readback} == "${SLOT_A_SHA}" ]] || fail "slot A post-write readback failed" -+[[ ${slot_b_readback} == "${SLOT_B_SHA}" ]] || fail "slot B post-write readback failed" -+ -+observed_at=$(date --iso-8601=seconds) -+cat >"${recovery_root}/INSTALL-RECEIPT.hldp" <"${work}/SHA256SUMS" -+install -m 0400 "${work}/SHA256SUMS" "${recovery_root}/SHA256SUMS" -+cat "${recovery_root}/INSTALL-RECEIPT.hldp" -diff --git a/guanghu-os/scripts/qemu-native-net-peer.py b/guanghu-os/scripts/qemu-native-net-peer.py -index 91af8f7..03f0adc 100644 ---- a/guanghu-os/scripts/qemu-native-net-peer.py -+++ b/guanghu-os/scripts/qemu-native-net-peer.py -@@ -1,5 +1,6 @@ - #!/usr/bin/env python3 - import argparse -+import hashlib - import socket - import struct - import time -@@ -14,6 +15,94 @@ LOGIN_MAGIC = b"HLDP-GHOS-LOGIN!" - COMMIT_MAGIC = b"HLDP-CODE-COMMIT" - BRANCH_MAGIC = b"HLDP-BRANCH-MOVE" - RECOVERY_MAGIC = b"HLDP-RECOVER-OS!" -+CONTROL_MAGIC = b"GHCTL2\0\0" -+CONTROL_MESSAGE_SIZE = 32 -+CONTROL_FRAME_SIZE = 48 -+ -+ -+def _rotate_left(value: int, shift: int) -> int: -+ return ((value << shift) | (value >> (64 - shift))) & 0xFFFFFFFFFFFFFFFF -+ -+ -+def siphash24(key: bytes, message: bytes) -> int: -+ if len(key) != 16: -+ raise ValueError("SipHash keys must contain exactly 16 bytes") -+ k0, k1 = struct.unpack(" None: -+ nonlocal v0, v1, v2, v3 -+ v0 = (v0 + v1) & 0xFFFFFFFFFFFFFFFF -+ v1 = _rotate_left(v1, 13) ^ v0 -+ v0 = _rotate_left(v0, 32) -+ v2 = (v2 + v3) & 0xFFFFFFFFFFFFFFFF -+ v3 = _rotate_left(v3, 16) ^ v2 -+ v0 = (v0 + v3) & 0xFFFFFFFFFFFFFFFF -+ v3 = _rotate_left(v3, 21) ^ v0 -+ v2 = (v2 + v1) & 0xFFFFFFFFFFFFFFFF -+ v1 = _rotate_left(v1, 17) ^ v2 -+ v2 = _rotate_left(v2, 32) -+ -+ whole = len(message) - (len(message) % 8) -+ for offset in range(0, whole, 8): -+ word = struct.unpack_from(" int: -+ if not node_id or any( -+ not (character.isascii() and (character.isupper() or character.isdigit() or character == "-")) -+ for character in node_id -+ ): -+ raise ValueError("node id must use uppercase ASCII letters, digits, and hyphens") -+ return int.from_bytes(hashlib.sha256(node_id.encode("ascii")).digest()[:8], "little") -+ -+ -+def control_frame( -+ *, -+ node_id: str, -+ nonce: int, -+ command: int, -+ controller_a_key: bytes, -+ controller_b_key: bytes, -+) -> bytes: -+ if not 0 < nonce < 1 << 64: -+ raise ValueError("control nonce must be a nonzero unsigned 64-bit integer") -+ if command not in range(4): -+ raise ValueError("control command is not registered") -+ message = ( -+ CONTROL_MAGIC -+ + struct.pack(" int: -@@ -69,6 +158,40 @@ def icmp_request(sequence: int, magic: bytes) -> bytes: - return GUEST_MAC + PEER_MAC + b"\x08\x00" + ip + icmp - - -+def authenticated_icmp_request(sequence: int, frame: bytes) -> bytes: -+ if len(frame) != CONTROL_FRAME_SIZE: -+ raise ValueError("authenticated control frame has an invalid size") -+ icmp = struct.pack("!BBHHH", 8, 0, 0, 0x4748, sequence) + frame -+ icmp = icmp[:2] + struct.pack("!H", checksum(icmp)) + icmp[4:] -+ total_length = 20 + len(icmp) -+ ip = struct.pack( -+ "!BBHHHBBH4s4s", -+ 0x45, -+ 0, -+ total_length, -+ 0x484C, -+ 0, -+ 64, -+ 1, -+ 0, -+ LOGIN_CLIENT_IP, -+ GUEST_IP, -+ ) -+ ip = ip[:10] + struct.pack("!H", checksum(ip)) + ip[12:] -+ return GUEST_MAC + PEER_MAC + b"\x08\x00" + ip + icmp -+ -+ -+def validate_authenticated_reply(frame: bytes, control: bytes) -> None: -+ assert frame[0:6] == PEER_MAC -+ assert frame[6:12] == GUEST_MAC -+ assert frame[12:14] == b"\x08\x00" -+ assert frame[26:30] == GUEST_IP -+ assert frame[30:34] == LOGIN_CLIENT_IP -+ assert frame[34] == 0 -+ assert frame[42:42 + CONTROL_FRAME_SIZE] == control -+ assert checksum(frame[34:]) == 0 -+ -+ - def validate_reply(frame: bytes, magic: bytes) -> None: - assert frame[0:6] == PEER_MAC - assert frame[6:12] == GUEST_MAC -@@ -87,8 +210,45 @@ def main() -> None: - parser.add_argument("--receipt", required=True) - parser.add_argument("--resident", action="store_true") - parser.add_argument("--login-only", action="store_true") -+ parser.add_argument("--guest-mac", default="52:54:00:26:71:98") -+ parser.add_argument("--guest-ip", default="10.0.0.7") -+ parser.add_argument("--peer-ip", default="10.0.0.1") -+ parser.add_argument("--login-client-ip", default="10.0.0.2") -+ parser.add_argument("--authenticated-control", action="store_true") -+ parser.add_argument("--node-id") -+ parser.add_argument("--controller-a-key-hex") -+ parser.add_argument("--controller-b-key-hex") -+ parser.add_argument("--nonce-start", type=int, default=1) -+ parser.add_argument("--exercise-auth-rejections", action="store_true") -+ parser.add_argument("--prior-nonce-probe", type=int, default=0) - args = parser.parse_args() - -+ global GUEST_MAC, GUEST_IP, PEER_IP, LOGIN_CLIENT_IP -+ GUEST_MAC = bytes.fromhex(args.guest_mac.replace(":", "")) -+ GUEST_IP = socket.inet_aton(args.guest_ip) -+ PEER_IP = socket.inet_aton(args.peer_ip) -+ LOGIN_CLIENT_IP = socket.inet_aton(args.login_client_ip) -+ controller_a_key = None -+ controller_b_key = None -+ if args.authenticated_control: -+ if not args.node_id or not args.controller_a_key_hex or not args.controller_b_key_hex: -+ parser.error("authenticated control requires a node id and two controller keys") -+ try: -+ controller_a_key = bytes.fromhex(args.controller_a_key_hex) -+ controller_b_key = bytes.fromhex(args.controller_b_key_hex) -+ except ValueError as error: -+ parser.error(f"controller keys must be hexadecimal: {error}") -+ if len(controller_a_key) != 16 or len(controller_b_key) != 16: -+ parser.error("each controller key must contain exactly 16 bytes") -+ if controller_a_key == controller_b_key: -+ parser.error("controller keys must be independent") -+ if args.nonce_start <= 0: -+ parser.error("nonce start must be positive") -+ elif args.exercise_auth_rejections: -+ parser.error("authentication rejection probes require authenticated control") -+ if args.prior_nonce_probe < 0: -+ parser.error("prior nonce probe cannot be negative") -+ - peer = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - peer.bind(("127.0.0.1", args.listen_port)) - peer.settimeout(0.2) -@@ -101,6 +261,14 @@ def main() -> None: - resident_login_reply_count = 0 - recovery_reply_verified = False - command_phase = "login" -+ control_nonce = args.nonce_start -+ last_control_frame = None -+ rejection_probes_sent = False -+ rejection_probes_sent_at = 0.0 -+ valid_control_sent = not args.exercise_auth_rejections -+ replay_probe_pending = False -+ replay_probe_sent_at = 0.0 -+ replay_probe_done = not args.exercise_auth_rejections - - def write_receipt( - *, -@@ -121,6 +289,19 @@ def main() -> None: - "recovery_reply_verified: " - f"{str(recovery_reply_verified).lower()}\n" - "login_magic: HLDP-GHOS-LOGIN!\n" -+ "authenticated_control: " -+ f"{str(args.authenticated_control).lower()}\n" -+ f"last_accepted_nonce: {control_nonce - 1 if args.authenticated_control else 0}\n" -+ "legacy_control_rejected: " -+ f"{str(args.exercise_auth_rejections).lower()}\n" -+ "wrong_target_rejected: " -+ f"{str(args.exercise_auth_rejections).lower()}\n" -+ "bad_dual_mac_rejected: " -+ f"{str(args.exercise_auth_rejections).lower()}\n" -+ "replayed_nonce_rejected: " -+ f"{str(args.exercise_auth_rejections).lower()}\n" -+ "persisted_prior_nonce_rejected: " -+ f"{str(args.prior_nonce_probe > 0).lower()}\n" - ) - - def phase_magic() -> bytes: -@@ -132,13 +313,85 @@ def main() -> None: - "recovery": RECOVERY_MAGIC, - }[command_phase] - -+ def phase_command() -> int: -+ return { -+ "login": 0, -+ "commit": 1, -+ "branch": 2, -+ "resident_login": 0, -+ "recovery": 3, -+ }[command_phase] -+ -+ def request(sequence: int) -> bytes: -+ nonlocal last_control_frame -+ if not args.authenticated_control: -+ return icmp_request(sequence, phase_magic()) -+ assert controller_a_key is not None and controller_b_key is not None -+ last_control_frame = control_frame( -+ node_id=args.node_id, -+ nonce=control_nonce, -+ command=phase_command(), -+ controller_a_key=controller_a_key, -+ controller_b_key=controller_b_key, -+ ) -+ return authenticated_icmp_request(sequence, last_control_frame) -+ - while time.monotonic() < deadline: - try: - frame = peer.recv(4096) - except TimeoutError: - if arp_verified: -+ if args.exercise_auth_rejections and not rejection_probes_sent: -+ assert controller_a_key is not None and controller_b_key is not None -+ peer.sendto(icmp_request(0, phase_magic()), qemu) -+ wrong_target = control_frame( -+ node_id="GH-CVM-MAIN-PROD-01-WRONG", -+ nonce=control_nonce, -+ command=phase_command(), -+ controller_a_key=controller_a_key, -+ controller_b_key=controller_b_key, -+ ) -+ peer.sendto(authenticated_icmp_request(0, wrong_target), qemu) -+ bad_mac = bytearray( -+ control_frame( -+ node_id=args.node_id, -+ nonce=control_nonce, -+ command=phase_command(), -+ controller_a_key=controller_a_key, -+ controller_b_key=controller_b_key, -+ ) -+ ) -+ bad_mac[-1] ^= 0x01 -+ peer.sendto(authenticated_icmp_request(0, bytes(bad_mac)), qemu) -+ if args.prior_nonce_probe: -+ prior_nonce = control_frame( -+ node_id=args.node_id, -+ nonce=args.prior_nonce_probe, -+ command=phase_command(), -+ controller_a_key=controller_a_key, -+ controller_b_key=controller_b_key, -+ ) -+ peer.sendto(authenticated_icmp_request(0, prior_nonce), qemu) -+ rejection_probes_sent = True -+ rejection_probes_sent_at = time.monotonic() -+ continue -+ if args.exercise_auth_rejections and not valid_control_sent: -+ if time.monotonic() - rejection_probes_sent_at < 0.6: -+ continue -+ peer.sendto(request(reply_count + 1), qemu) -+ valid_control_sent = True -+ login_sent = True -+ continue -+ if replay_probe_pending: -+ if time.monotonic() - replay_probe_sent_at < 0.6: -+ continue -+ replay_probe_pending = False -+ replay_probe_done = True -+ peer.sendto(request(reply_count + 1), qemu) -+ login_sent = True -+ continue - peer.sendto( -- icmp_request(reply_count + 1, phase_magic()), -+ request(reply_count + 1), - qemu, - ) - login_sent = True -@@ -148,13 +401,31 @@ def main() -> None: - arp_verified = True - continue - if frame[12:14] == b"\x08\x00": -+ if args.exercise_auth_rejections and ( -+ not valid_control_sent or replay_probe_pending -+ ): -+ raise SystemExit("native runtime replied to a rejected control probe") - magic = phase_magic() -- validate_reply(frame, magic) -+ if args.authenticated_control: -+ assert last_control_frame is not None -+ validate_authenticated_reply(frame, last_control_frame) -+ control_nonce += 1 -+ else: -+ validate_reply(frame, magic) - reply_count += 1 - if command_phase == "login": - login_reply_count += 1 -+ if args.exercise_auth_rejections and not replay_probe_done: -+ assert last_control_frame is not None -+ peer.sendto( -+ authenticated_icmp_request(reply_count, last_control_frame), -+ qemu, -+ ) -+ replay_probe_pending = True -+ replay_probe_sent_at = time.monotonic() -+ continue - if command_phase == "login" and reply_count < 3: -- peer.sendto(icmp_request(reply_count + 1, LOGIN_MAGIC), qemu) -+ peer.sendto(request(reply_count + 1), qemu) - continue - if command_phase == "login": - if args.login_only: -@@ -164,26 +435,23 @@ def main() -> None: - ) - return - command_phase = "commit" -- peer.sendto(icmp_request(4, COMMIT_MAGIC), qemu) -+ peer.sendto(request(4), qemu) - continue - if command_phase == "commit": - command_phase = "branch" -- peer.sendto(icmp_request(5, BRANCH_MAGIC), qemu) -+ peer.sendto(request(5), qemu) - continue - if command_phase == "branch" and args.resident: - command_phase = "resident_login" -- peer.sendto(icmp_request(6, LOGIN_MAGIC), qemu) -+ peer.sendto(request(6), qemu) - continue - if command_phase == "resident_login": - resident_login_reply_count += 1 - if resident_login_reply_count < 10: -- peer.sendto( -- icmp_request(6 + resident_login_reply_count, LOGIN_MAGIC), -- qemu, -- ) -+ peer.sendto(request(6 + resident_login_reply_count), qemu) - continue - command_phase = "recovery" -- peer.sendto(icmp_request(16, RECOVERY_MAGIC), qemu) -+ peer.sendto(request(16), qemu) - continue - if command_phase == "recovery": - recovery_reply_verified = True -diff --git a/guanghu-os/scripts/render-native-recovery-beacon.sh b/guanghu-os/scripts/render-native-recovery-beacon.sh -index d782f0c..47c29a4 100755 ---- a/guanghu-os/scripts/render-native-recovery-beacon.sh -+++ b/guanghu-os/scripts/render-native-recovery-beacon.sh -@@ -8,12 +8,18 @@ set -euo pipefail - - mkdir -p "$1" - output_root=$(cd "$1" && pwd) -+recovery_menu_id=${GHOS_RECOVERY_MENU_ID:-gnulinux-simple-9842d3d6-a839-4127-bda7-f19137effe71} -+[[ ${recovery_menu_id} =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$ ]] || { -+ echo "invalid GHOS recovery menu identifier" >&2 -+ exit 65 -+} - --python3 - "${output_root}" <<'PY' -+python3 - "${output_root}" "${recovery_menu_id}" <<'PY' - import pathlib - import sys - - output = pathlib.Path(sys.argv[1]) -+recovery_menu_id = sys.argv[2] - header = ( - b"# GRUB Environment Block\n" - b"# WARNING: Do not edit this file by tools other than grub-editenv!!!\n" -@@ -30,19 +36,21 @@ write_environment( - b"guanghu_recovery=ubuntu\n", - ) - write_environment("guanghu-recovery-clear.env") --PY - --install -m 0755 /dev/stdin "${output_root}/08_guanghu_native_recovery" <<'EOF' --#!/bin/sh -+grub_script = f'''#!/bin/sh - exec tail -n +3 $0 - insmod loadenv - set guanghu_recovery= - if load_env --file '(hd0)68+2' guanghu_recovery; then -- if [ "${guanghu_recovery}" = "ubuntu" ]; then -- set default="gnulinux-simple-9842d3d6-a839-4127-bda7-f19137effe71" -+ if [ "${{guanghu_recovery}}" = "ubuntu" ]; then -+ set default="{recovery_menu_id}" - fi - fi --EOF -+''' -+grub_path = output / "08_guanghu_native_recovery" -+grub_path.write_text(grub_script) -+grub_path.chmod(0o755) -+PY - - sha256sum \ - "${output_root}/guanghu-recovery-active.env" \ -diff --git a/guanghu-os/scripts/test-native-ab-signed-installer-contract.sh b/guanghu-os/scripts/test-native-ab-signed-installer-contract.sh -new file mode 100755 -index 0000000..27df457 ---- /dev/null -+++ b/guanghu-os/scripts/test-native-ab-signed-installer-contract.sh -@@ -0,0 +1,25 @@ -+#!/usr/bin/env bash -+set -euo pipefail -+ -+source_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) -+installer="${source_root}/scripts/install-native-ab-signed.sh" -+ -+bash -n "${installer}" -+grep -Fq 'verify-signed-layout-plan' "${installer}" -+grep -Fq 'PASS_100_SIGNED_LAYOUT_PLAN' "${installer}" -+grep -Fq 'verified_controller_count' "${installer}" -+grep -Fq 'recovery_evidence_sha256' "${installer}" -+grep -Fq 'cmp -s "${work}/slot-a.before" "${work}/slot-a.commit"' "${installer}" -+grep -Fq 'cmp -s "${work}/slot-b.before" "${work}/slot-b.commit"' "${installer}" -+grep -Fq 'target_grub_changed: false' "${installer}" -+grep -Fq 'target_rebooted: false' "${installer}" -+grep -Fq 'native_boot_armed: false' "${installer}" -+grep -Fq 'VERIFIED_WRITTEN_NOT_BOOTABLE' "${installer}" -+grep -Fq '(cd "${recovery_root}" && sha256sum ./*) >"${work}/SHA256SUMS"' "${installer}" -+ -+if grep -Eq '(^|[[:space:]])(mkfs|sfdisk[[:space:]]+[^-]|parted|grub-install|update-grub|reboot|shutdown)([[:space:]]|$)' "${installer}"; then -+ echo "installer must not format, repartition, change GRUB, or reboot" >&2 -+ exit 1 -+fi -+ -+echo "native A/B signed installer contract: PASS" -diff --git a/guanghu-os/scripts/test-native-physical-candidate.sh b/guanghu-os/scripts/test-native-physical-candidate.sh -index 1467a67..c896b71 100755 ---- a/guanghu-os/scripts/test-native-physical-candidate.sh -+++ b/guanghu-os/scripts/test-native-physical-candidate.sh -@@ -17,29 +17,65 @@ disk_image=${test_root}/physical-layout.img - failure_disk_image=${test_root}/physical-layout-failure.img - peer_receipt=${test_root}/native-net-peer.hldp - peer_log=${test_root}/native-net-peer.log -+guest_mac=${GHOS_TEST_GUEST_MAC:-52:54:00:26:71:98} -+guest_ip=${GHOS_TEST_GUEST_IP:-10.0.0.7} -+peer_ip=${GHOS_TEST_PEER_IP:-10.0.0.1} -+login_client_ip=${GHOS_TEST_LOGIN_CLIENT_IP:-10.0.0.2} -+node_id=${GHOS_TEST_NODE_ID:-BS-SH-005} -+candidate_lba=${GHOS_TEST_CANDIDATE_LBA:-34} -+control_auth=${GHOS_TEST_CONTROL_AUTH:-0} -+controller_a_key=${GHOS_TEST_CONTROLLER_A_KEY:-} -+controller_b_key=${GHOS_TEST_CONTROLLER_B_KEY:-} -+[[ ${node_id} =~ ^[A-Z0-9][A-Z0-9-]{0,63}$ ]] -+[[ ${candidate_lba} == 34 || ${candidate_lba} == 73 ]] -+[[ ${control_auth} == 0 || ${control_auth} == 1 ]] -+peer_args=( -+ --guest-mac "${guest_mac}" -+ --guest-ip "${guest_ip}" -+ --peer-ip "${peer_ip}" -+ --login-client-ip "${login_client_ip}" -+) -+if [[ ${control_auth} == 1 ]]; then -+ [[ ${controller_a_key} =~ ^[0-9a-fA-F]{32}$ ]] -+ [[ ${controller_b_key} =~ ^[0-9a-fA-F]{32}$ ]] -+ [[ ${controller_a_key} != "${controller_b_key}" ]] -+ peer_args+=( -+ --authenticated-control -+ --node-id "${node_id}" -+ --controller-a-key-hex "${controller_a_key}" -+ --controller-b-key-hex "${controller_b_key}" -+ --nonce-start 1 -+ --exercise-auth-rejections -+ ) -+fi -+shell_pid=${BASHPID:-$$} - peer_pid= - cleanup() { -+ status=$? - if [[ -n ${peer_pid} ]]; then - kill "${peer_pid}" 2>/dev/null || true - fi - rm -rf "${test_root}" -+ exit "${status}" - } - trap cleanup EXIT - - truncate -s 2M "${disk_image}" --nasm -f bin "${native_root}/physical-test-mbr.asm" \ -+nasm -f bin -dCANDIDATE_LBA="${candidate_lba}" \ -+ "${native_root}/physical-test-mbr.asm" \ - -o "${test_root}/physical-test-mbr.bin" - dd if="${test_root}/physical-test-mbr.bin" of="${disk_image}" \ - bs=512 seek=0 conv=notrunc status=none - dd if="${candidate}" of="${disk_image}" \ -- bs=512 seek=34 conv=notrunc status=none -+ bs=512 seek="${candidate_lba}" conv=notrunc status=none - --peer_port=$((22000 + BASHPID % 10000)) -+peer_port=$((22000 + shell_pid % 10000)) - qemu_port=$((peer_port + 1)) - python3 "${source_root}/scripts/qemu-native-net-peer.py" \ - --listen-port "${peer_port}" \ - --qemu-port "${qemu_port}" \ -- --receipt "${peer_receipt}" >"${peer_log}" 2>&1 & -+ --receipt "${peer_receipt}" \ -+ "${peer_args[@]}" >"${peer_log}" 2>&1 & - peer_pid=$! - set +e - timeout 20 qemu-system-x86_64 \ -@@ -48,7 +84,7 @@ timeout 20 qemu-system-x86_64 \ - -drive "if=none,id=ghboot,format=raw,file=${disk_image}" \ - -device virtio-blk-pci,drive=ghboot,disable-modern=on,bootindex=0 \ - -netdev "dgram,id=ghnet,local.type=inet,local.host=127.0.0.1,local.port=${qemu_port},remote.type=inet,remote.host=127.0.0.1,remote.port=${peer_port}" \ -- -device virtio-net-pci,netdev=ghnet,disable-modern=on,mac=52:54:00:26:71:98 \ -+ -device virtio-net-pci,netdev=ghnet,disable-modern=on,mac="${guest_mac}" \ - -display none \ - -monitor none \ - -serial "file:${serial_log}" \ -@@ -64,6 +100,14 @@ grep -q '^icmp_login_reply_verified: true$' "${peer_receipt}" - grep -q '^icmp_login_reply_count: 3$' "${peer_receipt}" - grep -q '^code_commit_reply_verified: true$' "${peer_receipt}" - grep -q '^branch_move_reply_verified: true$' "${peer_receipt}" -+if [[ ${control_auth} == 1 ]]; then -+ grep -q '^authenticated_control: true$' "${peer_receipt}" -+ grep -q '^last_accepted_nonce: 5$' "${peer_receipt}" -+ grep -q '^legacy_control_rejected: true$' "${peer_receipt}" -+ grep -q '^wrong_target_rejected: true$' "${peer_receipt}" -+ grep -q '^bad_dual_mac_rejected: true$' "${peer_receipt}" -+ grep -q '^replayed_nonce_rejected: true$' "${peer_receipt}" -+fi - for evidence in \ - GHOS_BOOT_STAGE0=BIOS \ - GHOS_NATIVE_KERNEL_ENTERED=true \ -@@ -88,15 +132,21 @@ for evidence in \ - GHOS_DISK_PROOF_OBSERVED_AFTER_RESET=LBA63; do - grep -q "^${evidence}" "${serial_log}" - done --python3 - "${disk_image}" <<'PY' -+python3 - "${disk_image}" "${guest_mac}" "${login_client_ip}" "${control_auth}" "${node_id}" <<'PY' -+import hashlib - import pathlib -+import socket - import sys - disk = pathlib.Path(sys.argv[1]).read_bytes() -+guest_mac = bytes.fromhex(sys.argv[2].replace(":", "")) -+login_client_ip = socket.inet_aton(sys.argv[3]) -+control_auth = sys.argv[4] == "1" -+node_id = sys.argv[5] - proof = disk[63 * 512:64 * 512] - assert proof[0] == 0xA7 - assert proof[1:].startswith(b"GHOS_NATIVE_LONG64_DISK_PROOF\x00") - assert proof[32:36] == bytes([1, 1, 1, 1]) --assert proof[36:42] == bytes.fromhex("525400267198") -+assert proof[36:42] == guest_mac - assert proof[42] == 0x7F - assert proof[43] == 0x00 - assert int.from_bytes(proof[44:46], "little") > 0 -@@ -105,12 +155,14 @@ assert int.from_bytes(proof[48:50], "little") > 0 - assert proof[54:60] != bytes(6) - assert proof[60:62] == bytes([1, 1]) - assert proof[62:64] == bytes([1, 1]) --assert proof[64:68] == bytes([10, 0, 0, 2]) -+assert proof[64:68] == login_client_ip - assert proof[72:88] == b"HLDP-GHOS-LOGIN!" - assert proof[88:90] == bytes([1, 1]) - assert proof[90:93] == bytes([3, 1, 1]) - assert proof[93:99] == bytes([1, 1, 1, 1, 1, 1]) - assert proof[102:105] == bytes([1, 1, 1]) -+if control_auth: -+ assert proof[105:111] == bytes([1, 1, 1, 1, 1, 1]) - world_store = disk[64 * 512:65 * 512] - assert world_store.startswith(b"GHOS_HLDP_WORLD_STORE_V1\n") - for identity in ( -@@ -149,13 +201,26 @@ assert gestational_root.startswith(b"GHOS_GHCIP_ROOT_V1\n") - assert b"GHCIP_REGISTRY_STATE=EMPTY\n" in gestational_root - assert b"GHCIP_REVIEW_STATE=NOT_STARTED\n" in gestational_root - assert b"GHCIP_PERSONA_STATE=NOT_BORN\n" in gestational_root -+if control_auth: -+ control_state = disk[72 * 512:73 * 512] -+ target = int.from_bytes( -+ hashlib.sha256(node_id.encode("ascii")).digest()[:8], "little" -+ ) -+ nonce = 5 -+ assert control_state[:8] == b"GHCTRLS2" -+ assert int.from_bytes(control_state[8:16], "little") == target -+ assert int.from_bytes(control_state[16:24], "little") == nonce -+ assert int.from_bytes(control_state[24:32], "little") == ( -+ nonce ^ 0xFFFFFFFFFFFFFFFF -+ ) -+ assert control_state[32:] == bytes(480) - PY - - truncate -s 2M "${failure_disk_image}" - dd if="${test_root}/physical-test-mbr.bin" of="${failure_disk_image}" \ - bs=512 seek=0 conv=notrunc status=none - dd if="${candidate}" of="${failure_disk_image}" \ -- bs=512 seek=34 conv=notrunc status=none -+ bs=512 seek="${candidate_lba}" conv=notrunc status=none - - set +e - timeout 20 qemu-system-x86_64 \ -@@ -192,7 +257,8 @@ observed_at=$(date --iso-8601=seconds) - image_sha=$(sha256sum "${candidate}" | awk '{print $1}') - cat >"${receipt}" <>"${receipt}.serial.log" - cat "${failure_serial_log}" >>"${receipt}.failure.serial.log" -diff --git a/guanghu-os/scripts/test-native-recovery-beacon-contract.sh b/guanghu-os/scripts/test-native-recovery-beacon-contract.sh -index cb381c9..a62fe3b 100755 ---- a/guanghu-os/scripts/test-native-recovery-beacon-contract.sh -+++ b/guanghu-os/scripts/test-native-recovery-beacon-contract.sh -@@ -7,6 +7,18 @@ trap 'rm -rf "${test_root}"' EXIT - - "${source_root}/scripts/render-native-recovery-beacon.sh" "${test_root}" - -+enterprise_root=${test_root}/enterprise -+enterprise_menu_id=gnulinux-simple-7bccaefa-b0a9-4f6f-bd32-22dde0066c0b -+GHOS_RECOVERY_MENU_ID=${enterprise_menu_id} \ -+ "${source_root}/scripts/render-native-recovery-beacon.sh" "${enterprise_root}" -+grep -Fq "set default=\"${enterprise_menu_id}\"" \ -+ "${enterprise_root}/08_guanghu_native_recovery" -+if GHOS_RECOVERY_MENU_ID='invalid id; reboot' \ -+ "${source_root}/scripts/render-native-recovery-beacon.sh" "${test_root}/invalid"; then -+ echo "invalid recovery menu identifiers must fail closed" >&2 -+ exit 1 -+fi -+ - active=${test_root}/guanghu-recovery-active.env - clear=${test_root}/guanghu-recovery-clear.env - grub=${test_root}/08_guanghu_native_recovery -diff --git a/guanghu-os/scripts/test-native-resident-candidate.sh b/guanghu-os/scripts/test-native-resident-candidate.sh -index 2c52fce..cab4bb2 100755 ---- a/guanghu-os/scripts/test-native-resident-candidate.sh -+++ b/guanghu-os/scripts/test-native-resident-candidate.sh -@@ -15,6 +15,7 @@ serial_log=${test_root}/serial.log - serial_log_second=${test_root}/serial-second.log - disk_image=${test_root}/resident-layout.img - corrupt_disk_image=${test_root}/resident-layout-corrupt.img -+control_state_corrupt_disk_image=${test_root}/resident-layout-control-state-corrupt.img - peer_receipt=${test_root}/native-net-peer.hldp - peer_log=${test_root}/native-net-peer.log - peer_receipt_second=${test_root}/native-net-peer-second.hldp -@@ -22,8 +23,37 @@ peer_log_second=${test_root}/native-net-peer-second.log - peer_receipt_corrupt=${test_root}/native-net-peer-corrupt.hldp - peer_log_corrupt=${test_root}/native-net-peer-corrupt.log - serial_log_corrupt=${test_root}/serial-corrupt.log -+serial_log_control_state_corrupt=${test_root}/serial-control-state-corrupt.log -+guest_mac=${GHOS_TEST_GUEST_MAC:-52:54:00:26:71:98} -+guest_ip=${GHOS_TEST_GUEST_IP:-10.0.0.7} -+peer_ip=${GHOS_TEST_PEER_IP:-10.0.0.1} -+login_client_ip=${GHOS_TEST_LOGIN_CLIENT_IP:-10.0.0.2} -+ubuntu_menu_id=${GHOS_TEST_UBUNTU_MENU_ID:-gnulinux-simple-9842d3d6-a839-4127-bda7-f19137effe71} -+node_id=${GHOS_TEST_NODE_ID:-BS-SH-005} -+[[ ${node_id} =~ ^[A-Z0-9][A-Z0-9-]{0,63}$ ]] -+control_auth=${GHOS_TEST_CONTROL_AUTH:-0} -+controller_a_key=${GHOS_TEST_CONTROLLER_A_KEY:-} -+controller_b_key=${GHOS_TEST_CONTROLLER_B_KEY:-} -+peer_auth_args=() -+if [[ ${control_auth} == 1 ]]; then -+ [[ ${controller_a_key} =~ ^[0-9a-fA-F]{32}$ ]] -+ [[ ${controller_b_key} =~ ^[0-9a-fA-F]{32}$ ]] -+ [[ ${controller_a_key} != "${controller_b_key}" ]] -+ peer_auth_args=( -+ --authenticated-control -+ --node-id "${node_id}" -+ --controller-a-key-hex "${controller_a_key}" -+ --controller-b-key-hex "${controller_b_key}" -+ --exercise-auth-rejections -+ ) -+elif [[ ${control_auth} != 0 ]]; then -+ echo "GHOS_TEST_CONTROL_AUTH must be 0 or 1" >&2 -+ exit 65 -+fi -+shell_pid=${BASHPID:-$$} - peer_pid= - cleanup() { -+ status=$? - if [[ -n ${peer_pid} ]]; then - kill "${peer_pid}" 2>/dev/null || true - fi -@@ -32,6 +62,7 @@ cleanup() { - else - echo "GHOS_TEST_ROOT=${test_root}" >&2 - fi -+ exit "${status}" - } - trap cleanup EXIT - -@@ -43,12 +74,19 @@ dd if="${test_root}/physical-test-mbr.bin" of="${disk_image}" \ - dd if="${candidate}" of="${disk_image}" \ - bs=512 seek=34 conv=notrunc status=none - --peer_port=$((24000 + BASHPID % 10000)) -+peer_port=$((24000 + shell_pid % 10000)) - qemu_port=$((peer_port + 1)) - python3 "${source_root}/scripts/qemu-native-net-peer.py" \ - --listen-port "${peer_port}" \ - --qemu-port "${qemu_port}" \ - --receipt "${peer_receipt}" \ -+ --guest-mac "${guest_mac}" \ -+ --guest-ip "${guest_ip}" \ -+ --peer-ip "${peer_ip}" \ -+ --login-client-ip "${login_client_ip}" \ -+ "${peer_auth_args[@]+"${peer_auth_args[@]}"}" \ -+ --nonce-start 1 \ -+ --prior-nonce-probe 0 \ - --resident >"${peer_log}" 2>&1 & - peer_pid=$! - set +e -@@ -58,7 +96,7 @@ timeout 30 qemu-system-x86_64 \ - -drive "if=none,id=ghboot,format=raw,file=${disk_image}" \ - -device virtio-blk-pci,drive=ghboot,disable-modern=on,bootindex=0 \ - -netdev "dgram,id=ghnet,local.type=inet,local.host=127.0.0.1,local.port=${qemu_port},remote.type=inet,remote.host=127.0.0.1,remote.port=${peer_port}" \ -- -device virtio-net-pci,netdev=ghnet,disable-modern=on,mac=52:54:00:26:71:98 \ -+ -device virtio-net-pci,netdev=ghnet,disable-modern=on,mac="${guest_mac}" \ - -display none \ - -monitor none \ - -serial "file:${serial_log}" \ -@@ -79,11 +117,14 @@ grep -q '^GHOS_NATIVE_RECOVERY_BEACON=WRITE_READ_VERIFIED' "${serial_log}" - grep -q '^GHOS_GHCIP_INDEX=INITIALIZED_WRITE_READ_VERIFIED' "${serial_log}" - grep -q '^GHOS_DISK_PROOF_OBSERVED_AFTER_RESET=LBA63' "${serial_log}" - --python3 - "${disk_image}" <<'PY' -+python3 - "${disk_image}" "${control_auth}" "${node_id}" <<'PY' -+import hashlib - import pathlib - import sys - - path = pathlib.Path(sys.argv[1]) -+control_auth = sys.argv[2] == "1" -+node_id = sys.argv[3] - with path.open("rb") as disk: - def sector(lba: int, count: int = 1) -> bytes: - disk.seek(lba * 512) -@@ -97,6 +138,8 @@ with path.open("rb") as disk: - assert proof[93:99] == bytes([1, 1, 1, 1, 1, 1]) - assert proof[99:102] == bytes([1, 1, 1]) - assert proof[102:105] == bytes([1, 1, 1]) -+ if control_auth: -+ assert proof[105:111] == bytes([1, 1, 1, 1, 1, 1]) - - world_store = sector(64) - assert world_store.startswith(b"GHOS_HLDP_WORLD_STORE_V1\n") -@@ -130,6 +173,19 @@ with path.open("rb") as disk: - assert b"GHCIP_HISTORICAL_TIME_WATERMARK=NONE\n" in gestational_root - assert b"GHCIP_PERSONA_STATE=NOT_BORN\n" in gestational_root - assert b"GHCIP_LAST_VERIFIED_BATCH=NONE\n" in gestational_root -+ if control_auth: -+ control_state = sector(72) -+ target = int.from_bytes( -+ hashlib.sha256(node_id.encode("ascii")).digest()[:8], "little" -+ ) -+ nonce = 16 -+ assert control_state[:8] == b"GHCTRLS2" -+ assert int.from_bytes(control_state[8:16], "little") == target -+ assert int.from_bytes(control_state[16:24], "little") == nonce -+ assert int.from_bytes(control_state[24:32], "little") == ( -+ nonce ^ 0xFFFFFFFFFFFFFFFF -+ ) -+ assert control_state[32:] == bytes(480) - PY - - index_sha_before=$(dd if="${disk_image}" bs=512 skip=70 count=2 status=none | -@@ -142,6 +198,13 @@ python3 "${source_root}/scripts/qemu-native-net-peer.py" \ - --listen-port "${peer_port_second}" \ - --qemu-port "${qemu_port_second}" \ - --receipt "${peer_receipt_second}" \ -+ --guest-mac "${guest_mac}" \ -+ --guest-ip "${guest_ip}" \ -+ --peer-ip "${peer_ip}" \ -+ --login-client-ip "${login_client_ip}" \ -+ "${peer_auth_args[@]+"${peer_auth_args[@]}"}" \ -+ --nonce-start 17 \ -+ --prior-nonce-probe 16 \ - --resident >"${peer_log_second}" 2>&1 & - peer_pid=$! - set +e -@@ -151,7 +214,7 @@ timeout 30 qemu-system-x86_64 \ - -drive "if=none,id=ghboot,format=raw,file=${disk_image}" \ - -device virtio-blk-pci,drive=ghboot,disable-modern=on,bootindex=0 \ - -netdev "dgram,id=ghnet,local.type=inet,local.host=127.0.0.1,local.port=${qemu_port_second},remote.type=inet,remote.host=127.0.0.1,remote.port=${peer_port_second}" \ -- -device virtio-net-pci,netdev=ghnet,disable-modern=on,mac=52:54:00:26:71:98 \ -+ -device virtio-net-pci,netdev=ghnet,disable-modern=on,mac="${guest_mac}" \ - -display none \ - -monitor none \ - -serial "file:${serial_log_second}" \ -@@ -175,15 +238,31 @@ fi - index_sha_after=$(dd if="${disk_image}" bs=512 skip=70 count=2 status=none | - sha256sum | awk '{print $1}') - [[ ${index_sha_before} == "${index_sha_after}" ]] --python3 - "${disk_image}" <<'PY' -+python3 - "${disk_image}" "${control_auth}" "${node_id}" <<'PY' -+import hashlib - import pathlib - import sys - - with pathlib.Path(sys.argv[1]).open("rb") as disk: - disk.seek(63 * 512) - proof = disk.read(512) -+ disk.seek(72 * 512) -+ control_state = disk.read(512) - assert proof[0] == 0xA7 - assert proof[102:105] == bytes([0, 1, 1]) -+if sys.argv[2] == "1": -+ assert proof[105:111] == bytes([1, 1, 1, 1, 1, 1]) -+ target = int.from_bytes( -+ hashlib.sha256(sys.argv[3].encode("ascii")).digest()[:8], "little" -+ ) -+ nonce = 32 -+ assert control_state[:8] == b"GHCTRLS2" -+ assert int.from_bytes(control_state[8:16], "little") == target -+ assert int.from_bytes(control_state[16:24], "little") == nonce -+ assert int.from_bytes(control_state[24:32], "little") == ( -+ nonce ^ 0xFFFFFFFFFFFFFFFF -+ ) -+ assert control_state[32:] == bytes(480) - PY - - cp "${disk_image}" "${corrupt_disk_image}" -@@ -199,6 +278,13 @@ python3 "${source_root}/scripts/qemu-native-net-peer.py" \ - --listen-port "${peer_port_corrupt}" \ - --qemu-port "${qemu_port_corrupt}" \ - --receipt "${peer_receipt_corrupt}" \ -+ --guest-mac "${guest_mac}" \ -+ --guest-ip "${guest_ip}" \ -+ --peer-ip "${peer_ip}" \ -+ --login-client-ip "${login_client_ip}" \ -+ "${peer_auth_args[@]+"${peer_auth_args[@]}"}" \ -+ --nonce-start 33 \ -+ --prior-nonce-probe 32 \ - --login-only >"${peer_log_corrupt}" 2>&1 & - peer_pid=$! - set +e -@@ -208,7 +294,7 @@ timeout 30 qemu-system-x86_64 \ - -drive "if=none,id=ghboot,format=raw,file=${corrupt_disk_image}" \ - -device virtio-blk-pci,drive=ghboot,disable-modern=on,bootindex=0 \ - -netdev "dgram,id=ghnet,local.type=inet,local.host=127.0.0.1,local.port=${qemu_port_corrupt},remote.type=inet,remote.host=127.0.0.1,remote.port=${peer_port_corrupt}" \ -- -device virtio-net-pci,netdev=ghnet,disable-modern=on,mac=52:54:00:26:71:98 \ -+ -device virtio-net-pci,netdev=ghnet,disable-modern=on,mac="${guest_mac}" \ - -display none \ - -monitor none \ - -serial "file:${serial_log_corrupt}" \ -@@ -237,11 +323,44 @@ assert proof[43] == 0x6C - assert proof[102:105] == bytes([0, 0, 0]) - PY - -+control_state_unknown_nonzero_failed_closed=false -+if [[ ${control_auth} == 1 ]]; then -+ cp "${disk_image}" "${control_state_corrupt_disk_image}" -+ dd if=/dev/zero of="${control_state_corrupt_disk_image}" bs=512 seek=63 count=1 \ -+ conv=notrunc status=none -+ printf '\x58' | dd of="${control_state_corrupt_disk_image}" bs=1 \ -+ seek=$((72 * 512)) count=1 conv=notrunc status=none -+ control_state_sha_before=$(dd if="${control_state_corrupt_disk_image}" \ -+ bs=512 skip=72 count=1 status=none | sha256sum | awk '{print $1}') -+ set +e -+ timeout 20 qemu-system-x86_64 \ -+ -machine pc,accel=tcg \ -+ -m 64M \ -+ -drive "if=none,id=ghcontrolfail,format=raw,file=${control_state_corrupt_disk_image}" \ -+ -device virtio-blk-pci,drive=ghcontrolfail,disable-modern=on,bootindex=0 \ -+ -netdev user,id=ghcontrolnet \ -+ -device virtio-net-pci,netdev=ghcontrolnet,disable-modern=on,mac="${guest_mac}" \ -+ -display none \ -+ -monitor none \ -+ -serial "file:${serial_log_control_state_corrupt}" \ -+ -device isa-debug-exit,iobase=0xf4,iosize=0x04 -+ control_state_qemu_status=$? -+ set -e -+ [[ ${control_state_qemu_status} -eq 33 ]] -+ grep -q '^GHOS_BOOT_ERROR=NATIVE_CONTROL_STATE' \ -+ "${serial_log_control_state_corrupt}" -+ control_state_sha_after=$(dd if="${control_state_corrupt_disk_image}" \ -+ bs=512 skip=72 count=1 status=none | sha256sum | awk '{print $1}') -+ [[ ${control_state_sha_before} == "${control_state_sha_after}" ]] -+ control_state_unknown_nonzero_failed_closed=true -+fi -+ - observed_at=$(date --iso-8601=seconds) - image_sha=$(sha256sum "${candidate}" | awk '{print $1}') - cat >"${receipt}" <>"${receipt}.serial.log" - cat "${serial_log_second}" >>"${receipt}.second-boot.serial.log" - cat "${serial_log_corrupt}" >>"${receipt}.corrupt-index.serial.log" -+if [[ ${control_auth} == 1 ]]; then -+ cat "${serial_log_control_state_corrupt}" \ -+ >>"${receipt}.corrupt-control-state.serial.log" -+fi -diff --git a/guanghu-os/scripts/test-qemu-native-control-auth.py b/guanghu-os/scripts/test-qemu-native-control-auth.py -new file mode 100644 -index 0000000..5c59141 ---- /dev/null -+++ b/guanghu-os/scripts/test-qemu-native-control-auth.py -@@ -0,0 +1,66 @@ -+#!/usr/bin/env python3 -+import importlib.util -+import pathlib -+import struct -+import unittest -+ -+ -+MODULE_PATH = pathlib.Path(__file__).with_name("qemu-native-net-peer.py") -+SPEC = importlib.util.spec_from_file_location("qemu_native_net_peer", MODULE_PATH) -+assert SPEC and SPEC.loader -+PEER = importlib.util.module_from_spec(SPEC) -+SPEC.loader.exec_module(PEER) -+ -+ -+class NativeControlAuthenticationTests(unittest.TestCase): -+ def test_siphash_matches_the_reference_32_byte_vector(self) -> None: -+ key = bytes(range(16)) -+ message = bytes(range(32)) -+ self.assertEqual(PEER.siphash24(key, message), 0x7127512F72F27CCE) -+ -+ def test_frame_binds_target_nonce_command_and_two_controllers(self) -> None: -+ frame = PEER.control_frame( -+ node_id="GH-CVM-MAIN-PROD-01", -+ nonce=41, -+ command=3, -+ controller_a_key=bytes.fromhex("00112233445566778899aabbccddeeff"), -+ controller_b_key=bytes.fromhex("ffeeddccbbaa99887766554433221100"), -+ ) -+ self.assertEqual(len(frame), 48) -+ self.assertEqual(frame[:8], b"GHCTL2\0\0") -+ self.assertEqual(struct.unpack_from(" None: -+ key_a = bytes.fromhex("00112233445566778899aabbccddeeff") -+ key_b = bytes.fromhex("ffeeddccbbaa99887766554433221100") -+ original = PEER.control_frame( -+ node_id="GH-CVM-MAIN-PROD-01", -+ nonce=1, -+ command=0, -+ controller_a_key=key_a, -+ controller_b_key=key_b, -+ ) -+ for changed in [ -+ PEER.control_frame(node_id="OTHER-NODE", nonce=1, command=0, controller_a_key=key_a, controller_b_key=key_b), -+ PEER.control_frame(node_id="GH-CVM-MAIN-PROD-01", nonce=2, command=0, controller_a_key=key_a, controller_b_key=key_b), -+ PEER.control_frame(node_id="GH-CVM-MAIN-PROD-01", nonce=1, command=1, controller_a_key=key_a, controller_b_key=key_b), -+ PEER.control_frame(node_id="GH-CVM-MAIN-PROD-01", nonce=1, command=0, controller_a_key=bytes(16), controller_b_key=key_b), -+ ]: -+ self.assertNotEqual(changed, original) -+ -+ def test_rejects_unregistered_frame_inputs(self) -> None: -+ key = bytes(16) -+ with self.assertRaises(ValueError): -+ PEER.control_frame(node_id="wrong_node", nonce=1, command=0, controller_a_key=key, controller_b_key=key) -+ with self.assertRaises(ValueError): -+ PEER.control_frame(node_id="GH-CVM-MAIN-PROD-01", nonce=0, command=0, controller_a_key=key, controller_b_key=key) -+ with self.assertRaises(ValueError): -+ PEER.control_frame(node_id="GH-CVM-MAIN-PROD-01", nonce=1, command=4, controller_a_key=key, controller_b_key=key) -+ -+ -+if __name__ == "__main__": -+ unittest.main() -diff --git a/guanghu-os/world-seed/WORLD-MANIFEST.hldp b/guanghu-os/world-seed/WORLD-MANIFEST.hldp -index 94668fd..8c099ba 100644 ---- a/guanghu-os/world-seed/WORLD-MANIFEST.hldp -+++ b/guanghu-os/world-seed/WORLD-MANIFEST.hldp -@@ -78,6 +78,9 @@ native_layout: - branch_receipt_lba: 67 - recovery_beacon_lba_start: 68 - gestational_index_lba_start: 70 -+ control_state_lba: 72 -+ alternate_kernel_lba_start: 73 -+ alternate_kernel_sector_count: 29 - first_partition_lba: 2048 - gestational_continuity: - id: GLS-0845 -diff --git a/guanghu-os/world-seed/world/services/native-storage/DISK-LAYOUT.hldp b/guanghu-os/world-seed/world/services/native-storage/DISK-LAYOUT.hldp -index a1702f8..820f900 100644 ---- a/guanghu-os/world-seed/world/services/native-storage/DISK-LAYOUT.hldp -+++ b/guanghu-os/world-seed/world/services/native-storage/DISK-LAYOUT.hldp -@@ -24,6 +24,10 @@ regions: - recovery_beacon_sector_count: 2 - gestational_index_lba_start: 70 - gestational_index_sector_count: 2 -+ control_state_lba: 72 -+ control_state_sector_count: 1 -+ alternate_kernel_lba_start: 73 -+ alternate_kernel_sector_count: 29 - first_partition_lba: 2048 - ownership: - pre_partition_region: GUANGHU_OS_NATIVE --- -2.50.1 (Apple Git-155) - diff --git a/deployment/GH-CVM-MAIN-PROD-01/0001-feat-guanghu-os-archive-enterprise-native-recovery-l.patch.gz b/deployment/GH-CVM-MAIN-PROD-01/0001-feat-guanghu-os-archive-enterprise-native-recovery-l.patch.gz new file mode 100644 index 0000000..c851a70 Binary files /dev/null and b/deployment/GH-CVM-MAIN-PROD-01/0001-feat-guanghu-os-archive-enterprise-native-recovery-l.patch.gz differ diff --git a/deployment/GH-CVM-MAIN-PROD-01/README.md b/deployment/GH-CVM-MAIN-PROD-01/README.md index 0e97bcd..e73740d 100644 --- a/deployment/GH-CVM-MAIN-PROD-01/README.md +++ b/deployment/GH-CVM-MAIN-PROD-01/README.md @@ -66,8 +66,11 @@ 源码归档: -- `0001-feat-guanghu-os-archive-enterprise-native-recovery-l.patch` -- 补丁 SHA-256:`f3b4204fc1251348da434bdea585bd0424be7ed8bed6cc8830d47bca02cbcd6e` +- `0001-feat-guanghu-os-archive-enterprise-native-recovery-l.patch.gz` +- 压缩补丁 SHA-256:`2096132af67fe835f52ef6441413dee01b4746d60e8de81d561f58cbf8d03039` + +解压后补丁 SHA-256: +`f3b4204fc1251348da434bdea585bd0424be7ed8bed6cc8830d47bca02cbcd6e`。 ## 四、验证结果