persistent config file via config.template.json and migrate_config.sh

This commit is contained in:
Chuck
2025-09-15 10:27:16 -04:00
parent 4b36937a55
commit fcbc67464d
73 changed files with 429 additions and 1330 deletions

2
.gitignore vendored
View File

@@ -5,6 +5,8 @@ __pycache__/
# Secrets
config/config_secrets.json
config/config.json
config/config.json.backup
credentials.json
token.pickle

View File

@@ -260,16 +260,38 @@ sudo reboot
## Configuration
1.Edit `config/config.json` with your preferences via `sudo nano config/config.json`
### Initial Setup
###API Keys
The system uses a template-based configuration approach to avoid Git conflicts during updates:
1. **First-time setup**: Copy the template to create your config:
```bash
cp config/config.template.json config/config.json
```
2. **Edit your configuration**:
```bash
sudo nano config/config.json
```
or edit via web interface at http://ledpi:5001
### API Keys and Secrets
For sensitive settings like API keys:
Copy the template: `cp config/config_secrets.template.json config/config_secrets.json`
Edit `config/config_secrets.json` with your API keys via `sudo nano config/config_secrets.json`
Ctrl + X to exit, Y to overwrite, Enter to Confirm
1. Copy the secrets template: `cp config/config_secrets.template.json config/config_secrets.json`
2. Edit `config/config_secrets.json` with your API keys via `sudo nano config/config_secrets.json`
3. Ctrl + X to exit, Y to overwrite, Enter to Confirm
Everything is configured via `config/config.json` and `config/config_secrets.json`.
### Automatic Configuration Migration
The system automatically handles configuration updates:
- **New installations**: Creates `config.json` from the template automatically
- **Existing installations**: Automatically adds new configuration options with default values when the system starts
- **Backup protection**: Creates a backup of your current config before applying updates
- **No conflicts**: Your custom settings are preserved while new options are added
Everything is configured via `config/config.json` and `config/config_secrets.json`. The `config.json` file is not tracked by Git to prevent conflicts during updates.

View File

