mirror of
https://github.com/fr33n0w/frup.git
synced 2025-12-22 09:57:10 +00:00
Major Update to v0.7
This commit is contained in:
179
README.md
179
README.md
@@ -1,57 +1,162 @@
|
||||
# frup
|
||||
FAST RETICULUM NETWORK PACKAGES UPDATER v0.6 by F.
|
||||
# Fast Reticulum Updater (F.R.U.) v0.7
|
||||
|
||||
#frup v0.6 - 12/10/2024 - frup.py
|
||||
--------------------------------------------------------------------
|
||||
A Python tool to check and update Reticulum ecosystem packages.
|
||||
|
||||
#FAST RETICULUM NETWORK PACKAGES UPDATER v0.6 by F.
|
||||
## New Features in v0.7
|
||||
|
||||
SIMPLE PYTHON SCRIPT TO CHECK FOR UPDATED VERSIONS OF RETICULUM NETWORK RELATED PACKAGES.
|
||||
### 🚀 Performance
|
||||
- **Single API call per package** - Caches GitHub version checks
|
||||
- **Timeouts** on all network and subprocess operations
|
||||
- **Efficient version checking** with normalized comparison
|
||||
|
||||
CHECK FOR ONLINE UPDATES, CHECKS FOR LOCAL INSTALLED VERSIONS AND COMPARE THEM TO CONTROL IF YOU ARE UP TO DATE OR ASK TO INSTALL NEW PACKAGES IF MISSING.
|
||||
### 🎨 User Experience
|
||||
- **Colored output** (automatic, with fallback if colorama not installed)
|
||||
- **Progress indicators** (✓ ✗ ⚠ − ℹ)
|
||||
- **Config status display** - Shows whether using default or custom config
|
||||
- **Summary report** showing updated, skipped, and failed packages
|
||||
- **Clear status messages** throughout the process
|
||||
|
||||
--------------------------------------------------------------------
|
||||
### ⚙️ Command Line Arguments
|
||||
- `--auto` / `-a` : Automatically update all packages without prompting
|
||||
- `--check-only` / `-c` : Only check versions without updating
|
||||
- `--quiet` / `-q` : Minimal output (errors and summary only)
|
||||
- `--save-config` : Generate example configuration file
|
||||
- `--version` / `-v` : Show version information
|
||||
- `--help` / `-h` : Show help message
|
||||
|
||||
WHAT'S NEW IN THIS VERSION:
|
||||
### 📁 Configuration Files
|
||||
Supports custom package lists via JSON configuration. Checks for config in:
|
||||
1. `frup_config.json` (current directory)
|
||||
2. `.frup_config.json` (hidden file in current directory)
|
||||
3. `~/.frup_config.json` (user home directory)
|
||||
|
||||
** CORRECTED LINUX ERROR BUG ON SCRIPT ENDING, DUE TO KEYBOARD MODULE REQUESTING SUDO, NOW REMOVED.
|
||||
When starting, the script shows whether it's using default or custom configuration.
|
||||
|
||||
### 🛡️ Error Handling
|
||||
- Network timeouts (10 seconds for API calls)
|
||||
- Process timeouts (120 seconds for pip operations)
|
||||
- Graceful failure handling with clear error messages
|
||||
- Keyboard interrupt handling (Ctrl+C)
|
||||
|
||||
--------------------------------------------------------------------
|
||||
CHECKING OFFICIAL GITHUBS REPOS FOR THESE RETICULUM NETWORK SOFTWARES PACKAGE UPDATES:
|
||||
## Installation
|
||||
|
||||
* RNS
|
||||
* LXMF
|
||||
* NOMADNET
|
||||
* RETICULUM MESHCHAT
|
||||
* SIDEBAND
|
||||
* RNODE STOCK
|
||||
* RNODE CE
|
||||
* RNODE TN
|
||||
### Requirements
|
||||
- Python 3.6+
|
||||
- pip
|
||||
- requests library (`pip install requests`)
|
||||
- colorama library (optional, for colored output: `pip install colorama`)
|
||||
|
||||
--------------------------------------------------------------------
|
||||
### Setup
|
||||
```bash
|
||||
# Make executable (optional)
|
||||
chmod +x frup.py
|
||||
|
||||
#INSTALLATION:
|
||||
# Install optional colorama for colored output
|
||||
pip install colorama
|
||||
```
|
||||
|
||||
1) DOWNLOAD FRUP.PY FROM THE LAST RELEASE ON THIS REPOSITORY
|
||||
## Usage Examples
|
||||
|
||||
2) INSTALL REQUIREMENST:
|
||||
pip install requests subprocess
|
||||
### Interactive Mode (default)
|
||||
```bash
|
||||
python3 frup.py
|
||||
```
|
||||
Shows all information and prompts for each update.
|
||||
|
||||
3) RUN THE SCRIPT:
|
||||
python frup.py
|
||||
(or python3 frup.py, based on your python version)
|
||||
### Auto-Update All Packages
|
||||
```bash
|
||||
python3 frup.py --auto
|
||||
```
|
||||
Automatically updates all out-of-date packages without prompting.
|
||||
|
||||
--------------------------------------------------------------------
|
||||
### Check Only (no updates)
|
||||
```bash
|
||||
python3 frup.py --check-only
|
||||
```
|
||||
Shows version comparison without performing any updates.
|
||||
|
||||
NOTE:
|
||||
### Quiet Auto-Update
|
||||
```bash
|
||||
python3 frup.py --quiet --auto
|
||||
```
|
||||
Silently updates all packages, showing only the summary.
|
||||
|
||||
AUTO UPDATES ONLY WORKS FOR: RNS, LXMF AND NOMADNET PACKAGES.
|
||||
### Generate Config File
|
||||
```bash
|
||||
python3 frup.py --save-config
|
||||
```
|
||||
Creates `frup_config_example.json` that you can customize and rename to `frup_config.json`.
|
||||
|
||||
FOR MESHCHAT, SIDEBAND , RNODE AND RNODE CE & TN,
|
||||
YOU GET ONLY THE NEW VERSION CHECKUP,
|
||||
THEN YOU NEED TO INSTALL THEM MANUALLY!
|
||||
## Configuration File Format
|
||||
|
||||
--------------------------------------------------------------------
|
||||
FRUP.PY V0.5 SCREENSHOT (v0.6 screenshot coming soon):
|
||||

