# 同屏专家 C++ Server Deployment Runbook

This runbook covers the current C++ MVP relay server on Windows. The server can run as a foreground console process for smoke testing or as a Windows Service for local/public relay deployments.

## Build Release

Run from the repository root:

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

The Release server executable is written to:

```text
build\x64\Release\SyncVisionServer.exe
```

## Install From Unsigned Development Package

From a development checkout, create unsigned Windows x64 output for manual verification with:

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

The output directory is:

```text
C:\ProgramData\SyncVisionTrustedPackage\release\SyncVision-0.1.0-win-x64-development-unsigned
```

The script also writes a matching `.zip`, a package marker named `SyncVisionPackage.json`, `SHA256SUMS.txt`, and an external `.SHA256SUMS.sha256` manifest-digest sidecar. The package name and marker identify development/unsigned/manual-verification output. The default package intentionally omits `Install LAN Server as Administrator.cmd`. Product build outputs are accepted only through the protected receipt-bound cache published by `build.ps1`; packaging first copies that cache into a protected snapshot, validates the snapshot against the receipt, and then reads only snapshot paths. It includes only runtime DLLs approved by `msbuild\SyncVisionWebRtcDependency.lock.json` and revalidated through build-flavor provenance.

The zip, internal manifest, and external digest provide transport integrity only; they are not publisher authentication. Commercial one-click Internet distribution requires a native bootstrap or MSI carrying a valid Authenticode signature from the intended production certificate. This repository does not currently have or emulate that certificate/signing integration. A self-signed certificate or self-hashing script is not an acceptable substitute.

For local development only, pass `-AllowUnsignedDevelopmentBootstrap` directly to `package-release.ps1`. This creates `SyncVision-<version>-win-x64-development-unsigned-bootstrap` and includes the legacy unsigned CMD. No package script enables this option implicitly, and that output is not suitable for commercial distribution.

Before copying the zip to a target server, run the package artifact smoke from the development checkout:

```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
```

The smoke receives the expected manifest digest from caller/out-of-package data, extracts the zip into `build\package-artifact-smoke`, compares that value with `SHA256SUMS.txt`, verifies every manifest entry with `Get-FileHash`, rejects unmanifested extra files, starts the packaged `build\x64\Release\SyncVisionServer.exe` on a dynamic loopback port, and calls the packaged `scripts\verify-public-relay.ps1` with a temporary token file for a local live-status probe.

Before installing from a package on a target machine, review `docs\runbooks\release-prerequisites.md`. The package includes the four Microsoft-signed Visual C++ x64 runtime DLLs required by the release at both the public and private runtime roots. `install-server.ps1` authenticates the package first, rejects an incomplete app-local runtime set, and copies the complete set beside the installed server so an older `System32` runtime cannot be selected.

When `install-server.ps1` runs from an unpacked package root with `-SkipBuild`, it requires `-ExpectedManifestSha256` from a separately obtained external digest, parses `SHA256SUMS.txt` from one byte snapshot, validates `SyncVisionPackage.json`, and verifies the complete package tree before protected staging. The digest check detects transport changes but does not authenticate a publisher. The default package has no elevation wrapper, so open an administrator PowerShell session yourself only after manual verification. `-AllowUntrustedPackageManifestForLocalDevelopment` remains an explicit local-only digest override. A markerless source checkout can use an existing Release binary only when it contains both `SyncVision.sln` and `scripts\build.ps1` and explicitly passes `-AllowSourceCheckoutSkipBuildForLocalDevelopment`; unknown or mixed layouts are rejected.

