Added bubblewrap / apparmor fixes and diagnostic tools for troubleshooting it on related distros.

This commit is contained in:
2026-09-03 22:16:43 +02:00
parent ecdea827ac
commit 5be4ace6c2
9 changed files with 1039 additions and 9 deletions

View File

@@ -22,6 +22,13 @@ from ptdiag.logs import (
from . import __version__
from .command import CommandError, CommandOutcome, CommandRunner
from .container_host import (
ContainerHostReport,
container_host_error,
inspect_container_host,
repair_container_host,
repair_container_host_after_update,
)
from .depot import (
DownloadOptions,
SyncProgress,
@@ -83,6 +90,15 @@ def build_parser() -> argparse.ArgumentParser:
subparsers.add_parser("gui", help="open the native Linux dashboard")
subparsers.add_parser("doctor", help="check cross-distribution dependencies")
container_host = subparsers.add_parser(
"container-host",
help="check Bubblewrap and Ubuntu AppArmor for the Steam Runtime",
)
container_host.add_argument(
"--repair",
action="store_true",
help="repair only recognized Ubuntu AppArmor profile states",
)
subparsers.add_parser(
"runtime-bootstrap",
help="download and verify the Steam Runtime launcher helper",
@@ -250,12 +266,15 @@ def main(argv: Sequence[str] | None = None) -> int:
try:
if command == "gui":
provision_umu_for_startup(paths)
_container_host_startup_migration(paths)
from .ui import run_dashboard
run_dashboard(paths)
return 0
if command == "doctor":
return _doctor(arguments.json)
if command == "container-host":
return _container_host(arguments.repair, arguments.json)
if command == "runtime-bootstrap":
return _runtime_bootstrap(paths, arguments.json)
if command == "login-diagnostics":
@@ -384,6 +403,46 @@ def _doctor(as_json: bool) -> int:
return 0 if report.ok else 1
def _container_host(repair: bool, as_json: bool) -> int:
report = repair_container_host() if repair else inspect_container_host()
ok = _container_host_ok(report)
payload = {"ok": ok, "container_host": report.to_dict()}
if as_json:
_print_json(payload)
else:
status = "ready" if report.ready else "blocked"
print(f"Bubblewrap: {status} ({report.bwrap_path or 'not found'})")
if report.active_profile_source:
print(f"AppArmor: {report.active_profile_source}")
elif report.apparmor_restrict_unprivileged_userns == "1":
print(
"AppArmor: unprivileged user namespaces are restricted; "
"no bwrap profile found"
)
if report.repair_actions:
for action in report.repair_actions:
print(f"Repair: {action}")
if report.repair_error:
print(f"Repair failed: {report.repair_error}", file=sys.stderr)
elif report.recommendation:
print(f"Action: {report.recommendation}", file=sys.stderr)
return 0 if ok else 1
def _container_host_ok(report: ContainerHostReport) -> bool:
return (
report.ready
and not report.broken_umu_profile_link
and report.repair_error is None
)
def _container_host_startup_migration(paths: AppPaths) -> None:
report = repair_container_host_after_update(paths)
if report.repair_attempted and not _container_host_ok(report):
print(f"Warning: {container_host_error(report)}", file=sys.stderr)
def _runtime_bootstrap(paths: AppPaths, as_json: bool) -> int:
_print_operation_status(as_json, 1, 1, "Preparing the Steam Runtime helper…")
result = provision_umu(paths)
@@ -1018,6 +1077,7 @@ def _runtime_issue_summary(stderr: str) -> dict[str, object]:
futex_message = "the futex facility returned an unexpected error code"
wrong_elf_message = "wrong elf class: elfclass64"
dbghelp_assertion = "assertion failed: module->module.symtype != symdeferred"
bwrap_uid_map_denied = "bwrap: setting up uid map: permission denied"
missing_libraries = sorted(
{
library
@@ -1038,6 +1098,7 @@ def _runtime_issue_summary(stderr: str) -> dict[str, object]:
"mesa_egl_initialization_failed": "egl: failed to create dri2 screen" in folded,
"steam_api_app_id_missing": "steamapi_init() failed; no appid found" in folded,
"dbghelp_symdeferred_assertion_count": folded.count(dbghelp_assertion),
"bubblewrap_uid_map_permission_denied": bwrap_uid_map_denied in folded,
}
@@ -1079,6 +1140,12 @@ def _print_game_outcome(
"can mask the client failure that occurred immediately before it.",
file=sys.stderr,
)
if runtime_issues["bubblewrap_uid_map_permission_denied"]:
print(
"Ubuntu denied Bubblewrap's UID map before Proton started. Run "
"`pristontale-eu container-host --repair` and retry.",
file=sys.stderr,
)
recommendation = runtime_issues.get("recommended_one_variable_comparison")
if isinstance(recommendation, str):
print(
@@ -1188,7 +1255,7 @@ def _launcher_update(
as_json,
)
return 0
_print_operation_status(as_json, 1, 3, "Verifying the signed launcher release…")
_print_operation_status(as_json, 1, 4, "Verifying the signed launcher release…")
result = install_launcher_update(
paths,
manifest_url=manifest_url,
@@ -1197,7 +1264,7 @@ def _launcher_update(
_print_operation_status(
as_json,
2,
3,
4,
"Launcher update installed; preparing the Steam Runtime helper…",
)
umu_result: UmuBootstrapResult | None = None
@@ -1216,7 +1283,19 @@ def _launcher_update(
umu_warning = warning_message
if not as_json:
print(f"Warning: {warning_message}", file=sys.stderr)
_print_operation_status(as_json, 3, 3, "Launcher update and runtime check complete.")
_print_operation_status(
as_json,
3,
4,
"Checking Steam Runtime sandbox permissions (authentication may be requested)…",
)
container_report = repair_container_host()
container_warning = container_report.repair_error
if not container_report.ready and container_warning is None:
container_warning = container_report.recommendation
if container_warning and not as_json:
print(f"Warning: {container_warning}", file=sys.stderr)
_print_operation_status(as_json, 4, 4, "Launcher update and runtime check complete.")
_emit(
{
"ok": True,
@@ -1226,6 +1305,10 @@ def _launcher_update(
"managed_download": asdict(umu_result) if umu_result else None,
"warning": umu_warning,
},
"container_host": {
**container_report.to_dict(),
"warning": container_warning,
},
},
as_json,
)