|
||||
Create a `frup_config.json` file to customize which packages to check:
|
||||
|
||||
```json
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"name": "RNS",
|
||||
"url": "https://github.com/markqvist/Reticulum"
|
||||
},
|
||||
{
|
||||
"name": "CustomPackage",
|
||||
"url": "https://github.com/user/repo",
|
||||
"manual_install": true,
|
||||
"skip_local_check": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Package Options
|
||||
- `name`: Package name (used for pip)
|
||||
- `url`: GitHub repository URL
|
||||
- `manual_install`: If true, only shows available version
|
||||
- `skip_local_check`: Skip checking local installation
|
||||
- `skip_version_comparison`: Skip version comparison
|
||||
- `online_only`: Only check GitHub version
|
||||
|
||||
## Output Symbols
|
||||
|
||||
- ✓ Success / Up to date
|
||||
- ✗ Error / Failed
|
||||
- ⚠ Warning / Timeout
|
||||
- − Not installed / Skipped
|
||||
- ℹ Information
|
||||
|
||||
## Status Messages
|
||||
|
||||
When starting, the script displays which configuration it's using:
|
||||
- `Using default configuration` - No config file found, using built-in packages
|
||||
- `Using custom config: [path]` - Found and loaded custom configuration
|
||||
|
||||
## Version History
|
||||
|
||||
- **v0.7** - Major rewrite with CLI args, config files, colored output, better error handling
|
||||
- **v0.6** - Fixed Linux keyboard permission issue
|
||||
- **v0.5** - Initial version
|
||||
|
||||
## Author
|
||||
|
||||
Created by F
|
||||
|
||||
## License
|
||||
|
||||
Open source - feel free to modify and distribute
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No colored output?
|
||||
Install colorama: `pip install colorama`
|
||||
|
||||
### Permission errors?
|
||||
No longer requires sudo! The keyboard dependency was removed in v0.6+
|
||||
|
||||
### Network timeouts?
|
||||
The tool has a 10-second timeout for GitHub API calls. Check your internet connection.
|
||||
|
||||
### Custom packages not updating?
|
||||
Make sure the package name matches exactly what's used in pip.
|
||||
|
||||
### Want to reset to default packages?
|
||||
Simply delete or rename your `frup_config.json` file.
|
||||
|
||||
57
README0.6.md
Normal file
57
README0.6.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# frup
|
||||
FAST RETICULUM NETWORK PACKAGES UPDATER v0.6 by F.
|
||||
|
||||
#frup v0.6 - 12/10/2024 - frup.py
|
||||
--------------------------------------------------------------------
|
||||
|
||||
#FAST RETICULUM NETWORK PACKAGES UPDATER v0.6 by F.
|
||||
|
||||
SIMPLE PYTHON SCRIPT TO CHECK FOR UPDATED VERSIONS OF RETICULUM NETWORK RELATED PACKAGES.
|
||||
|
||||
CHECK FOR ONLINE UPDATES, CHECKS FOR LOCAL INSTALLED VERSIONS AND COMPARE THEM TO CONTROL IF YOU ARE UP TO DATE OR ASK TO INSTALL NEW PACKAGES IF MISSING.
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
WHAT'S NEW IN THIS VERSION:
|
||||
|
||||
** CORRECTED LINUX ERROR BUG ON SCRIPT ENDING, DUE TO KEYBOARD MODULE REQUESTING SUDO, NOW REMOVED.
|
||||
|
||||
|
||||
--------------------------------------------------------------------
|
||||
CHECKING OFFICIAL GITHUBS REPOS FOR THESE RETICULUM NETWORK SOFTWARES PACKAGE UPDATES:
|
||||
|
||||
* RNS
|
||||
* LXMF
|
||||
* NOMADNET
|
||||
* RETICULUM MESHCHAT
|
||||
* SIDEBAND
|
||||
* RNODE STOCK
|
||||
* RNODE CE
|
||||
* RNODE TN
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
#INSTALLATION:
|
||||
|
||||
1) DOWNLOAD FRUP.PY FROM THE LAST RELEASE ON THIS REPOSITORY
|
||||
|
||||
2) INSTALL REQUIREMENST:
|
||||
pip install requests subprocess
|
||||
|
||||
3) RUN THE SCRIPT:
|
||||
python frup.py
|
||||
(or python3 frup.py, based on your python version)
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
NOTE:
|
||||
|
||||
AUTO UPDATES ONLY WORKS FOR: RNS, LXMF AND NOMADNET PACKAGES.
|
||||
|
||||
FOR MESHCHAT, SIDEBAND , RNODE AND RNODE CE & TN,
|
||||
YOU GET ONLY THE NEW VERSION CHECKUP,
|
||||
THEN YOU NEED TO INSTALL THEM MANUALLY!
|
||||
|
||||
--------------------------------------------------------------------
|
||||
FRUP.PY V0.5 SCREENSHOT (v0.6 screenshot coming soon):
|
||||

|
||||
492
frup.py
492
frup.py
@@ -1,92 +1,400 @@
|
||||
import requests
|
||||
import subprocess
|
||||
|
||||
# Print the title
|
||||
print()
|
||||
print("==============================================")
|
||||
print(" Fast Reticulum Updater v0.6 by F")
|
||||
print("==============================================")
|
||||
|
||||
# List of packages to check
|
||||
packages = [
|
||||
{'name': 'RNS', 'url': 'https://github.com/markqvist/Reticulum'},
|
||||
{'name': 'LXMF', 'url': 'https://github.com/markqvist/lxmf'},
|
||||
{'name': 'NomadNet', 'url': 'https://github.com/markqvist/nomadnet'},
|
||||
{'name': 'MeshChat', 'url': 'https://github.com/liamcottle/reticulum-meshchat', 'manual_install': True, 'skip_local_check': True, 'skip_version_comparison': True, 'online_only': True},
|
||||
{'name': 'Sideband', 'url': 'https://github.com/markqvist/Sideband', 'manual_install': True, 'skip_local_check': True, 'skip_version_comparison': True, 'online_only': True},
|
||||
{'name': 'RNode Stock', 'url': 'https://github.com/markqvist/RNode_Firmware', 'manual_install': True, 'skip_local_check': True, 'skip_version_comparison': True, 'online_only': True},
|
||||
{'name': 'RNode CE', 'url': 'https://github.com/liberatedsystems/RNode_Firmware_CE', 'manual_install': True, 'skip_local_check': True, 'skip_version_comparison': True, 'online_only': True},
|
||||
{'name': 'RNode Micro TN', 'url': 'https://github.com/attermann/microReticulum_Firmware', 'manual_install': True, 'skip_local_check': True, 'skip_version_comparison': True, 'online_only': True}
|
||||
]
|
||||
|
||||
# Online versions
|
||||
print("\n**Latest GitHub Versions:**")
|
||||
for package in packages:
|
||||
github_repo = package['url'].split('/')[-2] + '/' + package['url'].split('/')[-1]
|
||||
try:
|
||||
response = requests.get(f"https://api.github.com/repos/{github_repo}/releases/latest")
|
||||
response.raise_for_status() # Raise an exception for 4xx or 5xx status codes
|
||||
latest_version = response.json()["tag_name"]
|
||||
print(f"* {package['name']}: {latest_version}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"* {package['name']}: Failed to retrieve latest version from GitHub ({e})")
|
||||
|
||||
# Local versions
|
||||
print("\n**Local Installed Versions:**")
|
||||
for package in packages:
|
||||
if not ('skip_local_check' in package and package['skip_local_check']) and not ('online_only' in package and package['online_only']):
|
||||
try:
|
||||
pip_version = subprocess.check_output(["pip", "show", package['name']]).decode("utf-8")
|
||||
pip_version = [line.split(":")[1].strip() for line in pip_version.splitlines() if "Version:" in line][0]
|
||||
print(f"* {package['name']}: {pip_version}")
|
||||
except subprocess.CalledProcessError:
|
||||
print(f"* {package['name']}: Not installed via pip")
|
||||
|
||||
# Compare versions and ask to install/update
|
||||
print("\n**Version Comparison for Update Installation:**")
|
||||
for package in packages:
|
||||
if not ('skip_local_check' in package and package['skip_local_check']) and not ('skip_version_comparison' in package and package['skip_version_comparison']) and not ('online_only' in package and package['online_only']):
|
||||
github_repo = package['url'].split('/')[-2] + '/' + package['url'].split('/')[-1]
|
||||
try:
|
||||
response = requests.get(f"https://api.github.com/repos/{github_repo}/releases/latest")
|
||||
response.raise_for_status() # Raise an exception for 4xx or 5xx status codes
|
||||
latest_version = response.json()["tag_name"]
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Error fetching latest version: {e}")
|
||||
latest_version = None
|
||||
|
||||
try:
|
||||
pip_version = subprocess.check_output(["pip", "show", package['name']]).decode("utf-8")
|
||||
pip_version = [line.split(":")[1].strip() for line in pip_version.splitlines() if "Version:" in line][0]
|
||||
except subprocess.CalledProcessError:
|
||||
pip_version = None
|
||||
|
||||
print(f"* {package['name']}:")
|
||||
if pip_version == latest_version:
|
||||
print(f" Up to date!")
|
||||
else:
|
||||
if 'manual_install' in package and package['manual_install']:
|
||||
print(f" New version ({latest_version}) available. Please install it manually.")
|
||||
else:
|
||||
print(f" New version ({latest_version}) available.")
|
||||
if pip_version is None:
|
||||
print(f" {package['name']} is not installed. Do you want to install it? (y/n)")
|
||||
else:
|
||||
print(f" Do you want to update {package['name']} to the latest version? (y/n)")
|
||||
response = input()
|
||||
if response.lower() == 'y':
|
||||
print(f" Installing/Updating {package['name']}...")
|
||||
subprocess.run(["pip", "install", "--upgrade", package['name']])
|
||||
print(f" {package['name']} installed/updated successfully!")
|
||||
else:
|
||||
print(f" Skipping update of {package['name']}")
|
||||
|
||||
# Final message
|
||||
print("\n=====================================================")
|
||||
print(" Update process complete! F.R.U. v0.6 END")
|
||||
print("======================================================")
|
||||
|
||||
# Wait for a key press to exit
|
||||
print()
|
||||
print("------------- Press ENTER to exit... ---------------")
|
||||
input()
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Fast Reticulum Updater v0.7
|
||||
Author: F
|
||||
Improvements: Efficiency, error handling, CLI arguments, colored output, summary
|
||||
"""
|
||||
|
||||
import requests
|
||||
import subprocess
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
# Try to import colorama for colored output
|
||||
try:
|
||||
from colorama import init, Fore, Style
|
||||
init()
|
||||
GREEN = Fore.GREEN
|
||||
RED = Fore.RED
|
||||
YELLOW = Fore.YELLOW
|
||||
CYAN = Fore.CYAN
|
||||
RESET = Style.RESET_ALL
|
||||
BRIGHT = Style.BRIGHT
|
||||
except ImportError:
|
||||
# Fallback if colorama not installed
|
||||
GREEN = RED = YELLOW = CYAN = RESET = BRIGHT = ""
|
||||
|
||||
class ReticulumUpdater:
|
||||
def __init__(self, auto_update=False, quiet=False, check_only=False):
|
||||
self.auto_update = auto_update
|
||||
self.quiet = quiet
|
||||
self.check_only = check_only
|
||||
self.github_versions = {}
|
||||
self.local_versions = {}
|
||||
self.updated = []
|
||||
self.skipped = []
|
||||
self.failed = []
|
||||
self.already_updated = []
|
||||
self.using_custom_config = False
|
||||
self.config_path = None
|
||||
|
||||
# Default packages - can be overridden by config file
|
||||
self.packages = [
|
||||
{'name': 'RNS', 'url': 'https://github.com/markqvist/Reticulum'},
|
||||
{'name': 'LXMF', 'url': 'https://github.com/markqvist/lxmf'},
|
||||
{'name': 'NomadNet', 'url': 'https://github.com/markqvist/nomadnet'},
|
||||
{'name': 'MeshChat', 'url': 'https://github.com/liamcottle/reticulum-meshchat',
|
||||
'manual_install': True, 'skip_local_check': True, 'skip_version_comparison': True, 'online_only': True},
|
||||
{'name': 'Sideband', 'url': 'https://github.com/markqvist/Sideband',
|
||||
'manual_install': True, 'skip_local_check': True, 'skip_version_comparison': True, 'online_only': True},
|
||||
{'name': 'RNode Stock', 'url': 'https://github.com/markqvist/RNode_Firmware',
|
||||
'manual_install': True, 'skip_local_check': True, 'skip_version_comparison': True, 'online_only': True},
|
||||
{'name': 'RNode CE', 'url': 'https://github.com/liberatedsystems/RNode_Firmware_CE',
|
||||
'manual_install': True, 'skip_local_check': True, 'skip_version_comparison': True, 'online_only': True},
|
||||
{'name': 'RNode Micro TN', 'url': 'https://github.com/attermann/microReticulum_Firmware',
|
||||
'manual_install': True, 'skip_local_check': True, 'skip_version_comparison': True, 'online_only': True}
|
||||
]
|
||||
|
||||
# Load custom config if available
|
||||
self.load_config()
|
||||
|
||||
def load_config(self):
|
||||
"""Load configuration from file if it exists"""
|
||||
config_files = ['frup_config.json', '.frup_config.json', '~/.frup_config.json']
|
||||
for config_file in config_files:
|
||||
config_path = os.path.expanduser(config_file)
|
||||
if os.path.exists(config_path):
|
||||
try:
|
||||
with open(config_path, 'r') as f:
|
||||
config = json.load(f)
|
||||
if 'packages' in config:
|
||||
self.packages = config['packages']
|
||||
self.using_custom_config = True
|
||||
self.config_path = config_path
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"{RED}Error loading config from {config_path}: {e}{RESET}")
|
||||
print(f"{YELLOW}Using default configuration instead{RESET}")
|
||||
|
||||
def save_example_config(self):
|
||||
"""Save an example configuration file"""
|
||||
example_config = {
|
||||
"packages": self.packages,
|
||||
"comment": "Customize this file to add/remove packages to check"
|
||||
}
|
||||
with open('frup_config_example.json', 'w') as f:
|
||||
json.dump(example_config, f, indent=2)
|
||||
print(f"{GREEN}Example config saved to: frup_config_example.json{RESET}")
|
||||
print(f"{CYAN}Rename to 'frup_config.json' to use it{RESET}")
|
||||
|
||||
def normalize_version(self, version: Optional[str]) -> Optional[str]:
|
||||
"""Remove common prefixes from version strings for comparison"""
|
||||
if version:
|
||||
return version.lstrip('v').lstrip('V').strip()
|
||||
return version
|
||||
|
||||
def print_header(self):
|
||||
"""Print the application header"""
|
||||
if not self.quiet:
|
||||
print()
|
||||
print(f"{BRIGHT}=============================================={RESET}")
|
||||
print(f"{BRIGHT} Fast Reticulum Updater v0.7 by F{RESET}")
|
||||
print(f"{BRIGHT}=============================================={RESET}")
|
||||
|
||||
# Show config status
|
||||
if self.using_custom_config:
|
||||
print(f"{CYAN}Using custom config: {self.config_path}{RESET}")
|
||||
else:
|
||||
print(f"{CYAN}Using default configuration{RESET}")
|
||||
|
||||
def fetch_github_versions(self):
|
||||
"""Fetch all GitHub versions in one pass"""
|
||||
if not self.quiet:
|
||||
print(f"\n{BRIGHT}** Fetching Latest GitHub Versions **{RESET}")
|
||||
|
||||
for package in self.packages:
|
||||
repo_parts = package['url'].split('/')
|
||||
repo = f"{repo_parts[-2]}/{repo_parts[-1]}"
|
||||
|
||||
try:
|
||||
response = requests.get(
|
||||
f"https://api.github.com/repos/{repo}/releases/latest",
|
||||
timeout=10,
|
||||
headers={'Accept': 'application/vnd.github.v3+json'}
|
||||
)
|
||||
response.raise_for_status()
|
||||
version = response.json().get("tag_name", "Unknown")
|
||||
self.github_versions[package['name']] = version
|
||||
|
||||
if not self.quiet:
|
||||
print(f" {GREEN}✓{RESET} {package['name']}: {CYAN}{version}{RESET}")
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
self.github_versions[package['name']] = None
|
||||
if not self.quiet:
|
||||
print(f" {YELLOW}⚠{RESET} {package['name']}: Timeout")
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
self.github_versions[package['name']] = None
|
||||
if not self.quiet:
|
||||
print(f" {RED}✗{RESET} {package['name']}: Failed to fetch")
|
||||
|
||||
def check_local_versions(self):
|
||||
"""Check locally installed versions"""
|
||||
if not self.quiet:
|
||||
print(f"\n{BRIGHT}** Local Installed Versions **{RESET}")
|
||||
|
||||
for package in self.packages:
|
||||
# Skip packages marked as online_only or skip_local_check
|
||||
if package.get('skip_local_check') or package.get('online_only'):
|
||||
self.local_versions[package['name']] = None
|
||||
if not self.quiet and not package.get('online_only'):
|
||||
print(f" {YELLOW}−{RESET} {package['name']}: Skipped")
|
||||
continue
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["pip", "show", package['name']],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
for line in result.stdout.splitlines():
|
||||
if "Version:" in line:
|
||||
version = line.split(":")[1].strip()
|
||||
self.local_versions[package['name']] = version
|
||||
if not self.quiet:
|
||||
print(f" {GREEN}✓{RESET} {package['name']}: {CYAN}{version}{RESET}")
|
||||
break
|
||||
else:
|
||||
self.local_versions[package['name']] = None
|
||||
if not self.quiet:
|
||||
print(f" {YELLOW}−{RESET} {package['name']}: Not installed")
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
self.local_versions[package['name']] = None
|
||||
if not self.quiet:
|
||||
print(f" {YELLOW}⚠{RESET} {package['name']}: Check timeout")
|
||||
|
||||
except Exception as e:
|
||||
self.local_versions[package['name']] = None
|
||||
if not self.quiet:
|
||||
print(f" {RED}✗{RESET} {package['name']}: Error checking")
|
||||
|
||||
def compare_and_update(self):
|
||||
"""Compare versions and optionally update packages"""
|
||||
if self.check_only:
|
||||
print(f"\n{BRIGHT}** Version Comparison (Check Only Mode) **{RESET}")
|
||||
else:
|
||||
print(f"\n{BRIGHT}** Version Comparison & Update **{RESET}")
|
||||
|
||||
for package in self.packages:
|
||||
# Skip certain packages
|
||||
if package.get('skip_version_comparison') or package.get('online_only'):
|
||||
continue
|
||||
|
||||
name = package['name']
|
||||
github_version = self.github_versions.get(name)
|
||||
local_version = self.local_versions.get(name)
|
||||
|
||||
# Normalize versions for comparison
|
||||
norm_github = self.normalize_version(github_version)
|
||||
norm_local = self.normalize_version(local_version)
|
||||
|
||||
print(f"\n{BRIGHT}{name}:{RESET}")
|
||||
|
||||
# Check if versions could be retrieved
|
||||
if github_version is None:
|
||||
print(f" {RED}Cannot compare - GitHub version unavailable{RESET}")
|
||||
self.failed.append(name)
|
||||
continue
|
||||
|
||||
# Compare versions
|
||||
if local_version is None:
|
||||
print(f" {YELLOW}Not installed{RESET} (Available: {CYAN}{github_version}{RESET})")
|
||||
action = "install"
|
||||
should_update = True
|
||||
elif norm_local == norm_github:
|
||||
print(f" {GREEN}✓ Up to date!{RESET} ({CYAN}{local_version}{RESET})")
|
||||
self.already_updated.append(name)
|
||||
continue
|
||||
else:
|
||||
print(f" {YELLOW}Update available:{RESET} {local_version} → {CYAN}{github_version}{RESET}")
|
||||
action = "update"
|
||||
should_update = True
|
||||
|
||||
# Handle manual install packages
|
||||
if package.get('manual_install'):
|
||||
print(f" {CYAN}ℹ Please {action} manually from: {package['url']}{RESET}")
|
||||
self.skipped.append(name)
|
||||
continue
|
||||
|
||||
# Skip if check-only mode
|
||||
if self.check_only:
|
||||
continue
|
||||
|
||||
# Handle updates
|
||||
if should_update:
|
||||
if self.auto_update:
|
||||
response = 'y'
|
||||
print(f" {CYAN}Auto-{action}ing...{RESET}")
|
||||
else:
|
||||
response = input(f" Do you want to {action} {name}? (y/n): ").strip().lower()
|
||||
|
||||
if response == 'y':
|
||||
print(f" {CYAN}{action.capitalize()}ing {name}...{RESET}")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["pip", "install", "--upgrade", name],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print(f" {GREEN}✓ {name} {action}d successfully!{RESET}")
|
||||
self.updated.append(name)
|
||||
else:
|
||||
print(f" {RED}✗ Failed to {action} {name}{RESET}")
|
||||
if result.stderr:
|
||||
print(f" Error: {result.stderr[:200]}")
|
||||
self.failed.append(name)
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
print(f" {RED}✗ {action.capitalize()} timeout for {name}{RESET}")
|
||||
self.failed.append(name)
|
||||
|
||||
except Exception as e:
|
||||
print(f" {RED}✗ Error {action}ing {name}: {str(e)}{RESET}")
|
||||
self.failed.append(name)
|
||||
else:
|
||||
print(f" {YELLOW}Skipped {name}{RESET}")
|
||||
self.skipped.append(name)
|
||||
|
||||
def print_summary(self):
|
||||
"""Print a summary of actions taken"""
|
||||
print(f"\n{BRIGHT}=============================================={RESET}")
|
||||
print(f"{BRIGHT} SUMMARY{RESET}")
|
||||
print(f"{BRIGHT}=============================================={RESET}")
|
||||
|
||||
if self.already_updated:
|
||||
print(f"{GREEN}✓ Up to date:{RESET} {', '.join(self.already_updated)}")
|
||||
|
||||
if self.updated:
|
||||
print(f"{GREEN}✓ Updated:{RESET} {', '.join(self.updated)}")
|
||||
|
||||
if self.skipped:
|
||||
print(f"{YELLOW}− Skipped:{RESET} {', '.join(self.skipped)}")
|
||||
|
||||
if self.failed:
|
||||
print(f"{RED}✗ Failed:{RESET} {', '.join(self.failed)}")
|
||||
|
||||
if not any([self.updated, self.skipped, self.failed, self.already_updated]):
|
||||
print(f"{CYAN}No actions taken.{RESET}")
|
||||
|
||||
# Final status
|
||||
print(f"\n{BRIGHT}=============================================={RESET}")
|
||||
if self.check_only:
|
||||
print(f"{BRIGHT} Check Complete! F.R.U. v0.7 END{RESET}")
|
||||
else:
|
||||
print(f"{BRIGHT} Update Process Complete! F.R.U. v0.7 END{RESET}")
|
||||
print(f"{BRIGHT}=============================================={RESET}")
|
||||
|
||||
def run(self):
|
||||
"""Main execution flow"""
|
||||
self.print_header()
|
||||
|
||||
# Fetch all versions
|
||||
self.fetch_github_versions()
|
||||
self.check_local_versions()
|
||||
|
||||
# Compare and potentially update
|
||||
self.compare_and_update()
|
||||
|
||||
# Show summary
|
||||
self.print_summary()
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point with argument parsing"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Fast Reticulum Updater v0.7 - Update Reticulum ecosystem packages',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
frup.py # Interactive mode
|
||||
frup.py --auto # Auto-update all packages
|
||||
frup.py --check-only # Only check versions without updating
|
||||
frup.py --quiet --auto # Silent auto-update
|
||||
frup.py --save-config # Save example config file
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--auto', '-a',
|
||||
action='store_true',
|
||||
help='Automatically update all packages without prompting'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--check-only', '-c',
|
||||
action='store_true',
|
||||
help='Only check versions without updating'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--quiet', '-q',
|
||||
action='store_true',
|
||||
help='Minimal output (errors and summary only)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--save-config',
|
||||
action='store_true',
|
||||
help='Save an example configuration file and exit'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--version', '-v',
|
||||
action='version',
|
||||
version='Fast Reticulum Updater v0.7'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Create updater instance
|
||||
updater = ReticulumUpdater(
|
||||
auto_update=args.auto,
|
||||
quiet=args.quiet,
|
||||
check_only=args.check_only
|
||||
)
|
||||
|
||||
# Handle config save
|
||||
if args.save_config:
|
||||
updater.save_example_config()
|
||||
sys.exit(0)
|
||||
|
||||
try:
|
||||
# Run the updater
|
||||
updater.run()
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n{YELLOW}Interrupted by user{RESET}")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n{RED}Unexpected error: {e}{RESET}")
|
||||
sys.exit(1)
|
||||
|
||||
# Wait for user input before exiting (unless in quiet mode)
|
||||
if not args.quiet:
|
||||
print()
|
||||
print("------------- Press ENTER to exit... ---------------")
|
||||
input()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
BIN
frup0.7.release.zip
Normal file
BIN
frup0.7.release.zip
Normal file
Binary file not shown.
65
frup_config_example.json
Normal file
65
frup_config_example.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"comment": "Fast Reticulum Updater Configuration - Customize packages to check/update",
|
||||
"packages": [
|
||||
{
|
||||
"name": "RNS",
|
||||
"url": "https://github.com/markqvist/Reticulum"
|
||||
},
|
||||
{
|
||||
"name": "LXMF",
|
||||
"url": "https://github.com/markqvist/lxmf"
|
||||
},
|
||||
{
|
||||
"name": "NomadNet",
|
||||
"url": "https://github.com/markqvist/nomadnet"
|
||||
},
|
||||
{
|
||||
"name": "MeshChat",
|
||||
"url": "https://github.com/liamcottle/reticulum-meshchat",
|
||||
"manual_install": true,
|
||||
"skip_local_check": true,
|
||||
"skip_version_comparison": true,
|
||||
"online_only": true
|
||||
},
|
||||
{
|
||||
"name": "Sideband",
|
||||
"url": "https://github.com/markqvist/Sideband",
|
||||
"manual_install": true,
|
||||
"skip_local_check": true,
|
||||
"skip_version_comparison": true,
|
||||
"online_only": true
|
||||
},
|
||||
{
|
||||
"name": "RNode Stock",
|
||||
"url": "https://github.com/markqvist/RNode_Firmware",
|
||||
"manual_install": true,
|
||||
"skip_local_check": true,
|
||||
"skip_version_comparison": true,
|
||||
"online_only": true
|
||||
},
|
||||
{
|
||||
"name": "RNode CE",
|
||||
"url": "https://github.com/liberatedsystems/RNode_Firmware_CE",
|
||||
"manual_install": true,
|
||||
"skip_local_check": true,
|
||||
"skip_version_comparison": true,
|
||||
"online_only": true
|
||||
},
|
||||
{
|
||||
"name": "RNode Micro TN",
|
||||
"url": "https://github.com/attermann/microReticulum_Firmware",
|
||||
"manual_install": true,
|
||||
"skip_local_check": true,
|
||||
"skip_version_comparison": true,
|
||||
"online_only": true
|
||||
}
|
||||
],
|
||||
"additional_packages_example": [
|
||||
{
|
||||
"name": "some-other-package",
|
||||
"url": "https://github.com/user/repo",
|
||||
"manual_install": false,
|
||||
"comment": "Add your own packages here"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user