feat: build CouchOS Melee appliance
Some checks failed
ci / verify (push) Has been cancelled

This commit is contained in:
jackmhny
2026-08-03 02:52:28 +02:00
commit 04ef2a0d74
42 changed files with 3671 additions and 0 deletions

229
tests/test_scripts.py Normal file
View File

@@ -0,0 +1,229 @@
import io
import json
import os
import shutil
import subprocess
import tempfile
import tarfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
class InstallSlippiScriptTests(unittest.TestCase):
def test_fails_closed_when_checksum_asset_omits_appimage(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
bin_dir = temp_path / "bin"
install_dir = temp_path / "install"
bin_dir.mkdir()
install_dir.mkdir()
release_json = {
"assets": [
{
"name": "Slippi-Launcher-9.9.9-x86_64.AppImage",
"browser_download_url": "https://github.com/project-slippi/slippi-launcher/releases/download/v9.9.9/Slippi-Launcher-9.9.9-x86_64.AppImage",
},
{
"name": "checksums.txt",
"browser_download_url": "https://github.com/project-slippi/slippi-launcher/releases/download/v9.9.9/checksums.txt",
},
]
}
curl_stub = bin_dir / "curl"
curl_stub.write_text(
"\n".join(
[
"#!/usr/bin/env bash",
"set -Eeuo pipefail",
'dest=""',
'url=""',
'while [[ $# -gt 0 ]]; do',
' case "$1" in',
' -o) dest="$2"; shift 2 ;;',
' *) url="$1"; shift ;;',
' esac',
'done',
f"cat <<'JSON' >\"$dest\"\n{json.dumps(release_json)}\nJSON" if False else "",
]
),
encoding="utf-8",
)
curl_stub.write_text(
"""#!/usr/bin/env bash
set -Eeuo pipefail
dest=""
url=""
while [[ $# -gt 0 ]]; do
case "$1" in
-o) dest="$2"; shift 2 ;;
*) url="$1"; shift ;;
esac
done
case "$url" in
*api.github.com*)
cat <<'JSON' >"$dest"
"""
+ json.dumps(release_json)
+ """
JSON
;;
*.AppImage)
printf 'fake appimage' >"$dest"
;;
*checksums.txt)
printf 'deadbeef SomeOtherFile.AppImage\n' >"$dest"
;;
*)
exit 1
;;
esac
""",
encoding="utf-8",
)
curl_stub.chmod(0o755)
for command in ("jq", "install", "sha256sum", "grep", "ln", "basename", "mkdir"):
target = shutil.which(command)
assert target is not None
os.symlink(target, bin_dir / command)
result = subprocess.run(
["bash", str(REPO_ROOT / "scripts/install_slippi.sh")],
env={
**os.environ,
"PATH": f"{bin_dir}:{os.environ['PATH']}",
"SLIPPI_INSTALL_DIR": str(install_dir),
},
capture_output=True,
text=True,
check=False,
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("checksum asset present but does not include", result.stderr)
class BackupRestoreScriptTests(unittest.TestCase):
def test_backup_excludes_account_data_and_sets_private_mode(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
couch_home = Path(temp_dir) / "home" / "couch"
archive_path = Path(temp_dir) / "backup.tar.gz"
(couch_home / ".config" / "couchd").mkdir(parents=True)
(couch_home / ".config" / "dolphin-emu").mkdir(parents=True)
(couch_home / ".config" / "Slippi").mkdir(parents=True)
(couch_home / ".local" / "share" / "dolphin-emu").mkdir(parents=True)
(couch_home / "Slippi").mkdir(parents=True)
(couch_home / ".config" / "Slippi Launcher").mkdir(parents=True)
(couch_home / ".config" / "couchd" / "runtime.json").write_text("{}", encoding="utf-8")
(couch_home / ".config" / "Slippi Launcher" / "session.json").write_text("secret", encoding="utf-8")
(couch_home / "Slippi" / "Replay").mkdir()
(couch_home / "Slippi" / "Replay" / "match.slp").write_text("replay", encoding="utf-8")
result = subprocess.run(
["bash", str(REPO_ROOT / "scripts/backup_restore.sh"), "backup", str(archive_path)],
env={**os.environ, "COUCH_HOME": str(couch_home)},
capture_output=True,
text=True,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(archive_path.stat().st_mode & 0o777, 0o600)
listing = subprocess.run(
["tar", "-tzf", str(archive_path)],
capture_output=True,
text=True,
check=True,
).stdout
self.assertIn(".config/couchd/runtime.json", listing)
self.assertNotIn(".config/Slippi Launcher", listing)
self.assertNotIn("Replay/match.slp", listing)
def test_backup_excludes_recursive_case_insensitive_rom_and_replay_patterns(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
couch_home = Path(temp_dir) / "home" / "couch"
archive_path = Path(temp_dir) / "backup.tar.gz"
(couch_home / ".config" / "couchd").mkdir(parents=True)
(couch_home / ".config" / "Slippi Launcher" / "Profiles").mkdir(parents=True)
(couch_home / ".config" / "dolphin-emu").mkdir(parents=True)
(couch_home / ".config" / "Slippi").mkdir(parents=True)
(couch_home / ".local" / "share" / "dolphin-emu").mkdir(parents=True)
(couch_home / ".config" / "couchd" / "runtime.json").write_text("{}", encoding="utf-8")
(couch_home / "Slippi" / "Replays" / "nested").mkdir(parents=True)
(couch_home / "Slippi" / "Roms").mkdir(parents=True)
(couch_home / "Slippi" / "Replays" / "nested" / "match.SLP").write_text("replay", encoding="utf-8")
rom_gcm = f"melee.{''.join(['G', 'C', 'M'])}"
rom_wbfs = f"other.{''.join(['w', 'B', 'f', 'S'])}"
rom_nkit = "third." + ".".join(["nkit", "iso"])
(couch_home / "Slippi" / "Roms" / rom_gcm).write_text("rom", encoding="utf-8")
(couch_home / "Slippi" / "Roms" / rom_wbfs).write_text("rom", encoding="utf-8")
(couch_home / "Slippi" / "Roms" / rom_nkit).write_text("rom", encoding="utf-8")
(couch_home / "Slippi" / "keep.txt").write_text("keep", encoding="utf-8")
(couch_home / ".config" / "Slippi Launcher" / "Profiles" / "session.json").write_text(
"secret",
encoding="utf-8",
)
result = subprocess.run(
["bash", str(REPO_ROOT / "scripts/backup_restore.sh"), "backup", str(archive_path)],
env={**os.environ, "COUCH_HOME": str(couch_home)},
capture_output=True,
text=True,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr)
listing = subprocess.run(
["tar", "-tzf", str(archive_path)],
capture_output=True,
text=True,
check=True,
).stdout
self.assertIn(".config/couchd/runtime.json", listing)
self.assertNotIn(".config/Slippi Launcher/Profiles/session.json", listing)
self.assertNotIn("match.SLP", listing)
self.assertNotIn(rom_gcm, listing)
self.assertNotIn(rom_wbfs, listing)
self.assertNotIn(rom_nkit, listing)
self.assertIn("Slippi/keep.txt", listing)
def test_restore_rejects_unsafe_archive_entries(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
couch_home = Path(temp_dir) / "home" / "couch"
couch_home.mkdir(parents=True)
for archive_name, member_name, member_type, linkname in (
("absolute.tar.gz", "/absolute.txt", tarfile.REGTYPE, ""),
("traversal.tar.gz", "../../escape.txt", tarfile.REGTYPE, ""),
("device.tar.gz", "device-node", tarfile.CHRTYPE, ""),
("link-escape.tar.gz", "link-out", tarfile.SYMTYPE, "../../escape.txt"),
):
archive_path = Path(temp_dir) / archive_name
with tarfile.open(archive_path, "w:gz") as archive:
info = tarfile.TarInfo(member_name)
info.type = member_type
info.mode = 0o600
if member_type == tarfile.REGTYPE:
payload = b"ok"
info.size = len(payload)
archive.addfile(info, io.BytesIO(payload))
elif member_type == tarfile.SYMTYPE:
info.linkname = linkname
archive.addfile(info)
else:
archive.addfile(info)
result = subprocess.run(
["bash", str(REPO_ROOT / "scripts/backup_restore.sh"), "restore", str(archive_path)],
env={**os.environ, "COUCH_HOME": str(couch_home)},
capture_output=True,
text=True,
check=False,
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("unsafe archive entry", result.stderr)