persistent config file via config.template.json and migrate_config.sh
2
.gitignore
vendored
@@ -5,6 +5,8 @@ __pycache__/
|
||||
|
||||
# Secrets
|
||||
config/config_secrets.json
|
||||
config/config.json
|
||||
config/config.json.backup
|
||||
credentials.json
|
||||
token.pickle
|
||||
|
||||
|
||||
34
README.md
@@ -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.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -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
@@ -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
@@ -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!"
|
||||
1230
milb_main.py
@@ -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()
|
||||
@@ -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')
|
||||
|
||||
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 23 KiB |
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||