@@ -5,10 +5,10 @@
"start_time": "07:00",
"end_time": "23:00"
},
"timezone": "America/New_York",
"timezone": "America/Chicago",
"location": {
"city": "Tampa",
"state": "Florida",
"city": "Dallas",
"state": "Texas",
"country": "US"
},
"display": {
@@ -39,7 +39,6 @@
"daily_forecast": 30,
"stock_news": 20,
"odds_ticker": 60,
"leaderboard": 60,
"nhl_live": 30,
"nhl_recent": 30,
"nhl_upcoming": 30,
@@ -82,10 +81,10 @@
"update_interval": 1
},
"weather": {
"enabled": true,
"enabled": false,
"update_interval": 1800,
"units": "imperial",
"display_format": "{temp}\u00b0F\n{condition}"
"display_format": "{temp}°F\n{condition}"
},
"stocks": {
"enabled": false,
@@ -130,12 +129,11 @@
"duration_buffer": 0.1
},
"odds_ticker": {
"enabled": false,
"enabled": true,
"show_favorite_teams_only": true,
"games_per_favorite_team": 1,
"max_games_per_league": 5,
"show_odds_only": false,
"fetch_odds": true,
"sort_order": "soonest",
"enabled_leagues": [
"nfl",
@@ -152,46 +150,6 @@
"dynamic_duration": true,
"min_duration": 30,
"max_duration": 300,
"duration_buffer": 0.05
},
"leaderboard": {
"enabled": false,
"enabled_sports": {
"nfl": {
"enabled": true,
"top_teams": 10
},
"nba": {
"enabled": false,
"top_teams": 10
},
"mlb": {
"enabled": false,
"top_teams": 10
},
"ncaa_fb": {
"enabled": true,
"top_teams": 25,
"show_ranking": true
},
"nhl": {
"enabled": false,
"top_teams": 10
},
"ncaam_basketball": {
"enabled": false,
"top_teams": 25
}
},
"update_interval": 3600,
"scroll_speed": 1,
"scroll_delay": 0.01,
"display_duration": 60,
"loop": false,
"request_timeout": 30,
"dynamic_duration": true,
"min_duration": 45,
"max_duration": 600,
"duration_buffer": 0.1
},
"calendar": {
@@ -264,6 +222,8 @@
"live_update_interval": 30,
"live_odds_update_interval": 3600,
"odds_update_interval": 3600,
"recent_update_interval": 3600,
"upcoming_update_interval": 3600,
"recent_games_to_show": 1,
"upcoming_games_to_show": 1,
"show_favorite_teams_only": true,
@@ -299,10 +259,9 @@
],
"logo_dir": "assets/sports/ncaa_fbs_logos",
"show_records": true,
"show_ranking": true,
"display_modes": {
"ncaa_fb_live": true,
"ncaa_fb_recent": true ,
"ncaa_fb_recent": true,
"ncaa_fb_upcoming": true
}
},
@@ -409,7 +368,7 @@
}
},
"text_display": {
"enabled": true,
"enabled": false,
"text": "Subscribe to ChuckBuilds",
"font_path": "assets/fonts/press-start-2p.ttf",
"font_size": 8,

View File

@@ -150,17 +150,18 @@ echo ""
echo "This script will perform the following steps:"
echo "1. Install system dependencies"
echo "2. Fix cache permissions"
echo "3. Install main LED Matrix service"
echo "4. Install Python project dependencies (requirements.txt)"
echo "5. Build and install rpi-rgb-led-matrix and test import"
echo "6. Install web interface dependencies"
echo "7. Install web interface service"
echo "8. Configure web interface permissions"
echo "9. Configure passwordless sudo access"
echo "10. Set up proper file ownership"
echo "11. Configure sound module to avoid conflicts"
echo "12. Apply performance optimizations"
echo "13. Test the installation"
echo "3. Fix assets directory permissions"
echo "4. Install main LED Matrix service"
echo "5. Install Python project dependencies (requirements.txt)"
echo "6. Build and install rpi-rgb-led-matrix and test import"
echo "7. Install web interface dependencies"
echo "8. Install web interface service"
echo "9. Configure web interface permissions"
echo "10. Configure passwordless sudo access"
echo "11. Set up proper file ownership"
echo "12. Configure sound module to avoid conflicts"
echo "13. Apply performance optimizations"
echo "14. Test the installation"
echo ""
# Ask for confirmation
@@ -217,8 +218,57 @@ else
fi
echo ""
CURRENT_STEP="Fix assets directory permissions"
echo "Step 3: Fixing assets directory permissions..."
echo "--------------------------------------------"
# Run the assets permissions fix
if [ -f "$PROJECT_ROOT_DIR/fix_assets_permissions.sh" ]; then
echo "Running assets permissions fix..."
bash "$PROJECT_ROOT_DIR/fix_assets_permissions.sh"
echo "✓ Assets permissions fixed"
else
echo "⚠ Assets permissions script not found, fixing permissions manually..."
# Set ownership of the entire assets directory to the real user
echo "Setting ownership of assets directory..."
chown -R "$ACTUAL_USER:$ACTUAL_USER" "$PROJECT_ROOT_DIR/assets"
# Set permissions to allow read/write for owner and group, read for others
echo "Setting permissions for assets directory..."
chmod -R 775 "$PROJECT_ROOT_DIR/assets"
# Specifically ensure the sports logos directories are writable
SPORTS_DIRS=(
"sports/ncaa_fbs_logos"
"sports/nfl_logos"
"sports/nba_logos"
"sports/nhl_logos"
"sports/mlb_logos"
"sports/milb_logos"
"sports/soccer_logos"
)
echo "Ensuring sports logo directories are writable..."
for SPORTS_DIR in "${SPORTS_DIRS[@]}"; do
FULL_PATH="$PROJECT_ROOT_DIR/assets/$SPORTS_DIR"
if [ -d "$FULL_PATH" ]; then
chmod 775 "$FULL_PATH"
chown "$ACTUAL_USER:$ACTUAL_USER" "$FULL_PATH"
else
echo "Creating directory: $FULL_PATH"
mkdir -p "$FULL_PATH"
chown "$ACTUAL_USER:$ACTUAL_USER" "$FULL_PATH"
chmod 775 "$FULL_PATH"
fi
done
echo "✓ Assets permissions fixed manually"
fi
echo ""
CURRENT_STEP="Install main LED Matrix service"
echo "Step 3: Installing main LED Matrix service..."
echo "Step 4: Installing main LED Matrix service..."
echo "---------------------------------------------"
# Run the main service installation (idempotent)
@@ -233,14 +283,52 @@ else
fi
echo ""
CURRENT_STEP="Ensure secrets configuration exists"
echo "Step 3.1: Ensuring secrets configuration exists..."
echo "-----------------------------------------------"
CURRENT_STEP="Ensure configuration files exist"
echo "Step 4.1: Ensuring configuration files exist..."
echo "------------------------------------------------"
# Ensure config directory exists
mkdir -p "$PROJECT_ROOT_DIR/config"
chmod 755 "$PROJECT_ROOT_DIR/config" || true
# Create config.json from template if missing
if [ ! -f "$PROJECT_ROOT_DIR/config/config.json" ]; then
if [ -f "$PROJECT_ROOT_DIR/config/config.template.json" ]; then
echo "Creating config/config.json from template..."
cp "$PROJECT_ROOT_DIR/config/config.template.json" "$PROJECT_ROOT_DIR/config/config.json"
chown "$ACTUAL_USER:$ACTUAL_USER" "$PROJECT_ROOT_DIR/config/config.json" || true
chmod 644 "$PROJECT_ROOT_DIR/config/config.json"
echo "✓ Main config file created from template"
else
echo "⚠ Template config/config.template.json not found; creating a minimal config file"
cat > "$PROJECT_ROOT_DIR/config/config.json" <<'EOF'
{
"web_display_autostart": true,
"timezone": "America/Chicago",
"display": {
"hardware": {
"rows": 32,
"cols": 64,
"chain_length": 2,
"parallel": 1,
"brightness": 95,
"hardware_mapping": "adafruit-hat-pwm"
}
},
"clock": {
"enabled": true,
"format": "%I:%M %p"
}
}
EOF
chown "$ACTUAL_USER:$ACTUAL_USER" "$PROJECT_ROOT_DIR/config/config.json" || true
chmod 644 "$PROJECT_ROOT_DIR/config/config.json"
echo "✓ Minimal config file created"
fi
else
echo "✓ Main config file already exists"
fi
# Create config_secrets.json from template if missing
if [ ! -f "$PROJECT_ROOT_DIR/config/config_secrets.json" ]; then
if [ -f "$PROJECT_ROOT_DIR/config/config_secrets.template.json" ]; then
@@ -263,12 +351,12 @@ EOF
echo "✓ Minimal secrets file created"
fi
else
echo "Secrets file already exists; leaving as-is"
echo "Secrets file already exists"
fi
echo ""
CURRENT_STEP="Install project Python dependencies"
echo "Step 4: Installing Python project dependencies..."
echo "Step 5: Installing Python project dependencies..."
echo "-----------------------------------------------"
# Install main project Python dependencies
@@ -283,7 +371,7 @@ echo "✓ Project Python dependencies installed"
echo ""
CURRENT_STEP="Build and install rpi-rgb-led-matrix"
echo "Step 5: Building and installing rpi-rgb-led-matrix..."
echo "Step 6: Building and installing rpi-rgb-led-matrix..."
echo "-----------------------------------------------------"
# If already installed and not forcing rebuild, skip expensive build
@@ -327,7 +415,7 @@ fi
echo ""
CURRENT_STEP="Install web interface dependencies"
echo "Step 6: Installing web interface dependencies..."
echo "Step 7: Installing web interface dependencies..."
echo "------------------------------------------------"
# Install web interface dependencies
@@ -335,9 +423,9 @@ echo "Installing Python dependencies for web interface..."
cd "$PROJECT_ROOT_DIR"
# Try to install dependencies using the smart installer if available
if [ -f "$PROJECT_ROOT_DIR/install_dependencies_apt.py" ]; then
if [ -f "$PROJECT_ROOT_DIR/scripts/install_dependencies_apt.py" ]; then
echo "Using smart dependency installer..."
python3 "$PROJECT_ROOT_DIR/install_dependencies_apt.py"
python3 "$PROJECT_ROOT_DIR/scripts/install_dependencies_apt.py"
else
echo "Using pip to install dependencies..."
if [ -f "$PROJECT_ROOT_DIR/requirements_web_v2.txt" ]; then
@@ -351,7 +439,7 @@ echo "✓ Web interface dependencies installed"
echo ""
CURRENT_STEP="Install web interface service"
echo "Step 7: Installing web interface service..."
echo "Step 8: Installing web interface service..."
echo "-------------------------------------------"
if [ -f "$PROJECT_ROOT_DIR/install_web_service.sh" ]; then
@@ -369,7 +457,7 @@ fi
echo ""
CURRENT_STEP="Harden systemd unit file permissions"
echo "Step 7.1: Setting systemd unit file permissions..."
echo "Step 8.1: Setting systemd unit file permissions..."
echo "-----------------------------------------------"
for unit in "/etc/systemd/system/ledmatrix.service" "/etc/systemd/system/ledmatrix-web.service"; do
if [ -f "$unit" ]; then
@@ -382,7 +470,7 @@ echo "✓ Systemd unit file permissions set"
echo ""
CURRENT_STEP="Configure web interface permissions"
echo "Step 8: Configuring web interface permissions..."
echo "Step 9: Configuring web interface permissions..."
echo "------------------------------------------------"
# Add user to required groups (idempotent)
@@ -404,7 +492,7 @@ echo "✓ User added to required groups"
echo ""
CURRENT_STEP="Configure passwordless sudo access"
echo "Step 9: Configuring passwordless sudo access..."
echo "Step 10: Configuring passwordless sudo access..."
echo "------------------------------------------------"
# Create sudoers configuration for the web interface
@@ -451,7 +539,7 @@ echo "✓ Passwordless sudo access configured"
echo ""
CURRENT_STEP="Set proper file ownership"
echo "Step 10: Setting proper file ownership..."
echo "Step 11: Setting proper file ownership..."
echo "----------------------------------------"
# Set ownership of project files to the user
@@ -475,7 +563,7 @@ echo "✓ File ownership configured"
echo ""
CURRENT_STEP="Normalize project file permissions"
echo "Step 10.1: Normalizing project file and directory permissions..."
echo "Step 11.1: Normalizing project file and directory permissions..."
echo "--------------------------------------------------------------"
# Normalize directory permissions (exclude VCS metadata)
@@ -489,14 +577,14 @@ find "$PROJECT_ROOT_DIR" -path "*/.git*" -prune -o -type f -name "*.sh" -exec ch
# Explicitly ensure common helper scripts are executable (in case paths change)
chmod 755 "$PROJECT_ROOT_DIR/start_display.sh" "$PROJECT_ROOT_DIR/stop_display.sh" 2>/dev/null || true
chmod 755 "$PROJECT_ROOT_DIR/fix_cache_permissions.sh" "$PROJECT_ROOT_DIR/fix_web_permissions.sh" 2>/dev/null || true
chmod 755 "$PROJECT_ROOT_DIR/fix_cache_permissions.sh" "$PROJECT_ROOT_DIR/fix_web_permissions.sh" "$PROJECT_ROOT_DIR/fix_assets_permissions.sh" 2>/dev/null || true
chmod 755 "$PROJECT_ROOT_DIR/install_service.sh" "$PROJECT_ROOT_DIR/install_web_service.sh" 2>/dev/null || true
echo "✓ Project file permissions normalized"
echo ""
CURRENT_STEP="Sound module configuration"
echo "Step 11: Sound module configuration..."
echo "Step 12: Sound module configuration..."
echo "-------------------------------------"
# Remove services that may interfere with LED matrix timing
@@ -539,7 +627,7 @@ echo "✓ Sound module configuration applied"
echo ""
CURRENT_STEP="Apply performance optimizations"
echo "Step 12: Applying performance optimizations..."
echo "Step 13: Applying performance optimizations..."
echo "---------------------------------------------"
# Prefer /boot/firmware on newer Raspberry Pi OS, fall back to /boot on older
@@ -588,7 +676,7 @@ echo "✓ Performance optimizations applied"
echo ""
CURRENT_STEP="Test the installation"
echo "Step 13: Testing the installation..."
echo "Step 14: Testing the installation..."
echo "----------------------------------"
# Test sudo access
@@ -665,7 +753,8 @@ echo "Enable/disable web interface autostart:"
echo " Edit config/config.json and set 'web_display_autostart': true"
echo ""
echo "Configuration files:"
echo " Main config: config/config.json"
echo " Secrets: config/config_secrets.json (create from template if needed)"
echo " Main config: config/config.json (created from template automatically)"
echo " Secrets: config/config_secrets.json (created from template automatically)"
echo " Template: config/config.template.json (reference for new options)"
echo ""
echo "Enjoy your LED Matrix display!"

127
fix_assets_permissions.sh Normal file
View File

@@ -0,0 +1,127 @@
#!/bin/bash
# LEDMatrix Assets Permissions Fix Script
# This script fixes permissions on the assets directory so the application can download and save team logos
echo "Fixing LEDMatrix assets directory permissions..."
# Get the real user (not root when running with sudo)
REAL_USER=${SUDO_USER:-$USER}
# Resolve the home directory of the real user robustly
if command -v getent >/dev/null 2>&1; then
REAL_HOME=$(getent passwd "$REAL_USER" | cut -d: -f6)
else
REAL_HOME=$(eval echo ~"$REAL_USER")
fi
REAL_GROUP=$(id -gn "$REAL_USER")
# Get the project directory
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ASSETS_DIR="$PROJECT_DIR/assets"
echo "Project directory: $PROJECT_DIR"
echo "Assets directory: $ASSETS_DIR"
echo "Real user: $REAL_USER"
echo "Real group: $REAL_GROUP"
# Check if assets directory exists
if [ ! -d "$ASSETS_DIR" ]; then
echo "Error: Assets directory does not exist at $ASSETS_DIR"
exit 1
fi
echo ""
echo "Fixing permissions for assets directory and subdirectories..."
# Set ownership of the entire assets directory to the real user
echo "Setting ownership of assets directory..."
if sudo chown -R "$REAL_USER:$REAL_GROUP" "$ASSETS_DIR"; then
echo "✓ Set assets directory ownership to $REAL_USER:$REAL_GROUP"
else
echo "✗ Failed to set assets directory ownership"
exit 1
fi
# Set permissions to allow read/write for owner and group, read for others
echo "Setting permissions for assets directory..."
if sudo chmod -R 775 "$ASSETS_DIR"; then
echo "✓ Set assets directory permissions to 775"
else
echo "✗ Failed to set assets directory permissions"
exit 1
fi
# Specifically ensure the sports logos directories are writable
SPORTS_DIRS=(
"sports/ncaa_fbs_logos"
"sports/nfl_logos"
"sports/nba_logos"
"sports/nhl_logos"
"sports/mlb_logos"
"sports/milb_logos"
"sports/soccer_logos"
)
echo ""
echo "Ensuring sports logo directories are writable..."
for SPORTS_DIR in "${SPORTS_DIRS[@]}"; do
FULL_PATH="$ASSETS_DIR/$SPORTS_DIR"
echo ""
echo "Checking directory: $FULL_PATH"
if [ -d "$FULL_PATH" ]; then
echo " - Directory exists"
echo " - Current permissions:"
ls -ld "$FULL_PATH"
# Ensure the directory is writable
sudo chmod 775 "$FULL_PATH"
sudo chown "$REAL_USER:$REAL_GROUP" "$FULL_PATH"
echo " - Updated permissions:"
ls -ld "$FULL_PATH"
# Test write access
echo " - Testing write access as $REAL_USER..."
if sudo -u "$REAL_USER" test -w "$FULL_PATH"; then
echo "$FULL_PATH is writable by $REAL_USER"
else
echo "$FULL_PATH is not writable by $REAL_USER"
fi
else
echo " - Directory does not exist, creating it..."
sudo mkdir -p "$FULL_PATH"
sudo chown "$REAL_USER:$REAL_GROUP" "$FULL_PATH"
sudo chmod 775 "$FULL_PATH"
echo " - Created directory with proper permissions"
fi
done
echo ""
echo "Testing write access to ncaa_fbs_logos directory specifically..."
NCAA_DIR="$ASSETS_DIR/sports/ncaa_fbs_logos"
if [ -d "$NCAA_DIR" ]; then
# Create a test file to verify write access
TEST_FILE="$NCAA_DIR/.permission_test"
if sudo -u "$REAL_USER" touch "$TEST_FILE" 2>/dev/null; then
echo "✓ Successfully created test file in ncaa_fbs_logos directory"
sudo -u "$REAL_USER" rm -f "$TEST_FILE"
echo "✓ Successfully removed test file"
else
echo "✗ Failed to create test file in ncaa_fbs_logos directory"
echo " This indicates the permission fix did not work properly"
fi
else
echo "✗ ncaa_fbs_logos directory does not exist"
fi
echo ""
echo "Assets permissions fix completed!"
echo ""
echo "The application should now be able to download and save team logos."
echo "If you still see permission errors, check which user is running the LEDMatrix service"
echo "and ensure it matches the owner above ($REAL_USER)."
echo ""
echo "You may need to restart the LEDMatrix service for the changes to take effect:"
echo " sudo systemctl restart ledmatrix.service"

43
migrate_config.sh Normal file
View File

@@ -0,0 +1,43 @@
#!/bin/bash
# LED Matrix Configuration Migration Script
# This script helps migrate existing config.json to the new template-based system
set -e
echo "=========================================="
echo "LED Matrix Configuration Migration Script"
echo "=========================================="
echo ""
# Check if we're in the right directory
if [ ! -f "config/config.template.json" ]; then
echo "Error: config/config.template.json not found."
echo "Please run this script from the LEDMatrix project root directory."
exit 1
fi
# Check if config.json exists
if [ ! -f "config/config.json" ]; then
echo "No existing config.json found. Creating from template..."
cp config/config.template.json config/config.json
echo "✓ Created config/config.json from template"
echo ""
echo "You can now edit config/config.json with your preferences."
exit 0
fi
echo "Existing config.json found. The system will automatically handle migration."
echo ""
echo "What this means:"
echo "- Your current config.json will be preserved"
echo "- New configuration options will be automatically added with default values"
echo "- A backup will be created before any changes"
echo "- The system handles this automatically when it starts"
echo ""
echo "No manual migration is needed. The ConfigManager will handle everything automatically."
echo ""
echo "To see the latest configuration options, you can reference:"
echo " config/config.template.json"
echo ""
echo "Migration complete!"

File diff suppressed because it is too large Load Diff

View File

@@ -101,7 +101,7 @@ def show_status():
def main():
if len(sys.argv) < 2:
print("Usage: python3 enable_news_manager.py [enable|disable|status]")
print("Usage: python3 scripts/enable_news_manager.py [enable|disable|status]")
sys.exit(1)
command = sys.argv[1].lower()

View File

@@ -7,6 +7,7 @@ class ConfigManager:
# Use current working directory as base
self.config_path = config_path or "config/config.json"
self.secrets_path = secrets_path or "config/config_secrets.json"
self.template_path = "config/config.template.json"
self.config: Dict[str, Any] = {}
def get_config_path(self) -> str:
@@ -18,11 +19,18 @@ class ConfigManager:
def load_config(self) -> Dict[str, Any]:
"""Load configuration from JSON files."""
try:
# Check if config file exists, if not create from template
if not os.path.exists(self.config_path):
self._create_config_from_template()
# Load main config
print(f"Attempting to load config from: {os.path.abspath(self.config_path)}")
with open(self.config_path, 'r') as f:
self.config = json.load(f)
# Migrate config to add any new items from template
self._migrate_config()
# Load and merge secrets if they exist (be permissive on errors)
if os.path.exists(self.secrets_path):
try:
@@ -118,6 +126,85 @@ class ConfigManager:
else:
target[key] = value
def _create_config_from_template(self) -> None:
"""Create config.json from template if it doesn't exist."""
if not os.path.exists(self.template_path):
raise FileNotFoundError(f"Template file not found at {os.path.abspath(self.template_path)}")
print(f"Creating config.json from template at {os.path.abspath(self.template_path)}")
# Ensure config directory exists
os.makedirs(os.path.dirname(self.config_path), exist_ok=True)
# Copy template to config
with open(self.template_path, 'r') as template_file:
template_data = json.load(template_file)
with open(self.config_path, 'w') as config_file:
json.dump(template_data, config_file, indent=4)
print(f"Created config.json from template at {os.path.abspath(self.config_path)}")
def _migrate_config(self) -> None:
"""Migrate config to add new items from template with defaults."""
if not os.path.exists(self.template_path):
print(f"Template file not found at {os.path.abspath(self.template_path)}, skipping migration")
return
try:
with open(self.template_path, 'r') as f:
template_config = json.load(f)
# Check if migration is needed
if self._config_needs_migration(self.config, template_config):
print("Config migration needed - adding new configuration items with defaults")
# Create backup of current config
backup_path = f"{self.config_path}.backup"
with open(backup_path, 'w') as backup_file:
json.dump(self.config, backup_file, indent=4)
print(f"Created backup of current config at {os.path.abspath(backup_path)}")
# Merge template defaults into current config
self._merge_template_defaults(self.config, template_config)
# Save migrated config
with open(self.config_path, 'w') as f:
json.dump(self.config, f, indent=4)
print(f"Config migration completed and saved to {os.path.abspath(self.config_path)}")
else:
print("Config is up to date, no migration needed")
except Exception as e:
print(f"Error during config migration: {e}")
# Don't raise - continue with current config
def _config_needs_migration(self, current_config: Dict[str, Any], template_config: Dict[str, Any]) -> bool:
"""Check if config needs migration by comparing with template."""
return self._has_new_keys(current_config, template_config)
def _has_new_keys(self, current: Dict[str, Any], template: Dict[str, Any]) -> bool:
"""Recursively check if template has keys not in current config."""
for key, value in template.items():
if key not in current:
return True
if isinstance(value, dict) and isinstance(current[key], dict):
if self._has_new_keys(current[key], value):
return True
return False
def _merge_template_defaults(self, current: Dict[str, Any], template: Dict[str, Any]) -> None:
"""Recursively merge template defaults into current config."""
for key, value in template.items():
if key not in current:
# Add new key with template value
current[key] = value
print(f"Added new config key: {key}")
elif isinstance(value, dict) and isinstance(current[key], dict):
# Recursively merge nested dictionaries
self._merge_template_defaults(current[key], value)
def get_timezone(self) -> str:
"""Get the configured timezone."""
return self.config.get('timezone', 'UTC')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -206,13 +206,13 @@ This single script handles everything you need for a new installation.
### **Dependency Installation Scripts**
#### `install_dependencies_apt.py`
#### `scripts/install_dependencies_apt.py`
- **When to use**: When you want to install packages via apt first, then pip
- **What it does**:
- Tries to install packages via apt (system packages)
- Falls back to pip with `--break-system-packages`
- Handles externally managed Python environments
- **Usage**: `sudo python3 install_dependencies_apt.py`
- **Usage**: `sudo python3 scripts/install_dependencies_apt.py`
#### `start_web_v2.py`
- **When to use**: Manual web interface startup
@@ -252,7 +252,7 @@ sudo ./first_time_install.sh
### **Scenario 2: Adding Web Interface to Existing Installation**
```bash
# Install web interface dependencies
sudo python3 install_dependencies_apt.py
sudo python3 scripts/install_dependencies_apt.py
# Fix permissions
./fix_web_permissions.sh
@@ -332,7 +332,7 @@ http://your-pi-ip:5001
2. Log out and back in
### **Dependency Installation Errors**
1. Run: `sudo python3 install_dependencies_apt.py`
1. Run: `sudo python3 scripts/install_dependencies_apt.py`
## Summary

View File

@@ -50,17 +50,17 @@ Other: https://www.coveringthecorner.com/rss/current.xml
### Command Line Management
Use the `enable_news_manager.py` script to manage the news manager:
Use the `scripts/enable_news_manager.py` script to manage the news manager:
```bash
# Check current status
python3 enable_news_manager.py status
python3 scripts/enable_news_manager.py status
# Enable news manager
python3 enable_news_manager.py enable
python3 scripts/enable_news_manager.py enable
# Disable news manager
python3 enable_news_manager.py disable
python3 scripts/enable_news_manager.py disable
```
### Web Interface