For binary package installs, run commands from the unpacked package root and pass `-SkipBuild`. The installer stages the exact manifest-selected server files and any later executed `status-server.ps1` verifier under protected ProgramData, then revalidates the mutable package tree. It resolves `sc.exe` and Windows PowerShell only from validated absolute paths beneath the trusted Windows root, authenticates their Microsoft signatures, and binds their file identities across each trusted use. ACL-protected `Global\` mutexes serialize every shared install directory, data/log path, service name, and managed firewall rule independently, with deterministic lock ordering when an install touches several resources. Before any real mutation or first secret byte, every existing install/data/log ancestor must be non-reparse, trusted at the relevant ownership boundary, and free of content, metadata, delete, or ACL-control grants to unprivileged SIDs. The selected service SID is treated as an untrusted mutator on all ancestors and receives only exact `ReadAndExecute` rights on the data leaf and `Modify` rights on the log leaf. Missing data/log directories are atomically created with those protected ACLs and revalidated if another process preempts creation. Default Program Files and ProgramData destinations satisfy this policy; a custom destination with weaker ACLs is rejected fail-closed. For an upgrade the installer stops and quiesces the existing service before taking rollback snapshots, captures file bytes, timestamps, attributes, and security descriptors including the nested log ACL boundary, then repeats boundary checks around protected sibling publication. It rejects unmanaged destination entries, publishes a fresh exact install directory, and verifies exact filename/hash equality before service reconfiguration. The binary backup and exact data/log metadata snapshots, service configuration/existence/running state, and managed firewall rules are retained until config, firewall, start, and staged install verification all succeed; a later failure compensates the complete prior state. Known stale `datachannel.dll`, SRTP, and OpenSSL runtime files disappear when reinstalling a default package. `-WhatIf` returns before staging or destination changes.

## Run The Server

Use a non-default shared token for any non-local environment. The server rejects `dev-token` when the listen address is not loopback. For a foreground server, keep the secret in a token file and start the server from a config file:

```powershell
@"
listen=0.0.0.0:38580
token_file=C:\SyncVision\secrets\server.token
# Optional: device_tokens_file=C:\SyncVision\secrets\device.tokens
transport_encryption=required
transport_secret_file=C:\SyncVision\secrets\transport.secret
discovery_enabled=false
allow_unsafe_public_relay=true
"@ | Set-Content -Encoding UTF8 .\server.public.conf

.\build\x64\Release\SyncVisionServer.exe --config .\server.public.conf
```

For a shorter foreground command, the server binary and wrapper also accept token files directly:

```powershell
.\build\x64\Release\SyncVisionServer.exe --listen 0.0.0.0:38580 --token-file C:\SyncVision\secrets\server.token --no-discovery --allow-unsafe-public-relay --transport-encryption required --transport-secret-file C:\SyncVision\secrets\transport.secret
powershell -ExecutionPolicy Bypass -File .\scripts\start-server.ps1 -Configuration Release -Listen 0.0.0.0:38580 -TokenFile C:\SyncVision\secrets\server.token -NoDiscovery -AllowUnsafePublicRelay -TransportEncryption required -TransportSecretFile C:\SyncVision\secrets\transport.secret
```

`start-server.ps1` 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.

For one-command service setup from an already-built source checkout, prefer the installer; when `-Token` is omitted, it generates and ACL-protects `server.token`. When `-TransportEncryption required` is set and no transport secret is supplied, it also generates and ACL-protects `transport.secret`:

```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
```

Expected startup signal:

```text
[SyncVisionServer] listening on 0.0.0.0:38580
```

Foreground server runs write operational logs to stdout and stderr, so keep the console session alive while the server is running. Service installs additionally write file logs to `<DataDir>.logs\server.log` by default (`C:\ProgramData\SyncVision\Server.logs\server.log` for the default data directory) and rotate them with `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. For service installs, the installer rejects wildcard LAN-discovery advertisement unless `-Advertise <reachable-host:port>` is supplied; use `-NoDiscovery` when clients will connect by explicit public address. Current clients authenticate registration and status probes with nonce + HMAC-SHA256 proof. For public relay tests, set `transport_encryption=required` with a high-entropy secret stored in `transport_secret_file`; required transport wraps SyncVision frames in a PSK-derived AEAD envelope. This 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` remains an explicit acknowledgement that you are exposing the MVP relay protocol; restrict firewall source ranges and prefer VPN or a raw 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.

## LAN Discovery

LAN discovery is enabled by default. The server listens for UDP probes on `0.0.0.0:38581` and advertises its TCP relay address to clients that start without `--server`. Current clients send a random nonce and only accept discovery responses with an HMAC-SHA256 proof derived from the shared server token; the token itself is not sent in the UDP discovery probe or response. The server still answers the legacy single-line `syncvision.discover` probe for local smoke tests and simple diagnostics, but client auto-discovery uses the authenticated probe.

For a normal LAN deployment, run:

```powershell
@"
listen=0.0.0.0:38580
token_file=C:\SyncVision\secrets\server.token
advertise=<lan-ip-or-hostname>:38580
discovery_enabled=true
allow_unsafe_public_relay=true
"@ | Set-Content -Encoding UTF8 .\server.lan.conf

