diff --git a/scripts/control_from_mobile.sh b/scripts/control_from_mobile.sh new file mode 100755 index 0000000..b5c9129 --- /dev/null +++ b/scripts/control_from_mobile.sh @@ -0,0 +1,416 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +SCRIPT_NAME="$(basename "$0")" +CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/control-from-mobile" +STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/control-from-mobile" +PASSWORD_FILE="$CONFIG_DIR/vnc.pass" +ENV_FILE="$CONFIG_DIR/env" +RUNNER_FILE="$CONFIG_DIR/start-x11vnc.sh" +SERVICE_NAME="control-from-mobile.service" +SYSTEMD_USER_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user" +SERVICE_FILE="$SYSTEMD_USER_DIR/$SERVICE_NAME" +DEFAULT_DISPLAY="${DISPLAY:-:0}" +DEFAULT_PORT=5901 +DEFAULT_BIND_ADDR="0.0.0.0" +readonly SCRIPT_NAME CONFIG_DIR STATE_DIR PASSWORD_FILE ENV_FILE RUNNER_FILE SERVICE_NAME SYSTEMD_USER_DIR SERVICE_FILE DEFAULT_DISPLAY DEFAULT_PORT DEFAULT_BIND_ADDR + +usage() { + cat <<'EOF' +Usage: control_from_mobile.sh [options] + +Commands: + setup [--force-password] Install dependencies, create configs, and write the systemd user service. + start Start the VNC bridge (via systemd user unit when available). + stop Stop the bridge. + restart Restart the bridge. + status Show whether the bridge service is running. + enable Enable the service so it starts after login. + disable Disable automatic start after login. + info Show connection details and Android app suggestions. + uninstall Stop the service and remove generated files (keeps password unless --purge). + help Show this message. + +Options: + --force-password Regenerate the VNC password during setup. + --purge Delete the stored VNC password during uninstall. + +Examples: + ./control_from_mobile.sh setup + ./control_from_mobile.sh start + ./control_from_mobile.sh info + +EOF +} + +log() { + printf '[%s] %s\n' "$SCRIPT_NAME" "$*" +} + +warn() { + printf '[%s] %s\n' "$SCRIPT_NAME" "$*" >&2 +} + +die() { + warn "$*" + exit 1 +} + +require_non_root() { + if [[ "${EUID:-$(id -u)}" -eq 0 ]]; then + die "Run this script as a regular desktop user, not root." + fi +} + +prompt_yes_no() { + local prompt="$1" + local reply + read -r -p "$prompt [y/N]: " reply + case "$reply" in + [Yy][Ee][Ss]|[Yy]) return 0 ;; + *) return 1 ;; + esac +} + +ensure_directories() { + mkdir -p "$CONFIG_DIR" "$STATE_DIR" "$SYSTEMD_USER_DIR" + chmod 700 "$CONFIG_DIR" +} + +missing_commands() { + local missing=() + for cmd in "$@"; do + if ! command -v "$cmd" >/dev/null 2>&1; then + missing+=("$cmd") + fi + done + printf '%s\n' "${missing[@]-}" +} + +install_dependencies() { + if ! command -v systemctl >/dev/null 2>&1; then + die "systemctl not found. Install systemd before running this script." + fi + + local required=(x11vnc qrencode ssh) + local needed=() + mapfile -t needed < <(missing_commands "${required[@]}") + if (( ${#needed[@]} == 0 )); then + log "All required packages (${required[*]}) are present." + return + fi + + if command -v pacman >/dev/null 2>&1; then + log "Installing missing packages: ${needed[*]}" + sudo pacman -S --needed --noconfirm "${needed[@]}" + else + die "Missing commands (${needed[*]}). Install them manually and rerun setup." + fi +} + +create_password_file() { + local force=${1:-0} + if [[ -f "$PASSWORD_FILE" && "$force" -ne 1 ]]; + then + log "Using existing VNC password file at $PASSWORD_FILE" + return + fi + + if [[ -f "$PASSWORD_FILE" ]]; then + if ! prompt_yes_no "Regenerate the stored VNC password?"; then + log "Keeping existing password." + return + fi + fi + + local password confirm generated=0 + read -rsp "Enter VNC password (leave blank to auto-generate): " password + printf '\n' + if [[ -z "$password" ]]; then + generated=1 + password=$(LC_ALL=C tr -dc 'A-Za-z0-9' /dev/null + install -m 600 "$tmp" "$PASSWORD_FILE" + rm -f "$tmp" + + if (( generated == 0 )); then + log "Password stored securely at $PASSWORD_FILE (hashed)." + else + log "Please write down the generated password; it will be needed on your Android device." + fi +} + +create_env_file() { + if [[ -f "$ENV_FILE" ]]; then + return + fi + cat >"$ENV_FILE" <"$RUNNER_FILE" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' + +CONFIG_DIR="$(dirname "$(readlink -f "$0")")" +PASSWORD_FILE="$CONFIG_DIR/vnc.pass" +ENV_FILE="$CONFIG_DIR/env" +STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/control-from-mobile" +mkdir -p "$STATE_DIR" + +if [[ ! -f "$PASSWORD_FILE" ]]; then + echo "Missing VNC password file at $PASSWORD_FILE" >&2 + exit 1 +fi + +if [[ -f "$ENV_FILE" ]]; then + # shellcheck disable=SC1090 + source "$ENV_FILE" +fi + +X11_DISPLAY="${X11_DISPLAY:-${DISPLAY:-:0}}" +VNC_PORT="${VNC_PORT:-5901}" +VNC_BIND_ADDR="${VNC_BIND_ADDR:-0.0.0.0}" + +LOG_FILE="$STATE_DIR/x11vnc.log" +exec /usr/bin/x11vnc \ + -display "$X11_DISPLAY" \ + -rfbport "$VNC_PORT" \ + -listen "$VNC_BIND_ADDR" \ + -forever \ + -shared \ + -auth guess \ + -rfbauth "$PASSWORD_FILE" \ + -noxdamage \ + -repeat \ + -ncache 10 \ + -ncache_cr \ + -o "$LOG_FILE" +EOF + chmod 700 "$RUNNER_FILE" +} + +create_service_file() { + cat >"$SERVICE_FILE" </dev/null 2>&1; then + while IFS= read -r line; do + [[ -z "$line" ]] && continue + ip_list+=("$line") + done < <(hostname -I 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]' || true) + fi + + if (( ${#ip_list[@]} > 0 )); then + log "Detected LAN IPs:" + for ip in "${ip_list[@]}"; do + printf ' - %s\n' "$ip" + done + else + warn "Could not detect LAN IPs." + fi + + printf '\nRecommended Android clients (FOSS):\n' + printf ' • bVNC (available on F-Droid) — supports full control.\n' + printf ' • Termux + OpenSSH for establishing an SSH tunnel when exposing only on 127.0.0.1.\n' + printf '\nConnect via VNC:\n' + printf ' Host: \n Port: %s\n Password: \n' "$port" + + local qr_host + if (( ${#ip_list[@]} > 0 )); then + qr_host="${ip_list[0]}" + else + qr_host="$bind_addr" + if [[ "$qr_host" == "0.0.0.0" || "$qr_host" == "::" ]]; then + qr_host="127.0.0.1" + fi + warn "Using fallback host $qr_host for QR code; replace with an accessible IP if needed." + fi + + if command -v qrencode >/dev/null 2>&1; then + printf '\nConnection QR (vnc://%s:%s):\n' "$qr_host" "$port" + qrencode -o - "vnc://$qr_host:$port" -t ASCII || true + else + warn "qrencode not found; reinstall qrencode to get QR codes." + fi + + printf '\nFor encrypted access outside your LAN, use Termux on Android:\n' + printf ' ssh -L %s:localhost:%s @\n' "$port" "$port" + printf 'Then point bVNC to 127.0.0.1:%s.\n' "$port" +} + +uninstall_files() { + local purge_password=${1:-0} + stop_service + disable_service + rm -f "$SERVICE_FILE" + rm -f "$RUNNER_FILE" + rm -f "$ENV_FILE" + if (( purge_password )); then + rm -f "$PASSWORD_FILE" + log "Removed password file." + fi + reload_user_daemon + log "Removed generated files." +} + +main() { + require_non_root + + local cmd="${1:-}" + shift || true + + case "$cmd" in + setup) + local force=0 + if [[ "${1:-}" == "--force-password" ]]; then + force=1 + shift || true + fi + ensure_directories + install_dependencies + create_password_file "$force" + create_env_file + create_runner_script + create_service_file + reload_user_daemon + log "Setup complete. Start the service with: $SCRIPT_NAME start" + ;; + start) + start_service + show_info + ;; + stop) + stop_service + ;; + restart) + stop_service + start_service + ;; + status) + status_service + ;; + enable) + enable_service + ;; + disable) + disable_service + ;; + info) + show_info + ;; + uninstall) + local purge=0 + if [[ "${1:-}" == "--purge" ]]; then + purge=1 + shift || true + fi + uninstall_files "$purge" + ;; + help|--help|-h|"" ) + usage + ;; + *) + usage + die "Unknown command: $cmd" + ;; + esac +} + +main "$@" diff --git a/scripts/fix_virtualbox.sh b/scripts/fix_virtualbox.sh new file mode 100644 index 0000000..5500ce9 --- /dev/null +++ b/scripts/fix_virtualbox.sh @@ -0,0 +1,196 @@ +#!/usr/bin/env bash + +set -euo pipefail + +on_error() { + local exit_code=$? + local line_number=$1 + printf '\033[1;31m[ERROR]\033[0m Unexpected failure at line %s (exit code %s).\n' "${line_number}" "${exit_code}" >&2 +} +trap 'on_error ${LINENO}' ERR + +log_info() { + printf '\033[1;34m[INFO]\033[0m %s\n' "$*" +} + +log_warn() { + printf '\033[1;33m[WARN]\033[0m %s\n' "$*" +} + +log_error() { + printf '\033[1;31m[ERROR]\033[0m %s\n' "$*" >&2 + exit 1 +} + +require_root() { + if [[ "${EUID}" -ne 0 ]]; then + log_error "This script must be run as root (try again with sudo)." + fi +} + +require_pacman() { + if ! command -v pacman >/dev/null 2>&1; then + log_error "pacman not found. This script is intended for Arch Linux systems." + fi +} + +detect_kernel_release() { + uname -r +} + +select_host_package() { + local kernel_release=$1 + case "${kernel_release}" in + *-lts) + echo "virtualbox-host-modules-lts" + ;; + *-arch*) + echo "virtualbox-host-modules-arch" + ;; + *) + echo "virtualbox-host-dkms" + ;; + esac +} + +collect_kernel_headers() { + local -a headers=() + local kernel_pkg header_pkg + for kernel_pkg in linux linux-lts linux-zen linux-hardened; do + if pacman -Q "${kernel_pkg}" >/dev/null 2>&1; then + header_pkg="${kernel_pkg}-headers" + headers+=("${header_pkg}") + fi + done + if [[ ${#headers[@]} -gt 0 ]]; then + printf '%s\n' "${headers[@]}" + fi +} + +maybe_remove_conflicting_host_packages() { + local selected_package=$1 + local -a candidates=("virtualbox-host-dkms" "virtualbox-host-modules-arch" "virtualbox-host-modules-lts") + local pkg + for pkg in "${candidates[@]}"; do + if [[ "${pkg}" != "${selected_package}" ]] && pacman -Q "${pkg}" >/dev/null 2>&1; then + log_warn "Removing conflicting package ${pkg} before installing ${selected_package}." + pacman -Rsn "${PACMAN_REMOVE_FLAGS[@]}" "${pkg}" + fi + done +} + +install_packages() { + local -a packages=() + local -a headers=() + local host_package=$1 + shift + if [[ $# -gt 0 ]]; then + mapfile -t headers < <(printf '%s\n' "$@" | sort -u) + fi + packages+=("virtualbox" "virtualbox-guest-iso" "${host_package}") + if [[ "${host_package}" == "virtualbox-host-dkms" ]]; then + packages+=("dkms") + fi + if [[ ${#headers[@]} -gt 0 ]]; then + packages+=("${headers[@]}") + fi + log_info "Installing packages: ${packages[*]}" + pacman -S "${PACMAN_INSTALL_FLAGS[@]}" "${packages[@]}" +} + +rebuild_virtualbox_modules() { + local host_package=$1 + if [[ "${host_package}" == "virtualbox-host-dkms" ]]; then + if command -v dkms >/dev/null 2>&1; then + log_info "Rebuilding VirtualBox DKMS modules for all installed kernels." + dkms autoinstall + else + log_warn "dkms command not found; skipping DKMS rebuild." + fi + fi +} + +reload_virtualbox_modules() { + log_info "Loading VirtualBox kernel modules." + if [[ -x /sbin/rcvboxdrv ]]; then + /sbin/rcvboxdrv setup || log_warn "rcvboxdrv reported an issue while setting up modules." + elif [[ -x /usr/lib/virtualbox/vboxdrv.sh ]]; then + /usr/lib/virtualbox/vboxdrv.sh setup || log_warn "vboxdrv.sh reported an issue while setting up modules." + fi + + local -a modules=(vboxdrv vboxnetflt vboxnetadp vboxpci) + local mod + for mod in "${modules[@]}"; do + if ! lsmod | awk '{print $1}' | grep -Fxq "${mod}"; then + if ! modprobe "${mod}" >/dev/null 2>&1; then + log_warn "Module ${mod} failed to load; check dmesg for details." + fi + fi + done + + if ! lsmod | awk '{print $1}' | grep -Fxq "vboxdrv"; then + log_error "VirtualBox kernel driver (vboxdrv) failed to load. Review /var/log and dmesg output for clues." + fi + log_info "VirtualBox kernel driver loaded successfully." +} + +warn_if_secure_boot_enabled() { + local secure_boot_file + if [[ -d /sys/firmware/efi/efivars ]]; then + secure_boot_file=$(find /sys/firmware/efi/efivars -maxdepth 1 -name 'SecureBoot-*' -print -quit 2>/dev/null || true) + if [[ -n "${secure_boot_file}" && -r "${secure_boot_file}" ]]; then + local state + state=$(hexdump -n 1 -s 4 -e '1 "%d"' "${secure_boot_file}" 2>/dev/null || echo "0") + if [[ "${state}" == "1" ]]; then + log_warn "EFI Secure Boot appears to be enabled. You may need to sign VirtualBox modules manually." + fi + fi + fi +} + +remind_group_membership() { + local invoking_user=${SUDO_USER:-} + if [[ -n "${invoking_user}" && "${invoking_user}" != "root" ]]; then + if ! id -nG "${invoking_user}" | grep -qw "vboxusers"; then + log_warn "User ${invoking_user} is not in the vboxusers group. Add them with: sudo gpasswd -a ${invoking_user} vboxusers" + else + log_info "User ${invoking_user} is already in the vboxusers group." + fi + fi +} + +main() { + require_root + require_pacman + + PACMAN_INSTALL_FLAGS=(--needed) + PACMAN_REMOVE_FLAGS=() + if [[ "${PACMAN_CONFIRM:-0}" == "1" ]]; then + log_info "PACMAN_CONFIRM=1 detected; pacman will prompt for confirmation." + else + PACMAN_INSTALL_FLAGS+=(--noconfirm) + PACMAN_REMOVE_FLAGS+=(--noconfirm) + fi + + local kernel_release host_package + kernel_release=$(detect_kernel_release) + log_info "Detected running kernel: ${kernel_release}" + host_package=$(select_host_package "${kernel_release}") + log_info "Selected VirtualBox host package: ${host_package}" + + mapfile -t kernel_headers < <(collect_kernel_headers) + if [[ "${host_package}" == "virtualbox-host-dkms" && ${#kernel_headers[@]} -eq 0 ]]; then + log_warn "No matching kernel headers detected. Ensure you've installed headers for your kernel so DKMS can build modules." + fi + + maybe_remove_conflicting_host_packages "${host_package}" + install_packages "${host_package}" "${kernel_headers[@]}" + rebuild_virtualbox_modules "${host_package}" + reload_virtualbox_modules + warn_if_secure_boot_enabled + remind_group_membership + + log_info "VirtualBox installation and driver setup complete." +} + +main "$@" diff --git a/scripts/pacman_wrapper.sh b/scripts/pacman_wrapper.sh index 3fee140..5ef494a 100755 --- a/scripts/pacman_wrapper.sh +++ b/scripts/pacman_wrapper.sh @@ -120,7 +120,6 @@ function is_blocked_package_name() { # Explicitly blocked names list local blocked=( - "freetube-bin" "virtualbox" "virtualbox-host-modules-arch" "virtualbox-guest-iso" "virtualbox-ext-vnc" "virtualbox-guest-utils" "virtualbox-host-dkms" "brave" "brave-bin" "freetube" "seamonkey-bin" "seamonkey" "min-browser-bin" "min-browser" "beaker-browser" "catalyst-browser-bin" "hamsket" "min" "vieb-bin" "yt-dlp" "yt-dlp-git" "stremio" "stremio-git" "angelfish" "dooble" "eric" "falkon" "fiery" "maui" "konqueror" "liri" "otter" "quotebrowser" "beaker" "catalyst" "badwolf" "eolie" "epiphany" "surf" "uzbl" "vimb" "vimb-git" "web-browser" "web-browser-git" @@ -222,30 +221,6 @@ function check_for_steam() { return 1 # No steam package found } -# Function to check if user is trying to install virtualbox (always challenge-eligible package) -function check_for_virtualbox() { - # List of packages that require challenge (virtualbox packages) - local virtualbox_packages=("virtualbox" "virtualbox-host-modules-arch" "virtualbox-guest-iso" "virtualbox-ext-vnc" "virtualbox-guest-utils" "virtualbox-host-dkms") - - # Check if the command is an installation command - if [[ "$1" == "-S" || "$1" == "-Sy" || "$1" == "-Syu" || "$1" == "-Syyu" || "$1" == "-U" ]]; then - # Check all arguments - for arg in "$@"; do - # Strip repository prefix if present (like extra/ or community/) - local package_name="${arg##*/}" - - # Check if argument matches virtualbox - for package in "${virtualbox_packages[@]}"; do - if [[ "$arg" == "$package" || "$arg" == *"/$package-"* || "$arg" == *"/$package/"* || - "$arg" == *"/$package" || "$package_name" == "$package" ]]; then - return 0 # VirtualBox package found - fi - done - done - fi - return 1 # No virtualbox package found -} - # Function to check if current day is a weekday (after 4PM Friday until midnight Sunday) function is_weekday() { local day_of_week=$(date +%u) # %u gives 1-7 (Monday is 1, Sunday is 7) @@ -535,14 +510,6 @@ if check_for_always_blocked "$@"; then exit 1 fi -# Check for virtualbox (always challenge-eligible package) -if check_for_virtualbox "$@"; then - prompt_for_virtualbox_challenge - if [[ $? -ne 0 ]]; then - exit 1 - fi -fi - # Check for steam (challenge-eligible package) if check_for_steam "$@"; then prompt_for_steam_challenge