Skip to content

Background compiling after updates and a more capable Repair (0.3.24) #378

Background compiling after updates and a more capable Repair (0.3.24)

Background compiling after updates and a more capable Repair (0.3.24) #378

Workflow file for this run

name: Installation and launchers
# Every merge to main publishes the rolling nightly (Windows unsigned, Mac not notarized, Linux AppImage).
# Users get an update only from a stable vX.Y.Z release, published by running this
# workflow with "release" checked; Windows signing then waits for the release environment.
on:
push:
branches: [main]
# A change to the workflows alone does not publish a new Windows build.
paths-ignore:
- '**/*.md'
- '.github/**'
pull_request:
paths-ignore:
- '**/*.md'
workflow_dispatch:
inputs:
release:
description: 'Publish main as the stable release vX.Y.Z named in release_notes.json; users get an update notice'
type: boolean
default: false
defender_baseline:
description: 'Also diagnose the currently published package before rebuilding'
type: boolean
default: false
permissions:
contents: read
# Stable runs have their own group: a later merge must not replace a pending release.
concurrency:
group: freevideo-public-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name == 'workflow_dispatch' && inputs.release && 'stable' || 'build' }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
installation:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
- name: Check every ComfyUI browser module
run: |
for file in web/*.js; do node --input-type=module --check < "$file"; done
node scripts/test_freevideo_entry.mjs
node scripts/test_prompt_guide.mjs
- name: Check CLI, ComfyUI entry and packaged resources
run: |
python scripts/check_installation.py
bash -n setup.sh test.sh freevideo scripts/bootstrap_linux.sh
./setup.sh --help
./test.sh --help
./freevideo --help
- name: Build installable Python packages
run: |
python -m pip install build
python -m build
# Fail a stable run before anyone is asked to approve signing.
- name: Check that this version is not released yet
if: github.event_name == 'workflow_dispatch' && inputs.release
env:
GH_TOKEN: ${{ github.token }}
run: |
version=$(python -c 'import json; print(json.load(open("freevideo_engine/release_notes.json"))["product_version"])')
if gh api "repos/${{ github.repository }}/git/matching-refs/tags/v$version" --jq '.[].ref' | grep -qx "refs/tags/v$version"; then
echo "::error::v$version is already released. Bump product_version and the notes in freevideo_engine/release_notes.json first."
exit 1
fi
echo "Publishing v$version"
windows:
# Build once, after merging; pull requests run the installation checks only.
if: github.event_name != 'pull_request'
needs: [installation]
runs-on: windows-2025
timeout-minutes: 60
permissions:
contents: read
id-token: write
# Only stable releases are signed. They use the release environment, whose reviewers decide who can mint the OIDC token Azure trusts for signing.
environment: ${{ github.event_name == 'workflow_dispatch' && inputs.release && github.repository == 'FlashML-org/FreeVideo' && 'release' || '' }}
env:
PYTHONUTF8: '1'
PYTHONIOENCODING: utf-8
FREEVIDEO_RELEASE_TRACK: ${{ github.event_name == 'workflow_dispatch' && inputs.release && 'stable' || 'nightly' }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
cache: pip
cache-dependency-path: constraints/windows-launcher.txt
- run: python scripts/check_installation.py
- run: python -m pip install -r constraints/windows-launcher.txt
- name: Diagnose previous release with real-time Defender
if: github.event_name == 'workflow_dispatch' && inputs.defender_baseline
env:
GH_TOKEN: ${{ github.token }}
shell: pwsh
run: |
gh release download windows-preview --repo FlashML-org/FreeVideo --pattern FreeVideo.exe --dir build/previous-release
if ($LASTEXITCODE -ne 0) { throw 'Could not retrieve the previous release for comparison' }
python scripts/scan_windows_release.py --file build/previous-release/FreeVideo.exe --report antivirus/previous-release.json
$baselineExit = $LASTEXITCODE
if (Test-Path antivirus/previous-release.json) { Get-Content antivirus/previous-release.json -Raw }
Write-Output "Previous-release diagnosis exit code: $baselineExit"
# The old package is evidence, not the replacement candidate. The
# new final bytes must pass the mandatory protection gate below.
exit 0
- name: Azure login (OIDC)
if: github.event_name == 'workflow_dispatch' && inputs.release && github.repository == 'FlashML-org/FreeVideo'
uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
# The GitHub OIDC assertion behind the login expires after 5 minutes (azure-cli#29063). A signing token fetched now is cached by az for the whole job.
- name: Fetch the signing token
if: github.event_name == 'workflow_dispatch' && inputs.release && github.repository == 'FlashML-org/FreeVideo'
run: |
az account get-access-token --scope https://codesigning.azure.net/.default --output none
if ($LASTEXITCODE -ne 0) { throw 'Could not get a codesigning.azure.net token' }
# The launcher runs these scripts from its unpacked tree, where the executable's signature does not cover them. Sign the checkout's copies before the build bundles them.
- name: Sign bundled PowerShell scripts
if: github.event_name == 'workflow_dispatch' && inputs.release && github.repository == 'FlashML-org/FreeVideo'
uses: azure/trusted-signing-action@c7ab2a863ab5f9a846ddb8265964877ef296ee82 # v2.0.0
with:
endpoint: https://eus.codesigning.azure.net/
signing-account-name: flashmlsigning
certificate-profile-name: freetoken-public
files: |
${{ github.workspace }}/freevideo.ps1
${{ github.workspace }}/setup.ps1
${{ github.workspace }}/test.ps1
${{ github.workspace }}/freevideo_engine/windows_bootstrap.ps1
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
- name: Build Windows launcher
run: |
python scripts/build_windows.py --onefolder --out build/windows-folder
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
python scripts/build_windows.py --out build/windows-exe
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Sign Windows launchers
if: github.event_name == 'workflow_dispatch' && inputs.release && github.repository == 'FlashML-org/FreeVideo'
uses: azure/trusted-signing-action@c7ab2a863ab5f9a846ddb8265964877ef296ee82 # v2.0.0
with:
endpoint: https://eus.codesigning.azure.net/
signing-account-name: flashmlsigning
certificate-profile-name: freetoken-public
files: |
${{ github.workspace }}/build/windows-exe/dist/FreeVideo.exe
${{ github.workspace }}/build/windows-folder/dist/FreeVideo/FreeVideo.exe
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
# Check the copies that ship: the folder layout exposes every bundled script on disk, and the one-file launcher is built from the same inputs.
- name: Verify signatures and rebuild the folder ZIP around the signed launcher
if: github.event_name == 'workflow_dispatch' && inputs.release && github.repository == 'FlashML-org/FreeVideo'
shell: pwsh
run: |
$scripts = @(Get-ChildItem build/windows-folder/dist -Recurse -Filter *.ps1 | ForEach-Object FullName)
"bundled scripts: $($scripts.Count)"
./scripts/verify_windows_release.ps1 -FolderDist build/windows-folder/dist -Signed ($scripts + 'build/windows-exe/dist/FreeVideo.exe' + 'build/windows-folder/dist/FreeVideo/FreeVideo.exe')
- name: Hash final Windows launchers
shell: pwsh
run: |
python -c "from pathlib import Path; import hashlib; p=Path('build/windows-exe/dist'); (p/'SHA256SUMS.txt').write_text(''.join(hashlib.sha256(f.read_bytes()).hexdigest()+' '+f.name+'\n' for f in (p/'FreeVideo.exe',)))"
# Keep the full report in the job log; a blocked or inconclusive scan
# must stop publication instead of making a suspicious build public.
# The folder layout is only checked here, not published.
- name: Open both packaged launchers and scan with real-time Defender
timeout-minutes: 20
shell: pwsh
run: |
$scripts = @(Get-ChildItem build/windows-folder/dist -Recurse -Filter *.ps1 | ForEach-Object { '--script', $_.FullName })
python scripts/scan_windows_release.py --file build/windows-exe/dist/FreeVideo.exe --file build/windows-folder/dist/FreeVideo/FreeVideo.exe --file build/windows-folder/dist/FreeVideo-Windows-folder.zip @scripts --verify-launches build/windows-exe/dist/FreeVideo.exe build/windows-folder/dist/FreeVideo-Windows-folder.zip --report antivirus/report.json
$scanExit = $LASTEXITCODE
if (Test-Path antivirus/report.json) { Get-Content antivirus/report.json -Raw }
exit $scanExit
- name: Print retained protection and startup checks on failure
if: failure()
shell: pwsh
run: |
python -c "import json; from scripts.scan_windows_release import threat_history; print(json.dumps(threat_history(), indent=2))"
Get-ChildItem antivirus -File -ErrorAction SilentlyContinue | ForEach-Object { Write-Output $_.Name; Get-Content $_.FullName -Tail 100 }
Get-ChildItem (Join-Path $env:USERPROFILE 'Downloads/FreeVideo-release-check-*') -Recurse -File -ErrorAction SilentlyContinue |
Where-Object { $_.Name -eq 'smoke.json' } | ForEach-Object { Get-Content $_.FullName -Raw }
- name: Keep the Windows launcher for publishing
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: windows-launcher
path: |
build/windows-exe/dist/FreeVideo.exe
build/windows-exe/dist/launcher-build.json
if-no-files-found: error
retention-days: 1
macos:
if: github.event_name != 'pull_request'
needs: [installation]
runs-on: macos-15
timeout-minutes: 30
permissions:
contents: read
env:
FREEVIDEO_RELEASE_TRACK: ${{ github.event_name == 'workflow_dispatch' && inputs.release && 'stable' || 'nightly' }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
architecture: arm64
cache: pip
cache-dependency-path: constraints/macos-launcher.txt
- name: Check installation and build the Apple Silicon launcher
run: |
python -c "import platform; assert platform.machine() == 'arm64'"
python scripts/check_installation.py
python -m pip install -r constraints/macos-launcher.txt
python scripts/build_macos.py --out build/macos
- name: Verify the disk image and native startup
env:
FREEVIDEO_LAUNCHER_HOME: ${{ runner.temp }}/freevideo-launcher-state
run: |
python scripts/verify_macos_build.py --build build/macos --out build/macos-verification
build/macos/applications.noindex/FreeVideo.app/Contents/MacOS/FreeVideo --smoke-test "$RUNNER_TEMP/freevideo-smoke"
python -c "import json, os; from pathlib import Path; value=json.loads((Path(os.environ['RUNNER_TEMP'])/'freevideo-smoke/smoke.json').read_text()); assert value['success'] and not value['model_modules_imported']"
# Artifacts keep paths below the files' common folder; stage both side by side.
- name: Stage the Mac launcher for publishing
run: |
mkdir -p build/macos-publish
cp build/macos/dist/FreeVideo-Mac-arm64.dmg build/macos/launcher-build.json build/macos-publish/
- name: Keep the Mac launcher for publishing
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: macos-launcher
path: build/macos-publish/
if-no-files-found: error
retention-days: 1
- name: Keep failure diagnostics
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: macos-build-failure
path: |
build/macos/build-result.json
build/macos-verification/result.json
${{ runner.temp }}/freevideo-smoke/smoke.json
if-no-files-found: ignore
retention-days: 1
linux:
if: github.event_name != 'pull_request'
needs: [installation]
runs-on: ubuntu-24.04
timeout-minutes: 40
permissions:
contents: read
# glibc 2.34, the oldest PySide6 6.11 supports: Ubuntu 22.04, Debian 12, Fedora 36, RHEL 9 and newer.
container: quay.io/pypa/manylinux_2_34_x86_64:2026.10.03-1@sha256:e4ca528d41ab66b3654110295d7a2c7bfa599ddbb6efb269ec21f7f73b88f1b6
env:
FREEVIDEO_RELEASE_TRACK: ${{ github.event_name == 'workflow_dispatch' && inputs.release && 'stable' || 'nightly' }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install the libraries Qt loads, and those bundled for desktops that lack them
run: |
dnf install -y epel-release
dnf install -y xcb-util-cursor xcb-util-image xcb-util-keysyms xcb-util-renderutil xcb-util-wm xcb-util \
libxkbcommon-x11 libxkbcommon mesa-libGL mesa-libEGL fontconfig freetype dbus-libs xorg-x11-server-Xvfb
- name: Prepare Python 3.12 with a shared libpython
run: |
/opt/python/cp312-cp312/bin/python -m pip install uv==0.9.0
/opt/python/cp312-cp312/bin/uv python install 3.12
/opt/python/cp312-cp312/bin/uv venv /opt/launcher --python 3.12
/opt/python/cp312-cp312/bin/uv pip install --python /opt/launcher/bin/python -r constraints/linux-launcher.txt
- name: Check installation and build the Linux launcher
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
/opt/launcher/bin/python scripts/check_installation.py
/opt/launcher/bin/python scripts/build_linux.py --out build/linux
- name: Start the AppImage on a virtual display
env:
FREEVIDEO_LAUNCHER_HOME: ${{ runner.temp }}/freevideo-launcher-state
APPIMAGE_EXTRACT_AND_RUN: '1'
run: |
Xvfb :99 -screen 0 1280x800x24 &
export DISPLAY=:99
sleep 2
build/linux/release/FreeVideo-Linux-x86_64.AppImage --smoke-test "$RUNNER_TEMP/freevideo-smoke"
/opt/launcher/bin/python -c "import json, os; from pathlib import Path; value=json.loads((Path(os.environ['RUNNER_TEMP'])/'freevideo-smoke/smoke.json').read_text()); assert value['success'] and not value['model_modules_imported'], value"
- name: Stage the Linux launcher for publishing
run: |
mkdir -p build/linux-publish
cp build/linux/release/FreeVideo-Linux-x86_64.AppImage build/linux/release/launcher-build.json build/linux-publish/
- name: Keep the Linux launcher for publishing
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: linux-launcher
path: build/linux-publish/
if-no-files-found: error
retention-days: 1
- name: Keep failure diagnostics
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: linux-build-failure
path: |
${{ runner.temp }}/freevideo-smoke/smoke.json
${{ runner.temp }}/freevideo-smoke/launcher.png
if-no-files-found: ignore
retention-days: 1
publish:
# Release assets use Releases storage, not the Actions artifact quota.
if: github.event_name != 'pull_request' && github.repository == 'FlashML-org/FreeVideo' && github.ref == 'refs/heads/main'
needs: [windows, macos, linux]
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: windows-launcher
path: build/windows
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: macos-launcher
path: build/macos
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: linux-launcher
path: build/linux
- name: Publish the release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: >-
python scripts/publish_release.py --track ${{ github.event_name == 'workflow_dispatch' && inputs.release && 'stable' || 'nightly' }}
--windows build/windows --macos build/macos --linux build/linux --sha ${{ github.sha }}