.\build\x64\Release\SyncVisionServer.exe --config .\server.lan.conf
```

If the machine has multiple network paths or a public DNS name, explicitly set the advertised endpoint:

```powershell
@"
listen=0.0.0.0:38580
token_file=C:\SyncVision\secrets\server.token
advertise=sync.example.com:38580
discovery_enabled=true
allow_unsafe_public_relay=true
"@ | Set-Content -Encoding UTF8 .\server.lan.conf

.\build\x64\Release\SyncVisionServer.exe --config .\server.lan.conf
```

Disable LAN discovery when the server should only be reached by an explicit public address:

```powershell
@"
listen=0.0.0.0:38580
token_file=C:\SyncVision\secrets\server.token
transport_encryption=required
transport_secret_file=C:\SyncVision\secrets\transport.secret
discovery_enabled=false
allow_unsafe_public_relay=true
"@ | Set-Content -Encoding UTF8 .\server.public.conf

.\build\x64\Release\SyncVisionServer.exe --config .\server.public.conf
```

## Firewall Port

Allow inbound TCP traffic to the configured listen port. The default MVP TCP port is `38580`. The service installer can create firewall rules when `-ConfigureFirewall` is passed.

Installer-managed firewall example from 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
```

If you choose to create rules manually instead, a basic elevated PowerShell TCP rule is:

```powershell
New-NetFirewallRule -DisplayName "SyncVision Relay 38580" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 38580 -RemoteAddress "<trusted-client-cidr-or-ip>"
```

For LAN discovery, also allow inbound UDP traffic to `38581` on trusted local networks:

```powershell
New-NetFirewallRule -DisplayName "SyncVision Discovery 38581" -Direction Inbound -Action Allow -Protocol UDP -LocalPort 38581 -RemoteAddress LocalSubnet
```

For non-loopback service installs, `install-server.ps1 -ConfigureFirewall` requires `-FirewallRemoteAddress` so the installer does not create an unrestricted inbound rule. For production-like environments, restrict manual rules with `-RemoteAddress` or an equivalent network security control so only expected client networks can reach the relay.

## One-Command Windows Service Setup

Run the install command from an elevated PowerShell prompt. In a source checkout, the installer can build the Release server by default or use an existing Release binary only with `-SkipBuild -AllowSourceCheckoutSkipBuildForLocalDevelopment`; in an unsigned development package, pass `-SkipBuild -ExpectedManifestSha256 <externally-verified-digest>` after manual package verification. The service runs as `NetworkService` by default; use `-ServiceAccount LocalService` for a more constrained local-only identity, or `-ServiceAccount LocalSystem` only when that elevated built-in identity is explicitly required.

For explicit public-address clients with LAN discovery disabled:

```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
```

From an unpacked unsigned development package, use the same install flow with `-SkipBuild`:

```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
```

The package also includes `examples\install-public-relay.example.ps1`, `examples\install-lan-relay.example.ps1`, `examples\new-client-config.example.ps1`, `examples\server.public.conf.example`, `examples\server.lan.conf.example`, `examples\client.public.conf.example`, `examples\device.tokens.example`, `examples\start-host.example.ps1`, and `examples\start-viewer.example.ps1` as editable templates. Keep `server.token`, `server.conf`, client config files, device token maps, and any real token/key material outside source control and outside published package examples.

