mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 19:46:16 -08:00
fix(packaging): harden Linux frame-copy releases
This commit is contained in:
1 parent
a651dd69a4
commit
3236a17e99
41 files changed
+6028
-142
No files matched your search
@@ -43,7 +43,7 @@ jobs:
|
||||
echo 'meson=1.7.2'
|
||||
} > "${RUNNER_TEMP}/linux-runtime-toolchain.txt"
|
||||
TOOLCHAIN_SHA256="$(sha256sum "${RUNNER_TEMP}/linux-runtime-toolchain.txt" | cut -d ' ' -f 1)"
|
||||
SOURCE_SHA256="${{ hashFiles('tools/embedded-mpv/build-linux-runtime.cjs', 'tools/embedded-mpv/build-linux-runtime.mjs', 'tools/embedded-mpv/generate-linux-runtime-notices.cjs', 'tools/embedded-mpv/linux-runtime-manifest.cjs', 'tools/embedded-mpv/stage-runtime.mjs', 'tools/packaging/prepare-linux-runtime-source-snapshot.cjs') }}"
|
||||
SOURCE_SHA256="${{ hashFiles('tools/embedded-mpv/build-linux-runtime.cjs', 'tools/embedded-mpv/build-linux-runtime.mjs', 'tools/embedded-mpv/generate-linux-runtime-notices.cjs', 'tools/embedded-mpv/linux-runtime-manifest.cjs', 'tools/embedded-mpv/linux-source-archive-contract.cjs', 'tools/embedded-mpv/stage-runtime.mjs', 'tools/packaging/prepare-linux-runtime-source-snapshot.cjs') }}"
|
||||
echo "toolchain-sha256=${TOOLCHAIN_SHA256}" >> "${GITHUB_OUTPUT}"
|
||||
echo "key=linux-frame-copy-runtime-v5-ubuntu-22.04-${TOOLCHAIN_SHA256}-${SOURCE_SHA256}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
@@ -162,6 +162,7 @@ jobs:
|
||||
tools/embedded-mpv/build-linux-runtime.mjs \
|
||||
tools/embedded-mpv/generate-linux-runtime-notices.cjs \
|
||||
tools/embedded-mpv/linux-runtime-manifest.cjs \
|
||||
tools/embedded-mpv/linux-source-archive-contract.cjs \
|
||||
tools/embedded-mpv/stage-runtime.mjs \
|
||||
tools/packaging/prepare-linux-runtime-source-snapshot.cjs \
|
||||
"${SOURCE_BUNDLE_ROOT}/tooling/"
|
||||
@@ -173,6 +174,10 @@ jobs:
|
||||
const crypto = require('node:crypto');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const {
|
||||
EXPECTED_LIBPLACEBO_V7_360_1_SOURCE_SNAPSHOT_SHA256,
|
||||
validateLinuxRuntimeSourceSnapshot,
|
||||
} = require('./tools/packaging/prepare-linux-runtime-source-snapshot.cjs');
|
||||
|
||||
const manifest = JSON.parse(
|
||||
fs.readFileSync(path.join(process.env.RUNTIME_ROOT, 'runtime-manifest.json'), 'utf8')
|
||||
@@ -211,6 +216,10 @@ jobs:
|
||||
) {
|
||||
throw new Error('Prepared libplacebo source identity does not match the runtime manifest.');
|
||||
}
|
||||
validateLinuxRuntimeSourceSnapshot(libplacebo.sourceSnapshot, {
|
||||
expectedSha256:
|
||||
EXPECTED_LIBPLACEBO_V7_360_1_SOURCE_SNAPSHOT_SHA256,
|
||||
});
|
||||
|
||||
const notices = JSON.parse(
|
||||
fs.readFileSync(
|
||||
@@ -232,7 +241,7 @@ jobs:
|
||||
path.join(process.env.SOURCE_BUNDLE_ROOT, 'metadata', 'source-index.json'),
|
||||
`${JSON.stringify(
|
||||
{
|
||||
schemaVersion: 2,
|
||||
schemaVersion: 3,
|
||||
repositoryRevision,
|
||||
sourcePackages: manifest.packages,
|
||||
archives,
|
||||
@@ -268,6 +277,12 @@ jobs:
|
||||
--file dist/compliance/linux-frame-copy-runtime-sources.tar.xz \
|
||||
--directory "${SOURCE_BUNDLE_ROOT}" \
|
||||
.
|
||||
rm -f "${RUNTIME_ROOT}/source-archive-binding.json"
|
||||
node tools/embedded-mpv/linux-source-archive-contract.cjs create \
|
||||
--archive dist/compliance/linux-frame-copy-runtime-sources.tar.xz \
|
||||
--repository-revision "$(git rev-parse HEAD)" \
|
||||
--output "${RUNTIME_ROOT}/source-archive-binding.json"
|
||||
test -s "${RUNTIME_ROOT}/source-archive-binding.json"
|
||||
|
||||
- name: Upload staged Linux runtime
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -569,7 +584,7 @@ jobs:
|
||||
find dist/apps/electron-backend/native -maxdepth 1 \( -name 'mpv-2.dll' -o -name 'libmpv-2.dll' -o -name 'mpv.dll' -o -name 'libmpv.dll' \) -print -quit | grep -q .
|
||||
;;
|
||||
linux)
|
||||
node -e "const manifest = require('./dist/apps/electron-backend/native/embedded-mpv-runtime.json'); if (manifest.origin !== 'linux-frame-copy-build' || manifest.sourceRuntimeValidated !== true) { throw new Error('Linux embedded MPV build manifest must describe the validated source runtime.'); }"
|
||||
node -e "const { execFileSync } = require('node:child_process'); const manifest = require('./dist/apps/electron-backend/native/embedded-mpv-runtime.json'); const revision = execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).trim(); if (manifest.origin !== 'linux-frame-copy-build' || manifest.sourceRuntimeValidated !== true || manifest.sourceArchive?.schemaVersion !== 1 || manifest.sourceArchive?.name !== 'linux-frame-copy-runtime-sources.tar.xz' || !/^[a-f0-9]{64}$/.test(manifest.sourceArchive?.sha256 ?? '') || manifest.sourceArchive?.repositoryRevision !== revision) { throw new Error('Linux embedded MPV build manifest must describe the validated source runtime and exact source archive.'); }"
|
||||
test -f dist/apps/electron-backend/native/lib/libmpv.so.2
|
||||
test -f dist/apps/electron-backend/native/iptvnator_mpv_helper
|
||||
test -f dist/apps/electron-backend/native/embedded_mpv_frame_reader.node
|
||||
|
||||
@@ -9,23 +9,38 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish-snap:
|
||||
name: Publish public-release Snap to edge
|
||||
verify-snap:
|
||||
name: Verify public-release Snap assets
|
||||
if: ${{ startsWith(github.event.release.tag_name, 'v') && github.event.release.draft == false }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.snapcraft_token }}
|
||||
SOURCE_ARCHIVE_NAME: linux-frame-copy-runtime-sources.tar.xz
|
||||
outputs:
|
||||
receipt-sha256: ${{ steps.bind-transfer.outputs.receipt-sha256 }}
|
||||
|
||||
steps:
|
||||
- name: Checkout released tooling
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
with:
|
||||
ref: ${{ github.event.release.tag_name }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install release source verifier
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends -y \
|
||||
binutils \
|
||||
squashfs-tools \
|
||||
xz-utils
|
||||
|
||||
- name: Select exact public release assets
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -40,6 +55,8 @@ jobs:
|
||||
|
||||
- name: Download exact public release assets
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -61,26 +78,192 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
VERIFIED_ASSET_STAGING="${RUNNER_TEMP}/verified-snap-release-assets"
|
||||
SEALED_ASSET_PARENT="/var/lib/iptvnator-snap-release"
|
||||
SEALED_ASSET_DIRECTORY="${SEALED_ASSET_PARENT}/assets"
|
||||
test -s "${RUNNER_TEMP}/snap-release-downloads/${SOURCE_ARCHIVE_NAME}"
|
||||
test ! -e "${VERIFIED_ASSET_STAGING}"
|
||||
sudo test ! -e "${SEALED_ASSET_PARENT}"
|
||||
node tools/packaging/release-snap-assets.cjs verify \
|
||||
--manifest "${RUNNER_TEMP}/selected-snap-release-assets.json" \
|
||||
--directory "${RUNNER_TEMP}/snap-release-downloads"
|
||||
--directory "${RUNNER_TEMP}/snap-release-downloads" \
|
||||
--repository-revision "$(git rev-parse HEAD)" \
|
||||
--verified-directory "${VERIFIED_ASSET_STAGING}"
|
||||
sudo install -d -m 0700 -o root -g root "${SEALED_ASSET_PARENT}"
|
||||
sudo mv "${VERIFIED_ASSET_STAGING}" "${SEALED_ASSET_DIRECTORY}"
|
||||
sudo chown -R root:root "${SEALED_ASSET_DIRECTORY}"
|
||||
sudo find "${SEALED_ASSET_DIRECTORY}" -type d -exec chmod 0555 {} +
|
||||
sudo find "${SEALED_ASSET_DIRECTORY}" -type f -exec chmod 0444 {} +
|
||||
sudo chmod 0555 "${SEALED_ASSET_PARENT}"
|
||||
|
||||
- name: Setup Snapcraft
|
||||
uses: samuelmeuli/action-snapcraft@v3
|
||||
|
||||
- name: Publish all public-release snaps to edge
|
||||
- name: Reverify sealed public release assets
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
node -e \
|
||||
"const fs=require('node:fs'); const selected=JSON.parse(fs.readFileSync(process.argv[1],'utf8')); for (const asset of selected.snapAssets) console.log(asset.name);" \
|
||||
"${RUNNER_TEMP}/selected-snap-release-assets.json" |
|
||||
while IFS= read -r SNAP_NAME; do
|
||||
SNAP_FILE="${RUNNER_TEMP}/snap-release-downloads/${SNAP_NAME}"
|
||||
echo "Publishing public release asset: ${SNAP_NAME}"
|
||||
# Candidate/stable promotion is manual after installed-Snap frame-copy and missing-runtime fallback smoke.
|
||||
# GitHub Actions never promotes automatically.
|
||||
snapcraft upload --release=edge "${SNAP_FILE}"
|
||||
done
|
||||
VERIFIED_ASSET_DIRECTORY="/var/lib/iptvnator-snap-release/assets"
|
||||
node tools/packaging/release-snap-assets.cjs verify-sealed \
|
||||
--manifest "${RUNNER_TEMP}/selected-snap-release-assets.json" \
|
||||
--directory "${VERIFIED_ASSET_DIRECTORY}" \
|
||||
--receipt "${VERIFIED_ASSET_DIRECTORY}/verified-release-assets.json" \
|
||||
--repository-revision "$(git rev-parse HEAD)"
|
||||
|
||||
- name: Bind verified release transfer
|
||||
id: bind-transfer
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
RECEIPT_PATH="/var/lib/iptvnator-snap-release/assets/verified-release-assets.json"
|
||||
RECEIPT_RECORD="$(/usr/bin/sha256sum --binary "${RECEIPT_PATH}")"
|
||||
RECEIPT_SHA256="${RECEIPT_RECORD%% *}"
|
||||
[[ "${RECEIPT_SHA256}" =~ ^[a-f0-9]{64}$ ]]
|
||||
printf 'receipt-sha256=%s\n' "${RECEIPT_SHA256}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Transfer verified release assets
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
with:
|
||||
name: verified-snap-release-assets
|
||||
path: /var/lib/iptvnator-snap-release/assets
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
compression-level: 0
|
||||
include-hidden-files: true
|
||||
|
||||
publish-snap:
|
||||
name: Publish verified public-release Snap to edge
|
||||
needs: verify-snap
|
||||
if: ${{ needs.verify-snap.result == 'success' && startsWith(github.event.release.tag_name, 'v') && github.event.release.draft == false }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- name: Download verified release assets
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
with:
|
||||
name: verified-snap-release-assets
|
||||
path: ${{ runner.temp }}/verified-snap-release-assets
|
||||
|
||||
- name: Seal transferred public release assets
|
||||
shell: bash
|
||||
env:
|
||||
EXPECTED_RECEIPT_SHA256: ${{ needs.verify-snap.outputs.receipt-sha256 }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
TRANSFERRED_ASSET_DIRECTORY="${RUNNER_TEMP}/verified-snap-release-assets"
|
||||
SEALED_ASSET_PARENT="/var/lib/iptvnator-snap-release"
|
||||
SEALED_ASSET_DIRECTORY="${SEALED_ASSET_PARENT}/assets"
|
||||
test -d "${TRANSFERRED_ASSET_DIRECTORY}"
|
||||
test ! -L "${TRANSFERRED_ASSET_DIRECTORY}"
|
||||
shopt -s nullglob dotglob
|
||||
TRANSFERRED_FILES=("${TRANSFERRED_ASSET_DIRECTORY}"/*)
|
||||
TRANSFERRED_SNAPS=("${TRANSFERRED_ASSET_DIRECTORY}"/*.snap)
|
||||
test "${#TRANSFERRED_SNAPS[@]}" -gt 0
|
||||
test "${#TRANSFERRED_FILES[@]}" -eq "$(( ${#TRANSFERRED_SNAPS[@]} + 2 ))"
|
||||
test -f "${TRANSFERRED_ASSET_DIRECTORY}/linux-frame-copy-runtime-sources.tar.xz"
|
||||
test ! -L "${TRANSFERRED_ASSET_DIRECTORY}/linux-frame-copy-runtime-sources.tar.xz"
|
||||
test -f "${TRANSFERRED_ASSET_DIRECTORY}/verified-release-assets.json"
|
||||
test ! -L "${TRANSFERRED_ASSET_DIRECTORY}/verified-release-assets.json"
|
||||
for ASSET_FILE in "${TRANSFERRED_FILES[@]}"; do
|
||||
test -f "${ASSET_FILE}"
|
||||
test ! -L "${ASSET_FILE}"
|
||||
done
|
||||
RECEIPT_PATH="${TRANSFERRED_ASSET_DIRECTORY}/verified-release-assets.json"
|
||||
RECEIPT_RECORD="$(/usr/bin/sha256sum --binary "${RECEIPT_PATH}")"
|
||||
ACTUAL_RECEIPT_SHA256="${RECEIPT_RECORD%% *}"
|
||||
[[ "${EXPECTED_RECEIPT_SHA256}" =~ ^[a-f0-9]{64}$ ]]
|
||||
test "${ACTUAL_RECEIPT_SHA256}" = "${EXPECTED_RECEIPT_SHA256}"
|
||||
/usr/bin/jq --exit-status '
|
||||
type == "object" and
|
||||
(keys == ["assets", "repositoryRevision", "schemaVersion"]) and
|
||||
(.schemaVersion == 1) and
|
||||
(.repositoryRevision |
|
||||
type == "string" and test("^[a-f0-9]{40,64}$")) and
|
||||
(.assets | type == "array" and length >= 2) and
|
||||
(.assets | all(.[];
|
||||
type == "object" and
|
||||
(keys == ["id", "name", "sha256", "size"]) and
|
||||
(.id |
|
||||
type == "number" and . > 0 and
|
||||
. <= 9007199254740991 and . == floor) and
|
||||
(.name |
|
||||
type == "string" and length > 0 and
|
||||
. != "." and . != ".." and
|
||||
(contains("/") | not) and
|
||||
(contains("\\") | not) and
|
||||
(explode | all(.[]; . > 31 and . != 127))) and
|
||||
(.sha256 |
|
||||
type == "string" and test("^[a-f0-9]{64}$")) and
|
||||
(.size |
|
||||
type == "number" and . > 0 and
|
||||
. <= 9007199254740991 and . == floor))) and
|
||||
([.assets[].name] | length == (unique | length)) and
|
||||
([.assets[] |
|
||||
select(.name == "linux-frame-copy-runtime-sources.tar.xz")] |
|
||||
length == 1) and
|
||||
([.assets[] | select(.name | endswith(".snap"))] |
|
||||
length >= 1) and
|
||||
(.assets | all(.[];
|
||||
.name == "linux-frame-copy-runtime-sources.tar.xz" or
|
||||
(.name | endswith(".snap"))))
|
||||
' "${RECEIPT_PATH}" > /dev/null
|
||||
RECEIPT_ASSET_COUNT="$(/usr/bin/jq --raw-output '.assets | length' "${RECEIPT_PATH}")"
|
||||
test "${RECEIPT_ASSET_COUNT}" -eq "$(( ${#TRANSFERRED_SNAPS[@]} + 1 ))"
|
||||
SIZE_MANIFEST="${RUNNER_TEMP}/verified-release-asset-sizes.tsv"
|
||||
CHECKSUM_MANIFEST="${RUNNER_TEMP}/verified-release-asset-checksums.txt"
|
||||
umask 077
|
||||
/usr/bin/jq --raw-output \
|
||||
'.assets[] | [.name, (.size | tostring)] | @tsv' \
|
||||
"${RECEIPT_PATH}" > "${SIZE_MANIFEST}"
|
||||
while IFS=$'\t' read -r ASSET_NAME EXPECTED_SIZE; do
|
||||
ASSET_PATH="${TRANSFERRED_ASSET_DIRECTORY}/${ASSET_NAME}"
|
||||
ACTUAL_SIZE="$(/usr/bin/stat --format=%s -- "${ASSET_PATH}")"
|
||||
test "${ACTUAL_SIZE}" = "${EXPECTED_SIZE}"
|
||||
done < "${SIZE_MANIFEST}"
|
||||
/usr/bin/jq --raw-output \
|
||||
'.assets[] | "\(.sha256) \(.name)"' \
|
||||
"${RECEIPT_PATH}" > "${CHECKSUM_MANIFEST}"
|
||||
(
|
||||
cd "${TRANSFERRED_ASSET_DIRECTORY}"
|
||||
/usr/bin/sha256sum --strict --check "${CHECKSUM_MANIFEST}"
|
||||
)
|
||||
rm -f "${SIZE_MANIFEST}" "${CHECKSUM_MANIFEST}"
|
||||
shopt -u nullglob dotglob
|
||||
sudo test ! -e "${SEALED_ASSET_PARENT}"
|
||||
sudo install -d -m 0700 -o root -g root "${SEALED_ASSET_PARENT}"
|
||||
sudo mv "${TRANSFERRED_ASSET_DIRECTORY}" "${SEALED_ASSET_DIRECTORY}"
|
||||
sudo chown -R root:root "${SEALED_ASSET_DIRECTORY}"
|
||||
sudo find "${SEALED_ASSET_DIRECTORY}" -type d -exec chmod 0555 {} +
|
||||
sudo find "${SEALED_ASSET_DIRECTORY}" -type f -exec chmod 0444 {} +
|
||||
sudo chmod 0555 "${SEALED_ASSET_PARENT}"
|
||||
|
||||
- name: Install Snapcraft
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
sudo snap install snapcraft --classic --channel=stable
|
||||
|
||||
- name: Publish all public-release snaps to edge
|
||||
shell: bash
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.snapcraft_token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
VERIFIED_ASSET_DIRECTORY="/var/lib/iptvnator-snap-release/assets"
|
||||
STORE_CREDENTIALS="${SNAPCRAFT_STORE_CREDENTIALS}"
|
||||
unset SNAPCRAFT_STORE_CREDENTIALS
|
||||
shopt -s nullglob dotglob
|
||||
SNAP_FILES=("${VERIFIED_ASSET_DIRECTORY}"/*.snap)
|
||||
test "${#SNAP_FILES[@]}" -gt 0
|
||||
for SNAP_FILE in "${SNAP_FILES[@]}"; do
|
||||
SNAP_NAME="${SNAP_FILE##*/}"
|
||||
echo "Publishing public release asset: ${SNAP_NAME}"
|
||||
# Candidate/stable promotion is manual after installed-Snap frame-copy and missing-runtime fallback smoke.
|
||||
# GitHub Actions never promotes automatically.
|
||||
SNAPCRAFT_STORE_CREDENTIALS="${STORE_CREDENTIALS}" /snap/bin/snapcraft upload --release=edge "${SNAP_FILE}"
|
||||
done
|
||||
unset STORE_CREDENTIALS
|
||||
shopt -u nullglob dotglob
|
||||
Reference in new issue
Block a user