From 184bacb2db51e9dd981fb059469fc25b11b38ffd Mon Sep 17 00:00:00 2001 From: dzeusking-dev Date: Sun, 30 Aug 2026 22:49:58 +0200 Subject: [PATCH 1/2] fix(windows): make role autostart durable --- .codex-plugin/plugin.json | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- CHANGELOG.md | 6 +++ README.md | 8 ++-- deploy/docker/Dockerfile | 2 +- deploy/docker/compose.example.yml | 2 +- docs/TUNNEL_ROLES.md | 2 +- mcp/server.mjs | 2 +- package-lock.json | 4 +- package.json | 2 +- .../Connect-PCFileBridgeRoleTunnel-Task.ps1 | 16 ++++++- scripts/Install-PCFileBridgeAutostart.ps1 | 2 +- scripts/Install-PCFileBridgeRoleAutostart.ps1 | 45 +++++++++++++++++++ scripts/container-smoke.mjs | 4 +- scripts/mcp-config-smoke.mjs | 2 +- scripts/mcp-smoke.mjs | 2 +- scripts/package-contents-check.mjs | 1 + scripts/run-image-vulnerability-gate.sh | 2 +- scripts/tunnel-role-contract.mjs | 35 +++++++++++++++ src/server.ts | 2 +- 21 files changed, 122 insertions(+), 23 deletions(-) create mode 100644 scripts/Install-PCFileBridgeRoleAutostart.ps1 mode change 100755 => 100644 scripts/run-image-vulnerability-gate.sh diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index d4c1a43..0776b77 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "pc-filebridge", - "version": "0.2.1", + "version": "0.2.2", "description": "Read files and create new files on explicitly allowed PC folders without overwrite or delete capabilities.", "author": { "name": "Gexiro Global Enterprises Ltd." diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e14b632..e182a00 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,7 +87,7 @@ jobs: - name: Install locked dependencies run: npm ci --ignore-scripts - name: Build digest-pinned runtime image - run: docker build --pull -f deploy/docker/Dockerfile -t pc-filebridge:0.2.1 . + run: docker build --pull -f deploy/docker/Dockerfile -t pc-filebridge:0.2.2 . - name: Verify container policy, tunnel version, and fail-closed startup run: npm run check:container - name: Scan complete runtime image diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e3bae24..0002d32 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,7 +60,7 @@ jobs: - name: Verify hardened VPS container run: | - docker build --pull -f deploy/docker/Dockerfile -t pc-filebridge:0.2.1 . + docker build --pull -f deploy/docker/Dockerfile -t pc-filebridge:0.2.2 . npm run check:container docker compose -f deploy/docker/compose.example.yml -f deploy/docker/compose.host-ca.example.yml config --quiet sh scripts/run-image-vulnerability-gate.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index b19787c..896939d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.2.2 - 2026-08-30 + +- Fixed Windows autostart durability: the tunnel task now has no finite execution limit and is not stopped by battery or idle transitions. +- Added a Windows PowerShell 5.1-compatible role-bound auto-volume task installer with optional isolated runtime state. +- Added a regression contract for the required long-running Scheduled Task settings. + ## 0.2.1 - 2026-08-30 - Derive a stable, opaque volume id from host, disk, partition, volume, and serial identity; drive-letter, label, health, filesystem, and bus changes do not change that id. diff --git a/README.md b/README.md index 88b662b..55d8b3b 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ For ChatGPT, create a dedicated Secure MCP Tunnel and a dedicated runtime API ke The tunnel runtime must run on the computer that owns the configured folders. A VPS deployment can remain available while personal devices are offline, but it exposes files stored in its persistent server volume; it does not make a powered-off PC disk remotely readable. See [VPS deployment](docs/VPS_DEPLOYMENT.md). -Version 0.2.1 separates the main PC, laptop, and infrastructure into three role-bound tunnels. Their tunnel identifiers, runtime aliases, and root ids must never be reused across roles. The Windows and Docker launchers verify the operator-visible tunnel name before starting, and local ChatGPT connectors require an explicit `armed` operator gate. This prevents a PC and VPS poller from silently serving different backends through one connector. +Version 0.2.2 separates the main PC, laptop, and infrastructure into three role-bound tunnels. Their tunnel identifiers, runtime aliases, and root ids must never be reused across roles. The Windows and Docker launchers verify the operator-visible tunnel name before starting, and local ChatGPT connectors require an explicit `armed` operator gate. This prevents a PC and VPS poller from silently serving different backends through one connector. ChatGPT cannot securely infer which physical device opened a conversation. Operators must select the clearly named PC, laptop, or infrastructure connector. Local Codex can instead use its device-local stdio configuration. @@ -114,13 +114,13 @@ After a successful manual connection: The installer creates a new per-user scheduled task and refuses to replace an existing task. -For role-bound automatic volume discovery, configure the task or a hidden per-user Startup shortcut to run: +For role-bound automatic volume discovery, install the dedicated per-user task: ```powershell -powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\scripts\Connect-PCFileBridgeRoleTunnel-Task.ps1 -Role pc-local +.\scripts\Install-PCFileBridgeRoleAutostart.ps1 -Role pc-local ``` -The task script runs a singleton monitor and does not contain a tunnel identifier or API key in its arguments. +If the protected tunnel runtime intentionally uses a separate local application-data directory, pass its absolute path with `-RuntimeLocalAppData`. The task script runs a singleton monitor and does not contain a tunnel identifier or API key in its arguments. Both installers create long-running tasks with no finite execution limit and do not stop them on battery or idle transitions; they refuse to replace an existing task. ## Full-drive mode diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index f9f3a2b..9420351 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -6,7 +6,7 @@ ARG TUNNEL_ASSETS_IMAGE=ghcr.io/openai/tunnel-client:v0.0.13@sha256:4c6b1441a6ca ARG TUNNEL_COMMIT=4b5267f823be0b046bb883aacb51603cfde3a0ea ARG OTEL_VERSION=v1.44.0 ARG X_NET_VERSION=v0.56.0 -ARG APP_VERSION=0.2.1 +ARG APP_VERSION=0.2.2 FROM ${NODE_IMAGE} AS build WORKDIR /src diff --git a/deploy/docker/compose.example.yml b/deploy/docker/compose.example.yml index 5626a39..0e7e634 100644 --- a/deploy/docker/compose.example.yml +++ b/deploy/docker/compose.example.yml @@ -5,7 +5,7 @@ services: build: context: ../.. dockerfile: deploy/docker/Dockerfile - image: pc-filebridge:0.2.1 + image: pc-filebridge:0.2.2 container_name: pc-filebridge restart: unless-stopped init: true diff --git a/docs/TUNNEL_ROLES.md b/docs/TUNNEL_ROLES.md index d2c21fd..f7f1da8 100644 --- a/docs/TUNNEL_ROLES.md +++ b/docs/TUNNEL_ROLES.md @@ -1,6 +1,6 @@ # Tunnel roles and device isolation -PC FileBridge 0.2.1 uses three distinct Secure MCP Tunnel identities. A tunnel identifier must never be shared by two roles or by two active pollers. +PC FileBridge 0.2.2 uses three distinct Secure MCP Tunnel identities. A tunnel identifier must never be shared by two roles or by two active pollers. | Role | Required tunnel name | Runtime alias | Root ids | |---|---|---|---| diff --git a/mcp/server.mjs b/mcp/server.mjs index 2072f19..705e4e3 100644 --- a/mcp/server.mjs +++ b/mcp/server.mjs @@ -13504,7 +13504,7 @@ async function main() { const configPath = path2.resolve(process.env.FILEBRIDGE_CONFIG ?? path2.join(pluginRoot, "config", "roots.local.json")); const policy = await FileBridgePolicy.fromFile(configPath); const server = new McpServer( - { name: "pc-filebridge", version: "0.2.1" }, + { name: "pc-filebridge", version: "0.2.2" }, { instructions: "Create-only filesystem bridge. Use only configured root IDs and relative paths. Reads are bounded and secret-redacted. Writes may create a new file or directory only. Overwrite, append, patch, rename, move, link traversal, and delete are unavailable and must never be claimed." } diff --git a/package-lock.json b/package-lock.json index 7322e00..f4d98e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pc-filebridge", - "version": "0.2.1", + "version": "0.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pc-filebridge", - "version": "0.2.1", + "version": "0.2.2", "license": "Apache-2.0", "dependencies": { "@modelcontextprotocol/sdk": "1.30.0", diff --git a/package.json b/package.json index 8cdbf91..1318edc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pc-filebridge", - "version": "0.2.1", + "version": "0.2.2", "description": "Create-only filesystem MCP server for ChatGPT, Codex, and other MCP clients.", "license": "Apache-2.0", "repository": { diff --git a/scripts/Connect-PCFileBridgeRoleTunnel-Task.ps1 b/scripts/Connect-PCFileBridgeRoleTunnel-Task.ps1 index 66abf5d..9f95ce1 100644 --- a/scripts/Connect-PCFileBridgeRoleTunnel-Task.ps1 +++ b/scripts/Connect-PCFileBridgeRoleTunnel-Task.ps1 @@ -3,10 +3,22 @@ param( [ValidateSet('pc-local', 'laptop-local')] [string]$Role = 'pc-local', [ValidateRange(5, 3600)] - [int]$PollSeconds = 15 + [int]$PollSeconds = 15, + [string]$RuntimeLocalAppData = '' ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' -& (Join-Path $PSScriptRoot 'Start-PCFileBridgeVolumeMonitor.ps1') -Role $Role -PollSeconds $PollSeconds +$previousLocalAppData = $env:LOCALAPPDATA +try { + if (-not [string]::IsNullOrWhiteSpace($RuntimeLocalAppData)) { + if (-not [IO.Path]::IsPathRooted($RuntimeLocalAppData)) { throw 'RUNTIME_LOCALAPPDATA_MUST_BE_ABSOLUTE' } + $runtimeRoot = [IO.Path]::GetFullPath($RuntimeLocalAppData) + if (-not (Test-Path -LiteralPath $runtimeRoot -PathType Container)) { throw 'RUNTIME_LOCALAPPDATA_UNAVAILABLE' } + $env:LOCALAPPDATA = $runtimeRoot + } + & (Join-Path $PSScriptRoot 'Start-PCFileBridgeVolumeMonitor.ps1') -Role $Role -PollSeconds $PollSeconds +} finally { + $env:LOCALAPPDATA = $previousLocalAppData +} diff --git a/scripts/Install-PCFileBridgeAutostart.ps1 b/scripts/Install-PCFileBridgeAutostart.ps1 index 704509f..6678989 100644 --- a/scripts/Install-PCFileBridgeAutostart.ps1 +++ b/scripts/Install-PCFileBridgeAutostart.ps1 @@ -19,7 +19,7 @@ $arguments = '-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "{0}" -Tu $action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $arguments -WorkingDirectory ([IO.Path]::GetDirectoryName($connectScript)) $trigger = New-ScheduledTaskTrigger -AtLogOn -User $account $principal = New-ScheduledTaskPrincipal -UserId $account -LogonType Interactive -RunLevel Limited -$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -RestartCount 5 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit (New-TimeSpan -Minutes 10) +$settings = New-ScheduledTaskSettingsSet -Compatibility Win8 -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -DontStopOnIdleEnd -ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew -RestartCount 5 -RestartInterval (New-TimeSpan -Minutes 1) Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description 'Starts the private OpenAI Secure MCP Tunnel for PC FileBridge after user logon.' | Out-Null Write-Output "AUTOSTART_INSTALLED task=$TaskName overwrite=false" diff --git a/scripts/Install-PCFileBridgeRoleAutostart.ps1 b/scripts/Install-PCFileBridgeRoleAutostart.ps1 new file mode 100644 index 0000000..c10f760 --- /dev/null +++ b/scripts/Install-PCFileBridgeRoleAutostart.ps1 @@ -0,0 +1,45 @@ +[CmdletBinding()] +param( + [ValidateSet('pc-local', 'laptop-local')] + [string]$Role = 'pc-local', + [string]$TaskName = '', + [ValidateRange(5, 3600)] + [int]$PollSeconds = 15, + [string]$RuntimeLocalAppData = '' +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +if ([string]::IsNullOrWhiteSpace($TaskName)) { + $TaskName = if ($Role -ceq 'pc-local') { 'PC FileBridge PC Local' } else { 'PC FileBridge Laptop Local' } +} +if (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue) { + throw 'The scheduled task already exists; this installer never replaces it.' +} + +$taskScript = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot 'Connect-PCFileBridgeRoleTunnel-Task.ps1') -ErrorAction Stop).Path +$account = [Security.Principal.WindowsIdentity]::GetCurrent().Name +$argumentParts = @( + '-NoLogo', + '-NoProfile', + '-NonInteractive', + '-ExecutionPolicy', 'Bypass', + '-File', ('"' + $taskScript + '"'), + '-Role', $Role, + '-PollSeconds', [string]$PollSeconds +) +if (-not [string]::IsNullOrWhiteSpace($RuntimeLocalAppData)) { + if (-not [IO.Path]::IsPathRooted($RuntimeLocalAppData)) { throw 'RUNTIME_LOCALAPPDATA_MUST_BE_ABSOLUTE' } + $runtimeRoot = [IO.Path]::GetFullPath($RuntimeLocalAppData) + if (-not (Test-Path -LiteralPath $runtimeRoot -PathType Container)) { throw 'RUNTIME_LOCALAPPDATA_UNAVAILABLE' } + $argumentParts += @('-RuntimeLocalAppData', ('"' + $runtimeRoot + '"')) +} +$arguments = $argumentParts -join ' ' +$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $arguments -WorkingDirectory ([IO.Path]::GetDirectoryName($taskScript)) +$trigger = New-ScheduledTaskTrigger -AtLogOn -User $account +$principal = New-ScheduledTaskPrincipal -UserId $account -LogonType Interactive -RunLevel Limited +$settings = New-ScheduledTaskSettingsSet -Compatibility Win8 -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -DontStopOnIdleEnd -ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew -RestartCount 5 -RestartInterval (New-TimeSpan -Minutes 1) + +Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description "Starts the $Role PC FileBridge auto-volume monitor after user logon." | Out-Null +Write-Output "ROLE_AUTOSTART_INSTALLED task=$TaskName role=$Role overwrite=false" diff --git a/scripts/container-smoke.mjs b/scripts/container-smoke.mjs index 3f05ad3..f445c7b 100644 --- a/scripts/container-smoke.mjs +++ b/scripts/container-smoke.mjs @@ -4,7 +4,7 @@ import { execFileSync } from "node:child_process"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; -const image = process.env.FILEBRIDGE_IMAGE ?? "pc-filebridge:0.2.1"; +const image = process.env.FILEBRIDGE_IMAGE ?? "pc-filebridge:0.2.2"; const expectedTunnelVersion = "0.0.13"; const expectedNodeVersion = "v24.20.0"; const expectedTools = [ @@ -74,7 +74,7 @@ try { } if (!failedClosed) throw new Error("Container did not fail closed when secrets were absent."); -const client = new Client({ name: "pc-filebridge-container-smoke", version: "0.2.1" }); +const client = new Client({ name: "pc-filebridge-container-smoke", version: "0.2.2" }); const transport = new StdioClientTransport({ command: "docker", args: [ diff --git a/scripts/mcp-config-smoke.mjs b/scripts/mcp-config-smoke.mjs index 7bc69ed..1922eec 100644 --- a/scripts/mcp-config-smoke.mjs +++ b/scripts/mcp-config-smoke.mjs @@ -72,7 +72,7 @@ try { await request("initialize", { protocolVersion: "2025-06-18", capabilities: {}, - clientInfo: { name: "pc-filebridge-config-smoke", version: "0.2.1" }, + clientInfo: { name: "pc-filebridge-config-smoke", version: "0.2.2" }, }); send({ jsonrpc: "2.0", method: "notifications/initialized", params: {} }); diff --git a/scripts/mcp-smoke.mjs b/scripts/mcp-smoke.mjs index 518059b..ebe498e 100644 --- a/scripts/mcp-smoke.mjs +++ b/scripts/mcp-smoke.mjs @@ -14,7 +14,7 @@ const expectedTools = [ "stat_path", ]; -const client = new Client({ name: "pc-filebridge-smoke", version: "0.2.1" }); +const client = new Client({ name: "pc-filebridge-smoke", version: "0.2.2" }); const pluginRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const fixtureDirectory = await mkdtemp(path.join(pluginRoot, ".pc-filebridge-mcp-smoke-")); const configPath = path.join(fixtureDirectory, "roots.json"); diff --git a/scripts/package-contents-check.mjs b/scripts/package-contents-check.mjs index 6949dd0..66726bb 100644 --- a/scripts/package-contents-check.mjs +++ b/scripts/package-contents-check.mjs @@ -22,6 +22,7 @@ const required = [ "config/tunnel-roles.json", "scripts/Connect-PCFileBridgeRoleTunnel.ps1", "scripts/Connect-PCFileBridgeRoleTunnel-Task.ps1", + "scripts/Install-PCFileBridgeRoleAutostart.ps1", "scripts/Set-PCFileBridgeGate.ps1", "scripts/Start-PCFileBridgeVolumeMonitor.ps1", "scripts/Test-PCFileBridgeRoleConfig.ps1", diff --git a/scripts/run-image-vulnerability-gate.sh b/scripts/run-image-vulnerability-gate.sh old mode 100755 new mode 100644 index c6c8817..4ad9dec --- a/scripts/run-image-vulnerability-gate.sh +++ b/scripts/run-image-vulnerability-gate.sh @@ -2,7 +2,7 @@ set -eu -image="${FILEBRIDGE_IMAGE:-pc-filebridge:0.2.1}" +image="${FILEBRIDGE_IMAGE:-pc-filebridge:0.2.2}" scan_dir="$(mktemp -d "${TMPDIR:-/tmp}/pc-filebridge-scan.XXXXXX")" source_container="" govuln_container="" diff --git a/scripts/tunnel-role-contract.mjs b/scripts/tunnel-role-contract.mjs index 13f23ca..b0c5bb6 100644 --- a/scripts/tunnel-role-contract.mjs +++ b/scripts/tunnel-role-contract.mjs @@ -4,6 +4,41 @@ import { fileURLToPath } from "node:url"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const contract = JSON.parse(await readFile(path.join(root, "config", "tunnel-roles.json"), "utf8")); +const autostartInstallers = await Promise.all( + ["Install-PCFileBridgeAutostart.ps1", "Install-PCFileBridgeRoleAutostart.ps1"].map((name) => + readFile(path.join(root, "scripts", name), "utf8"), + ), +); +const roleTask = await readFile( + path.join(root, "scripts", "Connect-PCFileBridgeRoleTunnel-Task.ps1"), + "utf8", +); +for (const windowsPowerShellScript of [...autostartInstallers, roleTask]) { + if (windowsPowerShellScript.includes("IsPathFullyQualified")) { + throw new Error("Windows PowerShell 5.1 scripts must not call IsPathFullyQualified."); + } +} +const requiredAutostartSettings = [ + "-Compatibility Win8", + "-AllowStartIfOnBatteries", + "-DontStopIfGoingOnBatteries", + "-StartWhenAvailable", + "-DontStopOnIdleEnd", + "-ExecutionTimeLimit ([TimeSpan]::Zero)", + "-MultipleInstances IgnoreNew", + "-RestartCount 5", + "-RestartInterval (New-TimeSpan -Minutes 1)", +]; +for (const autostartInstaller of autostartInstallers) { + for (const setting of requiredAutostartSettings) { + if (!autostartInstaller.includes(setting)) { + throw new Error(`Autostart installer is missing durable setting: ${setting}`); + } + } + if (/ExecutionTimeLimit\s+\(New-TimeSpan/i.test(autostartInstaller)) { + throw new Error("Autostart installer must not impose a finite execution time limit."); + } +} const volumePolicy = (rootIdPrefix) => ({ enabled: true, driveType: 3, diff --git a/src/server.ts b/src/server.ts index 977d4dc..582e471 100644 --- a/src/server.ts +++ b/src/server.ts @@ -28,7 +28,7 @@ async function main(): Promise { const configPath = path.resolve(process.env.FILEBRIDGE_CONFIG ?? path.join(pluginRoot, "config", "roots.local.json")); const policy = await FileBridgePolicy.fromFile(configPath); const server = new McpServer( - { name: "pc-filebridge", version: "0.2.1" }, + { name: "pc-filebridge", version: "0.2.2" }, { instructions: "Create-only filesystem bridge. Use only configured root IDs and relative paths. " + From c7e002e4370584b02fa983a13c80a57312b6ebec Mon Sep 17 00:00:00 2001 From: dzeusking-dev Date: Sun, 30 Aug 2026 22:59:19 +0200 Subject: [PATCH 2/2] fix(windows): canonicalize role casing --- CHANGELOG.md | 1 + scripts/Connect-PCFileBridgeRoleTunnel-Task.ps1 | 1 + scripts/Install-PCFileBridgeRoleAutostart.ps1 | 1 + scripts/Start-PCFileBridgeVolumeMonitor.ps1 | 1 + scripts/tunnel-role-contract.mjs | 9 +++++++++ 5 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 896939d..27639c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Fixed Windows autostart durability: the tunnel task now has no finite execution limit and is not stopped by battery or idle transitions. - Added a Windows PowerShell 5.1-compatible role-bound auto-volume task installer with optional isolated runtime state. +- Canonicalized role casing before task registration and case-sensitive contract lookup. - Added a regression contract for the required long-running Scheduled Task settings. ## 0.2.1 - 2026-08-30 diff --git a/scripts/Connect-PCFileBridgeRoleTunnel-Task.ps1 b/scripts/Connect-PCFileBridgeRoleTunnel-Task.ps1 index 9f95ce1..252bdf8 100644 --- a/scripts/Connect-PCFileBridgeRoleTunnel-Task.ps1 +++ b/scripts/Connect-PCFileBridgeRoleTunnel-Task.ps1 @@ -9,6 +9,7 @@ param( Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' +$Role = $Role.ToLowerInvariant() $previousLocalAppData = $env:LOCALAPPDATA try { diff --git a/scripts/Install-PCFileBridgeRoleAutostart.ps1 b/scripts/Install-PCFileBridgeRoleAutostart.ps1 index c10f760..92bbce5 100644 --- a/scripts/Install-PCFileBridgeRoleAutostart.ps1 +++ b/scripts/Install-PCFileBridgeRoleAutostart.ps1 @@ -10,6 +10,7 @@ param( Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' +$Role = $Role.ToLowerInvariant() if ([string]::IsNullOrWhiteSpace($TaskName)) { $TaskName = if ($Role -ceq 'pc-local') { 'PC FileBridge PC Local' } else { 'PC FileBridge Laptop Local' } diff --git a/scripts/Start-PCFileBridgeVolumeMonitor.ps1 b/scripts/Start-PCFileBridgeVolumeMonitor.ps1 index a20c261..13a9478 100644 --- a/scripts/Start-PCFileBridgeVolumeMonitor.ps1 +++ b/scripts/Start-PCFileBridgeVolumeMonitor.ps1 @@ -19,6 +19,7 @@ param( Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' +$Role = $Role.ToLowerInvariant() $pluginRoot = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot '..') -ErrorAction Stop).Path if ([string]::IsNullOrWhiteSpace($ConnectScriptPath)) { $ConnectScriptPath = Join-Path $PSScriptRoot 'Connect-PCFileBridgeRoleTunnel.ps1' } diff --git a/scripts/tunnel-role-contract.mjs b/scripts/tunnel-role-contract.mjs index b0c5bb6..c474a16 100644 --- a/scripts/tunnel-role-contract.mjs +++ b/scripts/tunnel-role-contract.mjs @@ -13,6 +13,15 @@ const roleTask = await readFile( path.join(root, "scripts", "Connect-PCFileBridgeRoleTunnel-Task.ps1"), "utf8", ); +const volumeMonitor = await readFile( + path.join(root, "scripts", "Start-PCFileBridgeVolumeMonitor.ps1"), + "utf8", +); +for (const roleEntryPoint of [autostartInstallers[1], roleTask, volumeMonitor]) { + if (!roleEntryPoint.includes("$Role = $Role.ToLowerInvariant()")) { + throw new Error("Role entry points must normalize case before case-sensitive contract checks."); + } +} for (const windowsPowerShellScript of [...autostartInstallers, roleTask]) { if (windowsPowerShellScript.includes("IsPathFullyQualified")) { throw new Error("Windows PowerShell 5.1 scripts must not call IsPathFullyQualified.");