# 同屏专家

同屏专家 is a native Windows desktop/server MVP for remote screen synchronization. The internal executable and package family still use the `SyncVision` name. It uses a self-hosted relay server plus host/viewer desktop clients so a user can run the server on localhost, LAN, or a public Windows machine and connect clients by server address.

## Beginner Development Package

The current packaged output is named `SyncVision-*-win-x64-development-unsigned.zip`. It is unsigned development output for manual verification, not a publisher-authenticated commercial installer. Unzip the whole folder and double-click `Open Group Console.cmd`. When the connection fields are blank, the control console starts the bundled LAN relay automatically. Generate one `SyncVisionAgentSetup.exe` from the console for the controlled PCs; enter `<server-address>:38580` only when connecting through a separately started LAN or public relay.

The default package contains no package-local administrator CMD. For a dedicated LAN relay, first obtain the external manifest digest through a separate trusted channel and verify the package manually. Then open an administrator PowerShell session yourself and run `scripts\install-server.ps1 -SkipBuild -ExpectedManifestSha256 <verified-digest> ...`. Public internet relay setup still needs a reachable server address, a non-default token file, and firewall/NAT planning; use the generated `examples\` files and `docs\runbooks\cpp-server-deployment.md` for that path.

## Components

- `SyncVisionServer.exe`: TCP relay server for host/viewer registration, frame relay, input, clipboard, file messages, status, and LAN discovery.
- `SyncVisionClient.exe`: Windows client with host and viewer modes.
- `SyncVisionLauncher.exe`: 同屏专家 desktop launcher for saving client config files and opening the share and group-console entries without typing command-line arguments. It also remembers non-secret UI fields in the user's local app data profile, storing token and transport secret paths rather than inline secret values.
- `SyncVisionTests.exe`: native protocol, server, client, LAN, file-transfer, clipboard, and viewer tests.
- `scripts\install-server.ps1`: one-command Windows Service install flow for the relay server.

## Source Checkout Build

Run from the repository root in PowerShell:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\build.ps1 -Configuration Release
```

Debug and Release outputs are written under `build\x64\<Configuration>`.

The default build does not include third-party WebRTC dependencies. To build the optional libdatachannel WebRTC backend, run `scripts\build.ps1 -Configuration Release -EnableWebRtc`; the reviewed trust root is `msbuild\SyncVisionWebRtcDependency.lock.json`. See `docs\runbooks\webrtc-build.md` for the pinned dependency and runtime layout.

## Source Checkout Verify

Run the full verification chain:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\verify.ps1
```

`verify.ps1` treats screen-capture unavailability as a failure by default. In a non-interactive or locked desktop session where capture is expected to be unavailable, run the degraded verification explicitly:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\verify.ps1 -AllowCaptureSkip
```

For a shorter local smoke:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\run-local-smoke.ps1
```

The full verification chain includes relay, disabled-input, file-transfer, encrypted-client, local explicit-relay, reconnect, LAN-discovery, host auto-server LAN, encrypted start-host auto-server, host-frame, viewer-window, release-package, package artifact, and packaged install dry-run smokes. It creates each local release-package smoke under a unique ACL-protected `C:\ProgramData\SyncVisionTrustedPackage\verify-*` root and removes that root after verification. It then runs `scripts\run-package-artifact-smoke.ps1` against the generated zip with a caller-supplied trusted manifest digest, verifies `SHA256SUMS.txt`, starts the packaged Release server, and probes it with the package-local verifier before the packaged installer dry-run.

The local explicit-relay smoke starts a wildcard listener and connects clients through `127.0.0.1` with an explicit `-Server` path. It proves the non-discovery relay workflow on one machine; it does not prove external public reachability across DNS, NAT, cloud security groups, or remote firewalls.

For the real WebRTC video path, run `scripts\run-local-control-share-webrtc-smoke.ps1 -Configuration Release`. It starts one Host plus primary and late-joining Viewers, requires decoded H.264 frames from WebRTC, verifies TCP video relay stays suppressed after cutover, then sends a real relay video-policy change through a temporary Viewer. Both existing Viewers must fall back to TCP relay, decode fresh H.264 relay frames, and retain their original process IDs.

Real Windows Service installation and Windows Firewall creation are not part of the default verification chain because they require an elevated PowerShell session and mutate machine state. On a dedicated administrator test machine, run the optional service smoke explicitly:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\run-elevated-service-install-smoke.ps1 -Configuration Release -ServiceAccount NetworkService
```

