Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/create-release-tag.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Create release tag

on:
workflow_dispatch:
inputs:
tag:
description: Immutable semantic release tag, for example v0.2.2
required: true
type: string

permissions:
actions: write
contents: write

concurrency:
group: create-release-tag
cancel-in-progress: false

jobs:
create:
name: Verify main and create immutable tag
runs-on: ubuntu-latest
timeout-minutes: 10
environment:
name: github-release
steps:
- name: Check out current main
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: main
fetch-depth: 0

- name: Verify, push tag, and dispatch verified release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.tag }}
shell: bash
run: |
set -euo pipefail
[[ "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
package_version="$(node --input-type=commonjs -p "require('./package.json').version")"
[[ "${RELEASE_TAG}" == "v${package_version}" ]]

release_sha="$(git rev-parse HEAD)"
git fetch --no-tags --force origin main:refs/remotes/origin/main
[[ "${release_sha}" == "$(git rev-parse refs/remotes/origin/main)" ]]

if git ls-remote --exit-code --tags origin "refs/tags/${RELEASE_TAG}" >/dev/null 2>&1; then
echo "Tag ${RELEASE_TAG} already exists." >&2
exit 1
fi

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "${RELEASE_TAG}" "${release_sha}" -m "PC FileBridge ${RELEASE_TAG}"
git push origin "refs/tags/${RELEASE_TAG}"
Comment thread
dzeusking-dev marked this conversation as resolved.
gh workflow run release.yml --ref main -f "tag=${RELEASE_TAG}"
65 changes: 40 additions & 25 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,23 @@ on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: Existing immutable semantic release tag, for example v0.2.2
required: true
type: string

permissions:
contents: read

concurrency:
group: release-${{ github.ref }}
group: release-${{ inputs.tag || github.ref }}
cancel-in-progress: false

env:
RELEASE_TAG: ${{ inputs.tag || github.ref_name }}

jobs:
build:
name: Verify and build immutable artifacts
Expand All @@ -23,6 +32,7 @@ jobs:
- name: Check out tagged source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.tag || github.ref }}
fetch-depth: 0
persist-credentials: false

