mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-01-17 11:37:01 +01:00
Jackify provides native Linux support for Wabbajack modlist installation and management with automated Steam integration and Proton configuration. Key Features: - Almost Native Linux implementation (texconv.exe run via proton) - Automated Steam shortcut creation and Proton prefix management - Both CLI and GUI interfaces, with Steam Deck optimization Supported Games: - Skyrim Special Edition - Fallout 4 - Fallout New Vegas - Oblivion, Starfield, Enderal, and diverse other games Technical Architecture: - Clean separation between frontend and backend services - Powered by jackify-engine 0.3.x for Wabbajack-matching modlist installation
247 lines
8.9 KiB
Python
247 lines
8.9 KiB
Python
"""
|
|
Tuxborn Command
|
|
|
|
CLI command for the Tuxborn Automatic Installer.
|
|
Extracted from the original jackify-cli.py.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
# Import the backend services we'll need
|
|
from jackify.backend.models.modlist import ModlistContext
|
|
from jackify.shared.colors import COLOR_INFO, COLOR_ERROR, COLOR_RESET
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class TuxbornCommand:
|
|
"""Handler for the tuxborn-auto CLI command."""
|
|
|
|
def __init__(self, backend_services, system_info):
|
|
"""Initialize with backend services.
|
|
|
|
Args:
|
|
backend_services: Dictionary of backend service instances
|
|
system_info: System information (steamdeck flag, etc.)
|
|
"""
|
|
self.backend_services = backend_services
|
|
self.system_info = system_info
|
|
|
|
def add_args(self, parser):
|
|
"""Add tuxborn-auto arguments to the main parser.
|
|
|
|
Args:
|
|
parser: The main ArgumentParser
|
|
"""
|
|
parser.add_argument(
|
|
"--tuxborn-auto",
|
|
action="store_true",
|
|
help="Run the Tuxborn Automatic Installer non-interactively (for GUI integration)"
|
|
)
|
|
parser.add_argument(
|
|
"--install-dir",
|
|
type=str,
|
|
help="Install directory for Tuxborn (required with --tuxborn-auto)"
|
|
)
|
|
parser.add_argument(
|
|
"--download-dir",
|
|
type=str,
|
|
help="Downloads directory for Tuxborn (required with --tuxborn-auto)"
|
|
)
|
|
parser.add_argument(
|
|
"--modlist-name",
|
|
type=str,
|
|
default="Tuxborn",
|
|
help="Modlist name (optional, defaults to 'Tuxborn')"
|
|
)
|
|
|
|
def execute(self, args) -> int:
|
|
"""Execute the tuxborn-auto command.
|
|
|
|
Args:
|
|
args: Parsed command-line arguments
|
|
|
|
Returns:
|
|
Exit code (0 for success, 1 for failure)
|
|
"""
|
|
logger.info("Starting Tuxborn Automatic Installer (GUI integration mode)")
|
|
|
|
try:
|
|
# Set up logging redirection (copied from original)
|
|
self._setup_tee_logging()
|
|
|
|
# Build context from args
|
|
context = self._build_context_from_args(args)
|
|
|
|
# Validate required fields
|
|
if not self._validate_context(context):
|
|
return 1
|
|
|
|
# Use legacy implementation for now - will migrate to backend services later
|
|
result = self._execute_legacy_tuxborn(context)
|
|
|
|
logger.info("Finished Tuxborn Automatic Installer")
|
|
return result
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to run Tuxborn installer: {e}")
|
|
print(f"{COLOR_ERROR}Tuxborn installation failed: {e}{COLOR_RESET}")
|
|
return 1
|
|
finally:
|
|
# Restore stdout/stderr
|
|
self._restore_stdout_stderr()
|
|
|
|
def _build_context_from_args(self, args) -> dict:
|
|
"""Build context dictionary from command arguments.
|
|
|
|
Args:
|
|
args: Parsed command-line arguments
|
|
|
|
Returns:
|
|
Context dictionary
|
|
"""
|
|
install_dir = getattr(args, 'install_dir', None)
|
|
download_dir = getattr(args, 'download_dir', None)
|
|
modlist_name = getattr(args, 'modlist_name', 'Tuxborn')
|
|
machineid = 'Tuxborn/Tuxborn'
|
|
|
|
# Try to get API key from saved config first, then environment variable
|
|
from jackify.backend.services.api_key_service import APIKeyService
|
|
api_key_service = APIKeyService()
|
|
api_key = api_key_service.get_saved_api_key()
|
|
if not api_key:
|
|
api_key = os.environ.get('NEXUS_API_KEY')
|
|
|
|
resolution = getattr(args, 'resolution', None)
|
|
mo2_exe_path = getattr(args, 'mo2_exe_path', None)
|
|
skip_confirmation = True # Always true in GUI mode
|
|
|
|
context = {
|
|
'machineid': machineid,
|
|
'modlist_name': modlist_name,
|
|
'install_dir': install_dir,
|
|
'download_dir': download_dir,
|
|
'nexus_api_key': api_key,
|
|
'skip_confirmation': skip_confirmation,
|
|
'resolution': resolution,
|
|
'mo2_exe_path': mo2_exe_path,
|
|
}
|
|
|
|
# PATCH: Always set modlist_value and modlist_source for Tuxborn workflow
|
|
context['modlist_value'] = 'Tuxborn/Tuxborn'
|
|
context['modlist_source'] = 'identifier'
|
|
|
|
return context
|
|
|
|
def _validate_context(self, context: dict) -> bool:
|
|
"""Validate Tuxborn context.
|
|
|
|
Args:
|
|
context: Tuxborn context dictionary
|
|
|
|
Returns:
|
|
True if valid, False otherwise
|
|
"""
|
|
required_keys = ['modlist_name', 'install_dir', 'download_dir', 'nexus_api_key']
|
|
missing = [k for k in required_keys if not context.get(k)]
|
|
|
|
if missing:
|
|
print(f"{COLOR_ERROR}Missing required arguments for --tuxborn-auto.\\n"
|
|
f"--install-dir, --download-dir, and NEXUS_API_KEY (env, 32+ chars) are required.{COLOR_RESET}")
|
|
return False
|
|
|
|
return True
|
|
|
|
def _setup_tee_logging(self):
|
|
"""Set up TEE logging (copied from original implementation)."""
|
|
import shutil
|
|
|
|
# TEE logging setup & log rotation (copied from original)
|
|
class TeeStdout:
|
|
def __init__(self, *files):
|
|
self.files = files
|
|
def write(self, data):
|
|
for f in self.files:
|
|
f.write(data)
|
|
f.flush()
|
|
def flush(self):
|
|
for f in self.files:
|
|
f.flush()
|
|
|
|
log_dir = Path.home() / "Jackify" / "logs"
|
|
log_dir.mkdir(parents=True, exist_ok=True)
|
|
workflow_log_path = log_dir / "tuxborn_workflow.log"
|
|
|
|
# Log rotation: keep last 3 logs, 1KB each (for testing)
|
|
max_logs = 3
|
|
max_size = 1024 # 1KB for testing
|
|
if workflow_log_path.exists() and workflow_log_path.stat().st_size > max_size:
|
|
for i in range(max_logs, 0, -1):
|
|
prev = log_dir / f"tuxborn_workflow.log.{i-1}" if i > 1 else workflow_log_path
|
|
dest = log_dir / f"tuxborn_workflow.log.{i}"
|
|
if prev.exists():
|
|
if dest.exists():
|
|
dest.unlink()
|
|
prev.rename(dest)
|
|
|
|
self.workflow_log = open(workflow_log_path, 'a')
|
|
self.orig_stdout, self.orig_stderr = sys.stdout, sys.stderr
|
|
sys.stdout = TeeStdout(sys.stdout, self.workflow_log)
|
|
sys.stderr = TeeStdout(sys.stderr, self.workflow_log)
|
|
|
|
def _restore_stdout_stderr(self):
|
|
"""Restore original stdout/stderr."""
|
|
if hasattr(self, 'orig_stdout'):
|
|
sys.stdout = self.orig_stdout
|
|
sys.stderr = self.orig_stderr
|
|
if hasattr(self, 'workflow_log'):
|
|
self.workflow_log.close()
|
|
|
|
def _execute_legacy_tuxborn(self, context: dict) -> int:
|
|
"""Execute Tuxborn using legacy implementation.
|
|
|
|
Args:
|
|
context: Tuxborn context dictionary
|
|
|
|
Returns:
|
|
Exit code
|
|
"""
|
|
# Import backend services
|
|
from jackify.backend.core.modlist_operations import ModlistInstallCLI
|
|
from jackify.backend.handlers.menu_handler import MenuHandler
|
|
|
|
# Create legacy handler instances
|
|
menu_handler = MenuHandler()
|
|
modlist_cli = ModlistInstallCLI(
|
|
menu_handler=menu_handler,
|
|
steamdeck=self.system_info.get('is_steamdeck', False)
|
|
)
|
|
|
|
confirmed_context = modlist_cli.run_discovery_phase(context_override=context)
|
|
if confirmed_context:
|
|
menu_handler.logger.info("Tuxborn discovery confirmed by GUI. Proceeding to configuration/installation.")
|
|
modlist_cli.configuration_phase()
|
|
|
|
# Handle GUI integration prompts (copied from original)
|
|
print('[PROMPT:RESTART_STEAM]')
|
|
if os.environ.get('JACKIFY_GUI_MODE'):
|
|
input() # Wait for GUI to send confirmation, no CLI prompt
|
|
else:
|
|
answer = input('Restart Steam automatically now? (Y/n): ')
|
|
# ... handle answer as before ...
|
|
|
|
print('[PROMPT:MANUAL_STEPS]')
|
|
if os.environ.get('JACKIFY_GUI_MODE'):
|
|
input() # Wait for GUI to send confirmation, no CLI prompt
|
|
else:
|
|
input('Once you have completed ALL the steps above, press Enter to continue...')
|
|
|
|
return 0
|
|
else:
|
|
menu_handler.logger.info("Tuxborn discovery/confirmation cancelled or failed (GUI mode).")
|
|
print(f"{COLOR_INFO}Tuxborn installation cancelled or not confirmed.{COLOR_RESET}")
|
|
return 1 |