To dry-run the same service-install path against an actual unsigned development package zip without mutating the machine:

```powershell
$packageOutputRoot = "C:\ProgramData\SyncVisionTrustedPackage\release"
$expectedManifestSha256 = (Get-Content (Join-Path $packageOutputRoot "SyncVision-0.1.0-win-x64-development-unsigned.SHA256SUMS.sha256") -Raw).Trim()
powershell -ExecutionPolicy Bypass -File scripts\run-elevated-service-install-smoke.ps1 -PackageZip (Join-Path $packageOutputRoot "SyncVision-0.1.0-win-x64-development-unsigned.zip") -ExpectedManifestSha256 $expectedManifestSha256 -ServiceAccount NetworkService -WhatIf
```

## Unsigned Development Package

Create unsigned Windows x64 development output from a Release build:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\package-release.ps1 -Version 0.1.0
```

The package is written to the ACL-protected `C:\ProgramData\SyncVisionTrustedPackage\release\SyncVision-0.1.0-win-x64-development-unsigned\` root, with a matching `.zip`, `SyncVisionPackage.json`, `SHA256SUMS.txt`, and external `.SHA256SUMS.sha256` manifest-digest sidecar. `SyncVisionPackage.json`, the directory name, and the zip name label the artifact as development/unsigned/manual-verification output. The default package omits `Install LAN Server as Administrator.cmd`. A custom `-OutputRoot` is accepted only when its ACL and ancestor chain satisfy the same protected publication boundary.

The zip, manifest, and external digest provide transport integrity only. They do not authenticate a publisher and do not make an unsigned script safe to elevate. Commercial one-click Internet distribution requires a native bootstrap or MSI with a valid production Authenticode signature and the intended production certificate. SyncVision does not currently have or emulate that certificate/signing step; no self-signed or self-hashing substitute is used.

For local testing only, `-AllowUnsignedDevelopmentBootstrap` explicitly adds the legacy wrapper and changes the output name to `SyncVision-<version>-win-x64-development-unsigned-bootstrap`. The wrapper remains unsigned and is not suitable for commercial distribution. No other package script enables this switch implicitly.

The default package deliberately excludes WebRTC runtime DLLs even if stale copies remain in the output directory. Build and package the WebRTC edition explicitly with:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\package-release.ps1 -Version 0.1.0 -EnableWebRtc
```

That edition requires a single prepared dependency root. Preparation copies authenticated source, vcpkg inputs, and the complete lock-pinned CMake runtime/module tree into a protected, sealed ProgramData workspace and isolates MSBuild from ambient/user imports before execution. Product builds apply the same isolation to a fresh repository/WebRTC snapshot, use locked Microsoft tools and imports with `/m:1 /nodeReuse:false`, and publish a protected receipt-bound build cache only after post-build validation. Packaging accepts the canonical protected cache under `C:\ProgramData\SyncVisionTrustedBuild`, copies it into protected staging, verifies the snapshot against the original receipt, and reads package inputs only from that verified snapshot. The schema-v4 `SyncVisionWebRtcDependency.json` snapshot, including the exact CMake tree identity, and all actual directories and files must match the selected Debug or Release entry in `msbuild\SyncVisionWebRtcDependency.lock.json`; packaging revalidates the lock identity and byte hash recorded by the build. Tool identity uses the lock-pinned four-part `file_version_raw`, SHA-256, and Microsoft Authenticode signer; localized `FileVersion` text is diagnostic only because Windows can return different strings for the same signed bytes under different UI languages or snapshot paths. A legitimate dependency or Microsoft tool update that changes any approved hash or raw file version intentionally fails until the lock change is separately reviewed and updated. Only the approved `datachannel.dll`, `srtp2.dll`, `libssl-3-x64.dll`, and `libcrypto-3-x64.dll` are packaged next to the executables.

Before moving a zip to a target server, smoke-test the actual artifact:

