ci: isolate Ubuntu sources and select Git Bash for Windows tests
Launcher checks / check (push) Canceled after 0s

This commit is contained in:
Emil
2026-09-09 20:41:07 +03:00
parent daefc3f5e7
commit cacd1cd2bd
6 changed files with 95 additions and 15 deletions
+9 -3
View File
@@ -54,13 +54,19 @@ jobs:
- name: Install Linux desktop dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
# Ubuntu 22.04 keeps distro sources here; unrelated vendor repos are not needed.
test -s /etc/apt/sources.list
apt_options=(
-o Dir::Etc::sourcelist=/etc/apt/sources.list
-o Dir::Etc::sourceparts=-
)
sudo apt-get "${apt_options[@]}" --error-on=any update
sudo apt-get "${apt_options[@]}" install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- run: npm ci
- run: npm test
- run: cargo test --locked --manifest-path src-tauri/Cargo.toml
- run: cargo build --locked --release --manifest-path scripts/release-verifier/Cargo.toml
- run: python -m unittest discover -s scripts -p 'release_test.py' -v
- run: cargo test --locked --manifest-path src-tauri/Cargo.toml
- name: Generate disposable CI key (never a production secret)
run: |
python scripts/release.py ci-key --directory "$RUNNER_TEMP/ci-updater"
+9 -3
View File
@@ -27,11 +27,17 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
- name: Install Linux desktop dependencies
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
# Ubuntu 22.04 keeps distro sources here; unrelated vendor repos are not needed.
test -s /etc/apt/sources.list
apt_options=(
-o Dir::Etc::sourcelist=/etc/apt/sources.list
-o Dir::Etc::sourceparts=-
)
sudo apt-get "${apt_options[@]}" --error-on=any update
sudo apt-get "${apt_options[@]}" install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- run: npm ci
- run: npm test
- run: npm run build
- run: cargo test --locked --manifest-path src-tauri/Cargo.toml
- run: cargo build --locked --release --manifest-path scripts/release-verifier/Cargo.toml
- run: python -m unittest discover -s scripts -p 'release_test.py' -v
- run: cargo test --locked --manifest-path src-tauri/Cargo.toml
+8 -2
View File
@@ -115,8 +115,14 @@ jobs:
- name: Install Linux desktop dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
# Ubuntu 22.04 keeps distro sources here; unrelated vendor repos are not needed.
test -s /etc/apt/sources.list
apt_options=(
-o Dir::Etc::sourcelist=/etc/apt/sources.list
-o Dir::Etc::sourceparts=-
)
sudo apt-get "${apt_options[@]}" --error-on=any update
sudo apt-get "${apt_options[@]}" install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- run: npm ci
- run: npm test
- run: cargo test --locked --manifest-path src-tauri/Cargo.toml
+2
View File
@@ -109,6 +109,8 @@ payload are in `/root/shacraft` on the ShaCraft host; see
- `docs/game-trust-boundary.md` — the Mojang/NeoForge/Microsoft/Adoptium
trust domains used to install and run the game itself.
- `.github/workflows/check.yml` — push/PR UI checks and Linux Rust tests.
Ubuntu 22.04 dependency steps use only the runner main Ubuntu source list;
keep APT signature/hash checks and fail on index errors. Vendor PPAs are not needed.
- `.github/workflows/build.yml` — four-platform CI packages with disposable test
signing keys, explicitly unusable as production releases.
- Release workflows and `scripts/release.py` implement protected draft → publish
+4 -1
View File
@@ -110,7 +110,10 @@ Hostile same-user TOCTOU is outside this protection; it is not an OS sandbox.
`npm test` covers asynchronous helpers and state transitions;
`npm run build` runs strict TypeScript before Vite. `cargo test --locked`
covers native policy and storage. Push/PR CI repeats checks on Linux.
covers native policy and storage. Push/PR CI repeats checks on Linux. Ubuntu 22.04
CI installs desktop dependencies from the runner main distro source list only,
without indexing unrelated vendor repositories. APT signature/hash checks remain
mandatory, and any distro index failure stops installation.
The package workflow builds and checks Windows x64, Linux x64 and both macOS
architectures with disposable test signing keys. These artifacts cannot be
published as production updater releases. Separate protected workflows assemble
+63 -6
View File
@@ -2,10 +2,12 @@
import os
import re
import shutil
import subprocess
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
ROOT = Path(os.environ.get("RELEASE_TEST_ROOT", Path(__file__).resolve().parents[1]))
@@ -13,6 +15,32 @@ WORKFLOWS = [ROOT / ".github/workflows" / name for name in ("release.yml", "rele
GATE = " - name: Resolve tag using trusted workflow Git commands\n"
def gate_bash(windows=os.name == "nt"):
# Native CreateProcess can resolve bare bash to the System32 WSL launcher
# before Git Bash. Bind tests to the Bash installed with the Git we use.
if windows:
git = shutil.which("git")
if git:
directory = Path(git).resolve().parent
candidates = (
directory / "bash.exe", # Git/bin/git.exe
directory.parent / "bin/bash.exe", # Git/cmd/git.exe
directory.parent.parent / "bin/bash.exe", # Git/mingw64/bin/git.exe
)
for candidate in candidates:
if candidate.is_file():
return str(candidate.resolve())
raise RuntimeError("Git for Windows Bash was not found beside the installed Git")
bash = shutil.which("bash")
if not bash:
raise RuntimeError("Bash is required to execute the release workflow gate")
return str(Path(bash).resolve())
def gate_diagnostic(result):
return f"command={result.args!r}\nstdout={result.stdout!r}\nstderr={result.stderr!r}"
def workflow_gate(workflow):
after = workflow.read_text().split(GATE, 1)[1]
lines = after.split(" run: |\n", 1)[1].splitlines()
@@ -60,23 +88,52 @@ class ReleaseGateTests(unittest.TestCase):
def run_gate(self, workflow, tag):
output = self.directory / (workflow.stem + "-output")
output.unlink(missing_ok=True)
result = subprocess.run(["bash", "-c", workflow_gate(workflow)], cwd=self.directory,
result = subprocess.run([gate_bash(), "--noprofile", "--norc", "-c", workflow_gate(workflow)], cwd=self.directory,
env=dict(self.environment, RELEASE_TAG=tag, GITHUB_OUTPUT=str(output)),
capture_output=True, text=True, check=False)
return result, output.read_text() if output.exists() else ""
def test_windows_gate_uses_git_bash_even_when_path_contains_wsl_launcher(self):
git_root = self.directory / "Git with spaces"
bash = git_root / "bin/bash.exe"
bash.parent.mkdir(parents=True)
bash.touch()
wsl = self.directory / "System32/bash.exe"
wsl.parent.mkdir()
wsl.touch()
for layout in ("bin", "cmd", "mingw64/bin"):
with self.subTest(layout=layout):
git = git_root / layout / "git.exe"
git.parent.mkdir(parents=True, exist_ok=True)
git.touch()
with patch.object(shutil, "which", side_effect=lambda name: str(git if name == "git" else wsl)) as lookup:
self.assertEqual(gate_bash(windows=True), str(bash.resolve()))
lookup.assert_called_once_with("git")
def test_windows_gate_never_falls_back_to_unrelated_bash(self):
git = self.directory / "Git/cmd/git.exe"
git.parent.mkdir(parents=True)
git.touch()
wsl = self.directory / "System32/bash.exe"
wsl.parent.mkdir()
wsl.touch()
with patch.object(shutil, "which", side_effect=lambda name: str(git if name == "git" else wsl)) as lookup:
with self.assertRaisesRegex(RuntimeError, "Git for Windows Bash"):
gate_bash(windows=True)
lookup.assert_called_once_with("git")
def test_main_tag_emits_immutable_commit(self):
for workflow in WORKFLOWS:
with self.subTest(workflow=workflow.name):
result, output = self.run_gate(workflow, "v0.2.0")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.returncode, 0, gate_diagnostic(result))
self.assertEqual(output, f"commit={self.good}\n")
def test_unreviewed_tag_cannot_replace_its_own_ancestry_validator(self):
for workflow in WORKFLOWS:
with self.subTest(workflow=workflow.name):
result, output = self.run_gate(workflow, "v9.9.9")
self.assertNotEqual(result.returncode, 0)
self.assertNotEqual(result.returncode, 0, gate_diagnostic(result))
self.assertEqual(output, "")
self.assertFalse((self.directory / "untrusted-code-ran").exists())
@@ -84,15 +141,15 @@ class ReleaseGateTests(unittest.TestCase):
for workflow in WORKFLOWS:
with self.subTest(workflow=workflow.name):
result, output = self.run_gate(workflow, "v8.8.8")
self.assertNotEqual(result.returncode, 0)
self.assertNotEqual(result.returncode, 0, gate_diagnostic(result))
self.assertEqual(output, "")
self.git("tag", "-f", "v0.2.0", self.good)
result, output = self.run_gate(workflow, "v0.2.0")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.returncode, 0, gate_diagnostic(result))
self.git("tag", "-f", "v0.2.0", self.bad)
self.assertEqual(output, f"commit={self.good}\n")
result, fresh_output = self.run_gate(workflow, "v0.2.0")
self.assertNotEqual(result.returncode, 0)
self.assertNotEqual(result.returncode, 0, gate_diagnostic(result))
self.assertEqual(fresh_output, "")
def test_workflows_execute_candidate_code_only_after_gate_and_checkout_sha(self):