Refactor installation and uninstallation scripts; enhance logging and error handling
- Removed Docker installation instructions from README.md as they are not yet functional. - Simplified the installation script by removing the upgrade process and directly checking for existing installations. - Added checks for required dependencies (python3, curl, python3-venv) during installation. - Improved logging throughout the application, replacing print statements with logger calls. - Enhanced email validation using regex and added error handling for invalid email addresses. - Updated the uninstall script to provide options for complete or partial removal of Vigil. - Created a logger service to handle logging to both console and log files. - Updated requirements.txt to use newer versions of dependencies. - Added versioning to the main application and provided a version option in the command line interface.
This commit is contained in:
parent
a08228f7ef
commit
312d3e9674
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"idf.pythonInstallPath": "/usr/bin/python3"
|
||||||
|
}
|
||||||
22
README.md
22
README.md
@ -13,8 +13,6 @@ Vigil is designed for stability and security, avoiding keeping credentials in th
|
|||||||
- [Prerequisites](#prerequisites)
|
- [Prerequisites](#prerequisites)
|
||||||
- [Installation and Running](#installation-and-running)
|
- [Installation and Running](#installation-and-running)
|
||||||
- [Installation](#installation)
|
- [Installation](#installation)
|
||||||
- [Using the install script (recommended)](#using-the-install-script-recommended)
|
|
||||||
- [Using Docker](#using-docker)
|
|
||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
- [Running Vigil](#running-vigil)
|
- [Running Vigil](#running-vigil)
|
||||||
- [1. Scheduled Mode (serve)](#1-scheduled-mode-serve)
|
- [1. Scheduled Mode (serve)](#1-scheduled-mode-serve)
|
||||||
@ -91,8 +89,6 @@ This will check for updates every hour and send an email to the provided email a
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Using the install script (recommended)
|
|
||||||
|
|
||||||
The easiest way to install Vigil is by using the interactive install script.
|
The easiest way to install Vigil is by using the interactive install script.
|
||||||
This script automatically checks for required dependencies, offers to install any that are missing, and can optionally configure Vigil to start automatically on boot via **systemd**.
|
This script automatically checks for required dependencies, offers to install any that are missing, and can optionally configure Vigil to start automatically on boot via **systemd**.
|
||||||
|
|
||||||
@ -126,24 +122,6 @@ sudo bash uninstall.sh
|
|||||||
rm uninstall.sh
|
rm uninstall.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using docker
|
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
> The docker run command will not work yet and needs to be updated when released.
|
|
||||||
> The docker section will also be updated to include compose examples.
|
|
||||||
|
|
||||||
If you prefer to use Docker, you can download the latest Docker image from the [Docker Hub](https://hub.docker.com/r/uthmn/vigil)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker pull uthmn/vigil
|
|
||||||
```
|
|
||||||
|
|
||||||
You can now run Vigil using the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run -it --rm uthmn/vigil
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Once installed, Vigil can be run in two modes:
|
Once installed, Vigil can be run in two modes:
|
||||||
|
|||||||
428
install.sh
428
install.sh
@ -10,75 +10,23 @@ fi
|
|||||||
ACTUAL_USER=${SUDO_USER:-$USER}
|
ACTUAL_USER=${SUDO_USER:-$USER}
|
||||||
USER_HOME=$(eval echo ~$ACTUAL_USER)
|
USER_HOME=$(eval echo ~$ACTUAL_USER)
|
||||||
|
|
||||||
# Now check if we already have vigil installed
|
# /opt/vigil = static files/code
|
||||||
if [[ -d "/opt/vigil" ]]; then
|
# /etc/vigil = configurations
|
||||||
echo "Vigil is already installed."
|
# /var/log/vigil = logs
|
||||||
echo "Would you like to upgrade? This will preserve your .env and users.json files. (y/n)"
|
|
||||||
read -r answer
|
|
||||||
|
|
||||||
if [[ "${answer,,}" == "y" ]]; then
|
# Check if we are running on a supported system
|
||||||
echo "Backing up configuration files..."
|
|
||||||
|
|
||||||
# Create temporary backup directory
|
|
||||||
BACKUP_DIR="/tmp/vigil_backup_$(date +%s)"
|
|
||||||
mkdir -p "$BACKUP_DIR"
|
|
||||||
|
|
||||||
# Backup .env and users.json if they exist
|
|
||||||
if [[ -f "/opt/vigil/.env" ]]; then
|
|
||||||
cp /opt/vigil/.env "$BACKUP_DIR/.env"
|
|
||||||
echo "Backed up .env"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -f "/opt/vigil/users.json" ]]; then
|
|
||||||
cp /opt/vigil/users.json "$BACKUP_DIR/users.json"
|
|
||||||
echo "Backed up users.json"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Stop the service if it's running
|
|
||||||
if systemctl is-active --quiet vigil.service; then
|
|
||||||
echo "Stopping vigil service..."
|
|
||||||
systemctl stop vigil.service
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Remove the old installation
|
|
||||||
echo "Removing old installation..."
|
|
||||||
rm -rf /opt/vigil
|
|
||||||
|
|
||||||
echo "Upgrade preparation complete. Proceeding with fresh installation..."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Set flag to restore config later
|
|
||||||
UPGRADE_MODE=true
|
|
||||||
else
|
|
||||||
echo "Installation cancelled. Please remove /opt/vigil manually if you want a fresh install."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Now check if we are running on a supported system
|
|
||||||
if [[ "$OSTYPE" != "linux-gnu"* ]]; then
|
if [[ "$OSTYPE" != "linux-gnu"* ]]; then
|
||||||
echo "This script is only supported on Linux" 1>&2
|
echo "This script is only supported on Linux" 1>&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if we have apt installed
|
# Now check if we have apt installed
|
||||||
if ! command -v apt &> /dev/null; then
|
if ! command -v apt &> /dev/null; then
|
||||||
echo "Apt is not installed. Please install it before running this script." 1>&2
|
echo "Apt is not installed. Please install it before running this script." 1>&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Now check if we have git and python3 installed, if not offer to install them
|
# Now check if we have python3 installed, if not offer to install it
|
||||||
if ! command -v git &> /dev/null; then
|
|
||||||
echo "Git is not installed. Do you want to install it now? (y/n)"
|
|
||||||
read -r answer
|
|
||||||
if [[ "${answer,,}" == "y" ]]; then
|
|
||||||
apt install git -y
|
|
||||||
else
|
|
||||||
echo "Please install git before running this script." 1>&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v python3 &> /dev/null; then
|
if ! command -v python3 &> /dev/null; then
|
||||||
echo "Python 3 is not installed. Do you want to install it now? (y/n)"
|
echo "Python 3 is not installed. Do you want to install it now? (y/n)"
|
||||||
read -r answer
|
read -r answer
|
||||||
@ -90,7 +38,19 @@ if ! command -v python3 &> /dev/null; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Now check if we have python3 -m pip installed, if not offer to install it
|
# Now check if we have curl installed, if not offer to install it
|
||||||
|
if ! command -v curl &> /dev/null; then
|
||||||
|
echo "Curl is not installed. Do you want to install it now? (y/n)"
|
||||||
|
read -r answer
|
||||||
|
if [[ "${answer,,}" == "y" ]]; then
|
||||||
|
apt install curl -y
|
||||||
|
else
|
||||||
|
echo "Please install curl before running this script." 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Now check if we have python3-pip installed, if not offer to install it
|
||||||
if ! python3 -m pip --version &> /dev/null; then
|
if ! python3 -m pip --version &> /dev/null; then
|
||||||
echo "pip is not installed. Do you want to install it now? (y/n)"
|
echo "pip is not installed. Do you want to install it now? (y/n)"
|
||||||
read -r answer
|
read -r answer
|
||||||
@ -102,12 +62,76 @@ if ! python3 -m pip --version &> /dev/null; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Now git clone the repository to /opt/vigil
|
# Now check if we have python3-venv installed, if not offer to install it
|
||||||
echo ""
|
if ! python3 -m venv &> /dev/null; then
|
||||||
echo "Cloning Vigil repository..."
|
echo "Python3-venv is not installed. Do you want to install it now? (y/n)"
|
||||||
git clone https://git.uthmn.com/ufatih/vigil.git /opt/vigil
|
read -r answer
|
||||||
|
if [[ "${answer,,}" == "y" ]]; then
|
||||||
|
apt install python3-venv -y
|
||||||
|
else
|
||||||
|
echo "Please install python3-venv before running this script." 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Now create a virtual environment and install the required packages
|
if [[ -d "/opt/vigil" ]]; then
|
||||||
|
echo "Vigil is already installed."
|
||||||
|
echo "Would you like to upgrade? This will preserve your configuration. (y/n)"
|
||||||
|
read -r answer
|
||||||
|
|
||||||
|
if [[ "${answer,,}" == "y" ]]; then
|
||||||
|
echo "Stopping vigil service..."
|
||||||
|
systemctl stop vigil 2>/dev/null
|
||||||
|
echo "Removing old installation..."
|
||||||
|
rm -rf /opt/vigil
|
||||||
|
# Continue with installation
|
||||||
|
else
|
||||||
|
echo "Installation cancelled."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# First create the required directories
|
||||||
|
echo ""
|
||||||
|
# Creating the /opt/vigil directory
|
||||||
|
echo "Creating /opt/vigil directory..."
|
||||||
|
mkdir -p /opt/vigil
|
||||||
|
# Create the /opt/vigil/services directory
|
||||||
|
echo "Creating /opt/vigil/services directory..."
|
||||||
|
mkdir -p /opt/vigil/services
|
||||||
|
# Create the /etc/vigil directory
|
||||||
|
echo "Creating /etc/vigil directory..."
|
||||||
|
mkdir -p /etc/vigil
|
||||||
|
# Create the /var/log/vigil directory
|
||||||
|
echo "Creating /var/log/vigil directory..."
|
||||||
|
mkdir -p /var/log/vigil
|
||||||
|
|
||||||
|
# Now curl -fsSL each file into the /opt/vigil directory
|
||||||
|
curl -fsSL https://git.uthmn.com/ufatih/vigil/raw/branch/main/LICENSE -o /opt/vigil/LICENSE || { echo "Failed to download LICENSE file."; exit 1; }
|
||||||
|
echo " ✓ LICENSE"
|
||||||
|
curl -fsSL https://git.uthmn.com/ufatih/vigil/raw/branch/main/README.md -o /opt/vigil/README.md || { echo "Failed to download README.md file."; exit 1; }
|
||||||
|
echo " ✓ README.md"
|
||||||
|
curl -fsSL https://git.uthmn.com/ufatih/vigil/raw/branch/main/install.sh -o /opt/vigil/install.sh || { echo "Failed to download install.sh file."; exit 1; }
|
||||||
|
echo " ✓ install.sh"
|
||||||
|
curl -fsSL https://git.uthmn.com/ufatih/vigil/raw/branch/main/uninstall.sh -o /opt/vigil/uninstall.sh || { echo "Failed to download uninstall.sh file."; exit 1; }
|
||||||
|
echo " ✓ uninstall.sh"
|
||||||
|
curl -fsSL https://git.uthmn.com/ufatih/vigil/raw/branch/main/requirements.txt -o /opt/vigil/requirements.txt || { echo "Failed to download requirements.txt file."; exit 1; }
|
||||||
|
echo " ✓ requirements.txt"
|
||||||
|
|
||||||
|
curl -fsSL https://git.uthmn.com/ufatih/vigil/raw/branch/main/main.py -o /opt/vigil/main.py || { echo "Failed to download main.py file."; exit 1; }
|
||||||
|
echo " ✓ main.py"
|
||||||
|
curl -fsSL https://git.uthmn.com/ufatih/vigil/raw/branch/main/services/apt.py -o /opt/vigil/services/apt.py || { echo "Failed to download services/apt.py file."; exit 1; }
|
||||||
|
echo " ✓ services/apt.py"
|
||||||
|
curl -fsSL https://git.uthmn.com/ufatih/vigil/raw/branch/main/services/mail.py -o /opt/vigil/services/mail.py || { echo "Failed to download services/mail.py file."; exit 1; }
|
||||||
|
echo " ✓ services/mail.py"
|
||||||
|
curl -fsSL https://git.uthmn.com/ufatih/vigil/raw/branch/main/services/logger.py -o /opt/vigil/services/logger.py || { echo "Failed to download services/logger.py file."; exit 1; }
|
||||||
|
echo " ✓ services/logger.py"
|
||||||
|
curl -fsSL https://git.uthmn.com/ufatih/vigil/raw/branch/main/services/__init__.py -o /opt/vigil/services/__init__.py || { echo "Failed to download services/__init__.py file."; exit 1; }
|
||||||
|
echo " ✓ services/__init__.py"
|
||||||
|
|
||||||
|
echo "✓ All files downloaded successfully"
|
||||||
|
|
||||||
|
# Now create a venv and install the required packages
|
||||||
echo ""
|
echo ""
|
||||||
echo "Setting up virtual environment and installing dependencies..."
|
echo "Setting up virtual environment and installing dependencies..."
|
||||||
python3 -m venv /opt/vigil/.venv
|
python3 -m venv /opt/vigil/.venv
|
||||||
@ -115,221 +139,18 @@ source /opt/vigil/.venv/bin/activate
|
|||||||
pip install -r /opt/vigil/requirements.txt
|
pip install -r /opt/vigil/requirements.txt
|
||||||
deactivate
|
deactivate
|
||||||
|
|
||||||
# Now create a .env file and request the user to fill in the required values
|
# Create a systemd service
|
||||||
echo ""
|
|
||||||
echo "=== Email Configuration ==="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Check if we're in upgrade mode and have a backup
|
echo "Creating systemd service..."
|
||||||
if [[ "$UPGRADE_MODE" == true ]] && [[ -f "$BACKUP_DIR/.env" ]]; then
|
cat > /etc/systemd/system/vigil.service << 'EOF'
|
||||||
echo "Found existing .env configuration. Would you like to:"
|
|
||||||
echo "1) Keep existing configuration"
|
|
||||||
echo "2) Enter new configuration"
|
|
||||||
read -r config_choice
|
|
||||||
|
|
||||||
if [[ "$config_choice" == "1" ]]; then
|
|
||||||
cp "$BACKUP_DIR/.env" /opt/vigil/.env
|
|
||||||
echo "Restored existing .env configuration"
|
|
||||||
chmod 600 /opt/vigil/.env
|
|
||||||
|
|
||||||
# Skip to connection testing
|
|
||||||
SKIP_ENV_INPUT=true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$SKIP_ENV_INPUT" != true ]]; then
|
|
||||||
touch /opt/vigil/.env
|
|
||||||
|
|
||||||
# SMTP settings
|
|
||||||
echo "Please enter SMTP server address:"
|
|
||||||
read -r SMTP_SERVER
|
|
||||||
|
|
||||||
echo "Please enter SMTP port (default: 587 for TLS, 465 for SSL):"
|
|
||||||
read -r SMTP_PORT
|
|
||||||
|
|
||||||
echo "Please enter SMTP username:"
|
|
||||||
read -r SMTP_USERNAME
|
|
||||||
|
|
||||||
echo "Please enter SMTP password:"
|
|
||||||
read -rs SMTP_PASSWORD
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# IMAP settings
|
|
||||||
echo "Please enter IMAP server address:"
|
|
||||||
read -r IMAP_SERVER
|
|
||||||
|
|
||||||
echo "Please enter IMAP port (default: 993 for SSL):"
|
|
||||||
read -r IMAP_PORT
|
|
||||||
|
|
||||||
echo "Please enter IMAP username:"
|
|
||||||
read -r IMAP_USERNAME
|
|
||||||
|
|
||||||
echo "Please enter IMAP password:"
|
|
||||||
read -rs IMAP_PASSWORD
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Write to .env file
|
|
||||||
cat > /opt/vigil/.env << EOF
|
|
||||||
SMTP_SERVER=$SMTP_SERVER
|
|
||||||
SMTP_PORT=$SMTP_PORT
|
|
||||||
SMTP_USERNAME=$SMTP_USERNAME
|
|
||||||
SMTP_PASSWORD=$SMTP_PASSWORD
|
|
||||||
|
|
||||||
IMAP_SERVER=$IMAP_SERVER
|
|
||||||
IMAP_PORT=$IMAP_PORT
|
|
||||||
IMAP_USERNAME=$IMAP_USERNAME
|
|
||||||
IMAP_PASSWORD=$IMAP_PASSWORD
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo "Configuration saved to /opt/vigil/.env"
|
|
||||||
|
|
||||||
# Set secure permissions on .env file (contains passwords)
|
|
||||||
chmod 600 /opt/vigil/.env
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Test if the inputs work/are valid, else tell the user to fill in later and continue
|
|
||||||
if [[ "$SKIP_ENV_INPUT" != true ]]; then
|
|
||||||
echo ""
|
|
||||||
echo "Testing connections..."
|
|
||||||
|
|
||||||
# Check if required commands are available
|
|
||||||
if command -v nc >/dev/null 2>&1; then
|
|
||||||
# Test SMTP connection with netcat (timeout after 5 seconds)
|
|
||||||
echo "Testing SMTP connection to $SMTP_SERVER:$SMTP_PORT..."
|
|
||||||
if timeout 5 nc -zv "$SMTP_SERVER" "$SMTP_PORT" 2>&1 | grep -q succeeded; then
|
|
||||||
echo "[OK] SMTP connection successful"
|
|
||||||
SMTP_VALID=true
|
|
||||||
else
|
|
||||||
echo "[FAILED] SMTP connection failed - could not reach $SMTP_SERVER:$SMTP_PORT"
|
|
||||||
SMTP_VALID=false
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Test IMAP connection
|
|
||||||
echo "Testing IMAP connection to $IMAP_SERVER:$IMAP_PORT..."
|
|
||||||
if timeout 5 nc -zv "$IMAP_SERVER" "$IMAP_PORT" 2>&1 | grep -q succeeded; then
|
|
||||||
echo "[OK] IMAP connection successful"
|
|
||||||
IMAP_VALID=true
|
|
||||||
else
|
|
||||||
echo "[FAILED] IMAP connection failed - could not reach $IMAP_SERVER:$IMAP_PORT"
|
|
||||||
IMAP_VALID=false
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "[WARNING] Cannot test connections (nc not installed). Skipping validation."
|
|
||||||
SMTP_VALID=unknown
|
|
||||||
IMAP_VALID=unknown
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Summary and continue
|
|
||||||
echo ""
|
|
||||||
if [ "$SMTP_VALID" = false ] || [ "$IMAP_VALID" = false ]; then
|
|
||||||
echo "[WARNING] Some connections failed."
|
|
||||||
echo "Configuration has been saved to /opt/vigil/.env"
|
|
||||||
echo "Please verify your settings and update the file manually if needed."
|
|
||||||
echo ""
|
|
||||||
read -p "Press Enter to continue..." -r
|
|
||||||
else
|
|
||||||
echo "[OK] All connections successful!"
|
|
||||||
echo "Configuration saved to /opt/vigil/.env"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo ""
|
|
||||||
echo "[OK] Using existing email configuration"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Now create a users.json file and request the user to fill in the required values (or leave empty to skip)
|
|
||||||
echo ""
|
|
||||||
echo "=== Notification Recipients ==="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Check if we're in upgrade mode and have a backup
|
|
||||||
if [[ "$UPGRADE_MODE" == true ]] && [[ -f "$BACKUP_DIR/users.json" ]]; then
|
|
||||||
echo "Found existing users.json configuration. Would you like to:"
|
|
||||||
echo "1) Keep existing recipients"
|
|
||||||
echo "2) Enter new recipients"
|
|
||||||
read -r users_choice
|
|
||||||
|
|
||||||
if [[ "$users_choice" == "1" ]]; then
|
|
||||||
cp "$BACKUP_DIR/users.json" /opt/vigil/users.json
|
|
||||||
echo "Restored existing notification recipients"
|
|
||||||
|
|
||||||
# Clean up backup directory
|
|
||||||
rm -rf "$BACKUP_DIR"
|
|
||||||
|
|
||||||
# Skip to next section
|
|
||||||
SKIP_USERS_INPUT=true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$SKIP_USERS_INPUT" != true ]]; then
|
|
||||||
echo "Please enter the email addresses vigil will send notifications to (comma separated):"
|
|
||||||
echo "(Leave empty to skip)"
|
|
||||||
read -r EMAILS
|
|
||||||
|
|
||||||
# Strip whitespace and convert to JSON array
|
|
||||||
if [ -n "$EMAILS" ]; then
|
|
||||||
# Remove all whitespace, split by comma, and build JSON array
|
|
||||||
EMAILS_CLEAN=$(echo "$EMAILS" | tr -d '[:space:]')
|
|
||||||
|
|
||||||
# Convert to JSON array format
|
|
||||||
EMAILS_JSON="["
|
|
||||||
IFS=',' read -ra EMAIL_ARRAY <<< "$EMAILS_CLEAN"
|
|
||||||
for i in "${!EMAIL_ARRAY[@]}"; do
|
|
||||||
if [ $i -gt 0 ]; then
|
|
||||||
EMAILS_JSON="$EMAILS_JSON,"
|
|
||||||
fi
|
|
||||||
EMAILS_JSON="$EMAILS_JSON\"${EMAIL_ARRAY[$i]}\""
|
|
||||||
done
|
|
||||||
EMAILS_JSON="$EMAILS_JSON]"
|
|
||||||
|
|
||||||
echo "$EMAILS_JSON" > /opt/vigil/users.json
|
|
||||||
echo "Notification recipients saved to /opt/vigil/users.json"
|
|
||||||
else
|
|
||||||
echo "[]" > /opt/vigil/users.json
|
|
||||||
echo "No recipients configured. You can add them later in /opt/vigil/users.json"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clean up backup directory if it exists
|
|
||||||
if [[ -n "$BACKUP_DIR" ]] && [[ -d "$BACKUP_DIR" ]]; then
|
|
||||||
rm -rf "$BACKUP_DIR" # Don't want to hit a steam
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Set ownership of vigil directory to actual user
|
|
||||||
chown -R "$ACTUAL_USER:$ACTUAL_USER" /opt/vigil
|
|
||||||
|
|
||||||
# Create the alias to the command
|
|
||||||
echo ""
|
|
||||||
echo "Adding 'vigil' command alias..."
|
|
||||||
ALIAS_LINE="alias vigil='source /opt/vigil/.venv/bin/activate && python3 /opt/vigil/main.py && deactivate'"
|
|
||||||
|
|
||||||
# Add to user's bashrc if not already present
|
|
||||||
if ! grep -q "alias vigil=" "$USER_HOME/.bashrc" 2>/dev/null; then
|
|
||||||
echo "$ALIAS_LINE" >> "$USER_HOME/.bashrc"
|
|
||||||
echo "Alias added to $USER_HOME/.bashrc"
|
|
||||||
else
|
|
||||||
echo "Alias already exists in $USER_HOME/.bashrc"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Suggest to add vigil to systemctl to autostart (after network is up)
|
|
||||||
echo ""
|
|
||||||
echo "Would you like to add vigil to autostart on boot? (y/n)"
|
|
||||||
read -r answer
|
|
||||||
if [[ "${answer,,}" == "y" ]]; then
|
|
||||||
# Create systemd service file
|
|
||||||
cat > /etc/systemd/system/vigil.service << EOF
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Vigil Monitoring Service
|
Description=Vigil - APT Update Monitor
|
||||||
After=network-online.target
|
After=network-online.target
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=$ACTUAL_USER
|
ExecStart=/opt/vigil/.venv/bin/python /opt/vigil/main.py serve --check-daily 18
|
||||||
WorkingDirectory=/opt/vigil
|
|
||||||
Environment="PATH=/opt/vigil/.venv/bin"
|
|
||||||
ExecStart=/opt/vigil/.venv/bin/python3 /opt/vigil/main.py
|
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=10
|
RestartSec=10
|
||||||
|
|
||||||
@ -337,26 +158,53 @@ RestartSec=10
|
|||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Reload systemd, enable and start the service
|
systemctl daemon-reload
|
||||||
systemctl daemon-reload
|
echo "✓ Systemd service created (not enabled or started)"
|
||||||
systemctl enable vigil.service
|
|
||||||
systemctl start vigil.service
|
|
||||||
|
|
||||||
echo "[OK] Vigil service has been created and started"
|
# Create a command to run vigil
|
||||||
echo "You can check its status with: systemctl status vigil"
|
echo "Creating vigil command..."
|
||||||
|
cat > /usr/local/bin/vigil << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
exec /opt/vigil/.venv/bin/python /opt/vigil/main.py "$@"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x /usr/local/bin/vigil
|
||||||
|
echo "✓ Command 'vigil' available system-wide"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✓ Vigil installed successfully!"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check what needs to be configured
|
||||||
|
NEEDS_CONFIG=false
|
||||||
|
|
||||||
|
if [[ ! -f /etc/vigil/.env ]]; then
|
||||||
|
echo "⚠️ Missing: /etc/vigil/.env"
|
||||||
|
echo " Create this file with your SMTP credentials"
|
||||||
|
echo " See README for template: /opt/vigil/README.md"
|
||||||
|
echo ""
|
||||||
|
NEEDS_CONFIG=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f /etc/vigil/users.json ]]; then
|
||||||
|
echo "⚠️ Missing: /etc/vigil/users.json"
|
||||||
|
echo " Create this file with recipient email addresses"
|
||||||
|
echo " Example: [\"admin@example.com\"]"
|
||||||
|
echo ""
|
||||||
|
NEEDS_CONFIG=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$NEEDS_CONFIG" == "true" ]]; then
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Configure the files above"
|
||||||
|
echo " 2. Test: sudo vigil now"
|
||||||
|
echo " 3. Enable service: sudo systemctl enable --now vigil"
|
||||||
else
|
else
|
||||||
echo "Skipping autostart configuration"
|
echo "✓ Configuration files found"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Test: sudo vigil now"
|
||||||
|
echo " 2. Enable service: sudo systemctl enable --now vigil"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "========================================"
|
|
||||||
echo "Vigil has been installed successfully!"
|
|
||||||
echo "========================================"
|
|
||||||
echo ""
|
|
||||||
echo "To use vigil, run: source ~/.bashrc && vigil"
|
|
||||||
echo "Or simply open a new terminal and run: vigil"
|
|
||||||
echo ""
|
|
||||||
if [[ "${answer,,}" == "y" ]]; then
|
|
||||||
echo "Vigil is now running as a system service."
|
|
||||||
echo "Use 'systemctl status vigil' to check its status"
|
|
||||||
fi
|
|
||||||
152
main.py
152
main.py
@ -11,27 +11,58 @@ from typing import Union
|
|||||||
|
|
||||||
import services.apt
|
import services.apt
|
||||||
import services.mail
|
import services.mail
|
||||||
|
import html as hypertext
|
||||||
|
import re
|
||||||
|
|
||||||
from socket import gethostname
|
from socket import gethostname
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from os.path import dirname, join, exists
|
from os.path import exists
|
||||||
|
|
||||||
|
from services.logger import logger
|
||||||
|
|
||||||
import typer
|
import typer
|
||||||
|
|
||||||
|
import signal
|
||||||
|
|
||||||
|
# Its beautiful ;)
|
||||||
|
EMAIL_REGEX = re.compile(r"""
|
||||||
|
(?:[a-z0-9!#$%&'*+\x2f=?^_`\x7b-\x7d~\x2d]+(?:\.[a-z0-9!#$%&'*+\x2f=?^_`\x7b-\x7d~\x2d]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9\x2d]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9\x2d]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9\x2d]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])
|
||||||
|
""", re.VERBOSE | re.IGNORECASE)
|
||||||
|
|
||||||
|
# Change for version of Vigil
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
|
shutdown_flag = False
|
||||||
|
|
||||||
|
def handle_shutdown(signum, frame):
|
||||||
|
global shutdown_flag
|
||||||
|
logger.info("Shutdown signal received; finishing current cycle and exiting...")
|
||||||
|
shutdown_flag = True
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, handle_shutdown)
|
||||||
|
signal.signal(signal.SIGTERM, handle_shutdown)
|
||||||
|
|
||||||
app = typer.Typer()
|
app = typer.Typer()
|
||||||
|
|
||||||
def wait_until(target_time):
|
def wait_until_interruptible(target_time):
|
||||||
|
global shutdown_flag
|
||||||
|
while not shutdown_flag:
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
seconds_to_wait = (target_time - now).total_seconds()
|
remaining = (target_time - now).total_seconds()
|
||||||
if seconds_to_wait > 0:
|
if remaining <= 0:
|
||||||
time.sleep(seconds_to_wait)
|
return
|
||||||
|
# sleep in small chunks so shutdown is responsive
|
||||||
|
time.sleep(min(remaining, 0.5))
|
||||||
|
|
||||||
def schedule(mode, hour, minute=0, receiver_email=None):
|
def schedule(mode, hour, minute=0, receiver_email=None):
|
||||||
print(f"Scheduler started at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Mode: {mode}")
|
global shutdown_flag
|
||||||
while True:
|
logger.info(f"Scheduler started at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Mode: {mode}")
|
||||||
|
|
||||||
|
while not shutdown_flag:
|
||||||
try:
|
try:
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
|
|
||||||
if mode == "daily":
|
if mode == "daily":
|
||||||
target = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
|
target = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
|
||||||
if target <= now:
|
if target <= now:
|
||||||
@ -43,11 +74,19 @@ def schedule(mode, hour, minute=0, receiver_email=None):
|
|||||||
else:
|
else:
|
||||||
raise ValueError("Mode must be 'daily' or 'hourly'.")
|
raise ValueError("Mode must be 'daily' or 'hourly'.")
|
||||||
|
|
||||||
wait_until(target)
|
logger.info(f"Next check scheduled for {target.strftime('%Y-%m-%d %H:%M:%S')}")
|
||||||
|
wait_until_interruptible(target)
|
||||||
|
if shutdown_flag:
|
||||||
|
break
|
||||||
|
|
||||||
generate_email(receiver_email)
|
generate_email(receiver_email)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error in scheduled task: {e}")
|
logger.error(f"Error in scheduled task: {e}", exc_info=True)
|
||||||
time.sleep(60) # Wait before retrying
|
time.sleep(60)
|
||||||
|
|
||||||
|
logger.info("Scheduler exited cleanly.")
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def serve(receiver_email: str = None, check_hourly: bool = False, check_daily: str = "18"):
|
def serve(receiver_email: str = None, check_hourly: bool = False, check_daily: str = "18"):
|
||||||
@ -58,7 +97,7 @@ def serve(receiver_email: str = None, check_hourly: bool = False, check_daily: s
|
|||||||
- check_daily=int → hour of day for daily email
|
- check_daily=int → hour of day for daily email
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if check_hourly and check_daily:
|
if check_hourly and check_daily not in [False, "false", "False", None]:
|
||||||
check_daily = False
|
check_daily = False
|
||||||
|
|
||||||
if check_hourly:
|
if check_hourly:
|
||||||
@ -71,11 +110,11 @@ def serve(receiver_email: str = None, check_hourly: bool = False, check_daily: s
|
|||||||
try:
|
try:
|
||||||
hour = int(check_daily)
|
hour = int(check_daily)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print(f"Invalid check_daily value: {check_daily}")
|
logger.error(f"Invalid check_daily value: {check_daily}")
|
||||||
exit(1)
|
raise typer.Exit(1)
|
||||||
schedule("daily", hour, receiver_email=receiver_email)
|
schedule("daily", hour, receiver_email=receiver_email)
|
||||||
else:
|
else:
|
||||||
print("No schedule selected.")
|
logger.error("No schedule selected.")
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def now(receiver_email: str = None):
|
def now(receiver_email: str = None):
|
||||||
@ -87,25 +126,13 @@ def now(receiver_email: str = None):
|
|||||||
def generate_email(receiver_emails: Union[list, None]):
|
def generate_email(receiver_emails: Union[list, None]):
|
||||||
services.apt.require_root()
|
services.apt.require_root()
|
||||||
if not services.apt.detect_apt():
|
if not services.apt.detect_apt():
|
||||||
print("Apt not found on this system.")
|
logger.error("Apt not found on this system.")
|
||||||
exit(1)
|
raise typer.Exit(1)
|
||||||
|
try:
|
||||||
updates = services.apt.check_updates()
|
updates = services.apt.check_updates()
|
||||||
|
except Exception as e:
|
||||||
# For testing
|
logger.error(f"Failed to check for updates: {e}", exc_info=True)
|
||||||
#updates = {
|
raise typer.Exit(1)
|
||||||
# "vim": {
|
|
||||||
# "installed_version": "8.2.3400",
|
|
||||||
# "latest_version": "8.2.3400",
|
|
||||||
# "repo": "universe",
|
|
||||||
# "security": True
|
|
||||||
# },
|
|
||||||
# "vim-gtk3": {
|
|
||||||
# "installed_version": "8.2.3400",
|
|
||||||
# "latest_version": "8.2.3400",
|
|
||||||
# "repo": "universe",
|
|
||||||
# "security": False
|
|
||||||
# }
|
|
||||||
#}
|
|
||||||
|
|
||||||
receiver_email = []
|
receiver_email = []
|
||||||
if receiver_emails:
|
if receiver_emails:
|
||||||
@ -116,13 +143,27 @@ def generate_email(receiver_emails: Union[list, None]):
|
|||||||
|
|
||||||
# If no CLI emails are provided
|
# If no CLI emails are provided
|
||||||
if not receiver_email:
|
if not receiver_email:
|
||||||
users_json_path = join(dirname(__file__), "users.json")
|
users_json_path = "/etc/vigil/users.json"
|
||||||
if exists(users_json_path):
|
if exists(users_json_path):
|
||||||
|
try:
|
||||||
with open(users_json_path, "r") as f:
|
with open(users_json_path, "r") as f:
|
||||||
receiver_email = json.load(f)
|
data = json.load(f)
|
||||||
|
if not isinstance(data, list):
|
||||||
|
logger.error("users.json must contain a list of email addresses")
|
||||||
|
raise typer.Exit(1)
|
||||||
|
receiver_email = [str(email).strip() for email in data]
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
logger.error(f"Invalid JSON in users.json: {e}")
|
||||||
|
raise typer.Exit(1)
|
||||||
else:
|
else:
|
||||||
print("No email address provided and users.json not found.")
|
logger.error("No email address provided and /etc/vigil/users.json not found.")
|
||||||
exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
# Validate email address(es)
|
||||||
|
for email in receiver_email:
|
||||||
|
if not EMAIL_REGEX.match(email):
|
||||||
|
logger.error(f"Invalid email address: {email}")
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
# Get how many security updates are available
|
# Get how many security updates are available
|
||||||
security_updates = 0
|
security_updates = 0
|
||||||
@ -137,7 +178,7 @@ def generate_email(receiver_emails: Union[list, None]):
|
|||||||
|
|
||||||
# Check if there are any updates at all
|
# Check if there are any updates at all
|
||||||
if total_updates == 0:
|
if total_updates == 0:
|
||||||
print("No updates available.")
|
logger.info("No updates available.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get system hostname
|
# Get system hostname
|
||||||
@ -154,10 +195,10 @@ def generate_email(receiver_emails: Union[list, None]):
|
|||||||
continue
|
continue
|
||||||
chunk = f'''
|
chunk = f'''
|
||||||
<tr>
|
<tr>
|
||||||
<td style="border: 1px solid #ddd; padding: 8px;">{package}</td>
|
<td style="border: 1px solid #ddd; padding: 8px;">{hypertext.escape(package)}</td>
|
||||||
<td style="border: 1px solid #ddd; padding: 8px;">{updates[package]["installed_version"]}</td>
|
<td style="border: 1px solid #ddd; padding: 8px;">{hypertext.escape(updates[package]["installed_version"])}</td>
|
||||||
<td style="border: 1px solid #ddd; padding: 8px;">{updates[package]["latest_version"]}</td>
|
<td style="border: 1px solid #ddd; padding: 8px;">{hypertext.escape(updates[package]["latest_version"])}</td>
|
||||||
<td style="border: 1px solid #ddd; padding: 8px;">{updates[package]["repo"]}</td>
|
<td style="border: 1px solid #ddd; padding: 8px;">{hypertext.escape(updates[package]["repo"])}</td>
|
||||||
</tr>
|
</tr>
|
||||||
'''
|
'''
|
||||||
security_chunks += chunk
|
security_chunks += chunk
|
||||||
@ -185,10 +226,10 @@ def generate_email(receiver_emails: Union[list, None]):
|
|||||||
continue
|
continue
|
||||||
chunk = f'''
|
chunk = f'''
|
||||||
<tr>
|
<tr>
|
||||||
<td style="border: 1px solid #ddd; padding: 8px;">{package}</td>
|
<td style="border: 1px solid #ddd; padding: 8px;">{hypertext.escape(package)}</td>
|
||||||
<td style="border: 1px solid #ddd; padding: 8px;">{updates[package]["installed_version"]}</td>
|
<td style="border: 1px solid #ddd; padding: 8px;">{hypertext.escape(updates[package]["installed_version"])}</td>
|
||||||
<td style="border: 1px solid #ddd; padding: 8px;">{updates[package]["latest_version"]}</td>
|
<td style="border: 1px solid #ddd; padding: 8px;">{hypertext.escape(updates[package]["latest_version"])}</td>
|
||||||
<td style="border: 1px solid #ddd; padding: 8px;">{updates[package]["repo"]}</td>
|
<td style="border: 1px solid #ddd; padding: 8px;">{hypertext.escape(updates[package]["repo"])}</td>
|
||||||
</tr>
|
</tr>
|
||||||
'''
|
'''
|
||||||
general_chunks += chunk
|
general_chunks += chunk
|
||||||
@ -210,10 +251,29 @@ def generate_email(receiver_emails: Union[list, None]):
|
|||||||
|
|
||||||
html = security + general
|
html = security + general
|
||||||
|
|
||||||
|
try:
|
||||||
services.mail.send_email(receiver_email, subject, html)
|
services.mail.send_email(receiver_email, subject, html)
|
||||||
|
logger.info(f"Update notification sent: {security_updates} security, {general_updates} general updates to {len(receiver_email)} recipient(s)")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to send email notification: {e}", exc_info=True)
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
def version_callback(value: bool):
|
||||||
|
if value:
|
||||||
|
typer.echo(f"Vigil {__version__}")
|
||||||
|
raise typer.Exit()
|
||||||
|
|
||||||
@app.callback()
|
@app.callback()
|
||||||
def callback():
|
def callback(
|
||||||
|
version: bool = typer.Option(
|
||||||
|
None,
|
||||||
|
"--version",
|
||||||
|
"-v",
|
||||||
|
callback=version_callback,
|
||||||
|
is_eager=True,
|
||||||
|
help="Show version and exit"
|
||||||
|
)
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Checks apt for upgrades and emails them
|
Checks apt for upgrades and emails them
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -3,8 +3,8 @@ dotenv==0.9.9
|
|||||||
markdown-it-py==4.0.0
|
markdown-it-py==4.0.0
|
||||||
mdurl==0.1.2
|
mdurl==0.1.2
|
||||||
Pygments==2.19.2
|
Pygments==2.19.2
|
||||||
python-dotenv==1.1.1
|
python-dotenv==1.2.1
|
||||||
rich==14.2.0
|
rich==14.2.0
|
||||||
shellingham==1.5.4
|
shellingham==1.5.4
|
||||||
typer==0.19.2
|
typer==0.20.0
|
||||||
typing_extensions==4.15.0
|
typing_extensions==4.15.0
|
||||||
|
|||||||
0
services/__init__.py
Normal file
0
services/__init__.py
Normal file
@ -6,13 +6,15 @@
|
|||||||
# © Uthmn 2025 under MIT license
|
# © Uthmn 2025 under MIT license
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import os
|
from os import geteuid
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from services.logger import logger
|
||||||
|
|
||||||
def require_root():
|
def require_root():
|
||||||
if os.geteuid() != 0:
|
if geteuid() != 0:
|
||||||
print("This script requires root privileges. Please run with sudo.")
|
logger.error("This script requires root privileges. Please run with sudo.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def detect_apt():
|
def detect_apt():
|
||||||
@ -74,4 +76,4 @@ if __name__ == "__main__":
|
|||||||
require_root()
|
require_root()
|
||||||
if detect_apt():
|
if detect_apt():
|
||||||
updates = check_updates()
|
updates = check_updates()
|
||||||
print(updates)
|
logger.info(updates)
|
||||||
|
|||||||
46
services/logger.py
Normal file
46
services/logger.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# _ __________________
|
||||||
|
# | | / / _/ ____/ _/ /
|
||||||
|
# | | / // // / __ / // /
|
||||||
|
# | |/ // // /_/ // // /___
|
||||||
|
# |___/___/\____/___/_____/
|
||||||
|
# © Uthmn 2025 under MIT license
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
from os import makedirs
|
||||||
|
from os.path import exists
|
||||||
|
|
||||||
|
# Setup logging once, globally
|
||||||
|
logger = logging.getLogger('vigil')
|
||||||
|
|
||||||
|
# Prevent duplicate setup
|
||||||
|
if not logger.handlers:
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# Create log directory if it doesn't exist
|
||||||
|
log_dir = '/var/log/vigil'
|
||||||
|
if not exists(log_dir):
|
||||||
|
makedirs(log_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# File handler with rotation
|
||||||
|
file_handler = RotatingFileHandler(
|
||||||
|
'/var/log/vigil/vigil.log',
|
||||||
|
maxBytes=10*1024*1024, # 10MB
|
||||||
|
backupCount=3
|
||||||
|
)
|
||||||
|
file_handler.setLevel(logging.INFO)
|
||||||
|
file_handler.setFormatter(logging.Formatter(
|
||||||
|
'%(asctime)s [%(levelname)s] %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
))
|
||||||
|
|
||||||
|
# Console handler (for systemd journal)
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setLevel(logging.INFO)
|
||||||
|
console_handler.setFormatter(logging.Formatter(
|
||||||
|
'%(asctime)s [%(levelname)s] %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
))
|
||||||
|
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
logger.addHandler(console_handler)
|
||||||
@ -13,16 +13,18 @@ from email import message_from_bytes
|
|||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from os import getenv
|
from os import getenv
|
||||||
from os.path import dirname, join, exists
|
from os.path import exists
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from services.logger import logger
|
||||||
|
|
||||||
# Exit if .env does not exist
|
# Exit if .env does not exist
|
||||||
if not exists(join(dirname(__file__), "../.env")):
|
if not exists("/etc/vigil/.env"):
|
||||||
print("Please create a .env file in the root directory.")
|
logger.error("Please create a .env file in /etc/vigil.")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
# Load environment variables from .env file
|
# Load environment variables from .env file
|
||||||
dotenv_path = join(dirname(__file__), "../.env")
|
dotenv_path = "/etc/vigil/.env"
|
||||||
load_dotenv(dotenv_path)
|
load_dotenv(dotenv_path)
|
||||||
|
|
||||||
# SMTP server settings
|
# SMTP server settings
|
||||||
@ -43,7 +45,7 @@ RETRY_DELAY = 2 # in seconds
|
|||||||
|
|
||||||
# Check if all environment variables are set
|
# Check if all environment variables are set
|
||||||
if not SMTP_SERVER or not SMTP_PORT or not SMTP_USERNAME or not SMTP_PASSWORD or not IMAP_SERVER or not IMAP_PORT or not IMAP_USERNAME or not IMAP_PASSWORD:
|
if not SMTP_SERVER or not SMTP_PORT or not SMTP_USERNAME or not SMTP_PASSWORD or not IMAP_SERVER or not IMAP_PORT or not IMAP_USERNAME or not IMAP_PASSWORD:
|
||||||
print("Please set all environment variables in .env file.")
|
logger.error("Please set all environment variables in .env file.")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
# Make sure SMTP and IMAP ports are integers
|
# Make sure SMTP and IMAP ports are integers
|
||||||
@ -60,11 +62,11 @@ def connect_smtp_with_retry():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
if attempt < MAX_RETRIES - 1:
|
if attempt < MAX_RETRIES - 1:
|
||||||
wait_time = RETRY_DELAY * (2 ** attempt) # Exponential backoff
|
wait_time = RETRY_DELAY * (2 ** attempt) # Exponential backoff
|
||||||
print(f"SMTP connection attempt {attempt + 1} failed: {e}")
|
logger.error(f"SMTP connection attempt {attempt + 1} failed: {e}")
|
||||||
print(f"Retrying in {wait_time} seconds...")
|
logger.info(f"Retrying in {wait_time} seconds...")
|
||||||
time.sleep(wait_time)
|
time.sleep(wait_time)
|
||||||
else:
|
else:
|
||||||
print(f"Failed to connect to SMTP after {MAX_RETRIES} attempts: {e}")
|
logger.error(f"Failed to connect to SMTP after {MAX_RETRIES} attempts: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def connect_imap_with_retry():
|
def connect_imap_with_retry():
|
||||||
@ -77,11 +79,11 @@ def connect_imap_with_retry():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
if attempt < MAX_RETRIES - 1:
|
if attempt < MAX_RETRIES - 1:
|
||||||
wait_time = RETRY_DELAY * (2 ** attempt) # Exponential backoff
|
wait_time = RETRY_DELAY * (2 ** attempt) # Exponential backoff
|
||||||
print(f"IMAP connection attempt {attempt + 1} failed: {e}")
|
logger.error(f"IMAP connection attempt {attempt + 1} failed: {e}")
|
||||||
print(f"Retrying in {wait_time} seconds...")
|
logger.info(f"Retrying in {wait_time} seconds...")
|
||||||
time.sleep(wait_time)
|
time.sleep(wait_time)
|
||||||
else:
|
else:
|
||||||
print(f"Failed to connect to IMAP after {MAX_RETRIES} attempts: {e}")
|
logger.error(f"Failed to connect to IMAP after {MAX_RETRIES} attempts: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# Test all connections
|
# Test all connections
|
||||||
@ -89,14 +91,14 @@ try:
|
|||||||
server = connect_smtp_with_retry()
|
server = connect_smtp_with_retry()
|
||||||
server.quit()
|
server.quit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Initial SMTP connection test failed: {e}")
|
logger.error(f"Initial SMTP connection test failed: {e}")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
M = connect_imap_with_retry()
|
M = connect_imap_with_retry()
|
||||||
M.logout()
|
M.logout()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Initial IMAP connection test failed: {e}")
|
logger.error(f"Initial IMAP connection test failed: {e}")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
def send_email(receiver_emails, subject, body):
|
def send_email(receiver_emails, subject, body):
|
||||||
@ -113,11 +115,12 @@ def send_email(receiver_emails, subject, body):
|
|||||||
server = connect_smtp_with_retry()
|
server = connect_smtp_with_retry()
|
||||||
try:
|
try:
|
||||||
server.sendmail(sender_email, receiver_emails, message.as_string())
|
server.sendmail(sender_email, receiver_emails, message.as_string())
|
||||||
print("HTML email sent successfully!")
|
logger.info("HTML email sent successfully!")
|
||||||
finally:
|
finally:
|
||||||
server.quit()
|
server.quit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to send email: {e}")
|
logger.error(f"Failed to send email: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
def receive_emails(folder="INBOX", limit=50):
|
def receive_emails(folder="INBOX", limit=50):
|
||||||
emails = {}
|
emails = {}
|
||||||
@ -131,7 +134,7 @@ def receive_emails(folder="INBOX", limit=50):
|
|||||||
# Get all message IDs
|
# Get all message IDs
|
||||||
typ, data = M.search(None, 'ALL')
|
typ, data = M.search(None, 'ALL')
|
||||||
if typ != 'OK' or not data[0]:
|
if typ != 'OK' or not data[0]:
|
||||||
print("No messages found.")
|
logger.info("No messages found.")
|
||||||
return emails
|
return emails
|
||||||
|
|
||||||
all_ids = data[0].split()
|
all_ids = data[0].split()
|
||||||
@ -140,7 +143,7 @@ def receive_emails(folder="INBOX", limit=50):
|
|||||||
for num in last_ids:
|
for num in last_ids:
|
||||||
typ, msg_data = M.fetch(num, '(RFC822)')
|
typ, msg_data = M.fetch(num, '(RFC822)')
|
||||||
if typ != 'OK':
|
if typ != 'OK':
|
||||||
print(f"Failed to fetch message {num}")
|
logger.error(f"Failed to fetch message {num}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Parse the email
|
# Parse the email
|
||||||
@ -185,6 +188,6 @@ def receive_emails(folder="INBOX", limit=50):
|
|||||||
M.logout()
|
M.logout()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error receiving emails: {e}")
|
logger.error(f"Error receiving emails: {e}")
|
||||||
|
|
||||||
return emails
|
return emails
|
||||||
|
|||||||
108
uninstall.sh
108
uninstall.sh
@ -1,76 +1,70 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# TODO: Add uninstall to readme
|
# Check if running as root
|
||||||
|
|
||||||
# Just double check, check /opt/vigil exists, then remove it, also check+remove systemd service, remove aliases from bashrc and also dont offer but notify that dependencies not uninstalled
|
|
||||||
|
|
||||||
# Check if vigil is installed, if not then exit
|
|
||||||
if [[ ! -d "/opt/vigil" ]]; then
|
|
||||||
echo "Vigil is not installed. Exiting." 1>&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if we are running as root
|
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
echo "This script must be run as root" 1>&2
|
echo "This script must be run as root" 1>&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Get the actual user (not root) for later use
|
echo "This will uninstall Vigil from your system."
|
||||||
ACTUAL_USER=${SUDO_USER:-$USER}
|
|
||||||
USER_HOME=$(eval echo ~$ACTUAL_USER)
|
|
||||||
|
|
||||||
# Now check if we are running on a supported system
|
|
||||||
if [[ "$OSTYPE" != "linux-gnu"* ]]; then
|
|
||||||
echo "This script is only supported on Linux" 1>&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Warning and countdown
|
|
||||||
echo "WARNING: This will remove /opt/vigil and any related data."
|
|
||||||
echo "Press Ctrl+C to cancel..."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
for i in 5 4 3 2 1; do
|
|
||||||
echo -n "$i..."
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
echo ""
|
echo ""
|
||||||
|
echo "What would you like to remove?"
|
||||||
|
echo " 1) Everything (code, config, and logs)"
|
||||||
|
echo " 2) Just the code (preserve /etc/vigil and /var/log/vigil)"
|
||||||
|
echo " 3) Cancel"
|
||||||
echo ""
|
echo ""
|
||||||
|
read -p "Enter choice [1-3]: " choice
|
||||||
|
|
||||||
# Final confirmation
|
case $choice in
|
||||||
echo "Continue with uninstallation? (y/n)"
|
1)
|
||||||
read -r answer
|
REMOVE_ALL=true
|
||||||
if [[ "${answer,,}" != "y" ]]; then
|
;;
|
||||||
echo "Uninstallation cancelled."
|
2)
|
||||||
|
REMOVE_ALL=false
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
echo "Uninstall cancelled."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
;;
|
||||||
|
*)
|
||||||
|
echo "Invalid choice. Uninstall cancelled."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
# Now delete the vigil directory
|
# Stop and disable service
|
||||||
echo ""
|
echo ""
|
||||||
echo "Removing Vigil installation..."
|
echo "Stopping vigil service..."
|
||||||
|
systemctl stop vigil 2>/dev/null
|
||||||
|
systemctl disable vigil 2>/dev/null
|
||||||
|
|
||||||
|
# Remove systemd service
|
||||||
|
echo "Removing systemd service..."
|
||||||
|
rm -f /etc/systemd/system/vigil.service
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
# Remove command
|
||||||
|
echo "Removing vigil command..."
|
||||||
|
rm -f /usr/local/bin/vigil
|
||||||
|
|
||||||
|
# Remove code
|
||||||
|
echo "Removing code from /opt/vigil..."
|
||||||
rm -rf /opt/vigil
|
rm -rf /opt/vigil
|
||||||
|
|
||||||
# Now delete the systemd service (if it exists)
|
if [[ "$REMOVE_ALL" == "true" ]]; then
|
||||||
if [[ -f /etc/systemd/system/vigil.service ]]; then
|
echo "Removing configuration from /etc/vigil..."
|
||||||
echo "Removing Vigil systemd service..."
|
rm -rf /etc/vigil
|
||||||
systemctl stop vigil.service 2>/dev/null
|
echo "Removing logs from /var/log/vigil..."
|
||||||
systemctl disable vigil.service 2>/dev/null
|
rm -rf /var/log/vigil
|
||||||
rm /etc/systemd/system/vigil.service
|
echo ""
|
||||||
systemctl daemon-reload
|
echo "✓ Vigil completely removed from your system"
|
||||||
else
|
else
|
||||||
echo "No systemd service found (skipping)"
|
echo ""
|
||||||
fi
|
echo "✓ Vigil code removed"
|
||||||
|
echo "✓ Configuration preserved in /etc/vigil"
|
||||||
# Now delete the aliases from bashrc
|
echo "✓ Logs preserved in /var/log/vigil"
|
||||||
echo "Removing Vigil aliases from bashrc..."
|
echo ""
|
||||||
if grep -q "alias vigil=" "$USER_HOME/.bashrc" 2>/dev/null; then
|
echo "To reinstall: curl ... | sudo bash"
|
||||||
sed -i '/alias vigil=/d' "$USER_HOME/.bashrc"
|
|
||||||
echo "Aliases removed from $USER_HOME/.bashrc"
|
|
||||||
else
|
|
||||||
echo "No aliases found in bashrc"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Dependencies not uninstalled. Please uninstall them manually if needed: python3, git, pip"
|
|
||||||
echo "Vigil has been uninstalled successfully."
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user