Files
linux-user-timer-native-app/install.sh
T
2026-09-18 00:20:31 +05:00

813 lines
18 KiB
Bash
Executable File

#!/usr/bin/env bash
set -Eeuo pipefail
# ============================================================
# Linux Parental Control Installer
# ============================================================
APP_NAME="Parental Control"
SERVICE_NAME="parental-control.service"
DESKTOP_ID="parental-control.desktop"
INSTALL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VENV_DIR="$INSTALL_DIR/.venv"
PYTHON="$VENV_DIR/bin/python"
SERVICE_FILE="/etc/systemd/system/$SERVICE_NAME"
DESKTOP_FILE="/usr/share/applications/$DESKTOP_ID"
POLKIT_ACTION="org.parentalcontrol.modify"
POLKIT_SOURCE="$INSTALL_DIR/$POLKIT_ACTION.policy"
POLKIT_DEST="/etc/polkit-1/actions/$POLKIT_ACTION.policy"
SOCKET_PATH="/run/parental-control.sock"
INSTALL_USER=""
INSTALL_GROUP=""
PACKAGE_MANAGER=""
POLKIT_AGENT=""
# ============================================================
# Output helpers
# ============================================================
info() {
echo
echo "==> $1"
}
success() {
echo " $1"
}
warning() {
echo " WARNING: $1"
}
error() {
echo
echo "ERROR: $1"
echo
exit 1
}
# ============================================================
# Error handling
# ============================================================
on_error() {
echo
echo "Installation failed."
echo "Line: $1"
echo "Command: $2"
echo
}
trap 'on_error "$LINENO" "$BASH_COMMAND"' ERR
# ============================================================
# Root check
# ============================================================
if [ "$EUID" -ne 0 ]; then
error "Run this installer with sudo:
sudo ./install.sh"
fi
# ============================================================
# Find original user
# ============================================================
if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
INSTALL_USER="$SUDO_USER"
else
INSTALL_USER="$(stat -c '%U' "$INSTALL_DIR")"
fi
if ! id "$INSTALL_USER" >/dev/null 2>&1; then
error "Could not determine the normal installation user."
fi
INSTALL_GROUP="$(id -gn "$INSTALL_USER")"
INSTALL_USER_HOME="$(getent passwd "$INSTALL_USER" | cut -d: -f6)"
if [ -z "$INSTALL_USER_HOME" ] || [ ! -d "$INSTALL_USER_HOME" ]; then
error "Could not determine the home directory of $INSTALL_USER."
fi
# ============================================================
# Header
# ============================================================
clear 2>/dev/null || true
echo
echo "=========================================="
echo " Linux Parental Control Installer"
echo "=========================================="
echo
echo "Application:"
echo " $APP_NAME"
echo
echo "Installation directory:"
echo " $INSTALL_DIR"
echo
echo "Installation user:"
echo " $INSTALL_USER"
echo
# ============================================================
# Check project files
# ============================================================
info "Checking project files"
REQUIRED_FILES=(
"requirements.txt"
"parental-control.service"
"$POLKIT_ACTION.policy"
"app/__init__.py"
"app/main.py"
"app/config.py"
"app/enforcement.py"
"app/scheduler.py"
"app/users.py"
"app/ipc.py"
"desktop/__init__.py"
"desktop/main.py"
"desktop/client.py"
"config/users.yaml"
"config/state.json"
)
for file in "${REQUIRED_FILES[@]}"; do
if [ ! -f "$INSTALL_DIR/$file" ]; then
error "Required file not found: $file"
fi
done
success "All required project files found."
# ============================================================
# Detect operating system
# ============================================================
info "Detecting operating system"
if [ ! -f /etc/os-release ]; then
error "/etc/os-release was not found."
fi
# shellcheck disable=SC1091
source /etc/os-release
OS_ID="${ID:-unknown}"
OS_NAME="${NAME:-Unknown Linux}"
OS_VERSION="${VERSION_ID:-unknown}"
echo " Distribution: $OS_NAME"
echo " Version: $OS_VERSION"
# ============================================================
# Check systemd
# ============================================================
info "Checking systemd"
if ! command -v systemctl >/dev/null 2>&1; then
error "systemd is required for this version of the application.
This installer does not support non-systemd distributions."
fi
if [ ! -d /run/systemd/system ]; then
error "systemd is installed but is not currently running."
fi
if ! command -v loginctl >/dev/null 2>&1; then
error "loginctl was not found.
The application requires systemd-logind."
fi
success "systemd and loginctl detected."
# ============================================================
# Detect package manager
# ============================================================
info "Detecting package manager"
if command -v pacman >/dev/null 2>&1; then
PACKAGE_MANAGER="pacman"
elif command -v apt-get >/dev/null 2>&1; then
PACKAGE_MANAGER="apt"
elif command -v dnf >/dev/null 2>&1; then
PACKAGE_MANAGER="dnf"
elif command -v zypper >/dev/null 2>&1; then
PACKAGE_MANAGER="zypper"
else
error "No supported package manager was detected.
Supported package managers:
pacman
apt
dnf
zypper
You can install the required dependencies manually,
then run the installer again."
fi
success "Package manager: $PACKAGE_MANAGER"
# ============================================================
# Install system dependencies
# ============================================================
info "Installing system dependencies"
case "$PACKAGE_MANAGER" in
pacman)
pacman -S --needed --noconfirm \
python \
python-pip \
polkit \
polkit-gnome \
desktop-file-utils \
systemd
;;
apt)
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y \
python3 \
python3-pip \
python3-venv \
polkitd \
policykit-1 \
policykit-1-gnome \
desktop-file-utils \
systemd
;;
dnf)
dnf install -y \
python3 \
python3-pip \
python3-virtualenv \
polkit \
polkit-gnome \
desktop-file-utils \
systemd
;;
zypper)
zypper --non-interactive install \
python3 \
python3-pip \
python3-virtualenv \
polkit \
polkit-gnome \
desktop-file-utils \
systemd
;;
esac
success "System dependencies installed."
# ============================================================
# Find Python
# ============================================================
info "Checking Python"
SYSTEM_PYTHON=""
for candidate in python3 python; do
if command -v "$candidate" >/dev/null 2>&1; then
SYSTEM_PYTHON="$(command -v "$candidate")"
break
fi
done
if [ -z "$SYSTEM_PYTHON" ]; then
error "Python 3 could not be found."
fi
PYTHON_MAJOR="$(
"$SYSTEM_PYTHON" -c \
'import sys; print(sys.version_info.major)'
)"
PYTHON_MINOR="$(
"$SYSTEM_PYTHON" -c \
'import sys; print(sys.version_info.minor)'
)"
PYTHON_VERSION="$(
"$SYSTEM_PYTHON" -c \
'import sys; print(".".join(map(str, sys.version_info[:3])))'
)"
if [ "$PYTHON_MAJOR" -ne 3 ]; then
error "Python 3 is required.
Found Python $PYTHON_VERSION."
fi
if [ "$PYTHON_MINOR" -lt 9 ]; then
error "Python $PYTHON_VERSION is too old.
Python 3.9 or newer is required."
fi
echo " Python: $SYSTEM_PYTHON"
echo " Version: $PYTHON_VERSION"
success "Python version is supported."
# ============================================================
# Create virtual environment
# ============================================================
info "Preparing Python virtual environment"
if [ -x "$PYTHON" ]; then
success "Existing virtual environment found."
else
if [ -d "$VENV_DIR" ]; then
warning "Existing virtual environment is incomplete."
warning "Removing it."
rm -rf "$VENV_DIR"
fi
echo " Creating virtual environment..."
if ! "$SYSTEM_PYTHON" -m venv "$VENV_DIR"; then
error "Could not create the Python virtual environment.
Install your distribution's Python venv package."
fi
success "Virtual environment created."
fi
# ============================================================
# Install Python dependencies
# ============================================================
info "Installing Python dependencies"
"$PYTHON" -m pip install \
--disable-pip-version-check \
--upgrade pip
"$PYTHON" -m pip install \
--disable-pip-version-check \
-r "$INSTALL_DIR/requirements.txt"
success "Python dependencies installed."
# ============================================================
# Validate Python source
# ============================================================
info "Checking Python source files"
"$PYTHON" -m compileall \
-q \
"$INSTALL_DIR/app" \
"$INSTALL_DIR/desktop"
success "Python source validation passed."
# ============================================================
# Validate configuration
# ============================================================
info "Checking application configuration"
if [ ! -f "$INSTALL_DIR/config/users.yaml" ]; then
error "users.yaml was not found."
fi
if [ ! -f "$INSTALL_DIR/config/state.json" ]; then
error "state.json was not found."
fi
success "Configuration files found."
# ============================================================
# Install systemd service
# ============================================================
info "Installing systemd service"
SERVICE_TEMP="$(mktemp)"
sed \
"s|%INSTALL_DIR%|$INSTALL_DIR|g" \
"$INSTALL_DIR/parental-control.service" \
> "$SERVICE_TEMP"
if ! grep -q '^Environment=PYTHONDONTWRITEBYTECODE=' "$SERVICE_TEMP"; then
sed -i \
'/^\[Service\]/a Environment=PYTHONDONTWRITEBYTECODE=1' \
"$SERVICE_TEMP"
fi
install \
-o root \
-g root \
-m 644 \
"$SERVICE_TEMP" \
"$SERVICE_FILE"
rm -f "$SERVICE_TEMP"
success "Systemd service installed."
# ============================================================
# Install polkit policy
# ============================================================
info "Installing polkit policy"
if [ ! -f "$POLKIT_SOURCE" ]; then
error "Polkit policy file was not found:
$POLKIT_SOURCE"
fi
mkdir -p /etc/polkit-1/actions
install \
-o root \
-g root \
-m 644 \
"$POLKIT_SOURCE" \
"$POLKIT_DEST"
success "Polkit policy installed."
# ============================================================
# Detect polkit authentication agent
# ============================================================
info "Detecting polkit authentication agent"
POLKIT_AGENT=""
POLKIT_AGENT_CANDIDATES=(
"/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1"
"/usr/libexec/polkit-gnome-authentication-agent-1"
"/usr/lib/polkit-1-gnome/polkit-gnome-authentication-agent-1"
)
for candidate in "${POLKIT_AGENT_CANDIDATES[@]}"; do
if [ -x "$candidate" ]; then
POLKIT_AGENT="$candidate"
break
fi
done
if [ -n "$POLKIT_AGENT" ]; then
success "Authentication agent found:"
success "$POLKIT_AGENT"
else
warning "A polkit authentication agent was not found."
warning "GUI administrative actions may require manual configuration."
fi
# ============================================================
# Configure Sway polkit authentication agent
# ============================================================
configure_sway_polkit() {
local user_home="$1"
local sway_config="$user_home/.config/sway/config"
local marker="parental-control-polkit-agent"
if [ -z "$POLKIT_AGENT" ]; then
return 0
fi
if [ ! -f "$sway_config" ]; then
warning "Sway configuration not found:"
warning "$sway_config"
return 0
fi
if grep -Fq "$marker" "$sway_config"; then
success "Sway polkit authentication agent is already configured."
return 0
fi
cp -a \
"$sway_config" \
"$sway_config.parental-control-backup"
cat >> "$sway_config" <<EOF
# $marker
exec_always --no-startup-id $POLKIT_AGENT
EOF
chown \
"$INSTALL_USER:$INSTALL_GROUP" \
"$sway_config"
success "Sway polkit authentication agent configured."
}
if [ -n "$POLKIT_AGENT" ]; then
configure_sway_polkit "$INSTALL_USER_HOME"
fi
# ============================================================
# Install desktop launcher
# ============================================================
info "Installing desktop launcher"
cat > "$DESKTOP_FILE" <<EOF
[Desktop Entry]
Name=$APP_NAME
Comment=Linux Parental Control Manager
Exec=$PYTHON -m desktop.main
Path=$INSTALL_DIR
Icon=preferences-system-parental-controls
Terminal=false
Type=Application
Categories=Settings;
StartupNotify=true
EOF
chmod 644 "$DESKTOP_FILE"
success "Desktop launcher installed."
# ============================================================
# Validate desktop launcher
# ============================================================
if command -v desktop-file-validate >/dev/null 2>&1; then
info "Validating desktop launcher"
if ! desktop-file-validate "$DESKTOP_FILE"; then
error "Desktop launcher validation failed."
fi
success "Desktop launcher is valid."
else
warning "desktop-file-validate is not available."
fi
# ============================================================
# Update desktop database
# ============================================================
if command -v update-desktop-database >/dev/null 2>&1; then
update-desktop-database \
/usr/share/applications \
>/dev/null 2>&1 || true
fi
# ============================================================
# Restore source ownership
# ============================================================
info "Fixing source ownership"
chown -R \
"$INSTALL_USER:$INSTALL_GROUP" \
"$INSTALL_DIR"
success "Source tree owned by $INSTALL_USER."
# ============================================================
# Reload systemd
# ============================================================
info "Reloading systemd"
systemctl daemon-reload
success "Systemd reloaded."
# ============================================================
# Remove stale IPC socket
# ============================================================
info "Cleaning stale IPC socket"
if [ -e "$SOCKET_PATH" ]; then
if [ -S "$SOCKET_PATH" ]; then
rm -f "$SOCKET_PATH"
success "Removed stale IPC socket."
else
warning "$SOCKET_PATH exists but is not a socket."
warning "Leaving it untouched."
fi
fi
# ============================================================
# Enable service
# ============================================================
info "Enabling parental control service"
systemctl enable "$SERVICE_NAME"
success "Service enabled."
# ============================================================
# Restart service
# ============================================================
info "Starting parental control service"
systemctl restart "$SERVICE_NAME"
# ============================================================
# Wait for service
# ============================================================
info "Waiting for service startup"
SERVICE_READY=0
for _ in $(seq 1 15); do
if systemctl is-active --quiet "$SERVICE_NAME"; then
SERVICE_READY=1
break
fi
sleep 1
done
if [ "$SERVICE_READY" -ne 1 ]; then
echo
echo "=========================================="
echo " Service startup failed"
echo "=========================================="
echo
systemctl status \
"$SERVICE_NAME" \
--no-pager || true
echo
echo "Recent service logs:"
echo
journalctl \
-u "$SERVICE_NAME" \
-n 50 \
--no-pager || true
exit 1
fi
success "Service is running."
# ============================================================
# Verify IPC socket
# ============================================================
info "Checking IPC socket"
if [ ! -S "$SOCKET_PATH" ]; then
warning "IPC socket was not created:"
warning "$SOCKET_PATH"
else
success "IPC socket is active."
fi
# ============================================================
# Final verification
# ============================================================
info "Running final checks"
CHECK_FAILED=0
if systemctl is-active --quiet "$SERVICE_NAME"; then
echo " Service: OK"
else
echo " Service: FAILED"
CHECK_FAILED=1
fi
if [ -x "$PYTHON" ]; then
echo " Python: OK"
else
echo " Python: FAILED"
CHECK_FAILED=1
fi
if [ -f "$DESKTOP_FILE" ]; then
echo " Desktop: OK"
else
echo " Desktop: FAILED"
CHECK_FAILED=1
fi
if [ -S "$SOCKET_PATH" ]; then
echo " IPC: OK"
else
echo " IPC: WARNING"
fi
if [ -f "$POLKIT_DEST" ]; then
echo " Polkit: OK"
else
echo " Polkit: FAILED"
CHECK_FAILED=1
fi
if [ -f "$INSTALL_DIR/config/users.yaml" ]; then
echo " Users: OK"
else
echo " Users: FAILED"
CHECK_FAILED=1
fi
if [ -f "$INSTALL_DIR/config/state.json" ]; then
echo " State: OK"
else
echo " State: FAILED"
CHECK_FAILED=1
fi
if [ "$CHECK_FAILED" -ne 0 ]; then
error "One or more required installation checks failed."
fi
# ============================================================
# Finished
# ============================================================
echo
echo "=========================================="
echo " Installation complete"
echo "=========================================="
echo
echo "Application:"
echo " $APP_NAME"
echo
echo "Launch:"
echo " Application menu"
echo " Rofi"
echo
echo "Service:"
echo " systemctl status $SERVICE_NAME"
echo
echo "Logs:"
echo " journalctl -u $SERVICE_NAME"
echo
echo "IPC:"
echo " $SOCKET_PATH"
echo
echo "Configuration:"
echo " $INSTALL_DIR/config/users.yaml"
echo " $INSTALL_DIR/config/state.json"
echo
echo "Installation:"
echo " $INSTALL_DIR"
echo
echo "Polkit agent:"
if [ -n "$POLKIT_AGENT" ]; then
echo " $POLKIT_AGENT"
else
echo " Not detected"
fi
echo
echo "=========================================="
echo