#!/usr/bin/env bash set -u CONFIG_DIR="${FFX_CONFIG_DIR:-${HOME}/.local/etc}" CONFIG_FILE="${FFX_CONFIG_FILE:-${CONFIG_DIR}/ffx.json}" VAR_DIR="${FFX_VAR_DIR:-${HOME}/.local/var/ffx}" LOG_DIR="${FFX_LOG_DIR:-${HOME}/.local/var/log}" DATABASE_FILE="${FFX_DATABASE_FILE:-${VAR_DIR}/ffx.db}" CHECK_ONLY=0 WITH_TESTS=0 MUTATIONS=0 INSTALL_FAILURES=0 READINESS_FAILURES=0 MISSING_REQUIRED_SYSTEM=() MISSING_OPTIONAL_SYSTEM=() COLOR_RESET="" COLOR_GREEN="" COLOR_YELLOW="" COLOR_RED="" if [ -t 1 ]; then COLOR_RESET="$(printf '\033[0m')" COLOR_GREEN="$(printf '\033[32m')" COLOR_YELLOW="$(printf '\033[33m')" COLOR_RED="$(printf '\033[31m')" fi usage() { cat </dev/null 2>&1 } check_command_component() { command_exists "$2" } check_tmdb_key() { [ -n "${TMDB_API_KEY:-}" ] } check_seeded_dir() { [ -d "$1" ] } check_seeded_file() { [ -f "$1" ] } component_detail() { case "$1" in git|python3|ffmpeg|ffprobe|cpulimit) command -v "$1" || printf "command '%s' not found" "$1" ;; tmdb-key) if check_tmdb_key; then printf 'TMDB_API_KEY is set' else printf 'TMDB_API_KEY is unset; TMDB-backed flows will be skipped or fail' fi ;; config-dir) if check_seeded_dir "${CONFIG_DIR}"; then printf '%s' "${CONFIG_DIR}" else printf 'missing; prep can create it' fi ;; var-dir) if check_seeded_dir "${VAR_DIR}"; then printf '%s' "${VAR_DIR}" else printf 'missing; prep can create it' fi ;; log-dir) if check_seeded_dir "${LOG_DIR}"; then printf '%s' "${LOG_DIR}" else printf 'missing; prep can create it' fi ;; ffx-config) if check_seeded_file "${CONFIG_FILE}"; then printf '%s' "${CONFIG_FILE}" else printf 'missing; prep can seed a default non-destructively' fi ;; esac } report_toolchain_component() { local label="$1" local command_name="$2" local required="$3" if check_command_component "${label}" "${command_name}" "${required}"; then report_component ok "${label}" "$(component_detail "${command_name}")" else if [ "${required}" = "required" ]; then report_component failed "${label}" "$(component_detail "${command_name}")" MISSING_REQUIRED_SYSTEM+=("${command_name}") READINESS_FAILURES=$((READINESS_FAILURES + 1)) else report_component warn "${label}" "$(component_detail "${command_name}")" MISSING_OPTIONAL_SYSTEM+=("${command_name}") fi fi } report_tmdb_component() { if check_tmdb_key; then report_component ok "TMDB API key" "$(component_detail tmdb-key)" else report_component warn "TMDB API key" "$(component_detail tmdb-key)" fi } report_seeded_component() { local label="$1" local key="$2" local required="$3" local ok=1 case "${key}" in config-dir) check_seeded_dir "${CONFIG_DIR}" || ok=0 ;; var-dir) check_seeded_dir "${VAR_DIR}" || ok=0 ;; log-dir) check_seeded_dir "${LOG_DIR}" || ok=0 ;; ffx-config) check_seeded_file "${CONFIG_FILE}" || ok=0 ;; esac if [ "${ok}" -eq 1 ]; then report_component ok "${label}" "$(component_detail "${key}")" else if [ "${required}" = "required" ]; then report_component failed "${label}" "$(component_detail "${key}")" READINESS_FAILURES=$((READINESS_FAILURES + 1)) else report_component warn "${label}" "$(component_detail "${key}")" fi fi } print_dependency_status() { READINESS_FAILURES=0 MISSING_REQUIRED_SYSTEM=() MISSING_OPTIONAL_SYSTEM=() echo "Dependency status:" report_toolchain_component "git" "git" "required" report_toolchain_component "python3" "python3" "required" report_toolchain_component "ffmpeg" "ffmpeg" "required" report_toolchain_component "ffprobe" "ffprobe" "required" report_toolchain_component "cpulimit" "cpulimit" "required" report_tmdb_component } print_seeded_file_status() { echo "Seeded local files:" report_seeded_component "Config dir" "config-dir" "optional" report_seeded_component "Var dir" "var-dir" "optional" report_seeded_component "Log dir" "log-dir" "optional" report_seeded_component "ffx config" "ffx-config" "optional" } print_test_package_status() { if [ "${WITH_TESTS}" -eq 0 ]; then return 0 fi echo "Test environment notes:" report_component ok "system test dependencies" "no extra system packages beyond the standard runtime toolchain" report_component ok "Python test packages" "install via tools/setup.sh --with-tests" } detect_package_manager() { if command_exists apt-get; then printf 'apt-get\n' return 0 fi if command_exists pacman; then printf 'pacman\n' return 0 fi return 1 } run_root_command() { if [ "${EUID}" -eq 0 ]; then "$@" elif command_exists sudo; then sudo "$@" else return 1 fi } install_system_requirements() { local package_manager if ! package_manager="$(detect_package_manager)"; then printf 'No supported package manager found for automatic preparation.\n' >&2 INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) return 1 fi case "${package_manager}" in apt-get) printf 'Installing missing system dependencies via apt-get...\n' if ! run_root_command apt-get update; then printf 'apt-get update failed.\n' >&2 INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) return 1 fi if ! run_root_command apt-get install -y git python3 ffmpeg cpulimit; then printf 'apt-get install failed.\n' >&2 INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) return 1 fi ;; pacman) printf 'Installing missing system dependencies via pacman...\n' if ! run_root_command pacman -Sy --noconfirm git python ffmpeg cpulimit; then printf 'pacman install failed.\n' >&2 INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) return 1 fi ;; esac MUTATIONS=$((MUTATIONS + 1)) return 0 } seed_default_config() { if [ "${CHECK_ONLY}" -eq 1 ]; then return 0 fi local created_any=0 if [ ! -d "${CONFIG_DIR}" ]; then printf 'Creating config dir at %s...\n' "${CONFIG_DIR}" if ! mkdir -p "${CONFIG_DIR}"; then printf 'Failed to create config dir at %s.\n' "${CONFIG_DIR}" >&2 INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) return 1 fi created_any=1 fi if [ ! -d "${VAR_DIR}" ]; then printf 'Creating var dir at %s...\n' "${VAR_DIR}" if ! mkdir -p "${VAR_DIR}"; then printf 'Failed to create var dir at %s.\n' "${VAR_DIR}" >&2 INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) return 1 fi created_any=1 fi if [ ! -d "${LOG_DIR}" ]; then printf 'Creating log dir at %s...\n' "${LOG_DIR}" if ! mkdir -p "${LOG_DIR}"; then printf 'Failed to create log dir at %s.\n' "${LOG_DIR}" >&2 INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) return 1 fi created_any=1 fi if [ ! -f "${CONFIG_FILE}" ]; then printf 'Seeding ffx config at %s...\n' "${CONFIG_FILE}" if ! cat >"${CONFIG_FILE}" <&2 INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) return 1 fi created_any=1 fi if [ "${created_any}" -eq 1 ]; then MUTATIONS=$((MUTATIONS + 1)) fi return 0 } parse_args() { while [ "$#" -gt 0 ]; do case "$1" in --check) CHECK_ONLY=1 ;; --with-tests) WITH_TESTS=1 ;; --help|-h) usage exit 0 ;; *) printf 'Unknown option: %s\n\n' "$1" >&2 usage >&2 exit 2 ;; esac shift done } main() { parse_args "$@" print_dependency_status if [ "${CHECK_ONLY}" -eq 0 ] && [ "${#MISSING_REQUIRED_SYSTEM[@]}" -gt 0 ]; then install_system_requirements echo print_dependency_status fi echo print_seeded_file_status echo print_test_package_status if [ "${CHECK_ONLY}" -eq 0 ]; then seed_default_config echo print_dependency_status echo print_seeded_file_status echo print_test_package_status fi echo if [ "${INSTALL_FAILURES}" -gt 0 ]; then echo "One or more install steps failed; see the status checks above." >&2 return 1 fi if [ "${READINESS_FAILURES}" -gt 0 ]; then if [ "${CHECK_ONLY}" -eq 1 ]; then echo "Required system prerequisites are incomplete." >&2 else echo "Required components are still missing after preparation." >&2 fi return 1 fi if [ "${CHECK_ONLY}" -eq 1 ]; then echo "The FFX preparation environment is ready." elif [ "${MUTATIONS}" -gt 0 ]; then echo "The FFX preparation environment is ready." else echo "The FFX preparation environment is already prepared." fi return 0 } main "$@"