```powershell
$packageOutputRoot = "C:\ProgramData\SyncVisionTrustedPackage\release"
$expectedManifestSha256 = (Get-Content (Join-Path $packageOutputRoot "SyncVision-0.1.0-win-x64-development-unsigned.SHA256SUMS.sha256") -Raw).Trim()
powershell -ExecutionPolicy Bypass -File scripts\run-package-artifact-smoke.ps1 -PackageZip (Join-Path $packageOutputRoot "SyncVision-0.1.0-win-x64-development-unsigned.zip") -ExpectedManifestSha256 $expectedManifestSha256
```

On a target Windows server, unpack the zip, review `docs\runbooks\release-prerequisites.md`, and independently verify the external digest. The package carries the four Microsoft-signed Visual C++ x64 runtime DLLs required by its binaries at both runtime boundaries, so it does not fall back to an older `System32` runtime; an installed relay receives the same app-local files. Then open an administrator PowerShell session and install from the package root with `-SkipBuild`. The installer validates the complete package tree, rejects unmanifested extra files, and stages every subsequently used package input, including `status-server.ps1`, under protected ProgramData. It resolves `sc.exe` and Windows PowerShell only through validated absolute Windows paths and rechecks each bound identity before trusted use. ACL-protected `Global\` mutexes independently serialize the install directory, data and log paths, service, and managed firewall rules in deterministic order. Install, data, log, token, secret, and config paths reject reparses and unprivileged mutation rights before the first secret byte is written; the service SID is untrusted on ancestors and receives only the exact required rights on protected data/log leaves. Unsafe custom destinations fail closed; the default Program Files and ProgramData destinations remain supported. Upgrades quiesce the service before protected rollback snapshots capture default-stream bytes, timestamps, attributes, owner, group, and DACL metadata, including the nested log ACL boundary. Hard-linked files and NTFS named data streams are rejected because their topology/content cannot be recreated by this rollback format; extended attributes and audit SACLs are outside the snapshot contract. Publication uses a protected sibling, repeats boundary checks, and verifies exact destination names and hashes. Binary backup, data/log metadata snapshots, service state/configuration, firewall rules, and running state are retained until configuration, start, and staged verification all succeed, then compensated on failure. A default reinstall removes stale WebRTC/OpenSSL DLLs, while `-WhatIf` remains side-effect free.

```powershell
$expectedManifestSha256 = (Get-Content "<externally-verified-manifest-digest-sidecar>" -Raw).Trim()
powershell -ExecutionPolicy Bypass -File .\scripts\install-server.ps1 -SkipBuild -ExpectedManifestSha256 $expectedManifestSha256 -Configuration Release -Listen 0.0.0.0:38580 -NoDiscovery -AllowUnsafePublicRelay -TransportEncryption required -ConfigureFirewall -FirewallRemoteAddress "<trusted-client-cidr-or-ip>" -ServiceAccount NetworkService -Start -VerifyInstall
```

Use the generated `examples\install-public-relay.example.ps1`, `examples\install-lan-relay.example.ps1`, `examples\new-client-config.example.ps1`, `examples\client.public.conf.example`, `examples\start-host.example.ps1`, and `examples\start-viewer.example.ps1` as package-local command templates.

For a foreground relay from the package root, keep the shared secret in a file and start the wrapper with `-TokenFile` instead of passing the token text on the command line:

```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\start-server.ps1 -Configuration Release -Listen 0.0.0.0:38580 -TokenFile "<path-to-file-containing-shared-token>" -NoDiscovery -AllowUnsafePublicRelay -TransportEncryption required -TransportSecretFile "<path-to-transport.secret>"
```

After a public relay install has produced `server.token` and `transport.secret`, generate a client-side config bundle instead of hand-writing `client.public.conf` on every machine:

```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\new-client-config.ps1 -Server <server-address>:38580 -TokenFile "<path-to-server.token>" -OutputDir ".\client-config" -HostDevice host-001 -ViewerDevice viewer-001 -TransportEncryption required -TransportSecretFile "<path-to-generated-transport.secret>" -IncludeDeviceTokenTemplates
```

With `-Server`, the generator copies the shared token into the output directory, writes `client.public.conf` with `server=<server-address>:38580`, `token_file=server.token`, optional `transport_encryption=required` / `transport_secret_file=transport.secret`, and reconnect defaults, then creates `start-host.generated.ps1` / `start-viewer.generated.ps1` wrappers that call the package scripts with `-ClientConfig`. The generated host starter also includes `-AllowViewer <viewer-device-id>` and `-GrantInputTo <viewer-device-id>` for the paired viewer; remove `-GrantInputTo` from the generated starter when you want view-only access by default. The generation summary lists the generated secret file name without printing the transport secret.

## Local/LAN Usage

In `SyncVisionLauncher.exe`, leaving `Server` blank preserves the same local/LAN default path: hosts can auto-start a local relay and viewers can use discovery or local fallback. Enter `<server-address>:38580` only when connecting through an explicit public or LAN relay.

Start a host. When `-Server` is omitted, the wrapper starts a local relay on `127.0.0.1:38580` if one is not already listening, then connects the host to it. The C++ host binary also supports this default directly: `SyncVisionClient.exe --mode host ...` auto-starts a sibling `SyncVisionServer.exe` unless `--server` or `--no-auto-server` is supplied.

```powershell
powershell -ExecutionPolicy Bypass -File scripts\start-host.ps1 -Configuration Release -Device host-001 -Token dev-token
```

For stronger or lighter visuals, tune host capture FPS and JPEG quality with `-CaptureFps 1..60` and `-JpegQuality 1..100`; defaults are 10 FPS and JPEG quality 70. Choose the captured monitor with `-CaptureMonitor primary`, `-CaptureMonitor all`, or a zero-based monitor index such as `-CaptureMonitor 1`; the default is `primary`. Choose the capture backend with `-CaptureBackend auto`, `gdi`, `desktop-duplication`, or `dxgi`; `dxgi` is an alias for Desktop Duplication, `auto` tries Desktop Duplication for single-monitor and all-monitor captures and falls back to GDI/WIC, while explicit `gdi` keeps the legacy WIC path. The default `video_codec=auto` policy starts with H.264, tries every Media Foundation hardware encoder before any software encoder, and falls back to software H.264 or JPEG when the GPU path is unavailable or a Viewer requires JPEG.

Remote keyboard/mouse control is relay-denied by default. To let a named viewer control this host at startup, pass `-GrantInputTo viewer-001`; the wrapper forwards it as `--grant-input-to <device-id>` in the host registration. If that viewer is not connected yet, the relay treats this as a one-shot pending grant for the next matching viewer connection; once consumed, the actual input grant is bound to the viewer's current server-side connection session. A viewer reconnect, same-device replacement, or relay TTL cleanup requires a fresh host grant before input is forwarded again. Viewers can also ask for control with `--request-input-on-start`; the default host `--input-request-policy prompt` shows a 同屏专家远程控制请求 dialog naming the viewer, host, request id, and relay session, and only sends `grant` when the host chooses Yes. Explicit `--input-request-policy grant` / `deny` remains available for controlled smoke flows. For a view-only host even when a relay grant exists, add `-DisableRemoteInput` to the host wrapper, or `--disable-remote-input` when starting `SyncVisionClient.exe` directly. This keeps frame, clipboard, and file relay enabled while preventing keyboard/mouse injection on the host. To restrict which viewer device IDs can attach to this host at all, pass one or more `-AllowViewer viewer-001` values; the wrapper forwards each value as `--allow-viewer <device-id>`.

Host mode keeps the system and display awake by default so unattended Agents do not enter an idle sleep or display-off lock while remote control is available. Set `keep_awake=false` in the client config, or pass `--allow-system-sleep`, to opt out. Agents installed from the control console's generated `SyncVisionAgentSetup.exe` run through the protected automatic `SyncVisionAgent` LocalSystem service in `%ProgramData%\SyncVision\Agent`; the service places the supervised Share/Client pair in the active console session and the Client follows `default`/`Winlogon` input-desktop changes so an explicit `Win+L`, credential screen, and UAC secure desktop continue to refresh and accept authorized input. Starting `SyncVisionShare.exe` directly remains a per-user diagnostic path and does not provide secure-desktop control.

Start a viewer:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\start-viewer.ps1 -Configuration Release -Device viewer-001 -Target host-001 -Token dev-token
```

After the first valid frame arrives, the viewer telemetry overlay stays visible in the viewer window and reports `frames`, `fps`, `bitrate`, latest frame size, last-frame `age`, and metadata-backed `resolution`, `latency`, and `dropped` frame counts when the sender provides them.

To avoid repeating the shared token path while keeping the default local/LAN behavior, generate a local client config without `-Server`:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\new-client-config.ps1 -TokenFile "<path-to-file-containing-shared-token>" -OutputDir ".\client-config" -HostDevice host-001 -ViewerDevice viewer-001
```

This writes `client.local.conf` without a `server=` line. Hosts still auto-start the local relay when no server is found, and viewers still try LAN discovery before falling back to `127.0.0.1:38580`. The generated host/viewer starters pass the same config and device ids to `start-host.ps1` and `start-viewer.ps1`; the host starter also allowlists and grants input to the paired viewer unless you remove `-GrantInputTo`.

For another machine on the same LAN, put a non-default token in a local token file, then start the host wrapper in LAN mode. `-Lan` listens on all interfaces and automatically advertises the first usable non-loopback IPv4 address; pass `-Advertise <reachable-host:port>` only when you need to override that choice.

```powershell
powershell -ExecutionPolicy Bypass -File scripts\start-host.ps1 -Configuration Release -Lan -TokenFile "<path-to-file-containing-shared-token>" -AllowUnsafePublicRelay
```

When `-Server` is omitted, viewer clients try LAN discovery first and fall back to `127.0.0.1:38580`. Even LAN wildcard listeners are non-loopback raw TCP endpoints in this MVP, so `-AllowUnsafePublicRelay` is required; keep the firewall scope to `LocalSubnet` or narrower. For public relay usage, put the relay address and token file in a client config and pass `-ClientConfig` to both host and viewer, or install the relay service below.

If you run `SyncVisionClient.exe` directly instead of the wrapper and set `--auto-server-listen` to `0.0.0.0:<port>` or another non-loopback address, pass `--allow-unsafe-public-relay` with a non-default token so the child local server writes the same acknowledgement into its temporary config.

From an already-built source checkout, use `-Lan` so the installer automatically advertises the first usable non-loopback IPv4 address:

```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\install-server.ps1 -SkipBuild -AllowSourceCheckoutSkipBuildForLocalDevelopment -Configuration Release -Listen 0.0.0.0:38580 -Lan -AllowUnsafePublicRelay -ConfigureFirewall -FirewallRemoteAddress LocalSubnet -ServiceAccount NetworkService -Start -VerifyInstall
```

## Public Relay Setup

Use a non-default token for any non-loopback server. From an elevated PowerShell prompt in an already-built source checkout:

```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\install-server.ps1 -SkipBuild -AllowSourceCheckoutSkipBuildForLocalDevelopment -Configuration Release -Listen 0.0.0.0:38580 -NoDiscovery -AllowUnsafePublicRelay -TransportEncryption required -ConfigureFirewall -FirewallRemoteAddress "<trusted-client-cidr-or-ip>" -Start -VerifyInstall
```

`-SkipBuild` means “use the existing binary.” An unsigned development package is identified by `SyncVisionPackage.json` and requires a separately obtained `-ExpectedManifestSha256` transport-integrity digest before its complete `SHA256SUMS.txt` tree is accepted. A markerless source checkout must contain both `SyncVision.sln` and `scripts\build.ps1` and pass `-AllowSourceCheckoutSkipBuildForLocalDevelopment`; source-checkout trust is never inferred from those files alone.

When `-Token` is omitted, the installer generates a random shared token, writes it to `server.token`, protects the token file ACL for service-secret use, and writes a `token_file=...` reference in `server.conf`. With `-TransportEncryption required`, the installer also generates `transport.secret` when neither `-TransportSecret` nor `-TransportSecretFile` is supplied; if you pass an explicit transport secret, it copies that value into the installed `transport.secret`. The installed transport secret is ACL-protected with the same service-secret model and referenced as `transport_secret_file=...` in `server.conf`. It also writes file logging defaults: `log_file=C:\ProgramData\SyncVision\Server.logs\server.log` (the default `<DataDir>.logs\server.log`), `log_max_bytes=10485760`, and `log_retained_files=5`. Pass `-LogFile <path>` to override the default; the custom file's parent becomes the separately protected log directory. Use those secret files for host and viewer clients. For per-device hardening, create a private server-side `device_id=token` file and install with `-DeviceTokensFile <path>`; each host/viewer then passes its own `-DeviceTokenFile <path>` or config `device_token_file=...`. The default `dev-token` is rejected on non-loopback listeners. Current clients authenticate registration and status probes with a nonce plus HMAC-SHA256 proof so the shared token and device token are not sent directly on those normal paths; legacy `token=` / `device_token=` payloads remain accepted for compatibility. Required transport wraps SyncVision frames in a PSK-derived AEAD envelope, but it is not TLS: it does not provide certificate-based server identity, HTTP reverse-proxy compatibility, automatic secret rotation, or a replay window for every live side-effect message. `-AllowUnsafePublicRelay` / `allow_unsafe_public_relay=true` is still an explicit acknowledgement that you are exposing the MVP relay protocol; restrict firewall source ranges and prefer VPN or a TCP TLS tunnel when you need certificate identity or managed edge termination. A normal HTTP/HTTPS reverse proxy does not terminate the current SyncVision client protocol unless it is configured as a TCP stream tunnel on both sides. A wildcard listen such as `0.0.0.0:38580` with no `-Advertise` defaults to explicit public relay mode with LAN discovery disabled; pass `-Lan` to advertise an automatically selected LAN IPv4 address, or pass `-Advertise <reachable-host:port>` for manual LAN/multi-homed discovery. The service runs as `NetworkService` by default; pass `-ServiceAccount LocalService` for a more constrained local-only identity or `-ServiceAccount LocalSystem` only when that elevated built-in identity is explicitly required. In a source checkout, omit `-SkipBuild` only when you intentionally want the installer to build Release before installing.

For an operator-ready External TCP TLS Tunnel / Reverse Proxy topology, including the required client-side tunnel shape and the external probe command, see `docs\runbooks\cpp-server-deployment.md`.

For foreground public-relay testing without installing a service, use `start-server.ps1 -TokenFile <path>` or `SyncVisionServer.exe --token-file <path>`. The wrapper rejects missing or empty token files before launch, and explicit `-Token` cannot be combined with `-TokenFile`. When the wrapper is started on a wildcard listen address such as `0.0.0.0:38580` without `-Advertise`, it treats the run as explicit-address public relay mode and passes `--no-discovery`; pass `-Advertise <reachable-host:port>` only for LAN or multi-homed deployments that should answer discovery.

Create a client config on each host/viewer machine so users do not have to repeat or expose the relay address and token path. Prefer the generator when you have the relay `server.token` file available:

```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\new-client-config.ps1 -Server <server-address>:38580 -TokenFile "<path-to-server.token>" -OutputDir ".\client-config" -HostDevice host-001 -ViewerDevice viewer-001 -TransportEncryption required -TransportSecretFile "<path-to-generated-transport.secret>" -IncludeDeviceTokenTemplates
```

With `-Server`, it writes `client.public.conf`, a copied `server.token`, and generated host/viewer starter scripts without printing the token. The generated host starter allowlists and grants input to the paired viewer; remove `-GrantInputTo` when the default should be view-only. The equivalent manual public config shape is:

```powershell
@"
server=<server-address>:38580
token_file=<path-to-file-containing-shared-token>
device_token_file=<path-to-file-containing-this-device-token>
transport_encryption=required
transport_secret_file=<path-to-file-containing-transport-secret>
reconnect_attempts=3
"@ | Set-Content -Encoding UTF8 .\client.public.conf

powershell -ExecutionPolicy Bypass -File scripts\start-host.ps1 -Configuration Release -ClientConfig .\client.public.conf -Device host-001 -AllowViewer viewer-001 -GrantInputTo viewer-001
powershell -ExecutionPolicy Bypass -File scripts\start-viewer.ps1 -Configuration Release -ClientConfig .\client.public.conf -Device viewer-001 -Target host-001
```

When reconnect is enabled, each retry starts by resolving the server endpoint again. Explicit public configs keep using `server=...`; LAN clients that omit `server=` rerun discovery and status selection before reconnecting. Viewer reconnects keep the current viewer window alive, preserve the last rendered frame, show a reconnecting status, and reuse the same window when the relay session comes back.

Public relay deployments should keep LAN discovery disabled and restrict the firewall rule to trusted client networks. Use `-Advertise <reachable-host:port>` only for LAN or multi-homed deployments that intentionally rely on discovery.

For public or partially trusted relays, keep hosts view-only unless a viewer device should control them. Use `-AllowViewer <viewer-device-id>` when only named viewer devices should be allowed to bind to that host, and add `-GrantInputTo <viewer-device-id>` only for viewer devices that should send keyboard/mouse input for the next/current viewer connection session.

Check service status, live relay message counters, delivered-byte telemetry, latest frame resolution/latency, sequence-gap dropped-frame counts, and server-observed reconnect replacements:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\status-server.ps1 -Probe
powershell -ExecutionPolicy Bypass -File scripts\status-server.ps1 -Probe -Server <server-address>:38580 -TokenFile "<path-to-file-containing-shared-token>" -TransportEncryption required -TransportSecretFile "<path-to-transport.secret>"
```

The status script redacts inline legacy tokens when it prints config summaries or service `binPath` values, and new deployments should keep tokens in files so process command lines and configs do not expose reusable secrets.

To prove public reachability, run the external probe from an allowed client network outside the relay server itself:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\verify-public-relay.ps1 -Server <server-address>:38580 -TokenFile "<path-to-file-containing-shared-token>" -TransportEncryption required -TransportSecretFile "<path-to-transport.secret>"
```

To prove real host/viewer relay traffic from that same external client network, run the E2E smoke. It starts temporary host and viewer clients against the supplied server address, transfers a small file through the relay, and then checks live host, viewer, and file-message counters:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\run-public-relay-e2e-smoke.ps1 -Server <server-address>:38580 -TokenFile "<path-to-file-containing-shared-token>" -TransportEncryption required -TransportSecretFile "<path-to-transport.secret>"
```

See `docs\runbooks\cpp-server-deployment.md` and `docs\runbooks\cpp-local-smoke.md` for operational details.

## Linux Relay Server

The headless relay server also builds as a self-contained static Linux x86_64
ELF. The release artifact uses musl and statically embeds OpenSSL 3, libstdc++,
libgcc, and libc so it can run on Ubuntu 16.04+ with Linux kernel 4.4+.
Installation requires systemd 229+. Control, Host, Viewer, and
agent-generation workflows remain Windows applications.

Build and test in a musl build environment with CMake 3.20+, a C++20 compiler,
OpenSSL 3 development files, and the static OpenSSL libraries:

```bash
./scripts/build-linux-server.sh \
  --build-type Release \
  --build-dir build/linux-server/portable-static-Release
```

The build entrypoint rejects a non-musl release toolchain, verifies that the
ELF has no interpreter or shared-library dependencies, and runs the transport
crypto interoperability test with external OpenSSL configuration/modules
disabled. `--host-linked` is available only for local development; the release
packager rejects that output.

Create the offline installer package from PowerShell:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\package-linux-server.ps1 `
  -ServerBinary build\linux-server\portable-static-Release\syncvision-server `
  -Version 0.1.0 `
  -OutputRoot dist\linux
```

On Windows, the default archive builder uses the selected WSL distribution.
When Hyper-V/WSL2 is intentionally disabled so a desktop hypervisor can run,
the same package can be created without changing boot settings by explicitly
passing Git for Windows Bash, which includes GNU tar:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\package-linux-server.ps1 `
  -ServerBinary build\linux-server\portable-static-Release\syncvision-server `
  -Version 0.1.0 `
  -OutputRoot dist\linux-r4 `
  -ArchiveBashPath "C:\Program Files\Git\bin\bash.exe"
```

This fallback requires Git Bash 4.4+ and GNU tar. With Windows PowerShell 5,
the repository and `OutputRoot` paths used by this backend must not contain
whitespace or double quotes; the Git installation path itself may remain under
`C:\Program Files`.

The package installer never downloads dependencies or changes the firewall.
After an ordinary-user extraction, `sudo ./install-server.sh` automatically
copies the fixed seven-file package into root-owned private staging before
installation. See [`linux/README.md`](linux/README.md) for dependency preflight,
installation, Control bundle export, upgrade, rollback, uninstall, purge, and
removal-journal recovery.

## Unified Windows and Linux Release Bundle

After producing and verifying the Windows LAN package and the Linux Server
archive, combine them into one distribution ZIP:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\package-unified-release.ps1 `
  -WindowsReleaseRoot dist\publish\SyncVision-LAN-20260727 `
  -WindowsReleaseZip dist\publish\SyncVision-LAN-20260727.zip `
  -WindowsReleaseZipSha256 dist\publish\SyncVision-LAN-20260727.zip.sha256 `
  -LinuxReleaseRoot dist\linux `
  -Version 0.1.0 `
  -PackageName SyncVision-All-Platforms-20260728 `
  -OutputRoot dist\publish `
  -AllowHistoricalWindowsServerMetadataMismatch
```

