updated player deployment for digiserver

This commit is contained in:
ske087
2026-06-07 23:40:50 +03:00
parent b97372f74d
commit f674330b93
30 changed files with 3459 additions and 201 deletions
+128
View File
@@ -3,6 +3,7 @@ from flask import Blueprint, request, jsonify, current_app
from functools import wraps
from datetime import datetime, timedelta
import secrets
import hashlib
import bcrypt
from typing import Optional, Dict, List
@@ -860,6 +861,133 @@ def receive_edited_media():
return jsonify({'error': 'Internal server error'}), 500
# ──────────────────────────────────────────────────────────────────────────────
# SSH/Deployment Endpoints - For player provisioning and code deployment
# ──────────────────────────────────────────────────────────────────────────────
@api_bp.route('/deploy/test-ssh', methods=['POST'])
@rate_limit(max_requests=30, window=60)
def test_ssh_connection():
"""Test SSH connection to a remote host.
Request JSON:
hostname: Target hostname or IP (required)
username: SSH username (required)
password: SSH password (required)
port: SSH port (default: 22)
Returns:
JSON with connection test result
"""
try:
from app.utils.ssh_deploy import test_ssh_connection as test_ssh
data = request.get_json()
if not data:
return jsonify({'error': 'No data provided'}), 400
hostname = data.get('hostname', '').strip()
username = data.get('username', '').strip()
password = data.get('password', '').strip()
port = data.get('port', 22)
# Validation
if not hostname or not username or not password:
return jsonify({'error': 'hostname, username, and password are required'}), 400
# Test connection
result = test_ssh(hostname, username, password, port)
log_action('info', f'SSH test for {username}@{hostname}: {result["message"]}')
return jsonify(result), 200 if result['success'] else 400
except Exception as e:
log_action('error', f'Error testing SSH connection: {str(e)}')
return jsonify({
'success': False,
'error': str(e),
'message': f'SSH test error: {str(e)}'
}), 500
@api_bp.route('/deploy/player', methods=['POST'])
@rate_limit(max_requests=20, window=60)
def deploy_player():
"""Deploy player code to a remote host via SSH.
Request JSON:
hostname: Target hostname or IP (required)
username: SSH username (required)
password: SSH password (required)
player_name: Name for the player instance (required)
port: SSH port (default: 22)
deploy_path: Deployment path on remote host (default: /home/[user]/kiwy-signage)
repo_url: Git repository URL (default: official Kiwy-Signage repo)
Returns:
JSON with deployment status and step details
"""
try:
from app.utils.ssh_deploy import deploy_player_to_host
data = request.get_json()
if not data:
return jsonify({'error': 'No data provided'}), 400
hostname = data.get('hostname', '').strip()
username = data.get('username', '').strip()
password = data.get('password', '').strip()
player_name = data.get('player_name', '').strip()
port = data.get('port', 22)
deploy_path = data.get('deploy_path', None) # Will default to /home/[user]/kiwy-signage
repo_url = data.get('repo_url', 'https://gitea.moto-adv.com/ske087/Kiwy-Signage.git').strip()
# Validation
if not hostname or not username or not password:
return jsonify({'error': 'hostname, username, and password are required'}), 400
if not player_name:
return jsonify({'error': 'player_name is required'}), 400
# Get server URL for player configuration
# Use X-Forwarded-Proto and X-Forwarded-Host for proxy, fall back to request host
scheme = request.headers.get('X-Forwarded-Proto', request.scheme)
host = request.headers.get('X-Forwarded-Host', request.host)
server_url = f"{scheme}://{host}/digiserver"
# Get or generate API key for the player
# For now, use a hash of player_name and hostname as a simple key
import hashlib
api_key = hashlib.sha256(f'{player_name}:{hostname}'.encode()).hexdigest()[:32]
# Execute deployment
result = deploy_player_to_host(
hostname=hostname,
username=username,
password=password,
player_name=player_name,
repo_url=repo_url,
deploy_path=deploy_path, # Will use /home/[user]/kiwy-signage if None
port=port,
server_url=server_url,
server_api_key=api_key
)
log_action('info', f'Player deployment for {player_name} on {hostname}: success={result["success"]}')
return jsonify(result), 200 if result['success'] else 400
except Exception as e:
log_action('error', f'Error deploying player: {str(e)}')
return jsonify({
'success': False,
'error': str(e),
'message': f'Deployment error: {str(e)}',
'steps': []
}), 500
@api_bp.errorhandler(404)
def api_not_found(error):
"""Handle 404 errors in API."""
+46 -3
View File
@@ -41,7 +41,7 @@ def list():
@players_bp.route('/add', methods=['GET', 'POST'])
@login_required
def add_player():
"""Add a new player."""
"""Add a new player with optional SSH deployment."""
if request.method == 'GET':
playlists = Playlist.query.filter_by(is_active=True).order_by(Playlist.name).all()
return render_template('players/add_player.html', playlists=playlists)
@@ -55,6 +55,13 @@ def add_player():
orientation = request.form.get('orientation', 'Landscape')
playlist_id = request.form.get('playlist_id', '').strip()
# Get SSH deployment info if provided
ssh_hostname = request.form.get('ssh_hostname', '').strip()
ssh_username = request.form.get('ssh_username', '').strip()
ssh_password = request.form.get('ssh_password', '').strip()
ssh_port = int(request.form.get('ssh_port', '22')) if request.form.get('ssh_port') else 22
deploy_player = request.form.get('deploy_player', '').strip()
# Validation
if not name or len(name) < 3:
flash('Player name must be at least 3 characters long.', 'warning')
@@ -102,14 +109,50 @@ def add_player():
log_action('info', f'Player "{name}" (hostname: {hostname}) created')
# If deployment requested and SSH credentials provided, trigger background deployment
deployment_initiated = False
if deploy_player and ssh_hostname and ssh_username and ssh_password:
try:
from app.utils.background_tasks import background_player_deployment, run_background_task
# Get server URL for player configuration
from flask import request as flask_request
server_url = f"{flask_request.scheme}://{flask_request.host}/digiserver"
# Generate API key for player authentication
import hashlib
api_key = hashlib.sha256(f'{name}:{hostname}'.encode()).hexdigest()[:32]
# Start deployment in background thread
run_background_task(
background_player_deployment,
hostname=ssh_hostname,
username=ssh_username,
password=ssh_password,
player_name=name,
player_id=new_player.id,
port=ssh_port,
server_url=server_url,
server_api_key=api_key
)
deployment_initiated = True
log_action('info', f'Background deployment initiated for player "{name}" on {ssh_hostname}')
except Exception as deploy_err:
log_action('error', f'Failed to initiate background deployment for player "{name}": {str(deploy_err)}')
# Flash detailed success message
success_msg = f'''
Player "{name}" created successfully!<br>
<strong>Auth Code:</strong> {auth_code}<br>
<strong>Auth Code:</strong> <code style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px;">{auth_code}</code><br>
<strong>Hostname:</strong> {hostname}<br>
<strong>Quick Connect:</strong> {quickconnect_code}<br>
<small>Configure the player with these credentials in app_config.json</small>
'''
if deployment_initiated:
success_msg += f'<strong style="color: #0275d8;">⏳ Deployment in Progress</strong> Deploying to {ssh_hostname} in background...<br>'
success_msg += '<small>Check player status to see deployment completion</small><br>'
success_msg += '<small>Configure the player with these credentials in app_config.json</small>'
flash(success_msg, 'success')
return redirect(url_for('players.list'))