updated WMT client
This commit is contained in:
+1
-1
@@ -11,7 +11,7 @@ data/tag.txt
|
||||
data/idmasa.txt
|
||||
|
||||
# Offline wheel cache (large, OS-specific)
|
||||
Files/reposytory/
|
||||
Files/repository/
|
||||
Files/system_packages/
|
||||
|
||||
# Misc
|
||||
|
||||
Binary file not shown.
@@ -1,22 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDqjCCApKgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBiDEpMCcGA1UECgwgSEFS
|
||||
VElORyBJVCBTZXJ2aWNlcyBHbWJIICBDby4gS0cxDjAMBgNVBAsMBUlULUlTMRIw
|
||||
EAYDVQQHDAlFc3BlbGthbXAxDDAKBgNVBAgMA05SVzELMAkGA1UEBhMCREUxHDAa
|
||||
BgNVBAMME0hBUlRJTkcgV2ViIEdhdGV3YXkwHhcNMTYxMjIxMTU0NzA0WhcNMzYx
|
||||
MjIxMTU0NzA0WjCBiDEpMCcGA1UECgwgSEFSVElORyBJVCBTZXJ2aWNlcyBHbWJI
|
||||
ICBDby4gS0cxDjAMBgNVBAsMBUlULUlTMRIwEAYDVQQHDAlFc3BlbGthbXAxDDAK
|
||||
BgNVBAgMA05SVzELMAkGA1UEBhMCREUxHDAaBgNVBAMME0hBUlRJTkcgV2ViIEdh
|
||||
dGV3YXkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCm6bFPeLYge36x
|
||||
WAsif9qMKEyJtrgAhtAbRWgmMWZW3s2TUMkL9Ovi5M9jc+W0V5aGCS8ueul5o9z2
|
||||
acTV9FT5DDBRXGEEMITpzGh8iRPZsFrNStk8L9oEXzjj87OE+n/gvnCe7KALzsUR
|
||||
UQ+FDKLGQjMzHNTwUtadRZwm+MAP02l0izTQF7ny3zpRdco8faLvSVITkX9a6N5E
|
||||
N6HfftOeoQ9xMolwielanaEbcoUKJlmV7cmAw0BjezE8CT5J0MEcaGqw6ek3pCk5
|
||||
Glt7jk+JE4X3UdUB9M60P+6pgZEmpbeoRXQqIKqrS8vC0cn0RiBPcyeoBXy0sqvK
|
||||
Aun+2m+DAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqG
|
||||
SIb3DQEBCwUAA4IBAQBarWNg89Li73TnwQly0Pn6n+/xa4P9wfDjueotoBqFY1BH
|
||||
CWNPxQxgZoDoNrbj0HXbRPGs0KAXp0KJGES/SkgFwtByH1e2eCM3BWr9FfLJvU+e
|
||||
AuGPHtqBHMxXxFEWpk2PMVconP1G3wlfU36Gd/Ujnac0wDqjkkYrVJzSNjhWJ73X
|
||||
4TOLKLDyP01Q0I1mbKT6LNPmG/kBt0zWa513tt+PaU1fCejzFM1FBng2KPhmKoJn
|
||||
1fNZHJVUpVf0vo9Mf3QztMvpVuZ9uicK/B5qHIrfvCB/XD8hDmRfrvZF0cQMSp0D
|
||||
3JYs2emjeYBaH0phQtDfRJ8OUMe15DRBa7dy75hP
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,40 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIHBzCCBO+gAwIBAgITQwAAAAkJewLoz5nxpgADAAAACTANBgkqhkiG9w0BAQsF
|
||||
ADBKMRUwEwYKCZImiZPyLGQBGRYFSW50cmExFzAVBgoJkiaJk/IsZAEZFgdIQVJU
|
||||
SU5HMRgwFgYDVQQDEw9IQVJUSU5HIFJvb3QgQ0EwHhcNMTYwMTIyMTI1MjAxWhcN
|
||||
MjQwMTIyMTMwMjAxWjBOMRUwEwYKCZImiZPyLGQBGRYFaW50cmExFzAVBgoJkiaJ
|
||||
k/IsZAEZFgdoYXJ0aW5nMRwwGgYDVQQDExNIQVJUSU5HIElzc3VpbmcgQ0ExMIIB
|
||||
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAutoC7Q/AmRR+I/s7YRsotka7
|
||||
kgCfSkRINNeIut+0gv4GchmztbuLraIz7RpQbBW4pRPWJT4J5fVzBN6HSuGv6VBB
|
||||
H72MHD3cTAbPpCeAvMHQVYCt+nfM/5w4WnOfYpjSIcV3rf6d/cOWKESpQs81F3xz
|
||||
TNbITWP3SiCeRuxdL4U+1LvE1vj2O2mqG5kkXR51XWzgC4oOh2aR9y1TY48AmAva
|
||||
WOGBbAKz8QAV8dnW7kq+r9UmRxRisSVsLbWasiTfwndYacBQQK3Iw8YyJK0DbMqS
|
||||
rBbY5kkRoF48t4JuR3T7VGlGqhKlDg9AxxOw1HkNtBB3sQw05tUZhPtOxtyORQID
|
||||
AQABo4IC4DCCAtwwEgYJKwYBBAGCNxUBBAUCAwMAAzAjBgkrBgEEAYI3FQIEFgQU
|
||||
bxiNOHg8qzcCUYJasvRJYn+CpnUwHQYDVR0OBBYEFNUJktkZLIVxRG2f4lr9qFWs
|
||||
7SM5MBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNV
|
||||
HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFGetmY4Lmx9ZU/ng6WF5pRSGvFyJMIIB
|
||||
CwYDVR0fBIIBAjCB/zCB/KCB+aCB9oYyaHR0cDovL3d3dy5oYXJ0aW5nLmNvbS9w
|
||||
a2kvSEFSVElORyUyMFJvb3QlMjBDQS5jcmyGgb9sZGFwOi8vL0NOPUhBUlRJTkcl
|
||||
MjBSb290JTIwQ0EsQ049Y2FoYXJ0aW5nMSxDTj1DRFAsQ049UHVibGljJTIwS2V5
|
||||
JTIwU2VydmljZXMsQ049U2VydmljZXMsQ049Q29uZmlndXJhdGlvbixEQz1oYXJ0
|
||||
aW5nLERDPWludHJhP2NlcnRpZmljYXRlUmV2b2NhdGlvbkxpc3Q/YmFzZT9vYmpl
|
||||
Y3RDbGFzcz1jUkxEaXN0cmlidXRpb25Qb2ludDCCARcGCCsGAQUFBwEBBIIBCTCC
|
||||
AQUwTAYIKwYBBQUHMAKGQGh0dHA6Ly93d3cuaGFydGluZy5jb20vcGtpL2NhaGFy
|
||||
dGluZzFfSEFSVElORyUyMFJvb3QlMjBDQSgzKS5jcnQwgbQGCCsGAQUFBzAChoGn
|
||||
bGRhcDovLy9DTj1IQVJUSU5HJTIwUm9vdCUyMENBLENOPUFJQSxDTj1QdWJsaWMl
|
||||
MjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERD
|
||||
PWhhcnRpbmcsREM9aW50cmE/Y0FDZXJ0aWZpY2F0ZT9iYXNlP29iamVjdENsYXNz
|
||||
PWNlcnRpZmljYXRpb25BdXRob3JpdHkwDQYJKoZIhvcNAQELBQADggIBAGGWPYKM
|
||||
Kzh7KKiNgMKbPJH25JV201GDMOUEwmxW1umfbgrAtbHWfpCfGzbk6A8ekK/9A6yk
|
||||
fCj35WvJyB1L6lGbaAOJjC4loI9XQsbQokGJ+8SsUiVFABx63idXKVqBrbtpnj0L
|
||||
XY8RQikTbvvXZNHBmMlo6dZ6FDn+xHHKSTuISf88UTJE/X6beAiMe4m3gT4loiN1
|
||||
u9L987TvW6FBbstRFGjWaXX7dQA1JzyQaoM4QJNGU2ZCTdF+8oOYy7abwqHr66f+
|
||||
bHkTd0qXMA50T5hbQmgRc5usgHUR4RtcTFu636YDeS0fi/x/lwe1UygTcEBSQA5U
|
||||
TpJwjE3lX7aupdgF+g9hNBoFCI0HGnhzZDv4k1ugZYK533NleeKThmSWPmssxExb
|
||||
siuly872stkotB/9sCEb5jbT2QnjPCIyxhGeFmHnDpPXrYlhcfgqpgA81NDFJ2Bz
|
||||
McPf2qP+/uiPjbqbX6CG1cTF6QGuGdbSkLaH5cj2Pn9i9Mqzkb7qSB09wNiSI4B/
|
||||
/GzGKnal+1vIC78Y42FOU+b9WBegDLuNuWliQSZRcRGCj/CcegUwuGzOWf0Mc2SG
|
||||
I1A5UI4Q8arg1RAXggqSOjYNAtdgl6s8wRpwzv55BHAkxvCe2/0r7p62zSfsvM5k
|
||||
OIhrHDBIkA5bND00QUuVNf6pMb4sjN0d4cna
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,32 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
-----END CERTIFICATE-----
|
||||
Binary file not shown.
@@ -1,21 +0,0 @@
|
||||
Linux CUPS drivers for POS printer
|
||||
|
||||
The following CUPS drivers are included here.
|
||||
|
||||
ctzpos-cups_1.x.x-x_i386.deb - Debian (32 bit)
|
||||
ctzpos-cups_1.x.x-x_amd64.deb - Debian (64 bit)
|
||||
ctzpos-cups_1.x.x-x_armhf.deb - Raspberry Pi OS (32bit)
|
||||
ctzpos-cups_1.x.x-x_arm64.deb - Raspberry Pi OS (64bit)
|
||||
|
||||
ctzpos-cups-1.x.x-x.i586.rpm - Red Hat (32bit)
|
||||
ctzpos-cups-1.x.x-x.x86_64.rpm - Red Hat (64bit)
|
||||
|
||||
ctzpos-cups-1.x.x-x.src.rpm - CUPS driver source code in rpm format
|
||||
|
||||
"deb" file is for Debian and it can be used on Ubuntu etc.
|
||||
"rpm" file is for Red Hat and it can be used on CentOS, Fedora etc.
|
||||
|
||||
Source code is inlucded to use CUPS driver on other Linux distribution.
|
||||
Basic explanation for driver compile and driver installation is on following site.
|
||||
http://www.citizen-systems.co.jp/en/printer/download/driver-cups-source
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -14,24 +14,35 @@ CONFIGURATION_MODE = False
|
||||
|
||||
# Run dependency check before importing anything else
|
||||
try:
|
||||
# ABI-specific wheels are available for both Bookworm (cp311) and Trixie (cp313).
|
||||
# pip will pick the correct file for the running interpreter automatically.
|
||||
_pyver = f"cp{sys.version_info.major}{sys.version_info.minor}"
|
||||
_abi_wheels = {
|
||||
'aiohttp': f'aiohttp-3.13.5-{_pyver}-{_pyver}-linux_armv7l.whl',
|
||||
'frozenlist': f'frozenlist-1.8.0-{_pyver}-{_pyver}-linux_armv7l.whl',
|
||||
'multidict': f'multidict-6.7.1-{_pyver}-{_pyver}-linux_armv7l.whl',
|
||||
'propcache': f'propcache-0.5.2-{_pyver}-{_pyver}-linux_armv7l.whl',
|
||||
'yarl': f'yarl-1.24.2-{_pyver}-{_pyver}-linux_armv7l.whl',
|
||||
}
|
||||
check_and_install_dependencies({
|
||||
'rdm6300': 'rdm6300-0.1.1-py3-none-any.whl',
|
||||
'serial': 'pyserial-3.5-py2.py3-none-any.whl',
|
||||
'gpiozero': None,
|
||||
'requests': 'requests-2.32.3-py3-none-any.whl',
|
||||
'aiohttp': 'aiohttp-3.11.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl',
|
||||
'aiohttp': _abi_wheels['aiohttp'],
|
||||
'flask': None,
|
||||
'urllib3': 'urllib3-2.3.0-py3-none-any.whl',
|
||||
'certifi': 'certifi-2025.1.31-py3-none-any.whl',
|
||||
'charset_normalizer': 'charset_normalizer-3.4.1-py3-none-any.whl',
|
||||
'idna': 'idna-3.10-py3-none-any.whl',
|
||||
'multidict': 'multidict-6.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl',
|
||||
'multidict': _abi_wheels['multidict'],
|
||||
'aiosignal': 'aiosignal-1.3.2-py2.py3-none-any.whl',
|
||||
'frozenlist': 'frozenlist-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl',
|
||||
'frozenlist': _abi_wheels['frozenlist'],
|
||||
'attrs': 'attrs-25.3.0-py3-none-any.whl',
|
||||
'yarl': 'yarl-1.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl',
|
||||
'yarl': _abi_wheels['yarl'],
|
||||
'aiohappyeyeballs': 'aiohappyeyeballs-2.6.1-py3-none-any.whl',
|
||||
'propcache': 'propcache-0.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl'
|
||||
}, "./Files/reposytory")
|
||||
'propcache': _abi_wheels['propcache'],
|
||||
}, "./Files/repository")
|
||||
except Exception as e:
|
||||
print(f"Warning: Dependency check failed: {e}")
|
||||
print("Continuing with existing packages...")
|
||||
@@ -103,32 +114,6 @@ except ImportError as e:
|
||||
print("Async functionality may be limited")
|
||||
import asyncio
|
||||
|
||||
# Import Flask for command server
|
||||
try:
|
||||
from flask import Flask, request, jsonify
|
||||
print("✓ Flask imported successfully")
|
||||
FLASK_AVAILABLE = True
|
||||
except ImportError as e:
|
||||
print(f"Warning: Could not import Flask: {e}")
|
||||
print("Command server functionality will be disabled")
|
||||
FLASK_AVAILABLE = False
|
||||
# Create dummy Flask classes
|
||||
class Flask:
|
||||
def __init__(self, name):
|
||||
pass
|
||||
def route(self, *args, **kwargs):
|
||||
def decorator(f):
|
||||
return f
|
||||
return decorator
|
||||
def run(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def request():
|
||||
pass
|
||||
|
||||
def jsonify(data):
|
||||
return data
|
||||
|
||||
import configparser
|
||||
import json
|
||||
|
||||
@@ -646,58 +631,8 @@ def check_system_requirements():
|
||||
"""
|
||||
print("Checking system requirements...")
|
||||
|
||||
# 1. Check and install required system packages
|
||||
system_packages = {
|
||||
'sshpass': 'sshpass_1.09-1_armhf.deb' # Required for auto-update functionality
|
||||
}
|
||||
|
||||
for package, deb_file in system_packages.items():
|
||||
try:
|
||||
result = subprocess.run(['which', package], capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
print(f"✓ {package} is installed")
|
||||
else:
|
||||
print(f"Installing {package}...")
|
||||
|
||||
# Try online installation first
|
||||
try:
|
||||
install_result = subprocess.run(['sudo', 'apt', 'update'], capture_output=True, text=True, timeout=120)
|
||||
install_result = subprocess.run(['sudo', 'apt', 'install', '-y', package],
|
||||
capture_output=True, text=True, timeout=300)
|
||||
if install_result.returncode == 0:
|
||||
print(f"✓ {package} installed successfully (online)")
|
||||
continue
|
||||
else:
|
||||
print(f"Online installation failed, trying offline...")
|
||||
except Exception as online_error:
|
||||
print(f"Online installation failed: {online_error}, trying offline...")
|
||||
|
||||
# Try offline installation from local .deb file
|
||||
deb_path = f"./Files/system_packages/{deb_file}"
|
||||
if os.path.exists(deb_path):
|
||||
try:
|
||||
print(f"Installing {package} from local package: {deb_path}")
|
||||
offline_result = subprocess.run(['sudo', 'dpkg', '-i', deb_path],
|
||||
capture_output=True, text=True, timeout=120)
|
||||
if offline_result.returncode == 0:
|
||||
print(f"✓ {package} installed successfully (offline)")
|
||||
else:
|
||||
print(f"✗ Offline installation failed: {offline_result.stderr}")
|
||||
# Try to fix dependencies
|
||||
print("Attempting to fix dependencies...")
|
||||
subprocess.run(['sudo', 'apt', '--fix-broken', 'install', '-y'],
|
||||
capture_output=True, text=True, timeout=300)
|
||||
except Exception as offline_error:
|
||||
print(f"✗ Offline installation error: {offline_error}")
|
||||
else:
|
||||
print(f"✗ Local package not found: {deb_path}")
|
||||
print(f" To add offline support, download with: apt download {package}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not check/install {package}: {e}")
|
||||
|
||||
# 2. Check and create required directories
|
||||
required_dirs = ['./data', './Files', './Files/reposytory', './Files/system_packages']
|
||||
# 1. Check and create required directories
|
||||
required_dirs = ['./data', './Files', './Files/repository', './Files/system_packages']
|
||||
for dir_path in required_dirs:
|
||||
try:
|
||||
os.makedirs(dir_path, exist_ok=True)
|
||||
@@ -736,65 +671,6 @@ def check_system_requirements():
|
||||
|
||||
return True
|
||||
|
||||
def check_port_capabilities():
|
||||
"""
|
||||
Check if the application can bind to port 80 and set up capabilities if needed
|
||||
"""
|
||||
print("Checking port 80 capabilities...")
|
||||
|
||||
try:
|
||||
# Check if we're running as root
|
||||
if os.geteuid() == 0:
|
||||
print("✓ Running as root - port 80 access available")
|
||||
return True
|
||||
|
||||
# Check if capabilities are set
|
||||
python_path = sys.executable
|
||||
result = subprocess.run(['getcap', python_path], capture_output=True, text=True)
|
||||
|
||||
if 'cap_net_bind_service=ep' in result.stdout:
|
||||
print("✓ Port binding capabilities already set")
|
||||
return True
|
||||
|
||||
# Try to set capabilities
|
||||
print("Setting up port 80 binding capabilities...")
|
||||
setup_script = './setup_port_capability.sh'
|
||||
|
||||
if os.path.exists(setup_script):
|
||||
result = subprocess.run(['sudo', 'bash', setup_script], capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
print("✓ Port capabilities set successfully")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ Failed to set capabilities: {result.stderr}")
|
||||
else:
|
||||
# Create the setup script if it doesn't exist
|
||||
script_content = f'''#!/bin/bash
|
||||
# Set port binding capability for Python to allow port 80 access
|
||||
echo "Setting port binding capability for Python..."
|
||||
sudo setcap cap_net_bind_service=ep {python_path}
|
||||
echo "Capability set successfully"
|
||||
'''
|
||||
try:
|
||||
with open(setup_script, 'w') as f:
|
||||
f.write(script_content)
|
||||
os.chmod(setup_script, stat.S_IRWXU | stat.S_IRGRP | stat.S_IROTH)
|
||||
|
||||
result = subprocess.run(['sudo', 'bash', setup_script], capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
print("✓ Port capabilities set successfully")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ Failed to set capabilities: {result.stderr}")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to create setup script: {e}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not check port capabilities: {e}")
|
||||
|
||||
print("Warning: Port 80 may not be accessible. App will try to run on default port.")
|
||||
return False
|
||||
|
||||
def check_hardware_interfaces():
|
||||
"""
|
||||
Check hardware interfaces (UART/Serial) required for RFID reader
|
||||
@@ -920,7 +796,6 @@ def perform_system_initialization():
|
||||
|
||||
initialization_steps = [
|
||||
("System Requirements", check_system_requirements),
|
||||
("Port Capabilities", check_port_capabilities),
|
||||
("Hardware Interfaces", check_hardware_interfaces),
|
||||
("GPIO Permissions", initialize_gpio_permissions),
|
||||
("Network Connectivity", check_network_connectivity)
|
||||
@@ -1125,269 +1000,6 @@ def log_info_with_server(message):
|
||||
# CONFIGURATION_MODE not defined yet (during initialization)
|
||||
send_log_to_server(message, n_masa, hostname, device_ip) # Send the original message to the server
|
||||
|
||||
# Function to execute system commands with proper security
|
||||
def execute_system_command(command):
|
||||
"""
|
||||
Execute system commands with proper logging and security checks
|
||||
"""
|
||||
# Define allowed commands for security
|
||||
allowed_commands = [
|
||||
"sudo apt update",
|
||||
"sudo apt upgrade -y",
|
||||
"sudo apt update && sudo apt upgrade -y", # Combined update and upgrade
|
||||
"sudo apt autoremove -y",
|
||||
"sudo apt autoclean",
|
||||
"sudo reboot",
|
||||
"sudo shutdown -h now",
|
||||
"df -h",
|
||||
"free -m",
|
||||
"uptime",
|
||||
"systemctl status",
|
||||
"sudo systemctl restart networking",
|
||||
"sudo systemctl restart ssh"
|
||||
]
|
||||
|
||||
try:
|
||||
# Check if command is allowed
|
||||
if command not in allowed_commands:
|
||||
log_info_with_server(f"Command '{command}' is not allowed for security reasons")
|
||||
return {"status": "error", "message": f"Command '{command}' is not allowed", "output": ""}
|
||||
|
||||
log_info_with_server(f"Executing command: {command}")
|
||||
|
||||
# Execute the command
|
||||
result = subprocess.run(
|
||||
command.split(),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300 # 5 minute timeout
|
||||
)
|
||||
|
||||
output = result.stdout + result.stderr
|
||||
|
||||
if result.returncode == 0:
|
||||
log_info_with_server(f"Command '{command}' executed successfully")
|
||||
return {"status": "success", "message": "Command executed successfully", "output": output}
|
||||
else:
|
||||
log_info_with_server(f"Command '{command}' failed with return code {result.returncode}")
|
||||
return {"status": "error", "message": f"Command failed with return code {result.returncode}", "output": output}
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
log_info_with_server(f"Command '{command}' timed out")
|
||||
return {"status": "error", "message": "Command timed out", "output": ""}
|
||||
except Exception as e:
|
||||
log_info_with_server(f"Error executing command '{command}': {str(e)}")
|
||||
return {"status": "error", "message": f"Error: {str(e)}", "output": ""}
|
||||
|
||||
# Flask app for receiving commands (only if Flask is available)
|
||||
if FLASK_AVAILABLE:
|
||||
command_app = Flask(__name__)
|
||||
|
||||
@command_app.route('/execute_command', methods=['POST'])
|
||||
def handle_command_execution():
|
||||
"""
|
||||
Endpoint to receive and execute system commands
|
||||
"""
|
||||
try:
|
||||
data = request.json
|
||||
if not data or 'command' not in data:
|
||||
return jsonify({"error": "Invalid request. 'command' field is required"}), 400
|
||||
|
||||
command = data.get('command')
|
||||
|
||||
# Execute the command
|
||||
result = execute_system_command(command)
|
||||
|
||||
return jsonify(result), 200 if result['status'] == 'success' else 400
|
||||
|
||||
except Exception as e:
|
||||
log_info_with_server(f"Error handling command execution request: {str(e)}")
|
||||
return jsonify({"error": f"Server error: {str(e)}"}), 500
|
||||
|
||||
@command_app.route('/status', methods=['GET'])
|
||||
def get_device_status():
|
||||
"""
|
||||
Endpoint to get device status information
|
||||
"""
|
||||
try:
|
||||
n_masa = read_name_from_file()
|
||||
|
||||
# Get system information
|
||||
uptime_result = subprocess.run(['uptime'], capture_output=True, text=True)
|
||||
df_result = subprocess.run(['df', '-h', '/'], capture_output=True, text=True)
|
||||
free_result = subprocess.run(['free', '-m'], capture_output=True, text=True)
|
||||
|
||||
status_info = {
|
||||
"hostname": hostname,
|
||||
"device_ip": device_ip,
|
||||
"nume_masa": n_masa,
|
||||
"timestamp": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
"uptime": uptime_result.stdout.strip() if uptime_result.returncode == 0 else "N/A",
|
||||
"disk_usage": df_result.stdout.strip() if df_result.returncode == 0 else "N/A",
|
||||
"memory_usage": free_result.stdout.strip() if free_result.returncode == 0 else "N/A"
|
||||
}
|
||||
|
||||
return jsonify(status_info), 200
|
||||
|
||||
except Exception as e:
|
||||
log_info_with_server(f"Error getting device status: {str(e)}")
|
||||
return jsonify({"error": f"Error getting status: {str(e)}"}), 500
|
||||
|
||||
@command_app.route('/auto_update', methods=['POST'])
|
||||
def auto_update_app():
|
||||
"""
|
||||
Trigger an immediate WMT client update check against the monitoring server.
|
||||
Delegates to _check_and_apply_update().
|
||||
"""
|
||||
result = _check_and_apply_update()
|
||||
if result.get('updated'):
|
||||
return jsonify({"status": "success", "message": result['message']}), 200
|
||||
elif result.get('error'):
|
||||
return jsonify({"error": result['message']}), 500
|
||||
else:
|
||||
return jsonify({"status": "no_update", "message": result['message']}), 200
|
||||
|
||||
@command_app.route('/update_config', methods=['POST'])
|
||||
def update_config_endpoint():
|
||||
"""
|
||||
Update configuration from Server_Monitorizare_v2.
|
||||
Accepts a JSON body with sections matching config.txt structure.
|
||||
Example body: {"chrome": {"chrome_url": "http://..."}}
|
||||
"""
|
||||
global APP_CONFIG
|
||||
|
||||
ALLOWED_SECTIONS = {
|
||||
"chrome": ["chrome_url", "chrome_local_url", "chrome_insecure_origin"],
|
||||
"card_api": ["base_url"],
|
||||
"server": ["log_url", "update_host", "update_user", "internet_check_host"],
|
||||
"device": ["name", "hostname", "ip"],
|
||||
}
|
||||
|
||||
try:
|
||||
data = request.json
|
||||
if not data:
|
||||
return jsonify({"error": "JSON body required"}), 400
|
||||
|
||||
config_path = "./data/config.txt"
|
||||
parser = configparser.ConfigParser()
|
||||
if os.path.exists(config_path):
|
||||
parser.read(config_path)
|
||||
|
||||
updated_keys = []
|
||||
for section, allowed_keys in ALLOWED_SECTIONS.items():
|
||||
if section in data and isinstance(data[section], dict):
|
||||
if not parser.has_section(section):
|
||||
parser.add_section(section)
|
||||
for key in allowed_keys:
|
||||
if key in data[section]:
|
||||
parser.set(section, key, str(data[section][key]))
|
||||
updated_keys.append(f"{section}.{key}")
|
||||
|
||||
os.makedirs("./data", exist_ok=True)
|
||||
with open(config_path, "w") as f:
|
||||
f.write("# WMT Application Configuration\n")
|
||||
f.write(f"# Last updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
|
||||
parser.write(f)
|
||||
|
||||
APP_CONFIG = load_config()
|
||||
log_info_with_server(f"Configuration updated via API: {updated_keys}")
|
||||
return jsonify({"status": "success", "updated_keys": updated_keys}), 200
|
||||
|
||||
except Exception as e:
|
||||
log_info_with_server(f"Error updating config: {str(e)}")
|
||||
return jsonify({"error": f"Failed to update configuration: {str(e)}"}), 500
|
||||
|
||||
@command_app.route('/reload_config', methods=['POST'])
|
||||
def reload_config_endpoint():
|
||||
"""
|
||||
Reload configuration from data/config.txt into memory without restarting.
|
||||
"""
|
||||
global APP_CONFIG
|
||||
try:
|
||||
APP_CONFIG = load_config()
|
||||
log_info_with_server("Configuration reloaded via API")
|
||||
return jsonify({
|
||||
"status": "success",
|
||||
"message": "Configuration reloaded",
|
||||
"chrome_url": APP_CONFIG.get("chrome_url"),
|
||||
"card_post_base_url": APP_CONFIG.get("card_post_base_url"),
|
||||
"server_log_url": APP_CONFIG.get("server_log_url"),
|
||||
"device_name": APP_CONFIG.get("work_place"),
|
||||
}), 200
|
||||
except Exception as e:
|
||||
return jsonify({"error": f"Failed to reload config: {str(e)}"}), 500
|
||||
|
||||
def start_command_server():
|
||||
"""
|
||||
Start the Flask server with enhanced port handling and fallback
|
||||
"""
|
||||
# Try different ports in order of preference
|
||||
preferred_ports = [
|
||||
int(os.environ.get('FLASK_PORT', 80)), # Use environment variable or default to 80
|
||||
80, # Standard HTTP port
|
||||
5000, # Flask default
|
||||
8080, # Alternative HTTP port
|
||||
3000 # Development port
|
||||
]
|
||||
|
||||
for port in preferred_ports:
|
||||
try:
|
||||
print(f"Attempting to start command server on port {port}...")
|
||||
|
||||
# Test if port is available
|
||||
import socket
|
||||
test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
test_socket.bind(('0.0.0.0', port))
|
||||
test_socket.close()
|
||||
|
||||
# Port is available, start Flask server
|
||||
print(f"Port {port} is available. Starting command server...")
|
||||
command_app.run(host='0.0.0.0', port=port, debug=False, use_reloader=False)
|
||||
return # Success, exit function
|
||||
|
||||
except PermissionError:
|
||||
print(f"✗ Permission denied for port {port}")
|
||||
if port == 80:
|
||||
print(" Hint: Port 80 requires root privileges or capabilities")
|
||||
print(" Try running: sudo setcap cap_net_bind_service=ep $(which python3)")
|
||||
continue
|
||||
except OSError as e:
|
||||
if "Address already in use" in str(e):
|
||||
print(f"✗ Port {port} is already in use")
|
||||
else:
|
||||
print(f"✗ Port {port} error: {e}")
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to start on port {port}: {e}")
|
||||
continue
|
||||
|
||||
# If we get here, all ports failed
|
||||
log_info_with_server("Error: Could not start command server on any port")
|
||||
print("✗ Could not start command server on any available port")
|
||||
|
||||
# Start command server in a separate process with enhanced error handling
|
||||
try:
|
||||
print("Initializing command server...")
|
||||
command_server_process = Process(target=start_command_server)
|
||||
command_server_process.daemon = True # Ensure it dies with main process
|
||||
command_server_process.start()
|
||||
|
||||
# Give the server a moment to start and check if it's running
|
||||
import time
|
||||
time.sleep(2)
|
||||
|
||||
if command_server_process.is_alive():
|
||||
port = int(os.environ.get('FLASK_PORT', 80))
|
||||
print(f"✓ Command server started successfully on port {port}")
|
||||
else:
|
||||
print("Warning: Command server process stopped unexpectedly")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not start command server: {e}")
|
||||
log_info_with_server(f"Command server startup error: {str(e)}")
|
||||
else:
|
||||
print("Warning: Flask not available - Command server disabled")
|
||||
# Call the function to delete old logs
|
||||
delete_old_logs()
|
||||
def config():
|
||||
@@ -1482,110 +1094,6 @@ if not CONFIGURATION_MODE:
|
||||
else:
|
||||
print("🔧 Configuration mode: Internet connectivity monitoring DISABLED")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# HTTP-based client auto-update helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _get_local_version():
|
||||
"""Return local app version as float by reading the first line of this file."""
|
||||
try:
|
||||
with open(os.path.abspath(__file__), 'r') as f:
|
||||
first_line = f.readline()
|
||||
m = re.search(r'version\s+(\d+\.?\d*)', first_line, re.IGNORECASE)
|
||||
if m:
|
||||
return float(m.group(1))
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _check_and_apply_update():
|
||||
"""
|
||||
Query the monitoring server for the latest WMT release.
|
||||
If a newer version is available, download the zip, back up app.py,
|
||||
extract into the WMT directory, and schedule a systemd service restart.
|
||||
Returns a dict: {updated, message, error}.
|
||||
"""
|
||||
server_host = APP_CONFIG.get("server_host", "")
|
||||
server_port = APP_CONFIG.get("server_port", "5000")
|
||||
if not server_host:
|
||||
return {"updated": False, "error": False, "message": "server_host not configured – skipping update check"}
|
||||
|
||||
base_url = f"http://{server_host}:{server_port}"
|
||||
local_version = _get_local_version()
|
||||
|
||||
try:
|
||||
resp = requests.get(f"{base_url}/api/wmt/client/version", timeout=10)
|
||||
if resp.status_code != 200:
|
||||
return {"updated": False, "error": True, "message": f"Version endpoint returned {resp.status_code}"}
|
||||
meta = resp.json()
|
||||
server_version = float(meta.get("version", 0))
|
||||
except Exception as e:
|
||||
return {"updated": False, "error": True, "message": f"Could not reach version endpoint: {e}"}
|
||||
|
||||
if local_version is not None and server_version <= local_version:
|
||||
return {"updated": False, "error": False, "message": f"Already on latest version {local_version}"}
|
||||
|
||||
log_info_with_server(f"WMT update available: local={local_version} server={server_version} – downloading …")
|
||||
|
||||
wmt_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
tmp_zip = "/tmp/wmt_update.zip"
|
||||
app_py = os.path.join(wmt_dir, "app.py")
|
||||
|
||||
try:
|
||||
# Download the zip
|
||||
dl = requests.get(f"{base_url}/api/wmt/client/download", timeout=60, stream=True)
|
||||
if dl.status_code != 200:
|
||||
return {"updated": False, "error": True, "message": f"Download endpoint returned {dl.status_code}"}
|
||||
with open(tmp_zip, 'wb') as f:
|
||||
for chunk in dl.iter_content(chunk_size=65536):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
|
||||
# Validate the zip
|
||||
import zipfile
|
||||
if not zipfile.is_zipfile(tmp_zip):
|
||||
return {"updated": False, "error": True, "message": "Downloaded file is not a valid zip"}
|
||||
|
||||
# Backup current app.py
|
||||
bak = f"{app_py}.bak.{local_version or 'old'}"
|
||||
try:
|
||||
import shutil
|
||||
shutil.copy2(app_py, bak)
|
||||
except Exception as e:
|
||||
log_info_with_server(f"Warning: could not back up app.py: {e}")
|
||||
|
||||
# Extract into WMT directory – skip anything inside data/ to preserve device config
|
||||
with zipfile.ZipFile(tmp_zip, 'r') as zf:
|
||||
for member in zf.infolist():
|
||||
# Normalise path separators and skip the data folder
|
||||
member_path = member.filename.replace('\\', '/')
|
||||
if member_path.startswith('data/') or member_path == 'data':
|
||||
continue
|
||||
zf.extract(member, wmt_dir)
|
||||
|
||||
log_info_with_server(f"WMT updated to version {server_version} – scheduling service restart")
|
||||
|
||||
# Schedule restart via systemd (non-blocking)
|
||||
subprocess.Popen(
|
||||
["bash", "-c", "sleep 3 && sudo systemctl restart wmt"],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
stdin=subprocess.DEVNULL, start_new_session=True
|
||||
)
|
||||
|
||||
return {"updated": True, "error": False,
|
||||
"message": f"Updated from {local_version} to {server_version}. Restarting service …"}
|
||||
|
||||
except Exception as e:
|
||||
log_info_with_server(f"WMT auto-update failed: {e}")
|
||||
return {"updated": False, "error": True, "message": str(e)}
|
||||
finally:
|
||||
try:
|
||||
os.remove(tmp_zip)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Periodic server config sync (background thread)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -1619,19 +1127,10 @@ def _periodic_config_sync():
|
||||
except Exception as e:
|
||||
print(f"Periodic config sync error: {e}")
|
||||
|
||||
# Check for WMT client update every cycle (non-fatal)
|
||||
try:
|
||||
_check_and_apply_update()
|
||||
except Exception as e:
|
||||
print(f"Periodic update check error: {e}")
|
||||
|
||||
if not CONFIGURATION_MODE:
|
||||
_sync_thread = threading.Thread(target=_periodic_config_sync, daemon=True, name="config-sync")
|
||||
_sync_thread.start()
|
||||
print("✅ Periodic server config sync started (every 5 min)")
|
||||
# Startup version check (non-blocking)
|
||||
_update_thread = threading.Thread(target=_check_and_apply_update, daemon=True, name="startup-update-check")
|
||||
_update_thread.start()
|
||||
else:
|
||||
print("🔧 Configuration mode: Periodic config sync DISABLED")
|
||||
|
||||
|
||||
+1843
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user