Expand All @@ -36,19 +46,21 @@ jobs:
shell: bash
run: |
set -euo pipefail
[[ "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
package_version="$(node --input-type=commonjs -p "require('./package.json').version")"
expected_tag="v${package_version}"
if [[ "${GITHUB_REF_NAME}" != "${expected_tag}" ]]; then
echo "Release tag ${GITHUB_REF_NAME} does not match package version ${package_version}." >&2
if [[ "${RELEASE_TAG}" != "${expected_tag}" ]]; then
echo "Release tag ${RELEASE_TAG} does not match package version ${package_version}." >&2
exit 1
fi
if [[ "$(git rev-parse "${GITHUB_REF_NAME}^{commit}")" != "${GITHUB_SHA}" ]]; then
release_sha="$(git rev-parse "${RELEASE_TAG}^{commit}")"
if [[ "${GITHUB_EVENT_NAME}" == "push" && "${release_sha}" != "${GITHUB_SHA}" ]]; then
echo "The release tag does not resolve to the workflow commit." >&2
exit 1
fi
git fetch --no-tags --force origin main:refs/remotes/origin/main
if ! git merge-base --is-ancestor "${GITHUB_SHA}" refs/remotes/origin/main; then
echo "Tagged commit ${GITHUB_SHA} is not on origin/main." >&2
if ! git merge-base --is-ancestor "${release_sha}" refs/remotes/origin/main; then
echo "Tagged commit ${release_sha} is not on origin/main." >&2
exit 1
fi

Expand All @@ -72,17 +84,18 @@ jobs:
rm -rf release
mkdir release

runtime_name="pc-filebridge-${GITHUB_REF_NAME}-runtime-npm.tgz"
source_name="pc-filebridge-${GITHUB_REF_NAME}-source.tar.gz"
sbom_name="pc-filebridge-${GITHUB_REF_NAME}-sbom.cdx.json"
runtime_name="pc-filebridge-${RELEASE_TAG}-runtime-npm.tgz"
source_name="pc-filebridge-${RELEASE_TAG}-source.tar.gz"
sbom_name="pc-filebridge-${RELEASE_TAG}-sbom.cdx.json"

package_file="$(npm pack --ignore-scripts --silent)"
mv -- "${package_file}" "release/${runtime_name}"
release_sha="$(git rev-parse "${RELEASE_TAG}^{commit}")"
git archive \
--format=tar.gz \
--prefix="pc-filebridge-${GITHUB_REF_NAME}-source/" \
--prefix="pc-filebridge-${RELEASE_TAG}-source/" \
--output="release/${source_name}" \
"${GITHUB_SHA}"
"${release_sha}"
npm sbom --sbom-format cyclonedx > "release/${sbom_name}"

(
Expand All @@ -95,12 +108,12 @@ jobs:
run: >-
node scripts/verify-release-artifact.mjs
--release-dir release
--tag "${GITHUB_REF_NAME}"
--tag "${RELEASE_TAG}"

- name: Preserve verified release bundle
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: pc-filebridge-release-${{ github.ref_name }}
name: pc-filebridge-release-${{ env.RELEASE_TAG }}
path: release/
if-no-files-found: error
retention-days: 90
Expand All @@ -120,23 +133,25 @@ jobs:
- name: Re-check release identity
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.tag || github.ref }}
fetch-depth: 0
persist-credentials: false

- name: Verify tag still resolves to an origin/main commit
shell: bash
run: |
set -euo pipefail
[[ "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
package_version="$(node --input-type=commonjs -p "require('./package.json').version")"
[[ "${GITHUB_REF_NAME}" == "v${package_version}" ]]
[[ "$(git rev-parse "${GITHUB_REF_NAME}^{commit}")" == "${GITHUB_SHA}" ]]
[[ "${RELEASE_TAG}" == "v${package_version}" ]]
release_sha="$(git rev-parse "${RELEASE_TAG}^{commit}")"
git fetch --no-tags --force origin main:refs/remotes/origin/main
git merge-base --is-ancestor "${GITHUB_SHA}" refs/remotes/origin/main
git merge-base --is-ancestor "${release_sha}" refs/remotes/origin/main

- name: Download verified release bundle
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: pc-filebridge-release-${{ github.ref_name }}
name: pc-filebridge-release-${{ env.RELEASE_TAG }}
path: release

- name: Re-verify downloaded checksums
Expand All @@ -152,9 +167,9 @@ jobs:
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: |
release/pc-filebridge-${{ github.ref_name }}-runtime-npm.tgz
release/pc-filebridge-${{ github.ref_name }}-source.tar.gz
release/pc-filebridge-${{ github.ref_name }}-sbom.cdx.json
release/pc-filebridge-${{ env.RELEASE_TAG }}-runtime-npm.tgz
release/pc-filebridge-${{ env.RELEASE_TAG }}-source.tar.gz
release/pc-filebridge-${{ env.RELEASE_TAG }}-sbom.cdx.json
release/SHA256SUMS

- name: Create GitHub release
Expand All @@ -163,11 +178,11 @@ jobs:
shell: bash
run: |
set -euo pipefail
gh release create "${GITHUB_REF_NAME}" \
"release/pc-filebridge-${GITHUB_REF_NAME}-runtime-npm.tgz" \
"release/pc-filebridge-${GITHUB_REF_NAME}-source.tar.gz" \
"release/pc-filebridge-${GITHUB_REF_NAME}-sbom.cdx.json" \
gh release create "${RELEASE_TAG}" \
"release/pc-filebridge-${RELEASE_TAG}-runtime-npm.tgz" \
"release/pc-filebridge-${RELEASE_TAG}-source.tar.gz" \
"release/pc-filebridge-${RELEASE_TAG}-sbom.cdx.json" \
"release/SHA256SUMS" \
--verify-tag \
--generate-notes \
--title "PC FileBridge ${GITHUB_REF_NAME}"
--title "PC FileBridge ${RELEASE_TAG}"
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,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 five-minute recovery trigger so externally interrupted runtimes restart without another logon.
- Added a regression contract for the required long-running Scheduled Task settings.

## 0.2.1 - 2026-08-30
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ For role-bound automatic volume discovery, install the dedicated per-user task:
.\scripts\Install-PCFileBridgeRoleAutostart.ps1 -Role pc-local
```

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.
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, do not stop them on battery or idle transitions, and add a five-minute recovery trigger for externally interrupted runtimes; they refuse to replace an existing task.

## Full-drive mode

Expand Down
5 changes: 3 additions & 2 deletions scripts/Install-PCFileBridgeAutostart.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ $connectScript = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot 'Connect-PC
$account = [Security.Principal.WindowsIdentity]::GetCurrent().Name
$arguments = '-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "{0}" -TunnelId "{1}"' -f $connectScript, $TunnelId
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $arguments -WorkingDirectory ([IO.Path]::GetDirectoryName($connectScript))
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $account
$logonTrigger = New-ScheduledTaskTrigger -AtLogOn -User $account
$watchdogTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) -RepetitionInterval (New-TimeSpan -Minutes 5)
$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 private OpenAI Secure MCP Tunnel for PC FileBridge after user logon.' | Out-Null
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger @($logonTrigger, $watchdogTrigger) -Principal $principal -Settings $settings -Description 'Starts the private OpenAI Secure MCP Tunnel for PC FileBridge after user logon and retries stopped runtimes every five minutes.' | Out-Null
Write-Output "AUTOSTART_INSTALLED task=$TaskName overwrite=false"
5 changes: 3 additions & 2 deletions scripts/Install-PCFileBridgeRoleAutostart.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ if (-not [string]::IsNullOrWhiteSpace($RuntimeLocalAppData)) {
}
$arguments = $argumentParts -join ' '
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $arguments -WorkingDirectory ([IO.Path]::GetDirectoryName($taskScript))
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $account
$logonTrigger = New-ScheduledTaskTrigger -AtLogOn -User $account
$watchdogTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) -RepetitionInterval (New-TimeSpan -Minutes 5)
$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
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger @($logonTrigger, $watchdogTrigger) -Principal $principal -Settings $settings -Description "Starts the $Role PC FileBridge auto-volume monitor after user logon and retries stopped runtimes every five minutes." | Out-Null
Write-Output "ROLE_AUTOSTART_INSTALLED task=$TaskName role=$Role overwrite=false"
6 changes: 6 additions & 0 deletions scripts/tunnel-role-contract.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ for (const autostartInstaller of autostartInstallers) {
if (/ExecutionTimeLimit\s+\(New-TimeSpan/i.test(autostartInstaller)) {
throw new Error("Autostart installer must not impose a finite execution time limit.");
}
if (!autostartInstaller.includes("-RepetitionInterval (New-TimeSpan -Minutes 5)")) {
throw new Error("Autostart installer must include the five-minute recovery trigger.");
}
if (!autostartInstaller.includes("-Trigger @($logonTrigger, $watchdogTrigger)")) {
throw new Error("Autostart installer must register both logon and recovery triggers.");
}
}
const volumePolicy = (rootIdPrefix) => ({
enabled: true,
Expand Down