mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 13:02:59 +00:00
more robust first time install script, ensure it creates config_secrets
This commit is contained in:
@@ -233,6 +233,37 @@ else
|
|||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
CURRENT_STEP="Ensure secrets configuration exists"
|
||||||
|
echo "Step 3.1: Ensuring secrets configuration exists..."
|
||||||
|
echo "-----------------------------------------------"
|
||||||
|
|
||||||
|
# Ensure config directory exists
|
||||||
|
mkdir -p "$PROJECT_ROOT_DIR/config"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
echo "Creating config/config_secrets.json from template..."
|
||||||
|
cp "$PROJECT_ROOT_DIR/config/config_secrets.template.json" "$PROJECT_ROOT_DIR/config/config_secrets.json"
|
||||||
|
chmod 600 "$PROJECT_ROOT_DIR/config/config_secrets.json"
|
||||||
|
echo "✓ Secrets file created from template"
|
||||||
|
else
|
||||||
|
echo "⚠ Template config/config_secrets.template.json not found; creating a minimal secrets file"
|
||||||
|
cat > "$PROJECT_ROOT_DIR/config/config_secrets.json" <<'EOF'
|
||||||
|
{
|
||||||
|
"weather": {
|
||||||
|
"api_key": "YOUR_OPENWEATHERMAP_API_KEY"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
chmod 600 "$PROJECT_ROOT_DIR/config/config_secrets.json"
|
||||||
|
echo "✓ Minimal secrets file created"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Secrets file already exists; leaving as-is"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
CURRENT_STEP="Install project Python dependencies"
|
CURRENT_STEP="Install project Python dependencies"
|
||||||
echo "Step 4: Installing Python project dependencies..."
|
echo "Step 4: Installing Python project dependencies..."
|
||||||
echo "-----------------------------------------------"
|
echo "-----------------------------------------------"
|
||||||
@@ -413,6 +444,12 @@ if [ -f "$PROJECT_ROOT_DIR/config/config.json" ]; then
|
|||||||
echo "✓ Config file permissions set"
|
echo "✓ Config file permissions set"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Set proper permissions for secrets file (restrictive)
|
||||||
|
if [ -f "$PROJECT_ROOT_DIR/config/config_secrets.json" ]; then
|
||||||
|
chmod 600 "$PROJECT_ROOT_DIR/config/config_secrets.json"
|
||||||
|
echo "✓ Secrets file permissions set"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "✓ File ownership configured"
|
echo "✓ File ownership configured"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Wants=ledmatrix.service
|
|||||||
Type=simple
|
Type=simple
|
||||||
User=root
|
User=root
|
||||||
WorkingDirectory=/home/ledpi/LEDMatrix
|
WorkingDirectory=/home/ledpi/LEDMatrix
|
||||||
|
Environment=USE_THREADING=1
|
||||||
ExecStart=/usr/bin/python3 /home/ledpi/LEDMatrix/start_web_conditionally.py
|
ExecStart=/usr/bin/python3 /home/ledpi/LEDMatrix/start_web_conditionally.py
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=10
|
RestartSec=10
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ def main():
|
|||||||
# This is important for systemd to correctly manage the web server process.
|
# This is important for systemd to correctly manage the web server process.
|
||||||
# Ensure PYTHONPATH is set correctly if web_interface.py has relative imports to src
|
# Ensure PYTHONPATH is set correctly if web_interface.py has relative imports to src
|
||||||
# The WorkingDirectory in systemd service should handle this for web_interface.py
|
# The WorkingDirectory in systemd service should handle this for web_interface.py
|
||||||
|
print(f"Launching web interface: {sys.executable} {WEB_INTERFACE_SCRIPT} (USE_THREADING={os.getenv('USE_THREADING','0')}, FORCE_THREADING={os.getenv('FORCE_THREADING','0')})")
|
||||||
os.execvp(sys.executable, [sys.executable, WEB_INTERFACE_SCRIPT])
|
os.execvp(sys.executable, [sys.executable, WEB_INTERFACE_SCRIPT])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to exec web interface: {e}")
|
print(f"Failed to exec web interface: {e}")
|
||||||
|
|||||||
@@ -692,17 +692,17 @@
|
|||||||
<i class="fas fa-{{ 'play' if system_status.service_active else 'stop' }}"></i>
|
<i class="fas fa-{{ 'play' if system_status.service_active else 'stop' }}"></i>
|
||||||
Service {{ 'Active' if system_status.service_active else 'Inactive' }}
|
Service {{ 'Active' if system_status.service_active else 'Inactive' }}
|
||||||
</div>
|
</div>
|
||||||
<div class="status-item {{ 'status-warning' if system_status.cpu_percent > 80 else 'status-active' }}">
|
<div class="status-item {{ 'status-warning' if system_status and system_status.cpu_percent and system_status.cpu_percent > 80 else 'status-active' }}">
|
||||||
<i class="fas fa-microchip"></i>
|
<i class="fas fa-microchip"></i>
|
||||||
{{ system_status.cpu_percent }}% CPU
|
{{ system_status.cpu_percent if system_status and system_status.cpu_percent is defined else 0 }}% CPU
|
||||||
</div>
|
</div>
|
||||||
<div class="status-item {{ 'status-warning' if system_status.memory_used_percent > 80 else 'status-active' }}">
|
<div class="status-item {{ 'status-warning' if system_status and system_status.memory_used_percent and system_status.memory_used_percent > 80 else 'status-active' }}">
|
||||||
<i class="fas fa-memory"></i>
|
<i class="fas fa-memory"></i>
|
||||||
{{ system_status.memory_used_percent }}% RAM
|
{{ system_status.memory_used_percent if system_status and system_status.memory_used_percent is defined else 0 }}% RAM
|
||||||
</div>
|
</div>
|
||||||
<div class="status-item {{ 'status-warning' if system_status.cpu_temp > 70 else 'status-active' }}">
|
<div class="status-item {{ 'status-warning' if system_status and system_status.cpu_temp and system_status.cpu_temp > 70 else 'status-active' }}">
|
||||||
<i class="fas fa-thermometer-half"></i>
|
<i class="fas fa-thermometer-half"></i>
|
||||||
{{ system_status.cpu_temp }}°C
|
{{ system_status.cpu_temp if system_status and system_status.cpu_temp is defined else 0 }}°C
|
||||||
</div>
|
</div>
|
||||||
<div class="status-item status-active">
|
<div class="status-item status-active">
|
||||||
<i class="fas fa-clock"></i>
|
<i class="fas fa-clock"></i>
|
||||||
@@ -858,15 +858,15 @@
|
|||||||
<h3>System Overview</h3>
|
<h3>System Overview</h3>
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<div class="stat-value">{{ system_status.cpu_percent }}%</div>
|
<div class="stat-value">{{ system_status.cpu_percent if system_status and system_status.cpu_percent is defined else 0 }}%</div>
|
||||||
<div class="stat-label">CPU Usage</div>
|
<div class="stat-label">CPU Usage</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<div class="stat-value">{{ system_status.memory_used_percent }}%</div>
|
<div class="stat-value">{{ system_status.memory_used_percent if system_status and system_status.memory_used_percent is defined else 0 }}%</div>
|
||||||
<div class="stat-label">Memory Usage</div>
|
<div class="stat-label">Memory Usage</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<div class="stat-value">{{ system_status.cpu_temp }}°C</div>
|
<div class="stat-value">{{ system_status.cpu_temp if system_status and system_status.cpu_temp is defined else 0 }}°C</div>
|
||||||
<div class="stat-label">CPU Temperature</div>
|
<div class="stat-label">CPU Temperature</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
@@ -878,7 +878,7 @@
|
|||||||
<div class="stat-label">Resolution</div>
|
<div class="stat-label">Resolution</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<div class="stat-value">{{ system_status.disk_used_percent }}%</div>
|
<div class="stat-value">{{ system_status.disk_used_percent if system_status and system_status.disk_used_percent is defined else 0 }}%</div>
|
||||||
<div class="stat-label">Disk Usage</div>
|
<div class="stat-label">Disk Usage</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1630,7 +1630,7 @@
|
|||||||
<h4>Weather API</h4>
|
<h4>Weather API</h4>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="weather_api_key">OpenWeatherMap API Key:</label>
|
<label for="weather_api_key">OpenWeatherMap API Key:</label>
|
||||||
<input type="password" class="form-control" id="weather_api_key" name="weather_api_key" value="{{ secrets_config.weather.api_key if secrets_config.weather.api_key != 'YOUR_OPENWEATHERMAP_API_KEY' else '' }}" placeholder="Enter your OpenWeatherMap API key">
|
<input type="password" class="form-control" id="weather_api_key" name="weather_api_key" value="{{ (secrets_config.weather.api_key if secrets_config and secrets_config.weather and secrets_config.weather.api_key != 'YOUR_OPENWEATHERMAP_API_KEY' else '') if secrets_config else '' }}" placeholder="Enter your OpenWeatherMap API key">
|
||||||
<div class="description">Get your free API key from <a href="https://openweathermap.org/api" target="_blank">OpenWeatherMap</a></div>
|
<div class="description">Get your free API key from <a href="https://openweathermap.org/api" target="_blank">OpenWeatherMap</a></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -38,12 +38,16 @@ import logging
|
|||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = os.urandom(24)
|
app.secret_key = os.urandom(24)
|
||||||
# Prefer eventlet when available to avoid Werkzeug dev server quirks
|
# Prefer eventlet when available, but allow forcing threading via env for troubleshooting
|
||||||
try:
|
force_threading = os.getenv('USE_THREADING', '0') == '1' or os.getenv('FORCE_THREADING', '0') == '1'
|
||||||
import eventlet # noqa: F401
|
if force_threading:
|
||||||
ASYNC_MODE = 'eventlet'
|
|
||||||
except Exception:
|
|
||||||
ASYNC_MODE = 'threading'
|
ASYNC_MODE = 'threading'
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
import eventlet # noqa: F401
|
||||||
|
ASYNC_MODE = 'eventlet'
|
||||||
|
except Exception:
|
||||||
|
ASYNC_MODE = 'threading'
|
||||||
|
|
||||||
socketio = SocketIO(app, cors_allowed_origins="*", async_mode=ASYNC_MODE)
|
socketio = SocketIO(app, cors_allowed_origins="*", async_mode=ASYNC_MODE)
|
||||||
|
|
||||||
@@ -382,6 +386,16 @@ def index():
|
|||||||
# Get raw config data for JSON editors
|
# Get raw config data for JSON editors
|
||||||
main_config_data = config_manager.get_raw_file_content('main')
|
main_config_data = config_manager.get_raw_file_content('main')
|
||||||
secrets_config_data = config_manager.get_raw_file_content('secrets')
|
secrets_config_data = config_manager.get_raw_file_content('secrets')
|
||||||
|
# Normalize secrets structure for template safety
|
||||||
|
try:
|
||||||
|
if not isinstance(secrets_config_data, dict):
|
||||||
|
secrets_config_data = {}
|
||||||
|
if 'weather' not in secrets_config_data or not isinstance(secrets_config_data['weather'], dict):
|
||||||
|
secrets_config_data['weather'] = {}
|
||||||
|
if 'api_key' not in secrets_config_data['weather']:
|
||||||
|
secrets_config_data['weather']['api_key'] = ''
|
||||||
|
except Exception:
|
||||||
|
secrets_config_data = {'weather': {'api_key': ''}}
|
||||||
main_config_json = json.dumps(main_config_data, indent=4)
|
main_config_json = json.dumps(main_config_data, indent=4)
|
||||||
secrets_config_json = json.dumps(secrets_config_data, indent=4)
|
secrets_config_json = json.dumps(secrets_config_data, indent=4)
|
||||||
|
|
||||||
@@ -398,18 +412,20 @@ def index():
|
|||||||
editor_mode=editor_mode)
|
editor_mode=editor_mode)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Return a minimal, valid response to avoid Werkzeug assertion errors
|
# Return a minimal, valid response to avoid template errors when keys are missing
|
||||||
logger.error(f"Error loading configuration on index: {e}", exc_info=True)
|
logger.error(f"Error loading configuration on index: {e}", exc_info=True)
|
||||||
|
safe_system_status = get_system_status()
|
||||||
|
safe_secrets = {'weather': {'api_key': ''}}
|
||||||
return render_template('index_v2.html',
|
return render_template('index_v2.html',
|
||||||
schedule_config={},
|
schedule_config={},
|
||||||
main_config={},
|
main_config={},
|
||||||
main_config_data={},
|
main_config_data={},
|
||||||
secrets_config={},
|
secrets_config=safe_secrets,
|
||||||
main_config_json="{}",
|
main_config_json="{}",
|
||||||
secrets_config_json="{}",
|
secrets_config_json="{}",
|
||||||
main_config_path="",
|
main_config_path="",
|
||||||
secrets_config_path="",
|
secrets_config_path="",
|
||||||
system_status={'error': str(e)},
|
system_status=safe_system_status,
|
||||||
editor_mode=False)
|
editor_mode=False)
|
||||||
|
|
||||||
def get_system_status():
|
def get_system_status():
|
||||||
@@ -1377,4 +1393,5 @@ if __name__ == '__main__':
|
|||||||
# Run the app
|
# Run the app
|
||||||
# In threading mode this uses Werkzeug; allow it explicitly for systemd usage
|
# In threading mode this uses Werkzeug; allow it explicitly for systemd usage
|
||||||
# Use eventlet server when available; fall back to Werkzeug in threading mode
|
# Use eventlet server when available; fall back to Werkzeug in threading mode
|
||||||
|
logger.info(f"Starting web interface on http://0.0.0.0:5001 (async_mode={ASYNC_MODE})")
|
||||||
socketio.run(app, host='0.0.0.0', port=5001, debug=False, use_reloader=False)
|
socketio.run(app, host='0.0.0.0', port=5001, debug=False, use_reloader=False)
|
||||||
Reference in New Issue
Block a user