updated WMT client

This commit is contained in:
2026-06-24 15:22:12 +03:00
parent b17b9bb8da
commit 1ebbd79cc0
58 changed files with 1873 additions and 646 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ data/tag.txt
data/idmasa.txt
# Offline wheel cache (large, OS-specific)
Files/reposytory/
Files/repository/
Files/system_packages/
# Misc
@@ -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-----
MIIFljCCA36gAwIBAgIQWDrwWap0fIVAtVmbM/HtJzANBgkqhkiG9w0BAQsFADBK
MRUwEwYKCZImiZPyLGQBGRYFSW50cmExFzAVBgoJkiaJk/IsZAEZFgdIQVJUSU5H
MRgwFgYDVQQDEw9IQVJUSU5HIFJvb3QgQ0EwIBcNMDUwMzMwMDk0NjI0WhgPMjA2
MjEyMTIxMjE5MTBaMEoxFTATBgoJkiaJk/IsZAEZFgVJbnRyYTEXMBUGCgmSJomT
8ixkARkWB0hBUlRJTkcxGDAWBgNVBAMTD0hBUlRJTkcgUm9vdCBDQTCCAiIwDQYJ
KoZIhvcNAQEBBQADggIPADCCAgoCggIBANd3pkQAWf4/WV3220uv4O4utXMmBeUf
V90oVJMf07GQUaScfc+vUzgK80fzcDjmA4Hde2Ag+f8Dj/Q+ZLAukIVms6qmfOUC
p5dIkMca5ept5WRoOTu+opxylbC1cfEvesbPV7x82QTnIfzQGNTppH6SlRttPk2M
aYaKjOCFhvmnQa6IHqYOTHyk8RrLnBdXe378SEseNn9bJmySnW9agLWwL2L4d1Lz
DuXAC6EVAxkkTJKoQYu0WQaKLh7cdzDEuI4k8PiFatQUFRiQT6tbc6d7D1vE7+DT
SjU8YizU3m9Bs7kkHsSQ85EPQ1nZ6aQz9VC41Yjh3lxst3LscWj0unvur8rqeQow
UOVwFKERB+BB1jSCcHV136Ij0CzLRXSIYcMFAsHTLHscc5g6nrYfDPRU/RlhuSkV
9mpeKkv8XYa0N3yCVy3Fr0NSXNehaONs26v1tMheojsvOFDC93MCN3NuYHcPQRt7
MsLf1tuKTPUiagmCJLRbMaUCF5Prq2YI9kvE9crt3DvW75RFN2yO52Cjn4aLMacv
T+tmjbSQWzMZVOHTlEREsLcniOo4gGH1D/ijrJl1t/a8WiTxpv/Ub0S56NP1uZ1s
nukaQ5w/WeZk2mAe76pDUltGuZVk0opYsyVdbj30XlzSzNJOgAJ9UabkFjwwyIDx
AMHdwPrrPLzNAgMBAAGjdjB0MAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/
MB0GA1UdDgQWBBRnrZmOC5sfWVP54OlheaUUhrxciTAQBgkrBgEEAYI3FQEEAwIB
AzAjBgkrBgEEAYI3FQIEFgQU5EC6gNFM7jw9fXMRJa1eANrFWSYwDQYJKoZIhvcN
AQELBQADggIBAAFMdOlGwtolH9fqtPy6AvkmSVeQ1S5bcDYCfXFTX0MyctDZSAeq
qsvVoYsxh34eePMzYpTRjCFJ9fFwe3zFO34eDkInxbdLUTMxBhc1wf8Q57UTYB3v
rKnB0DWIfrvVV989iVU1Cb/WzixcWf5tQayifEf2mFQvPhAPATu5IxXhyVPGkuoO
3c1JRZvfxKXEq8lzwZz20yUMaWpu+uM8mFHQ1XKMz3DvKmvJDdvRuvjU5VfyED6W
a5s0Qv9vtpekb05Bw5f6V6HJabi/Pws3tImZdQi7XRsDvQSjOvGnsyp73xsfzkj4
JCsvXuf8M6k7rznPsE3iUm5rBLbTlb1WfTScs4PDIAxQK9o8QglQ/eD07VnHsgg9
fBPWXu7NFrT+y2QDbvDE/Mjq03l0KiAATY1IsD2kE7upKAyKiS/jF+dDyBEMqj6T
5yRVbyoI6vS4wINx21I7XsC3EzfaUs13QgGAIIgJKVYrGJwEq9ARBpqmKpyKAJwt
0UkTqGZjBYTOW8/hUNpJd07M4087PGVEUNbPBp6yrddpuVllGR3n8p296KcR6m+d
cSkKAdreXI0Cpb+KmJpOuQj1rOTRxYAnYEE4zR2nd2JptjDLcxOLgMP0naZ6g8qS
LarqnBFK6cVlpeNNg+A7Ns96PBSJoYlBd4EYqfD20he1mZVnCrmjOFZh
-----END CERTIFICATE-----
@@ -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.
+19 -520
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff