From e36d92340e1bf74023c7fc316dadf97080cdeaa5 Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Wed, 13 Aug 2025 11:36:59 -0500 Subject: [PATCH] more robust first time install script, ensure it creates config_secrets --- first_time_install.sh | 37 +++++++++++++++++++++++++++++++++++++ ledmatrix-web.service | 1 + start_web_conditionally.py | 1 + templates/index_v2.html | 22 +++++++++++----------- web_interface_v2.py | 33 +++++++++++++++++++++++++-------- 5 files changed, 75 insertions(+), 19 deletions(-) diff --git a/first_time_install.sh b/first_time_install.sh index 99b36c30..90e1479c 100644 --- a/first_time_install.sh +++ b/first_time_install.sh @@ -233,6 +233,37 @@ else fi 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" echo "Step 4: Installing Python project dependencies..." echo "-----------------------------------------------" @@ -413,6 +444,12 @@ if [ -f "$PROJECT_ROOT_DIR/config/config.json" ]; then echo "✓ Config file permissions set" 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 "" diff --git a/ledmatrix-web.service b/ledmatrix-web.service index 12d37573..11b0ad91 100644 --- a/ledmatrix-web.service +++ b/ledmatrix-web.service @@ -7,6 +7,7 @@ Wants=ledmatrix.service Type=simple User=root WorkingDirectory=/home/ledpi/LEDMatrix +Environment=USE_THREADING=1 ExecStart=/usr/bin/python3 /home/ledpi/LEDMatrix/start_web_conditionally.py Restart=on-failure RestartSec=10 diff --git a/start_web_conditionally.py b/start_web_conditionally.py index bdd14710..3de5b0c1 100644 --- a/start_web_conditionally.py +++ b/start_web_conditionally.py @@ -58,6 +58,7 @@ def main(): # 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 # 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]) except Exception as e: print(f"Failed to exec web interface: {e}") diff --git a/templates/index_v2.html b/templates/index_v2.html index 5b8ef708..60276621 100644 --- a/templates/index_v2.html +++ b/templates/index_v2.html @@ -692,17 +692,17 @@ Service {{ 'Active' if system_status.service_active else 'Inactive' }} -
+
- {{ system_status.cpu_percent }}% CPU + {{ system_status.cpu_percent if system_status and system_status.cpu_percent is defined else 0 }}% CPU
-
+
- {{ 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
-
+
- {{ system_status.cpu_temp }}°C + {{ system_status.cpu_temp if system_status and system_status.cpu_temp is defined else 0 }}°C
@@ -858,15 +858,15 @@

System Overview

-
{{ system_status.cpu_percent }}%
+
{{ system_status.cpu_percent if system_status and system_status.cpu_percent is defined else 0 }}%
CPU Usage
-
{{ system_status.memory_used_percent }}%
+
{{ system_status.memory_used_percent if system_status and system_status.memory_used_percent is defined else 0 }}%
Memory Usage
-
{{ system_status.cpu_temp }}°C
+
{{ system_status.cpu_temp if system_status and system_status.cpu_temp is defined else 0 }}°C
CPU Temperature
@@ -878,7 +878,7 @@
Resolution
-
{{ system_status.disk_used_percent }}%
+
{{ system_status.disk_used_percent if system_status and system_status.disk_used_percent is defined else 0 }}%
Disk Usage
@@ -1630,7 +1630,7 @@

Weather API

- +
Get your free API key from OpenWeatherMap
diff --git a/web_interface_v2.py b/web_interface_v2.py index ddfc2cfd..8fd24661 100644 --- a/web_interface_v2.py +++ b/web_interface_v2.py @@ -38,12 +38,16 @@ import logging app = Flask(__name__) app.secret_key = os.urandom(24) -# Prefer eventlet when available to avoid Werkzeug dev server quirks -try: - import eventlet # noqa: F401 - ASYNC_MODE = 'eventlet' -except Exception: +# Prefer eventlet when available, but allow forcing threading via env for troubleshooting +force_threading = os.getenv('USE_THREADING', '0') == '1' or os.getenv('FORCE_THREADING', '0') == '1' +if force_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) @@ -382,6 +386,16 @@ def index(): # Get raw config data for JSON editors main_config_data = config_manager.get_raw_file_content('main') 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) secrets_config_json = json.dumps(secrets_config_data, indent=4) @@ -398,18 +412,20 @@ def index(): editor_mode=editor_mode) 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) + safe_system_status = get_system_status() + safe_secrets = {'weather': {'api_key': ''}} return render_template('index_v2.html', schedule_config={}, main_config={}, main_config_data={}, - secrets_config={}, + secrets_config=safe_secrets, main_config_json="{}", secrets_config_json="{}", main_config_path="", secrets_config_path="", - system_status={'error': str(e)}, + system_status=safe_system_status, editor_mode=False) def get_system_status(): @@ -1377,4 +1393,5 @@ if __name__ == '__main__': # Run the app # In threading mode this uses Werkzeug; allow it explicitly for systemd usage # 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) \ No newline at end of file