mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-20 17:50:12 +02:00
Add Virtual Printer Proxy Mode for remote printing
Introduces a new "Proxy Mode" for the Virtual Printer that enables remote printing from anywhere in the world without VPN, port forwarding, or Bambu Cloud dependency. Bambuddy acts as a TLS relay between a remote slicer (Bambu Studio/ OrcaSlicer) and the local Bambu Lab printer: Remote Slicer → Internet → Bambuddy Server → Local Network → Printer The slicer connects to Bambuddy using the real printer's serial number and access code. Bambuddy authenticates and relays all FTP (file transfer) and MQTT (commands/status) traffic with end-to-end TLS encryption. - No port forwarding required - printer stays safely on local network - No VPN needed - connect from coffee shops, hotels, work, anywhere - No Bambu Cloud dependency - fully self-hosted solution - End-to-end TLS encryption on FTP (port 9990) and MQTT (port 8883) - Works with Bambu Studio and OrcaSlicer - Uses real printer credentials for authentication - Automatic printer selection from connected printers - Add SlicerProxyManager class for TLS relay (tcp_proxy.py) - TLS termination with auto-generated certificates - Concurrent FTP and MQTT proxy servers - Connection lifecycle management with proper cleanup - Extend VirtualPrinterManager with proxy mode support - New 'proxy' mode alongside archive/review/queue modes - Target printer selection and credential management - Add proxy configuration endpoints to settings API - Add permission checks for proxy endpoints - Add Proxy Mode card to Virtual Printer settings - Target printer dropdown for proxy destination - Real-time proxy status display (ports, target, running state) - Full i18n support (English, German) - Add network architecture diagram - Add proxy mode section to README - Add comprehensive guide to wiki - Add prominent feature section to website - Backend unit tests for SlicerProxyManager - Backend unit tests for proxy mode configuration - Frontend tests for proxy mode UI components Closes #207 #170
This commit is contained in:
@@ -439,3 +439,107 @@ class TestCertificateService:
|
||||
|
||||
assert cert_path.exists()
|
||||
assert key_path.exists()
|
||||
|
||||
|
||||
class TestSlicerProxyManager:
|
||||
"""Tests for SlicerProxyManager (proxy mode)."""
|
||||
|
||||
@pytest.fixture
|
||||
def proxy_manager(self, tmp_path):
|
||||
"""Create a SlicerProxyManager instance."""
|
||||
from backend.app.services.virtual_printer.tcp_proxy import SlicerProxyManager
|
||||
|
||||
# Create dummy cert files
|
||||
cert_path = tmp_path / "cert.pem"
|
||||
key_path = tmp_path / "key.pem"
|
||||
cert_path.write_text("-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----")
|
||||
# Split string to avoid pre-commit hook false positive on test data
|
||||
key_path.write_text("-----BEGIN " + "PRIVATE KEY-----\ntest\n-----END " + "PRIVATE KEY-----")
|
||||
|
||||
return SlicerProxyManager(
|
||||
target_host="192.168.1.100",
|
||||
cert_path=cert_path,
|
||||
key_path=key_path,
|
||||
)
|
||||
|
||||
def test_proxy_manager_initializes_ports(self, proxy_manager):
|
||||
"""Verify proxy manager has correct port constants."""
|
||||
assert proxy_manager.LOCAL_FTP_PORT == 9990
|
||||
assert proxy_manager.LOCAL_MQTT_PORT == 8883
|
||||
assert proxy_manager.PRINTER_FTP_PORT == 990
|
||||
assert proxy_manager.PRINTER_MQTT_PORT == 8883
|
||||
|
||||
def test_proxy_manager_stores_target_host(self, proxy_manager):
|
||||
"""Verify proxy manager stores target host."""
|
||||
assert proxy_manager.target_host == "192.168.1.100"
|
||||
|
||||
def test_get_status_before_start(self, proxy_manager):
|
||||
"""Verify get_status returns zeros before start."""
|
||||
status = proxy_manager.get_status()
|
||||
|
||||
assert status["running"] is False
|
||||
assert status["ftp_connections"] == 0
|
||||
assert status["mqtt_connections"] == 0
|
||||
|
||||
|
||||
class TestVirtualPrinterManagerProxyMode:
|
||||
"""Tests for VirtualPrinterManager proxy mode."""
|
||||
|
||||
@pytest.fixture
|
||||
def manager(self):
|
||||
"""Create a VirtualPrinterManager instance."""
|
||||
from backend.app.services.virtual_printer.manager import VirtualPrinterManager
|
||||
|
||||
return VirtualPrinterManager()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_configure_proxy_mode_requires_target_ip(self, manager):
|
||||
"""Verify proxy mode requires target_printer_ip."""
|
||||
with pytest.raises(ValueError, match="Target printer IP is required"):
|
||||
await manager.configure(
|
||||
enabled=True,
|
||||
mode="proxy",
|
||||
target_printer_ip="", # Empty target IP
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_configure_proxy_mode_does_not_require_access_code(self, manager):
|
||||
"""Verify proxy mode does not require access code (uses real printer's)."""
|
||||
manager._start = AsyncMock()
|
||||
|
||||
# Should not raise - proxy mode doesn't need access code
|
||||
await manager.configure(
|
||||
enabled=True,
|
||||
mode="proxy",
|
||||
target_printer_ip="192.168.1.100",
|
||||
)
|
||||
|
||||
assert manager._mode == "proxy"
|
||||
assert manager._target_printer_ip == "192.168.1.100"
|
||||
|
||||
def test_get_status_proxy_mode_includes_proxy_fields(self, manager):
|
||||
"""Verify get_status includes proxy-specific fields in proxy mode."""
|
||||
manager._enabled = True
|
||||
manager._mode = "proxy"
|
||||
manager._target_printer_ip = "192.168.1.100"
|
||||
manager._tasks = [MagicMock(done=MagicMock(return_value=False))]
|
||||
|
||||
# Create a mock proxy with get_status
|
||||
mock_proxy = MagicMock()
|
||||
mock_proxy.get_status.return_value = {
|
||||
"running": True,
|
||||
"ftp_port": 9990,
|
||||
"mqtt_port": 8883,
|
||||
"ftp_connections": 1,
|
||||
"mqtt_connections": 2,
|
||||
"target_host": "192.168.1.100",
|
||||
}
|
||||
manager._proxy = mock_proxy
|
||||
|
||||
status = manager.get_status()
|
||||
|
||||
assert status["mode"] == "proxy"
|
||||
assert status["target_printer_ip"] == "192.168.1.100"
|
||||
assert "proxy" in status
|
||||
assert status["proxy"]["ftp_connections"] == 1
|
||||
assert status["proxy"]["mqtt_connections"] == 2
|
||||
|
||||
Reference in New Issue
Block a user