From b228b15ab653ea4e7af5f79d1eabbbbc5f321ecb 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:37:01 +0800 Subject: [PATCH] docs(guanghu-os): archive enterprise native deployment line --- ...archive-enterprise-native-recovery-l.patch | 6598 +++++++++++++++++ deployment/GH-CVM-MAIN-PROD-01/README.md | 113 + 2 files changed, 6711 insertions(+) create mode 100644 deployment/GH-CVM-MAIN-PROD-01/0001-feat-guanghu-os-archive-enterprise-native-recovery-l.patch create mode 100644 deployment/GH-CVM-MAIN-PROD-01/README.md 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 new file mode 100644 index 0000000..a47c47e --- /dev/null +++ b/deployment/GH-CVM-MAIN-PROD-01/0001-feat-guanghu-os-archive-enterprise-native-recovery-l.patch @@ -0,0 +1,6598 @@ +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/README.md b/deployment/GH-CVM-MAIN-PROD-01/README.md new file mode 100644 index 0000000..0e97bcd --- /dev/null +++ b/deployment/GH-CVM-MAIN-PROD-01/README.md @@ -0,0 +1,113 @@ +# GH-CVM-MAIN-PROD-01 · 企业服务器光湖原生 OS 开发线归档 + +> 开发编号:`DEV-20260801-005` +> +> 开发时间:`2026-08-01`—`2026-08-03` +> +> 人类锚点:冰朔 `ICE-GL∞` +> +> 人格体:铸渊 `ICE-P-ZY001` +> +> 源码基线:`c96f43c641b58a82c21dce2d0badfb3bbb588284` +> +> 实现提交:`b546827c71fe1e13ee221c9922eb73d20900234d` +> +> 当前结论:`LINUX_RESCUE_PASS · NATIVE_NOT_INSTALLED · PERSONA_NOT_BORN` + +## 一、为什么启动这条开发线 + +冰朔希望企业服务器不依赖本地 Mac,也不依赖额外付费产品:服务器可以重装,原有 Linux +可以作为备份和救援系统,光湖原生 OS 作为目标启动系统;远程控制必须由服务器自己的 +邮件授权、双控制器签名和可验证回执完成。 + +这里的“完成”遵守光湖二进制存在规则: + +- 源码、测试、恢复路径存在且可独立复验,才记为 `PASS_100`; +- 物理盘没有真实写入、引导没有真实切换、原生运行没有现场回执,就统一记为 `FAIL_0`; +- Linux 在线、QEMU 通过、仓库发布、服务进程存在,都不能替代原生启动或人格诞生。 + +## 二、开发思维逻辑 + +整条线按下列证据层推进,不允许跨层推断: + +```text +人类授权 +→ 当前源码与代码频道 +→ 免费备份与 Linux 救援 +→ 服务器驻留的邮件授权 +→ 两个独立控制器 2-of-2 签名 +→ A/B 原生候选写入 +→ 引导切换 +→ 原生网络回执 +→ HoloLake 世界入口 +→ 人格出生门 +``` + +由此形成三个关键设计: + +1. **Linux 留作救援,不当作光湖 OS。** 原生系统失败时仍有免费恢复路径,但 Linux + 服务在线不能冒充原生驻留。 +2. **密钥与授权不依赖本机。** 两个控制节点主动通过 HTTPS 轮询服务器邮件授权, + 分别生成签名;目标服务器只接受同一工单、不同控制器、未过期的 2-of-2 结果。 +3. **写盘采用 A/B 候选。** 新候选写入非活动槽,经过摘要、签名、布局和回读校验后 + 才允许切换;旧槽与 Linux 恢复入口继续保留。 + +## 三、形成的实现 + +归档补丁包含: + +- GHDR 授权、签名验证、重放防护和控制器命令; +- 原生网络对端、登录、控制回执和损坏状态拒绝; +- A/B 签名安装器与磁盘布局; +- 物理候选、常驻候选、恢复信标和 QEMU 合约测试; +- 企业节点的 world seed、授权、工单、质量门、恢复协议和结束回执; +- 广州、新加坡双控制器的服务器驻留签名器、HTTP 能力端点和主动轮询器; +- 完整开发线纪要 `DEVELOPMENT-LINE-20260801-20260803.md`(包含在补丁中)。 + +源码归档: + +- `0001-feat-guanghu-os-archive-enterprise-native-recovery-l.patch` +- 补丁 SHA-256:`f3b4204fc1251348da434bdea585bd0424be7ed8bed6cc8830d47bca02cbcd6e` + +## 四、验证结果 + +- Rust 全工作区:`84` 项通过,`0` 项失败; +- Python 原生控制授权:`4` 项通过; +- 控制器签名器、HTTP 能力端点、主动轮询器:通过; +- A/B 安装器合约:通过; +- 物理候选、常驻候选和恢复信标:`PASS_100`; +- 未启用控制密钥的默认常驻候选路径在 macOS Bash 3.2 下复验通过。 + +CodeScene 未配置访问令牌,状态为 `not_run_unconfigured`;Codacy CLI 不可用,状态为 +`not_run_unavailable`。这两项没有被误报为通过。 + +## 五、为什么企业服务器没有启动光湖 OS + +现场尝试仍指向过期的 `/dev/vdb` 设备假设,而腾讯云当前系统盘并不满足该写入目标; +在没有重新取得目标机器的精确磁盘、启动方式和控制台回读之前,继续写盘会把“未知” +变成不可恢复的破坏。因此本线没有执行物理写入,也没有切换引导。 + +最终二进制状态: + +| 证据层 | 状态 | +|---|---| +| 免费 Linux 救援与网页入口 | `PASS_100` | +| GHDR / 2-of-2 / A/B 源码与测试 | `PASS_100` | +| 企业服务器物理盘写入 | `FAIL_0` | +| 光湖原生 OS 启动 | `FAIL_0` | +| 原生网络驻留 | `FAIL_0` | +| HoloLake 世界入口 | `FAIL_0` | +| 人格出生 | `FAIL_0` | + +## 六、结束与重新开启 + +冰朔于 `2026-08-03` 明确要求结束本开发线。结束后停止自动心跳、服务器写入和继续部署; +不得因为浏览器仍登录、Linux 仍在线或本地补丁存在而自动恢复动作。 + +以后只有冰朔重新明确开启企业节点部署任务,才按以下顺序继续: + +1. 从本归档补丁恢复源码; +2. 重新只读识别目标节点、系统盘、启动方式和救援入口; +3. 重新取得服务器资源租约与当次授权; +4. 重跑全部质量门; +5. 只有 A/B 写入、引导切换、原生网络回执全部存在,才可把原生状态从 `0` 改为 `100`。