The unified package keeps only `SyncVisionControl.exe` and
`SyncVisionServer.exe` as user-visible Windows entry points. The four helper
executables remain under `private-runtime`, while the Linux x86_64 Server
installer is preserved as a `tar.gz` under `linux-server` so its POSIX file
metadata is not changed by the outer ZIP.

When a public Relay profile is configured, Control also starts a local Relay
with the same token and transport-encryption settings. Generated Agents use
authenticated LAN discovery first and fall back to the configured public Relay
only when the local Relay is unavailable. Control probes both routes, merges
the device lists, and prefers the LAN route for duplicate device IDs. An Agent
that is already connected to the public Relay keeps checking for an
authenticated local Relay and switches to the verified LAN endpoint without
consuming its transport-failure retry budget; if the LAN session later drops,
normal endpoint resolution falls back to the configured public Relay.

`-AllowHistoricalWindowsServerMetadataMismatch` is narrowly scoped to the
already verified 2026-07-27 Windows LAN package, whose Server icon rebuild
changed the Server executable after `SyncVisionBuildFlavor.json` was written.
The unified manifest records both hashes. The switch does not permit any other
runtime executable mismatch.

## Current MVP Boundaries

- Control, Host, Viewer, and agent-generation applications are Windows C++20;
  the headless relay server additionally supports Linux x86_64/systemd.
- Public remote sessions currently use server relay, not peer-to-peer/NAT traversal.
- Registration and status authentication use nonce + HMAC proof with legacy raw-token compatibility. Required transport encryption wraps relay media/control bytes in a PSK-derived AEAD envelope, but it is not TLS and does not provide certificate identity, HTTP proxy compatibility, automatic credential rotation, or full replay-window protection for every live message.
- Service installs keep the shared token in `server.token` and reference it from `server.conf`; the installer restricts token-file ACLs to Administrators, SYSTEM, and the selected service account, but you should still protect the data directory and avoid copying tokens into screenshots or shared shell history. Service logs default to `<DataDir>.logs\server.log`, in a separately protected sibling log directory, and rotate according to `log_max_bytes` / `log_retained_files`; `-LogFile <path>` selects a custom log file instead.
- Host auto-start writes a per-user temporary local server config plus a sibling token file so the token is not exposed in the child process command line or embedded in `server.conf`; both temporary files are removed when the auto-started relay stops.
- Desktop Duplication is available for single-monitor and all-monitor host capture through `--capture-backend auto|desktop-duplication|dxgi`; `dxgi` is an alias for Desktop Duplication and `auto` keeps GDI/WIC as the fallback path. H.264 encoding uses Media Foundation with hardware-first MFT selection and software/JPEG fallback.
- Remote input is denied at the relay by default and is forwarded only after the host grants a viewer session. Startup grants use `--grant-input-to` or wrapper `-GrantInputTo`; offline startup grants are one-shot pending grants and are consumed into the viewer's active relay session. Viewers can send a session-bound request with `--request-input-on-start`; hosts default to `--input-request-policy prompt`, which shows a 同屏专家远程控制请求 dialog and sends `grant` only after the host chooses Yes. Explicit `--input-request-policy grant|deny` can auto-answer it for controlled smoke flows. Reconnecting, replacing the viewer connection, or relay TTL cleanup clears the old control permission. When forwarded, host-side input injection supports basic mouse and keyboard control, extended-key metadata for right-side modifier/control keys, X1/X2 mouse buttons, and vertical/horizontal mouse wheel events. Mouse and wheel coordinates are translated through the selected capture origin for primary, all-monitor, and indexed-monitor captures. Hosts can still use `--disable-remote-input` or wrapper `-DisableRemoteInput` as a local view-only guard while continuing to stream frames. Hosts can also use `--allow-viewer` or wrapper `-AllowViewer` values for relay-enforced viewer device allowlists.
