more robust first time install script, ensure it creates config_secrets

This commit is contained in:
Chuck
2025-08-13 11:36:59 -05:00
parent 6225189b3c
commit e36d92340e
5 changed files with 75 additions and 19 deletions

View File

@@ -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 ""

View File

@@ -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

View File

@@ -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}")

View File

@@ -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>

View File

@@ -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)