When `-Token` is omitted, the installer generates a random shared secret, writes it to `server.token`, writes `token_file=...` in `server.conf`, and reports `Access token: <generated; see token file>` after installation. When `-TransportEncryption required` or `optional` is enabled and no transport secret is supplied, it generates `transport.secret`, writes `transport_secret_file=...` in `server.conf`, and reports `Transport secret: <generated; see transport secret file>`. Read the protected `server.token` and `transport.secret` files locally when generating client configs. If you intentionally need a one-time interactive token display during install, pass `-RevealGeneratedToken`; avoid using it in shared terminals, transcripts, or automation logs. When you pass `-Token` explicitly, the installer does not echo the token back after installation.

After the relay is installed, generate a client bundle from the relay endpoint and the `server.token` file instead of hand-writing every client config:

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

The generator creates the output directory, copies the shared token as `server.token`, writes `client.public.conf` with `token_file=server.token` and reconnect defaults, and writes `start-host.generated.ps1` / `start-viewer.generated.ps1` command templates that call the packaged wrapper scripts with `-ClientConfig`. The generated host starter also includes `-AllowViewer <viewer-device-id>` and `-GrantInputTo <viewer-device-id>` for the paired viewer; remove `-GrantInputTo` when the default should be view-only. It prints generated paths without printing the token.

For per-device hardening, create a private UTF-8 file with one `device_id=token` entry per line, for example:

```text
host-001=<host-device-token>
viewer-001=<viewer-device-token>
```

Install with `-DeviceTokensFile C:\SyncVision\secrets\device.tokens`, or add `device_tokens_file=C:\SyncVision\secrets\device.tokens` to `server.conf`. Each host/viewer must then supply its own device token with `-DeviceTokenFile <path>` or client config `device_token_file=<path>`. Current clients prove device-token possession during registration instead of sending the device token directly, while legacy `device_token=` payloads remain accepted for compatibility. Device tokens let you revoke or rotate one device without changing every client. Required transport encryption protects relay payload confidentiality/integrity with AEAD, but keep the transport secret private and use a VPN or raw TCP TLS tunnel when the path also needs certificate identity or managed edge termination.

For an already-built source checkout, let the installer select the first usable non-loopback IPv4 address and write it as the advertised endpoint:

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

For a multi-homed server where you need to choose the advertised endpoint manually, pass `-Advertise <reachable-host:port>` instead of `-Lan`.

For an explicit public-only relay with LAN discovery disabled and a narrower firewall source:

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

If you need to pre-seed a known token, write it to the service data directory as a protected `server.token` after install and keep the command-line install flow token-free. For normal installs, read the generated value from the protected server token file locally, then place the same value in a protected client-side token file or use `new-client-config.ps1 -TokenFile`.

To run the service under a lower-privilege built-in account:

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

Dry-run the setup without elevation or filesystem/service changes:

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

## Elevated Service Install Smoke

On a dedicated administrator test machine, run the optional smoke below to prove the installer can create and start a real Windows Service, create loopback-scoped Windows Firewall rules, probe the live service, and clean everything up through the uninstaller:

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

After building a zip, use the same smoke in package mode to prove the packaged installer path. Start with `-WhatIf` on a development machine; omit `-WhatIf` only on a dedicated administrator test server where creating a temporary Windows Service and loopback-scoped Windows Firewall rules is acceptable:

```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
```

The script uses a unique `SyncVisionServerSmoke-*` service name, dynamic loopback TCP/UDP ports, `FirewallRemoteAddress 127.0.0.1`, and temporary install/data directories. It is intentionally not wired into `verify.ps1`, package smoke, or default local smoke because it writes under `Program Files` and `ProgramData`, starts a service, and creates firewall rules. Use `-WhatIf` to inspect the plan without machine changes, or `-KeepOnFailure` only when you need to inspect failed smoke artifacts before manual cleanup.

The non-elevated package artifact smoke is a different check: it validates the generated zip and starts the packaged server as a foreground loopback process, but it does not install a service, does not create firewall rules, and does not prove external public reachability through NAT, DNS, cloud security groups, or remote firewalls.

If `-Listen` uses `0.0.0.0` or another wildcard address and both `-Lan` and `-Advertise` are omitted, the installer defaults to explicit public relay mode and writes `discovery_enabled=false`. This path still requires `-AllowUnsafePublicRelay` because it exposes a non-loopback MVP relay protocol endpoint even when `transport_encryption=required` is enabled. Pass `-Lan` for the common LAN service path, or `-Advertise <reachable-host:port>` for manual LAN/multi-homed deployments where clients should discover the server automatically.

`-VerifyInstall` checks that the Windows Service exists, the copied server binary and config file are present, firewall rules exist when `-ConfigureFirewall` is used, and the live SyncVision status protocol responds when `-Start` is also supplied. The live probe targets `127.0.0.1:<port>` for wildcard listens, so it validates the local service path but does not prove external public reachability through NAT, DNS, cloud security groups, or remote firewalls.

The default service config is written to:

```text
C:\ProgramData\SyncVision\Server\server.conf
```

The service token is written separately to:

```text
C:\ProgramData\SyncVision\Server\server.token
```

The default service log is written to:

```text
C:\ProgramData\SyncVision\Server.logs\server.log
```

The installer writes `log_max_bytes=10485760` and `log_retained_files=5` unless you override those parameters.

Default installed binaries are copied to:

```text
C:\Program Files\SyncVision\Server
```

Check service state and discover the configured install/config paths:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\status-server.ps1
```

Probe the live relay protocol from the service config and return host/viewer counts plus relay counters. For wildcard listens, this is a local service probe:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\status-server.ps1 -Probe -ExpectProbeScope Local
```

Probe a foreground or remote server explicitly without putting the token on the command line:

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

Expected live status includes `status=ok`, `uptime_ms`, `hosts`, `viewers`, `active_connections`, `relayed_frames`, `relayed_inputs`, `relayed_clipboards`, `relayed_file_messages`, `relayed_frame_bytes`, `relayed_input_bytes`, `relayed_clipboard_bytes`, `relayed_file_bytes`, `rejected_registrations`, `latest_frame_width`, `latest_frame_height`, `latest_frame_latency_ms`, `dropped_frames`, and `reconnected_sessions`. `active_connections` counts registered relay clients plus sockets still waiting to send their first frame. Relay byte counters count successfully delivered payload bytes, so one host frame delivered to two viewers counts twice. `latest_frame_latency_ms` is computed from frame metadata and relay receive time, so it is most precise when host and relay clocks are aligned. `dropped_frames` is based on host frame sequence gaps, and `reconnected_sessions` counts server-side same-role/device session replacements. The status script redacts tokens when printing config summaries and legacy service `binPath` command lines. During elevated `-VerifyInstall`, the installer does not execute this mutable package-tree copy: it hashes and copies the manifest-covered script into separate protected staging, revalidates the package, and executes only the staged identity.

`status-server.ps1 -Probe` proves the service is reachable from the machine running the probe and that the configured token works. When the service listens on `0.0.0.0`, the probe targets `127.0.0.1`; it does not prove external public reachability through remote firewall, NAT, DNS, or cloud security groups.

## External Public Reachability Check

After installing a public relay, run the external verifier from an allowed client network outside the relay server itself:

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

