From f8bb6c4c51786ee51569d631493fcd16f71e3e47 Mon Sep 17 00:00:00 2001 From: thehack904 <35552907+thehack904@users.noreply.github.com> Date: Thu, 16 Oct 2025 07:19:54 -0500 Subject: [PATCH 01/30] Unified Linux Install for Debian / RHEL based distros --- retroiptv_linux.sh | 452 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 452 insertions(+) create mode 100644 retroiptv_linux.sh diff --git a/retroiptv_linux.sh b/retroiptv_linux.sh new file mode 100644 index 0000000..e6d71fe --- /dev/null +++ b/retroiptv_linux.sh @@ -0,0 +1,452 @@ +#!/usr/bin/env bash +# retroiptv_linux.sh — Unified installer/updater/uninstaller for RetroIPTVGuide (Linux only) +# Version: 3.2.0 +# License: Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) +# +# Usage: +# sudo ./retroiptv_linux.sh install [--agree|-a] [--yes|-y] +# sudo ./retroiptv_linux.sh uninstall [--yes|-y] +# sudo ./retroiptv_linux.sh update +# ./retroiptv_linux.sh --help +# +# Notes: +# - Designed for Debian/Ubuntu and RHEL-family (Rocky/Alma/CentOS Stream/Fedora). +# - Run with sudo for full install/uninstall. + +set -euo pipefail + +VERSION="3.2.0" +TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S") +LOGFILE="retroiptv_${TIMESTAMP}.log" + +# Log everything to file + console +exec > >(tee -a "$LOGFILE") 2>&1 + +# --- Banner --- +cat <<'EOF' +░█████████ ░██ ░██████░█████████ ░██████████░██ ░██ ░██████ ░██ ░██ +░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ +░██ ░██ ░███████ ░████████ ░██░████ ░███████ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░████████ ░███████ +░█████████ ░██ ░██ ░██ ░███ ░██ ░██ ░██ ░█████████ ░██ ░██ ░██ ░██ █████ ░██ ░██ ░██░██ ░██ ░██ ░██ +░██ ░██ ░█████████ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ██ ░██ ░██ ░██░██ ░██ ░█████████ +░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██░██ ░██ ░███ ░██ ░███ ░██░██ ░███ ░██ +░██ ░██ ░███████ ░████ ░██ ░███████ ░██████░██ ░██ ░███ ░█████░█ ░█████░██ ░██ ░█████░██ ░███████ + +EOF +printf "===========================================================================\n" +echo " RetroIPTVGuide | Linux Edition (Headless)" +printf "===========================================================================\n\n" + +echo "=== RetroIPTVGuide Unified Script (v$VERSION) ===" +echo "Start time: $(date)" +echo "Log file: $LOGFILE" + +# --- Globals --- +ACTION="${1:-}" +shift || true +AGREE_TERMS=false +AUTO_YES=false + +for arg in "$@"; do + case "$arg" in + --agree|-a) AGREE_TERMS=true ;; + --yes|-y) AUTO_YES=true ;; + esac +done + +if [[ $(id -u) -ne 0 ]]; then + echo "ERROR: This script must be run as root (use sudo)." + exit 1 +fi + +APP_USER="iptv" +APP_HOME="/home/$APP_USER" +APP_DIR="$APP_HOME/iptv-server" +SERVICE_NAME="iptv-server" +SYSTEMD_FILE="/etc/systemd/system/${SERVICE_NAME}.service" # /etc works on both Debian & RHEL for local units +LOG_DIR_LINUX="/var/log/iptv" + +# --- OS & package-manager detection --- +DISTRO_ID="" +if [[ -f /etc/os-release ]]; then + . /etc/os-release + DISTRO_ID=${ID,,} +fi + +PKG_MANAGER="" +PKG_INSTALL="" +PKG_UPDATE="" +PKG_REMOVE="" + +case "$DISTRO_ID" in + ubuntu|debian|raspbian) + PKG_MANAGER="apt" + PKG_INSTALL="apt-get install -y" + PKG_UPDATE="apt-get update -y" + PKG_REMOVE="apt-get remove -y" + ;; + rhel|centos|rocky|almalinux|fedora) + if command -v dnf >/dev/null 2>&1; then + PKG_MANAGER="dnf" + PKG_INSTALL="dnf install -y" + PKG_UPDATE="dnf -y makecache && dnf upgrade -y || true" + PKG_REMOVE="dnf remove -y" + else + PKG_MANAGER="yum" + PKG_INSTALL="yum install -y" + PKG_UPDATE="yum makecache -y && yum update -y || true" + PKG_REMOVE="yum remove -y" + fi + ;; + *) + echo "⚠️ Unsupported or unknown distribution. Proceeding best-effort." + if command -v apt-get >/dev/null 2>&1; then + PKG_MANAGER="apt"; PKG_INSTALL="apt-get install -y"; PKG_UPDATE="apt-get update -y"; PKG_REMOVE="apt-get remove -y" + elif command -v dnf >/dev/null 2>&1; then + PKG_MANAGER="dnf"; PKG_INSTALL="dnf install -y"; PKG_UPDATE="dnf -y makecache && dnf upgrade -y || true"; PKG_REMOVE="dnf remove -y" + elif command -v yum >/dev/null 2>&1; then + PKG_MANAGER="yum"; PKG_INSTALL="yum install -y"; PKG_UPDATE="yum makecache -y && yum update -y || true"; PKG_REMOVE="yum remove -y" + else + echo "❌ No supported package manager found (apt, dnf, yum)."; exit 1 + fi + ;; +esac + +# --- Helpers --- +usage() { + local SCRIPT_NAME + SCRIPT_NAME=$(basename "$0") + echo -e "\033[1;33mRetroIPTVGuide Unified Installer/Updater/Uninstaller (v$VERSION)\033[0m\n" + echo -e "Usage:" + echo -e " \033[1;32msudo $SCRIPT_NAME install [--agree|-a] [--yes|-y]\033[0m Install RetroIPTVGuide" + echo -e " \033[1;32msudo $SCRIPT_NAME uninstall [--yes|-y]\033[0m Uninstall RetroIPTVGuide" + echo -e " \033[1;32msudo $SCRIPT_NAME update\033[0m Update RetroIPTVGuide from GitHub" + echo -e " \033[1;32m$SCRIPT_NAME --help\033[0m Show this help\n" + echo "Flags:" + echo " --agree, -a Automatically agree to the license terms" + echo " --yes, -y Run non-interactively, auto-proceed on all prompts" + echo "" + echo "Examples:" + echo -e " \033[1;36msudo $SCRIPT_NAME install --agree --yes\033[0m" + echo -e " \033[1;36msudo $SCRIPT_NAME uninstall --yes\033[0m" + echo -e " \033[1;36msudo $SCRIPT_NAME update\033[0m\n" + echo "License: CC BY-NC-SA 4.0" +} + +agree_terms() { + if [[ "$AGREE_TERMS" == true ]]; then + echo "User pre-agreed to license terms via flag (--agree)." + return + fi + echo "" + echo "============================================================" + echo " RetroIPTVGuide Installer Agreement " + echo "============================================================" + echo "" + echo "This installer will perform the following actions:" + echo " - Create system user 'iptv' if not already present" + echo " - Ensure runtime dependencies are installed (Python, pip, git, curl, rsync, etc.)" + echo " - Copy project files into /home/iptv/iptv-server" + echo " - Create a Python virtual environment & install dependencies" + echo " - Create, enable, and start the iptv-server systemd service" + echo "" + echo "By continuing, you acknowledge and agree that:" + echo " - This software should ONLY be run on internal networks." + echo " - It must NOT be exposed to the public Internet." + echo " - You accept all risks; the author provides NO WARRANTY." + echo " - The author is NOT responsible for any damage, data loss," + echo " or security vulnerabilities created by this installation." + echo "" + read -rp "Do you agree to these terms? (yes/no): " agreement + if [[ "$agreement" != "yes" ]]; then + echo "Installation aborted by user." + exit 1 + fi +} + +ensure_packages() { + echo "=== Updating package cache..." + eval "$PKG_UPDATE" || true + + echo "=== Installing required packages..." + local pkgs=(git curl wget rsync python3 python3-pip unzip) + + # Debian-only helper for venv package; RHEL typically doesn't need a separate venv rpm + if [[ "$PKG_MANAGER" == "apt" ]]; then + pkgs+=(python3-venv) + fi + + # SQLite package name differs + if [[ "$PKG_MANAGER" == "apt" ]]; then + pkgs+=(sqlite3) + else + pkgs+=(sqlite) + fi + + # Tools that help with SELinux/firewalld on RHEL-based + if [[ "$PKG_MANAGER" == "dnf" || "$PKG_MANAGER" == "yum" ]]; then + pkgs+=(policycoreutils-python-utils firewalld) || true + fi + + eval "$PKG_INSTALL ${pkgs[*]}" +} + +ensure_user() { + echo "=== Creating system user ($APP_USER) if needed..." + if id "$APP_USER" &>/dev/null; then + echo "User $APP_USER already exists." + if [[ "$AUTO_YES" != true ]]; then + read -rp "Reuse existing user $APP_USER? (yes/no): " reuse + [[ "$reuse" != "yes" ]] && exit 1 + fi + else + if command -v adduser >/dev/null 2>&1; then + adduser --system --home "$APP_HOME" --group "$APP_USER" + else + # RHEL-friendly + useradd -r -m -d "$APP_HOME" -s /sbin/nologin -U "$APP_USER" + fi + echo "Created system user: $APP_USER" + fi +} + +clone_or_stage_project() { + echo "=== Preparing application directory: $APP_DIR" + mkdir -p "$APP_DIR" + chown -R "$APP_USER":"$APP_USER" "$APP_HOME" + + local TMP_CLONE_DIR="/tmp/retroiptvguide" + cd /tmp + + if [[ ! -f requirements.txt ]]; then + if command -v git >/dev/null 2>&1; then + echo "Project files not found locally — cloning RetroIPTVGuide (dev branch)..." + rm -rf "$TMP_CLONE_DIR" + git clone --depth 1 -b dev https://github.com/thehack904/RetroIPTVGuide.git "$TMP_CLONE_DIR" + SCRIPT_DIR=$(realpath "$TMP_CLONE_DIR") + else + echo "ERROR: requirements.txt not found and git is not installed." + echo "Please install git or run this script from within a cloned RetroIPTVGuide repo." + exit 1 + fi + else + SCRIPT_DIR=$(realpath "$(pwd)") + fi + + echo "Copying project files from: $SCRIPT_DIR" + rsync -a --delete --exclude 'venv' "$SCRIPT_DIR/" "$APP_DIR/" || { + echo "❌ ERROR: rsync failed to copy project files."; exit 1; } + chown -R "$APP_USER":"$APP_USER" "$APP_DIR" +} + +make_venv_and_install() { + echo "=== Ensuring Python virtual environment..." + if [[ -d "$APP_DIR/venv" && "$AUTO_YES" == true ]]; then + echo "Existing venv detected — auto-reusing (--yes)." + else + if sudo -u "$APP_USER" python3 -m venv "$APP_DIR/venv" 2>/dev/null; then + : + else + echo "⚠️ python3 -m venv failed — attempting virtualenv via pip." + sudo -u "$APP_USER" python3 -m pip install --user --upgrade virtualenv + sudo -u "$APP_USER" "$APP_HOME/.local/bin/virtualenv" "$APP_DIR/venv" + fi + fi + + echo "=== Installing Python dependencies..." + sudo -u "$APP_USER" "$APP_DIR/venv/bin/pip" install --upgrade pip + sudo -u "$APP_USER" "$APP_DIR/venv/bin/pip" install -r "$APP_DIR/requirements.txt" +} + +write_systemd_service() { + echo "=== Writing systemd service: $SYSTEMD_FILE" + cat > "$SYSTEMD_FILE" </dev/null 2>&1 && [[ $(getenforce) == "Enforcing" ]]; then + echo "=== Configuring SELinux for TCP/5000" + if command -v semanage >/dev/null 2>&1; then + semanage port -a -t http_port_t -p tcp 5000 2>/dev/null || \ + semanage port -m -t http_port_t -p tcp 5000 || true + else + echo "⚠️ 'semanage' not available. Install policycoreutils-python-utils if needed." + fi + fi +} + +start_service_and_verify() { + echo "=== Enabling and starting service..." + systemctl daemon-reload + systemctl enable ${SERVICE_NAME}.service + systemctl restart ${SERVICE_NAME}.service + + echo "\nVerifying service status..." + sleep 3 + if systemctl is-active --quiet ${SERVICE_NAME}; then + echo "✅ Service is active." + echo "Waiting for web interface to start..." + local wait_time=0 + local max_wait=15 + + if command -v curl >/dev/null 2>&1; then + while [[ $wait_time -lt $max_wait ]]; do + if curl -fs http://127.0.0.1:5000 >/dev/null 2>&1; then + echo "✅ Web interface responding on port 5000 (after ${wait_time}s)." | tee -a "$LOGFILE" + break + fi + sleep 2; wait_time=$((wait_time+2)) + done + elif command -v wget >/dev/null 2>&1; then + while [[ $wait_time -lt $max_wait ]]; do + if wget -q --spider http://127.0.0.1:5000 2>/dev/null; then + echo "✅ Web interface responding on port 5000 (after ${wait_time}s)." | tee -a "$LOGFILE" + break + fi + sleep 2; wait_time=$((wait_time+2)) + done + else + echo "⚠️ Neither curl nor wget found; skipping HTTP check."; wait_time=$max_wait + fi + + if [[ $wait_time -ge $max_wait ]]; then + echo "⚠️ Service active, but no HTTP response after ${max_wait}s. Check logs in $LOGFILE." | tee -a "$LOGFILE" + echo "⚠️ Possible slow startup on first run (SQLite or dependencies still initializing)." | tee -a "$LOGFILE" + fi + else + echo "❌ Service not active. Run: sudo systemctl status ${SERVICE_NAME}" + fi +} + +install_linux() { + agree_terms + ensure_packages + ensure_user + clone_or_stage_project + make_venv_and_install + write_systemd_service + # RHEL-specific network tweaks if we're on dnf/yum systems + if [[ "$PKG_MANAGER" == "dnf" || "$PKG_MANAGER" == "yum" ]]; then + rhel_network_adjustments + fi + start_service_and_verify + + # --- Install management script globally --- + local LOCAL_SCRIPT_PATH="/usr/local/bin/retroiptv_linux.sh" + echo "\n=== Installing management script to $LOCAL_SCRIPT_PATH ..." + if [[ -f "$0" ]]; then + cp "$0" "$LOCAL_SCRIPT_PATH" + else + curl -sSLo "$LOCAL_SCRIPT_PATH" "https://raw.githubusercontent.com/thehack904/RetroIPTVGuide/refs/heads/dev/retroiptv_linux.sh" + fi + chmod +x "$LOCAL_SCRIPT_PATH"; chown root:root "$LOCAL_SCRIPT_PATH" + ln -sf "$LOCAL_SCRIPT_PATH" /usr/local/bin/retroiptv + + echo "✅ Installed management script globally. You can now run:" + echo " sudo retroiptv install --agree --yes" + echo " sudo retroiptv update" + echo " sudo retroiptv uninstall --yes" + + echo "" + echo "============================================================" + echo " Installation Complete " + echo "============================================================" + echo "End time: $(date)" + echo "Access in browser: http://$(hostname -I | awk '{print $1}'):5000" + echo "Default login: admin / strongpassword123" + echo "NOTE: BETA build — internal network use only." + echo "Service: $SERVICE_NAME" + echo "User: $APP_USER" + echo "Install path: $APP_DIR" + echo "" + echo "Full log saved to: $LOGFILE" + echo "" +} + +uninstall_linux() { + echo "=== Stopping and disabling ${SERVICE_NAME}.service ..." + systemctl stop ${SERVICE_NAME}.service 2>/dev/null || true + systemctl disable ${SERVICE_NAME}.service 2>/dev/null || true + + echo "=== Removing systemd unit ..." + if [[ -f "$SYSTEMD_FILE" ]]; then + rm -f "$SYSTEMD_FILE" + systemctl daemon-reload + fi + + echo "=== Removing logs and user..." + rm -rf "$LOG_DIR_LINUX" 2>/dev/null || true + if id "$APP_USER" &>/dev/null; then + userdel -r "$APP_USER" || true + elif [[ -d "$APP_HOME" ]]; then + rm -rf "$APP_HOME" + fi + + echo "" + echo "============================================================" + echo " Uninstallation Complete " + echo "============================================================" + echo "End time: $(date)" + echo "User: $APP_USER" + echo "Service: $SERVICE_NAME" + echo "Removed directories: $APP_HOME, $LOG_DIR_LINUX" + echo "Full log saved to: $LOGFILE" + echo "" +} + +update_linux() { + echo "\n=== Updating RetroIPTVGuide from GitHub ===" + echo "Working directory: $APP_DIR" + if [[ ! -d "$APP_DIR/.git" ]]; then + echo "❌ ERROR: $APP_DIR is not a valid Git repository." + echo "Cannot update automatically. Please reinstall or clone manually." + exit 1 + fi + + echo "Fetching latest code from origin/main..." + sudo -u "$APP_USER" bash -H -c "cd '$APP_DIR' && git fetch --all && git reset --hard origin/main" | tee -a "$LOGFILE" + + echo "Reloading and restarting service..." + systemctl daemon-reload + systemctl restart "$SERVICE_NAME".service + + if systemctl is-active --quiet "$SERVICE_NAME"; then + echo "✅ Update complete. Service restarted successfully." + else + echo "⚠️ Update applied but service is not active. Run: sudo systemctl status $SERVICE_NAME" + fi + + echo "\nFull log saved to: $LOGFILE\n" +} + +case "$ACTION" in + install) install_linux ;; + uninstall) uninstall_linux ;; + update) update_linux ;; + -h|--help|help) usage ;; + *) usage ;; +} + +echo "End time: $(date)" + From 01e29ba75ab1e5e0f4237ddf2cbb4444e33156dc Mon Sep 17 00:00:00 2001 From: thehack904 <35552907+thehack904@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:25:20 -0500 Subject: [PATCH 02/30] Update retroiptv_linux.sh --- retroiptv_linux.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/retroiptv_linux.sh b/retroiptv_linux.sh index e6d71fe..4d494f1 100644 --- a/retroiptv_linux.sh +++ b/retroiptv_linux.sh @@ -446,7 +446,7 @@ case "$ACTION" in update) update_linux ;; -h|--help|help) usage ;; *) usage ;; -} +esac echo "End time: $(date)" From df1b78545a39b719ff55459443569f9460068330 Mon Sep 17 00:00:00 2001 From: thehack904 <35552907+thehack904@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:06:12 -0500 Subject: [PATCH 03/30] Update retroiptv_linux.sh --- retroiptv_linux.sh | 434 +++++++++------------------------------------ 1 file changed, 79 insertions(+), 355 deletions(-) diff --git a/retroiptv_linux.sh b/retroiptv_linux.sh index 4d494f1..c163ef2 100644 --- a/retroiptv_linux.sh +++ b/retroiptv_linux.sh @@ -1,25 +1,11 @@ #!/usr/bin/env bash # retroiptv_linux.sh — Unified installer/updater/uninstaller for RetroIPTVGuide (Linux only) -# Version: 3.2.0 # License: Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) -# -# Usage: -# sudo ./retroiptv_linux.sh install [--agree|-a] [--yes|-y] -# sudo ./retroiptv_linux.sh uninstall [--yes|-y] -# sudo ./retroiptv_linux.sh update -# ./retroiptv_linux.sh --help -# -# Notes: -# - Designed for Debian/Ubuntu and RHEL-family (Rocky/Alma/CentOS Stream/Fedora). -# - Run with sudo for full install/uninstall. set -euo pipefail - -VERSION="3.2.0" +VERSION="3.4.0" TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S") LOGFILE="retroiptv_${TIMESTAMP}.log" - -# Log everything to file + console exec > >(tee -a "$LOGFILE") 2>&1 # --- Banner --- @@ -37,230 +23,79 @@ printf "======================================================================== echo " RetroIPTVGuide | Linux Edition (Headless)" printf "===========================================================================\n\n" -echo "=== RetroIPTVGuide Unified Script (v$VERSION) ===" -echo "Start time: $(date)" -echo "Log file: $LOGFILE" - -# --- Globals --- -ACTION="${1:-}" -shift || true -AGREE_TERMS=false -AUTO_YES=false - +ACTION="${1:-}"; shift || true +AGREE_TERMS=false; AUTO_YES=false for arg in "$@"; do case "$arg" in --agree|-a) AGREE_TERMS=true ;; --yes|-y) AUTO_YES=true ;; esac done +[[ $(id -u) -ne 0 ]] && { echo "Run as root (sudo)."; exit 1; } -if [[ $(id -u) -ne 0 ]]; then - echo "ERROR: This script must be run as root (use sudo)." - exit 1 -fi - -APP_USER="iptv" -APP_HOME="/home/$APP_USER" -APP_DIR="$APP_HOME/iptv-server" -SERVICE_NAME="iptv-server" -SYSTEMD_FILE="/etc/systemd/system/${SERVICE_NAME}.service" # /etc works on both Debian & RHEL for local units +APP_USER="iptv"; APP_HOME="/home/$APP_USER"; APP_DIR="" +SERVICE_NAME="iptv-server"; SYSTEMD_FILE="/etc/systemd/system/${SERVICE_NAME}.service" LOG_DIR_LINUX="/var/log/iptv" -# --- OS & package-manager detection --- +# --- OS detection ------------------------------------------------------------ DISTRO_ID="" -if [[ -f /etc/os-release ]]; then - . /etc/os-release - DISTRO_ID=${ID,,} -fi - -PKG_MANAGER="" -PKG_INSTALL="" -PKG_UPDATE="" -PKG_REMOVE="" +[[ -f /etc/os-release ]] && . /etc/os-release && DISTRO_ID=${ID,,} case "$DISTRO_ID" in ubuntu|debian|raspbian) - PKG_MANAGER="apt" - PKG_INSTALL="apt-get install -y" - PKG_UPDATE="apt-get update -y" - PKG_REMOVE="apt-get remove -y" - ;; + PKG_MANAGER="apt"; PKG_INSTALL="apt-get install -y"; PKG_UPDATE="apt-get update -y"; APP_DIR_DEFAULT="$APP_HOME/iptv-server" ;; rhel|centos|rocky|almalinux|fedora) - if command -v dnf >/dev/null 2>&1; then - PKG_MANAGER="dnf" - PKG_INSTALL="dnf install -y" - PKG_UPDATE="dnf -y makecache && dnf upgrade -y || true" - PKG_REMOVE="dnf remove -y" - else - PKG_MANAGER="yum" - PKG_INSTALL="yum install -y" - PKG_UPDATE="yum makecache -y && yum update -y || true" - PKG_REMOVE="yum remove -y" - fi - ;; - *) - echo "⚠️ Unsupported or unknown distribution. Proceeding best-effort." - if command -v apt-get >/dev/null 2>&1; then - PKG_MANAGER="apt"; PKG_INSTALL="apt-get install -y"; PKG_UPDATE="apt-get update -y"; PKG_REMOVE="apt-get remove -y" - elif command -v dnf >/dev/null 2>&1; then - PKG_MANAGER="dnf"; PKG_INSTALL="dnf install -y"; PKG_UPDATE="dnf -y makecache && dnf upgrade -y || true"; PKG_REMOVE="dnf remove -y" - elif command -v yum >/dev/null 2>&1; then - PKG_MANAGER="yum"; PKG_INSTALL="yum install -y"; PKG_UPDATE="yum makecache -y && yum update -y || true"; PKG_REMOVE="yum remove -y" - else - echo "❌ No supported package manager found (apt, dnf, yum)."; exit 1 - fi - ;; + PKG_MANAGER=$(command -v dnf >/dev/null 2>&1 && echo dnf || echo yum) + PKG_INSTALL="$PKG_MANAGER install -y"; PKG_UPDATE="$PKG_MANAGER -y makecache && $PKG_MANAGER upgrade -y || true" + APP_DIR_DEFAULT="/opt/retroiptvguide" ;; + *) PKG_MANAGER=$(command -v apt-get >/dev/null 2>&1 && echo apt || echo dnf) + PKG_INSTALL="$PKG_MANAGER install -y"; PKG_UPDATE="$PKG_MANAGER -y makecache || true" + APP_DIR_DEFAULT="/opt/retroiptvguide" ;; esac +APP_DIR="$APP_DIR_DEFAULT" -# --- Helpers --- -usage() { - local SCRIPT_NAME - SCRIPT_NAME=$(basename "$0") - echo -e "\033[1;33mRetroIPTVGuide Unified Installer/Updater/Uninstaller (v$VERSION)\033[0m\n" - echo -e "Usage:" - echo -e " \033[1;32msudo $SCRIPT_NAME install [--agree|-a] [--yes|-y]\033[0m Install RetroIPTVGuide" - echo -e " \033[1;32msudo $SCRIPT_NAME uninstall [--yes|-y]\033[0m Uninstall RetroIPTVGuide" - echo -e " \033[1;32msudo $SCRIPT_NAME update\033[0m Update RetroIPTVGuide from GitHub" - echo -e " \033[1;32m$SCRIPT_NAME --help\033[0m Show this help\n" - echo "Flags:" - echo " --agree, -a Automatically agree to the license terms" - echo " --yes, -y Run non-interactively, auto-proceed on all prompts" - echo "" - echo "Examples:" - echo -e " \033[1;36msudo $SCRIPT_NAME install --agree --yes\033[0m" - echo -e " \033[1;36msudo $SCRIPT_NAME uninstall --yes\033[0m" - echo -e " \033[1;36msudo $SCRIPT_NAME update\033[0m\n" - echo "License: CC BY-NC-SA 4.0" -} +# --- Utility Functions ------------------------------------------------------- +usage(){ echo "Usage: sudo $0 [install|update|uninstall] [--agree|-a] [--yes|-y]"; } -agree_terms() { - if [[ "$AGREE_TERMS" == true ]]; then - echo "User pre-agreed to license terms via flag (--agree)." - return - fi - echo "" - echo "============================================================" - echo " RetroIPTVGuide Installer Agreement " - echo "============================================================" - echo "" - echo "This installer will perform the following actions:" - echo " - Create system user 'iptv' if not already present" - echo " - Ensure runtime dependencies are installed (Python, pip, git, curl, rsync, etc.)" - echo " - Copy project files into /home/iptv/iptv-server" - echo " - Create a Python virtual environment & install dependencies" - echo " - Create, enable, and start the iptv-server systemd service" - echo "" - echo "By continuing, you acknowledge and agree that:" - echo " - This software should ONLY be run on internal networks." - echo " - It must NOT be exposed to the public Internet." - echo " - You accept all risks; the author provides NO WARRANTY." - echo " - The author is NOT responsible for any damage, data loss," - echo " or security vulnerabilities created by this installation." - echo "" - read -rp "Do you agree to these terms? (yes/no): " agreement - if [[ "$agreement" != "yes" ]]; then - echo "Installation aborted by user." - exit 1 - fi -} - -ensure_packages() { - echo "=== Updating package cache..." - eval "$PKG_UPDATE" || true - - echo "=== Installing required packages..." +ensure_packages(){ + echo "Installing base packages..." local pkgs=(git curl wget rsync python3 python3-pip unzip) - - # Debian-only helper for venv package; RHEL typically doesn't need a separate venv rpm - if [[ "$PKG_MANAGER" == "apt" ]]; then - pkgs+=(python3-venv) - fi - - # SQLite package name differs - if [[ "$PKG_MANAGER" == "apt" ]]; then - pkgs+=(sqlite3) - else - pkgs+=(sqlite) - fi - - # Tools that help with SELinux/firewalld on RHEL-based - if [[ "$PKG_MANAGER" == "dnf" || "$PKG_MANAGER" == "yum" ]]; then - pkgs+=(policycoreutils-python-utils firewalld) || true - fi - - eval "$PKG_INSTALL ${pkgs[*]}" + [[ "$PKG_MANAGER" == apt ]] && pkgs+=(python3-venv sqlite3) || pkgs+=(sqlite) + [[ "$PKG_MANAGER" =~ dnf|yum ]] && pkgs+=(policycoreutils-python-utils firewalld) + eval "$PKG_UPDATE"; eval "$PKG_INSTALL ${pkgs[*]}" } -ensure_user() { - echo "=== Creating system user ($APP_USER) if needed..." - if id "$APP_USER" &>/dev/null; then - echo "User $APP_USER already exists." - if [[ "$AUTO_YES" != true ]]; then - read -rp "Reuse existing user $APP_USER? (yes/no): " reuse - [[ "$reuse" != "yes" ]] && exit 1 - fi - else - if command -v adduser >/dev/null 2>&1; then - adduser --system --home "$APP_HOME" --group "$APP_USER" - else - # RHEL-friendly - useradd -r -m -d "$APP_HOME" -s /sbin/nologin -U "$APP_USER" - fi - echo "Created system user: $APP_USER" +ensure_user(){ + echo "Ensuring system user..." + NOLOGIN=$(command -v nologin 2>/dev/null || echo /usr/sbin/nologin) + getent group "$APP_USER" >/dev/null || groupadd --system "$APP_USER" + if ! id "$APP_USER" >/dev/null 2>&1; then + useradd -r -m -d "$APP_HOME" -s "$NOLOGIN" -g "$APP_USER" "$APP_USER" fi + chmod 755 "$APP_HOME" || true } -clone_or_stage_project() { - echo "=== Preparing application directory: $APP_DIR" - mkdir -p "$APP_DIR" - chown -R "$APP_USER":"$APP_USER" "$APP_HOME" - - local TMP_CLONE_DIR="/tmp/retroiptvguide" - cd /tmp - - if [[ ! -f requirements.txt ]]; then - if command -v git >/dev/null 2>&1; then - echo "Project files not found locally — cloning RetroIPTVGuide (dev branch)..." - rm -rf "$TMP_CLONE_DIR" - git clone --depth 1 -b dev https://github.com/thehack904/RetroIPTVGuide.git "$TMP_CLONE_DIR" - SCRIPT_DIR=$(realpath "$TMP_CLONE_DIR") - else - echo "ERROR: requirements.txt not found and git is not installed." - echo "Please install git or run this script from within a cloned RetroIPTVGuide repo." - exit 1 - fi - else - SCRIPT_DIR=$(realpath "$(pwd)") - fi - - echo "Copying project files from: $SCRIPT_DIR" - rsync -a --delete --exclude 'venv' "$SCRIPT_DIR/" "$APP_DIR/" || { - echo "❌ ERROR: rsync failed to copy project files."; exit 1; } +clone_or_stage_project(){ + mkdir -p "$APP_DIR"; chown -R "$APP_USER":"$APP_USER" "$APP_DIR" + TMP="/tmp/retroiptvguide"; rm -rf "$TMP" + git clone --depth 1 -b dev https://github.com/thehack904/RetroIPTVGuide.git "$TMP" + rsync -a --delete --exclude 'venv' "$TMP/" "$APP_DIR/" chown -R "$APP_USER":"$APP_USER" "$APP_DIR" } -make_venv_and_install() { - echo "=== Ensuring Python virtual environment..." - if [[ -d "$APP_DIR/venv" && "$AUTO_YES" == true ]]; then - echo "Existing venv detected — auto-reusing (--yes)." - else - if sudo -u "$APP_USER" python3 -m venv "$APP_DIR/venv" 2>/dev/null; then - : - else - echo "⚠️ python3 -m venv failed — attempting virtualenv via pip." - sudo -u "$APP_USER" python3 -m pip install --user --upgrade virtualenv - sudo -u "$APP_USER" "$APP_HOME/.local/bin/virtualenv" "$APP_DIR/venv" - fi - fi - - echo "=== Installing Python dependencies..." +make_venv_and_install(){ + echo "Setting up virtualenv..." + sudo -u "$APP_USER" python3 -m ensurepip --upgrade 2>/dev/null || true + sudo -u "$APP_USER" python3 -m venv "$APP_DIR/venv" || \ + { sudo -u "$APP_USER" python3 -m pip install --user virtualenv; sudo -u "$APP_USER" "$APP_HOME/.local/bin/virtualenv" "$APP_DIR/venv"; } sudo -u "$APP_USER" "$APP_DIR/venv/bin/pip" install --upgrade pip sudo -u "$APP_USER" "$APP_DIR/venv/bin/pip" install -r "$APP_DIR/requirements.txt" } -write_systemd_service() { - echo "=== Writing systemd service: $SYSTEMD_FILE" - cat > "$SYSTEMD_FILE" <"$SYSTEMD_FILE"</dev/null 2>&1 && [[ $(getenforce) == "Enforcing" ]]; then - echo "=== Configuring SELinux for TCP/5000" - if command -v semanage >/dev/null 2>&1; then - semanage port -a -t http_port_t -p tcp 5000 2>/dev/null || \ - semanage port -m -t http_port_t -p tcp 5000 || true - else - echo "⚠️ 'semanage' not available. Install policycoreutils-python-utils if needed." - fi + if command -v semanage >/dev/null 2>&1; then + semanage port -a -t http_port_t -p tcp 5000 2>/dev/null || semanage port -m -t http_port_t -p tcp 5000 fi } -start_service_and_verify() { - echo "=== Enabling and starting service..." +start_and_verify(){ systemctl daemon-reload - systemctl enable ${SERVICE_NAME}.service - systemctl restart ${SERVICE_NAME}.service - - echo "\nVerifying service status..." + systemctl enable --now "$SERVICE_NAME" sleep 3 - if systemctl is-active --quiet ${SERVICE_NAME}; then - echo "✅ Service is active." - echo "Waiting for web interface to start..." - local wait_time=0 - local max_wait=15 - - if command -v curl >/dev/null 2>&1; then - while [[ $wait_time -lt $max_wait ]]; do - if curl -fs http://127.0.0.1:5000 >/dev/null 2>&1; then - echo "✅ Web interface responding on port 5000 (after ${wait_time}s)." | tee -a "$LOGFILE" - break - fi - sleep 2; wait_time=$((wait_time+2)) - done - elif command -v wget >/dev/null 2>&1; then - while [[ $wait_time -lt $max_wait ]]; do - if wget -q --spider http://127.0.0.1:5000 2>/dev/null; then - echo "✅ Web interface responding on port 5000 (after ${wait_time}s)." | tee -a "$LOGFILE" - break - fi - sleep 2; wait_time=$((wait_time+2)) - done - else - echo "⚠️ Neither curl nor wget found; skipping HTTP check."; wait_time=$max_wait - fi - - if [[ $wait_time -ge $max_wait ]]; then - echo "⚠️ Service active, but no HTTP response after ${max_wait}s. Check logs in $LOGFILE." | tee -a "$LOGFILE" - echo "⚠️ Possible slow startup on first run (SQLite or dependencies still initializing)." | tee -a "$LOGFILE" - fi + if systemctl is-active --quiet "$SERVICE_NAME"; then + echo "✅ Service active." else - echo "❌ Service not active. Run: sudo systemctl status ${SERVICE_NAME}" + echo "❌ Service failed. See: sudo journalctl -u $SERVICE_NAME" fi } -install_linux() { - agree_terms - ensure_packages - ensure_user - clone_or_stage_project - make_venv_and_install - write_systemd_service - # RHEL-specific network tweaks if we're on dnf/yum systems - if [[ "$PKG_MANAGER" == "dnf" || "$PKG_MANAGER" == "yum" ]]; then - rhel_network_adjustments - fi - start_service_and_verify - - # --- Install management script globally --- - local LOCAL_SCRIPT_PATH="/usr/local/bin/retroiptv_linux.sh" - echo "\n=== Installing management script to $LOCAL_SCRIPT_PATH ..." - if [[ -f "$0" ]]; then - cp "$0" "$LOCAL_SCRIPT_PATH" - else - curl -sSLo "$LOCAL_SCRIPT_PATH" "https://raw.githubusercontent.com/thehack904/RetroIPTVGuide/refs/heads/dev/retroiptv_linux.sh" - fi - chmod +x "$LOCAL_SCRIPT_PATH"; chown root:root "$LOCAL_SCRIPT_PATH" - ln -sf "$LOCAL_SCRIPT_PATH" /usr/local/bin/retroiptv - - echo "✅ Installed management script globally. You can now run:" - echo " sudo retroiptv install --agree --yes" - echo " sudo retroiptv update" - echo " sudo retroiptv uninstall --yes" - - echo "" - echo "============================================================" - echo " Installation Complete " - echo "============================================================" - echo "End time: $(date)" - echo "Access in browser: http://$(hostname -I | awk '{print $1}'):5000" - echo "Default login: admin / strongpassword123" - echo "NOTE: BETA build — internal network use only." - echo "Service: $SERVICE_NAME" - echo "User: $APP_USER" - echo "Install path: $APP_DIR" - echo "" - echo "Full log saved to: $LOGFILE" - echo "" +install_linux(){ + ensure_packages; ensure_user; clone_or_stage_project; make_venv_and_install; write_systemd_service + rhel_firewall_selinux; start_and_verify + echo "Installed to: $APP_DIR" + echo "Access at: http://$(hostname -I | awk '{print $1}'):5000" } -uninstall_linux() { - echo "=== Stopping and disabling ${SERVICE_NAME}.service ..." - systemctl stop ${SERVICE_NAME}.service 2>/dev/null || true - systemctl disable ${SERVICE_NAME}.service 2>/dev/null || true - - echo "=== Removing systemd unit ..." - if [[ -f "$SYSTEMD_FILE" ]]; then - rm -f "$SYSTEMD_FILE" - systemctl daemon-reload - fi - - echo "=== Removing logs and user..." - rm -rf "$LOG_DIR_LINUX" 2>/dev/null || true - if id "$APP_USER" &>/dev/null; then - userdel -r "$APP_USER" || true - elif [[ -d "$APP_HOME" ]]; then - rm -rf "$APP_HOME" - fi - - echo "" - echo "============================================================" - echo " Uninstallation Complete " - echo "============================================================" - echo "End time: $(date)" - echo "User: $APP_USER" - echo "Service: $SERVICE_NAME" - echo "Removed directories: $APP_HOME, $LOG_DIR_LINUX" - echo "Full log saved to: $LOGFILE" - echo "" +update_linux(){ + echo "Updating app..." + sudo -u "$APP_USER" bash -c "cd '$APP_DIR' && git fetch --all && git reset --hard origin/main" + systemctl daemon-reload; systemctl restart "$SERVICE_NAME" + echo "✅ Updated and restarted." } -update_linux() { - echo "\n=== Updating RetroIPTVGuide from GitHub ===" - echo "Working directory: $APP_DIR" - if [[ ! -d "$APP_DIR/.git" ]]; then - echo "❌ ERROR: $APP_DIR is not a valid Git repository." - echo "Cannot update automatically. Please reinstall or clone manually." - exit 1 - fi +uninstall_linux(){ + echo "Stopping and disabling service..." + systemctl stop "$SERVICE_NAME" 2>/dev/null || true + systemctl disable "$SERVICE_NAME" 2>/dev/null || true + [[ -f "$SYSTEMD_FILE" ]] && rm -f "$SYSTEMD_FILE" && systemctl daemon-reload - echo "Fetching latest code from origin/main..." - sudo -u "$APP_USER" bash -H -c "cd '$APP_DIR' && git fetch --all && git reset --hard origin/main" | tee -a "$LOGFILE" - - echo "Reloading and restarting service..." - systemctl daemon-reload - systemctl restart "$SERVICE_NAME".service + echo "Removing files..." + rm -rf "$LOG_DIR_LINUX" 2>/dev/null || true + [[ -d "/opt/retroiptvguide" ]] && { echo "Removing /opt/retroiptvguide ..."; rm -rf /opt/retroiptvguide; } + [[ -d "$APP_HOME/iptv-server" ]] && { echo "Removing $APP_HOME/iptv-server ..."; rm -rf "$APP_HOME/iptv-server"; } - if systemctl is-active --quiet "$SERVICE_NAME"; then - echo "✅ Update complete. Service restarted successfully." - else - echo "⚠️ Update applied but service is not active. Run: sudo systemctl status $SERVICE_NAME" - fi + echo "Removing user/group..." + id "$APP_USER" &>/dev/null && userdel -r "$APP_USER" 2>/dev/null || true + getent group "$APP_USER" >/dev/null && groupdel "$APP_USER" 2>/dev/null || true - echo "\nFull log saved to: $LOGFILE\n" + echo "✅ Uninstall complete. Logs saved to $LOGFILE." } case "$ACTION" in install) install_linux ;; - uninstall) uninstall_linux ;; update) update_linux ;; - -h|--help|help) usage ;; + uninstall) uninstall_linux ;; + -h|--help|help|"") usage ;; *) usage ;; esac - echo "End time: $(date)" - From 84ef23751ee3ecfc7c4085f583e1686e778ada95 Mon Sep 17 00:00:00 2001 From: thehack904 <35552907+thehack904@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:03:59 -0500 Subject: [PATCH 04/30] Updates for 3.4.0 Clean up and combined user management. Removed non-used webpages. Updated logs to track security issues as well. --- app.py | 110 +++++++-- templates/about.html | 35 ++- templates/add_user.html | 278 ---------------------- templates/base.html | 103 --------- templates/change_password.html | 39 ++-- templates/change_tuner.html | 40 ++-- templates/delete_user.html | 282 ----------------------- templates/guide.html | 78 +++++-- templates/layout.html | 15 -- templates/login.html | 116 ++++------ templates/logs.html | 407 +++++++-------------------------- templates/manage_users.html | 165 +++++++++++++ templates/new_user.html | 40 ---- templates/player.html | 25 -- 14 files changed, 504 insertions(+), 1229 deletions(-) delete mode 100644 templates/add_user.html delete mode 100644 templates/base.html delete mode 100644 templates/delete_user.html delete mode 100644 templates/layout.html create mode 100644 templates/manage_users.html delete mode 100644 templates/new_user.html delete mode 100644 templates/player.html diff --git a/app.py b/app.py index 5d0a800..e3f25b5 100644 --- a/app.py +++ b/app.py @@ -1,7 +1,7 @@ APP_VERSION = "v3.3.0" APP_RELEASE_DATE = "2025-10-11" -from flask import Flask, render_template, request, redirect, url_for, flash +from flask import Flask, render_template, request, redirect, url_for, flash, session from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user from werkzeug.security import generate_password_hash, check_password_hash import sqlite3 @@ -22,6 +22,8 @@ # ------------------- Config ------------------- app = Flask(__name__) app.secret_key = os.urandom(24) # replace with a fixed key in production +app.config['REMEMBER_COOKIE_DURATION'] = timedelta(days=30) + DATABASE = 'users.db' TUNER_DB = 'tuners.db' @@ -301,8 +303,10 @@ def login(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] + remember = 'remember' in request.form # ✅ new: detect Save Session checkbox user = get_user(username) if user and check_password_hash(user.password_hash, password): + login_user(user, remember=remember) # ✅ persist login if checked login_user(user) log_event(username, "Logged in") tuners = get_tuners() @@ -327,6 +331,14 @@ def logout(): logout_user() return redirect(url_for('login')) +def revoke_user_sessions(username): + # Placeholder: later this can use a session-tracking table or Redis + # For now, it clears any "remember" cookie or stored flag + session_key = f"user_session_{username}" + if session_key in session: + session.pop(session_key, None) + log_event("admin", f"Revoked sessions for {username}") + @app.route('/change_password', methods=['GET','POST']) @login_required def change_password(): @@ -400,6 +412,69 @@ def delete_user(): users = [row[0] for row in c.fetchall()] return render_template("delete_user.html", current_tuner=get_current_tuner(), users=users) +@app.route('/manage_users', methods=['GET', 'POST']) +@login_required +def manage_users(): + ua = request.headers.get('User-Agent', '').lower() + + # Detect Android / Fire / Google TV browsers + tv_patterns = ['silk', 'aft', 'android tv', 'googletv', 'mibox', 'bravia', 'shield', 'tcl', 'hisense', 'puffin', 'tv bro'] + is_tv = any(p in ua for p in tv_patterns) + + # Restrict access + if current_user.username != 'admin' or is_tv: + # Log unauthorized or TV-based attempt + log_event(current_user.username, f"Unauthorized attempt to access /manage_users from UA: {ua}") + flash("Unauthorized access.") + return redirect(url_for('guide')) + + # ---- Normal admin logic below ---- + with sqlite3.connect(DATABASE, timeout=10) as conn: + c = conn.cursor() + c.execute('SELECT username FROM users WHERE username != "admin"') + users = [row[0] for row in c.fetchall()] + + if request.method == 'POST': + action = request.form.get('action') + username = request.form.get('username') + password = request.form.get('password') + + if action == 'add': + if not username or not password: + flash("Please provide both username and password.") + else: + try: + with sqlite3.connect(DATABASE, timeout=10) as conn: + c = conn.cursor() + c.execute('INSERT INTO users (username, password) VALUES (?, ?)', + (username, generate_password_hash(password))) + conn.commit() + log_event(current_user.username, f"Added user {username}") + flash(f"✅ User '{username}' added successfully.") + except sqlite3.IntegrityError: + flash("⚠️ Username already exists.") + + elif action == 'delete': + if username == 'admin': + flash("❌ Cannot delete the admin account.") + else: + with sqlite3.connect(DATABASE, timeout=10) as conn: + c = conn.cursor() + c.execute('DELETE FROM users WHERE username=?', (username,)) + conn.commit() + log_event(current_user.username, f"Deleted user {username}") + flash(f"🗑 Deleted user '{username}'.") + + elif action == 'signout': + revoke_user_sessions(username) + log_event(current_user.username, f"Revoked sessions for {username}") + flash(f"🚪 Signed out all active logins for '{username}'.") + + return redirect(url_for('manage_users')) + + return render_template('manage_users.html', users=users, current_tuner=get_current_tuner()) + + @app.route("/about") @login_required # optional def about(): @@ -571,6 +646,7 @@ def view_logs(): log_event(current_user.username, "Accessed logs page") entries = [] log_size = 0 + if os.path.exists(LOG_PATH): log_size = os.path.getsize(LOG_PATH) with open(LOG_PATH, "r") as f: @@ -578,9 +654,15 @@ def view_logs(): parts = line.strip().split(" | ") if len(parts) == 3: user, action, timestamp = parts - entries.append((user, action, timestamp)) + log_type = "security" if any( + x in action.lower() for x in + ["unauthorized", "revoked", "failed", "denied"] + ) else "activity" + entries.append((user, action, timestamp, log_type)) else: - entries.append(("system", line.strip(), "")) + entries.append(("system", line.strip(), "", "activity")) + else: + entries = [("system", "No log file found.", "", "activity")] return render_template( "logs.html", @@ -589,22 +671,20 @@ def view_logs(): log_size=log_size ) -@app.route("/clear_logs") + + +@app.route('/clear_logs', methods=['POST']) @login_required def clear_logs(): - if current_user.username != "admin": - flash("Unauthorized: only admin can clear logs.", "error") - return redirect(url_for("view_logs")) + if current_user.username != 'admin': + flash("Unauthorized access.") + return redirect(url_for('view_logs')) - try: - # Truncate the log file instead of deleting it - with open(LOG_PATH, "w"): - pass - flash("✅ Logs cleared successfully.", "success") - except Exception as e: - flash(f"⚠️ Error clearing logs: {e}", "error") + open(LOG_PATH, "w").close() # clear the file + log_event("admin", "Cleared log file") + flash("🧹 Logs cleared successfully.") + return redirect(url_for('view_logs')) - return redirect(url_for("view_logs")) # ------------------- Constants ------------------- SCALE = 5 diff --git a/templates/about.html b/templates/about.html index de0a493..763d323 100644 --- a/templates/about.html +++ b/templates/about.html @@ -209,35 +209,30 @@ HOME {% if current_user is defined and current_user.username == 'admin' %} - + + MANAGE USERS {% endif %} - diff --git a/templates/change_tuner.html b/templates/change_tuner.html index f32d16e..b0a12a6 100644 --- a/templates/change_tuner.html +++ b/templates/change_tuner.html @@ -189,36 +189,34 @@