- Enhanced install.sh with comprehensive autostart workflow: * XDG autostart entry (desktop environment) * systemd user service (most reliable) * LXDE autostart support (Raspberry Pi OS) * Cron fallback (@reboot) * Terminal mode enabled for debugging - Added Raspberry Pi power management features: * Disable HDMI screen blanking * Prevent CPU power saving (performance mode) * Disable system sleep/suspend * X11 screensaver disabled * Display power management (DPMS) disabled - Fixed sudo compatibility: * Properly detects actual user when run with sudo * Correct file ownership for user configs * systemctl --user works correctly - Player launches in terminal for error visibility - Autostart configured to use start.sh (watchdog with auto-restart)
347 lines
11 KiB
Markdown
347 lines
11 KiB
Markdown
# Player Code HTTPS Integration Guide
|
|
|
|
## Server-Side Improvements Implemented
|
|
|
|
All critical and medium improvements have been implemented on the server:
|
|
|
|
### ✅ CORS Support Enabled
|
|
- **File**: `app/extensions.py` - CORS extension initialized
|
|
- **File**: `app/app.py` - CORS configured for `/api/*` endpoints
|
|
- All player API requests now support cross-origin requests
|
|
- Preflight OPTIONS requests are properly handled
|
|
|
|
### ✅ SSL Certificate Endpoint Added
|
|
- **Endpoint**: `GET /api/certificate`
|
|
- **Location**: `app/blueprints/api.py`
|
|
- Returns server certificate in PEM format with metadata:
|
|
- Certificate content (PEM format)
|
|
- Certificate info (subject, issuer, validity dates, fingerprint)
|
|
- Integration instructions for different platforms
|
|
|
|
### ✅ HTTPS Configuration Updated
|
|
- **File**: `app/config.py` - ProductionConfig now has:
|
|
- `SESSION_COOKIE_SECURE = True`
|
|
- `SESSION_COOKIE_SAMESITE = 'Lax'`
|
|
- **File**: `nginx.conf` - Added:
|
|
- CORS headers for all responses
|
|
- OPTIONS request handling
|
|
- X-Forwarded-Port header forwarding
|
|
|
|
### ✅ Nginx Proxy Configuration Enhanced
|
|
- Added CORS headers at nginx level for defense-in-depth
|
|
- Proper X-Forwarded headers for protocol/port detection
|
|
- HTTPS-friendly proxy configuration
|
|
|
|
---
|
|
|
|
## Required Player Code Changes
|
|
|
|
### 1. **For Python/Kivy Players Using Requests Library**
|
|
|
|
**Update:** Import and use certificate handling:
|
|
|
|
```python
|
|
import requests
|
|
from requests.adapters import HTTPAdapter
|
|
from requests.packages.urllib3.util.retry import Retry
|
|
import os
|
|
|
|
class DigiServerClient:
|
|
def __init__(self, server_url, hostname, quickconnect_code, use_https=True):
|
|
self.server_url = server_url
|
|
self.hostname = hostname
|
|
self.quickconnect_code = quickconnect_code
|
|
self.session = requests.Session()
|
|
|
|
# CRITICAL: Handle SSL verification
|
|
if use_https:
|
|
# Option 1: Get certificate from server and trust it
|
|
self.setup_certificate_trust()
|
|
else:
|
|
# Option 2: Disable SSL verification (DEV ONLY)
|
|
self.session.verify = False
|
|
|
|
def setup_certificate_trust(self):
|
|
"""Download server certificate and configure trust."""
|
|
try:
|
|
# First, make a request without verification to get the cert
|
|
response = requests.get(
|
|
f"{self.server_url}/api/certificate",
|
|
verify=False,
|
|
timeout=5
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
cert_data = response.json()
|
|
|
|
# Save certificate locally
|
|
cert_path = os.path.expanduser('~/.digiserver/server_cert.pem')
|
|
os.makedirs(os.path.dirname(cert_path), exist_ok=True)
|
|
|
|
with open(cert_path, 'w') as f:
|
|
f.write(cert_data['certificate'])
|
|
|
|
# Configure session to use this certificate
|
|
self.session.verify = cert_path
|
|
|
|
print(f"✓ Server certificate installed from {cert_data['certificate_info']['issuer']}")
|
|
print(f" Valid until: {cert_data['certificate_info']['valid_until']}")
|
|
|
|
except Exception as e:
|
|
print(f"⚠️ Failed to setup certificate trust: {e}")
|
|
print(" Falling back to unverified connection (not recommended for production)")
|
|
self.session.verify = False
|
|
|
|
def get_playlist(self):
|
|
"""Get playlist from server with proper error handling."""
|
|
try:
|
|
response = self.session.get(
|
|
f"{self.server_url}/api/playlists",
|
|
params={
|
|
'hostname': self.hostname,
|
|
'quickconnect_code': self.quickconnect_code
|
|
},
|
|
timeout=10
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
except requests.exceptions.SSLError as e:
|
|
print(f"❌ SSL Error: {e}")
|
|
# Log error for debugging
|
|
print(" This usually means the server certificate is not trusted.")
|
|
print(" Try running: DigiServerClient.setup_certificate_trust()")
|
|
raise
|
|
|
|
except requests.exceptions.ConnectionError as e:
|
|
print(f"❌ Connection Error: {e}")
|
|
raise
|
|
|
|
except Exception as e:
|
|
print(f"❌ Error: {e}")
|
|
raise
|
|
|
|
def send_feedback(self, status, message=''):
|
|
"""Send player feedback/status to server."""
|
|
try:
|
|
response = self.session.post(
|
|
f"{self.server_url}/api/player-feedback",
|
|
json={
|
|
'hostname': self.hostname,
|
|
'quickconnect_code': self.quickconnect_code,
|
|
'status': status,
|
|
'message': message,
|
|
'timestamp': datetime.utcnow().isoformat()
|
|
},
|
|
timeout=10
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
except Exception as e:
|
|
print(f"Error sending feedback: {e}")
|
|
return None
|
|
```
|
|
|
|
### 2. **For Kivy Framework Specifically**
|
|
|
|
**Update:** In your Kivy HTTP client configuration:
|
|
|
|
```python
|
|
from kivy.network.urlrequest import UrlRequest
|
|
from kivy.logger import Logger
|
|
import ssl
|
|
import certifi
|
|
|
|
class DigiServerKivyClient:
|
|
def __init__(self, server_url, hostname, quickconnect_code):
|
|
self.server_url = server_url
|
|
self.hostname = hostname
|
|
self.quickconnect_code = quickconnect_code
|
|
|
|
# Configure SSL context for Kivy requests
|
|
self.ssl_context = self._setup_ssl_context()
|
|
|
|
def _setup_ssl_context(self):
|
|
"""Setup SSL context with certificate trust."""
|
|
try:
|
|
# Try to get server certificate
|
|
import requests
|
|
response = requests.get(
|
|
f"{self.server_url}/api/certificate",
|
|
verify=False,
|
|
timeout=5
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
cert_data = response.json()
|
|
cert_path = os._get_cert_path()
|
|
|
|
with open(cert_path, 'w') as f:
|
|
f.write(cert_data['certificate'])
|
|
|
|
# Create SSL context
|
|
context = ssl.create_default_context()
|
|
context.load_verify_locations(cert_path)
|
|
|
|
Logger.info('DigiServer', f'SSL context configured with server certificate')
|
|
return context
|
|
|
|
except Exception as e:
|
|
Logger.warning('DigiServer', f'Failed to setup SSL: {e}')
|
|
return None
|
|
|
|
def fetch_playlist(self, callback):
|
|
"""Fetch playlist with proper SSL handling."""
|
|
url = f"{self.server_url}/api/playlists"
|
|
params = f"?hostname={self.hostname}&quickconnect_code={self.quickconnect_code}"
|
|
|
|
headers = {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'Kiwy-Signage-Player/1.0'
|
|
}
|
|
|
|
request = UrlRequest(
|
|
url + params,
|
|
on_success=callback,
|
|
on_error=self._on_error,
|
|
on_failure=self._on_failure,
|
|
headers=headers
|
|
)
|
|
|
|
return request
|
|
|
|
def _on_error(self, request, error):
|
|
Logger.error('DigiServer', f'Request error: {error}')
|
|
|
|
def _on_failure(self, request, result):
|
|
Logger.error('DigiServer', f'Request failed: {result}')
|
|
```
|
|
|
|
### 3. **Environment Configuration**
|
|
|
|
**Add to player app_config.json or environment:**
|
|
|
|
```json
|
|
{
|
|
"server": {
|
|
"url": "https://192.168.0.121",
|
|
"hostname": "player1",
|
|
"quickconnect_code": "ABC123XYZ",
|
|
"verify_ssl": false,
|
|
"use_server_certificate": true,
|
|
"certificate_path": "~/.digiserver/server_cert.pem"
|
|
},
|
|
"connection": {
|
|
"timeout": 10,
|
|
"retry_attempts": 3,
|
|
"retry_delay": 5
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Testing Checklist
|
|
|
|
### Server-Side Tests
|
|
|
|
- [ ] Verify CORS headers present: `curl -v https://192.168.0.121/api/health`
|
|
- [ ] Check certificate endpoint: `curl -k https://192.168.0.121/api/certificate`
|
|
- [ ] Test OPTIONS preflight: `curl -X OPTIONS https://192.168.0.121/api/playlists`
|
|
- [ ] Verify X-Forwarded headers: `curl -v https://192.168.0.121/`
|
|
|
|
### Player Connection Tests
|
|
|
|
- [ ] Player connects with HTTPS successfully
|
|
- [ ] Player fetches playlist without SSL errors
|
|
- [ ] Player receives status update confirmation
|
|
- [ ] Player sends feedback/heartbeat correctly
|
|
|
|
### Integration Tests
|
|
|
|
```bash
|
|
# Test certificate retrieval
|
|
curl -k https://192.168.0.121/api/certificate | jq '.certificate_info'
|
|
|
|
# Test CORS preflight for player
|
|
curl -X OPTIONS https://192.168.0.121/api/playlists \
|
|
-H "Origin: http://192.168.0.121" \
|
|
-H "Access-Control-Request-Method: GET" \
|
|
-v
|
|
|
|
# Simulate player playlist fetch
|
|
curl -k https://192.168.0.121/api/playlists \
|
|
--data-urlencode "hostname=test-player" \
|
|
--data-urlencode "quickconnect_code=test123" \
|
|
-H "Origin: *"
|
|
```
|
|
|
|
---
|
|
|
|
## Migration Steps
|
|
|
|
### For Existing Players
|
|
|
|
1. **Update player code** with new SSL handling from this guide
|
|
2. **Restart player application** to pick up changes
|
|
3. **Verify connection** works with HTTPS server
|
|
4. **Monitor logs** for any SSL-related errors
|
|
|
|
### For New Players
|
|
|
|
1. **Deploy updated player code** with SSL support from the start
|
|
2. **Configure with HTTPS server URL**
|
|
3. **Run initialization** to fetch and trust server certificate
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### "SSL: CERTIFICATE_VERIFY_FAILED"
|
|
- Player is rejecting the self-signed certificate
|
|
- **Solution**: Run certificate trust setup or disable SSL verification
|
|
|
|
### "Connection Refused"
|
|
- Server HTTPS port not accessible
|
|
- **Solution**: Check nginx is running, port 443 is open, firewall rules
|
|
|
|
### "CORS error"
|
|
- Browser/HTTP client blocking cross-origin request
|
|
- **Solution**: Verify CORS headers in response, check Origin header
|
|
|
|
### "Certificate not found at endpoint"
|
|
- Server certificate file missing
|
|
- **Solution**: Verify cert.pem exists at `/etc/nginx/ssl/cert.pem`
|
|
|
|
---
|
|
|
|
## Security Recommendations
|
|
|
|
1. **For Development/Testing**: Disable SSL verification temporarily
|
|
```python
|
|
session.verify = False
|
|
```
|
|
|
|
2. **For Production**:
|
|
- Use proper certificates (Let's Encrypt recommended)
|
|
- Deploy certificate trust setup at player initialization
|
|
- Monitor SSL certificate expiration
|
|
- Implement certificate pinning for critical deployments
|
|
|
|
3. **For Self-Signed Certificates**:
|
|
- Use `/api/certificate` endpoint to distribute certificates
|
|
- Store certificates in secure location on device
|
|
- Implement certificate update mechanism
|
|
- Log certificate trust changes for auditing
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
1. **Implement SSL handling** in player code using examples above
|
|
2. **Test with HTTP first** to ensure API works
|
|
3. **Enable HTTPS** and test with certificate handling
|
|
4. **Deploy to production** with proper SSL setup
|
|
5. **Monitor** player connections and SSL errors
|
|
|