The verifier uses the same status protocol as `status-server.ps1`, but requires an explicit server endpoint and reports that the result is scoped to the external client network running the script. Add thresholds when you want the check to prove that host/viewer clients are already connected or that relay traffic has moved:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\verify-public-relay.ps1 -Server <relay-public-host-or-ip>:38580 -TokenFile "<path-to-file-containing-shared-token>" -TransportEncryption required -TransportSecretFile "<path-to-transport.secret>" -MinHosts 1 -MinViewers 1 -MinRelayedFrames 1
```

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

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

The unsigned development package includes `examples\verify-public-relay.example.ps1` as an editable command template. Keep real tokens in a local token file rather than in copied runbook snippets.

The relay applies two pre-authentication resource limits by default: first-frame payloads are capped at 64 KiB before token validation, and no more than 64 sockets may wait for their first frame at the same time. Tune the latter with `--max-pending-first-frames` or `max_pending_first_frames=...` if a trusted deployment needs a different value. To prevent one slow viewer from blocking other relay destinations, forwarded sends use `--relay-send-timeout-ms` / `relay_send_timeout_ms=...`.

## External TCP TLS Tunnel / Reverse Proxy

SyncVision clients do not speak TLS in the current MVP. Required transport encryption uses a pre-shared `transport_secret` to wrap SyncVision frames in an AEAD envelope; that protects configured relay payloads from passive plaintext exposure, 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. If you need TLS identity or managed edge termination, use an external TCP TLS tunnel or stream proxy on both sides of the connection, not HTTP reverse-proxy mode.

Recommended topology:

- Server machine: bind the SyncVision relay to 127.0.0.1:38580 or another private backend endpoint.
- Server edge: run a TCP TLS tunnel/stream proxy that accepts `<relay-public-host-or-ip>:443` and forwards decrypted TCP to the private SyncVision backend.
- Firewall/security group: only expose the TLS tunnel port to trusted client networks; do not expose the raw SyncVision backend port publicly.
- Client machines: run a client-side TLS tunnel that listens on a local endpoint, for example `127.0.0.1:38580`, and forwards encrypted TCP to `<relay-public-host-or-ip>:443`.
- SyncVision host/viewer config: point `server=` at the client-side TLS tunnel endpoint, not directly at the public TLS port.

After the tunnel is configured, run the verifier from an allowed external client network against the client-side local tunnel endpoint, for example `scripts\verify-public-relay.ps1 -Server 127.0.0.1:38580 -TokenFile "<path-to-file-containing-shared-token>" -TransportEncryption required -TransportSecretFile "<path-to-transport.secret>"`. The verifier speaks raw SyncVision TCP, so it must run through the local TLS client tunnel rather than directly against `<relay-public-host-or-ip>:443`. That proves the public tunnel path is reachable from that network and that the backend SyncVision status protocol answers through the tunnel. Local package smokes and `status-server.ps1 -Probe` still do not prove external reachability.

Uninstall the service from an elevated PowerShell prompt:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\uninstall-server.ps1
```

Remove copied binaries and service data only when explicitly intended:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\uninstall-server.ps1 -RemoveInstallDir -RemoveDataDir
```

The install script creates firewall rules only when `-ConfigureFirewall` is passed. Without that switch, add the TCP and UDP rules above manually after choosing the listen and discovery ports, and restrict `-RemoteAddress` or `-FirewallRemoteAddress` for public or semi-public deployments.

## Token

- The server `--token-file` / config `token_file` contents, or legacy `--token` / `token=...` value, must match the host and viewer client `--token-file` contents or legacy `--token` value so clients can prove possession with nonce + HMAC-SHA256.
- Optional per-device tokens are configured on the server with `device_tokens_file`, using one `device_id=token` line per allowed device. A configured device must supply the matching client `--device-token-file` / `device_token_file` value so registration can prove possession.
- The checked-in default `dev-token` is only for loopback smoke testing and is rejected on non-loopback listeners.
- The service installer generates a random token when `-Token` is omitted.
- The service installer stores the shared token in `server.token`, writes `token_file=...` in `server.conf`, and disables inherited ACLs on the token file so only Administrators, SYSTEM, and the selected service account can read it. The service SID is not trusted on data/log ancestors; it receives only exact leaf rights needed to read protected data/config/secrets and modify the log leaf. Still restrict Windows account and directory access to `C:\ProgramData\SyncVision\Server`.
- Existing hand-written configs may still use `token=...`, but new service installs should prefer `token_file=...`.
- Foreground wrapper starts should prefer `scripts\start-server.ps1 -TokenFile <path>`; the wrapper rejects missing/empty token files and rejects combining explicit `-Token` with `-TokenFile`.
- Do not put production tokens in source control, shell history shared with other users, screenshots, or runbook copies.
- Proof-mode registration and status requests include nonce-based replay protection, and per-device tokens reduce the blast radius of a leaked device secret. Legacy raw-token compatibility remains. Required transport encryption protects configured relay payloads with AEAD, but it is not TLS and does not provide certificate identity, automatic rotation, or a persisted secret store. Use a trusted network path, VPN, or raw TCP TLS tunnel when those properties are required.

## Client Connection Shape

Host clients connect to the server with the wrapper:

```powershell
@"
server=<server-host-or-ip>: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-device-id>
```

When reconnect is enabled, each new host/viewer session attempt resolves the server endpoint again. Configs with an explicit `server=` keep using that endpoint; same-LAN clients that omit `server=` rerun LAN discovery and status selection before each retry.

For shared or partially trusted relays, restrict which viewer device IDs can bind to the host:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\start-host.ps1 -Configuration Release -ClientConfig .\client.public.conf -Device <host-device-id> -AllowViewer <viewer-device-id>
```

