Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
691714d
feat(logcore): add native C11 logging core with async sink and redaction
Oct 23, 2026
fcbd8fd
feat(core): paths, settings, HTTP, SQLite and the download manager
Oct 23, 2026
5f13289
feat(game): Java Edition pipeline, accounts, loaders, modpacks, insta…
Oct 23, 2026
5b4c187
feat(ui): Material QML interface with Chinese-first localisation
Oct 23, 2026
42db25c
test: unit, integration, C harness and headless UI verification
Oct 23, 2026
1efe71c
docs: add user and maintainer documentation, disclaimers and CI
Oct 23, 2026
d60f937
chore: stop ignoring negation for the native extension and crash reports
Oct 23, 2026
a53351f
test(e2e): allow an existing Java runtime instead of downloading Temurin
Oct 23, 2026
6580443
fix(mc): parse the client jar and log4j config entries correctly
Oct 23, 2026
43f7a16
docs: record the actual verification results
Oct 23, 2026
b34d7c9
fix(ci): make workflows portable to the Windows pwsh default shell
Oct 23, 2026
b9ed443
style: apply ruff format to the files touched by the last fix
Oct 23, 2026
ac0160c
fix(tests): make the suite platform- and locale-independent
Oct 23, 2026
e693f51
style: satisfy lint for the benign-warning filter
Oct 23, 2026
e8a9f62
fix: address the macOS test failures and the CI packaging job
Oct 23, 2026
6181031
fix(build): export PYTHONPATH to child processes and unblock macOS GU…
Oct 23, 2026
a79148f
fix(build): install the project editable before invoking Nuitka
Oct 23, 2026
d2fe87b
fix(build): locate the standalone artifact instead of hard-coding its…
Oct 23, 2026
2363f88
fix(build): stop Nuitka copying the logging extension twice
Oct 23, 2026
a086edb
docs: record the real CI results, including the unresolved packaging …
Oct 23, 2026
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
103 changes: 103 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
name: Build

on:
push:
tags: ["v*"]
pull_request:
paths:
- "src/**"
- "csrc/**"
- "packaging/**"
- "tools/**"
- "setup.py"
- "pyproject.toml"
- ".github/workflows/build.yml"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: build-${{ github.ref }}
cancel-in-progress: true

env:
PYTHONUNBUFFERED: "1"
PIP_DISABLE_PIP_VERSION_CHECK: "1"

jobs:
package:
name: Nuitka package (${{ matrix.os }})
# Building on the oldest still-supported hosted image gives the broadest
# glibc compatibility for the Linux artifact.
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
artifact: apeiron-linux-x86_64
- os: macos-14
artifact: apeiron-macos-arm64
- os: windows-2022
artifact: apeiron-windows-x86_64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- name: Install Qt runtime libraries (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libegl1 libgl1 libxkbcommon-x11-0 libdbus-1-3 libxcb-cursor0 libxcb-icccm4 libxcb-keysyms1 libxcb-shape0 patchelf
- name: Install build dependencies
run: |
python -m pip install --upgrade pip
python -m pip install "PySide6-Essentials>=6.8,<6.10" "PySide6-Addons>=6.8,<6.10" "nuitka>=2.4" "zstandard>=0.23" "ordered-set>=4.1" "setuptools>=68" wheel
- name: Build and self-check the artifact
run: python packaging/build_nuitka.py --output-dir dist
- name: Smoke-run the packaged entry point
shell: bash
run: |
# The standalone directory is named after the compiled entry script,
# so locate the binary instead of hard-coding it.
BIN=$(find dist -maxdepth 2 -type f -name "Apeiron*" | head -1)
test -n "$BIN" || { echo "no artifact produced"; ls -R dist | head -50; exit 1; }
echo "artifact: $BIN"
"$BIN" --version
QT_QPA_PLATFORM=offscreen "$BIN" --self-check | tee self-check.out
python - <<'PY'
import json
from pathlib import Path
raw = Path("self-check.out").read_text()
payload = json.loads(raw[raw.index("{"):])
assert payload["ok"], payload
checks = payload["checks"]
assert checks["nativeLogCore"]["loaded"], "native extension missing in artifact"
assert checks["qml"]["loaded"], "QML failed to load in artifact"
assert checks["database"]["integrity"], "database integrity check failed"
print("artifact self-check ok")
PY
env:
APEIRON_HOME: ${{ runner.temp }}/apeiron-artifact-home
- name: Archive the distribution
shell: bash
run: |
mkdir -p out
DIST_DIR=$(find dist -maxdepth 1 -type d -name "*.dist" | head -1)
test -n "$DIST_DIR" || { echo "no distribution directory"; exit 1; }
NAME=$(basename "$DIST_DIR")
if [ "$RUNNER_OS" = "Windows" ]; then
(cd dist && 7z a "../out/${{ matrix.artifact }}.zip" "$NAME" >/dev/null)
else
(cd dist && tar -czf "../out/${{ matrix.artifact }}.tar.gz" "$NAME")
fi
ls -lh out
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: out/*
if-no-files-found: error
retention-days: 14
180 changes: 180 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
name: CI

on:
push:
branches: ["main", "develop"]
pull_request:
workflow_dispatch:

# Least privilege: nothing here writes to the repository.
permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

env:
PYTHONUNBUFFERED: "1"
QT_QPA_PLATFORM: offscreen
PIP_DISABLE_PIP_VERSION_CHECK: "1"

jobs:
lint:
name: Lint and format
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- run: python -m pip install "ruff>=0.6"
- name: ruff check
run: ruff check src tests tools packaging
- name: ruff format
run: ruff format --check src tests tools packaging

types:
name: Type check
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- run: python -m pip install "PySide6-Essentials>=6.8,<6.10" "mypy>=1.11" "pytest>=8"
- name: mypy
run: mypy src/apeiron

c-core:
name: C logging core (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, macos-14, windows-2022]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Build and run the standalone C test harness (POSIX)
if: runner.os != 'Windows'
run: |
cc -std=c11 -O2 -Wall -Wextra -pthread csrc/logcore.c csrc/test_logcore.c -o test_logcore
./test_logcore
- name: Compile the extension and run the C harness (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
python -m pip install "setuptools>=68" wheel
python setup.py build_ext --inplace
# MSVC build of the same sources proves portability; the harness is
# compiled with the Windows toolchain through the extension build.
python -c "import apeiron._logcore as m; print('extension', m.__version__)"
env:
PYTHONPATH: src

tests:
name: Tests (${{ matrix.os }}, py${{ matrix.python }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, macos-14, windows-2022]
python: ["3.10", "3.12"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
cache: pip
- name: Install Qt runtime libraries (Linux)
if: runner.os == 'Linux'
run: sudo apt-get install -y libegl1 libgl1 libxkbcommon-x11-0 libdbus-1-3 libxcb-cursor0 libxcb-icccm4 libxcb-keysyms1 libxcb-shape0
- name: Install dependencies
# Single line on purpose: the default shell on Windows runners is pwsh,
# where a trailing backslash is not a line continuation.
run: |
python -m pip install --upgrade pip
python -m pip install "PySide6-Essentials>=6.8,<6.10" "PySide6-Addons>=6.8,<6.10" "pytest>=8" "pytest-timeout>=2.3" "setuptools>=68" wheel
- name: Build native extension
run: python setup.py build_ext --inplace
- name: Build translations
run: python tools/build_translations.py
- name: pytest
env:
PYTHONPATH: src
run: python -m pytest tests -q

i18n:
name: Translation completeness
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- run: python -m pip install "PySide6-Essentials>=6.8,<6.10"
- name: Every qsTr string is translated
run: python tools/build_translations.py --check
- name: Catalogues compile and are non-empty
run: |
python tools/build_translations.py
python - <<'PY'
from pathlib import Path
files = sorted(Path("src/apeiron/i18n").glob("*.qm"))
assert files, "no .qm files were produced"
for f in files:
assert f.stat().st_size > 512, f"{f} is suspiciously small"
print(f.name, f.stat().st_size, "bytes")
PY

qml:
name: QML load and UI smoke
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- name: Install Qt runtime libraries
run: sudo apt-get install -y libegl1 libgl1 libxkbcommon-x11-0 libdbus-1-3 libxcb-cursor0 libxcb-icccm4 libxcb-keysyms1 libxcb-shape0
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install "PySide6-Essentials>=6.8,<6.10" "PySide6-Addons>=6.8,<6.10" "pytest>=8" "pytest-qt>=4.4" "pytest-timeout>=2.3" "setuptools>=68" wheel
- run: python setup.py build_ext --inplace
- run: python tools/build_translations.py
- name: Headless UI smoke
env:
PYTHONPATH: src
run: python tools/gui_smoke.py "$RUNNER_TEMP/gui-root"

workflow-lint:
name: Workflow syntax
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Validate YAML
run: |
python -m pip install --quiet pyyaml
python - <<'PY'
import sys, yaml
from pathlib import Path
ok = True
for path in sorted(Path(".github/workflows").glob("*.yml")):
try:
data = yaml.safe_load(path.read_text())
jobs = data.get("jobs", {})
assert jobs, "workflow has no jobs"
print(f"{path.name}: {len(jobs)} jobs")
except Exception as exc: # noqa: BLE001
ok = False
print(f"{path.name}: {exc}", file=sys.stderr)
raise SystemExit(0 if ok else 1)
PY
41 changes: 41 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Python
__pycache__/
*.py[cod]
*.egg-info/
.venv/
venv/

# Native extension build output (rebuilt by setup.py / CI)
build/
*.so
*.pyd
*.dll

# Nuitka / packaging
dist/
*.dist/
*.onefile-build/
*.build/
*.egg
nuitka-crash-report.xml
self-check.out

# Test and tool caches
.pytest_cache/
.mypy_cache/
.ruff_cache/
.coverage
htmlcov/

# Qt Linguist intermediate output
*.qm.bak

# Editors and OS files
.idea/
.vscode/
.DS_Store
Thumbs.db

# Local runtime data that must never be committed
apeiron-data/
*.log
64 changes: 64 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Changelog

All notable changes to Apeiron are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
semantic versioning.

## [0.1.0] — unreleased

First implementation of the launcher.

### Added

- **Native C11 logging core** (`csrc/logcore.c`, `apeiron._logcore`): bounded
queue, dedicated writer thread, size-based rotation with retention, console and
file sinks, timestamps with millisecond precision, PID/TID, exception context,
UTF-8 validation and credential redaction. Includes a standalone C test harness
(44 assertions) and Python integration tests.
- **Launcher core**: application paths with a sandboxed instance root, typed
error taxonomy, settings with atomic writes and schema migration, HTTP client
restricted to TLS with retries/backoff and `Retry-After` support.
- **SQLite persistence** with three append-only migrations: instances, accounts
(metadata only), download tasks, instance content, launch history, app state.
- **Java Edition pipeline**: official version manifest with cache, version
inheritance, rule evaluation, Maven-base library resolution, library/native/
client/asset downloads with checksum verification, Java runtime discovery and
Adoptium provisioning, per-version command construction (modern and legacy),
process supervision with pipe draining and log capture.
- **Accounts**: Microsoft OAuth 2.0 device-code flow through Xbox Live and
Minecraft services with an explicit ownership check; offline profiles using the
vanilla offline-UUID algorithm; OS credential storage (DPAPI, Keychain,
libsecret) with a flagged file fallback; `AuthProvider` interface for future
providers.
- **Mod loaders**: Fabric and Quilt via official metadata profiles; Forge and
NeoForge via their vendor installers with mandatory SHA-1 verification and
explicit consent.
- **Modpacks and content**: Modrinth `.mrpack` import/export, CurseForge manifest
import gated on a user-supplied API key, per-kind content management
(mods/resource packs/shader packs/data packs) with enable/disable.
- **Instances**: create, copy, rename, delete with path guards, world backup and
restore, archive export/import.
- **Windows Bedrock adapter**: Store package detection and `shell:appsFolder`
activation, explicitly limited and experimental.
- **Material-style QML interface** with eight pages (home, library, accounts,
downloads, content, settings, logs, about), light/dark/system themes, keyboard
shortcuts and accessibility labels.
- **Localisation**: Simplified Chinese default with English, OS detection,
persistent manual override, Qt Linguist `.ts`/`.qm` pipeline, 179 translated
strings, and completeness checking in the build script.
- **Security controls**: safe archive extraction, download integrity checks,
path-traversal guards, log redaction, HTTPS-only transport, update checksum
verification.
- **Documentation and compliance**: README, English disclaimer (authoritative),
Chinese reference translation, contributing guide, security policy, architecture
/ platform / account / build / test / i18n / licence / disclaimer documents.
- **CI**: workflow definitions for lint, type check, C tests, Python tests,
i18n completeness, QML load and cross-platform Nuitka builds.

### Known limitations at this version

- Microsoft sign-in requires a user-registered Azure AD client id.
- Update manifests are checksum-verified but not cryptographically signed.
- macOS artifacts are not signed or notarised.
- Bedrock support is activation-only and unverified in CI.
- CurseForge installs require the user's own API key.
Loading
Loading