The wrapper forwards each `-AllowViewer` value as `--allow-viewer <viewer-device-id>`. When a host publishes an allowlist, the relay rejects other viewer devices for that host with `viewer_not_authorized`; clients display a friendly next action and keep the raw reason as the diagnostic code.

Keyboard and mouse input is a separate permission from viewer attachment. To allow a trusted viewer to control the host at startup, add `-GrantInputTo <viewer-device-id>`; the wrapper forwards it as `--grant-input-to <viewer-device-id>`. If that viewer is offline, the relay keeps a one-shot pending grant for the next matching viewer connection. Once consumed, the relay stores the grant against that viewer's active server-side connection session, so a viewer reconnect, same-device replacement, or relay TTL cleanup must be granted again before keyboard or mouse input is forwarded. Viewers can instead send a session-bound request with `--request-input-on-start`; hosts default to `--input-request-policy prompt`, which shows a 同屏专家远程控制请求 dialog naming the viewer, host, request id, and viewer session, and only grants control if the host chooses Yes. Use explicit `--input-request-policy grant|deny` only for controlled automated smoke flows. Without a current grant, the relay keeps video, clipboard, and file transfer available but rejects viewer input with `input_not_authorized`, which clients retain as the diagnostic code.

On the same LAN, omit `--server` to use authenticated discovery first and localhost fallback second:

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

Viewer clients connect to an already-online host device with the wrapper:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\start-viewer.ps1 -Configuration Release -ClientConfig .\client.public.conf -Device <viewer-device-id> -Target <host-device-id>
```

The viewer can also omit `--server` on the same LAN:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\start-viewer.ps1 -Configuration Release -Device <viewer-device-id> -Target <host-device-id> -TokenFile "<path-to-file-containing-shared-token>"
```

Direct binary invocations support the same secret-safe client option:

```powershell
.\build\x64\Release\SyncVisionClient.exe --mode host --server <server-host-or-ip>:38580 --device <host-device-id> --token-file "<path-to-file-containing-shared-token>"
.\build\x64\Release\SyncVisionClient.exe --mode viewer --server <server-host-or-ip>:38580 --device <viewer-device-id> --target <host-device-id> --token-file "<path-to-file-containing-shared-token>"
.\build\x64\Release\SyncVisionClient.exe --mode host --config .\client.public.conf --device <host-device-id>
.\build\x64\Release\SyncVisionClient.exe --mode viewer --config .\client.public.conf --device <viewer-device-id> --target <host-device-id>
```

The current server rejects a viewer registration when its target host is offline.

## Service Operations Notes

- The service runs `SyncVisionServer.exe --service --service-name <name> --config "<server.conf>"`.
- The installer configures service recovery by default with two automatic restart attempts. Pass `-NoServiceRecovery` only when another supervisor owns restart policy.
- The config file uses UTF-8 `key=value` lines for `listen`, `token_file` or legacy `token`, optional `device_tokens_file`, `log_file`, `log_max_bytes`, `log_retained_files`, `discovery_listen`, optional `advertise`, `discovery_enabled`, `first_frame_timeout_ms`, `idle_timeout_ms`, `heartbeat_ttl_ms`, `relay_send_timeout_ms`, `max_sessions`, `max_pending_first_frames`, optional `transport_encryption`, and preferred `transport_secret_file`. Legacy inline `transport_secret` is still accepted for compatibility, but new deployments should keep transport secrets in a file instead of command lines or shared config snippets.
- To change service options, uninstall and reinstall or update `server.conf` and restart the service.
- The MVP still relies on configured secrets and the surrounding network controls; registration/status have nonce + HMAC proof auth, and required transport encryption uses AEAD rather than TLS. The product does not yet provide certificate identity, automatic token rotation, persisted secret storage, or an automated rollback package.
